@clossys/launcher 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/README.md +1328 -60
  2. package/contracts/conversation-contract.md +2 -1
  3. package/contracts/product-ci-workflow.yml +74 -0
  4. package/contracts/repository-inventory.json +53 -0
  5. package/dist/admission-fixture.d.ts +168 -0
  6. package/dist/admission-fixture.d.ts.map +1 -0
  7. package/dist/admission-fixture.js +453 -0
  8. package/dist/admission-fixture.js.map +1 -0
  9. package/dist/admission.d.ts +124 -0
  10. package/dist/admission.d.ts.map +1 -0
  11. package/dist/admission.js +799 -0
  12. package/dist/admission.js.map +1 -0
  13. package/dist/agents-guide.d.ts +9 -0
  14. package/dist/agents-guide.d.ts.map +1 -0
  15. package/dist/agents-guide.js +26 -0
  16. package/dist/agents-guide.js.map +1 -0
  17. package/dist/apply-command-options.check.d.ts +12 -0
  18. package/dist/apply-command-options.check.d.ts.map +1 -0
  19. package/dist/apply-command-options.check.js +20 -0
  20. package/dist/apply-command-options.check.js.map +1 -0
  21. package/dist/apply-plan-cli.d.ts +39 -1
  22. package/dist/apply-plan-cli.d.ts.map +1 -1
  23. package/dist/apply-plan-cli.js +432 -15
  24. package/dist/apply-plan-cli.js.map +1 -1
  25. package/dist/apply-plan.d.ts +46 -59
  26. package/dist/apply-plan.d.ts.map +1 -1
  27. package/dist/apply-plan.js +112 -97
  28. package/dist/apply-plan.js.map +1 -1
  29. package/dist/apply-step-fixture.d.ts +87 -0
  30. package/dist/apply-step-fixture.d.ts.map +1 -0
  31. package/dist/apply-step-fixture.js +199 -0
  32. package/dist/apply-step-fixture.js.map +1 -0
  33. package/dist/apply-store.d.ts +93 -0
  34. package/dist/apply-store.d.ts.map +1 -0
  35. package/dist/apply-store.js +625 -0
  36. package/dist/apply-store.js.map +1 -0
  37. package/dist/approval-sheet.d.ts +21 -0
  38. package/dist/approval-sheet.d.ts.map +1 -0
  39. package/dist/approval-sheet.js +157 -0
  40. package/dist/approval-sheet.js.map +1 -0
  41. package/dist/body-command.d.ts +42 -0
  42. package/dist/body-command.d.ts.map +1 -0
  43. package/dist/body-command.js +143 -0
  44. package/dist/body-command.js.map +1 -0
  45. package/dist/change-set-contract.d.ts +381 -0
  46. package/dist/change-set-contract.d.ts.map +1 -0
  47. package/dist/change-set-contract.js +738 -0
  48. package/dist/change-set-contract.js.map +1 -0
  49. package/dist/change-set-digest.d.ts +28 -0
  50. package/dist/change-set-digest.d.ts.map +1 -0
  51. package/dist/change-set-digest.js +65 -0
  52. package/dist/change-set-digest.js.map +1 -0
  53. package/dist/check-cli.d.ts.map +1 -1
  54. package/dist/check-cli.js +14 -3
  55. package/dist/check-cli.js.map +1 -1
  56. package/dist/cli.d.ts +17 -6
  57. package/dist/cli.d.ts.map +1 -1
  58. package/dist/cli.js +84 -23
  59. package/dist/cli.js.map +1 -1
  60. package/dist/core.d.ts +79 -22
  61. package/dist/core.d.ts.map +1 -1
  62. package/dist/core.js +843 -268
  63. package/dist/core.js.map +1 -1
  64. package/dist/dry-materialize.d.ts +63 -0
  65. package/dist/dry-materialize.d.ts.map +1 -0
  66. package/dist/dry-materialize.js +330 -0
  67. package/dist/dry-materialize.js.map +1 -0
  68. package/dist/generated/contract-schema.generated.d.ts +97 -0
  69. package/dist/generated/contract-schema.generated.d.ts.map +1 -0
  70. package/dist/generated/contract-schema.generated.js +496 -0
  71. package/dist/generated/contract-schema.generated.js.map +1 -0
  72. package/dist/generated/package-scope.generated.d.ts +6 -0
  73. package/dist/generated/package-scope.generated.d.ts.map +1 -0
  74. package/dist/generated/package-scope.generated.js +10 -0
  75. package/dist/generated/package-scope.generated.js.map +1 -0
  76. package/dist/generated/plan-contracts.generated.d.ts +3 -0
  77. package/dist/generated/plan-contracts.generated.d.ts.map +1 -0
  78. package/dist/generated/plan-contracts.generated.js +2840 -0
  79. package/dist/generated/plan-contracts.generated.js.map +1 -0
  80. package/dist/host.d.ts.map +1 -1
  81. package/dist/host.js +11 -0
  82. package/dist/host.js.map +1 -1
  83. package/dist/identity.d.ts +15 -0
  84. package/dist/identity.d.ts.map +1 -0
  85. package/dist/identity.js +48 -0
  86. package/dist/identity.js.map +1 -0
  87. package/dist/index.d.ts +34 -5
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +19 -2
  90. package/dist/index.js.map +1 -1
  91. package/dist/inventory-adoption.d.ts +24 -5
  92. package/dist/inventory-adoption.d.ts.map +1 -1
  93. package/dist/inventory-adoption.js +70 -25
  94. package/dist/inventory-adoption.js.map +1 -1
  95. package/dist/inventory-choice.d.ts +40 -0
  96. package/dist/inventory-choice.d.ts.map +1 -0
  97. package/dist/inventory-choice.js +156 -0
  98. package/dist/inventory-choice.js.map +1 -0
  99. package/dist/inventory-contract.d.ts +89 -0
  100. package/dist/inventory-contract.d.ts.map +1 -0
  101. package/dist/inventory-contract.js +121 -0
  102. package/dist/inventory-contract.js.map +1 -0
  103. package/dist/key-editor.d.ts +30 -0
  104. package/dist/key-editor.d.ts.map +1 -0
  105. package/dist/key-editor.js +445 -0
  106. package/dist/key-editor.js.map +1 -0
  107. package/dist/ledger-contract.d.ts +187 -0
  108. package/dist/ledger-contract.d.ts.map +1 -0
  109. package/dist/ledger-contract.js +532 -0
  110. package/dist/ledger-contract.js.map +1 -0
  111. package/dist/ledger-trust.d.ts +90 -0
  112. package/dist/ledger-trust.d.ts.map +1 -0
  113. package/dist/ledger-trust.js +198 -0
  114. package/dist/ledger-trust.js.map +1 -0
  115. package/dist/lockfile-invariants.d.ts +48 -0
  116. package/dist/lockfile-invariants.d.ts.map +1 -0
  117. package/dist/lockfile-invariants.js +375 -0
  118. package/dist/lockfile-invariants.js.map +1 -0
  119. package/dist/lockfile-readers.d.ts +72 -0
  120. package/dist/lockfile-readers.d.ts.map +1 -0
  121. package/dist/lockfile-readers.js +713 -0
  122. package/dist/lockfile-readers.js.map +1 -0
  123. package/dist/lockfile-regen.d.ts +106 -0
  124. package/dist/lockfile-regen.d.ts.map +1 -0
  125. package/dist/lockfile-regen.js +760 -0
  126. package/dist/lockfile-regen.js.map +1 -0
  127. package/dist/lockfile-tool-env.d.ts +29 -0
  128. package/dist/lockfile-tool-env.d.ts.map +1 -0
  129. package/dist/lockfile-tool-env.js +111 -0
  130. package/dist/lockfile-tool-env.js.map +1 -0
  131. package/dist/materialize.d.ts +113 -0
  132. package/dist/materialize.d.ts.map +1 -0
  133. package/dist/materialize.js +840 -0
  134. package/dist/materialize.js.map +1 -0
  135. package/dist/observe-repository.d.ts +90 -0
  136. package/dist/observe-repository.d.ts.map +1 -0
  137. package/dist/observe-repository.js +1367 -0
  138. package/dist/observe-repository.js.map +1 -0
  139. package/dist/plan-bundle-setup-fixture.d.ts +68 -0
  140. package/dist/plan-bundle-setup-fixture.d.ts.map +1 -0
  141. package/dist/plan-bundle-setup-fixture.js +167 -0
  142. package/dist/plan-bundle-setup-fixture.js.map +1 -0
  143. package/dist/plan-bundle.d.ts +250 -0
  144. package/dist/plan-bundle.d.ts.map +1 -0
  145. package/dist/plan-bundle.js +827 -0
  146. package/dist/plan-bundle.js.map +1 -0
  147. package/dist/plan-command.d.ts +29 -0
  148. package/dist/plan-command.d.ts.map +1 -0
  149. package/dist/plan-command.js +493 -0
  150. package/dist/plan-command.js.map +1 -0
  151. package/dist/plan-contract.d.ts +153 -0
  152. package/dist/plan-contract.d.ts.map +1 -0
  153. package/dist/plan-contract.js +61 -0
  154. package/dist/plan-contract.js.map +1 -0
  155. package/dist/plan-digest.d.ts +25 -0
  156. package/dist/plan-digest.d.ts.map +1 -0
  157. package/dist/plan-digest.js +106 -0
  158. package/dist/plan-digest.js.map +1 -0
  159. package/dist/plan-rules.d.ts +23 -0
  160. package/dist/plan-rules.d.ts.map +1 -0
  161. package/dist/plan-rules.js +177 -0
  162. package/dist/plan-rules.js.map +1 -0
  163. package/dist/planned-bundle.d.ts +20 -0
  164. package/dist/planned-bundle.d.ts.map +1 -0
  165. package/dist/planned-bundle.js +191 -0
  166. package/dist/planned-bundle.js.map +1 -0
  167. package/dist/product-repository.d.ts +4 -0
  168. package/dist/product-repository.d.ts.map +1 -1
  169. package/dist/product-repository.js +9 -1
  170. package/dist/product-repository.js.map +1 -1
  171. package/dist/provenance-gate.d.ts +48 -0
  172. package/dist/provenance-gate.d.ts.map +1 -0
  173. package/dist/provenance-gate.js +324 -0
  174. package/dist/provenance-gate.js.map +1 -0
  175. package/dist/pull-request-body.d.ts +45 -0
  176. package/dist/pull-request-body.d.ts.map +1 -0
  177. package/dist/pull-request-body.js +232 -0
  178. package/dist/pull-request-body.js.map +1 -0
  179. package/dist/registry-snapshot.d.ts +141 -0
  180. package/dist/registry-snapshot.d.ts.map +1 -0
  181. package/dist/registry-snapshot.js +483 -0
  182. package/dist/registry-snapshot.js.map +1 -0
  183. package/dist/release-age-edit.d.ts +52 -0
  184. package/dist/release-age-edit.d.ts.map +1 -0
  185. package/dist/release-age-edit.js +413 -0
  186. package/dist/release-age-edit.js.map +1 -0
  187. package/dist/root-entries.d.ts +36 -0
  188. package/dist/root-entries.d.ts.map +1 -0
  189. package/dist/root-entries.js +80 -0
  190. package/dist/root-entries.js.map +1 -0
  191. package/dist/setup-template-scripts.d.ts +34 -0
  192. package/dist/setup-template-scripts.d.ts.map +1 -0
  193. package/dist/setup-template-scripts.js +557 -0
  194. package/dist/setup-template-scripts.js.map +1 -0
  195. package/dist/setup-templates.d.ts +54 -0
  196. package/dist/setup-templates.d.ts.map +1 -0
  197. package/dist/setup-templates.js +427 -0
  198. package/dist/setup-templates.js.map +1 -0
  199. package/dist/skills.d.ts +34 -1
  200. package/dist/skills.d.ts.map +1 -1
  201. package/dist/skills.js +129 -17
  202. package/dist/skills.js.map +1 -1
  203. package/dist/status.d.ts +63 -0
  204. package/dist/status.d.ts.map +1 -0
  205. package/dist/status.js +539 -0
  206. package/dist/status.js.map +1 -0
  207. package/dist/types.d.ts +151 -13
  208. package/dist/types.d.ts.map +1 -1
  209. package/package.json +4 -5
  210. package/skeleton/README.md +14 -9
  211. package/skeleton/package.json +2 -1
  212. package/skill/SKILL.md +20 -16
  213. package/skill-catalogue/advisor/SKILL.md +100 -11
  214. package/skill-catalogue/architect/SKILL.md +2 -13
  215. package/skill-catalogue/bouncer/SKILL.md +2 -13
  216. package/skill-catalogue/builder/SKILL.md +2 -13
  217. package/skill-catalogue/butler/SKILL.md +2 -13
  218. package/skill-catalogue/controller/SKILL.md +2 -13
  219. package/skill-catalogue/customer/SKILL.md +4 -13
  220. package/skill-catalogue/designer/SKILL.md +9 -14
  221. package/skill-catalogue/giver/SKILL.md +2 -13
  222. package/skill-catalogue/influencer/SKILL.md +2 -13
  223. package/skill-catalogue/inspector/SKILL.md +2 -13
  224. package/skill-catalogue/integrator/SKILL.md +2 -13
  225. package/skill-catalogue/keeper/SKILL.md +2 -13
  226. package/skill-catalogue/launcher/SKILL.md +20 -16
  227. package/skill-catalogue/locksmith/SKILL.md +2 -13
  228. package/skill-catalogue/messenger/SKILL.md +2 -13
  229. package/skill-catalogue/observer/SKILL.md +2 -13
  230. package/skill-catalogue/publisher/SKILL.md +11 -17
  231. package/skill-catalogue/starter/SKILL.md +3 -13
  232. package/skill-catalogue/strategist/SKILL.md +14 -17
  233. package/skill-catalogue/writer/SKILL.md +7 -14
  234. package/src/admission-fixture.ts +572 -0
  235. package/src/admission.ts +816 -0
  236. package/src/agents-guide.ts +29 -0
  237. package/src/apply-command-options.check.ts +27 -0
  238. package/src/apply-plan-cli.ts +454 -14
  239. package/src/apply-plan.ts +112 -124
  240. package/src/apply-step-fixture.ts +236 -0
  241. package/src/apply-store.ts +584 -0
  242. package/src/approval-sheet.ts +164 -0
  243. package/src/body-command.ts +162 -0
  244. package/src/change-set-contract.ts +937 -0
  245. package/src/change-set-digest.ts +70 -0
  246. package/src/check-cli.ts +14 -3
  247. package/src/cli.ts +90 -22
  248. package/src/core.ts +973 -275
  249. package/src/dry-materialize.ts +353 -0
  250. package/src/generated/contract-schema.generated.ts +520 -0
  251. package/src/generated/package-scope.generated.ts +10 -0
  252. package/src/generated/plan-contracts.generated.ts +2840 -0
  253. package/src/host.ts +10 -0
  254. package/src/identity.ts +51 -0
  255. package/src/index.ts +72 -3
  256. package/src/inventory-adoption.ts +107 -29
  257. package/src/inventory-choice.ts +172 -0
  258. package/src/inventory-contract.ts +166 -0
  259. package/src/key-editor.ts +446 -0
  260. package/src/ledger-contract.ts +637 -0
  261. package/src/ledger-trust.ts +267 -0
  262. package/src/lockfile-invariants.ts +421 -0
  263. package/src/lockfile-readers.ts +749 -0
  264. package/src/lockfile-regen.ts +851 -0
  265. package/src/lockfile-tool-env.ts +131 -0
  266. package/src/materialize.ts +886 -0
  267. package/src/observe-repository.ts +1365 -0
  268. package/src/plan-bundle-setup-fixture.ts +200 -0
  269. package/src/plan-bundle.ts +964 -0
  270. package/src/plan-command.ts +509 -0
  271. package/src/plan-contract.ts +179 -0
  272. package/src/plan-digest.ts +102 -0
  273. package/src/plan-rules.ts +188 -0
  274. package/src/planned-bundle.ts +211 -0
  275. package/src/product-repository.ts +10 -1
  276. package/src/provenance-gate.ts +352 -0
  277. package/src/pull-request-body.ts +261 -0
  278. package/src/registry-snapshot.ts +534 -0
  279. package/src/release-age-edit.ts +430 -0
  280. package/src/root-entries.ts +81 -0
  281. package/src/setup-template-scripts.ts +571 -0
  282. package/src/setup-templates.ts +471 -0
  283. package/src/skills.ts +161 -18
  284. package/src/status.ts +557 -0
  285. package/src/types.ts +148 -13
  286. package/CHANGELOG.md +0 -131
@@ -0,0 +1,191 @@
1
+ // Planned mode for `launcher-apply-plan plan` (issue #1708, RFC apply-approved-plan sections 4.2, 4.4 and 7).
2
+ //
3
+ // The planner and the dry tree produce a report: it claims no repository state and records no approval. This module turns
4
+ // that report into a planned bundle, but only when an approval binds exactly what was planned. Every ambiguity leaves the
5
+ // report as it is, or refuses; a rule is one of a fixed set of tokens and never carries tool output, a path, an id or a file
6
+ // name.
7
+ //
8
+ // P1. The result is planned only when the bundle says the plan is the committed one AND the hub's committed plan, read as a
9
+ // git object at an attached HEAD, is approved (readHubAuthority) for this very plan digest AND the bundle that approval
10
+ // names is one the hub holds, stored and verifying, for that plan digest (the same K2 read admission makes). An approval
11
+ // that names nothing the hub holds approves nothing this run could be bound by. Otherwise the result is returned
12
+ // untouched, so its bytes are what they were before this module existed.
13
+ // P2. A binding comes only from decideSetBinding, over the ledger this module reads at the set's base commit and trusts
14
+ // against the committed plan's acts (never the acts of the set being judged). Nothing here builds a binding by hand.
15
+ // P3. A repository that already carries a violated V3 (the planner's A4 flags: an authorization for another plan, or none)
16
+ // is not admitted, and no readiness check runs for it.
17
+ // P4. V3 is satisfied exactly when the set is bound. A refusal is V3 violated (exit 1) or indeterminate (exit 2), the rule is
18
+ // the refusal's fixed token, and the repository has no binding.
19
+ // P5. Planned mode reports V1, V2, V4, V5 and V7 as satisfied on every computed repository (a set that exists and validated
20
+ // has met them), and V9 as satisfied only for a set that changes no lockfile; the dry tree's V6 and V9 are kept.
21
+ // P6. A repository is `planned` when its verdict is satisfied and it has a binding (code rule A6). The bundle is validated
22
+ // again before it is returned. No set, digest or set byte moves: the bundle digest does not cover mode, state, binding
23
+ // or checks.
24
+ import { spawnSync } from "node:child_process";
25
+ import { realpathSync } from "node:fs";
26
+ import { decideSetBinding, planPackagesFor, readHubAuthority } from "./admission.js";
27
+ import { readStoredApplyBundle } from "./apply-store.js";
28
+ import { AUTHORIZATION_ABSENT, AUTHORIZATION_PLAN_MISMATCH, LEDGER_PATH, canonicalOrder, validateApplyBundle, worstVerdict, } from "./change-set-contract.js";
29
+ import { trustInstalledLedger } from "./ledger-trust.js";
30
+ import { derivedLockfile } from "./materialize.js";
31
+ const TOKEN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
32
+ /** The V3 rule of a refusal whose token is not a fixed one, or is one the bundle may carry only for its own authorization condition. */
33
+ const AUTHORIZATION_NOT_CURRENT = "authorization-not-current";
34
+ /** The V3 rule of a base ledger that cannot be read as a regular file. */
35
+ const LEDGER_UNREADABLE = "ledger-unreadable";
36
+ /** The V3 rule of a base ledger whose generation is not the one the set was computed over. */
37
+ const LEDGER_MISMATCH = "ledger-mismatch";
38
+ /** The V9 rule of a set that changes a lockfile and whose dry tree left no provenance check. */
39
+ const PROVENANCE_NOT_RUN = "provenance-not-run";
40
+ /** Rules only the bundle's own authorization condition may name (code rule A4). */
41
+ const RESERVED_RULES = new Set([AUTHORIZATION_PLAN_MISMATCH, AUTHORIZATION_ABSENT]);
42
+ const sortChecks = (checks) => canonicalOrder(checks, (check) => [check.check, check.rule ?? ""]);
43
+ const satisfied = (check) => ({ check, verdict: "satisfied" });
44
+ // ---------------------------------------------------------------------------
45
+ // the base ledger, from git objects only
46
+ const DROPPED_GIT_ENV = ["GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_OBJECT_DIRECTORY", "GIT_ALTERNATE_OBJECT_DIRECTORIES", "GIT_COMMON_DIR", "GIT_NAMESPACE", "GIT_PREFIX", "GIT_SHALLOW_FILE", "GIT_GRAFT_FILE"];
47
+ const OBJECT_ID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u;
48
+ /** Runs git with no repository named by the environment, no replace refs and literal pathspecs. Never throws. */
49
+ function runGit(cwd, args) {
50
+ const env = { ...process.env };
51
+ for (const name of DROPPED_GIT_ENV)
52
+ delete env[name];
53
+ env.GIT_LITERAL_PATHSPECS = "1";
54
+ env.GIT_TERMINAL_PROMPT = "0";
55
+ env.GIT_OPTIONAL_LOCKS = "0";
56
+ const run = spawnSync("git", ["--no-replace-objects", ...args], { cwd, env, stdio: ["ignore", "pipe", "ignore"], maxBuffer: 256 * 1024 * 1024 });
57
+ if (run.error !== undefined || run.stdout === null || run.stdout === undefined)
58
+ return { status: null, stdout: Buffer.alloc(0) };
59
+ return { status: run.status, stdout: run.stdout };
60
+ }
61
+ /**
62
+ * The base ledger's exact bytes at the set's base commit: null when the commit holds none, or when the commit cannot be
63
+ * read (materialize reads it the same way, and admission reads the base tree itself for every set that depends on it);
64
+ * "unreadable" when the path is there but is not one regular, non-executable blob, or the blob cannot be read.
65
+ */
66
+ function readBaseLedger(clone, baseCommit) {
67
+ if (!OBJECT_ID.test(baseCommit))
68
+ return null;
69
+ const listed = runGit(clone, ["ls-tree", "-z", "--full-tree", baseCommit, "--", LEDGER_PATH]);
70
+ if (listed.status !== 0)
71
+ return null;
72
+ const records = listed.stdout.toString("utf8").split("\0").filter((record) => record !== "");
73
+ if (records.length === 0)
74
+ return null;
75
+ if (records.length !== 1)
76
+ return "unreadable";
77
+ const record = records[0];
78
+ const tab = record.indexOf("\t");
79
+ if (tab === -1)
80
+ return "unreadable";
81
+ const [mode, type, oid, ...rest] = record.slice(0, tab).split(" ");
82
+ if (mode !== "100644" || type !== "blob" || oid === undefined || rest.length > 0 || !OBJECT_ID.test(oid) || record.slice(tab + 1) !== LEDGER_PATH)
83
+ return "unreadable";
84
+ const blob = runGit(clone, ["cat-file", "blob", oid]);
85
+ return blob.status === 0 ? blob.stdout : "unreadable";
86
+ }
87
+ /** The base ledger, trusted over the committed plan's acts (T9), and held to the generation the set was computed over. */
88
+ function trustedBaseLedger(clone, set, held, authority) {
89
+ const bytes = readBaseLedger(clone, set.repository.baseCommit);
90
+ if (bytes === "unreadable")
91
+ return { refused: { check: "V3", verdict: "indeterminate", rule: LEDGER_UNREADABLE } };
92
+ const trust = trustInstalledLedger(bytes === null || bytes.length === 0 ? null : bytes, { id: set.repository.id, nodeId: set.repository.nodeId }, held, {
93
+ planPackageActs: [{ planDigest: authority.planDigest, packages: planPackagesFor(authority, set.repository.id) }],
94
+ });
95
+ if (trust.state === "refused")
96
+ return { refused: { check: "V3", verdict: "indeterminate", rule: TOKEN.test(trust.rule) ? trust.rule : LEDGER_UNREADABLE } };
97
+ if ((trust.ledger?.generation ?? 0) !== set.ledger.generation)
98
+ return { refused: { check: "V3", verdict: "violated", rule: LEDGER_MISMATCH } };
99
+ return { ledger: trust.ledger, bytes: bytes === null || bytes.length === 0 ? null : bytes };
100
+ }
101
+ // ---------------------------------------------------------------------------
102
+ // V3 for one set
103
+ /** The V3 check of a refusal: exit 1 is violated, anything else indeterminate; the rule is its fixed detail, or its reason. */
104
+ function refusalCheck(refusal) {
105
+ const token = refusal.detail ?? refusal.reason;
106
+ const rule = TOKEN.test(token) && !RESERVED_RULES.has(token) ? token : AUTHORIZATION_NOT_CURRENT;
107
+ return { check: "V3", verdict: refusal.exitCode === 1 ? "violated" : "indeterminate", rule };
108
+ }
109
+ async function decideV3(entry, set, authority, options) {
110
+ let clone;
111
+ try {
112
+ clone = realpathSync(options.cloneFor(set.repository.id));
113
+ }
114
+ catch {
115
+ return { check: { check: "V3", verdict: "indeterminate", rule: "clone-unreadable" } };
116
+ }
117
+ const base = trustedBaseLedger(clone, set, options.heldChangeSets, authority);
118
+ if ("refused" in base)
119
+ return { check: base.refused };
120
+ const decided = await decideSetBinding({
121
+ hub: options.hub,
122
+ clone,
123
+ set,
124
+ authority,
125
+ baseLedger: base.ledger,
126
+ baseLedgerBytes: base.bytes,
127
+ now: options.now,
128
+ runReadiness: options.runReadiness,
129
+ });
130
+ if (decided.state !== "bound")
131
+ return { check: refusalCheck(decided) };
132
+ return { check: satisfied("V3"), binding: decided.binding };
133
+ }
134
+ // ---------------------------------------------------------------------------
135
+ // the bundle
136
+ /** Whether the hub stores, verified, the bundle the approval names, and it is for the approved plan's digest. A store that cannot be read holds nothing. */
137
+ function holdsApprovedBundle(hub, authority) {
138
+ try {
139
+ const approved = readStoredApplyBundle(hub, authority.subject);
140
+ return approved !== null && approved.bundleDigest === authority.subject && approved.plan.digest === authority.planDigest;
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ }
146
+ const PRE_APPLY = ["V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8", "V9"];
147
+ /**
148
+ * The result as a planned bundle, or the same result when nothing approves it (P1). A repository with no change set keeps
149
+ * its entry. Throws when the planned bundle does not validate; nothing is written here.
150
+ */
151
+ export async function plannedBundle(result, options) {
152
+ if (!result.bundle.plan.committed)
153
+ return result;
154
+ const authority = readHubAuthority(options.hub);
155
+ if ("state" in authority || authority.planDigest !== result.bundle.plan.digest)
156
+ return result;
157
+ if (!holdsApprovedBundle(options.hub, authority))
158
+ return result;
159
+ const repositories = [];
160
+ for (const entry of result.bundle.repositories) {
161
+ if (!("changeSet" in entry)) {
162
+ repositories.push(entry);
163
+ continue;
164
+ }
165
+ const set = result.changeSets.find((candidate) => candidate.changeSetDigest === entry.changeSet);
166
+ if (set === undefined)
167
+ throw new Error("a repository names a change set the result does not hold");
168
+ const { state: _state, binding: _binding, ...rest } = entry;
169
+ // P3: the planner already refused this repository's authorization; no readiness runs and nothing is bound.
170
+ const flagged = rest.checks.some((check) => check.check === "V3");
171
+ const decided = flagged ? undefined : await decideV3(rest, set, authority, options);
172
+ const others = rest.checks.filter((check) => !(decided !== undefined && check.check === "V3"));
173
+ const added = [satisfied("V1"), satisfied("V2"), satisfied("V4"), satisfied("V5"), satisfied("V7")];
174
+ // P5: V9 is satisfied only for a set with no lockfile to regenerate; for any other set it stands as the dry tree left it, and is
175
+ // indeterminate when the dry tree ran no provenance check at all.
176
+ if (derivedLockfile(set) === null)
177
+ added.push(satisfied("V9"));
178
+ else
179
+ added.push({ check: "V9", verdict: "indeterminate", rule: PROVENANCE_NOT_RUN });
180
+ const checks = sortChecks([...others, ...(decided === undefined ? [] : [decided.check]), ...added.filter((check) => !others.some((existing) => existing.check === check.check))]);
181
+ const verdict = worstVerdict(checks.map((check) => check.verdict));
182
+ const binding = decided?.binding;
183
+ const planned = verdict === "satisfied" && binding !== undefined && PRE_APPLY.every((id) => checks.some((check) => check.check === id && check.verdict === "satisfied"));
184
+ repositories.push({ ...rest, verdict, checks, ...(binding === undefined ? {} : { binding }), ...(planned ? { state: "planned" } : {}) });
185
+ }
186
+ const bundle = { ...result.bundle, mode: "planned", repositories };
187
+ if (!validateApplyBundle(bundle).valid)
188
+ throw new Error("the planned bundle does not validate");
189
+ return { bundle, changeSets: result.changeSets };
190
+ }
191
+ //# sourceMappingURL=planned-bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planned-bundle.js","sourceRoot":"","sources":["../src/planned-bundle.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,10 @@ export interface CloudBootstrapReport {
21
21
  * against `directory`. Never mutates anything, never runs `npm ci` itself
22
22
  * -- it checks that the manifest and lockfile are BOTH present, which is
23
23
  * the precondition a real `npm ci` needs, not a substitute for running it.
24
+ * An unsatisfied check is a readiness report, not a failure: a launcher run
25
+ * in the hub writes nothing into a product repository, whose `AGENTS.md`
26
+ * arrives only once it is staffed in an approved plan, with that plan's
27
+ * setup pull request.
24
28
  */
25
29
  export declare function checkCloudSessionBootstrap(host: WorkspaceHost, directory: string): CloudBootstrapReport;
26
30
  //# sourceMappingURL=product-repository.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"product-repository.d.ts","sourceRoot":"","sources":["../src/product-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,kBAAkB,GAAG,gBAAgB,GAAG,YAAY,CAAC;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAOD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,oBAAoB,CAuCvG"}
1
+ {"version":3,"file":"product-repository.d.ts","sourceRoot":"","sources":["../src/product-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,kBAAkB,GAAG,gBAAgB,GAAG,YAAY,CAAC;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAOD;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,oBAAoB,CA4CvG"}
@@ -8,6 +8,10 @@ const HUB_MARKER_REL = join("clossys", ".state", "workspace.json");
8
8
  * against `directory`. Never mutates anything, never runs `npm ci` itself
9
9
  * -- it checks that the manifest and lockfile are BOTH present, which is
10
10
  * the precondition a real `npm ci` needs, not a substitute for running it.
11
+ * An unsatisfied check is a readiness report, not a failure: a launcher run
12
+ * in the hub writes nothing into a product repository, whose `AGENTS.md`
13
+ * arrives only once it is staffed in an approved plan, with that plan's
14
+ * setup pull request.
11
15
  */
12
16
  export function checkCloudSessionBootstrap(host, directory) {
13
17
  const hasManifest = host.exists(join(directory, PACKAGE_JSON_REL));
@@ -34,7 +38,11 @@ export function checkCloudSessionBootstrap(host, directory) {
34
38
  satisfied: agentsPointsAtClossys,
35
39
  ...(agentsPointsAtClossys
36
40
  ? {}
37
- : { note: agentsRaw === null ? "AGENTS.md does not exist at the repository root." : "AGENTS.md exists but does not mention clossys/." }),
41
+ : {
42
+ note: agentsRaw === null
43
+ ? "AGENTS.md does not exist at the repository root yet; a launcher run in the hub writes nothing here, and once this repository is staffed in an approved plan, AGENTS.md arrives with that plan's setup pull request."
44
+ : "AGENTS.md exists but does not mention clossys/.",
45
+ }),
38
46
  };
39
47
  const hasHubMarker = host.exists(join(directory, HUB_MARKER_REL));
40
48
  const markerCheck = {
@@ -1 +1 @@
1
- {"version":3,"file":"product-repository.js","sourceRoot":"","sources":["../src/product-repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAsBjC,MAAM,gBAAgB,GAAG,cAAc,CAAC;AACxC,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAC7C,MAAM,aAAa,GAAG,WAAW,CAAC;AAClC,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;AAEnE;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAAmB,EAAE,SAAiB;IAC/E,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACnE,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAC/D,MAAM,aAAa,GAAwB;QACzC,EAAE,EAAE,kBAAkB;QACtB,KAAK,EAAE,qDAAqD;QAC5D,SAAS,EAAE,WAAW,IAAI,OAAO;QACjC,GAAG,CAAC,WAAW,IAAI,OAAO;YACxB,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;gBACE,IAAI,EAAE,CAAC,WAAW,IAAI,CAAC,OAAO;oBAC5B,CAAC,CAAC,wDAAwD;oBAC1D,CAAC,CAAC,CAAC,WAAW;wBACZ,CAAC,CAAC,qDAAqD;wBACvD,CAAC,CAAC,0EAA0E;aACjF,CAAC;KACP,CAAC;IAEF,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC;IAChE,MAAM,qBAAqB,GAAG,SAAS,KAAK,IAAI,IAAI,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACnF,MAAM,WAAW,GAAwB;QACvC,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,gEAAgE;QACvE,SAAS,EAAE,qBAAqB;QAChC,GAAG,CAAC,qBAAqB;YACvB,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,kDAAkD,CAAC,CAAC,CAAC,iDAAiD,EAAE,CAAC;KAC3I,CAAC;IAEF,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC;IAClE,MAAM,WAAW,GAAwB;QACvC,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,2DAA2D;QAClE,SAAS,EAAE,YAAY;QACvB,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,mDAAmD,EAAE,CAAC;KACvF,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,aAAa,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;IACzD,OAAO,EAAE,aAAa,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;AACvF,CAAC"}
1
+ {"version":3,"file":"product-repository.js","sourceRoot":"","sources":["../src/product-repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAsBjC,MAAM,gBAAgB,GAAG,cAAc,CAAC;AACxC,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAC7C,MAAM,aAAa,GAAG,WAAW,CAAC;AAClC,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;AAEnE;;;;;;;;;GASG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAAmB,EAAE,SAAiB;IAC/E,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACnE,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAC/D,MAAM,aAAa,GAAwB;QACzC,EAAE,EAAE,kBAAkB;QACtB,KAAK,EAAE,qDAAqD;QAC5D,SAAS,EAAE,WAAW,IAAI,OAAO;QACjC,GAAG,CAAC,WAAW,IAAI,OAAO;YACxB,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;gBACE,IAAI,EAAE,CAAC,WAAW,IAAI,CAAC,OAAO;oBAC5B,CAAC,CAAC,wDAAwD;oBAC1D,CAAC,CAAC,CAAC,WAAW;wBACZ,CAAC,CAAC,qDAAqD;wBACvD,CAAC,CAAC,0EAA0E;aACjF,CAAC;KACP,CAAC;IAEF,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC;IAChE,MAAM,qBAAqB,GAAG,SAAS,KAAK,IAAI,IAAI,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACnF,MAAM,WAAW,GAAwB;QACvC,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,gEAAgE;QACvE,SAAS,EAAE,qBAAqB;QAChC,GAAG,CAAC,qBAAqB;YACvB,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;gBACE,IAAI,EACF,SAAS,KAAK,IAAI;oBAChB,CAAC,CAAC,qNAAqN;oBACvN,CAAC,CAAC,iDAAiD;aACxD,CAAC;KACP,CAAC;IAEF,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC;IAClE,MAAM,WAAW,GAAwB;QACvC,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,2DAA2D;QAClE,SAAS,EAAE,YAAY;QACvB,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,mDAAmD,EAAE,CAAC;KACvF,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,aAAa,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;IACzD,OAAO,EAAE,aAAa,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;AACvF,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { ApplyCheck, ChangeSetItem } from "./change-set-contract.js";
2
+ import type { LockfileSpawn } from "./lockfile-regen.js";
3
+ import type { RegistrySnapshot } from "./registry-snapshot.js";
4
+ /** The bin Integrator installs, found under the hub's node_modules/.bin. */
5
+ export declare const PROVENANCE_CHECK_BIN = "integrator-provenance-check";
6
+ /** How long one run of the bin may take. */
7
+ export declare const PROVENANCE_CHECK_TIMEOUT_MS = 120000;
8
+ /** Bytes kept of the bin's stdout; a report at or past this is treated as truncated. */
9
+ export declare const PROVENANCE_CHECK_MAX_BUFFER = 1000000;
10
+ export interface ProvenanceGateInput {
11
+ /** Absolute, real (realpath-equal) directory holding the set's materialized tree. */
12
+ readonly tree: string;
13
+ /** Absolute, real (realpath-equal) hub root, whose node_modules holds Integrator. */
14
+ readonly hubRoot: string;
15
+ /** The change set's items. */
16
+ readonly items: readonly ChangeSetItem[];
17
+ }
18
+ /** Ports. Not reachable from a CLI. */
19
+ export interface ProvenanceGatePorts {
20
+ readonly spawn?: LockfileSpawn;
21
+ }
22
+ /**
23
+ * The snapshot's digest, as docs/contracts/registry-snapshot.json defines it
24
+ * (in the public repository, not shipped in this package): `sha256:` and the
25
+ * SHA-256 of the canonical JSON of `{registry, packages}`, packages sorted by
26
+ * name, each reduced to name, status, latest and versions, versions sorted by
27
+ * version and kept whole. fetchedAt, fetchedBy, responseSha256, schemaVersion
28
+ * and kind are left out. Throws TypeError for a snapshot that does not validate
29
+ * against the contract: an invalid snapshot has no digest. The gate itself
30
+ * does not read a snapshot; this is exported for the plan binding that a later
31
+ * change wires in.
32
+ */
33
+ export declare function registrySnapshotDigest(snapshot: RegistrySnapshot): string;
34
+ /**
35
+ * V9 for one change set. Satisfied without touching anything when the set
36
+ * installs or pins nothing its base lacks. Otherwise runs the hub's
37
+ * `integrator-provenance-check` against `tree` and judges each gated package
38
+ * by exact name and exact version: satisfied only when every one is verified
39
+ * at that version, with no exception for an unverified one. Other `@clossys`
40
+ * packages in the report never gate: an unrelated violated legacy pin passes
41
+ * when every gated package is verified. Indeterminate wins over violated; the
42
+ * result lists each distinct rule once, in code-unit order, and never mixes a
43
+ * satisfied entry with a refusal. Never throws for a bad input; everything it
44
+ * cannot rely on is an indeterminate entry. The input carries no ledger or
45
+ * snapshot: nothing here reads one.
46
+ */
47
+ export declare function checkSetProvenance(input: ProvenanceGateInput, ports?: ProvenanceGatePorts): Promise<readonly ApplyCheck[]>;
48
+ //# sourceMappingURL=provenance-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provenance-gate.d.ts","sourceRoot":"","sources":["../src/provenance-gate.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAI1E,OAAO,KAAK,EAAE,aAAa,EAAuB,MAAM,qBAAqB,CAAC;AAK9E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,gCAAgC,CAAC;AAClE,4CAA4C;AAC5C,eAAO,MAAM,2BAA2B,SAAU,CAAC;AACnD,wFAAwF;AACxF,eAAO,MAAM,2BAA2B,UAAY,CAAC;AAErD,MAAM,WAAW,mBAAmB;IAClC,qFAAqF;IACrF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8BAA8B;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;CAC1C;AAED,uCAAuC;AACvC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAChC;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAOzE;AA4KD;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,GAAE,mBAAwB,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC,CA6EpI"}
@@ -0,0 +1,324 @@
1
+ // Provenance gate for a change set (RFC apply-approved-plan: V9, D20, D23, T12).
2
+ //
3
+ // Every package version a set installs or pins must have registry provenance
4
+ // that Integrator verifies. The hub pins `@clossys/integrator` (D23), so this
5
+ // runs the hub's own `integrator-provenance-check` bin against the
6
+ // materialized tree, reads its JSON report strictly, and turns it into one V9
7
+ // verdict for the set. The bin is found only inside the hub's own
8
+ // node_modules, never through npx or PATH, and runs under the same literal
9
+ // environment allow-list the lockfile step uses (lockfile-tool-env.ts): no
10
+ // parent credential, proxy, CA-trust or NODE_OPTIONS value reaches it.
11
+ //
12
+ // Every ambiguity refuses. A report that does not parse to exactly the shape
13
+ // the bin prints, or that disagrees with its own exit code, is indeterminate,
14
+ // never trusted as far as it goes. A package version the bin does not verify is
15
+ // never passed by any exception: it stays violated or indeterminate exactly as
16
+ // Integrator reports it.
17
+ //
18
+ // D20's first-identity-publication exception is deliberately not implemented.
19
+ // It needs evidence that a version is a package's first publication, and the
20
+ // registry snapshot cannot give it: a snapshot taken to resolve a plan records
21
+ // only the one version `latest` names, whatever else the package has published,
22
+ // and Integrator reports "attestation failed" (violated) the same way as "no
23
+ // attestation". Any condition built on the snapshot would therefore also pass a
24
+ // package with earlier releases whose latest release fails verification. Until
25
+ // the snapshot contract records evidence of a first publication, an unattested
26
+ // first publication blocks the apply: fail closed. Nothing here fetches,
27
+ // repairs or retries.
28
+ //
29
+ // The RFC and the snapshot contract are in the public repository, not shipped
30
+ // in this package: docs/rfcs/apply-approved-plan.md and
31
+ // docs/contracts/registry-snapshot.json.
32
+ import { lstat, realpath, stat } from "node:fs/promises";
33
+ import { delimiter, dirname, isAbsolute, join, relative, sep } from "node:path";
34
+ import { compareCodeUnits } from "./change-set-contract.js";
35
+ import { readContractDocument } from "./generated/contract-schema.generated.js";
36
+ import { PACKAGE_SCOPE } from "./generated/package-scope.generated.js";
37
+ import { spawnLockfileTool } from "./lockfile-regen.js";
38
+ import { lockfileToolEnv, prepareLockfileScratch } from "./lockfile-tool-env.js";
39
+ import { canonicalDigest } from "./plan-digest.js";
40
+ import { canonicalRegistrySnapshot, registrySnapshotViolations } from "./registry-snapshot.js";
41
+ /** The bin Integrator installs, found under the hub's node_modules/.bin. */
42
+ export const PROVENANCE_CHECK_BIN = "integrator-provenance-check";
43
+ /** How long one run of the bin may take. */
44
+ export const PROVENANCE_CHECK_TIMEOUT_MS = 120_000;
45
+ /** Bytes kept of the bin's stdout; a report at or past this is treated as truncated. */
46
+ export const PROVENANCE_CHECK_MAX_BUFFER = 1_000_000;
47
+ // ---------------------------------------------------------------------------
48
+ // The snapshot digest
49
+ // ---------------------------------------------------------------------------
50
+ /**
51
+ * The snapshot's digest, as docs/contracts/registry-snapshot.json defines it
52
+ * (in the public repository, not shipped in this package): `sha256:` and the
53
+ * SHA-256 of the canonical JSON of `{registry, packages}`, packages sorted by
54
+ * name, each reduced to name, status, latest and versions, versions sorted by
55
+ * version and kept whole. fetchedAt, fetchedBy, responseSha256, schemaVersion
56
+ * and kind are left out. Throws TypeError for a snapshot that does not validate
57
+ * against the contract: an invalid snapshot has no digest. The gate itself
58
+ * does not read a snapshot; this is exported for the plan binding that a later
59
+ * change wires in.
60
+ */
61
+ export function registrySnapshotDigest(snapshot) {
62
+ if (registrySnapshotViolations(snapshot).length > 0)
63
+ throw new TypeError("a snapshot that does not validate against the registry snapshot contract has no digest");
64
+ const canonical = canonicalRegistrySnapshot(snapshot);
65
+ return canonicalDigest({
66
+ registry: canonical.registry,
67
+ packages: canonical.packages.map((entry) => ({ name: entry.name, status: entry.status, latest: entry.latest, versions: entry.versions })),
68
+ });
69
+ }
70
+ // ---------------------------------------------------------------------------
71
+ // Verdict plumbing
72
+ // ---------------------------------------------------------------------------
73
+ const indeterminate = (rule) => ({ check: "V9", verdict: "indeterminate", rule });
74
+ const violated = (rule) => ({ check: "V9", verdict: "violated", rule });
75
+ /** The rules behind a failed V9, one entry per distinct rule, in code-unit order. */
76
+ function distinctRules(verdict, rules) {
77
+ return [...rules].sort(compareCodeUnits).map((rule) => (verdict === "indeterminate" ? indeterminate(rule) : violated(rule)));
78
+ }
79
+ /**
80
+ * The packages this set installs or pins that its base does not already hold,
81
+ * by exact name, each once. Only a `satisfiedInBase` of exactly `true` skips an
82
+ * item: a value other than `true` (`false`, missing, `null`, a string or a
83
+ * number) is gated. Undefined when one name is gated at two versions: the set
84
+ * then asks for something no single report can answer.
85
+ */
86
+ function gatedPackages(items) {
87
+ const versions = new Map();
88
+ for (const item of items) {
89
+ if ((item.act !== "install" && item.act !== "pin-starter") || item.satisfiedInBase === true)
90
+ continue;
91
+ const earlier = versions.get(item.package.name);
92
+ if (earlier !== undefined && earlier !== item.package.version)
93
+ return undefined;
94
+ versions.set(item.package.name, item.package.version);
95
+ }
96
+ return [...versions].map(([name, version]) => ({ name, version }));
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // Roots and the bin
100
+ // ---------------------------------------------------------------------------
101
+ /** An absolute path that is its own realpath and names a directory. */
102
+ async function isCanonicalDirectory(path) {
103
+ if (typeof path !== "string" || !isAbsolute(path))
104
+ return false;
105
+ try {
106
+ if ((await realpath(path)) !== path)
107
+ return false;
108
+ return (await stat(path)).isDirectory();
109
+ }
110
+ catch {
111
+ return false;
112
+ }
113
+ }
114
+ /** Whether `child` lies strictly inside `parent`; both are already real paths. */
115
+ function isStrictlyInside(parent, child) {
116
+ const path = relative(parent, child);
117
+ return path !== "" && path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path);
118
+ }
119
+ /**
120
+ * The verified real path of the hub's Integrator bin, or undefined. The
121
+ * `.bin` entry must resolve to a regular file strictly inside the real path of
122
+ * `node_modules/@clossys/integrator`, and that directory's real path must
123
+ * itself lie strictly inside `<hubRoot>/node_modules`, so a link that leaves
124
+ * the hub, leaves node_modules, or points at some other package's file is
125
+ * refused. `hubRoot` is already its own realpath.
126
+ *
127
+ * The caller spawns the returned real path, not the `.bin` entry: the launch
128
+ * then goes to the very file that was checked, and a link swapped in after the
129
+ * check cannot redirect it.
130
+ */
131
+ async function verifiedBin(hubRoot) {
132
+ const nodeModules = join(hubRoot, "node_modules");
133
+ try {
134
+ const realBin = await realpath(join(nodeModules, ".bin", PROVENANCE_CHECK_BIN));
135
+ const realIntegrator = await realpath(join(nodeModules, "@clossys", "integrator"));
136
+ if (!(await lstat(realBin)).isFile() || !(await lstat(realIntegrator)).isDirectory())
137
+ return undefined;
138
+ if (!isStrictlyInside(nodeModules, realIntegrator) || !isStrictlyInside(realIntegrator, realBin))
139
+ return undefined;
140
+ return realBin;
141
+ }
142
+ catch {
143
+ return undefined;
144
+ }
145
+ }
146
+ const REPORT_KEYS = new Set(["state", "registryBaseUrl", "packages"]);
147
+ const PACKAGE_KEYS = new Set(["name", "installedVersion", "latestVersion", "currencyDistance", "state", "reasons"]);
148
+ const isPlainObject = (value) => {
149
+ if (typeof value !== "object" || value === null || Array.isArray(value))
150
+ return false;
151
+ const prototype = Object.getPrototypeOf(value);
152
+ return prototype === Object.prototype || prototype === null;
153
+ };
154
+ const isState = (value) => value === "verified" || value === "violated" || value === "indeterminate";
155
+ const isString = (value) => typeof value === "string";
156
+ /** Whether `value` is an own-keyed plain object whose keys all come from `allowed`. */
157
+ function hasOnlyKeys(value, allowed) {
158
+ return Object.keys(value).every((key) => allowed.has(key) && Object.hasOwn(value, key));
159
+ }
160
+ /**
161
+ * Reads the bin's stdout as the closed report it prints, or undefined. Output
162
+ * at or past the capture limit is refused as truncated. The text is read with
163
+ * the same strict reader the contracts use, so a repeated key or a byte order
164
+ * mark is refused as well as anything JSON.parse refuses; no key outside the
165
+ * closed sets is accepted, and each field must have the type the bin gives it.
166
+ * The report's reasons are checked for shape and never used.
167
+ */
168
+ function readReport(stdout) {
169
+ if (Buffer.byteLength(stdout, "utf8") >= PROVENANCE_CHECK_MAX_BUFFER)
170
+ return undefined;
171
+ let value;
172
+ try {
173
+ value = readContractDocument(Buffer.from(stdout, "utf8"));
174
+ }
175
+ catch {
176
+ return undefined;
177
+ }
178
+ if (!isPlainObject(value) || !hasOnlyKeys(value, REPORT_KEYS) || Object.keys(value).length !== REPORT_KEYS.size)
179
+ return undefined;
180
+ const { state, registryBaseUrl, packages } = value;
181
+ if (!isState(state) || registryBaseUrl !== PACKAGE_SCOPE.registry || !Array.isArray(packages))
182
+ return undefined;
183
+ const reported = [];
184
+ for (const entry of packages) {
185
+ if (!isPlainObject(entry) || !hasOnlyKeys(entry, PACKAGE_KEYS))
186
+ return undefined;
187
+ const { name, installedVersion, latestVersion, currencyDistance, state: packageState, reasons } = entry;
188
+ if (!isString(name) || !isString(installedVersion) || !isState(packageState))
189
+ return undefined;
190
+ if (!Array.isArray(reasons) || !reasons.every(isString))
191
+ return undefined;
192
+ if (Object.hasOwn(entry, "latestVersion") && !isString(latestVersion))
193
+ return undefined;
194
+ if (Object.hasOwn(entry, "currencyDistance") && !isString(currencyDistance))
195
+ return undefined;
196
+ reported.push({ name, installedVersion, state: packageState });
197
+ }
198
+ return { state, packages: reported };
199
+ }
200
+ /** Whether the report says what its exit code says: exit 0 all verified, exit 1 a violation and nothing indeterminate. */
201
+ function agreesWithExit(status, report) {
202
+ if (status === 0)
203
+ return report.state === "verified" && report.packages.length > 0 && report.packages.every((entry) => entry.state === "verified");
204
+ return report.state === "violated" && report.packages.some((entry) => entry.state === "violated") && report.packages.every((entry) => entry.state !== "indeterminate");
205
+ }
206
+ /** The rule an unsuccessful launch maps to, or undefined when the bin ran to exit 0 or 1 with no failure flag. */
207
+ function launchFailure(result) {
208
+ if (result.failure === "not-found")
209
+ return "engine-missing-bin";
210
+ if (result.failure === "timeout")
211
+ return "engine-timeout";
212
+ if (result.failure !== undefined)
213
+ return "engine-failed";
214
+ if (result.status === 2)
215
+ return "engine-indeterminate";
216
+ if (result.status !== 0 && result.status !== 1)
217
+ return "engine-failed";
218
+ return undefined;
219
+ }
220
+ // ---------------------------------------------------------------------------
221
+ // The gate
222
+ // ---------------------------------------------------------------------------
223
+ /**
224
+ * V9 for one change set. Satisfied without touching anything when the set
225
+ * installs or pins nothing its base lacks. Otherwise runs the hub's
226
+ * `integrator-provenance-check` against `tree` and judges each gated package
227
+ * by exact name and exact version: satisfied only when every one is verified
228
+ * at that version, with no exception for an unverified one. Other `@clossys`
229
+ * packages in the report never gate: an unrelated violated legacy pin passes
230
+ * when every gated package is verified. Indeterminate wins over violated; the
231
+ * result lists each distinct rule once, in code-unit order, and never mixes a
232
+ * satisfied entry with a refusal. Never throws for a bad input; everything it
233
+ * cannot rely on is an indeterminate entry. The input carries no ledger or
234
+ * snapshot: nothing here reads one.
235
+ */
236
+ export async function checkSetProvenance(input, ports = {}) {
237
+ // 1. The gated set.
238
+ const gated = gatedPackages(input.items);
239
+ if (gated === undefined)
240
+ return [indeterminate("gated-package-conflict")];
241
+ if (gated.length === 0)
242
+ return [{ check: "V9", verdict: "satisfied" }];
243
+ // 2 and 3. Roots, then the bin.
244
+ if (!(await isCanonicalDirectory(input.tree)) || !(await isCanonicalDirectory(input.hubRoot)))
245
+ return [indeterminate("root-not-canonical")];
246
+ const command = await verifiedBin(input.hubRoot);
247
+ if (command === undefined)
248
+ return [indeterminate("engine-missing-bin")];
249
+ // 4. Launch, in a scratch working directory, under the allow-list environment.
250
+ const run = ports.spawn ?? spawnLockfileTool;
251
+ let scratch;
252
+ try {
253
+ scratch = await prepareLockfileScratch();
254
+ }
255
+ catch {
256
+ return [indeterminate("engine-failed")];
257
+ }
258
+ let result;
259
+ try {
260
+ const env = lockfileToolEnv({ tool: "npm", scratch: scratch.path, registry: PACKAGE_SCOPE.registry, corepack: false, parent: process.env });
261
+ // The bin's `#!/usr/bin/env node` must find the node that is running this code.
262
+ env.PATH = `${dirname(process.execPath)}${delimiter}${env.PATH ?? ""}`;
263
+ result = await run({
264
+ command,
265
+ args: ["--cwd", input.tree, "--registry", PACKAGE_SCOPE.registry],
266
+ cwd: scratch.path,
267
+ env,
268
+ timeoutMs: PROVENANCE_CHECK_TIMEOUT_MS,
269
+ maxBuffer: PROVENANCE_CHECK_MAX_BUFFER,
270
+ });
271
+ }
272
+ catch {
273
+ result = undefined;
274
+ }
275
+ finally {
276
+ try {
277
+ await scratch.remove();
278
+ }
279
+ catch {
280
+ // The verdict does not depend on the scratch's removal, and it is under the OS temp root.
281
+ }
282
+ }
283
+ if (result === undefined)
284
+ return [indeterminate("engine-failed")];
285
+ const failure = launchFailure(result);
286
+ if (failure !== undefined)
287
+ return [indeterminate(failure)];
288
+ // 5 and 6. The report, and whether it agrees with its exit code.
289
+ const report = typeof result.stdout === "string" ? readReport(result.stdout) : undefined;
290
+ if (report === undefined)
291
+ return [indeterminate("report-unreadable")];
292
+ if (!agreesWithExit(result.status, report))
293
+ return [indeterminate("report-contradicts-exit")];
294
+ // 7 and 8. Each gated package, by exact name and exact version.
295
+ const indeterminateRules = new Set();
296
+ const violatedRules = new Set();
297
+ for (const pkg of gated) {
298
+ const entries = report.packages.filter((entry) => entry.name === pkg.name);
299
+ if (entries.length === 0) {
300
+ indeterminateRules.add("package-not-in-report");
301
+ continue;
302
+ }
303
+ if (entries.some((entry) => entry.installedVersion !== pkg.version)) {
304
+ violatedRules.add("version-mismatch");
305
+ continue;
306
+ }
307
+ // The report already agrees with its exit code, so no entry is indeterminate; this is the same refusal kept local.
308
+ if (entries.some((entry) => entry.state === "indeterminate")) {
309
+ indeterminateRules.add("package-indeterminate");
310
+ continue;
311
+ }
312
+ if (entries.every((entry) => entry.state === "verified"))
313
+ continue;
314
+ // Unverified at the act's own version: violated, with no exception.
315
+ violatedRules.add("provenance-unverified");
316
+ }
317
+ // 9. Indeterminate wins over violated; a satisfied entry never sits beside a refusal.
318
+ if (indeterminateRules.size > 0)
319
+ return distinctRules("indeterminate", indeterminateRules);
320
+ if (violatedRules.size > 0)
321
+ return distinctRules("violated", violatedRules);
322
+ return [{ check: "V9", verdict: "satisfied" }];
323
+ }
324
+ //# sourceMappingURL=provenance-gate.js.map