@aifabrix/builder 2.60.0 → 2.61.2

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 (112) 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/configuration.api.js +131 -0
  14. package/lib/api/system-secrets.api.js +72 -0
  15. package/lib/app/run-docker-fallback.js +6 -1
  16. package/lib/app/run-helpers.js +2 -1
  17. package/lib/app/run-parameter-sync.js +142 -0
  18. package/lib/build/docker-build-args.js +5 -2
  19. package/lib/build/index.js +3 -2
  20. package/lib/build/standard-docker-build.js +2 -2
  21. package/lib/cli/setup-app.js +5 -0
  22. package/lib/cli/setup-environment.js +156 -0
  23. package/lib/commands/datasource-capability-upsert-cli.js +112 -0
  24. package/lib/commands/datasource-capability.js +4 -2
  25. package/lib/commands/env-secret-context.js +113 -0
  26. package/lib/commands/env-secret-list.js +200 -0
  27. package/lib/commands/env-secret-push-confirm.js +54 -0
  28. package/lib/commands/env-secret-push-run.js +227 -0
  29. package/lib/commands/env-secret-push.js +168 -0
  30. package/lib/commands/repair-datasource-apply.js +2 -0
  31. package/lib/commands/repair-datasource-keyed-document.js +122 -0
  32. package/lib/commands/repair-datasource-run.js +1 -0
  33. package/lib/commands/setup-modes.js +1 -8
  34. package/lib/commands/setup-prompts.js +2 -180
  35. package/lib/commands/verify-operations-skip-e2e.js +25 -1
  36. package/lib/commands/verify-operations-steps.js +13 -1
  37. package/lib/commands/wizard-config-normalizer.js +7 -4
  38. package/lib/commands/wizard-core.js +5 -157
  39. package/lib/commands/wizard-file-saving.js +163 -0
  40. package/lib/core/env-platform-expand.js +5 -1
  41. package/lib/core/secrets-env-content.js +5 -1
  42. package/lib/core/secrets-env-write.js +10 -3
  43. package/lib/core/secrets-load.js +4 -2
  44. package/lib/datasource/binary-documents-validator.js +190 -0
  45. package/lib/datasource/capability/run-capability-upsert.js +202 -0
  46. package/lib/datasource/capability/upsert-ingredients.js +291 -0
  47. package/lib/datasource/capability/upsert-operations.js +138 -0
  48. package/lib/datasource/capability/upsert-test-scaffold.js +189 -0
  49. package/lib/datasource/validate.js +12 -5
  50. package/lib/generator/index.js +3 -0
  51. package/lib/lifecycle/product-model.js +4 -3
  52. package/lib/lifecycle/report-display.js +3 -2
  53. package/lib/parameters/infra-parameter-catalog.js +1 -1
  54. package/lib/programmatic/builder-help-enterprise-sync-fabrix.js +1 -1
  55. package/lib/programmatic/builder-help-governance.js +1 -1
  56. package/lib/programmatic/builder-help.js +1 -1
  57. package/lib/role-assistant/test-runner-workhub-answers.js +4 -1
  58. package/lib/role-assistant/test-runner-workhub-missing-fields.js +42 -0
  59. package/lib/role-assistant/test-runner-workhub-wait-stop.js +94 -0
  60. package/lib/role-assistant/test-runner-workhub.js +28 -19
  61. package/lib/schema/application-schema.json +14 -2
  62. package/lib/schema/external-datasource.schema.json +23 -3
  63. package/lib/schema/infra.parameter.yaml +388 -38
  64. package/lib/utils/compose-generate-docker-compose.js +7 -12
  65. package/lib/utils/datasource-binary-evidence.js +92 -0
  66. package/lib/utils/datasource-test-run-capability-scope.js +44 -1
  67. package/lib/utils/datasource-test-run-debug-display.js +2 -0
  68. package/lib/utils/datasource-test-run-display.js +8 -2
  69. package/lib/utils/datasource-test-run-issue-guidance.js +176 -0
  70. package/lib/utils/datasource-test-run-tty-log.js +2 -0
  71. package/lib/utils/env-copy.js +11 -10
  72. package/lib/utils/external-system-system-test-tty.js +3 -2
  73. package/lib/utils/image-tags.js +2 -2
  74. package/lib/utils/platform-kv-ref.js +1 -1
  75. package/lib/utils/prepare-local-data-mount.js +58 -0
  76. package/lib/utils/secrets-helpers.js +0 -1
  77. package/lib/utils/system-secret-mapping.js +125 -0
  78. package/lib/utils/test-log-writer.js +2 -1
  79. package/lib/validation/external-manifest-validator.js +5 -0
  80. package/lib/validation/openapi-contract-surface-validator.js +3 -1
  81. package/lib/validation/validate-external-file.js +5 -1
  82. package/package.json +4 -3
  83. package/templates/agent-kit/agent-kit.yaml +1 -1
  84. package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +2 -1
  85. package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +15 -0
  86. package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +3 -2
  87. package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +6 -2
  88. package/templates/agent-kit/skills/aifabrix-prove/references/evidence-lifecycle.md +22 -0
  89. package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +7 -1
  90. package/templates/agent-kit/skills/aifabrix-role-assistant/references/testing-playbook.md +117 -0
  91. package/templates/agent-kit/skills/shared/interaction.md +83 -0
  92. package/templates/agent-kit/skills/shared/status.md +3 -12
  93. package/templates/applications/builder-api/application.yaml +1 -1
  94. package/templates/applications/builder-api/env.template +5 -1
  95. package/templates/applications/dataplane/application.yaml +1 -1
  96. package/templates/applications/dataplane/env.template +10 -12
  97. package/templates/applications/keycloak/application.yaml +6 -1
  98. package/templates/applications/miso-controller/application.yaml +74 -2
  99. package/templates/applications/miso-controller/env.template +42 -3
  100. package/templates/applications/miso-controller/rbac.yaml +17 -0
  101. package/templates/external-system/external-datasource.yaml.hbs +7 -1
  102. package/templates/marketplace/main.json +4 -4
  103. package/templates/python/docker-compose.hbs +1 -1
  104. package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +0 -41
  105. /package/{lib/programmatic/help-content → docs/builder-help/content}/channel-onboarding.md +0 -0
  106. /package/{lib/programmatic/help-content → docs/builder-help/content}/cip-overview.md +0 -0
  107. /package/{lib/programmatic/help-content → docs/builder-help/content}/connected-system-ui.md +0 -0
  108. /package/{lib/programmatic/help-content → docs/builder-help/content}/dimensions-guide.md +0 -0
  109. /package/{lib/programmatic/help-content → docs/builder-help/content}/enterprise-sync-fabrix.md +0 -0
  110. /package/{lib/programmatic/help-content → docs/builder-help/content}/overview.md +0 -0
  111. /package/{lib/programmatic/help-content → docs/builder-help/content}/subscription-guide.md +0 -0
  112. /package/{lib/programmatic/help-content → docs/builder-help/content}/workflow.md +0 -0
@@ -7,7 +7,7 @@
7
7
  "key":"external-datasource-schema",
8
8
  "name":"External Data Source Configuration Schema",
9
9
  "description":"JSON schema for validating ExternalDataSource configuration files",
10
- "version":"2.9.12",
10
+ "version":"2.9.13",
11
11
  "type":"schema",
12
12
  "category":"integration",
13
13
  "author":"AI Fabrix Team",
@@ -31,6 +31,15 @@
31
31
  "external-system.schema.json"
32
32
  ],
33
33
  "changelog":[
34
+ {
35
+ "version":"2.9.13",
36
+ "date":"2026-09-24T00:00:00Z",
37
+ "changes":[
38
+ "Added fetch.responseType ('json' | 'binary', default json): a file body becomes one binary record with headers and request arguments exposed to fieldMappings while bytes stay out of traces.",
39
+ "Added output.mode 'documents' for entityType documentStorage: binary records are persisted through the document pipeline and emitted as document references; documentStorage.binaryOperationRef may name the datasource's own get for keyed retrieval."
40
+ ],
41
+ "breaking":false
42
+ },
34
43
  {
35
44
  "version":"2.9.12",
36
45
  "date":"2026-09-17T00:00:00Z",
@@ -2739,6 +2748,15 @@
2739
2748
  },
2740
2749
  "httpResponseNormalization":{
2741
2750
  "$ref":"#/$defs/httpResponseNormalization"
2751
+ },
2752
+ "responseType":{
2753
+ "type":"string",
2754
+ "enum":[
2755
+ "json",
2756
+ "binary"
2757
+ ],
2758
+ "default":"json",
2759
+ "description":"Body contract of the vendor response. 'binary' turns a file body (application/octet-stream, PDF, image ...) into one binary record whose bytes stay out of traces: {_binary:{contentType,size,filename,sha256}, headers:{...}, params:{...}} for map expressions such as raw.headers.AttachmentName or raw.params.PurchaseOrderNumber. A binary Content-Type is promoted automatically when responseType is omitted; declaring 'binary' fails closed when the body is JSON."
2742
2760
  }
2743
2761
  },
2744
2762
  "allOf":[
@@ -3174,9 +3192,11 @@
3174
3192
  "mode":{
3175
3193
  "type":"string",
3176
3194
  "enum":[
3177
- "records"
3195
+ "records",
3196
+ "documents"
3178
3197
  ],
3179
- "default":"records"
3198
+ "default":"records",
3199
+ "description":"'records' emits normalized records. 'documents' (entityType documentStorage only) stores each binary record through the document pipeline (document store, DocumentRecord/registry rows, FK dimensions, ingestion per documentStorage.ingestAfterSync) and emits document references {key, filename, contentType, size, sha256, downloadRef}; IDE simulation never persists."
3180
3200
  },
3181
3201
  "limit":{
3182
3202
  "type":"integer",
@@ -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.
@@ -594,6 +597,7 @@ parameters:
594
597
  - key: miso-controller-client-idKeyVault
595
598
  ownerAppKey: miso-controller
596
599
  scope: app
600
+ sensitive: false
597
601
  generator:
598
602
  type: literal
599
603
  value: 'miso-controller-miso-miso-controller'
@@ -615,10 +619,35 @@ parameters:
615
619
  Per-app OAuth client secret used by miso-controller for service-to-service auth (MISO_CLIENTSECRET).
616
620
  Generated on first ensure for local bootstrap.
617
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.
645
+
618
646
  # Dataplane ↔ controller OAuth (builder/dataplane env.template MISO_CLIENTID / MISO_CLIENTSECRET).
619
647
  - key: dataplane-client-idKeyVault
620
648
  ownerAppKey: dataplane
621
649
  scope: app
650
+ sensitive: false
622
651
  generator:
623
652
  type: literal
624
653
  value: 'miso-controller-dev-dataplane'
@@ -652,18 +681,39 @@ parameters:
652
681
  notes: Empty locally; set when calling Mori with API key auth.
653
682
 
654
683
  # Public, but a security-critical trust anchor. Never derive it from a pasted JWT.
684
+ #
685
+ # `sensitive: true` on a public key is deliberate -- 227.1 made Miso verify licences against a
686
+ # PINNED key precisely because a key travelling inside the token it verifies proves nothing, so
687
+ # this value must be protected from tampering even though disclosing it costs nothing.
688
+ #
689
+ # It is owned by `miso-controller` rather than left system-scoped because a system-scoped
690
+ # sensitive secret is readable by NO application (`forbidden-system-secret`). Measured on
691
+ # aifabrix-ce-16 2026-09-27: miso-controller declares MORI_LICENSE_PUBLIC_KEY in its manifest, yet
692
+ # the key was absent from its 70 registry rows and from its snapshot, so the installation could
693
+ # never have verified a licence. Ownership is what makes a trust anchor readable by the service
694
+ # whose trust it anchors -- the same correction already applied to secrets-policyPrivateKeyVault.
655
695
  - key: mori-license-public-keyKeyVault
656
696
  category: Mori
697
+ ownerAppKey: miso-controller
657
698
  sensitive: true
658
699
  valueSource: optionalInput
659
700
  requiredOnSystemOnboarding: false
660
- scope: system
701
+ scope: app
702
+ # The public half of the Mori licence pair above, so the key Miso verifies with cannot drift
703
+ # from the key Mori signs with. Was `emptyString`: the two halves came from unrelated places
704
+ # and an installation could hold a perfectly valid public key belonging to a different private
705
+ # key, which verifies nothing and reports no error.
706
+ #
707
+ # Rotating an installation that already holds a value here means clearing BOTH halves
708
+ # together — the generator is get-or-create, so replacing one alone leaves a mismatched pair.
661
709
  generator:
662
- type: emptyString
710
+ type: rsaKeyPair
711
+ pairId: mori-license-signing
712
+ part: public
663
713
  ensureOn: [upInfra, resolveApp]
664
714
  azure:
665
715
  vaultSecretName: mori-license-public-keyKeyVault
666
- notes: Trusted Mori SPKI PEM public key, provisioned by the installer or issuer release identity.
716
+ notes: SPKI PEM public half of the Mori licence signing pair; linked to secrets-licensePrivateKeyVault.
667
717
 
668
718
  - key: appinsights-connectionStringKeyVault
669
719
  category: Observability
@@ -705,7 +755,7 @@ parameters:
705
755
  vaultSecretName: loganalytics-workspace-id
706
756
  notes: Azure SDK-derived Log Analytics workspace customer identifier.
707
757
 
708
- - key: azureopenai-endpoint
758
+ - key: azure-openai/endpoint
709
759
  category: Azure OpenAI
710
760
  sensitive: false
711
761
  valueSource: optionalInput
@@ -715,10 +765,10 @@ parameters:
715
765
  type: emptyString
716
766
  ensureOn: [resolveApp]
717
767
  azure:
718
- vaultSecretName: azureopenai-endpoint
719
- notes: Azure OpenAI endpoint produced by the environment infrastructure deployment.
768
+ vaultSecretName: dev-dataplane-azure-openai-endpoint
769
+ notes: Azure OpenAI endpoint produced by infrastructure at the standard dev Dataplane address.
720
770
 
721
- - key: azureopenai-apikey
771
+ - key: azure-openai/apiKey
722
772
  category: Azure OpenAI
723
773
  sensitive: true
724
774
  valueSource: optionalInput
@@ -728,21 +778,8 @@ parameters:
728
778
  type: emptyString
729
779
  ensureOn: [resolveApp]
730
780
  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.
781
+ vaultSecretName: dev-dataplane-azure-openai-api-key
782
+ notes: Azure OpenAI API key produced by infrastructure at the standard dev Dataplane address.
746
783
 
747
784
  - key: azure-openaiapi-urlKeyVault
748
785
  scope: app
@@ -752,17 +789,13 @@ parameters:
752
789
  azure:
753
790
  notes: Empty until set; Azure OpenAI endpoint URL.
754
791
 
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
792
  # Dataplane RS256 signing identity. The two rows are generated as one linked pair.
764
793
  - key: secrets-certificatePrivateKeyVault
765
794
  ownerAppKey: dataplane
795
+ # Deliberately NOT shared with mori-controller. `readability-real-catalog.test.ts` asserts
796
+ # "denies mori signing licences with the dataplane signing key" — Mori's manifest pointing
797
+ # MORI_LICENSE_PRIVATE_KEY at this key is the misconfiguration, not the denial. The fix is a
798
+ # mori-controller-owned licence key and a manifest change, never widening this entry.
766
799
  scope: app
767
800
  generator:
768
801
  type: rsaKeyPair
@@ -773,6 +806,66 @@ parameters:
773
806
  vaultSecretName: secrets-certificatePrivateKeyVault
774
807
  notes: PKCS#8 PEM private key generated with the matching public key.
775
808
 
809
+ # Mori's policy signing key. Present in every installation's vault but absent from this catalog,
810
+ # so it fell through to the `^[a-zA-Z0-9_-]+KeyVault$` catch-all — `scope: app` with no owner,
811
+ # which `resolveOwnershipDecision` cannot classify and the policy therefore denies to everyone.
812
+ #
813
+ # Measured on aifabrix-ce-16, 2026-09-26: the secret exists, Mori reads it today through an App
814
+ # Service Key Vault reference, and bootstrap omitted it. That is the same key whose absence
815
+ # previously failed Mori's signing health check.
816
+ - key: secrets-policyPrivateKeyVault
817
+ ownerAppKey: mori-controller
818
+ scope: app
819
+ sensitive: true
820
+ generator:
821
+ type: rsaKeyPair
822
+ pairId: mori-policy-signing
823
+ part: private
824
+ ensureOn: [resolveApp]
825
+ azure:
826
+ vaultSecretName: secrets-policyPrivateKeyVault
827
+ notes: PKCS#8 PEM private key for Mori policy signing; MORI_POLICY_KEY_ID names the pair.
828
+
829
+ # Mori's LICENCE signing key, which is not its policy signing key and not the dataplane's.
830
+ #
831
+ # `subscription-license.operations.ts` refuses to issue at all without it:
832
+ # if (!privateKey) throw ErrorFactory.internalServerError('License signing is unavailable')
833
+ # and until this entry existed there was nothing for it to read. Mori's manifest pointed
834
+ # MORI_LICENSE_PRIVATE_KEY at `secrets-certificatePrivateKeyVault`, which the dataplane owns, so
835
+ # the read was denied -- correctly. An existing test (`denies mori signing licences with the
836
+ # dataplane signing key`) pins that denial, so the fix is a key Mori owns, never a wider grant.
837
+ #
838
+ # `emptyString` rather than `rsaKeyPair`: the operator supplies the pair, because whoever holds
839
+ # this key decides which licences an installation will believe. Generating it during onboarding
840
+ # would make the platform mint its own trust root as a side effect of deployment. Onboarding
841
+ # creates the placeholder, `aifabrix env secret push` writes the real value, and create-if-absent
842
+ # then leaves it alone. The verifying half is `mori-license-public-keyKeyVault`, pinned to
843
+ # miso-controller; a pair is only useful if both halves are readable by their own service.
844
+ - key: secrets-licensePrivateKeyVault
845
+ category: Mori
846
+ ownerAppKey: mori-controller
847
+ scope: app
848
+ sensitive: true
849
+ valueSource: optionalInput
850
+ requiredOnSystemOnboarding: false
851
+ # Generated as one linked pair with mori-license-public-keyKeyVault, the same way the
852
+ # dataplane's signing identity is. Was `emptyString`, which meant an installation that
853
+ # repointed Mori at this key got nothing and `subscription-license.operations.ts` refused
854
+ # with "License signing is unavailable" -- so the only working configuration was the wrong
855
+ # one: Mori signing with the DATAPLANE's key via secrets-certificatePrivateKeyVault.
856
+ #
857
+ # Mori and the dataplane must not share a signing identity. Sharing one means the dataplane
858
+ # holds a key that mints licences, and the two services have completely different blast
859
+ # radii. Mori is the licence server; the dataplane runs customer workloads.
860
+ generator:
861
+ type: rsaKeyPair
862
+ pairId: mori-license-signing
863
+ part: private
864
+ ensureOn: [resolveApp]
865
+ azure:
866
+ vaultSecretName: secrets-licensePrivateKeyVault
867
+ notes: PKCS#8 PEM private key for Mori licence signing; MORI_LICENSE_KEY_ID names the pair.
868
+
776
869
  - key: secrets-certificatePublicKeyVault
777
870
  ownerAppKey: dataplane
778
871
  scope: app
@@ -825,8 +918,12 @@ parameters:
825
918
  type: databaseUrl
826
919
  ensureOn: [upInfra, resolveApp]
827
920
  azure:
828
- vaultSecretNamePattern: '{appKey}-databases-{index}-urlKeyVault'
921
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-urlKeyVault'
829
922
  legacyVaultSecretNames:
923
+ # Pre-environment naming: app and index only, so two environments sharing one vault
924
+ # collided. Kept so installations written under it keep resolving without a vault write.
925
+ - '{appKey}-databases-{index}-urlKeyVault'
926
+ # The original logical key, which is what aifabrix-ce-16 actually holds.
830
927
  - '{key}'
831
928
  - '{serviceName}-databases-0-urlKeyVault'
832
929
 
@@ -837,8 +934,12 @@ parameters:
837
934
  type: databasePassword
838
935
  ensureOn: [upInfra, resolveApp]
839
936
  azure:
840
- vaultSecretNamePattern: '{appKey}-databases-{index}-passwordKeyVault'
937
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-passwordKeyVault'
841
938
  legacyVaultSecretNames:
939
+ # Pre-environment naming: app and index only, so two environments sharing one vault
940
+ # collided. Kept so installations written under it keep resolving without a vault write.
941
+ - '{appKey}-databases-{index}-passwordKeyVault'
942
+ # The original logical key, which is what aifabrix-ce-16 actually holds.
842
943
  - '{key}'
843
944
 
844
945
  - key: databases-miso-controller-1-urlKeyVault
@@ -848,8 +949,12 @@ parameters:
848
949
  type: databaseUrl
849
950
  ensureOn: [upInfra, resolveApp]
850
951
  azure:
851
- vaultSecretNamePattern: '{appKey}-databases-{index}-urlKeyVault'
952
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-urlKeyVault'
852
953
  legacyVaultSecretNames:
954
+ # Pre-environment naming: app and index only, so two environments sharing one vault
955
+ # collided. Kept so installations written under it keep resolving without a vault write.
956
+ - '{appKey}-databases-{index}-urlKeyVault'
957
+ # The original logical key, which is what aifabrix-ce-16 actually holds.
853
958
  - '{key}'
854
959
  - '{serviceName}-databases-1-urlKeyVault'
855
960
 
@@ -860,9 +965,120 @@ parameters:
860
965
  type: databasePassword
861
966
  ensureOn: [upInfra, resolveApp]
862
967
  azure:
863
- vaultSecretNamePattern: '{appKey}-databases-{index}-passwordKeyVault'
968
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-passwordKeyVault'
969
+ legacyVaultSecretNames:
970
+ # Pre-environment naming: app and index only, so two environments sharing one vault
971
+ # collided. Kept so installations written under it keep resolving without a vault write.
972
+ - '{appKey}-databases-{index}-passwordKeyVault'
973
+ # The original logical key, which is what aifabrix-ce-16 actually holds.
974
+ - '{key}'
975
+
976
+ # Installed Dataplane stacks used semantic database names before the environment-scoped
977
+ # convention. Keep the exact inventory here so onboarding can copy each value to its canonical
978
+ # name without guessing which database slot it belongs to.
979
+ - key: databases-dataplane-0-urlKeyVault
980
+ ownerAppKey: dataplane
981
+ scope: app
982
+ generator:
983
+ type: databaseUrl
984
+ ensureOn: [upInfra, resolveApp]
985
+ azure:
986
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-urlKeyVault'
987
+ legacyVaultSecretNames:
988
+ - '{appKey}-databases-{index}-urlKeyVault'
989
+ - '{key}'
990
+ - dataplane-connection-string
991
+
992
+ - key: databases-dataplane-0-passwordKeyVault
993
+ ownerAppKey: dataplane
994
+ scope: app
995
+ generator:
996
+ type: databasePassword
997
+ ensureOn: [upInfra, resolveApp]
998
+ azure:
999
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-passwordKeyVault'
1000
+ legacyVaultSecretNames:
1001
+ - '{appKey}-databases-{index}-passwordKeyVault'
1002
+ - '{key}'
1003
+ - dataplane-password
1004
+
1005
+ - key: databases-dataplane-1-urlKeyVault
1006
+ ownerAppKey: dataplane
1007
+ scope: app
1008
+ generator:
1009
+ type: databaseUrl
1010
+ ensureOn: [upInfra, resolveApp]
1011
+ azure:
1012
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-urlKeyVault'
864
1013
  legacyVaultSecretNames:
1014
+ - '{appKey}-databases-{index}-urlKeyVault'
865
1015
  - '{key}'
1016
+ - dataplane-vector-connection-string
1017
+
1018
+ - key: databases-dataplane-1-passwordKeyVault
1019
+ ownerAppKey: dataplane
1020
+ scope: app
1021
+ generator:
1022
+ type: databasePassword
1023
+ ensureOn: [upInfra, resolveApp]
1024
+ azure:
1025
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-passwordKeyVault'
1026
+ legacyVaultSecretNames:
1027
+ - '{appKey}-databases-{index}-passwordKeyVault'
1028
+ - '{key}'
1029
+ - dataplane-vector-password
1030
+
1031
+ - key: databases-dataplane-2-urlKeyVault
1032
+ ownerAppKey: dataplane
1033
+ scope: app
1034
+ generator:
1035
+ type: databaseUrl
1036
+ ensureOn: [upInfra, resolveApp]
1037
+ azure:
1038
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-urlKeyVault'
1039
+ legacyVaultSecretNames:
1040
+ - '{appKey}-databases-{index}-urlKeyVault'
1041
+ - '{key}'
1042
+ - dataplane-logs-connection-string
1043
+
1044
+ - key: databases-dataplane-2-passwordKeyVault
1045
+ ownerAppKey: dataplane
1046
+ scope: app
1047
+ generator:
1048
+ type: databasePassword
1049
+ ensureOn: [upInfra, resolveApp]
1050
+ azure:
1051
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-passwordKeyVault'
1052
+ legacyVaultSecretNames:
1053
+ - '{appKey}-databases-{index}-passwordKeyVault'
1054
+ - '{key}'
1055
+ - dataplane-logs-password
1056
+
1057
+ - key: databases-dataplane-3-urlKeyVault
1058
+ ownerAppKey: dataplane
1059
+ scope: app
1060
+ generator:
1061
+ type: databaseUrl
1062
+ ensureOn: [upInfra, resolveApp]
1063
+ azure:
1064
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-urlKeyVault'
1065
+ legacyVaultSecretNames:
1066
+ - '{appKey}-databases-{index}-urlKeyVault'
1067
+ - '{key}'
1068
+ - dataplane-records-connection-string
1069
+
1070
+ - key: databases-dataplane-3-passwordKeyVault
1071
+ ownerAppKey: dataplane
1072
+ scope: app
1073
+ generator:
1074
+ type: databasePassword
1075
+ ensureOn: [upInfra, resolveApp]
1076
+ azure:
1077
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-passwordKeyVault'
1078
+ legacyVaultSecretNames:
1079
+ - '{appKey}-databases-{index}-passwordKeyVault'
1080
+ - '{key}'
1081
+ - dataplane-records-password
866
1082
 
867
1083
  # Per-index database secrets. Index must match requires.databases order in application.yaml.
868
1084
  - keyPattern: '^databases-[a-z0-9-]+-\d+-urlKeyVault$'
@@ -871,8 +1087,12 @@ parameters:
871
1087
  type: databaseUrl
872
1088
  ensureOn: [upInfra, resolveApp]
873
1089
  azure:
874
- vaultSecretNamePattern: '{appKey}-databases-{index}-urlKeyVault'
1090
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-urlKeyVault'
875
1091
  legacyVaultSecretNames:
1092
+ # Pre-environment naming: app and index only, so two environments sharing one vault
1093
+ # collided. Kept so installations written under it keep resolving without a vault write.
1094
+ - '{appKey}-databases-{index}-urlKeyVault'
1095
+ # The original logical key, which is what aifabrix-ce-16 actually holds.
876
1096
  - '{key}'
877
1097
  notes: The manifest key stays logical; the app-prefixed name is the physical vault key.
878
1098
 
@@ -882,12 +1102,26 @@ parameters:
882
1102
  type: databasePassword
883
1103
  ensureOn: [upInfra, resolveApp]
884
1104
  azure:
885
- vaultSecretNamePattern: '{appKey}-databases-{index}-passwordKeyVault'
1105
+ vaultSecretNamePattern: '{environment}-{appKey}-databases-{index}-passwordKeyVault'
886
1106
  legacyVaultSecretNames:
1107
+ # Pre-environment naming: app and index only, so two environments sharing one vault
1108
+ # collided. Kept so installations written under it keep resolving without a vault write.
1109
+ - '{appKey}-databases-{index}-passwordKeyVault'
1110
+ # The original logical key, which is what aifabrix-ce-16 actually holds.
887
1111
  - '{key}'
888
1112
 
1113
+ # Addresses, not credentials.
1114
+ #
1115
+ # These defaulted to sensitive (`sensitive` is absent, and the service reads `entry.sensitive !== false`),
1116
+ # so the system-secrets list API withheld the VALUE of a URL, a hostname and a virtual directory. Six
1117
+ # literal entries — frontdoor-url, frontdoor-host, keycloak-web-server-url,
1118
+ # keycloak-internal-server-url, keycloak-vdir-public, miso-controller-web-server-url and friends —
1119
+ # exist only to override that one key at a time, which is the hardcoding this convention removes.
1120
+ # An address is operator-facing by nature: hiding it makes an installation harder to diagnose and
1121
+ # protects nothing.
889
1122
  # Keycloak and other *-url keys (non-database).
890
1123
  - keyPattern: '^keycloak-.+-url$'
1124
+ sensitive: false
891
1125
  scope: shared-service
892
1126
  generator:
893
1127
  type: emptyString
@@ -895,6 +1129,7 @@ parameters:
895
1129
 
896
1130
  # App-scoped URL placeholders (filled at resolve / deploy).
897
1131
  - keyPattern: '^[a-z0-9-]+-url$'
1132
+ sensitive: false
898
1133
  scope: app
899
1134
  generator:
900
1135
  type: emptyString
@@ -902,6 +1137,7 @@ parameters:
902
1137
 
903
1138
  # Deploy manifest maps frontDoorRouting.host templates to this secret (Azure / pipeline).
904
1139
  - keyPattern: '^[a-z0-9-]+-frontdoor-routing-host$'
1140
+ sensitive: false
905
1141
  scope: app
906
1142
  generator:
907
1143
  type: emptyString
@@ -910,32 +1146,146 @@ parameters:
910
1146
  notes: Hostname template for Traefik / Front Door; filled at deploy.
911
1147
 
912
1148
  - keyPattern: '^[a-z0-9-]+-vdir-public$'
1149
+ sensitive: false
913
1150
  scope: app
914
1151
  generator:
915
1152
  type: emptyString
916
1153
  ensureOn: [resolveApp]
917
1154
 
918
1155
  - keyPattern: '^[a-z0-9-]+-vdir-internal$'
1156
+ sensitive: false
919
1157
  scope: app
920
1158
  generator:
921
1159
  type: emptyString
922
1160
  ensureOn: [resolveApp]
923
1161
 
924
1162
  - keyPattern: '^[a-z0-9-]+-host-public$'
1163
+ sensitive: false
925
1164
  scope: app
926
1165
  generator:
927
1166
  type: emptyString
928
1167
  ensureOn: [resolveApp]
929
1168
 
930
1169
  - keyPattern: '^[a-z0-9-]+-host-internal$'
1170
+ sensitive: false
931
1171
  scope: app
932
1172
  generator:
933
1173
  type: emptyString
934
1174
  ensureOn: [resolveApp]
935
1175
 
936
- # Remaining *KeyVault secrets — generated tokens/passwords (not database rows above).
937
- - keyPattern: '^[a-zA-Z0-9_-]+KeyVault$'
1176
+ # Secrets that are genuinely generated here, declared explicitly because the catch-all below no
1177
+ # longer invents values. Each is a shared secret with no other issuer: nothing outside this
1178
+ # catalog mints them, so an empty value would leave the consumer with nothing to verify against.
1179
+ - key: notification-event-webhook-secretKeyVault
1180
+ scope: app
1181
+ generator:
1182
+ type: randomBytes32
1183
+ ensureOn: [resolveApp]
1184
+ azure:
1185
+ vaultSecretName: notification-event-webhook-secretKeyVault
1186
+ notes: Shared secret signing notification webhook callbacks; generated, never supplied.
1187
+
1188
+ - key: secrets-encryptionKeyVault
938
1189
  scope: app
939
1190
  generator:
940
1191
  type: randomBytes32
941
1192
  ensureOn: [resolveApp]
1193
+ azure:
1194
+ vaultSecretName: secrets-encryptionKeyVault
1195
+ notes: Application-scoped encryption key; generated, never supplied.
1196
+
1197
+ - key: miso-controller-api-key-secretKeyVault
1198
+ scope: app
1199
+ generator:
1200
+ type: randomBytes32
1201
+ ensureOn: [resolveApp]
1202
+ azure:
1203
+ vaultSecretName: miso-controller-api-key-secretKeyVault
1204
+ notes: Generated API key for test/integration apps; generated, never supplied.
1205
+
1206
+ # `-idKeyVault` and `-nameKeyVault` are NOT secrets.
1207
+ #
1208
+ # An identifier and a name are operator-facing: a client ID, a tenant ID, a subscription ID, a
1209
+ # service name. They live in the same store as the secrets because they arrive the same way, but
1210
+ # there is nothing to withhold, and withholding them makes an installation harder to read.
1211
+ #
1212
+ # These two rows replace five literal entries that existed only to set `sensitive: false` on one
1213
+ # key each — `keycloak-client-idKeyVault`, `entra-client-idKeyVault`,
1214
+ # `azure-subscription-idKeyVault`, `azure-tenant-idKeyVault`, `azure-service-nameKeyVault`. The
1215
+ # convention now carries the decision, so a customer's own `myapp-tenant-idKeyVault` is readable
1216
+ # without an edit here.
1217
+ #
1218
+ # ORDER MATTERS: `findCatalogEntry` checks exact keys, then patterns in FILE ORDER and returns the
1219
+ # first match. Both suffixes also match the generic `*KeyVault` catch-all below, so they have to
1220
+ # stay above it or they inherit `sensitive: true` and the convention silently does nothing.
1221
+ - keyPattern: '^[a-zA-Z0-9_-]+-idKeyVault$'
1222
+ scope: app
1223
+ sensitive: false
1224
+ generator:
1225
+ type: emptyString
1226
+ ensureOn: [resolveApp]
1227
+
1228
+ - keyPattern: '^[a-zA-Z0-9_-]+-nameKeyVault$'
1229
+ scope: app
1230
+ sensitive: false
1231
+ generator:
1232
+ type: emptyString
1233
+ ensureOn: [resolveApp]
1234
+
1235
+ # Remaining *KeyVault secrets — created EMPTY, then supplied.
1236
+ #
1237
+ # This generated `randomBytes32`, so any `kv://<anything>KeyVault` key the catalog did not name
1238
+ # explicitly received 32 random bytes — 44 base64 characters — and the reference resolved to a
1239
+ # value that was never a credential anybody issued.
1240
+ #
1241
+ # Measured on installation aifabrix-ce-16, 2026-09-25: `secrets-policyPrivateKeyVault` is Mori's
1242
+ # licence-policy SIGNING KEY and held exactly that, 44 characters and not a PEM. Mori's
1243
+ # `signingKeysHealthCheck` parses the key rather than checking presence, so it reported
1244
+ # `signingKeys` down, the container exited, and the web app answered HTTP 503 on every path for two
1245
+ # days while App Service reported it Running. Mori's own comment had predicted this class in
1246
+ # writing: "the deployment builder provisioned an unknown Key Vault catalog key with a random
1247
+ # 44-character secret rather than failing: present, resolved, and not a private key at all".
1248
+ #
1249
+ # Empty is the correct default for a key nobody here mints. It creates the secret — so `kv://`
1250
+ # resolves and the reference is valid — while leaving the value visibly unset, which is a state an
1251
+ # operator can see and fix. A random value is indistinguishable from a real credential from the
1252
+ # outside, which is precisely what made it expensive.
1253
+ #
1254
+ # The catch-all stays a pattern rather than becoming a list of literals on purpose: a customer
1255
+ # deploying their own application declares its own `kv://` keys, and those must work without being
1256
+ # hardcoded here. Values arrive afterwards through the write-only system-secrets API
1257
+ # (`PUT /api/v1/environments/{envKey}/system-secrets/{catalogKey}`), which is what
1258
+ # aifabrix-builder plan 204.0 builds the CLI for.
1259
+ #
1260
+ # A secret that genuinely has no issuer but must exist gets an explicit `randomBytes32` entry
1261
+ # above. Adding one is the deliberate act; generation is no longer the silent default.
1262
+ - keyPattern: '^[a-zA-Z0-9_-]+KeyVault$'
1263
+ scope: app
1264
+ sensitive: true
1265
+ generator:
1266
+ type: emptyString
1267
+ ensureOn: [resolveApp]
1268
+
1269
+ # Non-secret values in the same store — the suffix carries the sensitivity.
1270
+ #
1271
+ # `*KeyVault` above means "secret": hidden by the list API, write-only through
1272
+ # `PUT /api/v1/environments/{envKey}/system-secrets/{catalogKey}`. `*Param` means the same storage
1273
+ # with nothing to hide: the list API returns the value, because `summarize()` emits `value` only
1274
+ # when `sensitive === false`.
1275
+ #
1276
+ # Why a pattern and not more literals: the suffix used to say nothing about sensitivity, so every
1277
+ # non-secret that happened to end in `KeyVault` needed its own literal row purely to set
1278
+ # `sensitive: false` — `keycloak-client-idKeyVault`, `entra-client-idKeyVault`,
1279
+ # `azure-subscription-idKeyVault`, `azure-tenant-idKeyVault`, `azure-service-nameKeyVault`. Client
1280
+ # IDs, tenant IDs and service names are not credentials, and hardcoding each one does not scale:
1281
+ # a customer deploying their own application cannot get a visible parameter without an edit here.
1282
+ #
1283
+ # With the convention, visibility is a naming decision the application author makes and a reader
1284
+ # can see: name it `…Param` to be readable, `…KeyVault` to be secret. Changing your mind is a
1285
+ # rename, not a catalog change. Both create the entry EMPTY and are supplied afterwards.
1286
+ - keyPattern: '^[a-zA-Z0-9_-]+Param$'
1287
+ scope: app
1288
+ sensitive: false
1289
+ generator:
1290
+ type: emptyString
1291
+ ensureOn: [resolveApp]