@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,201 @@
1
+ # Attestation
2
+
3
+ An attestation is a small governed document (`atelier-attestation@v1`,
4
+ `contracts/atelier-attestation.v1.schema.json`) in which an issuer records an
5
+ admission decision about one payload document — an export, a readiness run, or
6
+ a claim. It binds that decision to the exact payload bytes through a canonical
7
+ hash, names the issuer, and either carries a signature or explicitly declares
8
+ itself unsigned.
9
+
10
+ An attestation is a record of a decision, not the decision mechanism. It never
11
+ grants runtime authority, never mutates anything, and never substitutes for
12
+ running the published validators yourself.
13
+
14
+ ## Admission versus conformance
15
+
16
+ The two words are deliberately kept apart, and the schema enforces the split
17
+ structurally:
18
+
19
+ - Conformance is public and offline. Anyone can run the published validators
20
+ in this package against a document and reach the same answer. No attestation
21
+ is needed to establish conformance, and no attestation can establish it.
22
+ - Admission is a decision by a specific issuer to accept a specific payload
23
+ into a specific destination. An attestation only records an admission
24
+ decision. MNSTRY's private validator only ever decides MNSTRY admission; it
25
+ does not define, extend, or gate conformance.
26
+
27
+ `verdict.scope` is the constant `"admission"`. A document claiming a
28
+ conformance verdict cannot validate against the schema, so the vocabulary
29
+ cannot drift into "certified conformant" territory by accident.
30
+
31
+ The admission decision itself is one of `admitted`, `rejected`, or
32
+ `needs-review`, with optional machine-readable `reasons` entries
33
+ (`code` + `message`) explaining a rejection or review request.
34
+
35
+ ## Canonical payload hash
36
+
37
+ `subject.payloadHash` binds the attestation to exact payload content. The
38
+ procedure is normative:
39
+
40
+ 1. Canonicalize the attested payload document with RFC 8785 (JSON
41
+ Canonicalization Scheme, JCS).
42
+ 2. Hash the canonical bytes with SHA-256.
43
+ 3. Encode the digest as lowercase hexadecimal (64 characters).
44
+
45
+ The fields are pinned accordingly: `algorithm` is the constant `"sha-256"`,
46
+ `canonicalization` is the constant `"RFC8785-JCS"`, and `digest` must match
47
+ `^[0-9a-f]{64}$`. A relying party that re-canonicalizes the payload and gets a
48
+ different digest must treat the attestation as not applying to that payload.
49
+
50
+ This package ships the reference implementation of this procedure —
51
+ `src/attestation/jcs.mjs` (RFC 8785 canonicalization), `src/attestation/sign.mjs`
52
+ (hashing, signing, verification), and the `atelier attestation hash|sign|verify`
53
+ command line. The contract fixes the procedure and the encoding; the
54
+ implementation is replaceable by anything that produces the same bytes.
55
+
56
+ `subject.schema` names the contract of the attested payload (for example
57
+ `atelier-export@v1`), `subject.ref` carries its stable identifier (exportId,
58
+ runId, or claimId), and optional `subject.references` entries cross-reference
59
+ related claims, runs, exports, or protocols.
60
+
61
+ ## Signature semantics
62
+
63
+ `signature` is required in shape and nullable in value. Every attestation must
64
+ say one of two things — silently absent is not expressible:
65
+
66
+ - `"signature": null` — the attestation is unsigned and therefore
67
+ non-authoritative. It is advisory only: useful as a local record or a
68
+ tooling artifact, but it proves nothing about who issued it.
69
+ - A signature object — `algorithm` (`ed25519` or `es256`), `keyId`, and a
70
+ base64url `value`. An admission decision is authoritative only when it
71
+ carries a signature whose `keyId` the relying party recognizes as belonging
72
+ to the claimed issuer.
73
+
74
+ The `issuer` block asserts identity (`id`, `role`, optional `keyId`); the
75
+ signature is what makes that assertion checkable. `issuer.role` distinguishes
76
+ an admission authority from a tool or a self-attestation, but the role field
77
+ alone confers no authority.
78
+
79
+ Verifier implementation is in scope (see the verification procedure below).
80
+ Key distribution and trust establishment remain policy-level: relying parties
81
+ decide which keys they recognize, and MNSTRY publishes its admission public
82
+ keys out of band.
83
+
84
+ ## Signing procedure
85
+
86
+ The signing input is the UTF-8 encoding of the RFC 8785 (JCS) canonicalization
87
+ of the attestation document with its `signature` member set to `null`. That
88
+ sentence is normative; everything else here is mechanics.
89
+
90
+ - To sign: take a schema-valid attestation whose `signature` is `null`,
91
+ canonicalize it, sign the canonical bytes, and embed the result as
92
+ `{ "algorithm": ..., "keyId": ..., "value": ... }` where `value` is the
93
+ unpadded base64url encoding of the raw signature bytes.
94
+ - To verify: take the signed document, replace `signature` with `null`,
95
+ canonicalize, and verify `signature.value` over those bytes.
96
+ - `ed25519` signatures are raw 64-byte RFC 8032 signatures.
97
+ - `es256` signatures are the 64-byte IEEE P1363 concatenation of r and s,
98
+ not DER. A DER-encoded signature also fits the schema's base64url pattern,
99
+ so a verifier must not accept one by accident and a signer must never emit
100
+ one — both signature shapes are 64 bytes (86 base64url characters).
101
+
102
+ A signer must refuse to sign a document that is not schema-valid, already
103
+ carries a signature, or names an `issuer.keyId` different from the signing
104
+ key's `keyId`.
105
+
106
+ ## Verification procedure
107
+
108
+ `verifyAttestation` returns `{ valid, reasons }` and collects every applicable
109
+ reason instead of stopping at the first, in this order:
110
+
111
+ 1. `schema.invalid` — the document fails the attestation schema.
112
+ 2. `signature.missing` — `signature` is `null`. An unsigned attestation is
113
+ non-authoritative, so verification fails by definition.
114
+ 3. `signature.algorithm-mismatch` — `signature.algorithm` does not match the
115
+ public key document's `algorithm` (or its JWK is for another curve).
116
+ 4. `signature.key-id-mismatch` — `signature.keyId` does not match the public
117
+ key document's `keyId`.
118
+ 5. `issuer.key-id-mismatch` — `issuer.keyId` is present and differs from
119
+ `signature.keyId`.
120
+ 6. `signature.invalid` — cryptographic verification fails over the signing
121
+ input defined above.
122
+
123
+ Payload binding is checked separately by `verifyPayloadBinding`, with reasons
124
+ `payload.digest-mismatch` (recomputed canonical digest differs from
125
+ `subject.payloadHash.digest`) and `payload.schema-field-mismatch` (the
126
+ payload's `schema` field differs from `subject.schema`). Signature validity
127
+ and payload binding are independent answers: a signature can be genuine while
128
+ the attestation is being presented against the wrong payload, and vice versa.
129
+
130
+ ## Key format and the local key file
131
+
132
+ Key material is handled as JWK only — never PEM. The private key file lives in
133
+ the signing project's working directory, is gitignored by default, and must
134
+ never be committed:
135
+
136
+ ```json
137
+ {
138
+ "keyId": "example-issuer-2026",
139
+ "algorithm": "ed25519",
140
+ "privateKeyJwk": { "kty": "OKP", "crv": "Ed25519", "x": "wafd…", "d": "9dfu…" }
141
+ }
142
+ ```
143
+
144
+ The signing key loads with fail-closed precedence: the
145
+ `ATELIER_ATTESTATION_KEY_JSON` environment variable (key file JSON), then
146
+ `--key <file>`, then `./atelier-attestation-key.local.json` in the current
147
+ directory. If none is present, signing exits with code 2 — there is no
148
+ "sign without a key" mode.
149
+
150
+ The public key document is freely shareable and is what verifiers consume:
151
+
152
+ ```json
153
+ {
154
+ "keyId": "example-issuer-2026",
155
+ "algorithm": "ed25519",
156
+ "publicKeyJwk": { "kty": "OKP", "crv": "Ed25519", "x": "wafd…" }
157
+ }
158
+ ```
159
+
160
+ Errors and logs may name `keyId` and `algorithm`, and never any JWK member
161
+ value.
162
+
163
+ ## Command line
164
+
165
+ ```
166
+ atelier attestation hash <payload.json>
167
+ atelier attestation sign <attestation.json> [--key FILE] [--out FILE]
168
+ atelier attestation verify <attestation.json> --public-key FILE [--payload FILE] [--json]
169
+ atelier attestation keygen --key-id ID [--algorithm ed25519|es256] [--out FILE]
170
+ ```
171
+
172
+ - `hash` prints the canonical `payloadHash` object for a payload document.
173
+ - `sign` signs an unsigned attestation with the local signing key and writes
174
+ the signed document to stdout or `--out`.
175
+ - `verify` checks the signature against a public key file, plus payload
176
+ binding when `--payload` is given; `--json` prints `{ valid, reasons }`.
177
+ When no `--payload` is given, the output says so explicitly.
178
+ - `keygen` generates a key pair, writes the private key file (mode 0600,
179
+ refusing to overwrite an existing file), and prints only the public key
180
+ document.
181
+
182
+ Exit codes: 0 success (for `verify`: valid), 1 `verify` judged the attestation
183
+ invalid, 2 usage or input error.
184
+
185
+ ## Fixtures and tests
186
+
187
+ Reference documents live in `fixtures/atelier-attestation/` (`valid/` and
188
+ `invalid/`), and `test/attestation-contract.test.mjs` validates all of them
189
+ against the schema, asserting that each invalid fixture fails for its intended
190
+ reason.
191
+
192
+ `valid/` fixtures are schema-valid shapes; only `signed-roundtrip.v1.json` is
193
+ also cryptographically real. Its signature was produced once at authoring time
194
+ with a throwaway key whose private half was never persisted; the public half
195
+ is committed as `keys/roundtrip-issuer.public.v1.json`, and its digest is the
196
+ real canonical hash of `fixtures/atelier-export/sample-studio-offer.v1.json`.
197
+ `test/attestation-signing.test.mjs` verifies the committed signature and the
198
+ payload binding on every run (and never re-signs), alongside a tamper matrix
199
+ asserting the exact failure reason for each mutation.
200
+ `test/attestation-jcs.test.mjs` pins the canonicalizer to the RFC 8785 vectors,
201
+ and `test/attestation-cli.test.mjs` exercises the command line end to end.
@@ -0,0 +1,55 @@
1
+ # Distribution continuity commitment
2
+
3
+ Anyone building on this package needs to know their access cannot be revoked
4
+ by a repository permission change. This document is MNSTRY's answer, and it is
5
+ written to survive skeptical reading.
6
+
7
+ ## What the license already guarantees
8
+
9
+ Every copy of this package you have received is licensed under Apache-2.0.
10
+ That grant is perpetual and irrevocable for the copy you hold: nothing MNSTRY
11
+ does later — repository changes, relicensing of future versions, commercial
12
+ disagreements — removes your right to use, modify, and redistribute the
13
+ version you received, subject only to the license's own terms (including the
14
+ NOTICE attribution obligation and the trademark limits in `TRADEMARKS.md`).
15
+
16
+ ## What MNSTRY commits to beyond the license
17
+
18
+ 1. **Source grant on every tag.** Every tagged release that MNSTRY
19
+ distributes to a collaborator or partner is theirs to keep. If your access
20
+ channel is the Git repository and that access ever ends, you retain every
21
+ tag you fetched, with full Apache-2.0 rights over it. MNSTRY will not ask
22
+ for deletion of received tagged source and has no license mechanism to do
23
+ so.
24
+ 2. **npm publication at public release.** When the package's public flip
25
+ happens, tagged releases will be published to the npm registry under
26
+ `@mnstry/atelier` with public access, making the registry — not repository
27
+ permission — the distribution channel of record.
28
+ 3. **Contract compatibility by machinery.** From tag `v0.2.0-alpha.0`
29
+ onward, the published contracts are under a compatibility gate
30
+ (`npm run contract:compat`): documents valid against the baseline tag must
31
+ stay valid, and schema widening outside `ext` containers is refused by a
32
+ schema-vs-schema differ. Breaking changes require a new major contract
33
+ version, recorded in `CHANGELOG.md` under a **Breaking** bullet and in the
34
+ migrations map.
35
+ 4. **Conformance stays offline.** Validating documents against the published
36
+ contracts requires only this package and never a MNSTRY service. No future
37
+ version will make offline conformance depend on a network call — that is
38
+ enforced by the egress gate, not promised by intention.
39
+
40
+ ## What this document does not promise
41
+
42
+ - It does not promise that unreleased work, private extension packs, or
43
+ MNSTRY's runtime services are or will become open.
44
+ - It does not promise support, maintenance windows, or acceptance of
45
+ contributions.
46
+ - It does not grant trademark rights; `TRADEMARKS.md` governs naming.
47
+
48
+ ## Verifying instead of trusting
49
+
50
+ - The license grant: `LICENSE` (Apache-2.0, section 2 — "perpetual,
51
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable").
52
+ - The compatibility gate: `scripts/check-contract-compat.mjs` and
53
+ `contracts/compat-baseline.json`.
54
+ - The offline-conformance property: `npm run egress:check` and the served-CSP
55
+ assertions in the test suite.
@@ -0,0 +1,111 @@
1
+ # Contract stability
2
+
3
+ How the Atelier contracts evolve without breaking pinned consumers: the
4
+ stability epoch, the `contractVersion` field, the reserved `ext` extension
5
+ containers and their must-ignore rules, the widening ban, the deprecation
6
+ policy, and the compatibility gate that enforces all of it mechanically.
7
+
8
+ ## The stability epoch
9
+
10
+ The contracts underwent one sanctioned flag-day break — the epoch. All
11
+ breaking surgery landed together, before the next release tag: the
12
+ vendor-neutral runtime owner vocabulary, the `ext` containers, the optional
13
+ `contractVersion` field, the claim provider addition, and the schema const
14
+ alignments. The individual changes are listed in the changelog.
15
+
16
+ The first tag cut after this surgery is the epoch baseline. The compatibility
17
+ gate pins to it through `contracts/compat-baseline.json` and is deliberately
18
+ inert — a clean pass with a notice — until that tag exists.
19
+
20
+ From the epoch onward the rule is mechanical: any change that widens the
21
+ accepted document space outside `ext` is a new major version, and the gate
22
+ enforces this automatically, because validators pinned to the baseline reject
23
+ widened documents.
24
+
25
+ ## Contract version
26
+
27
+ Every document contract declares an optional root `contractVersion` field:
28
+
29
+ - Type: string matching `^1\.[0-9]+\.[0-9]+$`.
30
+ - Semantics: the contract revision (major.minor.patch) the producer targeted.
31
+ - The major digit must equal the schema's `@vN` suffix.
32
+ - Absence means `1.0.0`.
33
+ - Producers should emit it; validators must not require it.
34
+
35
+ Schemas are not republished per revision. The `schema` consts stay `…@v1`;
36
+ each schema root carries a `$comment` naming the current revision (for
37
+ example `contract revision 1.1.0 (contract-stability epoch)`), which is the
38
+ machine-visible revision marker.
39
+
40
+ ## Extension containers
41
+
42
+ Every object subschema that closes itself with `additionalProperties: false`
43
+ — the root and every nested closed object — declares an optional `ext`
44
+ property of type object. `ext` never appears in `required`. This placement is
45
+ uniform across all contracts and is enforced by the contract hygiene test, so
46
+ new contracts pick up the rule automatically.
47
+
48
+ `ext` is the only sanctioned place for data the contracts do not define.
49
+ Producers may add namespaced members; vendor-prefixed keys are recommended.
50
+ Contents are not validated at v1 and carry no authority over first-class
51
+ fields.
52
+
53
+ ## Must-ignore rules
54
+
55
+ - Documents are closed at every level except `ext`. Unknown properties
56
+ outside `ext` are rejected — typo safety is a feature, not a bug.
57
+ - `ext` members must be ignored by consumers that do not recognize them, must
58
+ not alter the meaning of first-class fields, and must not be required to
59
+ interpret the document.
60
+ - Safety scanners are exempt from must-ignore. The dry-run validator's
61
+ mutation-intent, target-field, and source-reference scanners descend into
62
+ every nested object, including `ext`, and fail closed. A mutation flag or
63
+ an unsupported runtime target hidden inside `ext` is still a refusal;
64
+ must-ignore never suppresses a refusal.
65
+
66
+ ## The widening ban
67
+
68
+ A v1 revision may tighten constraints or add semantics carried in `ext`;
69
+ anything that widens the accepted document space outside `ext` is a new
70
+ major. Widening includes adding enum values, opening a closed object, adding
71
+ optional first-class properties, and relaxing a pattern or a bound. The
72
+ compatibility gate turns this policy into a mechanical check: a widened
73
+ document fails validation under the baseline's validators.
74
+
75
+ ## Deprecation policy
76
+
77
+ - Deprecating a field or value starts with a dated notice in the docs, at
78
+ least one minor revision before any behavioral change.
79
+ - Deprecated fields keep validating for their entire deprecation window.
80
+ - Removal — or any other change that rejects previously valid documents —
81
+ happens only at a major version.
82
+
83
+ ## The compatibility gate
84
+
85
+ `scripts/check-contract-compat.mjs` (npm script `contract:compat`) validates
86
+ the current document corpus against the validators of a past release:
87
+
88
+ 1. Resolve the baseline: a `--baseline <ref>` override, else the
89
+ `baselineTag` in `contracts/compat-baseline.json`. A null `baselineTag`
90
+ means the epoch is not yet tagged; the gate prints a notice and passes.
91
+ 2. For each corpus entry, fetch the old schema from the baseline ref.
92
+ Contracts that do not exist at the baseline are reported as new since
93
+ baseline and skipped — new contracts are always admissible.
94
+ 3. Compile the old schema and validate the current corpus against it: valid
95
+ fixtures, registered sample documents, and generated documents such as the
96
+ bundled readiness protocols. Generated documents are the ones most likely
97
+ to drift, so they are always included.
98
+ 4. Any rejection names the fixture, the contract, and the baseline, and fails
99
+ the gate.
100
+
101
+ A self-consistency test runs the same plumbing with the working tree as its
102
+ own baseline, so the machinery is proven even while the gate is inert.
103
+
104
+ ## Baseline procedure at release time
105
+
106
+ Cutting a release tag updates `contracts/compat-baseline.json` in the release
107
+ commit, setting `baselineTag` to the tag being cut, and runs
108
+ `npm run contract:compat` as part of the release checks. During the following
109
+ development cycle the gate therefore validates the evolving corpus against
110
+ the most recent release's validators. The first tag after the epoch is the
111
+ first baseline; before it exists, the gate stays inert.
@@ -0,0 +1,220 @@
1
+ # Distributions
2
+
3
+ A distribution is a branded wrapper package around `@mnstry/atelier`. It gives
4
+ a partner or program its own command name, display name, and look while the
5
+ MNSTRY Atelier package underneath keeps doing all of the work. This document
6
+ describes the model, the one invariant every distribution must hold, the
7
+ attribution requirement, and how to build one.
8
+
9
+ ## The three-tier model
10
+
11
+ 1. **Root package** — `@mnstry/atelier`. Owns every command, contract,
12
+ validator, and the bundled MNSTRY readiness pack. Published and versioned by
13
+ MNSTRY.
14
+ 2. **Distribution wrapper** — a small npm package (for example Loomworks
15
+ Studio) that depends on the root package, ships a branded bin, and may add
16
+ extension packs, skills, and projection branding. It never modifies the
17
+ root package.
18
+ 3. **End workspace** — what a user of the distribution initializes and works
19
+ in. Workspaces created through a distribution are ordinary Atelier
20
+ workspaces; nothing about them is fork-specific.
21
+
22
+ Because the wrapper is a dependency edge and not a fork, upgrading a
23
+ distribution is `npm install` of a newer root package, and every root gate
24
+ (contracts, boundary rules, egress checks) applies unchanged.
25
+
26
+ ## The invariant
27
+
28
+ Add and rebrand, never alter root semantics.
29
+
30
+ A distribution may add commands of its own, add extension packs, add skills,
31
+ and restyle the projection. It must not remap or shadow root commands, replace
32
+ or edit bundled readiness protocols, or alter the bundled MNSTRY pack in any
33
+ way. Extension packs are the additive path: namespaced protocols load
34
+ alongside the bundled twelve, they never substitute for them. A wrapper that
35
+ changes what a root command means is a fork, not a distribution.
36
+
37
+ ## Attribution requirement
38
+
39
+ Distributions must carry MNSTRY attribution. The normative wording of this
40
+ requirement lives in `TRADEMARKS.md` under "Required attribution" — this
41
+ document deliberately does not restate it, so the two cannot drift. See also
42
+ `docs/attestation.md` for how admission decisions are recorded.
43
+
44
+ Three related obligations live alongside it:
45
+
46
+ - **Quiet software.** Anything carrying the MNSTRY marks is bound by the
47
+ "Quiet software" clause in `TRADEMARKS.md`: attribution is a passive line,
48
+ never an upsell, interruption, or call-to-action.
49
+ - **Applications without a CLI.** An app powered by this package that ships no
50
+ CLI carries the attribution in its user-facing credits or about surface —
51
+ see "Applications" in `TRADEMARKS.md`.
52
+ - **NOTICE.** The package ships a `NOTICE` file at its root; under
53
+ Apache-2.0 section 4(d), every derivative redistribution must reproduce it.
54
+
55
+ Mechanically, attribution has three surfaces:
56
+
57
+ - **CLI output — automatic.** `runCli` renders the attribution line in
58
+ `--version` and `--help` output for any non-default brand. A wrapper that
59
+ dispatches through `runCli` does not need to print anything itself.
60
+ - **Distribution README — blocking.** The distribution's root `README.md`
61
+ must contain the exact byte string `powered by MNSTRY Atelier`. Surrounding
62
+ text is free.
63
+ - **Pack manifest — advisory.** An extension-pack manifest should declare
64
+ `"ext": { "mnstry.atelier/attribution": "powered by MNSTRY Atelier" }`.
65
+ The `mnstry.atelier/attribution` key is distinct from the `mnstry.atelier`
66
+ container used for projection branding.
67
+
68
+ `atelier distribution check` verifies the last two, plus a CLI probe: the
69
+ README byte string is blocking (exit 1 when absent), and when the target's
70
+ `package.json` declares a bin, the check **executes that bin** with
71
+ `--version` (spawned with the current Node, working directory set to the
72
+ target, real pipes) and requires the attribution in its output — this closes
73
+ the loophole where a wrapper injects no-op output streams into `runCli`.
74
+ Because the probe runs the target's code, only point it at distributions you
75
+ trust. A target that looks like a distribution (declares `@mnstry/atelier`,
76
+ ships `packs/`, or carries `atelier.project.json`) but declares no probe-able
77
+ bin — or whose `package.json` does not parse — is a blocking failure; only a
78
+ target with none of those markers gets an advisory skip. The manifest key is
79
+ reported but never blocks. Run it from the distribution root, or point it
80
+ elsewhere with `--target DIR`; `--pack DIR` overrides the default
81
+ `packs/*/atelier.pack.json` manifest lookup. Exit codes: 0 checks passed,
82
+ 1 blocking attribution failure, 2 usage error.
83
+
84
+ ## Building a distribution: the Loomworks walkthrough
85
+
86
+ The reference distribution, Loomworks Studio, lives at
87
+ `examples/loomworks-studio/` in this repository; the steps below are the
88
+ complete recipe it follows, and `npm run distribution:smoke` runs that recipe
89
+ against a packed tarball on every publish. Paths are relative to the wrapper
90
+ package root, for example `~/workspace/loomworks-studio`.
91
+
92
+ ### 1. Create the wrapper package
93
+
94
+ ```json
95
+ {
96
+ "name": "loomworks-studio",
97
+ "private": true,
98
+ "type": "module",
99
+ "version": "0.1.0",
100
+ "bin": { "loomworks": "bin/loomworks.mjs" },
101
+ "dependencies": { "@mnstry/atelier": "file:../.." }
102
+ }
103
+ ```
104
+
105
+ A published distribution depends on a released `@mnstry/atelier` version
106
+ instead of a `file:` path.
107
+
108
+ ### 2. Write the branded bin
109
+
110
+ The whole wrapper story is one small file:
111
+
112
+ ```js
113
+ #!/usr/bin/env node
114
+ import { runCli } from '@mnstry/atelier/cli'
115
+ import { createRequire } from 'node:module'
116
+ const { version } = createRequire(import.meta.url)('../package.json')
117
+ process.exit(await runCli({
118
+ argv: process.argv.slice(2),
119
+ brand: { command: 'loomworks', displayName: 'Loomworks Studio', version },
120
+ }))
121
+ ```
122
+
123
+ With that in place, `loomworks --version` prints
124
+ `Loomworks Studio 0.1.0 — powered by MNSTRY Atelier <root version>` and
125
+ `loomworks --help` opens with the display name and the attribution line — both
126
+ rendered by `runCli`, neither maintained by the wrapper.
127
+
128
+ ### 3. Put the attribution line in the README
129
+
130
+ Add the exact byte string `powered by MNSTRY Atelier` near the top of the
131
+ wrapper's `README.md`. This is what `distribution check` blocks on.
132
+
133
+ ### 4. Brand the projection
134
+
135
+ Workspace projections read
136
+ `ext["mnstry.atelier"].distribution` from `atelier.project.json`:
137
+
138
+ ```json
139
+ {
140
+ "ext": {
141
+ "mnstry.atelier": {
142
+ "distribution": {
143
+ "name": "Loomworks Studio",
144
+ "eyebrow": "studio projection",
145
+ "theme": { "accent": "#7a9e7e" }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ The "MNSTRY Tenant Readiness" section heading in the rendered projection stays
153
+ as is: it names the bundled MNSTRY pack.
154
+
155
+ ### 5. Add an extension pack
156
+
157
+ Branded protocols live under `packs/<pack-name>/` with a manifest
158
+ (`atelier.pack.json`, schema `mnstry-atelier-extension-pack@v1`) whose
159
+ protocol ids are namespaced, for example `loomworks.readiness:open-intake`.
160
+ The manifest carries the advisory attribution key:
161
+
162
+ ```json
163
+ {
164
+ "ext": { "mnstry.atelier/attribution": "powered by MNSTRY Atelier" }
165
+ }
166
+ ```
167
+
168
+ Extension protocols load alongside the bundled twelve; they never replace
169
+ them. Note that pack contributions to the tenant readiness packet ride in an
170
+ unschema'd runtime document (`mnstry.tenant-readiness-packet@v1`) whose shape
171
+ is pinned by tests, not by the versioned contracts the compat gate checks.
172
+
173
+ Declared pack paths resolve from the directory holding `atelier.project.json`
174
+ and may not contain `..` segments, so the pack directory has to sit inside the
175
+ config directory. Put the workspace config wherever the packs are, not one
176
+ level above them — `examples/loomworks-studio/` keeps both at the wrapper
177
+ root for exactly this reason.
178
+
179
+ ### 6. Ship skills
180
+
181
+ Distribution skills follow the root package's dual-tree convention:
182
+ `skills/claude/<skill-name>/SKILL.md` and a byte-identical
183
+ `skills/codex/<skill-name>/SKILL.md`.
184
+
185
+ ### 7. Verify
186
+
187
+ ```
188
+ loomworks distribution check
189
+ ```
190
+
191
+ Exit 0 means the README attribution is present; the output also reports
192
+ whether each pack manifest declares the advisory attribution key. The repo's
193
+ distribution smoke gate runs the same check against the installed example.
194
+
195
+ ## `ext["mnstry.atelier"].distribution` reference
196
+
197
+ All fields are optional; absence renders the default MNSTRY Atelier styling.
198
+
199
+ | Field | Type | Meaning |
200
+ | --- | --- | --- |
201
+ | `name` | string | Display name used in the projection title and eyebrow. |
202
+ | `eyebrow` | string | Second half of the eyebrow line (default `local projection`). |
203
+ | `theme` | object | Color overrides, keyed by theme token name. |
204
+
205
+ Theme token names: `background`, `surface`, `text`, `accent`, `eyebrow`. Each
206
+ maps to a CSS custom property (`--atelier-bg`, `--atelier-surface`,
207
+ `--atelier-text`, `--atelier-accent`, `--atelier-eyebrow`).
208
+
209
+ Hex-only rule: every theme value must be a CSS hex color of 3, 4, 6, or 8
210
+ digits (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`), and unknown theme keys fail
211
+ the build rather than silently dropping. Values are interpolated into a style
212
+ block, so non-hex input fails the build fast — this is an injection guard, not
213
+ a stylistic preference.
214
+
215
+ ## Admission
216
+
217
+ A distribution changes branding, not authority. Admission of any payload into
218
+ a MNSTRY destination is decided and recorded exactly as for the root package —
219
+ see `docs/attestation.md` for the admission model and how attestations record
220
+ those decisions.