@aifabrix/builder 2.60.0 → 2.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +14 -11
  2. package/docs/README.md +80 -0
  3. package/docs/builder-help/evidence-patterns.json +155 -0
  4. package/docs/builder-help/golden-examples/crm-company.json +30 -0
  5. package/docs/builder-help/golden-examples/crm-deal.json +29 -0
  6. package/docs/builder-help/golden-examples/document-storage-keyed-get.json +85 -0
  7. package/docs/builder-help/golden-examples/document-storage.json +30 -0
  8. package/docs/builder-help/golden-examples/meeting-transcript.json +29 -0
  9. package/docs/builder-help/golden-examples/repository-template.json +29 -0
  10. package/docs/builder-help/golden-examples/service-ticket.json +29 -0
  11. package/docs/builder-help/platform-roles.json +98 -0
  12. package/docs/builder-help/resource-type-catalog.json +402 -0
  13. package/lib/api/applications.api.js +22 -2
  14. package/lib/api/bootstrap-snapshot.api.js +115 -0
  15. package/lib/api/configuration.api.js +131 -0
  16. package/lib/api/system-secrets.api.js +72 -0
  17. package/lib/app/deploy.js +14 -5
  18. package/lib/app/run-docker-fallback.js +6 -1
  19. package/lib/app/run-env-compose.js +5 -6
  20. package/lib/app/run-helpers.js +2 -1
  21. package/lib/app/run-parameter-sync.js +142 -0
  22. package/lib/app/run.js +6 -4
  23. package/lib/build/docker-build-args.js +5 -2
  24. package/lib/build/index.js +3 -2
  25. package/lib/build/standard-docker-build.js +6 -2
  26. package/lib/channels/channel-artifact-generator.js +13 -3
  27. package/lib/cli/infra-guided-footers.js +10 -8
  28. package/lib/cli/infra-guided.js +30 -10
  29. package/lib/cli/setup-app.js +5 -0
  30. package/lib/cli/setup-environment.js +156 -0
  31. package/lib/cli/setup-infra-up-platform-action.js +14 -1
  32. package/lib/cli/setup-platform.js +2 -0
  33. package/lib/commands/auth-config.js +2 -0
  34. package/lib/commands/datasource-capability-upsert-cli.js +112 -0
  35. package/lib/commands/datasource-capability.js +4 -2
  36. package/lib/commands/env-secret-context.js +113 -0
  37. package/lib/commands/env-secret-list.js +200 -0
  38. package/lib/commands/env-secret-push-confirm.js +54 -0
  39. package/lib/commands/env-secret-push-run.js +227 -0
  40. package/lib/commands/env-secret-push.js +168 -0
  41. package/lib/commands/login.js +4 -0
  42. package/lib/commands/repair-datasource-apply.js +5 -1
  43. package/lib/commands/repair-datasource-keyed-document.js +122 -0
  44. package/lib/commands/repair-datasource-legacy.js +94 -0
  45. package/lib/commands/repair-datasource-run.js +1 -0
  46. package/lib/commands/setup-image-refresh.js +26 -0
  47. package/lib/commands/setup-modes.js +8 -9
  48. package/lib/commands/setup-prompts-platform-mode.js +34 -0
  49. package/lib/commands/setup-prompts.js +2 -180
  50. package/lib/commands/setup.js +3 -0
  51. package/lib/commands/up-builder-api.js +24 -8
  52. package/lib/commands/up-common.js +16 -1
  53. package/lib/commands/up-dataplane-bootstrap.js +70 -14
  54. package/lib/commands/up-dataplane-credentials.js +21 -0
  55. package/lib/commands/up-dataplane.js +37 -18
  56. package/lib/commands/up-integration-server.js +112 -0
  57. package/lib/commands/verify-operations-skip-e2e.js +25 -1
  58. package/lib/commands/verify-operations-steps.js +13 -1
  59. package/lib/commands/wizard-config-normalizer.js +12 -5
  60. package/lib/commands/wizard-core.js +5 -157
  61. package/lib/commands/wizard-file-saving.js +163 -0
  62. package/lib/core/admin-secrets-env-overlay.js +8 -3
  63. package/lib/core/env-platform-expand.js +5 -1
  64. package/lib/core/environment-access-policy.js +121 -0
  65. package/lib/core/environment-mode-policy.js +177 -0
  66. package/lib/core/local-env-overrides.js +149 -0
  67. package/lib/core/secrets-ensure.js +4 -2
  68. package/lib/core/secrets-env-content.js +17 -10
  69. package/lib/core/secrets-env-write.js +11 -4
  70. package/lib/core/secrets-load.js +4 -2
  71. package/lib/datasource/binary-documents-validator.js +190 -0
  72. package/lib/datasource/capability/capability-manifest.js +1 -0
  73. package/lib/datasource/capability/run-capability-upsert.js +202 -0
  74. package/lib/datasource/capability/upsert-ingredients.js +291 -0
  75. package/lib/datasource/capability/upsert-operations.js +138 -0
  76. package/lib/datasource/capability/upsert-test-scaffold.js +189 -0
  77. package/lib/datasource/validate.js +12 -5
  78. package/lib/deployment/installation/index.js +14 -1
  79. package/lib/deployment/installation/infra-catalog.js +3 -0
  80. package/lib/deployment/installation/local-environment-stage.js +285 -0
  81. package/lib/deployment/installation/local-installation.js +76 -0
  82. package/lib/deployment/installation/stage-input.js +5 -1
  83. package/lib/generator/builders.js +3 -1
  84. package/lib/generator/index.js +3 -0
  85. package/lib/generator/split-variables.js +2 -2
  86. package/lib/lifecycle/product-model.js +4 -3
  87. package/lib/lifecycle/report-display.js +3 -2
  88. package/lib/parameters/database-adoption.js +125 -0
  89. package/lib/parameters/infra-parameter-catalog.js +1 -1
  90. package/lib/parameters/physical-database-naming.js +136 -0
  91. package/lib/parameters/physical-secret-name.js +194 -0
  92. package/lib/parameters/rsa-secret-values.js +165 -0
  93. package/lib/programmatic/builder-help-enterprise-sync-fabrix.js +1 -1
  94. package/lib/programmatic/builder-help-governance.js +1 -1
  95. package/lib/programmatic/builder-help.js +1 -1
  96. package/lib/programmatic/run-channel-install-artifacts.js +28 -2
  97. package/lib/programmatic/run-channel-package.js +3 -1
  98. package/lib/programmatic/workspace-context.js +37 -2
  99. package/lib/role-assistant/test-runner-workhub-answers.js +4 -1
  100. package/lib/role-assistant/test-runner-workhub-missing-fields.js +42 -0
  101. package/lib/role-assistant/test-runner-workhub-wait-stop.js +94 -0
  102. package/lib/role-assistant/test-runner-workhub.js +28 -19
  103. package/lib/schema/application-schema.json +14 -2
  104. package/lib/schema/external-datasource.schema.json +23 -3
  105. package/lib/schema/infra.parameter.yaml +440 -56
  106. package/lib/schema/infrastructure-schema.json +213 -120
  107. package/lib/utils/compose-generate-docker-compose.js +9 -14
  108. package/lib/utils/compose-generator.js +4 -7
  109. package/lib/utils/compose-miso-env.js +2 -2
  110. package/lib/utils/compose-traefik-ingress-base.js +3 -4
  111. package/lib/utils/datasource-binary-evidence.js +92 -0
  112. package/lib/utils/datasource-test-run-capability-scope.js +44 -1
  113. package/lib/utils/datasource-test-run-debug-display.js +2 -0
  114. package/lib/utils/datasource-test-run-display.js +8 -2
  115. package/lib/utils/datasource-test-run-issue-guidance.js +176 -0
  116. package/lib/utils/datasource-test-run-tty-log.js +2 -0
  117. package/lib/utils/declarative-url-ports.js +3 -0
  118. package/lib/utils/env-copy.js +11 -10
  119. package/lib/utils/env-environment-file-paths.js +105 -0
  120. package/lib/utils/environment-scoped-resources.js +48 -14
  121. package/lib/utils/external-system-system-test-tty.js +3 -2
  122. package/lib/utils/image-tags.js +2 -2
  123. package/lib/utils/paths-system-builder-keys.js +7 -1
  124. package/lib/utils/paths.js +7 -2
  125. package/lib/utils/platform-kv-ref.js +1 -1
  126. package/lib/utils/postgres-platform-bootstrap.js +35 -1
  127. package/lib/utils/prepare-local-data-mount.js +58 -0
  128. package/lib/utils/redis-env-scope.js +21 -1
  129. package/lib/utils/registry-auth-sources.js +154 -0
  130. package/lib/utils/registry-credentials.js +131 -14
  131. package/lib/utils/secrets-generator.js +30 -21
  132. package/lib/utils/secrets-helpers.js +5 -8
  133. package/lib/utils/secrets-kv-scope.js +127 -28
  134. package/lib/utils/secrets-materialize-local.js +9 -0
  135. package/lib/utils/secrets-missing-error.js +35 -1
  136. package/lib/utils/system-secret-mapping.js +125 -0
  137. package/lib/utils/test-log-writer.js +2 -1
  138. package/lib/utils/token-manager.js +14 -8
  139. package/lib/utils/url-public-path-prefix.js +6 -10
  140. package/lib/validation/external-manifest-validator.js +5 -0
  141. package/lib/validation/openapi-contract-surface-validator.js +3 -1
  142. package/lib/validation/validate-external-file.js +5 -1
  143. package/package.json +6 -4
  144. package/templates/agent-kit/agent-kit.yaml +1 -1
  145. package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +2 -1
  146. package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +15 -0
  147. package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +3 -2
  148. package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +6 -2
  149. package/templates/agent-kit/skills/aifabrix-prove/references/evidence-lifecycle.md +22 -0
  150. package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +7 -1
  151. package/templates/agent-kit/skills/aifabrix-role-assistant/references/testing-playbook.md +117 -0
  152. package/templates/agent-kit/skills/shared/interaction.md +83 -0
  153. package/templates/agent-kit/skills/shared/status.md +3 -12
  154. package/templates/applications/builder-api/application.yaml +1 -1
  155. package/templates/applications/builder-api/env.template +5 -1
  156. package/templates/applications/dataplane/application.yaml +2 -2
  157. package/templates/applications/dataplane/env.template +10 -12
  158. package/templates/applications/integration-server/application.yaml +91 -0
  159. package/templates/applications/integration-server/env.template +215 -0
  160. package/templates/applications/keycloak/application.yaml +6 -1
  161. package/templates/applications/mirrored-templates.json +52 -0
  162. package/templates/applications/miso-controller/application.yaml +116 -2
  163. package/templates/applications/miso-controller/env.template +62 -17
  164. package/templates/applications/miso-controller/rbac.yaml +17 -0
  165. package/templates/external-system/external-datasource.yaml.hbs +7 -1
  166. package/templates/marketplace/createUiDefinition.json +52 -4
  167. package/templates/marketplace/main.json +88 -10
  168. package/templates/python/docker-compose.hbs +1 -1
  169. package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +0 -41
  170. /package/{lib/programmatic/help-content → docs/builder-help/content}/channel-onboarding.md +0 -0
  171. /package/{lib/programmatic/help-content → docs/builder-help/content}/cip-overview.md +0 -0
  172. /package/{lib/programmatic/help-content → docs/builder-help/content}/connected-system-ui.md +0 -0
  173. /package/{lib/programmatic/help-content → docs/builder-help/content}/dimensions-guide.md +0 -0
  174. /package/{lib/programmatic/help-content → docs/builder-help/content}/enterprise-sync-fabrix.md +0 -0
  175. /package/{lib/programmatic/help-content → docs/builder-help/content}/overview.md +0 -0
  176. /package/{lib/programmatic/help-content → docs/builder-help/content}/subscription-guide.md +0 -0
  177. /package/{lib/programmatic/help-content → docs/builder-help/content}/workflow.md +0 -0
@@ -105,6 +105,9 @@ parameters:
105
105
  value: 'redis://${REDIS_HOST}:${REDIS_PORT}'
106
106
  ensureOn: [upInfra, resolveApp]
107
107
  azure:
108
+ vaultSecretName: redis-url
109
+ legacyVaultSecretNames:
110
+ - redis-urlKeyVault
108
111
  notes: >-
109
112
  Local up-infra resolves the REDIS_HOST/REDIS_PORT placeholders. Azure application deploy
110
113
  must leave this key absent so the Azure SDK derives the rediss URL and credential.
@@ -590,53 +593,92 @@ parameters:
590
593
  azure:
591
594
  notes: Not a typical Key Vault name; local optional pypi token.
592
595
 
593
- # Default client id for controller app in local multi-service layout; override after register if needed.
596
+ # Miso Controller ↔ controller OAuth. The Controller issues both values at registration;
597
+ # local infrastructure must leave them empty until application resolution retrieves them.
594
598
  - key: miso-controller-client-idKeyVault
595
599
  ownerAppKey: miso-controller
596
600
  scope: app
601
+ sensitive: false
597
602
  generator:
598
- type: literal
599
- value: 'miso-controller-miso-miso-controller'
600
- ensureOn: [upInfra, resolveApp]
603
+ type: emptyString
604
+ ensureOn: [resolveApp]
601
605
  azure:
602
606
  legacyVaultSecretNames:
603
607
  - '{serviceName}-miso-clientIdKeyVault'
604
608
  notes: >-
605
- Per-app OAuth client id from controller registration; literal default matches local Keycloak client naming.
609
+ Per-app OAuth client id issued when miso-controller is registered with Miso.
606
610
 
607
611
  - key: miso-controller-client-secretKeyVault
608
612
  ownerAppKey: miso-controller
609
613
  scope: app
610
614
  generator:
611
- type: randomBytes32
612
- ensureOn: [upInfra, resolveApp]
615
+ type: emptyString
616
+ ensureOn: [resolveApp]
613
617
  azure:
614
618
  notes: >-
615
- Per-app OAuth client secret used by miso-controller for service-to-service auth (MISO_CLIENTSECRET).
616
- Generated on first ensure for local bootstrap.
619
+ Per-app OAuth client secret issued when miso-controller is registered with Miso and used
620
+ for service-to-service auth (MISO_CLIENTSECRET). Never generated by local infrastructure.
621
+
622
+ # Builder API ↔ controller OAuth. These are explicit declarations rather than catch-all
623
+ # matches so the backup catalog records the owner and canonical names unambiguously.
624
+ - key: builder-api-client-idKeyVault
625
+ ownerAppKey: builder-api
626
+ scope: app
627
+ sensitive: false
628
+ generator:
629
+ type: emptyString
630
+ ensureOn: [resolveApp]
631
+ azure:
632
+ vaultSecretName: builder-api-client-idKeyVault
633
+ notes: Per-app OAuth client id issued when builder-api is registered with Miso.
634
+
635
+ - key: builder-api-client-secretKeyVault
636
+ ownerAppKey: builder-api
637
+ scope: app
638
+ sensitive: true
639
+ generator:
640
+ type: emptyString
641
+ ensureOn: [resolveApp]
642
+ azure:
643
+ vaultSecretName: builder-api-client-secretKeyVault
644
+ notes: Per-app OAuth client secret issued when builder-api is registered with Miso.
617
645
 
618
646
  # Dataplane ↔ controller OAuth (builder/dataplane env.template MISO_CLIENTID / MISO_CLIENTSECRET).
647
+ # Plan 265.0: issued by the Controller at registration, so the catalog must not produce a
648
+ # value for it. The previous `literal` on the `upInfra` hook meant Builder generated an id
649
+ # the Controller owns — and the literal was `miso-controller-dev-dataplane`, with `dev`
650
+ # baked into an entry that is not dev-specific. `emptyString` on `resolveApp` is the shape
651
+ # `builder-api-client-idKeyVault` already uses for the same relationship.
619
652
  - key: dataplane-client-idKeyVault
620
653
  ownerAppKey: dataplane
621
654
  scope: app
655
+ sensitive: false
622
656
  generator:
623
- type: literal
624
- value: 'miso-controller-dev-dataplane'
625
- ensureOn: [upInfra, resolveApp]
657
+ type: emptyString
658
+ ensureOn: [resolveApp]
626
659
  azure:
627
660
  notes: >-
628
- Per-app OAuth client id from controller registration; literal default matches local Keycloak client naming
629
- (override for environments such as miso-controller-dev-dataplane).
630
-
661
+ Per-app OAuth client id issued when dataplane is registered with Miso. Not generated
662
+ locally: the Controller owns it, and a locally invented id is one the Controller has
663
+ never seen.
664
+
665
+ # Plan 265.0: the Controller issues this secret, so a `randomBytes32` generator on the
666
+ # `upInfra` hook had Builder inventing an OAuth secret the Controller owns. That invented
667
+ # value is what defeated the "credentials missing locally" guard and surfaced as a
668
+ # misleading auth error rather than a clear one. Matches
669
+ # `builder-api-client-secretKeyVault`.
631
670
  - key: dataplane-client-secretKeyVault
632
671
  ownerAppKey: dataplane
633
672
  scope: app
634
673
  generator:
635
- type: randomBytes32
636
- ensureOn: [upInfra, resolveApp]
674
+ type: emptyString
675
+ ensureOn: [resolveApp]
637
676
  azure:
638
677
  vaultSecretName: dataplane-client-secretKeyVault
639
- notes: Per-app OAuth client secret for dataplane; generated on first ensure.
678
+ notes: >-
679
+ Per-app OAuth client secret issued when dataplane is registered with Miso. Never
680
+ generated locally; it is fetched from the Controller, which is the only party that
681
+ can issue one it will accept.
640
682
 
641
683
  # Mori / telemetry is optional for an unlicensed Community installation.
642
684
  - key: mori-controller-api-keyKeyVault
@@ -652,18 +694,46 @@ parameters:
652
694
  notes: Empty locally; set when calling Mori with API key auth.
653
695
 
654
696
  # Public, but a security-critical trust anchor. Never derive it from a pasted JWT.
697
+ #
698
+ # `sensitive: true` on a public key is deliberate -- 227.1 made Miso verify licences against a
699
+ # PINNED key precisely because a key travelling inside the token it verifies proves nothing, so
700
+ # this value must be protected from tampering even though disclosing it costs nothing.
701
+ #
702
+ # It is owned by `miso-controller` rather than left system-scoped because a system-scoped
703
+ # sensitive secret is readable by NO application (`forbidden-system-secret`). Measured on
704
+ # aifabrix-ce-16 2026-09-27: miso-controller declares MORI_LICENSE_PUBLIC_KEY in its manifest, yet
705
+ # the key was absent from its 70 registry rows and from its snapshot, so the installation could
706
+ # never have verified a licence. Ownership is what makes a trust anchor readable by the service
707
+ # whose trust it anchors -- the same correction already applied to secrets-policyPrivateKeyVault.
655
708
  - key: mori-license-public-keyKeyVault
656
709
  category: Mori
710
+ ownerAppKey: miso-controller
657
711
  sensitive: true
658
712
  valueSource: optionalInput
659
713
  requiredOnSystemOnboarding: false
660
- scope: system
714
+ scope: app
715
+ # The public half of the Mori licence pair above, so the key Miso verifies with cannot drift
716
+ # from the key Mori signs with. Was `emptyString`: the two halves came from unrelated places
717
+ # and an installation could hold a perfectly valid public key belonging to a different private
718
+ # key, which verifies nothing and reports no error.
719
+ #
720
+ # Rotating an installation that already holds a value here means clearing BOTH halves
721
+ # together — the generator is get-or-create, so replacing one alone leaves a mismatched pair.
722
+ # Supplied, never generated. Mori owns this pair and keeps the private half; an installation
723
+ # that minted it would hold the private half of the key it verifies against and could sign a
724
+ # licence it would then accept. Builder refuses these pairs (aifabrix-builder 211.0 /
725
+ # 5bfeb06d); `rsaKeyPair` here made Miso mint them anyway, because catalog-resolver runs the
726
+ # generator unconditionally and never consults valueSource. `emptyAllowed` is what stops it:
727
+ # the materializer skips isEmptyByDesign rows, so Miso no longer writes this secret at all.
728
+ # It arrives instead as a deploy parameter -- packages.json -> main.bicep ->
729
+ # 08_keyvault-secrets.bicep -> the installation vault -- which is also the only writer that
730
+ # can hold Mori's real key rather than a locally invented one.
661
731
  generator:
662
- type: emptyString
732
+ type: emptyAllowed
663
733
  ensureOn: [upInfra, resolveApp]
664
734
  azure:
665
735
  vaultSecretName: mori-license-public-keyKeyVault
666
- notes: Trusted Mori SPKI PEM public key, provisioned by the installer or issuer release identity.
736
+ notes: SPKI PEM public half of the Mori licence signing pair; linked to secrets-licensePrivateKeyVault.
667
737
 
668
738
  - key: appinsights-connectionStringKeyVault
669
739
  category: Observability
@@ -705,7 +775,7 @@ parameters:
705
775
  vaultSecretName: loganalytics-workspace-id
706
776
  notes: Azure SDK-derived Log Analytics workspace customer identifier.
707
777
 
708
- - key: azureopenai-endpoint
778
+ - key: azure-openai/endpoint
709
779
  category: Azure OpenAI
710
780
  sensitive: false
711
781
  valueSource: optionalInput
@@ -715,10 +785,10 @@ parameters:
715
785
  type: emptyString
716
786
  ensureOn: [resolveApp]
717
787
  azure:
718
- vaultSecretName: azureopenai-endpoint
719
- notes: Azure OpenAI endpoint produced by the environment infrastructure deployment.
788
+ vaultSecretName: dev-dataplane-azure-openai-endpoint
789
+ notes: Azure OpenAI endpoint produced by infrastructure at the standard dev Dataplane address.
720
790
 
721
- - key: azureopenai-apikey
791
+ - key: azure-openai/apiKey
722
792
  category: Azure OpenAI
723
793
  sensitive: true
724
794
  valueSource: optionalInput
@@ -728,21 +798,8 @@ parameters:
728
798
  type: emptyString
729
799
  ensureOn: [resolveApp]
730
800
  azure:
731
- vaultSecretName: azureopenai-apikey
732
- notes: Azure OpenAI API key produced by the environment infrastructure deployment.
733
-
734
- # Dataplane builder/dataplane env.template (AI/LLM); optional until OpenAI or Azure OpenAI is configured.
735
- - key: secrets-openaiApiKeyVault
736
- category: OpenAI
737
- sensitive: true
738
- valueSource: optionalInput
739
- requiredOnSystemOnboarding: false
740
- scope: system
741
- generator:
742
- type: emptyString
743
- ensureOn: [resolveApp]
744
- azure:
745
- notes: Optional shared OpenAI API key; empty until supplied and never auto-generated.
801
+ vaultSecretName: dev-dataplane-azure-openai-api-key
802
+ notes: Azure OpenAI API key produced by infrastructure at the standard dev Dataplane address.
746
803
 
747
804
  - key: azure-openaiapi-urlKeyVault
748
805
  scope: app
@@ -752,17 +809,13 @@ parameters:
752
809
  azure:
753
810
  notes: Empty until set; Azure OpenAI endpoint URL.
754
811
 
755
- - key: secrets-azureOpenaiApiKeyVault
756
- scope: app
757
- generator:
758
- type: emptyString
759
- ensureOn: [upInfra, resolveApp]
760
- azure:
761
- notes: Empty until set; user-supplied Azure OpenAI API key (not auto-generated).
762
-
763
812
  # Dataplane RS256 signing identity. The two rows are generated as one linked pair.
764
813
  - key: secrets-certificatePrivateKeyVault
765
814
  ownerAppKey: dataplane
815
+ # Deliberately NOT shared with mori-controller. `readability-real-catalog.test.ts` asserts
816
+ # "denies mori signing licences with the dataplane signing key" — Mori's manifest pointing
817
+ # MORI_LICENSE_PRIVATE_KEY at this key is the misconfiguration, not the denial. The fix is a
818
+ # mori-controller-owned licence key and a manifest change, never widening this entry.
766
819
  scope: app
767
820
  generator:
768
821
  type: rsaKeyPair
@@ -773,6 +826,80 @@ parameters:
773
826
  vaultSecretName: secrets-certificatePrivateKeyVault
774
827
  notes: PKCS#8 PEM private key generated with the matching public key.
775
828
 
829
+ # Mori's policy signing key. Present in every installation's vault but absent from this catalog,
830
+ # so it fell through to the `^[a-zA-Z0-9_-]+KeyVault$` catch-all — `scope: app` with no owner,
831
+ # which `resolveOwnershipDecision` cannot classify and the policy therefore denies to everyone.
832
+ #
833
+ # Measured on aifabrix-ce-16, 2026-09-26: the secret exists, Mori reads it today through an App
834
+ # Service Key Vault reference, and bootstrap omitted it. That is the same key whose absence
835
+ # previously failed Mori's signing health check.
836
+ - key: secrets-policyPrivateKeyVault
837
+ ownerAppKey: mori-controller
838
+ scope: app
839
+ sensitive: true
840
+ # Supplied, never generated. Mori owns this pair and keeps the private half; an installation
841
+ # that minted it would hold the private half of the key it verifies against and could sign a
842
+ # licence it would then accept. Builder refuses these pairs (aifabrix-builder 211.0 /
843
+ # 5bfeb06d); `rsaKeyPair` here made Miso mint them anyway, because catalog-resolver runs the
844
+ # generator unconditionally and never consults valueSource. `emptyAllowed` is what stops it:
845
+ # the materializer skips isEmptyByDesign rows, so Miso no longer writes this secret at all.
846
+ # It arrives instead as a deploy parameter -- packages.json -> main.bicep ->
847
+ # 08_keyvault-secrets.bicep -> the installation vault -- which is also the only writer that
848
+ # can hold Mori's real key rather than a locally invented one.
849
+ generator:
850
+ type: emptyAllowed
851
+ ensureOn: [resolveApp]
852
+ azure:
853
+ vaultSecretName: secrets-policyPrivateKeyVault
854
+ notes: PKCS#8 PEM private key for Mori policy signing; MORI_POLICY_KEY_ID names the pair.
855
+
856
+ # Mori's LICENCE signing key, which is not its policy signing key and not the dataplane's.
857
+ #
858
+ # `subscription-license.operations.ts` refuses to issue at all without it:
859
+ # if (!privateKey) throw ErrorFactory.internalServerError('License signing is unavailable')
860
+ # and until this entry existed there was nothing for it to read. Mori's manifest pointed
861
+ # MORI_LICENSE_PRIVATE_KEY at `secrets-certificatePrivateKeyVault`, which the dataplane owns, so
862
+ # the read was denied -- correctly. An existing test (`denies mori signing licences with the
863
+ # dataplane signing key`) pins that denial, so the fix is a key Mori owns, never a wider grant.
864
+ #
865
+ # `emptyString` rather than `rsaKeyPair`: the operator supplies the pair, because whoever holds
866
+ # this key decides which licences an installation will believe. Generating it during onboarding
867
+ # would make the platform mint its own trust root as a side effect of deployment. Onboarding
868
+ # creates the placeholder, `aifabrix env secret push` writes the real value, and create-if-absent
869
+ # then leaves it alone. The verifying half is `mori-license-public-keyKeyVault`, pinned to
870
+ # miso-controller; a pair is only useful if both halves are readable by their own service.
871
+ - key: secrets-licensePrivateKeyVault
872
+ category: Mori
873
+ ownerAppKey: mori-controller
874
+ scope: app
875
+ sensitive: true
876
+ valueSource: optionalInput
877
+ requiredOnSystemOnboarding: false
878
+ # Generated as one linked pair with mori-license-public-keyKeyVault, the same way the
879
+ # dataplane's signing identity is. Was `emptyString`, which meant an installation that
880
+ # repointed Mori at this key got nothing and `subscription-license.operations.ts` refused
881
+ # with "License signing is unavailable" -- so the only working configuration was the wrong
882
+ # one: Mori signing with the DATAPLANE's key via secrets-certificatePrivateKeyVault.
883
+ #
884
+ # Mori and the dataplane must not share a signing identity. Sharing one means the dataplane
885
+ # holds a key that mints licences, and the two services have completely different blast
886
+ # radii. Mori is the licence server; the dataplane runs customer workloads.
887
+ # Supplied, never generated. Mori owns this pair and keeps the private half; an installation
888
+ # that minted it would hold the private half of the key it verifies against and could sign a
889
+ # licence it would then accept. Builder refuses these pairs (aifabrix-builder 211.0 /
890
+ # 5bfeb06d); `rsaKeyPair` here made Miso mint them anyway, because catalog-resolver runs the
891
+ # generator unconditionally and never consults valueSource. `emptyAllowed` is what stops it:
892
+ # the materializer skips isEmptyByDesign rows, so Miso no longer writes this secret at all.
893
+ # It arrives instead as a deploy parameter -- packages.json -> main.bicep ->
894
+ # 08_keyvault-secrets.bicep -> the installation vault -- which is also the only writer that
895
+ # can hold Mori's real key rather than a locally invented one.
896
+ generator:
897
+ type: emptyAllowed
898
+ ensureOn: [resolveApp]
899
+ azure:
900
+ vaultSecretName: secrets-licensePrivateKeyVault
901
+ notes: PKCS#8 PEM private key for Mori licence signing; MORI_LICENSE_KEY_ID names the pair.
902
+
776
903
  - key: secrets-certificatePublicKeyVault
777
904
  ownerAppKey: dataplane
778
905
  scope: app
@@ -825,8 +952,12 @@ parameters:
825
952
  type: databaseUrl
826
953
  ensureOn: [upInfra, resolveApp]
827
954
  azure:
828
- vaultSecretNamePattern: '{appKey}-databases-{index}-urlKeyVault'
955
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-urlKeyVault'
829
956
  legacyVaultSecretNames:
957
+ # Pre-environment naming: app and index only, so two environments sharing one vault
958
+ # collided. Kept so installations written under it keep resolving without a vault write.
959
+ - '{appKey}-databases-{index}-urlKeyVault'
960
+ # The original logical key, which is what aifabrix-ce-16 actually holds.
830
961
  - '{key}'
831
962
  - '{serviceName}-databases-0-urlKeyVault'
832
963
 
@@ -837,8 +968,12 @@ parameters:
837
968
  type: databasePassword
838
969
  ensureOn: [upInfra, resolveApp]
839
970
  azure:
840
- vaultSecretNamePattern: '{appKey}-databases-{index}-passwordKeyVault'
971
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-passwordKeyVault'
841
972
  legacyVaultSecretNames:
973
+ # Pre-environment naming: app and index only, so two environments sharing one vault
974
+ # collided. Kept so installations written under it keep resolving without a vault write.
975
+ - '{appKey}-databases-{index}-passwordKeyVault'
976
+ # The original logical key, which is what aifabrix-ce-16 actually holds.
842
977
  - '{key}'
843
978
 
844
979
  - key: databases-miso-controller-1-urlKeyVault
@@ -848,8 +983,12 @@ parameters:
848
983
  type: databaseUrl
849
984
  ensureOn: [upInfra, resolveApp]
850
985
  azure:
851
- vaultSecretNamePattern: '{appKey}-databases-{index}-urlKeyVault'
986
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-urlKeyVault'
852
987
  legacyVaultSecretNames:
988
+ # Pre-environment naming: app and index only, so two environments sharing one vault
989
+ # collided. Kept so installations written under it keep resolving without a vault write.
990
+ - '{appKey}-databases-{index}-urlKeyVault'
991
+ # The original logical key, which is what aifabrix-ce-16 actually holds.
853
992
  - '{key}'
854
993
  - '{serviceName}-databases-1-urlKeyVault'
855
994
 
@@ -860,9 +999,120 @@ parameters:
860
999
  type: databasePassword
861
1000
  ensureOn: [upInfra, resolveApp]
862
1001
  azure:
863
- vaultSecretNamePattern: '{appKey}-databases-{index}-passwordKeyVault'
1002
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-passwordKeyVault'
1003
+ legacyVaultSecretNames:
1004
+ # Pre-environment naming: app and index only, so two environments sharing one vault
1005
+ # collided. Kept so installations written under it keep resolving without a vault write.
1006
+ - '{appKey}-databases-{index}-passwordKeyVault'
1007
+ # The original logical key, which is what aifabrix-ce-16 actually holds.
1008
+ - '{key}'
1009
+
1010
+ # Installed Dataplane stacks used semantic database names before the environment-scoped
1011
+ # convention. Keep the exact inventory here so onboarding can copy each value to its canonical
1012
+ # name without guessing which database slot it belongs to.
1013
+ - key: databases-dataplane-0-urlKeyVault
1014
+ ownerAppKey: dataplane
1015
+ scope: app
1016
+ generator:
1017
+ type: databaseUrl
1018
+ ensureOn: [upInfra, resolveApp]
1019
+ azure:
1020
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-urlKeyVault'
1021
+ legacyVaultSecretNames:
1022
+ - '{appKey}-databases-{index}-urlKeyVault'
1023
+ - '{key}'
1024
+ - dataplane-connection-string
1025
+
1026
+ - key: databases-dataplane-0-passwordKeyVault
1027
+ ownerAppKey: dataplane
1028
+ scope: app
1029
+ generator:
1030
+ type: databasePassword
1031
+ ensureOn: [upInfra, resolveApp]
1032
+ azure:
1033
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-passwordKeyVault'
1034
+ legacyVaultSecretNames:
1035
+ - '{appKey}-databases-{index}-passwordKeyVault'
1036
+ - '{key}'
1037
+ - dataplane-password
1038
+
1039
+ - key: databases-dataplane-1-urlKeyVault
1040
+ ownerAppKey: dataplane
1041
+ scope: app
1042
+ generator:
1043
+ type: databaseUrl
1044
+ ensureOn: [upInfra, resolveApp]
1045
+ azure:
1046
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-urlKeyVault'
1047
+ legacyVaultSecretNames:
1048
+ - '{appKey}-databases-{index}-urlKeyVault'
1049
+ - '{key}'
1050
+ - dataplane-vector-connection-string
1051
+
1052
+ - key: databases-dataplane-1-passwordKeyVault
1053
+ ownerAppKey: dataplane
1054
+ scope: app
1055
+ generator:
1056
+ type: databasePassword
1057
+ ensureOn: [upInfra, resolveApp]
1058
+ azure:
1059
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-passwordKeyVault'
1060
+ legacyVaultSecretNames:
1061
+ - '{appKey}-databases-{index}-passwordKeyVault'
1062
+ - '{key}'
1063
+ - dataplane-vector-password
1064
+
1065
+ - key: databases-dataplane-2-urlKeyVault
1066
+ ownerAppKey: dataplane
1067
+ scope: app
1068
+ generator:
1069
+ type: databaseUrl
1070
+ ensureOn: [upInfra, resolveApp]
1071
+ azure:
1072
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-urlKeyVault'
1073
+ legacyVaultSecretNames:
1074
+ - '{appKey}-databases-{index}-urlKeyVault'
1075
+ - '{key}'
1076
+ - dataplane-logs-connection-string
1077
+
1078
+ - key: databases-dataplane-2-passwordKeyVault
1079
+ ownerAppKey: dataplane
1080
+ scope: app
1081
+ generator:
1082
+ type: databasePassword
1083
+ ensureOn: [upInfra, resolveApp]
1084
+ azure:
1085
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-passwordKeyVault'
1086
+ legacyVaultSecretNames:
1087
+ - '{appKey}-databases-{index}-passwordKeyVault'
1088
+ - '{key}'
1089
+ - dataplane-logs-password
1090
+
1091
+ - key: databases-dataplane-3-urlKeyVault
1092
+ ownerAppKey: dataplane
1093
+ scope: app
1094
+ generator:
1095
+ type: databaseUrl
1096
+ ensureOn: [upInfra, resolveApp]
1097
+ azure:
1098
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-urlKeyVault'
864
1099
  legacyVaultSecretNames:
1100
+ - '{appKey}-databases-{index}-urlKeyVault'
865
1101
  - '{key}'
1102
+ - dataplane-records-connection-string
1103
+
1104
+ - key: databases-dataplane-3-passwordKeyVault
1105
+ ownerAppKey: dataplane
1106
+ scope: app
1107
+ generator:
1108
+ type: databasePassword
1109
+ ensureOn: [upInfra, resolveApp]
1110
+ azure:
1111
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-passwordKeyVault'
1112
+ legacyVaultSecretNames:
1113
+ - '{appKey}-databases-{index}-passwordKeyVault'
1114
+ - '{key}'
1115
+ - dataplane-records-password
866
1116
 
867
1117
  # Per-index database secrets. Index must match requires.databases order in application.yaml.
868
1118
  - keyPattern: '^databases-[a-z0-9-]+-\d+-urlKeyVault$'
@@ -871,8 +1121,12 @@ parameters:
871
1121
  type: databaseUrl
872
1122
  ensureOn: [upInfra, resolveApp]
873
1123
  azure:
874
- vaultSecretNamePattern: '{appKey}-databases-{index}-urlKeyVault'
1124
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-urlKeyVault'
875
1125
  legacyVaultSecretNames:
1126
+ # Pre-environment naming: app and index only, so two environments sharing one vault
1127
+ # collided. Kept so installations written under it keep resolving without a vault write.
1128
+ - '{appKey}-databases-{index}-urlKeyVault'
1129
+ # The original logical key, which is what aifabrix-ce-16 actually holds.
876
1130
  - '{key}'
877
1131
  notes: The manifest key stays logical; the app-prefixed name is the physical vault key.
878
1132
 
@@ -882,12 +1136,26 @@ parameters:
882
1136
  type: databasePassword
883
1137
  ensureOn: [upInfra, resolveApp]
884
1138
  azure:
885
- vaultSecretNamePattern: '{appKey}-databases-{index}-passwordKeyVault'
1139
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-passwordKeyVault'
886
1140
  legacyVaultSecretNames:
1141
+ # Pre-environment naming: app and index only, so two environments sharing one vault
1142
+ # collided. Kept so installations written under it keep resolving without a vault write.
1143
+ - '{appKey}-databases-{index}-passwordKeyVault'
1144
+ # The original logical key, which is what aifabrix-ce-16 actually holds.
887
1145
  - '{key}'
888
1146
 
1147
+ # Addresses, not credentials.
1148
+ #
1149
+ # These defaulted to sensitive (`sensitive` is absent, and the service reads `entry.sensitive !== false`),
1150
+ # so the system-secrets list API withheld the VALUE of a URL, a hostname and a virtual directory. Six
1151
+ # literal entries — frontdoor-url, frontdoor-host, keycloak-web-server-url,
1152
+ # keycloak-internal-server-url, keycloak-vdir-public, miso-controller-web-server-url and friends —
1153
+ # exist only to override that one key at a time, which is the hardcoding this convention removes.
1154
+ # An address is operator-facing by nature: hiding it makes an installation harder to diagnose and
1155
+ # protects nothing.
889
1156
  # Keycloak and other *-url keys (non-database).
890
1157
  - keyPattern: '^keycloak-.+-url$'
1158
+ sensitive: false
891
1159
  scope: shared-service
892
1160
  generator:
893
1161
  type: emptyString
@@ -895,6 +1163,7 @@ parameters:
895
1163
 
896
1164
  # App-scoped URL placeholders (filled at resolve / deploy).
897
1165
  - keyPattern: '^[a-z0-9-]+-url$'
1166
+ sensitive: false
898
1167
  scope: app
899
1168
  generator:
900
1169
  type: emptyString
@@ -902,6 +1171,7 @@ parameters:
902
1171
 
903
1172
  # Deploy manifest maps frontDoorRouting.host templates to this secret (Azure / pipeline).
904
1173
  - keyPattern: '^[a-z0-9-]+-frontdoor-routing-host$'
1174
+ sensitive: false
905
1175
  scope: app
906
1176
  generator:
907
1177
  type: emptyString
@@ -910,32 +1180,146 @@ parameters:
910
1180
  notes: Hostname template for Traefik / Front Door; filled at deploy.
911
1181
 
912
1182
  - keyPattern: '^[a-z0-9-]+-vdir-public$'
1183
+ sensitive: false
913
1184
  scope: app
914
1185
  generator:
915
1186
  type: emptyString
916
1187
  ensureOn: [resolveApp]
917
1188
 
918
1189
  - keyPattern: '^[a-z0-9-]+-vdir-internal$'
1190
+ sensitive: false
919
1191
  scope: app
920
1192
  generator:
921
1193
  type: emptyString
922
1194
  ensureOn: [resolveApp]
923
1195
 
924
1196
  - keyPattern: '^[a-z0-9-]+-host-public$'
1197
+ sensitive: false
925
1198
  scope: app
926
1199
  generator:
927
1200
  type: emptyString
928
1201
  ensureOn: [resolveApp]
929
1202
 
930
1203
  - keyPattern: '^[a-z0-9-]+-host-internal$'
1204
+ sensitive: false
931
1205
  scope: app
932
1206
  generator:
933
1207
  type: emptyString
934
1208
  ensureOn: [resolveApp]
935
1209
 
936
- # Remaining *KeyVault secrets — generated tokens/passwords (not database rows above).
937
- - keyPattern: '^[a-zA-Z0-9_-]+KeyVault$'
1210
+ # Secrets that are genuinely generated here, declared explicitly because the catch-all below no
1211
+ # longer invents values. Each is a shared secret with no other issuer: nothing outside this
1212
+ # catalog mints them, so an empty value would leave the consumer with nothing to verify against.
1213
+ - key: notification-event-webhook-secretKeyVault
1214
+ scope: app
1215
+ generator:
1216
+ type: randomBytes32
1217
+ ensureOn: [resolveApp]
1218
+ azure:
1219
+ vaultSecretName: notification-event-webhook-secretKeyVault
1220
+ notes: Shared secret signing notification webhook callbacks; generated, never supplied.
1221
+
1222
+ - key: secrets-encryptionKeyVault
1223
+ scope: app
1224
+ generator:
1225
+ type: randomBytes32
1226
+ ensureOn: [resolveApp]
1227
+ azure:
1228
+ vaultSecretName: secrets-encryptionKeyVault
1229
+ notes: Application-scoped encryption key; generated, never supplied.
1230
+
1231
+ - key: miso-controller-api-key-secretKeyVault
938
1232
  scope: app
939
1233
  generator:
940
1234
  type: randomBytes32
941
1235
  ensureOn: [resolveApp]
1236
+ azure:
1237
+ vaultSecretName: miso-controller-api-key-secretKeyVault
1238
+ notes: Generated API key for test/integration apps; generated, never supplied.
1239
+
1240
+ # `-idKeyVault` and `-nameKeyVault` are NOT secrets.
1241
+ #
1242
+ # An identifier and a name are operator-facing: a client ID, a tenant ID, a subscription ID, a
1243
+ # service name. They live in the same store as the secrets because they arrive the same way, but
1244
+ # there is nothing to withhold, and withholding them makes an installation harder to read.
1245
+ #
1246
+ # These two rows replace five literal entries that existed only to set `sensitive: false` on one
1247
+ # key each — `keycloak-client-idKeyVault`, `entra-client-idKeyVault`,
1248
+ # `azure-subscription-idKeyVault`, `azure-tenant-idKeyVault`, `azure-service-nameKeyVault`. The
1249
+ # convention now carries the decision, so a customer's own `myapp-tenant-idKeyVault` is readable
1250
+ # without an edit here.
1251
+ #
1252
+ # ORDER MATTERS: `findCatalogEntry` checks exact keys, then patterns in FILE ORDER and returns the
1253
+ # first match. Both suffixes also match the generic `*KeyVault` catch-all below, so they have to
1254
+ # stay above it or they inherit `sensitive: true` and the convention silently does nothing.
1255
+ - keyPattern: '^[a-zA-Z0-9_-]+-idKeyVault$'
1256
+ scope: app
1257
+ sensitive: false
1258
+ generator:
1259
+ type: emptyString
1260
+ ensureOn: [resolveApp]
1261
+
1262
+ - keyPattern: '^[a-zA-Z0-9_-]+-nameKeyVault$'
1263
+ scope: app
1264
+ sensitive: false
1265
+ generator:
1266
+ type: emptyString
1267
+ ensureOn: [resolveApp]
1268
+
1269
+ # Remaining *KeyVault secrets — created EMPTY, then supplied.
1270
+ #
1271
+ # This generated `randomBytes32`, so any `kv://<anything>KeyVault` key the catalog did not name
1272
+ # explicitly received 32 random bytes — 44 base64 characters — and the reference resolved to a
1273
+ # value that was never a credential anybody issued.
1274
+ #
1275
+ # Measured on installation aifabrix-ce-16, 2026-09-25: `secrets-policyPrivateKeyVault` is Mori's
1276
+ # licence-policy SIGNING KEY and held exactly that, 44 characters and not a PEM. Mori's
1277
+ # `signingKeysHealthCheck` parses the key rather than checking presence, so it reported
1278
+ # `signingKeys` down, the container exited, and the web app answered HTTP 503 on every path for two
1279
+ # days while App Service reported it Running. Mori's own comment had predicted this class in
1280
+ # writing: "the deployment builder provisioned an unknown Key Vault catalog key with a random
1281
+ # 44-character secret rather than failing: present, resolved, and not a private key at all".
1282
+ #
1283
+ # Empty is the correct default for a key nobody here mints. It creates the secret — so `kv://`
1284
+ # resolves and the reference is valid — while leaving the value visibly unset, which is a state an
1285
+ # operator can see and fix. A random value is indistinguishable from a real credential from the
1286
+ # outside, which is precisely what made it expensive.
1287
+ #
1288
+ # The catch-all stays a pattern rather than becoming a list of literals on purpose: a customer
1289
+ # deploying their own application declares its own `kv://` keys, and those must work without being
1290
+ # hardcoded here. Values arrive afterwards through the write-only system-secrets API
1291
+ # (`PUT /api/v1/environments/{envKey}/system-secrets/{catalogKey}`), which is what
1292
+ # aifabrix-builder plan 204.0 builds the CLI for.
1293
+ #
1294
+ # A secret that genuinely has no issuer but must exist gets an explicit `randomBytes32` entry
1295
+ # above. Adding one is the deliberate act; generation is no longer the silent default.
1296
+ - keyPattern: '^[a-zA-Z0-9_-]+KeyVault$'
1297
+ scope: app
1298
+ sensitive: true
1299
+ generator:
1300
+ type: emptyString
1301
+ ensureOn: [resolveApp]
1302
+
1303
+ # Non-secret values in the same store — the suffix carries the sensitivity.
1304
+ #
1305
+ # `*KeyVault` above means "secret": hidden by the list API, write-only through
1306
+ # `PUT /api/v1/environments/{envKey}/system-secrets/{catalogKey}`. `*Param` means the same storage
1307
+ # with nothing to hide: the list API returns the value, because `summarize()` emits `value` only
1308
+ # when `sensitive === false`.
1309
+ #
1310
+ # Why a pattern and not more literals: the suffix used to say nothing about sensitivity, so every
1311
+ # non-secret that happened to end in `KeyVault` needed its own literal row purely to set
1312
+ # `sensitive: false` — `keycloak-client-idKeyVault`, `entra-client-idKeyVault`,
1313
+ # `azure-subscription-idKeyVault`, `azure-tenant-idKeyVault`, `azure-service-nameKeyVault`. Client
1314
+ # IDs, tenant IDs and service names are not credentials, and hardcoding each one does not scale:
1315
+ # a customer deploying their own application cannot get a visible parameter without an edit here.
1316
+ #
1317
+ # With the convention, visibility is a naming decision the application author makes and a reader
1318
+ # can see: name it `…Param` to be readable, `…KeyVault` to be secret. Changing your mind is a
1319
+ # rename, not a catalog change. Both create the entry EMPTY and are supplied afterwards.
1320
+ - keyPattern: '^[a-zA-Z0-9_-]+Param$'
1321
+ scope: app
1322
+ sensitive: false
1323
+ generator:
1324
+ type: emptyString
1325
+ ensureOn: [resolveApp]