YYayaw
Documentation

Plan de contrôle

Accès MCP production et stdio local au plan de contrôle pour clients MCP et automatisations.

Vue D'Ensemble

Yayaw expose un plan de contrôle production via MCP. Codex est un client pris en charge, mais l'endpoint est conçu pour tout client MCP ou runner d'automatisation de confiance.

  • La production et le staging utilisent Streamable HTTP sur /api/mcp.
  • Le développement local peut utiliser stdio avec bun --conditions react-server --env-file=.env src/lib/scripts/mcp/yayaw-mcp-server.ts.
  • Les requêtes MCP production peuvent s'authentifier avec une clé API Better Auth ou un access token OAuth Better Auth. Les deux chemins utilisent des permissions explicites de plan de contrôle.
  • Les requêtes runtime data-plane utilisent le même format d'acteur bearer, mais peuvent être bornées à la ressource de clé API plus étroite dynamic-data-runtime.
  • Le réglage de site control-plane-mcp-enabled est le kill switch serveur.
  • L'autorisation Yayaw sous-jacente passe toujours par can(...) ou des checks équivalents de membership borné.

Permissions Des Clés API

Les clés API utilisent les permissions Better Auth API key sous la ressource control-plane:

  • read: statut, discovery, ressources, outils list/get.
  • write: mutations brouillon/données/média/design-token.
  • publish: outils de cycle de vie publish/archive.
  • admin: administration d'audit et de feature flags.

admin ne contourne pas l'autorisation de ressource Yayaw. Il satisfait seulement la porte de permission du plan de contrôle.

Les appelants runtime chauds devraient préférer la ressource plus étroite dynamic-data-runtime:

  • read: exécuter les routes runtime dynamiques déployées en lecture seule.
  • write: exécuter les routes runtime dynamiques d'écriture comme heartbeat, mises à jour de statut, claims de pairing ou ingestion de metrics.

dynamic-data-runtime:write implique l'accès runtime read lorsque l'UI Developer normalise les permissions. Les clés MCP complètes existantes restent compatibles avec le data-plane: control-plane:read peut exécuter les routes runtime en lecture, tandis que control-plane:write ou control-plane:admin peut exécuter les routes runtime d'écriture. Tous les chemins exécutent encore l'autorisation Yayaw dynamic-data après les checks de permission API key, donc les scopes bearer ne remplacent jamais l'autorisation par groupes.

Connexion MCP Production

Créez une clé MCP production, stockez-la dans l'environnement du client comme YAYAW_MCP_API_KEY, puis configurez le client MCP. Codex peut utiliser:

[mcp_servers.yayaw-prod]
url = "https://yayaw.app/api/mcp"
bearer_token_env_var = "YAYAW_MCP_API_KEY"

Avant la connexion, activez le réglage de site:

bun run seed

Puis passez control-plane-mcp-enabled à true depuis les réglages Admin ou avec l'outil de flag du plan de contrôle en utilisant une clé admin existante.

Connexion MCP OAuth

Yayaw expose aussi une discovery OAuth 2.1 compatible MCP pour ChatGPT Apps et les autres clients qui suivent le flux d'autorisation MCP. OAuth supporte authorization code avec PKCE et refresh tokens; l'accès machine-to-machine doit continuer à utiliser les clés API MCP.

  • Métadonnées de ressource protégée: /.well-known/oauth-protected-resource/api/mcp
  • Métadonnées du serveur d'autorisation: /.well-known/oauth-authorization-server/api/auth
  • Métadonnées OpenID: /.well-known/openid-configuration/api/auth
  • Base du serveur d'autorisation: /api/auth
  • Ressource/audience MCP: /api/mcp

L'endpoint MCP renvoie un challenge WWW-Authenticate sur les requêtes OAuth non authentifiées ou invalides afin que les clients découvrent les métadonnées de ressource protégée. La politique CORS du MCP distant autorise exactement l'origine https://chatgpt.com et expose le challenge aux clients navigateur, tout en refusant les origines sans rapport. Les tokens OAuth sont vérifiés avec le JWKS Better Auth, l'issuer, l'audience, l'expiration et les scopes de plan de contrôle avant l'exécution d'un outil.

Le fournisseur OAuth initialise au démarrage la ressource RFC 8707 canonique /api/mcp. Les clients dynamiques peuvent demander cette ressource active sans liaison client-ressource préconfigurée, mais ils doivent toujours terminer l'authentification et le consentement utilisateur avant que Better Auth émette un token limité par ses scopes.

Le fournisseur OAuth annonce volontairement toute la surface de permissions MCP:

control-plane:read control-plane:write control-plane:publish control-plane:admin

Cela garde la surface d'outils MCP inchangée pour les clients liés de confiance. Les handlers appliquent toujours la permission de plan de contrôle requise et l'autorisation Yayaw sous-jacente pour chaque opération; les scopes OAuth ne remplacent donc jamais l'autorisation applicative.

Pour les ChatGPT Apps publiées, vérifiez que NEXT_PUBLIC_BASE_URL est l'origine HTTPS publique avant de générer les métadonnées. ChatGPT redirige vers https://chatgpt.com/connector/oauth/{callback_id}; le client OAuth enregistré doit autoriser cette redirect URI.

Connexion MCP Locale

Le stdio local est utile en développement et ne nécessite pas d'ouvrir un endpoint public. Codex peut l'enregistrer avec:

codex mcp add yayaw-local -- bun --conditions react-server --env-file=.env src/lib/scripts/mcp/yayaw-mcp-server.ts

Pour le stdio local, définissez YAYAW_MCP_API_KEY pour tester une vérification de clé API comme en production. Sans clé, le lanceur crée un acteur de développement local avec YAYAW_MCP_LOCAL_USER_ID ou local-codex. Pour les runs smoke dynamic-data, cet acteur local stdio de confiance peut utiliser ses permissions locales control-plane:admin pour passer les contrôles control-plane dynamic-data sans utilisateur stocké en base. Les acteurs HTTP, OAuth, clé API, preview et production requièrent toujours la décision d'autorisation Yayaw normale pour chaque opération dynamic-data.

Lancez le smoke MCP dynamic-data générique avant ou après un travail de bundle:

bun run mcp:dynamic-data-smoke

La commande démarre le serveur MCP stdio local, liste les outils, appelle yayaw_dynamic_data_ui_capabilities, et vérifie le contrat de checklist bundle.postInstall.verification ainsi que des capacités représentatives Yayaw Table, formulaire généré, panneau détail, navigation dynamique et dashboard.operatorReadiness. La sortie du smoke inclut la racine control-plane Admin de readiness opérateur, le pattern de route des sections dynamiques, les états d'attention, champs de résumé et endpoints de passage MCP/runtime afin que les runs déployés montrent où les surfaces dashboard sont visibles. Le loader dashboard lit aussi cette même readiness de schéma cible avant de lister les modèles, affiche les tables control-plane requises manquantes dans le panneau opérateur, et évite les requêtes registry tant que le schéma est bloqué. Définissez YAYAW_MCP_SMOKE_TRANSPORT=http, YAYAW_MCP_URL=https://yayaw.app/api/mcp, et YAYAW_MCP_API_KEY pour lancer le même smoke contre un endpoint déployé. Définissez YAYAW_MCP_SMOKE_VALIDATE_BUNDLE=1 pour parser, normaliser et valider localement la fixture de bundle configurée sans nécessiter de connectivité DB ni d'authz dynamic-data sur l'endpoint cible. Définissez YAYAW_MCP_SMOKE_BUNDLE_DRY_RUN=1 seulement quand l'environnement cible dispose de la DB et de l'état authz nécessaires au dry-run du modèle resource générique inclus et de ses modèles liés. Par défaut, ce dry-run optionnel charge src/lib/scripts/mcp/fixtures/dynamic-data-resource-bundle.json; définissez YAYAW_MCP_SMOKE_BUNDLE_FILE pour pointer vers une autre entrée JSON complète de yayaw_dynamic_data_bundle_apply. Pour les bundles produit externes conservés hors du dépôt, préférez les scripts génériques mcp:dynamic-data-bundle-*: mcp:dynamic-data-bundle-smoke, mcp:dynamic-data-bundle-target-readiness, mcp:dynamic-data-bundle-dry-run, mcp:dynamic-data-bundle-apply, mcp:dynamic-data-bundle-installed-smoke et mcp:dynamic-data-bundle-seeded-smoke. Ces scripts définissent YAYAW_MCP_SMOKE_REQUIRE_BUNDLE_FILE=1, donc le run échoue si YAYAW_MCP_SMOKE_BUNDLE_FILE manque au lieu de retomber accidentellement sur la fixture intégrée. Lancez bun run mcp:dynamic-data-foundation-smoke pour valider la fixture foundation générique dans src/lib/scripts/mcp/fixtures/dynamic-data-foundation-bundle.json. Ce script définit YAYAW_MCP_SMOKE_VALIDATE_FOUNDATION=1, ce qui exige qu'un bundle couvre tables natives, UI dashboard et navigation, registre de principals dynamique, références et grants par principal, contrainte de grant actif, routes runtime, modèles enfants d'endpoints de ressource, trust bindings de ressource, heartbeat avec clés d'idempotence composites, routes de statut de lifecycle de workflow avec effets terminaux, claim pairing avec secret hashé et compteur de tentatives, ingestion metrics avec rollups, rate limit par principal, guards scopés au principal, effets cross-model, topics realtime, projections et échantillons bruts à rétention courte. Lancez bun run mcp:dynamic-data-foundation-dry-run quand l'endpoint cible dispose des tables DB dynamic-data et de l'authz dynamic-data:manage pour l'acteur MCP. La commande utilise la même fixture foundation et les mêmes validations, puis appelle yayaw_dynamic_data_bundle_apply avec dryRun=true pour confirmer que la cible est prête avant un vrai apply publish/deploy. Lancez bun run mcp:dynamic-data-target-readiness avant le premier vrai apply sur un endpoint cible. La commande charge la fixture foundation générique, valide le contrat local, découvre les outils MCP, lit les capabilities dynamic-data, appelle yayaw_dynamic_data_target_readiness pour le schéma DB cible, liste les modèles dans le scope du bundle seulement quand ce préflight de schéma est ready, puis dry-run yayaw_dynamic_data_bundle_apply. Elle imprime un rapport targetReadiness avec statut ready ou blocked et sort en non-zéro quand une étape requise échoue, afin de rendre visibles l'authz manquante, les migrations, les tables DB ou la compatibilité du bundle avant toute mutation. Lancez bun run mcp:dynamic-data-foundation-apply seulement après un rapport readiness ready. La commande réutilise la même fixture foundation, garde la readiness activée, exige la confirmation de vrai apply YAYAW_MCP_SMOKE_BUNDLE_APPLY_CONFIRM=publish-deploy, envoie une raison d'audit, puis appelle yayaw_dynamic_data_bundle_apply avec dryRun=false, publish=true et deploy=true. Si la readiness est blocked, elle indique bundleApply.status: skipped et sort avant toute mutation. Le script apply définit aussi YAYAW_MCP_SMOKE_POST_INSTALL_VERIFY=1; après un apply réussi, il exécute les appels MCP sûrs de postInstall.verification.checklist et classe les étapes manuelles ou dépendantes de données produit comme skipped avec des raisons telles que manual_step, requires_input ou requires_values. Lancez bun run mcp:dynamic-data-foundation-installed-smoke après un apply réussi pour vérifier toute la foundation déployée. La commande dérive la liste de modèles attendus depuis la même fixture générique, vérifie que yayaw_dynamic_data_models_list indique chaque modèle comme publié, déployé et valide, lit le manifest runtime, vérifie les routes dashboard générées, la navigation, les réglages Yayaw Table normalisés pour table, formulaire et détail, ainsi que les bindings d'extensions d'entités, compare les ids de contrôles UI déclarés au manifest runtime installé, compare les contrats de routes runtime déclarés à yayaw_dynamic_data_runtime_routes, y compris les rollups de metrics-ingest, compare les contrats de feeds d'événements realtime déclarés aux runtimeEventFeeds du manifest installé, query chaque modèle, lit les événements runtime et résout chaque route runtime déclarée. Elle appelle aussi yayaw_dynamic_data_entity_extensions pour chaque target d'extension déclarée, afin de prouver que la découverte d'extensions correspond au manifest déployé. Définissez YAYAW_MCP_SMOKE_MODEL_SLUG, YAYAW_MCP_SMOKE_MODEL_SCOPE, et optionnellement YAYAW_MCP_SMOKE_ORG_ID pour vérifier un modèle dynamique déjà déployé via le manifest, la route dashboard générée, les champs normalisés du manifest, les colonnes système, ui.table, ui.form, ui.detail, ui.nav, une query record bornée, le contrat de feed d'événements runtime, une lecture d'events runtime et la résolution des routes runtime. Ajoutez YAYAW_MCP_SMOKE_RUNTIME_ROUTE_EXAMPLES_JSON ou YAYAW_MCP_SMOKE_RUNTIME_ROUTE_EXAMPLES_FILE pour exécuter en dry-run certaines routes runtime pendant le smoke de modèle installé. Chaque exemple est un objet JSON avec modelSlug plus routeKey, routeId, templatePath ou method, et un path concret, avec éventuellement recordId, values, filters, sort, search, recordLimit, recordOffset, expectedDataVersion ou reason. Les payloads spécifiques au produit restent ainsi hors du dépôt, tandis que MCP peut prouver que les routes génériques déployées sont résolubles et exécutables. Les fixtures de bundle externes peuvent aussi inclure smoke.runtimeRouteExamples au top-level avec le même format d'exemple. Le runner smoke lit ces exemples pour la vérification de modèle installé et de bundle installé, tout en retirant smoke avant d'appeler yayaw_dynamic_data_bundle_apply; les exemples fournis via YAYAW_MCP_SMOKE_RUNTIME_ROUTE_EXAMPLES_JSON ou YAYAW_MCP_SMOKE_RUNTIME_ROUTE_EXAMPLES_FILE gardent la priorité sur les exemples intégrés au bundle. Les fixtures externes peuvent aussi inclure smoke.recordSeeds, un tableau ordonné d'objets avec modelSlug, recordId optionnel, scope/orgId optionnels, un label optionnel et values. Définissez YAYAW_MCP_SMOKE_RECORD_SEEDS=1 pour préflight ces seeds via yayaw_dynamic_data_record_create avec dryRun=true. Pour créer les records sur une cible de smoke end-to-end, définissez aussi YAYAW_MCP_SMOKE_RECORD_SEEDS_APPLY=1, YAYAW_MCP_SMOKE_RECORD_SEEDS_CONFIRM=create-records et YAYAW_MCP_SMOKE_RECORD_SEEDS_REASON pour le journal d'audit. Les bundles produit peuvent ainsi porter leurs propres données de test tout en gardant le dépôt Yayaw neutre. Définissez YAYAW_MCP_SMOKE_RECORD_SEEDS_RESET=1 pour supprimer les records de seed existants avant création, en utilisant les recordId stables du bundle dans l'ordre inverse des seeds. Le reset reste en dryRun=true par défaut, ignore les records déjà absents et requiert YAYAW_MCP_SMOKE_RECORD_SEEDS_RESET_APPLY=1, YAYAW_MCP_SMOKE_RECORD_SEEDS_RESET_CONFIRM=delete-records et YAYAW_MCP_SMOKE_RECORD_SEEDS_RESET_REASON pour les suppressions réelles. Définissez YAYAW_MCP_SMOKE_RECORD_SEEDS_CLEANUP=1 pour exécuter yayaw_dynamic_data_record_delete sur les records créés par le même run smoke. Le cleanup reste en dryRun=true par défaut; un cleanup réel requiert YAYAW_MCP_SMOKE_RECORD_SEEDS_CLEANUP_APPLY=1, YAYAW_MCP_SMOKE_RECORD_SEEDS_CLEANUP_CONFIRM=delete-records et YAYAW_MCP_SMOKE_RECORD_SEEDS_CLEANUP_REASON, afin que les bundles smoke produit puissent vérifier les chemins d'écriture sans laisser de records de validation derrière eux. La fixture embarquée dynamic-data-foundation-bundle.json inclut des smoke.recordSeeds neutres produit pour ses modèles ressource, endpoint, trust, session et métriques, afin que le contrat foundation générique dispose d'un exemple réutilisable de chemin d'écriture. Elle inclut aussi des smoke.runtimeRouteExamples neutres produit pour les routes déterministes de heartbeat, trust, statut de session et ingestion de métriques. Le runner smoke exécute ces exemples de routes embarqués uniquement quand la création réelle des record seeds est activée avec YAYAW_MCP_SMOKE_RECORD_SEEDS_APPLY=1, ou quand un opérateur définit explicitement YAYAW_MCP_SMOKE_RUNTIME_EXAMPLES_FROM_BUNDLE=1, afin que les vérifications simples de bundle installé ne dépendent pas de records de seed préexistants. La même fixture foundation relie des ui.table.rowActions et ui.detail.actions neutres produit aux routes runtime heartbeat, check endpoint, trust-seen et fin de session. Le validateur foundation vérifie maintenant runtimeUiActions, afin que le bundle générique continue de prouver que MCP peut décrire tables natives, routes runtime et UI backend actionnable sans code repository. Après déploiement du bundle foundation sur une cible avec les tables dynamic-data, lancez bun run mcp:dynamic-data-foundation-seeded-smoke pour combiner vérification du bundle installé, reset des seeds, préflight de création des seeds et préflight de cleanup dans une seule commande neutre produit. Ajoutez les variables d'environnement *_APPLY, *_CONFIRM et *_REASON documentées uniquement lorsque vous voulez volontairement écrire et supprimer ces records de seed sur cette cible. Définissez YAYAW_MCP_SMOKE_ENTITY_TARGET avec les options YAYAW_MCP_SMOKE_ENTITY_SCOPE, YAYAW_MCP_SMOKE_ENTITY_ORG_ID, YAYAW_MCP_SMOKE_ENTITY_TARGET_ID, YAYAW_MCP_SMOKE_ENTITY_BINDING_KINDS, YAYAW_MCP_SMOKE_ENTITY_INCLUDE_COUNTS=1, et YAYAW_MCP_SMOKE_ENTITY_MIN_EXTENSIONS pour vérifier yayaw_dynamic_data_entity_extensions sur des modèles dynamiques déployés qui extends ou references des entités natives ou produit. C'est le chemin générique pour afficher des extensions sur les pages dashboard natives sans code React produit.

Gestion Des Clés

Pour une clé opérateur MCP totalement de confiance, utilisez l'ensemble complet de permissions:

control-plane:read,write,publish,admin

La page Developer settings du dashboard utilise le bloc de clé API custom Yayaw plutôt que l'UI Better Auth générique, parce que Better Auth traite les permissions de clés API comme des champs server-only. Utilisez ce bloc pour créer des clés MCP avec scopes explicites ou réparer une clé existante avec le preset Accès API complet. Le preset écrit:

control-plane:read,write,publish,admin
dynamic-data-runtime:read,write

Pour une clé runtime data-plane seule, laissez les permissions control-plane vides et accordez:

dynamic-data-runtime:read,write

Pour une clé qui peut à la fois configurer les modèles via MCP et appeler les routes runtime chaudes, combinez les deux ressources:

control-plane:read,write,publish,admin
dynamic-data-runtime:read,write

L'UI peut mettre à jour les permissions d'une clé existante sans révéler ou faire tourner le secret. Utilisez cela seulement lorsque le secret actuel est déjà stocké en sécurité, parce que Better Auth n'affiche le secret qu'une fois.

Les clés détenues par l'organisation sont le choix par défaut pour les clients MCP qui opèrent sur un périmètre client/workspace. Elles sont émises pour l'organisation active, gérables par les propriétaires, admins et managers Better Auth, portent des métadonnées d'émetteur et sont limitées à leur organisation pour les opérations bornées sur pages, données CMS et médias.

Les clés personnelles sont superadmin-only et destinées à l'administration globale de Yayaw. Utilisez-les pour le contenu global, les feature flags ou les opérations inter-organisations uniquement lorsque c'est intentionnel.

Émettre une clé API MCP détenue par utilisateur:

bun run mcp:key -- issue --email admin@example.com --permissions read,write,publish,admin

Émettre une clé API MCP détenue par organisation:

bun run mcp:key -- issue --email owner@example.com --organization-slug acme --permissions read,write,publish,admin

Lister les clés d'un utilisateur et leurs permissions stockées:

bun run mcp:key -- list --email admin@example.com

Lister les clés d'une organisation et leurs permissions stockées:

bun run mcp:key -- list --organization-slug acme

Inspecter une clé par id:

bun run mcp:key -- inspect --key-id <api-key-id>

Inspecter le secret configuré pour un client MCP local:

YAYAW_MCP_API_KEY=<secret> bun run mcp:key -- inspect --token-env YAYAW_MCP_API_KEY

Révoquer une clé:

bun run mcp:key -- revoke --key-id <api-key-id>

La commande issue affiche le secret une seule fois. Stockez-le dans un gestionnaire de secrets ou l'environnement du client MCP local; ne le commitez pas.

Outils

Noyau:

  • yayaw_capabilities
  • yayaw_status
  • yayaw_audit_list
  • yayaw_deploy_status

CMS et pages:

  • yayaw_pages_list, yayaw_pages_get, yayaw_pages_validate, yayaw_pages_diff
  • yayaw_pages_publish, yayaw_pages_archive
  • yayaw_pages_create_draft, yayaw_pages_save_draft (bypass de maintenance désactivés)
  • yayaw_components_list, yayaw_component_get, yayaw_component_import, yayaw_component_recipe_import
  • yayaw_components_sync_registry, yayaw_component_recompile
  • yayaw_component_publish, yayaw_component_delete
  • yayaw_sections_list, yayaw_sections_get, yayaw_sections_create_draft
  • yayaw_sections_validate, yayaw_sections_save_draft
  • yayaw_sections_publish, yayaw_sections_archive
  • yayaw_kibo_sections_generate
  • yayaw_cms_design_prepare, yayaw_cms_design_resume, yayaw_cms_design_guidance_get, yayaw_cms_design_context_renew, yayaw_cms_design_inventory_refresh
  • yayaw_cms_prototypes_register, yayaw_cms_prototype_runtime_previews_get, yayaw_cms_prototype_runtime_review_complete, yayaw_cms_prototype_select
  • yayaw_cms_page_design, yayaw_cms_page_rework, yayaw_cms_page_review, yayaw_cms_page_review_complete
  • yayaw_cms_page_design_legacy_admin (bypass de compatibilité admin désactivé)
  • yayaw_cms_personalization_requests_list, yayaw_cms_personalization_request_get, yayaw_cms_personalization_request_status_update, yayaw_cms_personalization_request_delete, yayaw_cms_personalization_requests_prune (admin uniquement)
  • yayaw_data_models_list, yayaw_data_schema_get, yayaw_data_entry_get
  • yayaw_data_entry_upsert, yayaw_data_entry_publish
  • yayaw_dynamic_data_ui_capabilities
  • yayaw_dynamic_data_target_readiness
  • yayaw_dynamic_data_reference_options
  • yayaw_dynamic_data_models_list, yayaw_dynamic_data_model_get
  • yayaw_dynamic_data_model_revisions, yayaw_dynamic_data_model_revision_diff, yayaw_dynamic_data_model_revision_restore
  • yayaw_dynamic_data_entity_extensions
  • yayaw_dynamic_data_runtime_manifest
  • yayaw_dynamic_data_runtime_routes
  • yayaw_dynamic_data_runtime_events
  • yayaw_dynamic_data_runtime_route_resolve
  • yayaw_dynamic_data_runtime_execute
  • yayaw_dynamic_data_model_save, yayaw_dynamic_data_model_publish
  • yayaw_dynamic_data_model_deployment_plan, yayaw_dynamic_data_model_deploy
  • yayaw_dynamic_data_bundle_apply
  • yayaw_dynamic_data_records_query, yayaw_dynamic_data_record_get
  • yayaw_dynamic_data_record_create, yayaw_dynamic_data_record_update, yayaw_dynamic_data_record_delete
  • yayaw_email_templates_list, yayaw_email_template_get
  • yayaw_email_template_save, yayaw_email_templates_sync_system
  • yayaw_org_domains_list, yayaw_org_domain_add, yayaw_org_domain_check
  • yayaw_org_domain_set_primary, yayaw_org_domain_archive

Opérations site:

  • yayaw_billing_products_list, yayaw_billing_product_update
  • yayaw_stripe_discounts_list, yayaw_stripe_discounts_sync
  • yayaw_media_list, yayaw_media_search, yayaw_media_generate, yayaw_media_upload, yayaw_media_import_url, yayaw_media_upload_prepare, yayaw_media_upload_complete
  • yayaw_design_tokens_get, yayaw_design_tokens_save
  • yayaw_flags_list, yayaw_flags_update

Les outils de données dynamiques natives pilotent des tables Postgres propres au projet. Ils stockent des définitions versionnées, valident les contrats de champs et d'index, publient les révisions approuvées, puis déploient seulement de la DDL additive générée par le serveur. yayaw_dynamic_data_model_revisions liste les révisions immuables avec les marqueurs latest, published et deployed, ainsi que les documents complets en option pour les clients MCP qui doivent comparer des définitions avant de publier, déployer ou restaurer un contrat précédent. yayaw_dynamic_data_model_revision_diff compare deux révisions immuables, par défaut de deployed vers latest, et renvoie les résumés par section, les chemins modifiés, les valeurs before/after ou documents complets en option, ainsi que les mêmes métadonnées d'évolution de déploiement que le planner. yayaw_dynamic_data_model_revision_restore restaure une révision immuable précédente comme dernier draft uniquement ; les opérateurs doivent explicitement publier puis déployer ce draft restauré après revue des diagnostics et diffs. Les réponses de plan et de déploiement exposent aussi evolution.status, evolution.canDeploy, les changements par champ, et les compteurs de champs ajoutés, conservés, retirés, renommés, changés de type et bloquants. Les agents doivent traiter blocked comme un signal de préparer une migration de données séparée plutôt que de tenter une DDL destructive via dynamic-data. Utilisez yayaw_dynamic_data_bundle_apply pour les manifests d'extension produit qui doivent installer plusieurs modèles ensemble. L'outil accepte une liste bornée de définitions de modèles dans un même scope global ou organisation, vérifie dynamic-data:manage pour chaque modèle cible, sauvegarde chaque draft via le même validateur, et peut optionnellement publier et déployer les révisions résultantes dans l'ordre. Il valide aussi les champs de référence vers modèles dynamiques sur tout le bundle avant de sauvegarder un draft: relation.modelSlug doit pointer vers un autre modèle du bundle ou vers un modèle existant lisible dans le scope cible résolu, et les valeurs déclarées displayField ou valueField doivent exister sur le modèle cible ou être des colonnes système d'enregistrement sûres. Les metricRollups, recordEffects, recordGuards et relatedRecordFilters runtime doivent aussi cibler des modèles du bundle ou des modèles existants lisibles; chaque champ écrit ou filtré doit exister sur le modèle cible avant que le bundle puisse être sauvegardé. Il requiert control-plane:admin, car deploy=true peut appliquer du DDL de tables natives généré; lancez d'abord dryRun=true pour valider le manifest sans écriture. Le détail modèle du dashboard utilise le même générateur de plan pour les gestionnaires, afin que les opérateurs puissent vérifier le SQL exact, le nom de table, les index, les diagnostics et le hash des statements avant d'appliquer la DDL générée. Quand deploy=true réussit hors dry-run, la réponse inclut manifest.models pour les modèles déployés du bundle afin que les clients MCP puissent inspecter immédiatement leurs champs, colonnes système, routes runtime et UI dashboard sans appel manifest séparé. Chaque modèle de bundle déployé retourne aussi deploy.evolution, afin que les installateurs batch puissent s'arrêter sur les changements non-additifs bloqués avec le même rapport par champ que l'outil de plan de déploiement unitaire. Chaque réponse de bundle inclut aussi postInstall, un guide de passage de relais MCP sûr pour le dashboard. Une fois déployé, postInstall.models liste la route dashboard canonique de chaque modèle installé, le template de route record, les métadonnées d'extension, les routes runtime avec des blueprints d'entrée yayaw_dynamic_data_runtime_execute, les topics realtime avec des blueprints yayaw_dynamic_data_runtime_events plus des handoffs data-plane JSON/SSE, et les blueprints des outils record query/get/create/update/delete pour les smoke checks. L'inventaire ui.surfaces de chaque modèle liste les surfaces navigation, table, formulaire et détail générées avec les routes dashboard, colonnes de table ordonnées, champs et sections de formulaire, modes d'affichage, IDs d'actions ligne/détail, panels d'enregistrements liés, cartes métriques, timelines d'événements et IDs d'items de résumé. Chaque route runtime inclut aussi un handoff dataPlane avec l'endpoint HTTP générique sous /api/dynamic-data/runtime/*, les query params requis, l'action de clé API dynamic-data-runtime attendue, l'action d'autorisation Yayaw sous-jacente, la politique de rate limit de la route, et si la route exige une raison. Ce guide n'expose jamais les noms de tables physiques et permet à un client MCP de passer de l'installation à la vérification spécifique produit sans coder les internes Yayaw en dur. Quand le déploiement a été lancé, postInstall.verification ajoute une checklist ordonnée qui commence par un refresh du manifest puis pointe le client MCP vers la route dashboard générée, la query record, le dry-run create record, les dry-runs de routes runtime, et la lecture des events runtime pour chaque modèle installé. La réponse yayaw_dynamic_data_ui_capabilities expose les kinds de checklist supportés, les valeurs de statut, l'ordre recommandé et les noms d'outils de smoke test sous bundle.postInstall.verification. Chaque réponse de bundle inclut aussi un objet summary global et des données models[].summary par modèle. Ces résumés sont disponibles en dry-run comme en apply réel et indiquent les modèles créés ou mis à jour, les modèles ready ou bloqués, les compteurs de routes runtime, de surfaces UI, de champs, colonnes, formulaires et actions, les compteurs de diagnostics par sévérité, ainsi que les étapes publish/deploy qui seraient exécutées. Les clients MCP devraient utiliser ce résumé d'impact comme smoke check rapide avant de demander l'approbation opérateur d'un manifest produit. La validation de bundle en dry-run peut omettre reason; les écritures réelles de bundle continuent d'en exiger un pour l'auditabilité. Les définitions de modèles peuvent porter un bloc extension pour l'ownership produit, les capacités et les bindings d'entités, un bloc constraints pour les contraintes uniques générées, un bloc runtime pour la surface data-plane attendue, et un bloc ui validé.

Les control planes pour devices, agents, remote-control, workflows ou analytics doivent être modélisés comme des extensions dynamic data bornées à l'organisation, pas comme des forks backend spécifiques à un produit. Un manifeste peut définir des registres de principals, des registres de ressources avec statuts calculés côté serveur depuis heartbeat et workflows, des modèles enfants runtime ou endpoint, des trust bindings, des flows de pairing à code hashé et TTL court, des grants bornés à un principal, des enregistrements de cycle de vie, des samples metrics bruts à rétention courte, des rollups minute/hour/day et des événements d'audit. Ces modèles doivent rester des documents yayaw_dynamic_data_bundle_apply normaux avec métadonnées extension, runtime.api, runtime.realtime, runtime.projections, ui.nav, et réglages Yayaw Table ui.table/ui.form.

Les clients MCP possèdent les documents de bundle spécifiques à chaque produit et peuvent les dry-run, publier et déployer via yayaw_dynamic_data_bundle_apply. Le repository Yayaw ne doit pas embarquer de manifestes produit ni de wrappers dédiés: il expose les contrats génériques de modèles, runtime, UI, authz et audit que ces manifestes fournis par MCP utilisent. Les appelants produit chauds doivent utiliser /api/dynamic-data/runtime/* ou de futurs endpoints data-plane dédiés pour les heartbeats, claims de pairing, changements de statut de session et ingestion de metrics, plutôt que de router du trafic haute fréquence via MCP.

Par exemple, un registre de ressources peut exposer des heartbeats dashboard sur record sélectionné et une route heartbeat externe idempotente comme /devices/heartbeat; les appelants transmettent un slug stable et les champs de registre à publier, puis l'exécuteur heartbeat générique crée ou met à jour la ligne native. Les lectures list peuvent utiliser relatedRecordFilters pour que /devices ne renvoie que les ressources ayant un grant actif, non révoqué et non expiré pour le principal runtime courant. Les modèles enfants endpoint ou trust-binding peuvent utiliser le même contrat heartbeat générique avec des clés d'idempotence composites comme resource_id + endpoint_kind + host + port ou resource_id + hostname + fingerprint. Les routes de statut et révocation peuvent utiliser patch_status, des preconditions, et des assignments serveur afin que les actions de ligne dashboard et les clients MCP n'envoient pas de timestamps de cycle de vie fiables.

extension.entityBindings permet à un modèle dynamique de déclarer qu'il represents, extends ou references une entité produit durable comme organization, user, principal, resource, workflow, ou tout slug spécifique au produit. organization reste la cible canonique Yayaw pour une team ou un workspace; les manifests et appels de discovery peuvent utiliser team comme alias et Yayaw le normalise en organization. Les bindings extends et references doivent pointer vers un champ déclaré, tandis que represents peut décrire le modèle lui-même. Ces bindings servent de métadonnée pour MCP, la navigation dashboard, l'orchestration runtime et la discovery d'extensions; ils ne donnent jamais d'accès et ne remplacent pas l'autorisation Yayaw can(...). Le builder no-code de modèle dans le dashboard peut authorer la même clé d'extension, libellé, description, version, capacités, tags et bindings d'entités pour les brouillons en table native; les bundles MCP restent le chemin pour les manifests d'extension multi-modèles plus larges. Les clients MCP peuvent appeler yayaw_dynamic_data_entity_extensions avec une target comme user, organization, team, principal, ou tout slug produit pour découvrir les modèles déployés qui represents, extends ou references cette target. Lorsque targetId et includeRecordCounts=true sont fournis, Yayaw compte les enregistrements natifs liés seulement après filtrage de chaque modèle par dynamic-data:list; les bindings basés sur un champ comptent les lignes via leur champ déclaré, tandis que represents compte l'enregistrement représenté par id. constraints génère aujourd'hui des indexes uniques sûrs, y compris des indexes uniques partiels depuis des prédicats de champs comme revoked_at is null, sans accepter de SQL arbitraire. runtime.api, runtime.realtime et runtime.projections documentent les routes runtime, topics d'événements realtime durables et champs dérivés consommés par les handlers serveur. MCP reste le control plane et ne doit pas se trouver dans les chemins chauds de heartbeat, sessions, metrics ou ingestion d'événements. runtime.projections supporte last_seen_status pour les projections de présence sur le même enregistrement: la projection lit des inputs timestamp, applique un seuil configurable staleAfterSeconds avec un défaut de 120 secondes, et utilise les deux premières values comme libellés offline/reachable. Il supporte aussi record_status pour les projections de statut génériques comme les ressources, workflows, tickets ou inventaires: values[0] est le fallback, les inputs booléens true et numériques positifs résolvent vers le values[inputIndex + 1] correspondant, et les inputs texte passent à travers quand ils correspondent à une valeur autorisée. state utilise la même résolution priorisée pour les champs d'état de workflow, cycle de vie ou disponibilité. count additionne des inputs du même enregistrement en traitant les tableaux comme leur longueur, les nombres finis comme leur valeur, les booléens true et scalaires non vides comme 1, et les valeurs vides ou manquantes comme 0. Les projections on_read s'appliquent aux valeurs retournées sans muter le stockage. Les champs projetés sont toujours retirés des payloads create/update des appelants et ne sont pas exigés des callers record même si la définition du champ est marquée required. Les projections on_write sont matérialisées pendant create/update après retrait des valeurs client pour le champ projeté, afin que le modèle, pas l'appelant, possède la valeur calculée. yayaw_dynamic_data_runtime_manifest renvoie les déclarations runtime normalisées et déployées pour la discovery par les futures couches API, realtime, projections, UI et orchestration d'extensions. Les entrées incluent les fields normalisés, fieldKeys, runtimeApiRoutes effectives, ui.dashboardRoute résolu ainsi que les configs ui.dashboardRecordRouteTemplate, ui.dashboardSection, systemColumns, ui.nav, ui.table, ui.form et ui.runtimeActions normalisées, pour que les clients MCP puissent découvrir les interfaces backend sans reconstruire les réglages Yayaw Table depuis les documents modèle bruts. ui.runtimeActions résume les actions ligne/détail, leurs surfaces dashboard, leur mode de rafraîchissement, leur route key, la méthode/le chemin résolus, le comportement record-id et verrou optimiste, ainsi que les champs de bindings littéraux et de chemin. runtimeApiRoutes filtre les routes désactivées et résout les defaults de méthode, chemin, action d'authz, idempotence, rate limits, filtres, assignments et preconditions pour le modèle. Le builder de modèle no-code peut configurer des déclarations de routes runtime bornées avec chemin de base, type, méthode, chemin, action d'authz, clé stable, champs d'idempotence, champs client acceptés, mapping de paramètres vers champs, préconditions par champ, assignations de valeurs de route, conditions de valeurs client, filtres d'enregistrements de route, incréments de valeurs de route, transformations de valeurs de route, rate limits et exigence de reason avant publication/déploiement. Les champs d'idempotence et les champs client acceptés utilisent des pickers structurés dérivés des champs du brouillon; les conditions de valeurs client utilisent des contrôles bornés champ, opérateur et valeur littérale; le mapping de paramètres vers champs utilise des lignes de bindings bornées; et les préconditions utilisent des contrôles bornés champ, opérateur, valeur littérale ou now, tandis que les filtres d'enregistrements de route utilisent des contrôles bornés champ, opérateur, valeur littérale, paramètre de chemin, organisation ou acteur, et les assignations de valeurs peuvent définir des champs depuis une valeur littérale, now, un paramètre de chemin, l'id d'organisation ou l'identité acteur, avec des listes générales, spécifiques à la création et spécifiques à la mise à jour. L'entrée MCP peut toujours envoyer les mêmes valeurs comme arrays, objets ou chaînes séparées par des virgules. Les incréments de valeurs peuvent ajuster atomiquement des champs numériques avec bornes min/max optionnelles. Les transformations de valeurs peuvent hasher des champs de valeur ou paramètres de chemin vers des champs cibles avec SHA-256 ou HMAC SHA-256, encodage hex ou Base64URL, trimming optionnel et suppression optionnelle de la source. Les effets de route plus avancés, guards, rollups et effets cross-model restent disponibles via les bundles MCP. Le même builder no-code peut lier des actions runtime bornées à ces clés de routes avec libellé, icône, style, comportement de rafraîchissement, inclusion de l'ID d'enregistrement, verrou optimiste, reason d'audit optionnelle et placement optionnel dans la vue détail; les conditions avancées, valeurs littérales, paramètres de chemin et bindings de valeurs restent du ressort des bundles MCP. La même surface dashboard peut rouvrir un modèle manageable comme nouveau brouillon seulement lorsque le document courant est entièrement représentable par le formulaire no-code borné; elle envoie expectedRevisionId pour détecter les conflits et laisse les documents avancés MCP-only sous contrôle MCP jusqu'à l'existence de contrôles visuels équivalents. Une route peut déclarer une key stable (ou les alias manifest routeKey ou id), normalisée et renvoyée avec la route afin que les clients MCP et les appelants chauds ciblent une route déployée précise sans dépendre de l'ordre du tableau de manifest. yayaw_dynamic_data_runtime_routes utilise les mêmes entrées de routes du manifest et ajoute le signalement de conflits cross-model avant l'activation d'un endpoint runtime générique. Les entrées de route incluent un handoff dataPlane avec le template d'endpoint HTTP, les query params requis, l'action de clé API runtime, l'action d'autorisation Yayaw, la politique de rate limit, et l'exigence de reason, afin que les clients MCP puissent découvrir les routes chaudes déjà installées sans rejouer l'installation de bundle. Pour les modèles déployés, le détail de contrat du dashboard et les actions record/detail générées lisent ce même manifest runtime normalisé; les cartes de routes déployées affichent aussi l'endpoint data-plane générique, les query params, l'action de clé API runtime, l'action d'autorisation Yayaw, la politique de rate limit et l'exigence de reason depuis ce manifest. L'interface affiche donc les routes et ui.runtimeActions réellement installées, pas un document draft non publié. yayaw_dynamic_data_runtime_route_resolve matche une méthode et un chemin avec les déclarations de routes déployées et renvoie les paramètres de chemin pour les tests MCP à sec. Les outils manifest, route-list, resolve et execute acceptent routeKey comme filtre optionnel lorsque plusieurs routes génériques partagent volontairement une forme méthode/chemin. yayaw_dynamic_data_runtime_execute exécute les routes runtime record supportées via les services génériques après résolution de la route, check d'authz Yayaw dynamic-data, et reason obligatoire pour les mutations MCP réelles. Sa réponse MCP inclut l'action d'authz effective et les éventuels conflicts de routes observés pendant la résolution en plus du résultat d'exécution. L'exécuteur runtime partagé ne demande un reason que si la route matchée déclare reasonRequired, afin que les futurs endpoints data-plane chauds puissent exécuter heartbeat et metrics sans hériter de la friction d'audit MCP. L'exécuteur supporte aujourd'hui list, get, create, update, patch_status, delete, heartbeat, metrics_ingest et pairing_claim. heartbeat et metrics_ingest peuvent faire un upsert quand la route déclare idempotencyKeyField ou une liste composite idempotencyKeyFields; sinon l'ingestion metrics crée un enregistrement. pairing_claim exige des champs d'idempotence, trouve exactement un enregistrement existant via ces champs, puis le met à jour avec les paramètres de route et valeurs de requête fusionnés. Les clés composites servent aux modèles enfants runtime identifiés par des champs stables comme resource_id + hostname. Les routes metrics_ingest peuvent aussi déclarer des metricRollups pour alimenter des agrégats en tables natives à côté du modèle de samples bruts. Chaque rollup cible un autre modèle dynamique, choisit une fenêtre minute, hour ou day, écrit un champ bucket normalisé, copie des dimensions déclarées comme workflow_id ou resource_id, et peut incrémenter un countField ainsi que des champs de valeurs count, sum, min, max, avg ou last. Les rollups s'exécutent après les params de route, transforms de valeurs et assignments serveur, donc MCP peut lier un paramètre comme :workflowId une seule fois puis réutiliser le champ obtenu pour les samples bruts et les dimensions d'agrégat. Les modèles cibles sont résolus via les mêmes services record dynamic-data déployés et restent dans le scope de la route source sauf si targetScope est déclaré. Le builder de routes du dashboard peut configurer les cibles de rollup, fenêtres, champs bucket/count, dimensions et valeurs agrégées des routes metrics_ingest. L'exécution des rollups est reportée dans le résultat runtime et le payload d'événement metric, tandis que l'ingestion brute reste la source de vérité durable pour le debug et l'analyse à rétention courte. Les modèles de samples bruts peuvent déclarer runtime.retention avec maxAgeSeconds, un timestampField optionnel par défaut à createdAt, et un pruneBatchSize optionnel plafonné à 1 000. Les opérateurs MCP de confiance peuvent appeler yayaw_dynamic_data_retention_prune avec dryRun d'abord, une reason, et une limit optionnelle plus basse; le tool vérifie l'authz Yayaw dynamic-data:manage, résout la politique du modèle déployé, requête les records expirés via le service record générique, puis supprime chaque ligne expirée par le même chemin de delete record afin que les politiques de référence continuent de s'appliquer. Cela donne aux data planes chauds une table raw-metric bornée pour le debug tout en gardant les modèles de rollup minute/hour/day disponibles pour des fenêtres d'analytics plus longues. Les routes peuvent déclarer recordFilters: des filtres contrôlés serveur ajoutés avant les filtres fournis par l'appelant sur les lectures list/get et les lookups de mutation. Ils supportent les mêmes opérateurs de comparaison que les preconditions, avec une value littérale ou les sources now, actor_user_id, actor_api_key_id, actor_principal, organization_id et param. Ils supportent aussi in et not_in avec un tableau littéral non vide, ainsi que is_null_or_greater_than pour les timestamps de validité nullable. Utilisez-les pour limiter les routes runtime génériques à des records comme les grants du principal courant, les ressources possédées par un principal, les lignes d'organisation ou les ressources possédées par un paramètre de route. Ils complètent l'authz Yayaw dynamic-data; ils ne remplacent pas l'autorisation serveur. Les routes peuvent aussi déclarer relatedRecordFilters: des filtres d'existence cross-model contrôlés serveur, appliqués à la requête du record source elle-même. Un filtre déclare un sourceField, un modèle cible et un targetField, puis des filters cibles optionnels; le runtime compile cela en condition Postgres exists, afin que la pagination list et totalCount restent corrects. Les filtres cibles supportent les mêmes sources que les record guards/effects, dont actor_principal, param, source: "value" et source: "record". Utilisez ces filtres pour des contrats de lecture comme "lister uniquement les ressources qui ont un grant actif pour le principal courant" sans handler query spécifique au produit. Les cibles restent dans le scope de la route source sauf si targetScope est déclaré, et le bundle apply valide chaque modèle cible ainsi que targetField et les champs filtrés avant d'enregistrer les drafts. Le builder de routes du dashboard peut configurer le mapping de base champ source, slug de modèle cible, champ cible, scope cible et mode d'existence; les filters cibles imbriqués restent disponibles via les manifests MCP pour les contrats avancés. Les routes peuvent aussi déclarer recordGuards: des vérifications d'existence cross-model en lecture seule, évaluées avant que la route runtime lise ou mute des records. Les filtres de guard supportent les sources de filtres normales plus source: "value" avec sourceField, qui lit une valeur normalisée depuis le payload de la route, et source: "record" avec sourceField, qui lit une valeur depuis le record source existant sur les routes de type update. Utilisez-les pour exprimer des contrats produit comme "le principal courant doit avoir un grant control/admin actif sur cette ressource" sans ajouter de handler backend codé en dur. Les cibles de guard restent dans le scope de la route source sauf si targetScope est déclaré, et le bundle apply valide chaque modèle cible et chaque champ filtré avant d'enregistrer les drafts. Le builder de routes du dashboard peut configurer les modèles cibles de guard, scopes cibles, mode d'existence et filtres bornés avec les mêmes sources exposées par MCP. Les routes d'écriture peuvent déclarer recordEffects pour des effets de bord cross-model bornés après la réussite de la mutation source. Le premier kind supporté est update: il résout exactement un record cible par filters, puis applique des valueAssignments et valueIncrements contrôlés serveur via le même service dynamic-data record. Les filtres d'effet supportent les sources de filtres normales plus source: "value" avec sourceField, qui lit une valeur normalisée depuis le payload de mutation source, par exemple resource_id pour mettre à jour la ressource liée à un workflow. Les routes de type update peuvent utiliser source: "record" avec sourceField pour lire des champs immuables ou sensibles depuis le record source existant, par exemple terminer un workflow et mettre à jour la ressource depuis le resource_id stocké sans accepter ce champ depuis le client. Les effects peuvent déclarer conditions, évaluées sur le payload source normalisé avant le lookup de la cible. Utilisez les opérateurs equals, not_equals, in, not_in, is_empty et is_not_empty pour des effets de workflow comme décrémenter le compteur de workflows actifs d'une ressource seulement quand le nouveau statut du workflow est terminal. Les cibles d'effet restent dans le scope de la route source sauf si targetScope est déclaré, les écritures utilisent la dataVersion courante comme garde optimiste, et le résultat d'effet est exposé dans la réponse runtime et le payload d'événement. Le builder de routes du dashboard peut configurer les effets update avec modèle/scope cible, filtres cibles bornés, assignments contrôlés serveur et incréments numériques; les manifests MCP restent le meilleur chemin pour les gros bundles générés. Les routes d'écriture peuvent aussi déclarer preconditions: des filtres de champs bornés qui doivent matcher l'enregistrement cible avant la mutation. Elles supportent equals, not_equals, greater_than, greater_or_equal, less_than, less_or_equal, in, not_in, is_null et is_not_null; les opérateurs de comparaison acceptent une value littérale ou valueSource: "now" pour des gardes de timestamp comme expires_at > now, tandis que les opérateurs de set exigent un tableau littéral non vide. Les routes génériques update, delete, heartbeat, metrics et pairing_claim évaluent toutes le même contrat via le service record, afin que les extensions produit puissent modéliser des claims uniquement pending, des grants non révoqués, des sessions non expirées ou des transitions de workflow sans ajouter de handlers backend codés en dur. Les routes d'écriture peuvent déclarer clientValueFields pour n'accepter que certains champs bruts du payload client avant l'application des params, transforms, assignments, increments, guards et de la validation record. Quand clientValueFields est omis, le comportement historique reste inchangé; quand la liste est vide, aucun champ brut client n'est accepté. Le builder dashboard présente cette liste comme des checkboxes de champs plutôt que comme un texte libre séparé par des virgules. Les valueTransforms déclarés avec sourceType: "value" acceptent implicitement leur champ source comme entrée transitoire, afin qu'une route puisse hasher un code court sans persister ni allowlister séparément la valeur en clair. Utilisez cette primitive pour les endpoints de workflow étroits comme /status, les routes de révocation, les payloads heartbeat et les contrats d'ingestion de metrics. Les routes peuvent aussi déclarer clientValueConditions avec les mêmes opérateurs bornés que les assignments et les effects. Ces conditions sont évaluées sur le payload client normalisé après allowlist et transforms, mais avant les assignments contrôlés serveur. Si elles ne matchent pas, la route échoue avant tout lookup ou write; utilisez-les pour contraindre des entrées de cycle de vie, par exemple un endpoint de statut de session qui n'accepte que active, ended ou failed. Les routes d'écriture peuvent déclarer valueAssignments pour des valeurs de champs contrôlées serveur, appliquées après paramFieldMap et valueTransforms, et qui écrasent toute valeur client pour le même champ déclaré. Les sources d'assignments sont literal, now, actor_user_id, actor_api_key_id, actor_principal, organization_id et param. actor_principal stocke le format de référence principal générique utilisé par les références dynamic data, en préférant principal:api_key:<id> pour les acteurs API key et en retombant sur principal:user:<id> pour les acteurs session ou OAuth. Utilisez ces assignments pour des champs comme claimed_at, claimed_by_principal_id, des timestamps de heartbeat, des états de workflow et des valeurs de route organisationnelles qui ne doivent pas être confiées à l'appelant. Les assignments source: "now" peuvent déclarer un offsetSeconds borné, par exemple 300 pour un expires_at à cinq minutes, afin que les TTL de pairing sessions, grants temporaires ou endpoints expirants restent contrôlés par le serveur. Les assignments peuvent aussi déclarer conditions, avec les mêmes opérateurs bornés que les record effects, afin qu'une route conserve l'état de workflow fourni par l'appelant tout en assignant des champs compagnons serveur seulement pour certaines valeurs entrantes, par exemple ended_at seulement quand status in ["ended", "failed"]. Utilisez valueAssignments pour les valeurs qui doivent s'appliquer sur toutes les branches d'écriture. Les routes qui peuvent se diviser entre create/update peuvent aussi déclarer createValueAssignments et updateValueAssignments: les routes de création, les metrics append-only et la branche création des upserts heartbeat/metrics appliquent la liste create, tandis que les routes update, pairing_claim et la branche update des upserts heartbeat/metrics appliquent la liste update. C'est utile pour des valeurs comme les timestamps trust first_seen_at qui doivent être définis une seule fois à la création, ou des champs serveur accepted_at/updated_by_principal_id qui doivent changer uniquement lorsqu'un record existant est muté. valueTransforms s'exécute avant le lookup d'idempotence et la validation de l'enregistrement: les routes peuvent donc transformer params.code ou une valeur de requête en champ déclaré comme code_hash sans écrire la valeur source en clair. Les sources transformées sont supprimées par défaut des valeurs persistées, des params de réponse, des payloads d'événements et des params utilisés pour les topics de route; définir removeSource: false uniquement lorsqu'une route doit intentionnellement garder la source brute visible. Les algorithmes supportés sont sha256 et hmac_sha256; les transforms HMAC requièrent DYNAMIC_DATA_RUNTIME_TRANSFORM_SECRET. paramFieldMap peut lier des paramètres de chemin comme :workflowId à des champs déclarés du modèle avant validation. Le builder dashboard édite ce mapping comme des lignes de bindings structurées plutôt que comme un texte libre param=field. Les clés paramFieldMap et les valueTransforms avec sourceType: "param" doivent référencer un paramètre déclaré par le path effectif de la route, y compris les paths par défaut résolus depuis le type de route. Les routes de mutation réussies et non dry-run ajoutent un événement durable dans dynamic_data_runtime_events comme outbox générique avant de répondre. Le mapping est create -> created, update -> updated, patch_status -> status_changed, delete -> deleted, heartbeat -> heartbeat et metrics_ingest -> metric, et pairing_claim -> updated; list et get n'émettent pas d'événements runtime. Chaque événement stocke le scope, l'organisation, le modèle, l'enregistrement, la route, le type d'événement, le payload, l'acteur et les topics rendus depuis runtime.realtime.channels. Les templates de topics peuvent utiliser des valeurs réservées comme {organizationId}, {modelSlug}, {recordId}, {routeKind} et {eventType}, ainsi que les paramètres de route et les valeurs primitives du payload. Les channels avec des tokens manquants sont ignorés. La persistance d'événement est fail-open pour l'appelant: la mutation d'enregistrement peut réussir avec eventId: null tandis que les topics restent visibles dans la réponse. Cette outbox est le socle long terme pour SSE, WebSocket, workers et fan-out analytics. Les consommateurs d'événements runtime peuvent lire le feed générique sur /api/dynamic-data/runtime-events. L'endpoint utilise le même modèle d'acteur session ou bearer que l'exécution runtime, requiert dynamic-data-runtime:read pour les clés API runtime dédiées, conserve le fallback control-plane pour les clés MCP existantes, et vérifie toujours l'authz Yayaw dynamic-data:list pour le scope ou le modèle demandé. Les clients MCP de confiance peuvent inspecter la même outbox avec yayaw_dynamic_data_runtime_events pour le debug et les opérations, tandis que les consommateurs runtime chauds doivent continuer à utiliser HTTP/SSE. Le polling JSON est le mode par défaut. stream=true ou Accept: text/event-stream ouvre un stream SSE borné sur l'outbox, avec le curseur d'événement comme id SSE afin que les clients puissent reprendre avec Last-Event-ID. Les filtres incluent scope, orgId, topic, modelId, modelSlug, recordId, routeId, routeKind, eventType, cursor et limit; SSE accepte aussi pollMs et durationSeconds. yayaw_dynamic_data_ui_capabilities annonce l'endpoint, les transports supportés, le paramètre de curseur, le header de reprise et les filtres de query, tandis que chaque manifest runtime déployé expose des entrées runtimeEventFeeds dérivées de runtime.realtime.channels avec des templates de query JSON et SSE prêts à utiliser. Les clients remplacent les tokens de template de topic comme {recordId} ou {organizationId} avant la requête. Les définitions de modèles peuvent aussi déclarer ui.detail.eventTimelines pour rendre des timelines dashboard nommées avec eventType, routeKind, topic, topicTemplate, limit et recordFilter, afin d'inspecter l'activité ressource, workflow, trust ou métriques sans page custom. Les timelines qui déclarent routeKind doivent correspondre à un kind de route runtime activé, et celles qui déclarent topicTemplate doivent correspondre au template de topic d'un channel realtime déclaré, afin que les panneaux dashboard générés ne rendent pas silencieusement des feeds vides. Les filtres de containment sur les topics sont couverts par un index GIN sur l'outbox durable, afin que les feeds par channel restent viables quand les événements de sessions et metrics augmentent. Le transport actuel interroge l'outbox durable et émet des événements heartbeat et complete; une livraison future WebSocket ou pub/sub DB pourra réutiliser le même contrat d'événement. Les appelants runtime chauds doivent utiliser l'endpoint data-plane générique /api/dynamic-data/runtime/* plutôt que MCP. Par exemple, POST /api/dynamic-data/runtime/resources/heartbeat résout la route runtime déployée /resources/heartbeat et l'exécute via le même exécuteur générique. L'endpoint accepte la session dashboard active ou un acteur bearer Yayaw. Les clés API devraient utiliser dynamic-data-runtime:read pour les routes en lecture et dynamic-data-runtime:write pour les routes d'écriture; les clés MCP existantes restent compatibles via le fallback control-plane décrit plus haut. Les acteurs organisation ciblent par défaut les routes runtime de leur organisation, et l'endpoint vérifie toujours l'authz Yayaw dynamic-data avant toute exécution. Les query params scope=global|organization, orgId, extensionKey et routeKey peuvent sélectionner explicitement un scope ou désambiguïser une route d'extension stable quand l'acteur y est autorisé. Les bodies JSON peuvent transmettre values, filtres, search, sort, limites/offsets d'enregistrements, expectedDataVersion optimiste, et reason optionnel pour les routes qui déclarent reasonRequired. Après un déploiement de bundle, les clients MCP n'ont pas besoin de reconstruire ces appels HTTP eux-mêmes: postInstall.models[].routes[].dataPlane expose l'endpoint, les query params, l'action de clé API, l'action d'autorisation Yayaw, la politique de rate limit, et l'exigence de reason pour chaque route runtime déployée. Les channels realtime exposent le même passage de relais via postInstall.models[].realtime[].dataPlane: json est l'endpoint de polling, sse ajoute stream=true, et cursorParam plus resumeHeader indiquent comment reprendre la lecture des événements. Les réponses HTTP réussies exposent une enveloppe data-plane publique: result, params, rateLimit, dryRun, runtimeEvent, l'action d'authz effective, les conflicts de routes et une métadonnée de route sûre (id, key, kind, method, path, modelSlug, scope, dashboardRoute, dashboardRecordRouteTemplate et dashboardSection). Les détails internes d'exécution comme les noms de tables physiques restent côté serveur. Quand une route déployée déclare rateLimit.maxRequests et rateLimit.windowSeconds, l'endpoint data-plane applique cette limite avant exécution avec le sujet configuré (actor, ip, organization, principal ou record). Les réponses comptées incluent RateLimit-Limit, RateLimit-Remaining et RateLimit-Reset; les routes dépassées renvoient 429 avec Retry-After. Les champs reference utilisent le même contrat de modèle natif. Un champ reference doit déclarer soit relation.modelSlug pour un autre modèle dynamique, soit relation.targetEntity pour une entité système (user, organization, team, api_key, ou principal). Il peut aussi déclarer targetScope, displayField, valueField et l'intention onDelete pour les références vers modèles dynamiques. La table générée stocke la référence dans une colonne texte native et indexe les champs reference par défaut pour garder les modèles riches en relations, comme principals, ressources, grants et workflows, rapides. Les opérations create et update d'enregistrements résolvent les modèles dynamiques cibles déployés et refusent les références vers des enregistrements absents avant l'écriture, avec id par défaut ou le valueField déclaré lorsqu'il est configuré. Les références système stockent aujourd'hui des ids et valident user, organization, team et api_key dans les tables système Yayaw. Les références principal utilisent des valeurs stables comme principal:user:<userId> et principal:api_key:<keyId>; les principals user doivent résoudre vers un utilisateur membre de l'organisation source quand elle existe, et les principals API key doivent résoudre vers une clé active de cette organisation. Les autres types de sujet comme resource ou agent résolvent via des modèles dynamiques déployés dans le même scope: soit un modèle déclare extension.entityBindings avec kind: "represents" et target égal au type de sujet, puis l'id de la référence principal est comparé à l'id d'enregistrement de ce modèle, soit un modèle générique représente principal et stocke le sujet dans des champs conventionnels type et ref_id. Le builder no-code de brouillons de modèles du dashboard expose les mêmes réglages de référence, d'index composites, de contraintes uniques et de présentation UI par champ pour les nouveaux modèles, afin que les opérateurs puissent créer des contrats de tables natives riches en relations et des surfaces Yayaw Table formulaire/table utilisables sans éditer le JSON brut du modèle. Les sélecteurs de champs pour index composite et contrainte unique sont des pickers structurés dérivés des champs du brouillon, tandis que les bundles MCP continuent d'utiliser des tableaux de champs explicites. Le builder inclut les listes d'options finies pour les widgets select, radio et multiSelect, ainsi que les bindings de champ value-type pour les éditeurs primitifs dynamiques. Les builders de routes runtime résolvent les slugs de modèles cibles visibles en selects et dérivent des pickers de champs cible pour les rollups métriques, les filtres related-record, les guards et les effects, tout en conservant les valeurs existantes inconnues pour compatibilité. targetScope: "same" suit le scope du modèle source pour les cibles dynamiques, tandis que les cibles organization explicites exigent un contexte organisation. Les suppressions d'enregistrements appliquent les politiques de référence dynamiques avant de retirer la cible: l'absence de onDelete se comporte comme restrict, restrict bloque la suppression tant que des dépendances existent, et set_null nullifie les champs référents optionnels en incrémentant leur data version. cascade est volontairement refusé tant que des dépendances existent, jusqu'à ce que Yayaw ait une suppression transactionnelle de graphe avec garde de cycles pour les tables générées. Dans le formulaire dashboard des enregistrements, les champs reference vers modèles dynamiques peuvent devenir des selects quand le modèle cible est visible pour l'acteur: valueField fournit la valeur stockée, displayField fournit le libellé d'option, et targetScope: "same" suit le scope du modèle source. Les références système vers user, organization, team et api_key peuvent aussi devenir des selects depuis les tables système Yayaw scopées; principal compile les sujets user, API key, modèles de sujets dynamiques et enregistrements de registre principal dynamique en options préfixées quand ils sont disponibles. Les options principal dynamiques suivent le même scope que la validation runtime: les modèles déployés avec extension.entityBindings.kind: "represents" peuvent représenter des types de sujets concrets, tandis qu'un modèle qui représente principal peut exposer des enregistrements type plus ref_id. Si les options ne peuvent pas être chargées, le champ retombe sur l'input texte normal sans affaiblir l'autorisation. Les clients MCP peuvent appeler yayaw_dynamic_data_reference_options avec un slug de modèle et une clé de champ pour charger les mêmes options sûres que le dashboard pour les références système et les références vers modèles dynamiques, après vérification server-side des permissions du modèle source et du modèle cible. La liste des modèles du dashboard dynamic-data expose aussi un flux no-code borné pour les acteurs avec dynamic-data:manage. Il crée des documents de modèle draft en table native depuis un nom, un slug, une portée, une section de navigation et des champs scalaires, puis passe par le même pipeline de validation, publication et déploiement dynamic-data que MCP avant toute DDL Postgres générée. Le même builder peut aussi configurer des runtime.realtime.channels et des ui.detail.eventTimelines bornés, en validant les types de routes des timelines contre les routes runtime activées et les templates de topics contre les channels realtime déclarés avant l'enregistrement du brouillon. Les filtres d'événements des channels realtime sont rendus comme des checkboxes d'événements structurées, tandis que les bundles MCP peuvent toujours envoyer des arrays d'événements ou des chaînes séparées par des virgules. Le détail modèle expose aussi une preview du plan de déploiement pour la révision publiée et le bouton qui applique ce plan vérifié, tandis que l'action de ligne de la liste ouvre cette review au lieu d'appliquer la DDL directement. La preview affiche le même statut d'évolution de schéma et les mêmes compteurs de changements bloquants que MCP. Le détail affiche aussi un panneau de cycle des révisions avec les marqueurs dernière, publiée et déployée, les révisions immuables récentes et le résumé du diff déployée vers dernière produit par les mêmes services de révisions exposés via MCP. Les acteurs avec dynamic-data:manage peuvent restaurer une ancienne révision immuable depuis ce panneau après confirmation de la cible et saisie d'une raison; la révision restaurée devient le dernier brouillon et nécessite encore une publication explicite puis une review de déploiement. Ces actions réutilisent les services dynamic-data partagés et l'autorisation server-side dynamic-data:manage au lieu de faire confiance à l'état du dashboard. Admin > Dynamic Data et chaque racine de section dynamic-data générée par MCP affichent un panneau de readiness opérateur au-dessus de la table des modèles, avec le résumé des modèles visibles, brouillons bloqués, backlog publication/déploiement, routes runtime déployées, routes d'ingestion métrique et compteurs de rollups, feeds d'événements runtime, couverture UI/navigation et endpoints de passage MCP/runtime à vérifier après application d'un bundle, ainsi que les compteurs de liaisons d'extensions d'entités et de cibles liées. Les clients MCP peuvent appeler yayaw_dynamic_data_ui_capabilities pour découvrir les types de champs, capacités d'extension, kinds de liaisons d'entités, targets système réservées, kinds/defaults de routes runtime API, actions d'authz runtime, sujets de rate-limit, événements realtime, kinds de projections, renderers de colonnes Yayaw Table, aliases sûrs, widgets de formulaire et aliases de widgets, sources d'options de référence, outils de discovery d'extensions, sections de navigation supportées, racine control-plane Admin statique et pattern de route des sections dynamiques avant de générer un document de modèle. Le même payload de capabilities expose dashboard.operatorReadiness, avec la racine control-plane statique, le pattern de sections générées, états d'attention, champs résumés, garanties d'authz et endpoints de passage MCP/runtime rendus par le panneau de readiness du dashboard. Les réponses bundle apply et post-install MCP exposent aussi les compteurs de routes métriques, compteurs de rollups, clés de routes métriques, compteurs de liaisons d'extensions et cibles liées par modèle pour vérifier le routing analytics et les extensions natives sans code produit spécifique. Le payload de capabilities inclut aussi les defaults de compatibilité par type de champ pour widgets de formulaire, renderers de table et éditeurs inline, les racines de routes par section, modes d'affichage, presets de layout, modes de clic ligne, variantes de colonnes, modes d'affichage URL, éditeurs/triggers d'inline edit, presets galerie, sources d'assignation, de filtre et de transform runtime, filtres de timelines de détail, exemples de targets génériques comme ressources et activités, et colonnes système sûres (id, dataVersion, timestamps, IDs acteurs et ID organization) utilisables dans la config table, le tri, les filtres et la recherche globale. La validation de modèle signale des warnings non bloquants quand les widgets ui.form, renderers de colonnes ui.table ou éditeurs inline déclarés sortent de ces defaults de compatibilité, afin que les interfaces générées par MCP restent neutres produit tout en exposant les choix UI suspects avant déploiement. ui.table configure la surface Yayaw Table des enregistrements déployés, ui.form configure le formulaire create/edit généré par Yayaw Table, et ui.nav ajoute les modèles déployés à la sidebar, aux breadcrumbs, à la navigation de section backend et à la recherche du command menu quand l'objet ui.nav est présent, sauf si enabled vaut explicitement false. La navigation utilise la section dashboard configurée, built-in ou générée par MCP, et pointe vers la route canonique dynamic-data /dashboard/{section}/dynamic-data/{scope}/{modelSlug} avec le modèle cible déjà sélectionné. Le créateur no-code de modèle dans le dashboard écrit les mêmes métadonnées ui.nav que MCP pour enabled, le libellé, l'icône, l'ordre et la section, afin que les opérateurs puissent promouvoir un brouillon en table native dans une navigation dashboard propre sans toucher au code. Quand des liens statiques de section et des liens de modèles dynamiques coexistent, la sidebar sépare les liens de modèles générés tandis que les breadcrumbs, la navigation de section et la recherche du command menu n'exposent que des destinations navigables. Admin > Dynamic Data est la surface statique de control plane pour gérer les modèles. Les surfaces produit doivent être déclarées par MCP avec ui.nav.section; toute section normalisée hors du set built-in devient une section d'app dashboard à /dashboard/{section}/dynamic-data dès qu'au moins un modèle déployé visible y est rattaché. Les sections d'app générées mettent d'abord en avant la surface Yayaw Table d'enregistrements du modèle sélectionné, les métadonnées de détail, les panneaux d'enregistrements liés et les timelines d'événements runtime ; le cycle de vie/readiness control-plane du modèle reste disponible depuis Admin > Dynamic Data. Les bundles peuvent définir ui.nav.sectionLabel, ui.nav.sectionIcon et ui.nav.sectionOrder pour piloter la section top-level générée sans changement de code ; définissez ui.nav.enabled=false uniquement pour les modèles déployés qui doivent rester cachés de la navigation. Les modèles sans section configurée restent dans Admin > Dynamic Data, et l'ancienne sélection par query ?model=&scope= reste acceptée pour compatibilité. ui.nav.icon doit faire partie des valeurs nav.icons renvoyées par yayaw_dynamic_data_ui_capabilities; les icônes non supportées sont ignorées pour laisser la sidebar utiliser le fallback TableProperties. yayaw_dynamic_data_runtime_manifest renvoie aussi les fields normalisés, runtimeApiRoutes effectives, runtimeEventFeeds, systemColumns, ui.dashboardRoute, ui.dashboardRecordRouteTemplate, ui.dashboardSection, ui.table, ui.form, ui.detail et ui.runtimeActions pour chaque modèle déployé afin que les clients MCP puissent pointer vers la surface dashboard modèle résolue, ouvrir un enregistrement sélectionné via le template recordId={recordId}, inspecter les actions runtime ligne/détail, s'abonner aux feeds d'événements runtime JSON/SSE déclarés, et rendre les interfaces Yayaw Table configurées sans reconstruire les règles de schéma, de record-system, de route, d'event-feed ou d'UI. La checklist de vérification post-install du bundle inclut des étapes runtime_ui_action_dry_run pour les actions dont la cible runtime se résout vers une route déployée. La navigation par modèle est filtrée avec l'authz Yayaw sur dynamic-data:list et le resourceId du modèle déployé: un grant global ou organisation large expose chaque modèle visible dans ce scope, tandis qu'un grant resource-scoped n'expose que le modèle correspondant. Les gardes des routes canoniques de modèle résolvent {scope, modelSlug} vers l'item de registre déployé puis rejouent le même check dynamic-data:list avec le resourceId du modèle, afin que les deep links directs suivent le même contrat resource-scoped que la sidebar au lieu d'exiger un accès dynamic-data large. La liste de modèles du dashboard utilise le même filtre par modèle, affiche les labels et capacités d'extension comme colonnes Yayaw Table filtrables, et dynamic-data:manage par modèle contrôle les actions d'enregistrement générées. Le filtrage de navigation conserve le parent de section configuré quand un modèle dynamique autorisé est visible, même si cette section n'a aucun enfant statique visible pour l'acteur courant. Le détail de modèle du dashboard dynamic-data expose aussi les clé/label/version d'extension, capacités, tags, liaisons d'entités, le label, l'icône, la section et la route canonique ui.nav résolus, les routes API runtime, les feeds d'événements runtime, les topics realtime, les projections et les métadonnées de surfaces UI déployées, afin que les contrats produit écrits par MCP soient inspectables en production sans ouvrir le JSON brut du modèle. Les nouvelles interfaces générées par MCP peuvent aussi apparaître sur les pages natives via le panneau réutilisable d'extensions d'entités: les réglages d'organisation le montent pour l'organisation active, les réglages de compte pour l'utilisateur courant, et ces panneaux serveur restent cachés tant qu'aucune extension autorisée n'existe afin de garder les pages natives silencieuses par défaut. Admin > Users monte le panneau client pour le détail utilisateur sélectionné, et les réglages développeur le montent sous les clés API listées; ces panneaux client restent aussi cachés tant qu'aucune extension autorisée n'existe. Les actions générées par MCP devraient définir ui.table.rowActions[].runtime.routeKey avec la clé stable de la route runtime.api.routes[].key; le builder no-code du dashboard écrit le même contrat route-key stable pour les actions bornées et peut aussi les exposer dans la vue détail d'enregistrement sans ajouter de code repository. La méthode et le chemin sont optionnels et restent un fallback de résolution et la source des paramètres de chemin. Lorsqu'une action déclare directement method/path, ou les surcharge en plus de routeKey, la cible effective doit correspondre à une route runtime activée après résolution de runtime.api.basePath. Cette vue de contrat expose aussi les actions ui.table.rowActions avec routeKey, méthode/chemin runtime, mode de rafraîchissement, conditions, params de chemin, bindings, valeurs littérales, inclusion d'ID record et verrou optimiste, puis liste les boutons ui.detail.actions et l'action ligne qu'ils référencent. yayaw_dynamic_data_ui_capabilities expose le même contrat d'exécution des row actions sous forme lisible machine: refresh par défaut, checks serveur, ordre de résolution de cible, fallback method/path, path params, valeurs littérales, value bindings, enabledWhen, verrou optimiste et support route-key. Les pages natives du dashboard peuvent aussi afficher les extensions écrites par MCP sans ajouter de React spécifique au produit. Le loader partagé d'extensions d'entités dashboard découvre les modèles déployés dont extension.entityBindings cible une entité native comme organization, interroge seulement le scope global et le scope de l'organisation active de l'acteur, filtre chaque modèle avec dynamic-data:list ou dynamic-data:manage avant de charger les compteurs, puis retourne la route canonique du modèle, le champ de liaison, les métadonnées d'extension et le compteur autorisé. Les settings d'organisation utilisent ce panneau réutilisable pour la cible organisation active, les settings de compte utilisent le même panneau pour la cible utilisateur courant, et Admin > Users monte une version client pour le détail de l'utilisateur sélectionné. Les settings développeur montent le même panneau client sous chaque clé API listée quand un modèle déployé étend api_key, tout en gardant la page silencieuse lorsqu'aucune extension de clé API n'existe. Les opérateurs peuvent ainsi voir les modèles dynamiques en tables natives qui étendent des entités natives coeur et ouvrir leur surface depuis la page native. Quand la page native fournit un identifiant de cible, le panneau pointe vers la route dynamic-data canonique avec les filtres de la table Yayaw Table du modèle encodés dans l'URL pour les bindings basés sur un champ, ou avec recordId pour les bindings represents, et le preload serveur du dashboard applique cet état avant de rendre la table d'enregistrements. yayaw_dynamic_data_ui_capabilities expose ces points de montage natifs sous extension.nativeSurfaces, avec la cible, la route dashboard, le scope et la source d'identifiant de cible comme current_user_id, selected_user_id, listed_api_key_id ou active_organization_id, afin que les clients MCP sachent où un binding represents, extends ou references deviendra visible avant d'appliquer un bundle. ui.form permet au MCP de piloter l'ordre, la visibilité et les sections des champs, les libellés, aides, placeholders, états read-only/désactivés, layout drawer/modal, et des hints de widgets sûrs comme text, textarea, number, select, radio, switch, checkbox, url, email, tel, password, date, json, multiSelect, value-type et collection. ui.form.sections groupe des champs existants sous des sections titrées sans changer la forme du payload record; les champs omis de toutes les sections restent rendus après les sections déclarées. ui.table accepte les renderers Yayaw Table sûrs (text, number, boolean, date, select, tag, multiSelect, image, url, code, json, string, dynamicType) ainsi que des alias comme jsonb, badge, option, multiOption, currency, decimal, integer et dynamic-type; il peut aussi piloter les vues sauvegardées, l'inline edit, les empty states, les formats nombre/date, les couleurs de tags, le nombre d'items de prévisualisation JSON via columns.<field>.maxItems, l'affichage des guillemets string via columns.<field>.showQuotes, la participation à la recherche globale via columns.<field>.enableGlobalSearch et les modes table/kanban/gallery. Le builder no-code du dashboard peut configurer un sous-ensemble borné de ces métadonnées de vue, dont le mode de sélection de lignes, les modes disponibles, le mode par défaut, le regroupement/titre/colonnes de cartes kanban et la présentation image/titre/colonnes de cartes galerie, sans éditer directement le document brut du modèle. Le contrôle de sélection peut garder l'inférence des colonnes virtuelles MCP en auto, ou persister une valeur Yayaw Table enableRowSelection explicite. Les modes d'affichage, les références de champs kanban/gallery et les listes de champs d'index ou de contraintes uniques sont rendus comme des selects ou multi-selects structurés dérivés des champs du brouillon courant, tandis que l'entrée MCP peut toujours envoyer les mêmes valeurs sous forme d'arrays ou de chaînes séparées par des virgules. Il peut aussi éditer des options bornées au niveau des colonnes Yayaw Table pour les en-têtes, le tri, les filtres, la recherche globale, le groupement, les tailles, les calculs par défaut, les presets de date, les formats numériques, les modes d'affichage URL, les limites d'aperçu JSON, les couleurs de tags, l'affichage des guillemets string et les clés de type dynamique. Les champs select/radio peuvent recevoir des options fournies par MCP, les champs numériques min, max et step, les champs date minDate et maxDate sérialisés, les champs textarea/json rows, les champs URL la prévisualisation de métadonnées, et les widgets value-type doivent déclarer un valueTypeField valide pour que Yayaw Table choisisse l'éditeur. Les widgets collection sont supportés sur les champs modèle json via un bloc collection déclaratif avec champs d'item, colonnes, create item/actions optionnels, labels d'item, empty labels et validation de champs d'item obligatoires; le dashboard compile ce bloc vers l'éditeur collection natif de Yayaw Table sans accepter de renderer React custom depuis MCP. Le builder de modèle no-code peut configurer les champs d'item, colonnes, libellés et valeurs par défaut des collections adossées à du JSON sans éditer directement le document de modèle. Les widgets multiSelect sont aussi supportés sur les champs modèle json quand le champ déclare des options finies string ou number; le dashboard les compile vers le champ formulaire checkbox-list natif de Yayaw Table et stocke les valeurs sélectionnées comme tableau JSON. Cette configuration UI ne donne jamais d'accès aux données et ne modifie pas la DDL Postgres générée. Le champ de formulaire Yayaw Table custom demande encore des renderers React et ne doit pas être exposé par les documents modèle MCP. Les tables dashboard d'enregistrements dynamiques envoient la pagination, les filtres simples, les filtres avancés Yayaw Table supportés et le tri à Postgres au lieu de découper un échantillon fixe côté client; filtres, recherches et tris restent limités aux champs déclarés et aux colonnes système sûres. La recherche globale utilise par défaut id plus les champs textuels déclarés, ou les champs explicitement marqués enableGlobalSearch dans ui.table.columns. L'outil MCP yayaw_dynamic_data_records_query accepte soit une chaîne de recherche, soit { query, fields } pour une recherche bornée explicite. Les filtres avancés supportés couvrent texte contains/starts/ends/not-contains/empty, égalités et ranges nombre/date, inclusion/exclusion select, et une containment textuelle conservatrice pour les valeurs multi-select stockées comme JSON.

Lors de l'ajout d'un outil, préférez une implémentation service-layer réutilisable par les server actions UI ou les scripts. Les handlers doivent rester fins: valider l'entrée, vérifier les permissions, appeler le service partagé et écrire l'audit.

Ressources

  • yayaw://status
  • yayaw://schemas
  • yayaw://pages/{scope}/{slug}
  • yayaw://data/{scope}/{modelSlug}/{entrySlug}
  • yayaw://email-templates/{slug}
  • yayaw://audit/recent

Les workflows facturation, remises, médias, design tokens, feature flags et domaines d'organisation sont exposés comme outils plutôt que comme ressources MCP. Les lectures de pages et données scopées organisation doivent utiliser les outils lorsqu'un orgId explicite est requis.

Règles De Sécurité

Les écritures production exigent tout ce qui suit:

  • control-plane-mcp-enabled est activé.
  • Une clé API Better Auth valide ou un access token OAuth est fourni comme Authorization: Bearer <token>.
  • La clé ou le token OAuth a la permission de plan de contrôle requise.
  • L'acteur passe les checks d'autorisation de ressource Yayaw sous-jacents.
  • L'entrée de l'outil inclut reason.

Les clés détenues par l'organisation sont en plus limitées à leur organisation pour les opérations contenu/média bornées. Les clés personnelles nécessitent toujours la permission seedée Yayaw apikey:manage pour être émises depuis le dashboard.

Les outils de domaines publics d'organisation suivent la même frontière d'organisation. Les clés d'organisation opèrent uniquement sur leur organisation; les clés personnelles doivent passer un orgId explicite. Les mutations de domaines exigent organization:update ou organization-settings:manage, un reason, et utilisent dryRun pour le prévol. PUBLIC_DOMAIN_PROVIDER=vercel attache le hostname au projet Vercel partagé, lit les enregistrements DNS recommandés et vérifie les challenges de propriété Vercel. PUBLIC_DOMAIN_PROVIDER=manual-dns génère un token TXT de propriété et vérifie le DNS public pour le TXT attendu plus les indications CNAME/A configurées.

yayaw_deploy_status renvoie des métadonnées de déploiement neutres vis-à-vis du fournisseur. Sur Vercel, il expose les champs runtime Vercel; sur les déploiements auto-hébergés, il expose les valeurs statiques DEPLOYMENT_URL, DEPLOYMENT_ENV, DEPLOYMENT_GIT_COMMIT_SHA et DEPLOYMENT_GIT_COMMIT_REF lorsqu'elles sont configurées.

Les écritures de composants sont des imports catalogue ou recompilations en base depuis des snapshots source stockés. yayaw_component_recipe_import n'accepte que des recettes déclaratives bornées adossées à des kinds runtime sûrs présents dans le dépôt, crée une révision draft_ready et laisse la publication explicite. Les outils MCP composants ne mutent jamais les fichiers du dépôt en production et n'exécutent jamais de source stockée en base au runtime. Utilisez yayaw_components_list(includeSchemas=true) avant de composer des sections ; le contrat annoncé liste aussi les noms de capacités créatives inférés par le serveur, les types et champs obligatoires des props de chaque nœud, les rôles explicites de familles typographiques, les surfaces sémantiques stables et les alias de compatibilité qui ne peuvent pas attester une intention sémantique. Il expose aussi les handlers de formulaires CMS exacts enregistrés. Le seul handler enregistré est POST /api/cms/forms/personalization-request avec un encodage multipart. Il exige email (email), firstName (text), consent (checkbox) et photo (upload natif images) ; birthDate (date) et message (textarea) sont optionnels. Les noms, types de contrôle et indicateurs obligatoires doivent correspondre exactement. La photo doit être au format JPEG, PNG ou WebP et ne pas dépasser 8 Mo ; elle est stockée de manière privée. La soumission crée une demande de personnalisation destinée au suivi par un opérateur, pas un checkout, une confirmation de commande ou un paiement. Tout autre handler ou action doit utiliser email-fallback au lieu d'inventer un comportement POST ou multipart same-origin.

Les opérations réservées aux admins sont yayaw_cms_personalization_requests_list, yayaw_cms_personalization_request_get, yayaw_cms_personalization_request_status_update, yayaw_cms_personalization_request_delete et yayaw_cms_personalization_requests_prune. Get émet une URL de photo same-origin chiffrée et à courte durée, qui n'expose aucune coordonnée de stockage, est servie comme pièce jointe no-store et expire avec une marge avant la rétention. La fenêtre terminale ne retourne aucune URL. Delete supprime définitivement une demande et sa photo privée, et prune prend en charge le dry-run avant de retirer les demandes dont la rétention de 180 jours est expirée. Le worker Page AI exécute la rétention bornée et le nettoyage objet réessayable indépendamment des jobs IA. Ces cinq opérations sur des données personnelles exigent control-plane:admin, un acteur plateforme sans portée organisation et l'autorisation Yayaw globale user:manage. Un administrateur d'organisation ou une claim de scope admin sans ce droit RBAC ne peut pas y accéder. Pour creative-composition, les inputs runtime sont limités à propSchema : apiSchema et harnessSchema doivent être omis ou vides, fixtures doit être vide, et leurs réglages API, harness ou fixtures associés ne peuvent pas transporter du contenu runtime caché. Chaque prototype candidat enregistré déclare un manifeste requiredCapabilities non vide. Le dry-run bloque la persistance tant que les révisions publiées exactes placées à la racine des compositions mappées ne reproduisent pas les manifestes régionaux exacts de structure, d'implémentation et de rendu. Le manifeste calculé d'un candidat doit inclure à la fois display_typography et responsive_recomposition, au moins une signature structurelle parmi layered_composition, sticky_narrative, custom_grid_ratios, continuous_canvas et compact_band, ainsi qu'au moins un traitement de direction artistique parmi image_art_direction, paper_collage, watercolor_wash, media_texture et decorative_shapes. text.style: "note" seul n'infère pas paper_collage. Au moins une région signature doit inférer ensemble la typographie display, la recomposition responsive, une signature structurelle et un traitement de direction artistique depuis sa recette exacte. Une région new_recipe exige une révision créée dans le même contexte dont la topologie compositionHash n'est pas déjà présente sur une creative-composition publiée dans l'inventaire préparé, tandis que reuse_published exige son composant publié exact. Un composant imbriqué, un arbre différent aux mêmes capacités grossières ou des bindings d'instance modifiés échouent ; l'auto-déclaration ne compte pas. Le contrat annoncé inclut canvas.height avec content et band ainsi qu'un mobileGap borné ; image.mobileAspect, image.desktopAspect et image.height pour le responsive ; decoration.treatment: "wash" ; frame.material pour le papier, frame.rotation et placement.rotation bornées, text.style: "note" ; text.weight ; visibility.show ; le nœud sémantique disclosure ; section.overflow et frame.overflow explicites ; ainsi que des valeurs bornées pour image.blend, image.opacity et l'appearance des contrôles. Un overflow visible sur section/frame n'infère continuous_canvas que si un placement descendant, une frame tournée ou scotchée, ou une grande décoration/wash peut réellement traverser la frontière. Un blend d'image non standard (multiply, screen ou soft-light) ou une opacité non solide (soft ou faint) infère media_texture. Actions et submits acceptent bare, outline, paper, solid ou underline ; les fields acceptent bare, line, outline ou paper ; les uploads acceptent bare, dashed ou paper. Ces primitives infèrent selon leur usage compact_band, responsive_media, watercolor_wash, paper_collage, typographic_weight, responsive_recomposition et responsive_navigation. Le client construit un ledger de faisabilité pour chaque région du prototype depuis ces schémas exacts. Chaque région déclare des nœuds allowlistés exacts avec chemins canoniques (tree, puis tree.children.N récursivement), props bornées, recipePropSchema déclaratif exact, liste recipePropNames possédant les mêmes clés et recipePropBindings exacts. Les props requises sans défaut exigent un binding, les clés de binding appartiennent au schéma et chaque image.src doit être exactement $props.<identifier>, avec cette prop liée comme media_asset ou localized_media_asset à un média durable de l'inventaire préparé. mediaAssetIds égale l'ensemble exact et dédupliqué des IDs utilisés par tous les bindings média. Yayaw reconstruit la recette complète et la valide par le contrat runtime canonique. Il calcule feasibility.compositionHash depuis les noms de nœuds, chemins canoniques et props structurelles uniquement, après exclusion du contenu, des médias, actions, choix de palette surface/tone et autres props non structurelles, avec normalisation des références runtime. Il calcule séparément feasibility.implementationHash depuis les noms/schéma/arbre exacts et feasibility.renderManifestHash depuis cette implémentation et ses bindings d'instance exacts ; le client ne fournit aucun de ces hashes. compact_band exige height: "band", responsive_media exige des ratios mobile et desktop non-auto distincts sans hauteur fixe, et responsive_navigation exige un disclosure mobile avec une branche desktop non vide. Les props d'ordre responsive (desktopOrder ou mobileOrder) exigent un parent direct grid ou stack, et les props de span responsive (desktopSpan ou mobileSpan) un parent direct grid ; un ancêtre compatible ne suffit pas. La grille, les stacks directionnels, les placements et le sticky partagent le breakpoint de recomposition md. mobileSpan: "2" est refusé car la grille mobile possède une seule colonne explicite. Le renderer créatif canonique transmet sans modification les props liées exactes, y compris les noms historiques de fixtures catalogue comme value, page, checked, disabled et pressed. Quand un aperçu prototype ou draft ne dispose pas du contexte de formulaire live, il conserve exactement le layout et les contrôles du formulaire enregistré dans un wrapper inerte et impossible à soumettre. Une couverture globale ne rend pas réalisable une région bloquée. Chaque texte, lien, label, placeholder, texte alternatif ou message d'upload visible dans un prototype doit être un sink direct $props.<identifier>. Les bindings de texte sont des littéraux localisés exacts pour toutes les locales préparées ; les liens utilisent des cibles littérales ou localisées sûres et bornées. Les bindings global-data critiques et repeat/$item/$index sont refusés : chaque élément répété visible est développé en nœuds planifiés explicites.

Le design de page CMS est un workflow contextuel orchestré par le client. Lisez et approuvez d'abord toute modification large de design tokens, puis commencez par yayaw_cms_design_prepare et répondez aux questions utiles par l'élicitation MCP ou yayaw_cms_design_resume. Son visualIntent durable porte le niveau d'ambition : une direction nouvelle ou évoluée dérive une stratégie créative et ne peut pas être abaissée pour éviter les trois prototypes. Le modèle client utilise l'inventaire et charge chaque stage lié à l'acteur requis via yayaw_cms_design_guidance_get : brief, composition, critique, ainsi que rework pour un rework. Si une modification large de tokens est découverte après la préparation, abandonnez le contexte, enregistrez la couche et préparez à nouveau ; un refresh ne peut pas déplacer un contexte vers un autre hash de tokens.

Avant l'enregistrement des prototypes, finalisez chaque image de récit ou de recette via yayaw_media_upload_prepare puis yayaw_media_upload_complete, ou via yayaw_media_import_url, avec le designContextId actif. Exécutez ensuite un unique yayaw_cms_design_inventory_refresh de présélection. Il ne peut qu'ajouter des médias finalisés de ce contexte, sans modifier les baselines de catalogue ni le hash des design tokens. Les planches candidates et les screenshots runtime post-enregistrement sont interrogés directement par ID et ne doivent jamais déclencher ce refresh.

Pour un workflow créatif, les trois planches composites candidates inspectées sont uploadées avec le designContextId, puis yayaw_cms_prototypes_register est appelé avec kind: "generated_set_v2" et exige exactement trois artefacts image distincts avec hashes vérifiés côté serveur et des dimensions d'au moins 1200 × 900. Un prototype utilisateur peut ancrer un candidat mais ne réduit jamais le jeu sous trois. Chaque planche contient toutes les sections prévues à une fidélité suffisante. Yayaw calcule aussi une empreinte normalisée de matrice d'arêtes en niveaux de gris 16 × 16 pour chaque planche complète stockée et rejette les clones ou les planches sous la distance structurelle minimale. Ce garde-fou grossier de structure normalisée ne prouve jamais à lui seul une direction ou une fidélité distincte ; il complète les trois signatures produites par le serveur, la critique par paires, l'inspection visuelle et une Visual QA substantielle. Tous les candidats déclarent les mêmes IDs et libellés de région stables, dans le même ordre, pour les futures preuves prototypeRegionRef, des preuves d'originalité distinctes pour la silhouette, la typographie, le rythme narratif, la métaphore et le dispositif signature, ainsi que les décisions structurées de chaque stage de guidance requis. Chaque directive unique est classée prototype_applicable, implementation_only ou not_applicable; une directive applicable aux prototypes porte un emplacement observé et validé pour chacun des trois candidats. Les IDs soumis doivent respecter exactement les classifications et garde-fous imposés par le serveur. Les traces version 2 exposent un verificationCheckId déterministe et optionnel possédé par le serveur. Les plans ultérieurs ne soumettent que les IDs exacts des directives d'implémentation via guidanceVerification ; Yayaw exécute le checker à son garde-fou fixe et conserve la preuve hashée. Le client ne peut inventer ni checker, ni chemin de preuve, ni section de revue. not_applicable n'est accepté que pour une règle autorisée côté serveur, avec une raison précise et dans la limite bornée du stage. Les IDs soumis doivent couvrir exactement une fois toutes les directives de chaque stage requis et chargé ; un ID omis, dupliqué ou inventé bloque l'enregistrement. Yayaw les conserve avec ses propres hashes du contenu exact de guidance. Un rejet ou une combinaison demandée exige un jeu de remplacement de trois planches enregistrées. Un candidat dont une région exige une primitive, un asset, une police, une interaction, un handler, un comportement responsive ou un fallback indisponible est bloqué avant présentation et enregistrement ; le client remplace le jeu complet de trois au lieu de promettre une approximation ultérieure. Le même appel d'enregistrement contient exactement trois comparaisons structurées : A/B, A/C et B/C. Chaque paire doit être matériellement distincte par sa silhouette, sa typographie, son rythme narratif, sa métaphore visuelle, son dispositif signature et sa résolution mobile/thème, avec des preuves <artifactId>#<regionId> exactes pour les deux candidats dans chaque région ordonnée. Le serveur exige aussi trois signatures de structure, d'implémentation et de rendu A/B/C distinctes. Les compositionHash régionaux ordonnés forment la signature structurelle du candidat. Le manifeste normalisé du page shell est épinglé séparément et doit correspondre exactement au candidat sélectionné dans le plan et le runtime ; il ne fait pas partie de la signature de distinction entre candidats. Les implementationHash et renderManifestHash régionaux ordonnés forment les deux autres. Des changements de contenu, média, palette, défauts de schéma ou bindings ne transforment pas une topologie régionale inchangée en direction distincte. Si la stratégie préparée est design_system_extension, chaque candidat mappe au moins une région visible vers new_recipe ; la région sélectionnée doit ensuite résoudre la recette publiée du même contexte à la racine de sa section.

Chaque candidat fournit un pageShell exact avec layout et une couche designTokens de page bornée ou null. Chaque région fournit ses nœuds, schéma, bindings, médias et artifactBounds entiers exacts. Une région mesure au moins 200 × 120 pixels et 2 % de la planche ; leur union couvre au moins 40 % ; le chevauchement de deux régions ne dépasse pas 50 % de la plus petite. Le serveur fingerprint chaque crop. Hors du scope d'une page globale, les tokens de page valent obligatoirement null. Chaque paire de candidats doit avoir au moins un crop de région correspondante matériellement distinct, afin que le chrome de la planche seul ne puisse pas passer. Le client ne fournit jamais les compositionHash, implementationHash, renderManifestHash, hashes du pageShell, visualFingerprint ni runtimeHash. Le serveur les calcule depuis les artefacts, le snapshot de tokens, les manifestes exacts, les médias finalisés, les locales, le renderer canonique et l'empreinte de déploiement. En production, la résolution échoue de façon fermée sans fingerprint immuable fourni par NEXT_DEPLOYMENT_ID, le gitCommitSha résolu tel que DEPLOYMENT_GIT_COMMIT_SHA, ou le deploymentId résolu ; "local" est un fallback réservé au hors production. Le fingerprint de déploiement et la version du contrat renderer entrent dans runtimeHash : un changement de build renderer invalide donc la revue antérieure. La frontière exacte courante est le contrat renderer page 7 et le contrat runtime prototype 4.

L'enregistrement renvoie des URLs runtime canoniques signées, un runtimeHash et le contrat d'upload de preuves pour chaque candidat. Renouvelez une URL expirée avec yayaw_cms_prototype_runtime_previews_get seulement tant que le runtime stocké reste courant. Capturez chaque locale préparée en desktop/mobile et clair/sombre. Chaque image pleine page distincte porte exactement les designContextId, designPlanHash égal au runtimeHash, et visualRole renvoyés. Le desktop fait au moins 1024 pixels de large, le mobile 240 à 767. Chaque capture finalisée porte aussi l'attestation de capture version 1 de son scénario. Elle lie l'artefact et son hash exacts, le hash de runtime, l'ID du scénario, la hauteur de document observée, la fin du document et les IDs de région ordonnés. Le ledger est une partition verticale contiguë et unique de 0 à la hauteur image/document, avec des bandes d'au moins 24 pixels, sans trou ni chevauchement. La hauteur de l'image égale la hauteur attestée et reste sous le maximum renvoyé. IDs et hashes de fichier sont uniques.

Soumettez la matrice complète à yayaw_cms_prototype_runtime_review_complete avec boardMatchesRuntime: true, les huit fidélités à true et aucun blocage ni écart. reviewerRole: "visual_qa" est un rôle logique lié à l'acteur courant, pas la preuve d'un reviewer authentifié séparément. La planche et le runtime restent des artefacts distincts comparés visuellement : hashes, manifestes, provenance et bindings de capture détectent substitution, troncature, rejeu et dérive. Le profil courant cms-server-visual-comparison-v3 utilise une normalisation contenue 192 × 192. Ses distances maximales sont 0.06 au global, 0.12 pour l'aspect, 0.14 pour les contours, 0.12 pour la luminance, 0.12 pour la palette et 0.08 pour la topologie ; retirer un wash ou une texture signature devient donc bloquant. La frontière explicite client_observed_server_validated ne prétend pas prouver cryptographiquement l'exécution du navigateur.

Seule une revue runtime courante débloque yayaw_cms_prototype_select. Pour exact-v2, le premier appel fournit le contexte et la raison, sans candidat ni note de décision. L'opération présente les candidats A/B/C dans l'ordre canonique avec des aperçus runtime signés et frais. Un client compatible avec l'élicitation MCP enregistre la réponse enum acceptée dans ce même appel ; un client classique reçoit input_required puis reprend avec le challengeId borné et le prototypeId choisis. Une decisionNote écrite par le client est refusée. Le reçu généré par le serveur lie présentation, challenge, source de réponse, hashes candidat/runtime et preuve de revue runtime. Le plan et la publication recalculent ce reçu et refusent toute preuve absente, legacy, issue d'un autre contexte, conflictuelle ou altérée, tout en autorisant la reprise idempotente de la même réponse acceptée. Le candidat verrouillé renvoie ensuite le designBasis. resumable_client est une attestation du client liée à l'acteur, pas une preuve cryptographique d'un clic humain ; l'élicitation fournit une preuve d'interaction protocolaire plus forte, mais reste médiée par le client. Une modification du renderer/déploiement, d'une recette, d'un média, d'une capture ou des tokens périme la revue. Un changement de tokens après l'enregistrement exige un nouveau contexte et trois nouveaux candidats ; un refresh ne peut pas rebaser le gate. Après sélection, importez, compilez et publiez les new_recipe retenues avec ce contexte, et finalisez chaque média supplémentaire que le plan liera, puis exécutez un refresh avec le même hash de tokens. Il conserve le gate v2, le reçu runtime, la sélection et le designBasis, tout en ajoutant les révisions et assets liés. Soumettez ensuite un plan strict version: 2 avec page.layout et ce designBasis à yayaw_cms_page_design ou yayaw_cms_page_rework. Le layout et la couche de tokens de page du plan reproduisent exactement le pageShell du candidat sélectionné.

Le résultat de préparation expose expiresAt. Pendant une longue pause de prototype, import, correction ou revue, appelez yayaw_cms_design_context_renew avant cette échéance. Le renouvellement préserve le contexte complet et reste plafonné à deux heures depuis la création initiale de la session ; un contexte expiré ne peut pas être réactivé. Les imports de composants liés au design portent le designContextId actif et ne sont acceptés qu'après la sélection explicite du prototype. Cette provenance est stockée sur la révision compilée immuable, jamais prouvée par les métadonnées mutables du registre. Le garde-fou d'extension rejette un composant déjà présent dans la baseline initiale, créé par un autre contexte ou référencé par une autre révision publiée.

Pendant un déploiement progressif, les serveurs compatibles en lecture comprennent les contextes legacy prototypeSet.kind: "generated_set" et les contextes prototypeGateV2.kind: "generated_set_v2", et échouent de façon fermée si les deux champs coexistent. Le champ séparé distingue les formats, mais ne sécurise pas à lui seul une flotte mixte : une instance antérieure au reader compatible peut encore opérer sur l'état legacy, l'écrire ou le sélectionner. La production utilise deux switches d'écriture explicites. Déployez le build reader avec CMS_LEGACY_PROTOTYPE_WRITES_ENABLED=true et CMS_PROTOTYPE_RUNTIME_V2_WRITES_ENABLED=false, drainez toutes les instances antérieures et faites de ce build le rollback floor. Configurez et vérifiez le staging privé, puis passez les deux switches à false, redémarrez et drainez les writers legacy. Activez ensuite uniquement la v2 : legacy false, v2 true. Les deux à true échouent de façon fermée. Pour une évolution du renderer en development, utilisez d'abord le workflow runtime manuel pour geler le SHA courant, puis relancez le déploiement de la PR cible. Son resolver issu du main de confiance ne préserve freeze que si la configuration Coolify et chaque app/worker live le prouvent déjà ; il déploie le nouveau SHA immuable de PR sous le lock partagé et prouve ce SHA avec freeze avant que l'exécution exacte manuelle ne consomme le reçu suivant. Il ne peut initier ni freeze ni exact. Pour une évolution production, gelez d'abord tous les conteneurs live, puis déclenchez le workflow rolling avec le SHA complet cible appartenant à l'historique de main et la confirmation freeze-upgrade:<sha> ; il refuse de construire tant que la flotte n'est pas live-frozen, exécute migrations et deux rollouts sous ce freeze, puis prouve les nouvelles images et le SHA. Lancez enfin le workflow exact manuel sur le même SHA pour créer et consommer le reçu suivant. La consommation fait avancer l'opération de rollout courante tandis que la première opération du floor reste immuable. Les contextes exacts existants se mettent à jour normalement ; seul l'ajout d'un nouveau gate exact exige l'opération courante transactionnelle. Une fois le floor actif, ne rollbackez jamais en dessous et ne réactivez jamais les écritures legacy. Les contextes legacy existants restent lisibles et annulables uniquement. Les assets liés à un grant d'upload direct legacy doivent être réuploadés via le staging privé avant de servir de prototype exact ou de preuve de revue. Un writer désactivé renvoie cms_design_prototype_runtime_rollout_pending. En local et dans les autres environnements hors production, les écritures v2 sont activées par défaut lorsque la variable est absente.

La garantie de preuve exacte s'arrête au moment de la publication. Une évolution comportementale ultérieure du renderer ne recapture pas automatiquement les pages déjà publiées. Tant qu'un gate de promotion ne sait pas inventorier les contrats publiés et fournir un rendu versionné rétrocompatible ou une migration avec nouvelle revue visuelle, ces évolutions du renderer bloquent la release. Les versions partagées vivent dans cms-renderer-contract.ts ; les incrémenter trace la frontière, sans remplacer la sécurité de promotion manquante.

Utilisez dryRun=true avant la persistance, puis yayaw_cms_page_review pour obtenir des diagnostics statiques et des aperçus desktop/mobile immuables à durée courte. Un pass Visual QA doit rendre un PASS explicite pour la révision immuable et le runtime exacts avant tout upload de preuve navigateur durable. Le premier brouillon immuable et chaque correction immuable exigent un nouveau pass. Un reviewer séparé est préférable quand il est disponible, sans que le rôle persistant prouve une identité distincte. La preuve prototype soumise à yayaw_cms_page_review_complete répète l'identité exacte de l'artefact, lie chaque section à un prototypeRegionRef, vérifie les huit dimensions de fidélité et garde unapprovedDeviations vide. Le dry-run et la revue revalident chacun le kind image, le même contexte, le hash enregistré et les dimensions minimales du prototype sélectionné. Les sections du plan se lient une à une, dans l'ordre, aux régions du prototype sous la forme <prototypeId>#<regionId> ; une région échangée, inventée ou omise échoue. Chaque evidenceRef de scénario ou de réduction de mouvement est l'ID d'une image durable liée au contexte, au hash de plan et au rôle visual-review:* exacts. Le préfixe de rôle renvoyé par la revue lie aussi la capture à la révision de page et au hash runtime courants. Chaque capture porte aussi l'attestation version 1 renvoyée avec l'artefact et son hash, le runtime, le scénario, la hauteur de document et les bandes de sections contiguës exacts. Le serveur vérifie pour chacune ses octets statiques, son kind image, son contexte, son hash de plan, son hash de fichier, ses dimensions, son rôle et son attestation, puis lie ces champs au ledger du reçu. Ces outils ne créent que des brouillons ; la publication retélécharge et réinspecte ce ledger ainsi que toutes les planches/captures runtime prototype, et refuse toute capture supprimée, modifiée, animée, tronquée, rejouée, reclassifiée ou provenant d'un autre contexte comme revue périmée, puis exige une demande explicite. Les plans corrigés restent liés à la première page contextuelle, à son chemin/slug et aux IDs/slugs de sections stables. Les révisions de page et de sections, le ledger de session et les hashes de plan média sont persistés dans une transaction sérialisable unique : tout échec annule l'ensemble. Les registres portent un pointeur durable vers leur révision de tête, avancé par la base à l'insertion d'une révision immuable plutôt que déduit du tri des dates. Une révision manuelle ou externe bloque ce chemin. Une correction A → B → A qui réutiliserait une ancienne révision immuable renvoie un conflit explicite au lieu de présenter l'ancienne révision comme nouvelle tête. Les créations et sauvegardes directes de page exigent control-plane:admin et CMS_LOW_LEVEL_PAGE_ADMIN_BYPASS_ENABLED=true. L'ancien outil de design est aussi réservé aux admins et désactivé sauf si CMS_LEGACY_PAGE_DESIGN_ADMIN_BYPASS_ENABLED=true.

La boucle de blocage ne saute aucune révision : dry-run et persistance, obtention de review_ready, puis Visual QA sur cette révision/runtime exacts. Un diagnostic statique/runtime, un scénario ou champ de fidélité en échec, un problème bloquant ou un écart non approuvé interdit l'upload de preuves visual-review:*, la complétion de revue et la publication. Le même plan contextuel est corrigé, une nouvelle révision immuable est persistée et la boucle reprend au dry-run. Seul un PASS sans problème bloquant autorise l'upload des preuves. Toute correction ou modification de dépendance invalide le pass, les captures, le hash runtime et le reçu.

Les valeurs littérales CMS utilisent du JSON borné récursivement. Tableaux, objets, primitives et null peuvent être envoyés directement dans les props et bindings ; les valueShape récursifs valident les valeurs imbriquées. Les anciennes chaînes JSON restent lisibles.

yayaw_kibo_sections_generate crée ou met à jour des recettes CMS natives kibo-ui-block pour le catalogue Kibo UI suivi. Il ne stocke jamais du TSX tiers arbitraire comme contenu exécutable au runtime: chaque section générée est une section recipe Yayaw rendue par des exemples Kibo présents dans le dépôt et leurs dépendances Kibo/shadcn vendored. Les recettes générées exposent des props éditables texte, lien, média et listes JSON afin que les pages lient du contenu Yayaw localisé via PageSection.inputBindings pendant que les défauts Kibo restent le repli de prévisualisation. Utilisez dryRun=true pour inspecter les slugs cibles et publish=true seulement lorsque l'appelant veut explicitement publier les révisions validées.

Les outils de publication/archive de page, publication d'entrée de données et publication de section exigent expectedRevisionId pour préserver le verrouillage optimiste. L'archive de section accepte expectedRevisionId lorsque l'appelant connaît la dernière révision. Les outils d'archive destructeurs, y compris page, section, composant et domaine public d'organisation, exigent confirm: true.

Chaque appel d'outil MCP tente d'écrire control_plane_audit_events avec acteur, clé API, opération, cible, environnement, transport, état dry-run, raison, statut, résumé d'erreur et métadonnées de résultat. Une insertion d'audit réussie renvoie auditId dans la réponse de l'outil; si le stockage d'audit est indisponible, le serveur journalise l'échec et renvoie la réponse sans auditId.

Contrat Des Futures Fonctionnalités

Les nouvelles fonctionnalités opérationnelles doivent être conçues comme opérables par le plan de contrôle sauf exclusion volontaire UI-only.

Lorsqu'une fonctionnalité nécessite des workflows de contenu, configuration, publication, statut ou audit, implémentez-la avec le pattern partagé du plan de contrôle:

  • Placez la logique métier dans un point d'entrée service réutilisable qui accepte un argument acteur/contexte explicite.
  • Gardez les lectures de cookie-session dans les adaptateurs UI, pas dans la couche service réutilisable.
  • Faites appeler la même logique permissionnée par les server actions UI, outils MCP, scripts CLI et API HTTP optionnelles.
  • Enregistrez des outils/ressources MCP typés dans src/lib/server/services/control-plane lorsque les clients MCP doivent opérer ou inspecter la fonctionnalité.
  • Exigez reason pour les écritures, dryRun lorsque c'est pratique, expectedRevisionId pour les flux publish optimistes et confirm: true pour les actions destructrices.
  • Gatez l'accès par clé API avec les permissions Better Auth control-plane:* et exécutez quand même l'autorisation Yayaw avec can(...) ou des checks de membership borné.
  • Tentez l'enregistrement d'événements d'audit pour toutes les opérations et exposez auditId lorsque le stockage réussit.
  • Mettez à jour cette documentation et content/llm/llm-source.md, puis régénérez les fichiers assistants avec bun run docs:llm:generate.
  • Ajoutez des tests ciblés pour schémas, mapping de permissions, gardes dry-run/write, insertion d'audit et couverture smoke discovery MCP ou appel d'outil.

Si une fonctionnalité est volontairement exclue de MCP/CLI, documentez la raison dans les docs de fonctionnalité ou le résumé de PR.

Validation

Checks utiles après des changements du plan de contrôle:

bun test src/lib/server/services/control-plane/control-plane.test.ts
bun test src/lib/server/services/control-plane/oauth-metadata.test.ts
bun run mcp:stdio