@mnstry/atelier 0.2.0-alpha.1

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 (260) hide show
  1. package/CHANGELOG.md +383 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +5 -0
  4. package/README.md +262 -0
  5. package/SECURITY.md +93 -0
  6. package/TRADEMARKS.md +88 -0
  7. package/announcements/2026-08-06-announcements-channel.v1.json +12 -0
  8. package/announcements/keys/mnstry-announcements.public.v1.json +9 -0
  9. package/bin/atelier.mjs +2 -0
  10. package/bin/mnstry-atelier.mjs +3 -0
  11. package/contracts/analysis-adapter.v1.schema.json +248 -0
  12. package/contracts/atelier-action-intent.v1.schema.json +143 -0
  13. package/contracts/atelier-analysis-adapter.v1.schema.json +360 -0
  14. package/contracts/atelier-attestation.v1.schema.json +239 -0
  15. package/contracts/atelier-boundary-policy.v1.schema.json +339 -0
  16. package/contracts/atelier-claim.v1.schema.json +97 -0
  17. package/contracts/atelier-export.v1.schema.json +1015 -0
  18. package/contracts/atelier-extension-pack.v1.schema.json +290 -0
  19. package/contracts/atelier-kit-manifest.v1.schema.json +273 -0
  20. package/contracts/atelier-lock.v1.schema.json +364 -0
  21. package/contracts/atelier-migration.v1.schema.json +162 -0
  22. package/contracts/atelier-project-config.v1.schema.json +352 -0
  23. package/contracts/atelier-readiness-protocol.v1.schema.json +517 -0
  24. package/contracts/atelier-readiness-run.v1.schema.json +229 -0
  25. package/contracts/atelier-readiness.v1.schema.json +457 -0
  26. package/contracts/compat-baseline.json +10 -0
  27. package/contracts/git-promote-event.v1.schema.json +145 -0
  28. package/contracts/knowledge-source-sidecar.v1.schema.json +157 -0
  29. package/contracts/mnstry-atelier-semantic-profile.v1.json +254 -0
  30. package/docs/announcements.md +101 -0
  31. package/docs/atelier-runtime.md +34 -0
  32. package/docs/attestation.md +201 -0
  33. package/docs/continuity.md +55 -0
  34. package/docs/contract-stability.md +111 -0
  35. package/docs/distributions.md +220 -0
  36. package/docs/install.md +140 -0
  37. package/docs/knowledge-graph.md +140 -0
  38. package/docs/ontology.md +206 -0
  39. package/docs/release-engineering.md +151 -0
  40. package/docs/repo-boundary-guard.md +199 -0
  41. package/docs/tenant-readiness.md +64 -0
  42. package/docs/upgrade.md +124 -0
  43. package/fixtures/analysis/invalid/native-graph.v1.json +4 -0
  44. package/fixtures/analysis/invalid/promoted-claim.v1.json +17 -0
  45. package/fixtures/analysis/invalid/runtime-import.v1.json +20 -0
  46. package/fixtures/analysis/valid/output.claims.v1.json +17 -0
  47. package/fixtures/analysis-adapter/invalid/manifest-hidden-provider.v1.json +24 -0
  48. package/fixtures/analysis-adapter/invalid/output-frontmatter-mutation.v1.json +21 -0
  49. package/fixtures/analysis-adapter/invalid/output-native-graph.v1.json +14 -0
  50. package/fixtures/analysis-adapter/manifest.disabled.v1.json +22 -0
  51. package/fixtures/analysis-adapter/manifest.enabled-local.v1.json +23 -0
  52. package/fixtures/analysis-adapter/output.claims.v1.json +21 -0
  53. package/fixtures/atelier-action-intent/invalid/runtime-mutation-authority.v1.json +16 -0
  54. package/fixtures/atelier-action-intent/invalid/unknown-top-level-field.v1.json +17 -0
  55. package/fixtures/atelier-action-intent/invalid/writes-beyond-clipboard.v1.json +16 -0
  56. package/fixtures/atelier-action-intent/valid/copy-agent-prompt.v1.json +16 -0
  57. package/fixtures/atelier-action-intent/valid/copy-repo-path.v1.json +22 -0
  58. package/fixtures/atelier-analysis-adapter/invalid/hidden-model-provider.v1.json +24 -0
  59. package/fixtures/atelier-analysis-adapter/invalid/inert-with-execute-command.v1.json +24 -0
  60. package/fixtures/atelier-analysis-adapter/invalid/proposals-with-runtime-import.v1.json +30 -0
  61. package/fixtures/atelier-analysis-adapter/valid/manifest-inert.v1.json +24 -0
  62. package/fixtures/atelier-analysis-adapter/valid/proposals-claim-only.v1.json +27 -0
  63. package/fixtures/atelier-attestation/invalid/bad-digest.v1.json +23 -0
  64. package/fixtures/atelier-attestation/invalid/conformance-scope.v1.json +23 -0
  65. package/fixtures/atelier-attestation/invalid/missing-signature.v1.json +22 -0
  66. package/fixtures/atelier-attestation/invalid/unknown-root-field.v1.json +24 -0
  67. package/fixtures/atelier-attestation/keys/roundtrip-issuer.public.v1.json +9 -0
  68. package/fixtures/atelier-attestation/valid/rejected-with-reasons.v1.json +49 -0
  69. package/fixtures/atelier-attestation/valid/signed-admitted.v1.json +35 -0
  70. package/fixtures/atelier-attestation/valid/signed-roundtrip.v1.json +29 -0
  71. package/fixtures/atelier-attestation/valid/unsigned-advisory.v1.json +23 -0
  72. package/fixtures/atelier-claim/invalid/unknown-provider.v1.json +15 -0
  73. package/fixtures/atelier-claim/invalid/unknown-top-level-field.v1.json +16 -0
  74. package/fixtures/atelier-claim/invalid/wrong-schema-const.v1.json +15 -0
  75. package/fixtures/atelier-claim/valid/adapter-style-proposed-relation.v1.json +19 -0
  76. package/fixtures/atelier-claim/valid/codex-proposed-relation.v1.json +16 -0
  77. package/fixtures/atelier-claim/valid/manual-proposed-relation.v1.json +16 -0
  78. package/fixtures/atelier-claim/valid/readiness-proposed-relation.v1.json +18 -0
  79. package/fixtures/atelier-export/invalid/dirty-unforced-empty-taint.v1.json +19 -0
  80. package/fixtures/atelier-export/invalid/object-local-audience-visibility.v1.json +11 -0
  81. package/fixtures/atelier-export/invalid/open-object-class-owner.v1.json +23 -0
  82. package/fixtures/atelier-export/invalid/pricing-legacy-amount.v1.json +23 -0
  83. package/fixtures/atelier-export/invalid/public-disguised-internal-source-ref.v1.json +11 -0
  84. package/fixtures/atelier-export/invalid/public-nested-private-source-ref.v1.json +17 -0
  85. package/fixtures/atelier-export/invalid/public-resolved-private-source-ref.v1.json +13 -0
  86. package/fixtures/atelier-export/invalid/public-resolved-sensitive-source-ref.v1.json +13 -0
  87. package/fixtures/atelier-export/invalid/public-source-ref-missing-provenance.v1.json +20 -0
  88. package/fixtures/atelier-export/invalid/public-source-team-kg.json +12 -0
  89. package/fixtures/atelier-export/invalid/source-node-legacy-visibility.v1.json +21 -0
  90. package/fixtures/atelier-export/invalid/source-node-unknown-field.v1.json +18 -0
  91. package/fixtures/atelier-export/invalid/unresolved-public-source-ref.v1.json +11 -0
  92. package/fixtures/atelier-export/sample-studio-offer.v1.json +397 -0
  93. package/fixtures/atelier-extension-pack/invalid/bundled-slug-collision.v1.json +22 -0
  94. package/fixtures/atelier-extension-pack/invalid/escapes-root.v1.json +22 -0
  95. package/fixtures/atelier-extension-pack/invalid/posture-tampered.v1.json +22 -0
  96. package/fixtures/atelier-extension-pack/invalid/protocols/autonomous-authority.v1.json +118 -0
  97. package/fixtures/atelier-extension-pack/invalid/protocols/offer-map.v1.json +109 -0
  98. package/fixtures/atelier-extension-pack/invalid/redefines-runtime-term.v1.json +19 -0
  99. package/fixtures/atelier-extension-pack/invalid/wrong-namespace.v1.json +19 -0
  100. package/fixtures/atelier-extension-pack/valid/protocols/contract-gate.v1.json +109 -0
  101. package/fixtures/atelier-extension-pack/valid/sample-pack.v1.json +48 -0
  102. package/fixtures/atelier-kit-manifest/invalid/runtime-copy-without-canonical-role.v1.json +53 -0
  103. package/fixtures/atelier-kit-manifest/invalid/telemetry-send-path.v1.json +21 -0
  104. package/fixtures/atelier-kit-manifest/valid/mnstry-atelier.valid.v1.json +76 -0
  105. package/fixtures/atelier-lock/invalid/absolute-contract-path.v1.json +40 -0
  106. package/fixtures/atelier-lock/invalid/runtime-mutation-enabled.v1.json +41 -0
  107. package/fixtures/atelier-lock/invalid/unknown-top-level-field.v1.json +42 -0
  108. package/fixtures/atelier-lock/valid/extension-pack.v1.json +55 -0
  109. package/fixtures/atelier-lock/valid/minimal.v1.json +44 -0
  110. package/fixtures/atelier-migration/invalid/live-apply-mode.v1.json +21 -0
  111. package/fixtures/atelier-migration/invalid/missing-review-evidence.v1.json +20 -0
  112. package/fixtures/atelier-migration/invalid/runtime-mutation-enabled.v1.json +21 -0
  113. package/fixtures/atelier-migration/valid/minimal.v1.json +32 -0
  114. package/fixtures/atelier-project-config/invalid/absolute-repo-path.v1.json +11 -0
  115. package/fixtures/atelier-project-config/invalid/ext-not-object.v1.json +12 -0
  116. package/fixtures/atelier-project-config/invalid/external-kind-read-boundary.v1.json +12 -0
  117. package/fixtures/atelier-project-config/invalid/missing-repos-and-alignment.v1.json +7 -0
  118. package/fixtures/atelier-project-config/invalid/unknown-top-level-field.v1.json +14 -0
  119. package/fixtures/atelier-project-config/valid/alignment-scaffold.v1.json +15 -0
  120. package/fixtures/atelier-project-config/valid/ext-extension-packs.v1.json +36 -0
  121. package/fixtures/atelier-project-config/valid/repo-workspace.v1.json +23 -0
  122. package/fixtures/atelier-project-config/validator-only/ext-member-not-object.v1.json +14 -0
  123. package/fixtures/atelier-readiness/invalid/dry-run-only-false.v1.json +87 -0
  124. package/fixtures/atelier-readiness/invalid/support-bundle-send-path.v1.json +87 -0
  125. package/fixtures/atelier-readiness/invalid/unknown-top-level-field.v1.json +88 -0
  126. package/fixtures/atelier-readiness/valid/ready-workspace.v1.json +84 -0
  127. package/fixtures/boundary-policy/invalid/content-rule-exception-blanket-path.v1.json +67 -0
  128. package/fixtures/boundary-policy/invalid/invalid-actor-private-repo-mapping.v1.json +20 -0
  129. package/fixtures/boundary-policy/invalid/legacy-warning-missing-warning.v1.json +20 -0
  130. package/fixtures/boundary-policy/invalid/missing-repo-coverage.v1.json +19 -0
  131. package/fixtures/boundary-policy/invalid/unknown-key.v1.json +12 -0
  132. package/fixtures/boundary-policy/valid/content-rule-exception.v1.json +67 -0
  133. package/fixtures/boundary-policy/valid/legacy-warning.v1.json +33 -0
  134. package/fixtures/boundary-policy/valid/strict-private-shared.v1.json +40 -0
  135. package/fixtures/git-promote/invalid/provision-named-revocable.v1.json +29 -0
  136. package/fixtures/git-promote/private-to-team.v1.json +29 -0
  137. package/fixtures/knowledge-source-sidecar/invalid/legacy-visibility-field.v1.json +17 -0
  138. package/fixtures/knowledge-source-sidecar/invalid/missing-audience.v1.json +15 -0
  139. package/fixtures/knowledge-source-sidecar/invalid/unknown-relation-kind.v1.json +18 -0
  140. package/fixtures/knowledge-source-sidecar/valid/html-source.v1.json +18 -0
  141. package/fixtures/knowledge-source-sidecar/valid/pdf-evidence.v1.json +18 -0
  142. package/fixtures/projects/neutral-project/atelier.project.json +15 -0
  143. package/fixtures/projects/neutral-project/ops/config/repo-access.v1.json +12 -0
  144. package/fixtures/projects/neutral-project/project-knowledge.graph.json +69 -0
  145. package/fixtures/projects/sample-workspace/atelier.project.json +23 -0
  146. package/fixtures/projects/sample-workspace/boundary-policy.v1.json +59 -0
  147. package/fixtures/projects/sample-workspace/content/README.md +16 -0
  148. package/fixtures/projects/sample-workspace/content/source.html +5 -0
  149. package/fixtures/projects/sample-workspace/content/source.html.kg.json +15 -0
  150. package/fixtures/projects/sample-workspace/repo-access.v1.json +9 -0
  151. package/fixtures/projects/source-formats-workspace/atelier.project.json +23 -0
  152. package/fixtures/projects/source-formats-workspace/content/README.md +17 -0
  153. package/fixtures/projects/source-formats-workspace/content/data.json +7 -0
  154. package/fixtures/projects/source-formats-workspace/content/data.json.kg.json +15 -0
  155. package/fixtures/projects/source-formats-workspace/content/logo.png.kg.json +15 -0
  156. package/fixtures/projects/source-formats-workspace/content/metrics.csv +3 -0
  157. package/fixtures/projects/source-formats-workspace/content/metrics.csv.kg.json +15 -0
  158. package/fixtures/projects/source-formats-workspace/content/pipeline.yaml +4 -0
  159. package/fixtures/projects/source-formats-workspace/content/pipeline.yaml.kg.json +15 -0
  160. package/fixtures/projects/source-formats-workspace/repo-access.v1.json +9 -0
  161. package/fixtures/readiness-protocols/extension-pack/invalid/protocol-missing-path.v1.json +20 -0
  162. package/fixtures/readiness-protocols/extension-pack/valid/with-protocol.v1.json +24 -0
  163. package/fixtures/readiness-protocols/protocol/invalid/missing-check-target.v1.json +103 -0
  164. package/fixtures/readiness-protocols/protocol/invalid/runtime-mutation-enabled.v1.json +107 -0
  165. package/fixtures/readiness-protocols/protocol/valid/minimal.v1.json +100 -0
  166. package/fixtures/readiness-protocols/run/invalid/missing-evidence.v1.json +37 -0
  167. package/fixtures/readiness-protocols/run/invalid/pass-with-blocker.v1.json +22 -0
  168. package/fixtures/readiness-protocols/run/invalid/runtime-mutation-enabled.v1.json +22 -0
  169. package/fixtures/readiness-protocols/run/valid/pass.v1.json +44 -0
  170. package/fixtures/support/invalid/leaky-support.v1.json +13 -0
  171. package/fixtures/support/valid/minimal-safe.v1.json +20 -0
  172. package/package.json +98 -0
  173. package/skills/claude/atelier-open/SKILL.md +68 -0
  174. package/skills/claude/mnstry-readiness/SKILL.md +41 -0
  175. package/skills/codex/atelier-open/SKILL.md +68 -0
  176. package/skills/codex/mnstry-readiness/SKILL.md +41 -0
  177. package/src/analysis/adapter.mjs +20 -0
  178. package/src/analysis/analysis-claim-contract.mjs +335 -0
  179. package/src/attestation/jcs.mjs +64 -0
  180. package/src/attestation/sign.mjs +305 -0
  181. package/src/boundary/content-rules.mjs +262 -0
  182. package/src/boundary/policy.mjs +806 -0
  183. package/src/check-atelier-export-contract.mjs +116 -0
  184. package/src/cli/run.mjs +283 -0
  185. package/src/collaboration/proposals.mjs +262 -0
  186. package/src/commands/analysis.mjs +8 -0
  187. package/src/commands/announcements.mjs +308 -0
  188. package/src/commands/attestation.mjs +198 -0
  189. package/src/commands/boundary.mjs +25 -0
  190. package/src/commands/config.mjs +4 -0
  191. package/src/commands/context.mjs +3 -0
  192. package/src/commands/distribution.mjs +224 -0
  193. package/src/commands/egress.mjs +11 -0
  194. package/src/commands/extension-pack.mjs +209 -0
  195. package/src/commands/feedback.mjs +3 -0
  196. package/src/commands/graph.mjs +3 -0
  197. package/src/commands/init.mjs +130 -0
  198. package/src/commands/lock.mjs +3 -0
  199. package/src/commands/project.mjs +3 -0
  200. package/src/commands/promote.mjs +4 -0
  201. package/src/commands/readiness.mjs +3 -0
  202. package/src/commands/server.mjs +6 -0
  203. package/src/commands/setup.mjs +250 -0
  204. package/src/commands/support.mjs +3 -0
  205. package/src/commands/upgrade.mjs +3 -0
  206. package/src/contracts/corpus.mjs +171 -0
  207. package/src/egress/check.mjs +45 -0
  208. package/src/egress/forbidden-egress.mjs +323 -0
  209. package/src/export/atelier-export-contract.mjs +722 -0
  210. package/src/extension-packs/loader.mjs +491 -0
  211. package/src/graph/graph.mjs +370 -0
  212. package/src/graph/knowledge-graph.mjs +887 -0
  213. package/src/harness/context-client.mjs +144 -0
  214. package/src/harness/context.mjs +27 -0
  215. package/src/index.mjs +88 -0
  216. package/src/project/config.mjs +557 -0
  217. package/src/project/file-class.mjs +113 -0
  218. package/src/project/git-ignore.mjs +31 -0
  219. package/src/project/path-match.mjs +30 -0
  220. package/src/project/repo-identity.mjs +176 -0
  221. package/src/projection/alignment-projection.mjs +169 -0
  222. package/src/projection/policy.mjs +90 -0
  223. package/src/projection/project.mjs +152 -0
  224. package/src/readiness/readiness.mjs +312 -0
  225. package/src/readiness-protocols/bundled-pack.mjs +766 -0
  226. package/src/readiness-protocols/runtime.mjs +419 -0
  227. package/src/server/local-sidecar.mjs +640 -0
  228. package/src/server/security.mjs +229 -0
  229. package/src/server/server.mjs +42 -0
  230. package/src/support/feedback-report.mjs +414 -0
  231. package/src/support/support-bundle.mjs +226 -0
  232. package/src/ui/html-primitives.mjs +104 -0
  233. package/src/upgrade/upgrade.mjs +701 -0
  234. package/src/validate-atelier-export-dry-run.mjs +75 -0
  235. package/templates/atelier.project.example.json +23 -0
  236. package/templates/distribution-workspace/.claude/launch.json +12 -0
  237. package/templates/distribution-workspace/README.md +86 -0
  238. package/templates/distribution-workspace/atelier.project.json +36 -0
  239. package/templates/distribution-workspace/boundary-policy.v1.json +61 -0
  240. package/templates/distribution-workspace/gitignore +29 -0
  241. package/templates/distribution-workspace/governance/repo-boundary-ledger.md +5 -0
  242. package/templates/distribution-workspace/project/README.md +19 -0
  243. package/templates/distribution-workspace/repo-access.v1.json +9 -0
  244. package/templates/launch.json +12 -0
  245. package/templates/private-domain-workspace/.claude/launch.json +12 -0
  246. package/templates/private-domain-workspace/README.md +50 -0
  247. package/templates/private-domain-workspace/atelier.project.json +28 -0
  248. package/templates/private-domain-workspace/boundary-policy.v1.json +54 -0
  249. package/templates/private-domain-workspace/domain/README.md +19 -0
  250. package/templates/private-domain-workspace/gitignore +29 -0
  251. package/templates/private-domain-workspace/governance/repo-boundary-ledger.md +4 -0
  252. package/templates/private-domain-workspace/repo-access.v1.json +9 -0
  253. package/templates/shared-project-workspace/.claude/launch.json +12 -0
  254. package/templates/shared-project-workspace/README.md +51 -0
  255. package/templates/shared-project-workspace/atelier.project.json +28 -0
  256. package/templates/shared-project-workspace/boundary-policy.v1.json +61 -0
  257. package/templates/shared-project-workspace/gitignore +29 -0
  258. package/templates/shared-project-workspace/governance/repo-boundary-ledger.md +5 -0
  259. package/templates/shared-project-workspace/project/README.md +19 -0
  260. package/templates/shared-project-workspace/repo-access.v1.json +9 -0
@@ -0,0 +1,419 @@
1
+ import crypto from 'node:crypto'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { createProposalStore } from '../collaboration/proposals.mjs'
5
+ import { readJson, writeJson } from '../project/config.mjs'
6
+ import { bundledReadinessPack, bundledReadinessProtocols, protocolById } from './bundled-pack.mjs'
7
+
8
+ export const READINESS_RUN_SCHEMA = 'atelier-readiness-run@v1'
9
+ export const TENANT_PACKET_SCHEMA = 'mnstry.tenant-readiness-packet@v1'
10
+ export const CLAIM_SCHEMA = 'atelier-claim@v1'
11
+
12
+ export const DIMENSIONS = [
13
+ ['identity', 'identity readiness', 'mnstry.readiness:identity-map'],
14
+ ['transformation', 'transformation clarity', 'mnstry.readiness:transformation-map'],
15
+ ['offer', 'offer readiness', 'mnstry.readiness:offer-map'],
16
+ ['space', 'space readiness', 'mnstry.readiness:space-design'],
17
+ ['trackable', 'trackable readiness', 'mnstry.readiness:trackables'],
18
+ ['consent', 'consent readiness', 'mnstry.readiness:consent-boundaries'],
19
+ ['content', 'content/material readiness', 'mnstry.readiness:content-inventory'],
20
+ ['discovery', 'discovery readiness', 'mnstry.readiness:discovery-engine'],
21
+ ['journey', 'journey readiness', 'mnstry.readiness:journey-map'],
22
+ ['operations', 'operations readiness', 'mnstry.readiness:operations-readiness'],
23
+ ['runtime', 'runtime readiness', 'mnstry.readiness:runtime-readiness'],
24
+ ['export', 'export readiness', 'mnstry.readiness:tenant-packet'],
25
+ ]
26
+
27
+ export function readinessLocalRoot(project) {
28
+ return path.join(project.configDir || project.workspaceRoot, '.atelier-local', 'readiness')
29
+ }
30
+
31
+ export function readinessRunsDir(project) {
32
+ return path.join(readinessLocalRoot(project), 'runs')
33
+ }
34
+
35
+ export function readinessPacketsDir(project) {
36
+ return path.join(readinessLocalRoot(project), 'packets')
37
+ }
38
+
39
+ function nowIso() {
40
+ return new Date().toISOString()
41
+ }
42
+
43
+ function stableHash(value) {
44
+ return crypto.createHash('sha256').update(String(value)).digest('hex')
45
+ }
46
+
47
+ function slugify(value, fallback = 'item') {
48
+ const clean = String(value || '')
49
+ .toLowerCase()
50
+ .replace(/[^a-z0-9:-]+/g, '-')
51
+ .replace(/^-+|-+$/g, '')
52
+ return clean || fallback
53
+ }
54
+
55
+ function answerPresent(value) {
56
+ if (value == null) return false
57
+ if (Array.isArray(value)) return value.length > 0 && value.some(answerPresent)
58
+ if (typeof value === 'object') return Object.keys(value).length > 0
59
+ return String(value).trim().length > 0
60
+ }
61
+
62
+ function pathVariants(fieldId) {
63
+ const plain = String(fieldId || '').replace(/\[\]/g, '')
64
+ const root = plain.split('.')[0]
65
+ return [...new Set([fieldId, plain, root].filter(Boolean))]
66
+ }
67
+
68
+ function nestedAnswer(value, parts) {
69
+ if (!parts.length) return value
70
+ if (Array.isArray(value)) return value.map((item) => nestedAnswer(item, parts)).filter(answerPresent)
71
+ if (!value || typeof value !== 'object') return undefined
72
+ const [head, ...rest] = parts
73
+ return nestedAnswer(value[head], rest)
74
+ }
75
+
76
+ function answerForField(answers, fieldId) {
77
+ for (const key of pathVariants(fieldId)) {
78
+ if (Object.hasOwn(answers, key)) return answers[key]
79
+ }
80
+ const parts = String(fieldId || '').replace(/\[\]/g, '').split('.').filter(Boolean)
81
+ return nestedAnswer(answers, parts)
82
+ }
83
+
84
+ export function normalizeAnswers(protocol, answers = {}) {
85
+ const out = {}
86
+ for (const question of protocol.questions) {
87
+ if (Object.hasOwn(answers, question.id)) out[question.id] = answers[question.id]
88
+ }
89
+ for (const [key, value] of Object.entries(answers)) {
90
+ if (!Object.hasOwn(out, key)) out[key] = value
91
+ }
92
+ return out
93
+ }
94
+
95
+ export function evaluateProtocolAnswers(protocol, answers = {}) {
96
+ const blockers = []
97
+ const warnings = []
98
+ for (const field of protocol.inputFields) {
99
+ if (field.required && !answerPresent(answerForField(answers, field.id))) blockers.push(`missing-answer:${field.id}`)
100
+ }
101
+ const required = protocol.inputFields.filter((field) => field.required)
102
+ const answeredRequired = required.filter((field) => answerPresent(answerForField(answers, field.id)))
103
+ const score = required.length ? Math.round((answeredRequired.length / required.length) * 100) : 100
104
+ const status = blockers.length ? (Object.keys(answers).length ? 'draft' : 'missing') : 'review-needed'
105
+ return { blockers, warnings, score, status }
106
+ }
107
+
108
+ export function claimsForProtocolRun({ protocol, answers, projectName, runId, createdAt }) {
109
+ const protocolSlug = slugify(protocol.slug || protocol.id.split(':').at(-1), 'protocol')
110
+ const subject = `readiness:${slugify(projectName, 'project')}:${protocolSlug}`
111
+ return protocol.claimMappings.map((mapping) => {
112
+ const mappingSlug = slugify(mapping.id.split('.').at(-1), 'claim')
113
+ return {
114
+ schema: CLAIM_SCHEMA,
115
+ claimId: `claim:${runId}:${mappingSlug}`,
116
+ subject,
117
+ predicate: mapping.claimPredicate,
118
+ object: `readiness:${protocolSlug}:${mappingSlug}`,
119
+ provider: 'atelier-readiness',
120
+ status: 'proposed',
121
+ promoted: false,
122
+ confidence: Object.values(answers).some(answerPresent) ? 0.7 : 0.2,
123
+ evidence: [`readiness-run:${runId}`],
124
+ notes: [`Proposed ${mapping.id} from ${protocol.title}.`],
125
+ createdAt,
126
+ }
127
+ })
128
+ }
129
+
130
+ export function buildReadinessRun({ project, protocol, answers = {}, createdAt = nowIso() }) {
131
+ const normalizedAnswers = normalizeAnswers(protocol, answers)
132
+ const evaluation = evaluateProtocolAnswers(protocol, normalizedAnswers)
133
+ const seed = JSON.stringify({ project: project.config.name || path.basename(project.workspaceRoot), protocol: protocol.id, answers: normalizedAnswers, createdAt })
134
+ const runId = `readiness-run-${stableHash(seed).slice(0, 24)}`
135
+ const projectName = project.config.name || path.basename(project.workspaceRoot)
136
+ return {
137
+ schema: READINESS_RUN_SCHEMA,
138
+ runId,
139
+ protocolId: protocol.id,
140
+ createdAt,
141
+ project: projectName,
142
+ answers: normalizedAnswers,
143
+ status: evaluation.status,
144
+ score: evaluation.score,
145
+ blockers: evaluation.blockers,
146
+ warnings: evaluation.warnings,
147
+ claims: claimsForProtocolRun({ protocol, answers: normalizedAnswers, projectName, runId, createdAt }),
148
+ safety: {
149
+ runtimeMutation: false,
150
+ canonicalWrites: false,
151
+ claimOnly: true,
152
+ storage: 'ignored-local',
153
+ },
154
+ }
155
+ }
156
+
157
+ export function writeReadinessRun(project, run) {
158
+ const dir = readinessRunsDir(project)
159
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 })
160
+ const file = path.join(dir, `${run.runId}.json`)
161
+ writeJson(file, run)
162
+ try {
163
+ fs.chmodSync(file, 0o600)
164
+ } catch {
165
+ // Best effort on filesystems that do not support chmod.
166
+ }
167
+ return file
168
+ }
169
+
170
+ export function createReadinessProposal(project, run) {
171
+ const store = createProposalStore({ workspaceRoot: project.configDir || project.workspaceRoot })
172
+ return store.createProposal({
173
+ path: `.atelier-local/readiness/runs/${run.runId}.json`,
174
+ action: 'copy.agentPrompt',
175
+ intent: `Review proposed claims from ${run.protocolId}.`,
176
+ reason: 'MNSTRY Readiness protocol runs are claim-first and do not mutate canonical graph/front matter.',
177
+ proposal: {
178
+ kind: 'mnstry-readiness-run',
179
+ protocolId: run.protocolId,
180
+ runId: run.runId,
181
+ claims: run.claims,
182
+ safety: run.safety,
183
+ },
184
+ diff: JSON.stringify(run.claims, null, 2),
185
+ })
186
+ }
187
+
188
+ export function listProtocolRuns(project) {
189
+ const dir = readinessRunsDir(project)
190
+ if (!fs.existsSync(dir)) return []
191
+ return fs.readdirSync(dir)
192
+ .filter((name) => name.endsWith('.json'))
193
+ .sort()
194
+ .map((name) => {
195
+ try {
196
+ return readJson(path.join(dir, name))
197
+ } catch {
198
+ return null
199
+ }
200
+ })
201
+ .filter(Boolean)
202
+ }
203
+
204
+ // Shared per-dimension facts derived from a stored run. Used by both the
205
+ // bundled journey dimensions and pack dimensions so one builder defines what
206
+ // "run data" means everywhere.
207
+ function dimensionRunFacts(run, protocolId) {
208
+ return {
209
+ status: run ? run.status : 'missing',
210
+ score: run ? run.score ?? 0 : 0,
211
+ blockers: run?.blockers ?? [`not-run:${protocolId}`],
212
+ warnings: run?.warnings ?? [],
213
+ sourceRefs: run ? [`readiness-run:${run.runId}`] : [],
214
+ proposedClaims: run?.claims?.map((claim) => claim.claimId) ?? [],
215
+ }
216
+ }
217
+
218
+ // Pack protocol records from an explicit registry (createProtocolRegistry).
219
+ // Bundled behavior is the default: no registry means no pack records.
220
+ function packProtocolRecords(registry) {
221
+ return (registry?.packs ?? []).flatMap((pack) => pack.protocols ?? [])
222
+ }
223
+
224
+ export function summarizeReadinessJourney(project, { runs = listProtocolRuns(project), registry = null } = {}) {
225
+ const runsByProtocol = new Map()
226
+ for (const run of runs) runsByProtocol.set(run.protocolId, run)
227
+ const dimensions = DIMENSIONS.map(([key, label, protocolId]) => {
228
+ const protocol = protocolById(protocolId)
229
+ const facts = dimensionRunFacts(runsByProtocol.get(protocolId), protocolId)
230
+ return {
231
+ key,
232
+ label,
233
+ protocolId,
234
+ title: protocol?.title ?? protocolId,
235
+ ...facts,
236
+ nextSuggestedProtocol: facts.status === 'ready' || facts.status === 'review-needed' ? null : protocolId,
237
+ agentPrompt: protocol?.ui?.agentPrompt ?? `Run atelier readiness run ${protocolId} --project ./atelier.project.json.`,
238
+ }
239
+ })
240
+ // Pack protocols never join the 12 canonical dimensions: score, ready, and
241
+ // nextProtocol stay bundled-only; packs report through the sibling array.
242
+ const packDimensions = packProtocolRecords(registry).map((record) => ({
243
+ key: record.id,
244
+ packId: record.packId,
245
+ gate: record.gate,
246
+ protocolId: record.id,
247
+ title: record.title ?? record.protocol?.title ?? record.id,
248
+ ...dimensionRunFacts(runsByProtocol.get(record.id), record.id),
249
+ }))
250
+ const readyCount = dimensions.filter((item) => item.status === 'ready' || item.status === 'review-needed').length
251
+ const score = Math.round((readyCount / dimensions.length) * 100)
252
+ return {
253
+ schema: 'mnstry.tenant-readiness-journey@v1',
254
+ generatedAt: 'deterministic',
255
+ project: project.config.name || path.basename(project.workspaceRoot),
256
+ score,
257
+ ready: readyCount === dimensions.length,
258
+ dimensions,
259
+ packDimensions,
260
+ nextProtocol: dimensions.find((item) => item.nextSuggestedProtocol)?.nextSuggestedProtocol ?? null,
261
+ pack: {
262
+ id: bundledReadinessPack.id,
263
+ version: bundledReadinessPack.version,
264
+ protocolCount: bundledReadinessProtocols.length,
265
+ },
266
+ packs: (registry?.packs ?? []).map((pack) => ({
267
+ id: pack.id,
268
+ version: pack.version,
269
+ protocolCount: pack.protocols?.length ?? 0,
270
+ })),
271
+ }
272
+ }
273
+
274
+ function answersFor(run, key) {
275
+ return run?.answers?.[key] ?? null
276
+ }
277
+
278
+ // Contract note: the tenant packet (mnstry.tenant-readiness-packet@v1) is a
279
+ // runtime-defined document with no JSON Schema under contracts/, so the
280
+ // contract:compat gate cannot see shape changes here. Its shape is pinned by
281
+ // the runtime and CLI tests instead; if it ever gets a schema, register it so
282
+ // compat checking takes over.
283
+ export function buildTenantPacket(project, { runs = listProtocolRuns(project), registry = null } = {}) {
284
+ const byId = new Map(runs.map((run) => [run.protocolId, run]))
285
+ const journey = summarizeReadinessJourney(project, { runs, registry })
286
+ // Generic pack contribution: built entirely from run fields, no per-field
287
+ // plucking (the literal byId.get(...) reads below stay bundled-only). Only
288
+ // required-gate pack protocols can block export; advisory ones just report.
289
+ const packRecords = packProtocolRecords(registry)
290
+ const packExportBlockers = packRecords
291
+ .filter((record) => {
292
+ if (record.gate !== 'required') return false
293
+ const status = byId.get(record.id)?.status ?? 'missing'
294
+ return status === 'missing' || status === 'draft'
295
+ })
296
+ .map((record) => `readiness-incomplete:${record.id}`)
297
+ const packContribution = (registry?.packs ?? []).map((pack) => ({
298
+ id: pack.id,
299
+ version: pack.version,
300
+ protocols: (pack.protocols ?? []).map((record) => {
301
+ const facts = dimensionRunFacts(byId.get(record.id), record.id)
302
+ return {
303
+ protocolId: record.id,
304
+ gate: record.gate,
305
+ status: facts.status,
306
+ score: facts.score,
307
+ blockers: facts.blockers,
308
+ warnings: facts.warnings,
309
+ proposedClaims: facts.proposedClaims,
310
+ }
311
+ }),
312
+ }))
313
+ const packet = {
314
+ schema: TENANT_PACKET_SCHEMA,
315
+ generatedAt: 'deterministic',
316
+ project: project.config.name || path.basename(project.workspaceRoot),
317
+ readinessScore: journey.score,
318
+ runtimeMutation: false,
319
+ runtimeImport: false,
320
+ tenantCandidate: answersFor(byId.get('mnstry.readiness:identity-map'), 'tenantCandidate') ?? answersFor(byId.get('mnstry.readiness:identity-map'), 'actors')?.[0]?.displayName ?? null,
321
+ actorsAndRoles: {
322
+ primaryPractitioner: answersFor(byId.get('mnstry.readiness:identity-map'), 'primaryPractitioner') ?? answersFor(byId.get('mnstry.readiness:identity-map'), 'actors')?.[0] ?? null,
323
+ collaborators: answersFor(byId.get('mnstry.readiness:identity-map'), 'collaborators') ?? answersFor(byId.get('mnstry.readiness:identity-map'), 'actors')?.slice?.(1) ?? [],
324
+ },
325
+ boundaries: {
326
+ privateDomainRepo: answersFor(byId.get('mnstry.readiness:identity-map'), 'privateDomainRepo'),
327
+ privateMaterial: answersFor(byId.get('mnstry.readiness:consent-boundaries'), 'privateMaterial') ?? answersFor(byId.get('mnstry.readiness:consent-boundaries'), 'boundaries') ?? [],
328
+ sharedMaterial: answersFor(byId.get('mnstry.readiness:consent-boundaries'), 'sharedMaterial') ?? [],
329
+ runtimeConsentNeeds: answersFor(byId.get('mnstry.readiness:consent-boundaries'), 'runtimeConsentNeeds') ?? answersFor(byId.get('mnstry.readiness:consent-boundaries'), 'boundaries') ?? [],
330
+ },
331
+ offers: answersFor(byId.get('mnstry.readiness:offer-map'), 'offer') ? [{
332
+ name: answersFor(byId.get('mnstry.readiness:offer-map'), 'offer')?.name,
333
+ promise: answersFor(byId.get('mnstry.readiness:offer-map'), 'offer')?.promise,
334
+ idealParticipant: answersFor(byId.get('mnstry.readiness:offer-map'), 'offer')?.audience,
335
+ }] : [],
336
+ spaces: {
337
+ private: answersFor(byId.get('mnstry.readiness:space-design'), 'privateSpaces') ?? [],
338
+ shared: answersFor(byId.get('mnstry.readiness:space-design'), 'sharedSpaces') ?? [],
339
+ public: answersFor(byId.get('mnstry.readiness:space-design'), 'publicSpaces') ?? [],
340
+ runtime: answersFor(byId.get('mnstry.readiness:space-design'), 'space') ? [answersFor(byId.get('mnstry.readiness:space-design'), 'space')] : [],
341
+ },
342
+ trackables: answersFor(byId.get('mnstry.readiness:trackables'), 'trackables') ?? [],
343
+ materials: answersFor(byId.get('mnstry.readiness:content-inventory'), 'materials') ?? [],
344
+ discoveryEngine: {
345
+ audienceSignals: answersFor(byId.get('mnstry.readiness:discovery-engine'), 'audienceSignals') ?? answersFor(byId.get('mnstry.readiness:discovery-engine'), 'signals') ?? [],
346
+ channels: answersFor(byId.get('mnstry.readiness:discovery-engine'), 'channels') ?? [],
347
+ conversionPath: answersFor(byId.get('mnstry.readiness:discovery-engine'), 'conversionPath') ?? answersFor(byId.get('mnstry.readiness:discovery-engine'), 'nextSteps'),
348
+ },
349
+ journey: {
350
+ entryPoint: answersFor(byId.get('mnstry.readiness:journey-map'), 'entryPoint') ?? answersFor(byId.get('mnstry.readiness:journey-map'), 'stages')?.[0] ?? null,
351
+ commitmentPath: answersFor(byId.get('mnstry.readiness:journey-map'), 'commitmentPath') ?? answersFor(byId.get('mnstry.readiness:journey-map'), 'stages') ?? [],
352
+ continuityLoop: answersFor(byId.get('mnstry.readiness:journey-map'), 'continuityLoop') ?? answersFor(byId.get('mnstry.readiness:journey-map'), 'handoffs') ?? [],
353
+ },
354
+ operationsRequirements: {
355
+ roles: answersFor(byId.get('mnstry.readiness:operations-readiness'), 'roles') ?? answersFor(byId.get('mnstry.readiness:operations-readiness'), 'responsibilities') ?? [],
356
+ runbooks: answersFor(byId.get('mnstry.readiness:operations-readiness'), 'runbooks') ?? [],
357
+ policies: answersFor(byId.get('mnstry.readiness:operations-readiness'), 'policies') ?? answersFor(byId.get('mnstry.readiness:operations-readiness'), 'escalations') ?? [],
358
+ },
359
+ runtimeObjectCandidates: answersFor(byId.get('mnstry.readiness:runtime-readiness'), 'runtimeObjectCandidates') ?? answersFor(byId.get('mnstry.readiness:runtime-readiness'), 'runtimeOwners') ?? [],
360
+ exportBlockers: [
361
+ ...journey.dimensions.filter((item) => item.status === 'missing' || item.status === 'draft').map((item) => `readiness-incomplete:${item.protocolId}`),
362
+ ...(answersFor(byId.get('mnstry.readiness:tenant-packet'), 'exportBlockers') ?? []),
363
+ ...packExportBlockers,
364
+ ],
365
+ openQuestions: answersFor(byId.get('mnstry.readiness:tenant-packet'), 'openQuestions') ?? [],
366
+ proposedClaims: runs.flatMap((run) => run.claims ?? []).map((claim) => claim.claimId),
367
+ ext: {
368
+ 'mnstry.atelier': {
369
+ extensionPacks: packContribution,
370
+ },
371
+ },
372
+ }
373
+ return packet
374
+ }
375
+
376
+ export function writeTenantPacket(project, packet) {
377
+ const dir = readinessPacketsDir(project)
378
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 })
379
+ const file = path.join(dir, 'tenant-readiness-packet.json')
380
+ writeJson(file, packet)
381
+ try {
382
+ fs.chmodSync(file, 0o600)
383
+ } catch {
384
+ // Best effort on filesystems that do not support chmod.
385
+ }
386
+ return file
387
+ }
388
+
389
+ export function buildReadinessExportDryRun(project, { registry = null } = {}) {
390
+ const packet = buildTenantPacket(project, { registry })
391
+ return {
392
+ schema: 'mnstry.readiness-export-dry-run@v1',
393
+ generatedAt: 'deterministic',
394
+ project: packet.project,
395
+ accepted: true,
396
+ importable: false,
397
+ dryRunOnly: true,
398
+ runtimeMutation: false,
399
+ runtimeImport: false,
400
+ worstOperationStatus: packet.exportBlockers.length ? 'blocked' : 'warning',
401
+ tenantPacket: packet,
402
+ blockers: [
403
+ 'runtime-import-not-implemented',
404
+ ...packet.exportBlockers,
405
+ ],
406
+ }
407
+ }
408
+
409
+ export function runProtocol(project, protocolId, { answers = {}, write = true, createProposal = true, registry = null } = {}) {
410
+ // An explicit registry (createProtocolRegistry) resolves bundled protocols
411
+ // first, then pack protocols by full namespaced id only; the default stays
412
+ // bundled-only so every existing call site is unchanged.
413
+ const protocol = registry ? registry.protocolById(protocolId) : protocolById(protocolId)
414
+ if (!protocol) throw new Error(`unknown readiness protocol: ${protocolId}`)
415
+ const run = buildReadinessRun({ project, protocol, answers })
416
+ const file = write ? writeReadinessRun(project, run) : null
417
+ const proposal = createProposal ? createReadinessProposal(project, run) : null
418
+ return { run, file, proposal }
419
+ }