@unbrained/pm-cli 2026.8.10 → 2026.8.12

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 (243) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +153 -100
  3. package/dist/cli/commander-usage.js +113 -15
  4. package/dist/cli/error-guidance.d.ts +12 -0
  5. package/dist/cli/error-guidance.js +147 -29
  6. package/dist/cli/main.js +38 -19
  7. package/dist/cli/register-annotations.js +56 -3
  8. package/dist/cli/register-assurance.d.ts +8 -0
  9. package/dist/cli/register-assurance.js +52 -0
  10. package/dist/cli/register-files-lookup.js +14 -3
  11. package/dist/cli/register-list-query.js +10 -7
  12. package/dist/cli/register-mutation.js +30 -9
  13. package/dist/cli/register-operations.js +30 -15
  14. package/dist/cli-bundle/bundle-manifest.json +155 -155
  15. package/dist/cli-bundle/chunks/{chunk-TRWXF476.js → chunk-3PS7X6GS.js} +2 -2
  16. package/dist/cli-bundle/chunks/chunk-7KL6KMWF.js +13 -0
  17. package/dist/cli-bundle/chunks/{chunk-EBO7OUUE.js → chunk-BM5YJ2UW.js} +69 -55
  18. package/dist/cli-bundle/chunks/chunk-D55A5AEN.js +33 -0
  19. package/dist/cli-bundle/chunks/chunk-KIKDWECH.js +196 -0
  20. package/dist/cli-bundle/chunks/{chunk-ETML5C6H.js → chunk-KOQMGCTA.js} +47 -47
  21. package/dist/cli-bundle/chunks/chunk-R76342NV.js +2 -0
  22. package/dist/cli-bundle/chunks/chunk-RBTK5EZV.js +5 -0
  23. package/dist/cli-bundle/chunks/chunk-VLUQOIDT.js +2 -0
  24. package/dist/cli-bundle/chunks/chunk-WLAHPSOX.js +3 -0
  25. package/dist/cli-bundle/chunks/register-list-query-XPCJRUUD.js +10 -0
  26. package/dist/cli-bundle/chunks/register-mutation-4RJCBKRS.js +20 -0
  27. package/dist/cli-bundle/chunks/register-operations-2OGISOVR.js +2 -0
  28. package/dist/cli-bundle/chunks/{register-setup-S3KT2LTH.js → register-setup-LLHAKP2V.js} +2 -2
  29. package/dist/cli-bundle/focused-chunks/chunk-2DALMVF7.js +5 -0
  30. package/dist/cli-bundle/focused-chunks/chunk-5OQIMELN.js +155 -0
  31. package/dist/cli-bundle/focused-chunks/chunk-5TAGTDRE.js +2 -0
  32. package/dist/cli-bundle/focused-chunks/chunk-ECADZMVC.js +18 -0
  33. package/dist/cli-bundle/focused-chunks/chunk-EJGXRA6B.js +2 -0
  34. package/dist/cli-bundle/focused-chunks/chunk-FFGSZCPK.js +16 -0
  35. package/dist/cli-bundle/focused-chunks/{chunk-IW43E4XL.js → chunk-GQW4BPUT.js} +2 -2
  36. package/dist/cli-bundle/focused-chunks/chunk-ISKOBJCM.js +2 -0
  37. package/dist/cli-bundle/focused-chunks/{chunk-75774JWP.js → chunk-JAFRIWVF.js} +2 -2
  38. package/dist/cli-bundle/focused-chunks/{chunk-2COZ67XT.js → chunk-KHM2CS2C.js} +2 -2
  39. package/dist/cli-bundle/focused-chunks/{chunk-M6RPIXKC.js → chunk-KPSXVBDV.js} +2 -2
  40. package/dist/cli-bundle/focused-chunks/{chunk-34SX654S.js → chunk-KTLOKKCQ.js} +2 -2
  41. package/dist/cli-bundle/focused-chunks/chunk-LGYK3NUF.js +8 -0
  42. package/dist/cli-bundle/focused-chunks/{chunk-2EXI2DKR.js → chunk-NBLUKITQ.js} +2 -2
  43. package/dist/cli-bundle/focused-chunks/chunk-NYLOUPOB.js +2 -0
  44. package/dist/cli-bundle/focused-chunks/chunk-Q4T463SD.js +31 -0
  45. package/dist/cli-bundle/focused-chunks/chunk-QVZVY53N.js +2 -0
  46. package/dist/cli-bundle/focused-chunks/{chunk-DZGAHZZV.js → chunk-RWPAWUOT.js} +2 -2
  47. package/dist/cli-bundle/focused-chunks/chunk-UTEWXDEQ.js +17 -0
  48. package/dist/cli-bundle/focused-chunks/{chunk-7UN6BHHO.js → chunk-YIAXMC3T.js} +2 -2
  49. package/dist/cli-bundle/focused-chunks/chunk-ZMGWAYH7.js +2 -0
  50. package/dist/cli-bundle/main.js +13 -13
  51. package/dist/cli-bundle/sdk-authoring.js +1 -1
  52. package/dist/cli-bundle/sdk-contracts.js +1 -1
  53. package/dist/cli-bundle/sdk-core.js +41 -39
  54. package/dist/cli-bundle/sdk-governance.js +1 -1
  55. package/dist/cli-bundle/sdk-graph.js +1 -1
  56. package/dist/cli-bundle/sdk-merge.js +1 -1
  57. package/dist/cli-bundle/sdk-query.js +1 -1
  58. package/dist/cli-bundle/sdk-runtime.js +1 -1
  59. package/dist/cli-bundle/sdk-testing.js +1 -1
  60. package/dist/cli-bundle/sdk.js +2 -2
  61. package/dist/core/extensions/reserved-host-flags.js +3 -2
  62. package/dist/core/history/drift-scan.js +13 -8
  63. package/dist/core/history/history.d.ts +8 -0
  64. package/dist/core/history/history.js +57 -7
  65. package/dist/core/history/replay.d.ts +9 -1
  66. package/dist/core/history/replay.js +48 -13
  67. package/dist/core/output/output.d.ts +2 -0
  68. package/dist/core/output/output.js +46 -14
  69. package/dist/core/session/session-state.d.ts +47 -0
  70. package/dist/core/session/session-state.js +207 -6
  71. package/dist/core/shared/author.d.ts +7 -1
  72. package/dist/core/shared/author.js +66 -46
  73. package/dist/core/shared/command-types.d.ts +2 -0
  74. package/dist/core/shared/command-types.js +2 -2
  75. package/dist/core/shared/errors.d.ts +13 -0
  76. package/dist/core/shared/errors.js +2 -2
  77. package/dist/core/store/item-metadata-cache.d.ts +1 -1
  78. package/dist/core/store/item-metadata-cache.js +14 -7
  79. package/dist/sdk/agent/refusal-reachability.d.ts +40 -0
  80. package/dist/sdk/agent/refusal-reachability.js +72 -0
  81. package/dist/sdk/agent/subcommand-recovery.d.ts +40 -0
  82. package/dist/sdk/agent/subcommand-recovery.js +84 -0
  83. package/dist/sdk/cli-bootstrap.d.ts +10 -0
  84. package/dist/sdk/cli-bootstrap.js +11 -7
  85. package/dist/sdk/cli-contracts/completeness.js +14 -3
  86. package/dist/sdk/cli-contracts/enum-contracts.d.ts +1 -1
  87. package/dist/sdk/cli-contracts/enum-contracts.js +3 -2
  88. package/dist/sdk/cli-contracts/flag-contracts.d.ts +2 -0
  89. package/dist/sdk/cli-contracts/flag-contracts.js +20 -2
  90. package/dist/sdk/cli-contracts/registration-helpers.d.ts +20 -3
  91. package/dist/sdk/cli-contracts/registration-helpers.js +18 -12
  92. package/dist/sdk/cli-contracts/runtime-contracts.js +4 -3
  93. package/dist/sdk/cli-contracts/schemas/assurance-definition.d.ts +515 -0
  94. package/dist/sdk/cli-contracts/schemas/assurance-definition.js +252 -0
  95. package/dist/sdk/cli-contracts/tool-option-contracts.js +3 -2
  96. package/dist/sdk/cli-contracts/tool-parameter-tables.js +99 -2
  97. package/dist/sdk/cli-contracts/tool-schema.d.ts +3 -3
  98. package/dist/sdk/cli-contracts/tool-schema.js +38 -5
  99. package/dist/sdk/cli-contracts.d.ts +1 -1
  100. package/dist/sdk/cli-contracts.js +3 -3
  101. package/dist/sdk/cli-program.js +3 -2
  102. package/dist/sdk/completion.js +23 -8
  103. package/dist/sdk/config.js +9 -3
  104. package/dist/sdk/context/semantic-session-attribution.d.ts +32 -0
  105. package/dist/sdk/context/semantic-session-attribution.js +83 -0
  106. package/dist/sdk/context-intent-contracts.js +45 -4
  107. package/dist/sdk/core-governance.d.ts +3 -1
  108. package/dist/sdk/core-governance.js +5 -3
  109. package/dist/sdk/core.d.ts +2 -1
  110. package/dist/sdk/core.js +3 -2
  111. package/dist/sdk/error-code-catalog.d.ts +13 -0
  112. package/dist/sdk/error-code-catalog.js +35 -2
  113. package/dist/sdk/extension.js +18 -17
  114. package/dist/sdk/files.d.ts +11 -0
  115. package/dist/sdk/files.js +82 -24
  116. package/dist/sdk/generated/generated-error-code-catalog-part-1.d.ts +8 -0
  117. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1585 -0
  118. package/dist/sdk/generated/generated-error-code-catalog-part-2.d.ts +8 -0
  119. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1608 -0
  120. package/dist/sdk/generated-error-code-catalog.js +6 -3121
  121. package/dist/sdk/governance/assurance-action-contracts.d.ts +7 -0
  122. package/dist/sdk/governance/assurance-action-contracts.js +5 -0
  123. package/dist/sdk/governance/assurance-action.d.ts +70 -0
  124. package/dist/sdk/governance/assurance-action.js +200 -0
  125. package/dist/sdk/governance/assurance-limits.d.ts +7 -0
  126. package/dist/sdk/governance/assurance-limits.js +11 -0
  127. package/dist/sdk/governance/assurance-mutation-error.d.ts +7 -0
  128. package/dist/sdk/governance/assurance-mutation-error.js +35 -0
  129. package/dist/sdk/governance/assurance-runtime.d.ts +20 -0
  130. package/dist/sdk/governance/assurance-runtime.js +134 -0
  131. package/dist/sdk/governance/assurance.d.ts +438 -0
  132. package/dist/sdk/governance/assurance.js +804 -0
  133. package/dist/sdk/governance/health.d.ts +3 -1
  134. package/dist/sdk/governance/health.js +21 -4
  135. package/dist/sdk/governance/validate-item-reader.js +3 -3
  136. package/dist/sdk/governance/validate.d.ts +2 -0
  137. package/dist/sdk/governance/validate.js +13 -3
  138. package/dist/sdk/graph/durable-cache.d.ts +1 -1
  139. package/dist/sdk/graph/durable-cache.js +98 -29
  140. package/dist/sdk/graph/governance.d.ts +14 -0
  141. package/dist/sdk/graph/governance.js +34 -9
  142. package/dist/sdk/graph/run.js +8 -3
  143. package/dist/sdk/index.d.ts +7 -2
  144. package/dist/sdk/index.js +9 -4
  145. package/dist/sdk/lifecycle/claim.js +26 -4
  146. package/dist/sdk/lifecycle/focus.js +13 -3
  147. package/dist/sdk/lifecycle/plan.js +8 -3
  148. package/dist/sdk/merge/driver.d.ts +2 -2
  149. package/dist/sdk/merge/driver.js +3 -3
  150. package/dist/sdk/merge/receipts.d.ts +6 -4
  151. package/dist/sdk/merge/receipts.js +7 -5
  152. package/dist/sdk/merge/three-way.d.ts +2 -2
  153. package/dist/sdk/merge/three-way.js +3 -3
  154. package/dist/sdk/output-projection.d.ts +9 -1
  155. package/dist/sdk/output-projection.js +45 -9
  156. package/dist/sdk/provenance.d.ts +38 -0
  157. package/dist/sdk/provenance.js +64 -2
  158. package/dist/sdk/query/activity.d.ts +49 -2
  159. package/dist/sdk/query/activity.js +183 -37
  160. package/dist/sdk/query/context.js +19 -8
  161. package/dist/sdk/query/get.js +19 -2
  162. package/dist/sdk/read-output-contracts.d.ts +5 -1
  163. package/dist/sdk/read-output-contracts.js +17 -2
  164. package/dist/sdk/read-output-session.d.ts +2 -0
  165. package/dist/sdk/read-output-session.js +12 -5
  166. package/dist/sdk/runtime-extended-actions.js +22 -5
  167. package/dist/sdk/runtime-primitives.d.ts +5 -3
  168. package/dist/sdk/runtime-primitives.js +7 -5
  169. package/dist/sdk/runtime-stats-options.js +3 -2
  170. package/dist/sdk/runtime.d.ts +4 -0
  171. package/dist/sdk/runtime.js +34 -34
  172. package/dist/sdk/stats.d.ts +36 -3
  173. package/dist/sdk/stats.js +55 -15
  174. package/dist/sdk/telemetry.js +7 -4
  175. package/dist/sdk/test/execution.d.ts +4 -0
  176. package/dist/sdk/test/execution.js +86 -21
  177. package/dist/sdk/traceability/runtime-files-lookup.d.ts +8 -0
  178. package/dist/sdk/traceability/runtime-files-lookup.js +33 -0
  179. package/dist/sdk/traceability/source-traceability.d.ts +102 -0
  180. package/dist/sdk/traceability/source-traceability.js +293 -0
  181. package/dist/types.d.ts +10 -2
  182. package/dist/types.js +2 -2
  183. package/docs/ASSURANCE.md +137 -0
  184. package/docs/COMMANDS.md +29 -8
  185. package/docs/CONFIGURATION.md +7 -0
  186. package/docs/DEPENDENCY_KIND_CONTRACT.md +10 -8
  187. package/docs/MERGE_SAFETY.md +3 -1
  188. package/docs/OUTPUT_PROJECTION_CONTRACTS.md +26 -13
  189. package/docs/README.md +3 -0
  190. package/docs/READ_OUTPUT_CONTRACTS.md +17 -2
  191. package/docs/RELEASING.md +9 -5
  192. package/docs/SDK.md +12 -1
  193. package/docs/SDK_AGENT_SESSION_CONTEXT.md +52 -5
  194. package/docs/SDK_CONTEXT_EVIDENCE_CONTRACTS.md +53 -0
  195. package/docs/SDK_CONTEXT_INTEGRITY.md +28 -1
  196. package/docs/SDK_EVIDENCE_TRACEABILITY.md +39 -1
  197. package/docs/SDK_RUNTIME_BOUNDARIES.md +36 -1
  198. package/docs/TESTING.md +12 -9
  199. package/docs/TRUSTWORTHY_CONTEXT_EVIDENCE.md +106 -0
  200. package/docs/generated/AGENT_COMMAND_SURFACE.md +1 -0
  201. package/docs/performance/sdk-entrypoint-import-costs.md +6 -2
  202. package/marketplace.json +2 -2
  203. package/package.json +7 -7
  204. package/packages/pm-beads/package.json +1 -1
  205. package/packages/pm-calendar/package.json +1 -1
  206. package/packages/pm-command-kit/package.json +1 -1
  207. package/packages/pm-digital-twin/package.json +1 -1
  208. package/packages/pm-governance-audit/package.json +1 -1
  209. package/packages/pm-guide-shell/package.json +1 -1
  210. package/packages/pm-kanban/package.json +1 -1
  211. package/packages/pm-lifecycle-hooks/package.json +1 -1
  212. package/packages/pm-linked-test-adapters/package.json +1 -1
  213. package/packages/pm-search-advanced/package.json +1 -1
  214. package/packages/pm-templates/extensions/templates/index.ts +9 -3
  215. package/packages/pm-templates/package.json +1 -1
  216. package/packages/pm-todos/package.json +1 -1
  217. package/packages/pm-vcs/package.json +1 -1
  218. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  219. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  220. package/sdk/public-surface.json +1030 -67
  221. package/dist/cli-bundle/chunks/chunk-2QYH4QMI.js +0 -30
  222. package/dist/cli-bundle/chunks/chunk-JUDU4RO4.js +0 -194
  223. package/dist/cli-bundle/chunks/chunk-JZSNADRR.js +0 -5
  224. package/dist/cli-bundle/chunks/chunk-MENWVGJW.js +0 -2
  225. package/dist/cli-bundle/chunks/chunk-S4EITGLA.js +0 -3
  226. package/dist/cli-bundle/chunks/chunk-TIGKWVLA.js +0 -13
  227. package/dist/cli-bundle/chunks/chunk-TSLDTJLQ.js +0 -2
  228. package/dist/cli-bundle/chunks/register-list-query-OJ6ZXKV4.js +0 -10
  229. package/dist/cli-bundle/chunks/register-mutation-DKI7QC7O.js +0 -20
  230. package/dist/cli-bundle/chunks/register-operations-P5OGMT6I.js +0 -2
  231. package/dist/cli-bundle/focused-chunks/chunk-36FTZ2NT.js +0 -2
  232. package/dist/cli-bundle/focused-chunks/chunk-3CQECMKR.js +0 -16
  233. package/dist/cli-bundle/focused-chunks/chunk-CHIBMEZR.js +0 -8
  234. package/dist/cli-bundle/focused-chunks/chunk-FF4MNAYB.js +0 -153
  235. package/dist/cli-bundle/focused-chunks/chunk-GHTN2ILR.js +0 -2
  236. package/dist/cli-bundle/focused-chunks/chunk-I4YM2I7S.js +0 -2
  237. package/dist/cli-bundle/focused-chunks/chunk-IMDTEN7K.js +0 -2
  238. package/dist/cli-bundle/focused-chunks/chunk-JMVA4HKR.js +0 -5
  239. package/dist/cli-bundle/focused-chunks/chunk-KR5RVXLQ.js +0 -31
  240. package/dist/cli-bundle/focused-chunks/chunk-KSNJG4KI.js +0 -16
  241. package/dist/cli-bundle/focused-chunks/chunk-MXYSIQ56.js +0 -18
  242. package/dist/cli-bundle/focused-chunks/chunk-RIIN5V4N.js +0 -2
  243. package/dist/cli-bundle/focused-chunks/chunk-RKWSCYYN.js +0 -2
@@ -0,0 +1,804 @@
1
+ /**
2
+ * @module sdk/assurance
3
+ *
4
+ * Declares, persists, evaluates, and audits project assurance contracts. The
5
+ * module deliberately owns semantics while hosts inject workspace data and
6
+ * external measurement adapters, keeping CLI, MCP, CI, and embedded callers on
7
+ * one deterministic SDK path.
8
+ */
9
+
10
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ecc8eb89-b9e9-51fa-8573-2afb8ec801be")}catch(e){}}();
11
+ import { createHash } from "node:crypto";
12
+ import path from "node:path";
13
+ import { EXIT_CODE } from "../../core/shared/constants.js";
14
+ import { PmCliError } from "../../core/shared/errors.js";
15
+ import { appendWorkspaceAuditEvent, mutateWorkspaceJsonWithHistory, readFileIfExists, readHistoryEntries, readSettings, resolveAuthor, stableStringify, WORKSPACE_HISTORY_ID, getWorkspaceHistoryPath, } from "../runtime-primitives.js";
16
+ import { resolveCanonicalRelationshipKind } from "../relationships.js";
17
+ import { MAX_ASSURANCE_VERDICT_LIMIT } from "./assurance-limits.js";
18
+ import { AssuranceMutationRefusalError } from "./assurance-mutation-error.js";
19
+ /** Current serialized assurance registry format. */
20
+ export const ASSURANCE_DOCUMENT_VERSION = 1;
21
+ /** Supported lifecycle points that can evaluate a gate. */
22
+ export const ASSURANCE_GATE_TRIGGERS = [
23
+ "pre-commit",
24
+ "pre-push",
25
+ "pre-merge",
26
+ "ci",
27
+ "pre-release",
28
+ "post-release",
29
+ "scheduled",
30
+ "on-claim",
31
+ "on-close",
32
+ ];
33
+ const POLARITY_KEYS = [
34
+ "ceiling",
35
+ "floor",
36
+ "equals",
37
+ "zero",
38
+ "monotone_nondecreasing",
39
+ "monotone_nonincreasing",
40
+ "subset_of",
41
+ ];
42
+ const ID_PATTERN = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;
43
+ const LOCK_TTL_SECONDS = 30;
44
+ const LOCK_WAIT_MS = 5_000;
45
+ const EVALUATION_CONCURRENCY = 4;
46
+ const DEFAULT_VERDICT_LIMIT = 50;
47
+ function requireStableId(value, field) {
48
+ if (!ID_PATTERN.test(value)) {
49
+ throw new AssuranceMutationRefusalError(`${field} must be a stable lowercase id`);
50
+ }
51
+ }
52
+ function requireFiniteNonNegative(value, field) {
53
+ if (!Number.isFinite(value) || value < 0) {
54
+ throw new AssuranceMutationRefusalError(`${field} must be a finite non-negative number`);
55
+ }
56
+ }
57
+ function boundFor(assertion) {
58
+ const present = POLARITY_KEYS.filter((key) => assertion[key] !== undefined);
59
+ if (present.length !== 1) {
60
+ throw new AssuranceMutationRefusalError(`assertion ${assertion.id} must declare exactly one polarity`);
61
+ }
62
+ const polarity = present[0];
63
+ const raw = assertion[polarity];
64
+ return {
65
+ polarity,
66
+ value: polarity === "zero" ? 0 : raw,
67
+ };
68
+ }
69
+ function valuesEqual(left, right) {
70
+ return stableStringify(left) === stableStringify(right);
71
+ }
72
+ function compareBound(observed, bound) {
73
+ if (bound.polarity === "equals")
74
+ return valuesEqual(observed, bound.value);
75
+ if (bound.polarity === "subset_of") {
76
+ if (!Array.isArray(observed) || !Array.isArray(bound.value))
77
+ return false;
78
+ const allowed = bound.value;
79
+ return observed.every((entry) => allowed.includes(entry));
80
+ }
81
+ if (typeof observed !== "number") {
82
+ return false;
83
+ }
84
+ const numericBound = bound.value;
85
+ if (bound.polarity === "ceiling" ||
86
+ bound.polarity === "monotone_nonincreasing") {
87
+ return observed <= numericBound;
88
+ }
89
+ if (bound.polarity === "floor" ||
90
+ bound.polarity === "monotone_nondecreasing") {
91
+ return observed >= numericBound;
92
+ }
93
+ return observed === 0;
94
+ }
95
+ function proveNegativeControl(assertion) {
96
+ const bound = boundFor(assertion);
97
+ const cases = assertion.negative_control.cases;
98
+ return (cases.some((entry) => entry.expected === "pass") &&
99
+ cases.some((entry) => entry.expected === "fail") &&
100
+ cases.every((entry) => compareBound(entry.observed, bound) === (entry.expected === "pass")));
101
+ }
102
+ function boundUpdateIsLoosening(beforeBound, afterBound) {
103
+ if (beforeBound.polarity !== afterBound.polarity)
104
+ return true;
105
+ if (typeof beforeBound.value === "number" &&
106
+ typeof afterBound.value === "number") {
107
+ return beforeBound.polarity === "ceiling" ||
108
+ beforeBound.polarity === "monotone_nonincreasing"
109
+ ? afterBound.value > beforeBound.value
110
+ : beforeBound.polarity === "floor" ||
111
+ beforeBound.polarity === "monotone_nondecreasing"
112
+ ? afterBound.value < beforeBound.value
113
+ : afterBound.value !== beforeBound.value;
114
+ }
115
+ if (beforeBound.polarity === "subset_of") {
116
+ const beforeValues = beforeBound.value;
117
+ const afterValues = afterBound.value;
118
+ return (!Array.isArray(beforeValues) ||
119
+ !Array.isArray(afterValues) ||
120
+ afterValues.some((entry) => !beforeValues.includes(entry)));
121
+ }
122
+ return !valuesEqual(beforeBound.value, afterBound.value);
123
+ }
124
+ /** Return true when an assertion update weakens scope, bound, lifetime, or enforcement. */
125
+ export function assuranceAssertionUpdateIsLoosening(before, after) {
126
+ if (before.measurement_id !== after.measurement_id ||
127
+ before.owner_item_id !== after.owner_item_id ||
128
+ stableStringify(before.scope) !== stableStringify(after.scope)) {
129
+ return true;
130
+ }
131
+ const enforcementRank = {
132
+ observe: 0,
133
+ warn: 1,
134
+ block: 2,
135
+ };
136
+ const lifetimeLoosened = (before.lifetime ?? "hold") === "hold" &&
137
+ (after.lifetime ?? "hold") === "retire";
138
+ return (boundUpdateIsLoosening(boundFor(before), boundFor(after)) ||
139
+ enforcementRank[after.enforcement] < enforcementRank[before.enforcement] ||
140
+ lifetimeLoosened);
141
+ }
142
+ /** Validate the field predicate contract of an item-backed measurement source. */
143
+ function validateItemsMeasurementSource(source) {
144
+ if (source.state !== undefined &&
145
+ !(source.state === "present" || source.state === "missing")) {
146
+ throw new AssuranceMutationRefusalError("items source state must be present or missing");
147
+ }
148
+ const hasEquals = Object.hasOwn(source, "equals");
149
+ const hasState = source.state !== undefined;
150
+ const predicateCount = Number(hasEquals) + Number(hasState);
151
+ if (source.field !== undefined && predicateCount !== 1) {
152
+ throw new AssuranceMutationRefusalError("items source with field requires exactly one of equals or state");
153
+ }
154
+ if (source.field === undefined && predicateCount !== 0) {
155
+ throw new AssuranceMutationRefusalError("items source equals/state requires field");
156
+ }
157
+ }
158
+ /** Validate and return one measurement declaration. */
159
+ export function validateMeasurementDefinition(definition) {
160
+ requireStableId(definition.id, "measurement.id");
161
+ if (definition.max_cost !== undefined) {
162
+ requireFiniteNonNegative(definition.max_cost, "measurement.max_cost");
163
+ }
164
+ const source = definition.source;
165
+ if (source.kind === "items") {
166
+ validateItemsMeasurementSource(source);
167
+ }
168
+ if (source.kind === "dependency_kind" &&
169
+ (typeof source.dependency_kind !== "string" ||
170
+ source.dependency_kind.trim().length === 0)) {
171
+ throw new AssuranceMutationRefusalError("dependency_kind source requires dependency_kind");
172
+ }
173
+ if ((source.kind === "graph" ||
174
+ source.kind === "validate" ||
175
+ source.kind === "health") &&
176
+ (typeof source.field !== "string" || source.field.trim().length === 0)) {
177
+ throw new AssuranceMutationRefusalError(`${source.kind} source requires field`);
178
+ }
179
+ if (source.kind === "provider") {
180
+ requireStableId(source.provider, "provider source provider");
181
+ requireStableId(source.key, "provider source key");
182
+ }
183
+ return definition;
184
+ }
185
+ /** Validate and return one assertion declaration. */
186
+ export function validateAssertionDefinition(definition) {
187
+ requireStableId(definition.id, "assertion.id");
188
+ requireStableId(definition.measurement_id, "assertion.measurement_id");
189
+ if (typeof definition.owner_item_id !== "string" ||
190
+ definition.owner_item_id.trim().length === 0) {
191
+ throw new AssuranceMutationRefusalError("assertion.owner_item_id is required");
192
+ }
193
+ const bound = boundFor(definition);
194
+ if (typeof bound.value === "number" && !Number.isFinite(bound.value)) {
195
+ throw new AssuranceMutationRefusalError(`assertion.${bound.polarity} must be a finite number`);
196
+ }
197
+ if (definition.lifetime === "retire" &&
198
+ (typeof definition.retire_reason !== "string" ||
199
+ definition.retire_reason.trim().length === 0)) {
200
+ throw new AssuranceMutationRefusalError("retired assertion requires retire_reason");
201
+ }
202
+ if (definition.scope.kind === "filter") {
203
+ requireStableId(definition.scope.measurement_id, "assertion.scope.measurement_id");
204
+ }
205
+ const cases = definition.negative_control?.cases;
206
+ if (!Array.isArray(cases) ||
207
+ !cases.some((entry) => entry.expected === "pass") ||
208
+ !cases.some((entry) => entry.expected === "fail")) {
209
+ throw new AssuranceMutationRefusalError("assertion negative control requires pass and fail cases");
210
+ }
211
+ if (!proveNegativeControl(definition)) {
212
+ throw new AssuranceMutationRefusalError(`assertion ${definition.id} negative control does not prove its bound`);
213
+ }
214
+ return definition;
215
+ }
216
+ /** Validate and return one gate declaration. */
217
+ export function validateGateDefinition(definition) {
218
+ requireStableId(definition.id, "gate.id");
219
+ if (definition.assertion_ids.length === 0) {
220
+ throw new AssuranceMutationRefusalError("gate requires at least one assertion");
221
+ }
222
+ for (const id of definition.assertion_ids)
223
+ requireStableId(id, "gate.assertion_id");
224
+ if (definition.triggers.length === 0) {
225
+ throw new AssuranceMutationRefusalError("gate requires at least one trigger");
226
+ }
227
+ return definition;
228
+ }
229
+ /** Validate registry references, uniqueness, and declaration-local contracts. */
230
+ export function validateAssuranceDocument(document) {
231
+ if (document.version !== ASSURANCE_DOCUMENT_VERSION) {
232
+ throw new AssuranceMutationRefusalError(`unsupported assurance document version ${String(document.version)}`);
233
+ }
234
+ const ids = new Set();
235
+ for (const definition of document.measurements) {
236
+ validateMeasurementDefinition(definition);
237
+ if (ids.has(`measurement:${definition.id}`)) {
238
+ throw new AssuranceMutationRefusalError(`duplicate measurement ${definition.id}`);
239
+ }
240
+ ids.add(`measurement:${definition.id}`);
241
+ }
242
+ for (const definition of document.assertions) {
243
+ validateAssertionDefinition(definition);
244
+ if (ids.has(`assertion:${definition.id}`)) {
245
+ throw new AssuranceMutationRefusalError(`duplicate assertion ${definition.id}`);
246
+ }
247
+ ids.add(`assertion:${definition.id}`);
248
+ }
249
+ for (const definition of document.gates) {
250
+ validateGateDefinition(definition);
251
+ const missing = definition.assertion_ids.filter((id) => !document.assertions.some((assertionDefinition) => assertionDefinition.id === id));
252
+ if (missing.length > 0) {
253
+ throw new AssuranceMutationRefusalError(`gate ${definition.id} references missing assertions: ${missing.join(", ")}`);
254
+ }
255
+ if (ids.has(`gate:${definition.id}`)) {
256
+ throw new AssuranceMutationRefusalError(`duplicate gate ${definition.id}`);
257
+ }
258
+ ids.add(`gate:${definition.id}`);
259
+ }
260
+ const measurementIds = new Set(document.measurements.map((entry) => entry.id));
261
+ for (const [measurementId, consumers] of collectMeasurementReferences(document)) {
262
+ if (!measurementIds.has(measurementId)) {
263
+ throw new AssuranceMutationRefusalError(`${consumers.join(", ")} references missing measurement ${measurementId}`);
264
+ }
265
+ }
266
+ return document;
267
+ }
268
+ function collectExpressionMeasurementReferences(expression, references) {
269
+ if ("measurement" in expression) {
270
+ references.push(expression.measurement);
271
+ return;
272
+ }
273
+ if ("operator" in expression) {
274
+ for (const operand of expression.operands) {
275
+ collectExpressionMeasurementReferences(operand, references);
276
+ }
277
+ }
278
+ }
279
+ function collectMeasurementReferences(document) {
280
+ const references = new Map();
281
+ const add = (measurementId, consumer) => {
282
+ const consumers = references.get(measurementId) ?? [];
283
+ consumers.push(consumer);
284
+ references.set(measurementId, consumers);
285
+ };
286
+ for (const assertionDefinition of document.assertions) {
287
+ add(assertionDefinition.measurement_id, `assertion ${assertionDefinition.id}`);
288
+ if (assertionDefinition.scope.kind === "filter") {
289
+ add(assertionDefinition.scope.measurement_id, `assertion ${assertionDefinition.id} scope`);
290
+ }
291
+ }
292
+ for (const measurementDefinition of document.measurements) {
293
+ if (measurementDefinition.source.kind !== "derived")
294
+ continue;
295
+ const derivedReferences = [];
296
+ collectExpressionMeasurementReferences(measurementDefinition.source.expression, derivedReferences);
297
+ for (const measurementId of derivedReferences) {
298
+ add(measurementId, `derived measurement ${measurementDefinition.id}`);
299
+ }
300
+ }
301
+ return references;
302
+ }
303
+ /** Create an empty, valid assurance registry. */
304
+ export function createEmptyAssuranceDocument() {
305
+ return {
306
+ version: ASSURANCE_DOCUMENT_VERSION,
307
+ measurements: [],
308
+ assertions: [],
309
+ gates: [],
310
+ };
311
+ }
312
+ function itemsSourceResult(source, context) {
313
+ const matches = context.items.filter((item) => (source.statuses === undefined ||
314
+ source.statuses.includes(item.status)) &&
315
+ (source.types === undefined || source.types.includes(item.type)) &&
316
+ (source.tags === undefined ||
317
+ source.tags.every((tag) => item.tags?.includes(tag))) &&
318
+ (source.field === undefined ||
319
+ (() => {
320
+ const value = item[source.field];
321
+ if (source.state !== undefined) {
322
+ const missing = value === undefined ||
323
+ value === null ||
324
+ value === "" ||
325
+ (Array.isArray(value) && value.length === 0);
326
+ return source.state === "missing" ? missing : !missing;
327
+ }
328
+ return valuesEqual(value, source.equals);
329
+ })()));
330
+ return {
331
+ value: matches.length,
332
+ population_size: context.items.length,
333
+ cost: context.items.length,
334
+ contributors: matches.map((item) => item.id),
335
+ };
336
+ }
337
+ function dependencyKindSourceResult(source, context) {
338
+ const contributors = [];
339
+ const requestedKind = resolveCanonicalRelationshipKind(source.dependency_kind) ??
340
+ source.dependency_kind.trim().toLowerCase();
341
+ for (const item of context.items) {
342
+ for (const dependency of item.dependencies ?? []) {
343
+ const dependencyKind = resolveCanonicalRelationshipKind(dependency.kind) ??
344
+ dependency.kind.trim().toLowerCase();
345
+ if (dependencyKind === requestedKind) {
346
+ contributors.push(`${item.id}->${dependency.id}`);
347
+ }
348
+ }
349
+ }
350
+ return {
351
+ value: contributors.length,
352
+ population_size: context.items.length,
353
+ cost: context.items.length,
354
+ contributors,
355
+ };
356
+ }
357
+ function historySourceResult(source, context) {
358
+ const matches = context.history.filter((entry) => (source.op === undefined || entry.op === source.op) &&
359
+ (source.author === undefined || entry.author === source.author) &&
360
+ (source.harness === undefined ||
361
+ entry.agent_harness === source.harness) &&
362
+ (source.model === undefined || entry.agent_model === source.model));
363
+ return {
364
+ value: matches.length,
365
+ population_size: context.history.length,
366
+ cost: context.history.length,
367
+ };
368
+ }
369
+ function linksSourceResult(source, context) {
370
+ const matches = context.items.filter((item) => {
371
+ const links = item[source.link];
372
+ const present = Array.isArray(links) && links.length > 0;
373
+ return source.state === "present" ? present : !present;
374
+ });
375
+ return {
376
+ value: matches.length,
377
+ population_size: context.items.length,
378
+ cost: context.items.length,
379
+ contributors: matches.map((item) => item.id),
380
+ };
381
+ }
382
+ function sourceResult(definition, context) {
383
+ const source = definition.source;
384
+ if (source.kind === "items")
385
+ return itemsSourceResult(source, context);
386
+ if (source.kind === "dependency_kind") {
387
+ return dependencyKindSourceResult(source, context);
388
+ }
389
+ if (source.kind === "history")
390
+ return historySourceResult(source, context);
391
+ if (source.kind === "links")
392
+ return linksSourceResult(source, context);
393
+ return null;
394
+ }
395
+ function sumCosts(costs, durationMs) {
396
+ return costs.reduce((total, cost) => ({
397
+ units: total.units + cost.units,
398
+ items_scanned: total.items_scanned + cost.items_scanned,
399
+ history_entries: total.history_entries + cost.history_entries,
400
+ provider_calls: total.provider_calls + cost.provider_calls,
401
+ duration_ms: durationMs,
402
+ }), {
403
+ units: 0,
404
+ items_scanned: 0,
405
+ history_entries: 0,
406
+ provider_calls: 0,
407
+ duration_ms: durationMs,
408
+ });
409
+ }
410
+ function arithmeticValue(operator, values) {
411
+ if (values.length === 0)
412
+ throw new TypeError(`derived ${operator} requires operands`);
413
+ if (operator === "add")
414
+ return values.reduce((sum, value) => sum + value, 0);
415
+ if (operator === "subtract")
416
+ return values.slice(1).reduce((result, value) => result - value, values[0]);
417
+ if (operator === "multiply")
418
+ return values.reduce((result, value) => result * value, 1);
419
+ if (operator === "divide") {
420
+ if (values.slice(1).includes(0))
421
+ throw new TypeError("derived divide cannot divide by zero");
422
+ return values.slice(1).reduce((result, value) => result / value, values[0]);
423
+ }
424
+ return operator === "min" ? Math.min(...values) : Math.max(...values);
425
+ }
426
+ async function mapWithConcurrency(values, limit, transform) {
427
+ const results = new Array(values.length);
428
+ let nextIndex = 0;
429
+ const workers = Array.from({ length: Math.min(limit, values.length) }, async () => {
430
+ while (nextIndex < values.length) {
431
+ const index = nextIndex;
432
+ nextIndex += 1;
433
+ results[index] = await transform(values[index], index);
434
+ }
435
+ });
436
+ await Promise.all(workers);
437
+ return results;
438
+ }
439
+ async function evaluateExpression(expression, context, definitions, stack, state) {
440
+ if ("literal" in expression)
441
+ return {
442
+ value: expression.literal,
443
+ costs: [],
444
+ population_size: 0,
445
+ contributors: [],
446
+ };
447
+ if ("measurement" in expression) {
448
+ const definition = definitions.find((entry) => entry.id === expression.measurement);
449
+ if (!definition)
450
+ throw new TypeError(`derived measurement references missing ${expression.measurement}`);
451
+ const result = await evaluateMeasurementCached(definition, context, definitions, stack, state);
452
+ if (typeof result.value !== "number")
453
+ throw new TypeError(`derived measurement ${definition.id} is not numeric`);
454
+ const costs = state.charged.has(definition.id) ? [] : [result.cost];
455
+ state.charged.add(definition.id);
456
+ return {
457
+ value: result.value,
458
+ costs,
459
+ population_size: result.population_size,
460
+ contributors: result.contributors,
461
+ };
462
+ }
463
+ const operands = await mapWithConcurrency(expression.operands, EVALUATION_CONCURRENCY, (operand) => evaluateExpression(operand, context, definitions, stack, state));
464
+ return {
465
+ value: arithmeticValue(expression.operator, operands.map((entry) => entry.value)),
466
+ costs: operands.flatMap((entry) => entry.costs),
467
+ population_size: Math.max(0, ...operands.map((entry) => entry.population_size)),
468
+ contributors: [...new Set(operands.flatMap((entry) => entry.contributors))],
469
+ };
470
+ }
471
+ async function evaluateMeasurementInternal(definition, context, definitions, stack, state) {
472
+ validateMeasurementDefinition(definition);
473
+ const startedAt = Date.now();
474
+ let result;
475
+ let nestedCosts = [];
476
+ let providerCalls = 0;
477
+ if (definition.source.kind === "derived") {
478
+ const derived = await evaluateExpression(definition.source.expression, context, definitions, [...stack, definition.id], state);
479
+ result = {
480
+ value: derived.value,
481
+ population_size: derived.population_size,
482
+ cost: 0,
483
+ contributors: derived.contributors,
484
+ };
485
+ nestedCosts = derived.costs;
486
+ }
487
+ else {
488
+ const builtin = sourceResult(definition, context);
489
+ if (builtin !== null) {
490
+ result = builtin;
491
+ }
492
+ else if (definition.source.kind === "graph" ||
493
+ definition.source.kind === "validate" ||
494
+ definition.source.kind === "health" ||
495
+ definition.source.kind === "provider") {
496
+ result = await context.external(definition.source);
497
+ providerCalls = 1;
498
+ }
499
+ else {
500
+ throw new TypeError(`unsupported assurance source ${definition.source.kind}`);
501
+ }
502
+ }
503
+ const ownCost = {
504
+ units: result.cost,
505
+ items_scanned: definition.source.kind === "items" ||
506
+ definition.source.kind === "dependency_kind" ||
507
+ definition.source.kind === "links"
508
+ ? context.items.length
509
+ : 0,
510
+ history_entries: definition.source.kind === "history" ? context.history.length : 0,
511
+ provider_calls: providerCalls,
512
+ duration_ms: Date.now() - startedAt,
513
+ };
514
+ const cost = sumCosts([...nestedCosts, ownCost], Date.now() - startedAt);
515
+ if (definition.max_cost !== undefined && cost.units > definition.max_cost) {
516
+ throw new TypeError(`measurement ${definition.id} exceeded cost ceiling ${definition.max_cost} with ${cost.units}`);
517
+ }
518
+ return {
519
+ id: definition.id,
520
+ definition_fingerprint: `sha256:${createHash("sha256")
521
+ .update(stableStringify(definition))
522
+ .digest("hex")}`,
523
+ value: result.value,
524
+ population_size: result.population_size,
525
+ cost,
526
+ contributors: result.contributors ?? [],
527
+ };
528
+ }
529
+ function evaluateMeasurementCached(definition, context, definitions, stack, state) {
530
+ if (stack.includes(definition.id)) {
531
+ throw new TypeError(`derived measurement cycle: ${[...stack, definition.id].join(" -> ")}`);
532
+ }
533
+ const cached = state.cache.get(definition.id);
534
+ if (cached)
535
+ return cached;
536
+ const evaluation = evaluateMeasurementInternal(definition, context, definitions, stack, state);
537
+ state.cache.set(definition.id, evaluation);
538
+ return evaluation;
539
+ }
540
+ /** Evaluate one measurement with cycle detection and a complete cost receipt. */
541
+ export async function evaluateMeasurement(definition, context, definitions = [definition]) {
542
+ return evaluateMeasurementCached(definition, context, definitions, [], {
543
+ cache: new Map(),
544
+ charged: new Set(),
545
+ });
546
+ }
547
+ function assertionDistance(observed, bound) {
548
+ if (typeof observed !== "number" || typeof bound.value !== "number")
549
+ return null;
550
+ if (bound.polarity === "ceiling" ||
551
+ bound.polarity === "monotone_nonincreasing")
552
+ return bound.value - observed;
553
+ if (bound.polarity === "floor" || bound.polarity === "monotone_nondecreasing")
554
+ return observed - bound.value;
555
+ return observed - bound.value;
556
+ }
557
+ /** Evaluate one assertion against a precomputed measurement receipt. */
558
+ export function evaluateAssuranceAssertion(definition, measurement, options = {}) {
559
+ validateAssertionDefinition(definition);
560
+ const bound = boundFor(definition);
561
+ const retired = definition.lifetime === "retire" && options.owner_terminal === true;
562
+ return {
563
+ assertion_id: definition.id,
564
+ measurement_id: definition.measurement_id,
565
+ measurement_definition_fingerprint: measurement.definition_fingerprint,
566
+ scope: definition.scope,
567
+ population_size: measurement.population_size,
568
+ observed: measurement.value,
569
+ bound,
570
+ distance: assertionDistance(measurement.value, bound),
571
+ verdict: retired
572
+ ? "retired"
573
+ : compareBound(measurement.value, bound)
574
+ ? "pass"
575
+ : "fail",
576
+ enforcement: definition.enforcement,
577
+ negative_control_proven: proveNegativeControl(definition),
578
+ cost: measurement.cost,
579
+ contributors: measurement.contributors,
580
+ };
581
+ }
582
+ /** Evaluate a named gate into one presentation-independent verdict document. */
583
+ export async function evaluateAssuranceGate(gateId, document, context, options) {
584
+ validateAssuranceDocument(document);
585
+ const gateDefinition = document.gates.find((entry) => entry.id === gateId);
586
+ if (!gateDefinition)
587
+ throw new TypeError(`assurance gate ${gateId} not found`);
588
+ if (!gateDefinition.triggers.includes(options.trigger)) {
589
+ throw new TypeError(`assurance gate ${gateId} does not declare trigger ${options.trigger}`);
590
+ }
591
+ const assertions = await mapWithConcurrency(gateDefinition.assertion_ids, EVALUATION_CONCURRENCY, async (id) => {
592
+ const assertionDefinition = document.assertions.find((entry) => entry.id === id);
593
+ const measurementDefinition = document.measurements.find((entry) => entry.id === assertionDefinition.measurement_id);
594
+ let scopedContext = context;
595
+ if (assertionDefinition.scope.kind === "active") {
596
+ const terminalStatuses = new Set(context.terminal_statuses ?? ["closed", "canceled"]);
597
+ scopedContext = {
598
+ ...context,
599
+ items: context.items.filter((item) => !terminalStatuses.has(item.status)),
600
+ };
601
+ }
602
+ else if (assertionDefinition.scope.kind === "filter") {
603
+ const scopeMeasurementId = assertionDefinition.scope.measurement_id;
604
+ const scopeDefinition = document.measurements.find((entry) => entry.id === scopeMeasurementId);
605
+ const scope = await evaluateMeasurement(scopeDefinition, context, document.measurements);
606
+ const contributorIds = new Set(scope.contributors);
607
+ scopedContext = {
608
+ ...context,
609
+ items: context.items.filter((item) => contributorIds.has(item.id)),
610
+ };
611
+ }
612
+ const owner = context.items.find((item) => item.id === assertionDefinition.owner_item_id);
613
+ const ownerTerminal = owner !== undefined &&
614
+ new Set(context.terminal_statuses ?? ["closed", "canceled"]).has(owner.status);
615
+ return evaluateAssuranceAssertion(assertionDefinition, await evaluateMeasurement(measurementDefinition, scopedContext, document.measurements), { owner_terminal: ownerTerminal });
616
+ });
617
+ const blocking = assertions.some((entry) => entry.verdict === "fail" && entry.enforcement === "block");
618
+ const warning = assertions.some((entry) => entry.verdict === "fail" && entry.enforcement === "warn");
619
+ return {
620
+ gate_id: gateDefinition.id,
621
+ tree_id: context.tree_id,
622
+ trigger: options.trigger,
623
+ dry_run: options.dry_run === true,
624
+ evaluated_at: new Date().toISOString(),
625
+ verdict: blocking ? "block" : warning ? "warn" : "pass",
626
+ exit_code: blocking ? 1 : 0,
627
+ assertions,
628
+ cost: sumCosts(assertions.map((entry) => entry.cost), Math.max(0, ...assertions.map((entry) => entry.cost.duration_ms))),
629
+ };
630
+ }
631
+ function assurancePath(pmRoot) {
632
+ return path.join(pmRoot, "assurance.json");
633
+ }
634
+ async function readDocument(pmRoot) {
635
+ const raw = await readFileIfExists(assurancePath(pmRoot));
636
+ if (raw === null)
637
+ return createEmptyAssuranceDocument();
638
+ return parseAssuranceDocument(raw);
639
+ }
640
+ function parseAssuranceDocument(raw) {
641
+ try {
642
+ return validateAssuranceDocument(JSON.parse(raw));
643
+ }
644
+ catch (error) {
645
+ throw new PmCliError("The assurance registry is invalid. Repair or restore .agents/pm/assurance.json and retry.", EXIT_CODE.GENERIC_FAILURE, {
646
+ code: "assurance_registry_invalid",
647
+ reason: String(error),
648
+ nextSteps: [
649
+ "Restore a valid assurance.json from version control or repair its declaration references.",
650
+ "Run pm assurance list measurement after the repair.",
651
+ ],
652
+ });
653
+ }
654
+ }
655
+ function collectionFor(document, kind) {
656
+ if (kind === "measurement")
657
+ return document.measurements;
658
+ if (kind === "assertion")
659
+ return document.assertions;
660
+ return document.gates;
661
+ }
662
+ function validateForKind(kind, definition) {
663
+ if (kind === "measurement")
664
+ validateMeasurementDefinition(definition);
665
+ else if (kind === "assertion")
666
+ validateAssertionDefinition(definition);
667
+ else
668
+ validateGateDefinition(definition);
669
+ }
670
+ /** Read all declarations of one kind with a stable row selector receipt. */
671
+ export async function listAssuranceDeclarations(pmRoot, kind) {
672
+ const items = structuredClone(collectionFor(await readDocument(pmRoot), kind));
673
+ return {
674
+ items,
675
+ count: items.length,
676
+ row_contract: { row_keys: ["items"], jq_selector: ".items[]" },
677
+ };
678
+ }
679
+ /** Read one named declaration or fail loudly. */
680
+ export async function getAssuranceDeclaration(pmRoot, kind, id) {
681
+ const found = collectionFor(await readDocument(pmRoot), kind).find((entry) => entry.id === id);
682
+ if (!found)
683
+ throw new TypeError(`assurance ${kind} ${id} not found`);
684
+ return structuredClone(found);
685
+ }
686
+ /** Create or update one declaration through audited singleton persistence. */
687
+ export async function putAssuranceDeclaration(pmRoot, kind, definition, options = {}) {
688
+ validateForKind(kind, definition);
689
+ const settings = await readSettings(pmRoot);
690
+ const author = resolveAuthor(options.author, settings.author_default);
691
+ const mutation = await mutateWorkspaceJsonWithHistory({
692
+ pmRoot,
693
+ filePath: assurancePath(pmRoot),
694
+ op: `assurance:${kind}:put`,
695
+ author,
696
+ message: options.message,
697
+ lockTtlSeconds: LOCK_TTL_SECONDS,
698
+ lockWaitMs: LOCK_WAIT_MS,
699
+ mutate: (beforeRaw) => {
700
+ const document = beforeRaw === null
701
+ ? createEmptyAssuranceDocument()
702
+ : parseAssuranceDocument(beforeRaw);
703
+ const collection = collectionFor(document, kind);
704
+ const index = collection.findIndex((entry) => entry.id === definition.id);
705
+ const action = index < 0 ? "created" : "updated";
706
+ if (kind === "assertion" &&
707
+ index >= 0 &&
708
+ assuranceAssertionUpdateIsLoosening(collection[index], definition)) {
709
+ const decision = definition
710
+ .authorization_decision;
711
+ if (!decision ||
712
+ !options.authorized_decision_ids?.includes(decision)) {
713
+ throw new AssuranceMutationRefusalError(`loosening assertion ${definition.id} requires a verified authorization_decision`);
714
+ }
715
+ }
716
+ if (index < 0)
717
+ collection.push(structuredClone(definition));
718
+ else
719
+ collection[index] = structuredClone(definition);
720
+ validateAssuranceDocument(document);
721
+ return {
722
+ raw: `${JSON.stringify(document, null, 2)}\n`,
723
+ result: { changed: true, action, kind, id: definition.id },
724
+ };
725
+ },
726
+ });
727
+ return { ...mutation.result, changed: mutation.changed };
728
+ }
729
+ /** Remove an unreferenced declaration through audited singleton persistence. */
730
+ export async function removeAssuranceDeclaration(pmRoot, kind, id, options = {}) {
731
+ const settings = await readSettings(pmRoot);
732
+ const author = resolveAuthor(options.author, settings.author_default);
733
+ const mutation = await mutateWorkspaceJsonWithHistory({
734
+ pmRoot,
735
+ filePath: assurancePath(pmRoot),
736
+ op: `assurance:${kind}:remove`,
737
+ author,
738
+ message: options.message,
739
+ lockTtlSeconds: LOCK_TTL_SECONDS,
740
+ lockWaitMs: LOCK_WAIT_MS,
741
+ mutate: (beforeRaw) => {
742
+ const document = beforeRaw === null
743
+ ? createEmptyAssuranceDocument()
744
+ : parseAssuranceDocument(beforeRaw);
745
+ if (kind === "measurement") {
746
+ const consumers = collectMeasurementReferences(document).get(id);
747
+ if (consumers && consumers.length > 0) {
748
+ throw new AssuranceMutationRefusalError(`assurance measurement ${id} is referenced by ${consumers.join(", ")}`);
749
+ }
750
+ }
751
+ if (kind === "assertion" &&
752
+ document.gates.some((entry) => entry.assertion_ids.includes(id))) {
753
+ throw new AssuranceMutationRefusalError(`assurance assertion ${id} is referenced by gate`);
754
+ }
755
+ const collection = collectionFor(document, kind);
756
+ const index = collection.findIndex((entry) => entry.id === id);
757
+ if (index < 0) {
758
+ throw new AssuranceMutationRefusalError(`assurance ${kind} ${id} not found`);
759
+ }
760
+ collection.splice(index, 1);
761
+ validateAssuranceDocument(document);
762
+ return {
763
+ raw: `${JSON.stringify(document, null, 2)}\n`,
764
+ result: { changed: true, action: "removed", kind, id },
765
+ };
766
+ },
767
+ });
768
+ return { ...mutation.result, changed: mutation.changed };
769
+ }
770
+ /** Append one immutable gate verdict without changing registry state. */
771
+ export async function recordAssuranceVerdict(pmRoot, verdict, options = {}) {
772
+ if (verdict.dry_run)
773
+ throw new TypeError("dry-run assurance verdicts are not persisted");
774
+ const settings = await readSettings(pmRoot);
775
+ await appendWorkspaceAuditEvent({
776
+ pmRoot,
777
+ op: "assurance:gate:verdict",
778
+ author: resolveAuthor(options.author, settings.author_default),
779
+ context: { assurance_verdict: verdict },
780
+ message: options.message ??
781
+ `Assurance gate ${verdict.gate_id} returned ${verdict.verdict}`,
782
+ lockTtlSeconds: LOCK_TTL_SECONDS,
783
+ lockWaitMs: LOCK_WAIT_MS,
784
+ });
785
+ }
786
+ /** Query durable assurance verdicts from the verified workspace stream. */
787
+ export async function listAssuranceVerdicts(pmRoot, options = {}) {
788
+ const limit = options.limit ?? DEFAULT_VERDICT_LIMIT;
789
+ if (!Number.isInteger(limit) ||
790
+ limit < 1 ||
791
+ limit > MAX_ASSURANCE_VERDICT_LIMIT) {
792
+ throw new TypeError(`assurance verdict limit must be an integer from 1 through ${MAX_ASSURANCE_VERDICT_LIMIT}`);
793
+ }
794
+ const entries = await readHistoryEntries(getWorkspaceHistoryPath(pmRoot), WORKSPACE_HISTORY_ID);
795
+ return entries
796
+ .reverse()
797
+ .filter((entry) => entry.op === "assurance:gate:verdict")
798
+ .map((entry) => entry.context?.assurance_verdict)
799
+ .filter((value) => typeof value === "object" && value !== null && "gate_id" in value)
800
+ .filter((verdict) => options.gate_id === undefined || verdict.gate_id === options.gate_id)
801
+ .slice(0, limit);
802
+ }
803
+ //# sourceMappingURL=assurance.js.map
804
+ //# debugId=ecc8eb89-b9e9-51fa-8573-2afb8ec801be