M54: harden operations and demo resilience
This commit is contained in:
@@ -21,7 +21,13 @@ function partsAt(value: Date): Record<string, string> {
|
||||
|
||||
export function toBrusselsDateTimeLocal(value: Date): string {
|
||||
const parts = partsAt(value);
|
||||
return `${parts.year}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}`;
|
||||
return `${toBrusselsDate(value)}T${parts.hour}:${parts.minute}`;
|
||||
}
|
||||
|
||||
/** Render an instant as the Fleet Ops Europe/Brussels calendar date (YYYY-MM-DD). */
|
||||
export function toBrusselsDate(value: Date): string {
|
||||
const parts = partsAt(value);
|
||||
return `${parts.year}-${parts.month}-${parts.day}`;
|
||||
}
|
||||
|
||||
export function brusselsDateTimeFromNow(hours: number): string {
|
||||
@@ -42,7 +48,21 @@ export function brusselsLocalToIso(value: string): string {
|
||||
const represented = Date.UTC(+parts.year, +parts.month - 1, +parts.day, +parts.hour, +parts.minute);
|
||||
candidate += wallClockUtc - represented;
|
||||
}
|
||||
return new Date(candidate).toISOString();
|
||||
const result = new Date(candidate);
|
||||
// Date.UTC normalises impossible calendar values and Brussels' spring transition
|
||||
// contains a wall-clock hour that does not exist. Never silently shift either one.
|
||||
if (toBrusselsDateTimeLocal(result) !== value) {
|
||||
throw new Error("Invalid or non-existent Brussels date-time");
|
||||
}
|
||||
return result.toISOString();
|
||||
}
|
||||
|
||||
export function tryBrusselsLocalToIso(value: string): string | null {
|
||||
try {
|
||||
return brusselsLocalToIso(value);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Start of the given Brussels calendar day (YYYY-MM-DD) as a UTC ISO instant. */
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
"accessHeading": "Choose how to start",
|
||||
"accessIntro": "No password needed. Each role opens a scoped synthetic environment — all workflows and controls are really implemented.",
|
||||
"startGuidedDemo": "Start guided demo",
|
||||
"guidedDemoChecking": "Preparing the guided demo…",
|
||||
"guidedDemoManifestUnavailable": "The guided-demo information could not be loaded.",
|
||||
"guidedDemoNotReady": "The guided demo is not ready to start right now.",
|
||||
"guidedDemoRetry": "Reload demo information",
|
||||
"startRecruiterTour": "See the highlights in 90 seconds",
|
||||
"exploreAsOperationsManager": "Explore as Operations Manager",
|
||||
"exploreAsOperationsManagerDetail": "Full overview, quality resolution and retries",
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
"loadingVehicles": "Checking availability…",
|
||||
"chooseVehicle": "Select a vehicle",
|
||||
"noVehicles": "No vehicle available for this window",
|
||||
"requirementsComplete": "Driving licence and rental requirements have been checked",
|
||||
"invalidLocalTime": "Choose a valid Europe/Brussels time. The skipped hour during the spring clock change does not exist.",
|
||||
"endAfterStart": "The booking end must be after its start.",
|
||||
"cancel": "Cancel",
|
||||
"save": "Create booking",
|
||||
"saving": "Saving booking…",
|
||||
@@ -118,6 +119,7 @@
|
||||
"confirmReschedule": "Save new window",
|
||||
"rescheduling": "Saving window…",
|
||||
"rescheduleFailed": "The reservation could not be rescheduled.",
|
||||
"invalidScheduleWindow": "Choose valid Europe/Brussels times and make sure the end is after the start.",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"cancelAction": "Cancel booking",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"expectedOutcome": "Expected outcome",
|
||||
"goToStep": "Go to this step",
|
||||
"next": "Next",
|
||||
"finish": "Finish demo",
|
||||
"close": "Close",
|
||||
"restart": "Prepare demo again",
|
||||
"restarting": "Restarting…",
|
||||
@@ -107,6 +108,7 @@
|
||||
"title": "Try a demonstration scenario",
|
||||
"description": "Five focused scenarios that always use the same fixed bookings, customers and vehicles — always re-findable after a reset.",
|
||||
"loading": "Loading scenarios…",
|
||||
"unavailable": "The demo scenarios could not be loaded. Please try again.",
|
||||
"ready": "Ready for demo",
|
||||
"notReady": "Not available",
|
||||
"duration": "Duration",
|
||||
@@ -164,6 +166,7 @@
|
||||
"title": "What {{productName}} is and isn't",
|
||||
"description": "{{orgName}} is a fictional rental organisation that makes this demo tangible — not a real company.",
|
||||
"loading": "Loading demo information…",
|
||||
"unavailable": "The demo information could not be loaded. Please try again.",
|
||||
"ctaTitle": "Choose how much time you have",
|
||||
"ctaBody": "See the three strongest engineering moments in 90 seconds, or take the complete operational tour.",
|
||||
"ctaButton": "Start full demo",
|
||||
@@ -185,6 +188,48 @@
|
||||
"architectureDatabase": "PostgreSQL + audit trail",
|
||||
"architectureOutbox": "Reliable outbox",
|
||||
"architectureExternal": "External services: n8n, RAGcore and MCP Hub",
|
||||
"architectureZonesLabel": "Architecture responsibility boundaries",
|
||||
"architectureZoneIntent": "User intent",
|
||||
"architectureZoneTransaction": "Local transaction",
|
||||
"architectureZoneEdge": "Recoverable system edge",
|
||||
"architectureFlowLabel": "Interactive system flow from interface to external services",
|
||||
"architectureCommitBoundary": "After commit",
|
||||
"architectureInteractionHint": "Select a step to inspect its operational guarantee and control points.",
|
||||
"architectureSelectedStep": "Step {{current}} of {{total}}",
|
||||
"architectureBoundaryLabel": "Responsibility",
|
||||
"architectureEvidenceLabel": "Control points",
|
||||
"architectureDetails": {
|
||||
"Frontend": {
|
||||
"summary": "Makes role and intent explicit",
|
||||
"body": "Accessible forms collect validated input and show the impact before confirmation. The interface never decides a domain status on its own.",
|
||||
"proofOne": "Role-based route guard",
|
||||
"proofTwo": "Preview before confirmation"
|
||||
},
|
||||
"Api": {
|
||||
"summary": "Validates every operational rule",
|
||||
"body": "FastAPI enforces statuses, invariants and resolution rules at the API boundary, independently of what the browser submits.",
|
||||
"proofOne": "Pydantic validation",
|
||||
"proofTwo": "Server-side domain decision"
|
||||
},
|
||||
"Database": {
|
||||
"summary": "Commits data and audit atomically",
|
||||
"body": "PostgreSQL stores the operational change and its audit evidence in the same transaction, preventing a partial state change.",
|
||||
"proofOne": "UUID + public reference",
|
||||
"proofTwo": "UTC + unbroken audit trail"
|
||||
},
|
||||
"Outbox": {
|
||||
"summary": "Records follow-up work durably",
|
||||
"body": "The outbox record commits with the operation. A worker only delivers afterwards, idempotently and with bounded retries, to the orchestration layer.",
|
||||
"proofOne": "Post-commit delivery",
|
||||
"proofTwo": "Idempotency + bounded retries"
|
||||
},
|
||||
"External": {
|
||||
"summary": "Degrades without local data loss",
|
||||
"body": "n8n, RAGcore and MCP Hub have timeouts and visible health state. Failure remains recoverable and AI never answers without sufficient source evidence.",
|
||||
"proofOne": "Health state + timeouts",
|
||||
"proofTwo": "No answer without evidence"
|
||||
}
|
||||
},
|
||||
"verificationTitle": "Built to be verified",
|
||||
"verificationBody": "Domain rules, API contracts, degraded modes and the complete demo are tested automatically. The repository contains the exact acceptance commands and evidence bundle.",
|
||||
"problemTitle": "The fictional problem",
|
||||
|
||||
@@ -38,6 +38,11 @@
|
||||
"explanation": "This issue has already been resolved, deferred or rejected.",
|
||||
"nextStep": "Refresh the page to see its current state."
|
||||
},
|
||||
"ISSUE_CHANGED": {
|
||||
"title": "Issue evidence has changed",
|
||||
"explanation": "New evidence was recorded while this correction was being prepared.",
|
||||
"nextStep": "Refresh the issue and review the current evidence before deciding again."
|
||||
},
|
||||
"BOOKING_NOT_ACTIVE": {
|
||||
"title": "Booking is not active",
|
||||
"explanation": "Only a reserved or active booking can be used for this action."
|
||||
@@ -88,6 +93,18 @@
|
||||
"title": "Invalid event reference",
|
||||
"explanation": "This automation event reference is not valid."
|
||||
},
|
||||
"INVALID_EVENT_CORRELATION": {
|
||||
"title": "Invalid correlation reference",
|
||||
"explanation": "The automation callback does not contain a valid correlation reference."
|
||||
},
|
||||
"CALLBACK_EVENT_MISMATCH": {
|
||||
"title": "Callback event does not match",
|
||||
"explanation": "The automation callback refers to a different event than the event being updated."
|
||||
},
|
||||
"CALLBACK_CORRELATION_MISMATCH": {
|
||||
"title": "Callback trace does not match",
|
||||
"explanation": "The automation callback does not match the correlation reference stored for this event."
|
||||
},
|
||||
"INVALID_IDEMPOTENCY_KEY": {
|
||||
"title": "Request could not be repeated safely",
|
||||
"explanation": "This request's tracking key is not valid.",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"users": { "eyebrow": "Administration / Access", "title": "Users", "description": "Manage operational access and roles. Every change is audited.", "addTitle": "Add user", "name": "Name", "email": "Email", "role": "Role", "password": "Temporary password", "status": "Status", "action": "Action", "add": "Add user", "saving": "Saving…", "loading": "Loading users…", "active": "active", "inactive": "inactive", "activate": "Activate", "deactivate": "Deactivate", "edit": "Edit", "editTitle": "Edit user {{ref}}", "newPassword": "New password", "passwordUnchanged": "Leave empty to keep unchanged", "saveChanges": "Save changes", "cancel": "Cancel", "createFailed": "The user could not be created.", "updateFailed": "The user could not be updated." },
|
||||
"users": { "eyebrow": "Administration / Access", "title": "Users", "description": "Manage operational access and roles. Every change is audited.", "managerOnly": "User administration is available to Operations Managers only.", "addTitle": "Add user", "name": "Name", "email": "Email", "role": "Role", "password": "Temporary password", "status": "Status", "action": "Action", "add": "Add user", "saving": "Saving…", "loading": "Loading users…", "active": "active", "inactive": "inactive", "activate": "Activate", "deactivate": "Deactivate", "edit": "Edit", "editTitle": "Edit user {{ref}}", "newPassword": "New password", "passwordUnchanged": "Leave empty to keep unchanged", "saveChanges": "Save changes", "cancel": "Cancel", "createFailed": "The user could not be created.", "updateFailed": "The user could not be updated." },
|
||||
"roles": { "operations_manager": "Operations Manager", "rental_employee": "Rental employee" }
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"selected": "{{count}} selected",
|
||||
"assignTo": "Assign to",
|
||||
"dueAt": "Due date",
|
||||
"invalidDueAt": "Choose a valid Europe/Brussels date and time.",
|
||||
"bulkApply": "Update work queue",
|
||||
"bulkSaving": "Updating…",
|
||||
"bulkFailed": "The work queue could not be updated.",
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
"accessHeading": "Choisissez comment démarrer",
|
||||
"accessIntro": "Aucun mot de passe requis. Chaque rôle ouvre un environnement synthétique délimité — tous les workflows et contrôles sont réellement implémentés.",
|
||||
"startGuidedDemo": "Démarrer la démo guidée",
|
||||
"guidedDemoChecking": "Préparation de la démo guidée…",
|
||||
"guidedDemoManifestUnavailable": "Les informations de la démo guidée n’ont pas pu être chargées.",
|
||||
"guidedDemoNotReady": "La démo guidée n’est pas prête à démarrer pour le moment.",
|
||||
"guidedDemoRetry": "Recharger les informations de démo",
|
||||
"startRecruiterTour": "Voir les points forts en 90 secondes",
|
||||
"exploreAsOperationsManager": "Explorer en tant que Responsable des opérations",
|
||||
"exploreAsOperationsManagerDetail": "Vue d'ensemble complète, résolution qualité et nouvelles tentatives",
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
"loadingVehicles": "Vérification des disponibilités…",
|
||||
"chooseVehicle": "Sélectionnez un véhicule",
|
||||
"noVehicles": "Aucun véhicule disponible pour cette période",
|
||||
"requirementsComplete": "Le permis de conduire et les exigences de location ont été vérifiés",
|
||||
"invalidLocalTime": "Choisissez une heure Europe/Brussels valide. L’heure sautée lors du passage à l’heure d’été n’existe pas.",
|
||||
"endAfterStart": "La fin de la réservation doit être postérieure à son début.",
|
||||
"cancel": "Annuler",
|
||||
"save": "Créer la réservation",
|
||||
"saving": "Enregistrement…",
|
||||
@@ -118,6 +119,7 @@
|
||||
"confirmReschedule": "Enregistrer la nouvelle période",
|
||||
"rescheduling": "Enregistrement…",
|
||||
"rescheduleFailed": "La réservation n’a pas pu être replanifiée.",
|
||||
"invalidScheduleWindow": "Choisissez des heures Europe/Brussels valides et assurez-vous que la fin suit le début.",
|
||||
"yes": "Oui",
|
||||
"no": "Non",
|
||||
"cancelAction": "Annuler la réservation",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"expectedOutcome": "Résultat attendu",
|
||||
"goToStep": "Aller à cette étape",
|
||||
"next": "Suivant",
|
||||
"finish": "Terminer la démo",
|
||||
"close": "Fermer",
|
||||
"restart": "Préparer à nouveau la démo",
|
||||
"restarting": "Réinitialisation…",
|
||||
@@ -107,6 +108,7 @@
|
||||
"title": "Essayer un scénario de démonstration",
|
||||
"description": "Cinq scénarios ciblés qui utilisent toujours les mêmes réservations, clients et véhicules fixes — toujours retrouvables après une réinitialisation.",
|
||||
"loading": "Chargement des scénarios…",
|
||||
"unavailable": "Les scénarios de démonstration n'ont pas pu être chargés. Veuillez réessayer.",
|
||||
"ready": "Prêt pour la démo",
|
||||
"notReady": "Non disponible",
|
||||
"duration": "Durée",
|
||||
@@ -164,6 +166,7 @@
|
||||
"title": "Ce que {{productName}} est et n'est pas",
|
||||
"description": "{{orgName}} est une organisation de location fictive qui rend cette démo concrète — pas une véritable entreprise.",
|
||||
"loading": "Chargement des informations de démo…",
|
||||
"unavailable": "Les informations de démonstration n'ont pas pu être chargées. Veuillez réessayer.",
|
||||
"ctaTitle": "Choisissez le temps dont vous disposez",
|
||||
"ctaBody": "Découvrez les trois moments d'ingénierie les plus forts en 90 secondes, ou suivez la démo opérationnelle complète.",
|
||||
"ctaButton": "Démarrer la démo complète",
|
||||
@@ -185,6 +188,48 @@
|
||||
"architectureDatabase": "PostgreSQL + piste d'audit",
|
||||
"architectureOutbox": "Outbox fiable",
|
||||
"architectureExternal": "Services externes : n8n, RAGcore et MCP Hub",
|
||||
"architectureZonesLabel": "Limites de responsabilité de l'architecture",
|
||||
"architectureZoneIntent": "Intention utilisateur",
|
||||
"architectureZoneTransaction": "Transaction locale",
|
||||
"architectureZoneEdge": "Périphérie système récupérable",
|
||||
"architectureFlowLabel": "Flux système interactif de l'interface aux services externes",
|
||||
"architectureCommitBoundary": "Après validation",
|
||||
"architectureInteractionHint": "Sélectionnez une étape pour examiner sa garantie opérationnelle et ses points de contrôle.",
|
||||
"architectureSelectedStep": "Étape {{current}} sur {{total}}",
|
||||
"architectureBoundaryLabel": "Responsabilité",
|
||||
"architectureEvidenceLabel": "Points de contrôle",
|
||||
"architectureDetails": {
|
||||
"Frontend": {
|
||||
"summary": "Rend le rôle et l'intention explicites",
|
||||
"body": "Des formulaires accessibles recueillent les données validées et montrent l'impact avant confirmation. L'interface ne décide jamais seule d'un statut métier.",
|
||||
"proofOne": "Garde de route par rôle",
|
||||
"proofTwo": "Aperçu avant confirmation"
|
||||
},
|
||||
"Api": {
|
||||
"summary": "Valide chaque règle opérationnelle",
|
||||
"body": "FastAPI applique les statuts, invariants et règles de résolution à la frontière de l'API, indépendamment de ce que le navigateur envoie.",
|
||||
"proofOne": "Validation Pydantic",
|
||||
"proofTwo": "Décision métier côté serveur"
|
||||
},
|
||||
"Database": {
|
||||
"summary": "Valide les données et l'audit atomiquement",
|
||||
"body": "PostgreSQL enregistre la modification opérationnelle et sa preuve d'audit dans la même transaction, empêchant tout changement d'état partiel.",
|
||||
"proofOne": "UUID + référence publique",
|
||||
"proofTwo": "UTC + piste d'audit continue"
|
||||
},
|
||||
"Outbox": {
|
||||
"summary": "Enregistre durablement le suivi",
|
||||
"body": "L'enregistrement outbox est validé avec l'opération. Un worker ne livre qu'ensuite, de façon idempotente et avec des tentatives limitées, vers l'orchestration.",
|
||||
"proofOne": "Livraison après validation",
|
||||
"proofTwo": "Idempotence + tentatives limitées"
|
||||
},
|
||||
"External": {
|
||||
"summary": "Se dégrade sans perte locale",
|
||||
"body": "n8n, RAGcore et MCP Hub disposent de délais d'attente et d'un état de santé visible. Une panne reste récupérable et l'IA ne répond jamais sans preuves suffisantes.",
|
||||
"proofOne": "État de santé + délais",
|
||||
"proofTwo": "Aucune réponse sans preuve"
|
||||
}
|
||||
},
|
||||
"verificationTitle": "Construit pour être vérifiable",
|
||||
"verificationBody": "Les règles métier, contrats d'API, modes dégradés et la démo complète sont testés automatiquement. Le dépôt contient les commandes d'acceptation exactes et le dossier de preuves.",
|
||||
"problemTitle": "Le problème fictif",
|
||||
|
||||
@@ -38,6 +38,11 @@
|
||||
"explanation": "Ce problème a déjà été résolu, reporté ou rejeté.",
|
||||
"nextStep": "Actualisez la page pour voir son état actuel."
|
||||
},
|
||||
"ISSUE_CHANGED": {
|
||||
"title": "Les éléments de preuve ont changé",
|
||||
"explanation": "De nouveaux éléments ont été enregistrés pendant la préparation de cette correction.",
|
||||
"nextStep": "Actualisez le problème et réexaminez les éléments actuels avant de décider."
|
||||
},
|
||||
"BOOKING_NOT_ACTIVE": {
|
||||
"title": "La réservation n'est pas active",
|
||||
"explanation": "Seule une réservation réservée ou active peut être utilisée pour cette action."
|
||||
@@ -88,6 +93,18 @@
|
||||
"title": "Référence d'événement non valide",
|
||||
"explanation": "Cette référence d'événement d'automatisation n'est pas valide."
|
||||
},
|
||||
"INVALID_EVENT_CORRELATION": {
|
||||
"title": "Référence de corrélation non valide",
|
||||
"explanation": "Le rappel d'automatisation ne contient pas de référence de corrélation valide."
|
||||
},
|
||||
"CALLBACK_EVENT_MISMATCH": {
|
||||
"title": "Le rappel concerne un autre événement",
|
||||
"explanation": "Le rappel d'automatisation fait référence à un événement différent de celui qui est mis à jour."
|
||||
},
|
||||
"CALLBACK_CORRELATION_MISMATCH": {
|
||||
"title": "La trace du rappel ne correspond pas",
|
||||
"explanation": "Le rappel d'automatisation ne correspond pas à la référence de corrélation enregistrée pour cet événement."
|
||||
},
|
||||
"INVALID_IDEMPOTENCY_KEY": {
|
||||
"title": "La demande n'a pas pu être répétée en toute sécurité",
|
||||
"explanation": "La clé de suivi de cette demande n'est pas valide.",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"users": { "eyebrow": "Administration / Accès", "title": "Utilisateurs", "description": "Gérez les accès opérationnels et les rôles. Chaque modification est auditée.", "addTitle": "Ajouter un utilisateur", "name": "Nom", "email": "E-mail", "role": "Rôle", "password": "Mot de passe temporaire", "status": "Statut", "action": "Action", "add": "Ajouter", "saving": "Enregistrement…", "loading": "Chargement des utilisateurs…", "active": "actif", "inactive": "inactif", "activate": "Activer", "deactivate": "Désactiver", "edit": "Modifier", "editTitle": "Modifier l’utilisateur {{ref}}", "newPassword": "Nouveau mot de passe", "passwordUnchanged": "Laisser vide pour ne pas modifier", "saveChanges": "Enregistrer les modifications", "cancel": "Annuler", "createFailed": "L'utilisateur n'a pas pu être créé.", "updateFailed": "L'utilisateur n'a pas pu être mis à jour." },
|
||||
"users": { "eyebrow": "Administration / Accès", "title": "Utilisateurs", "description": "Gérez les accès opérationnels et les rôles. Chaque modification est auditée.", "managerOnly": "L’administration des utilisateurs est réservée aux Responsables des opérations.", "addTitle": "Ajouter un utilisateur", "name": "Nom", "email": "E-mail", "role": "Rôle", "password": "Mot de passe temporaire", "status": "Statut", "action": "Action", "add": "Ajouter", "saving": "Enregistrement…", "loading": "Chargement des utilisateurs…", "active": "actif", "inactive": "inactif", "activate": "Activer", "deactivate": "Désactiver", "edit": "Modifier", "editTitle": "Modifier l’utilisateur {{ref}}", "newPassword": "Nouveau mot de passe", "passwordUnchanged": "Laisser vide pour ne pas modifier", "saveChanges": "Enregistrer les modifications", "cancel": "Annuler", "createFailed": "L'utilisateur n'a pas pu être créé.", "updateFailed": "L'utilisateur n'a pas pu être mis à jour." },
|
||||
"roles": { "operations_manager": "Responsable des opérations", "rental_employee": "Employé de location" }
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"selected": "{{count}} sélectionné(s)",
|
||||
"assignTo": "Attribuer à",
|
||||
"dueAt": "Échéance",
|
||||
"invalidDueAt": "Choisissez une date et une heure Europe/Brussels valides.",
|
||||
"bulkApply": "Mettre à jour la file",
|
||||
"bulkSaving": "Mise à jour…",
|
||||
"bulkFailed": "La file de travail n’a pas pu être mise à jour.",
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
"accessHeading": "Kies hoe je wil starten",
|
||||
"accessIntro": "Geen wachtwoord nodig. Elke rol opent een afgebakende, synthetische omgeving — alle workflows en controles zijn echt geïmplementeerd.",
|
||||
"startGuidedDemo": "Start begeleide demo",
|
||||
"guidedDemoChecking": "Begeleide demo voorbereiden…",
|
||||
"guidedDemoManifestUnavailable": "De begeleide demo-informatie kon niet geladen worden.",
|
||||
"guidedDemoNotReady": "De begeleide demo is momenteel niet klaar om te starten.",
|
||||
"guidedDemoRetry": "Demo-informatie opnieuw laden",
|
||||
"startRecruiterTour": "Bekijk de highlights in 90 seconden",
|
||||
"exploreAsOperationsManager": "Verken als Operationsmanager",
|
||||
"exploreAsOperationsManagerDetail": "Volledig overzicht, kwaliteitsoplossing en herpogingen",
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
"loadingVehicles": "Beschikbaarheid controleren…",
|
||||
"chooseVehicle": "Selecteer een voertuig",
|
||||
"noVehicles": "Geen beschikbaar voertuig in deze periode",
|
||||
"requirementsComplete": "Rijbewijs- en huurvereisten zijn gecontroleerd",
|
||||
"invalidLocalTime": "Kies een geldig tijdstip in Europe/Brussels. Het overgeslagen uur bij de omschakeling naar zomertijd bestaat niet.",
|
||||
"endAfterStart": "Het einde moet na de start van de boeking vallen.",
|
||||
"cancel": "Annuleren",
|
||||
"save": "Boeking aanmaken",
|
||||
"saving": "Boeking opslaan…",
|
||||
@@ -118,6 +119,7 @@
|
||||
"confirmReschedule": "Nieuwe periode opslaan",
|
||||
"rescheduling": "Periode opslaan…",
|
||||
"rescheduleFailed": "De reservatie kon niet worden verplaatst.",
|
||||
"invalidScheduleWindow": "Kies geldige tijdstippen in Europe/Brussels en zorg dat het einde na de start valt.",
|
||||
"yes": "Ja",
|
||||
"no": "Nee",
|
||||
"cancelAction": "Boeking annuleren",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"expectedOutcome": "Verwacht resultaat",
|
||||
"goToStep": "Ga naar deze stap",
|
||||
"next": "Volgende",
|
||||
"finish": "Demo afronden",
|
||||
"close": "Sluiten",
|
||||
"restart": "Demo opnieuw voorbereiden",
|
||||
"restarting": "Bezig met herstellen…",
|
||||
@@ -107,6 +108,7 @@
|
||||
"title": "Probeer een demonstratiescenario",
|
||||
"description": "Vijf afgebakende scenario's die telkens dezelfde vaste boekingen, klanten en voertuigen gebruiken — na een reset zijn ze altijd opnieuw te vinden.",
|
||||
"loading": "Scenario's laden…",
|
||||
"unavailable": "De demonstratiescenario's konden niet geladen worden. Probeer het opnieuw.",
|
||||
"ready": "Klaar voor demo",
|
||||
"notReady": "Niet beschikbaar",
|
||||
"duration": "Duur",
|
||||
@@ -164,6 +166,7 @@
|
||||
"title": "Wat {{productName}} wel en niet is",
|
||||
"description": "{{orgName}} is een fictieve verhuurorganisatie die dient om deze demo tastbaar te maken — geen bestaand bedrijf.",
|
||||
"loading": "Demo-informatie laden…",
|
||||
"unavailable": "De demo-informatie kon niet geladen worden. Probeer het opnieuw.",
|
||||
"ctaTitle": "Kies hoeveel tijd je hebt",
|
||||
"ctaBody": "Bekijk de drie sterkste engineeringmomenten in 90 seconden, of doorloop de volledige operationele demo.",
|
||||
"ctaButton": "Start volledige demo",
|
||||
@@ -185,6 +188,48 @@
|
||||
"architectureDatabase": "PostgreSQL + audittrail",
|
||||
"architectureOutbox": "Betrouwbare outbox",
|
||||
"architectureExternal": "Externe diensten: n8n, RAGcore en MCP Hub",
|
||||
"architectureZonesLabel": "Verantwoordelijkheidsgrenzen in de architectuur",
|
||||
"architectureZoneIntent": "Gebruikersintentie",
|
||||
"architectureZoneTransaction": "Lokale transactie",
|
||||
"architectureZoneEdge": "Herstelbare systeemrand",
|
||||
"architectureFlowLabel": "Interactieve systeemflow van interface naar externe diensten",
|
||||
"architectureCommitBoundary": "Na commit",
|
||||
"architectureInteractionHint": "Selecteer een stap om de operationele garantie en controlepunten te bekijken.",
|
||||
"architectureSelectedStep": "Stap {{current}} van {{total}}",
|
||||
"architectureBoundaryLabel": "Verantwoordelijkheid",
|
||||
"architectureEvidenceLabel": "Controlepunten",
|
||||
"architectureDetails": {
|
||||
"Frontend": {
|
||||
"summary": "Maakt rol en intentie expliciet",
|
||||
"body": "Toegankelijke formulieren verzamelen gevalideerde invoer en tonen de impact vóór bevestiging. De interface beslist nooit zelfstandig over een domeinstatus.",
|
||||
"proofOne": "Routeguard per rol",
|
||||
"proofTwo": "Preview vóór bevestiging"
|
||||
},
|
||||
"Api": {
|
||||
"summary": "Valideert elke operationele regel",
|
||||
"body": "FastAPI bewaakt statussen, invarianten en oplossingsregels aan de API-grens, onafhankelijk van wat de browser aanlevert.",
|
||||
"proofOne": "Pydantic-validatie",
|
||||
"proofTwo": "Domeinbeslissing op de server"
|
||||
},
|
||||
"Database": {
|
||||
"summary": "Commit data en audit atomair",
|
||||
"body": "PostgreSQL bewaart de operationele wijziging en het auditbewijs binnen dezelfde transactie, zodat een gedeeltelijke statuswijziging niet kan ontstaan.",
|
||||
"proofOne": "UUID + publieke referentie",
|
||||
"proofTwo": "UTC + onverbreekbare audittrail"
|
||||
},
|
||||
"Outbox": {
|
||||
"summary": "Legt vervolgwerk duurzaam vast",
|
||||
"body": "Het outboxrecord wordt samen met de operatie gecommit. Een worker levert pas daarna, idempotent en met begrensde herpogingen, aan de orkestratielaag.",
|
||||
"proofOne": "Post-commit aflevering",
|
||||
"proofTwo": "Idempotency + begrensde retries"
|
||||
},
|
||||
"External": {
|
||||
"summary": "Degradeert zonder lokaal dataverlies",
|
||||
"body": "n8n, RAGcore en MCP Hub hebben time-outs en zichtbare healthstatus. Een storing blijft herstelbaar en AI antwoordt nooit zonder voldoende bronbewijs.",
|
||||
"proofOne": "Healthstatus + time-outs",
|
||||
"proofTwo": "Geen antwoord zonder bewijs"
|
||||
}
|
||||
},
|
||||
"verificationTitle": "Verifieerbaar gebouwd",
|
||||
"verificationBody": "Domeinregels, API-contracten, degradatiemodi en de volledige demonstratie worden automatisch getest. De repository bevat de exacte acceptatiecommando's en bewijsbundel.",
|
||||
"problemTitle": "Het fictieve probleem",
|
||||
|
||||
@@ -38,6 +38,11 @@
|
||||
"explanation": "Dit probleem is al opgelost, uitgesteld of verworpen.",
|
||||
"nextStep": "Vernieuw de pagina om de huidige status te zien."
|
||||
},
|
||||
"ISSUE_CHANGED": {
|
||||
"title": "De bewijslast is gewijzigd",
|
||||
"explanation": "Er werd nieuwe bewijslast geregistreerd terwijl deze correctie werd voorbereid.",
|
||||
"nextStep": "Vernieuw het probleem en beoordeel de actuele bewijslast opnieuw."
|
||||
},
|
||||
"BOOKING_NOT_ACTIVE": {
|
||||
"title": "Boeking is niet actief",
|
||||
"explanation": "Enkel een gereserveerde of actieve boeking kan voor deze actie gebruikt worden."
|
||||
@@ -88,6 +93,18 @@
|
||||
"title": "Ongeldige opdrachtreferentie",
|
||||
"explanation": "Deze referentie naar een automatiseringsopdracht is niet geldig."
|
||||
},
|
||||
"INVALID_EVENT_CORRELATION": {
|
||||
"title": "Ongeldige correlatiereferentie",
|
||||
"explanation": "De automatiseringscallback bevat geen geldige correlatiereferentie."
|
||||
},
|
||||
"CALLBACK_EVENT_MISMATCH": {
|
||||
"title": "Callback hoort bij een ander event",
|
||||
"explanation": "De automatiseringscallback verwijst naar een ander event dan het event dat wordt bijgewerkt."
|
||||
},
|
||||
"CALLBACK_CORRELATION_MISMATCH": {
|
||||
"title": "Callbacktrace komt niet overeen",
|
||||
"explanation": "De automatiseringscallback komt niet overeen met de opgeslagen correlatiereferentie van dit event."
|
||||
},
|
||||
"INVALID_IDEMPOTENCY_KEY": {
|
||||
"title": "Aanvraag kon niet veilig herhaald worden",
|
||||
"explanation": "De trackingsleutel van deze aanvraag is niet geldig.",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"users": { "eyebrow": "Beheer / Toegang", "title": "Gebruikers", "description": "Beheer operationele toegang en rollen. Elke wijziging wordt geaudit.", "addTitle": "Gebruiker toevoegen", "name": "Naam", "email": "E-mail", "role": "Rol", "password": "Tijdelijk wachtwoord", "status": "Status", "action": "Actie", "add": "Gebruiker toevoegen", "saving": "Opslaan…", "loading": "Gebruikers laden…", "active": "actief", "inactive": "inactief", "activate": "Activeren", "deactivate": "Deactiveren", "edit": "Bewerken", "editTitle": "Gebruiker {{ref}} bewerken", "newPassword": "Nieuw wachtwoord", "passwordUnchanged": "Leeg laten om niet te wijzigen", "saveChanges": "Wijzigingen opslaan", "cancel": "Annuleren", "createFailed": "De gebruiker kon niet worden aangemaakt.", "updateFailed": "De gebruiker kon niet worden bijgewerkt." },
|
||||
"users": { "eyebrow": "Beheer / Toegang", "title": "Gebruikers", "description": "Beheer operationele toegang en rollen. Elke wijziging wordt geaudit.", "managerOnly": "Gebruikersbeheer is uitsluitend beschikbaar voor Operationsmanagers.", "addTitle": "Gebruiker toevoegen", "name": "Naam", "email": "E-mail", "role": "Rol", "password": "Tijdelijk wachtwoord", "status": "Status", "action": "Actie", "add": "Gebruiker toevoegen", "saving": "Opslaan…", "loading": "Gebruikers laden…", "active": "actief", "inactive": "inactief", "activate": "Activeren", "deactivate": "Deactiveren", "edit": "Bewerken", "editTitle": "Gebruiker {{ref}} bewerken", "newPassword": "Nieuw wachtwoord", "passwordUnchanged": "Leeg laten om niet te wijzigen", "saveChanges": "Wijzigingen opslaan", "cancel": "Annuleren", "createFailed": "De gebruiker kon niet worden aangemaakt.", "updateFailed": "De gebruiker kon niet worden bijgewerkt." },
|
||||
"roles": { "operations_manager": "Operationeel beheerder", "rental_employee": "Verhuurmedewerker" }
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"selected": "{{count}} geselecteerd",
|
||||
"assignTo": "Toewijzen aan",
|
||||
"dueAt": "Uiterste datum",
|
||||
"invalidDueAt": "Kies een geldige datum en tijd in Europe/Brussels.",
|
||||
"bulkApply": "Werkvoorraad bijwerken",
|
||||
"bulkSaving": "Bijwerken…",
|
||||
"bulkFailed": "De werkvoorraad kon niet bijgewerkt worden.",
|
||||
|
||||
Reference in New Issue
Block a user