@unbrained/pm-cli 2026.8.11 → 2026.8.13

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 (250) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +74 -21
  3. package/dist/cli/commander-usage.js +37 -11
  4. package/dist/cli/error-guidance.d.ts +2 -0
  5. package/dist/cli/error-guidance.js +82 -19
  6. package/dist/cli/main.js +35 -19
  7. package/dist/cli/register-annotations.js +56 -3
  8. package/dist/cli/register-assurance.js +28 -17
  9. package/dist/cli/register-list-query.js +10 -7
  10. package/dist/cli/register-operations.js +16 -6
  11. package/dist/cli-bundle/bundle-manifest.json +163 -163
  12. package/dist/cli-bundle/chunks/chunk-3ZMOWAFM.js +2 -0
  13. package/dist/cli-bundle/chunks/chunk-DEDU3DG7.js +196 -0
  14. package/dist/cli-bundle/chunks/chunk-DL5M5J3V.js +13 -0
  15. package/dist/cli-bundle/chunks/chunk-F7FJYGK3.js +8 -0
  16. package/dist/cli-bundle/chunks/chunk-HIUZVCMN.js +5 -0
  17. package/dist/cli-bundle/chunks/chunk-IMGIN7R6.js +2 -0
  18. package/dist/cli-bundle/chunks/{chunk-A2SJGHSI.js → chunk-Q47C67TQ.js} +2 -2
  19. package/dist/cli-bundle/chunks/{chunk-WMEZHCFI.js → chunk-QGTUPEU5.js} +62 -54
  20. package/dist/cli-bundle/chunks/chunk-TF3TPKPR.js +34 -0
  21. package/dist/cli-bundle/chunks/{chunk-ZU34C3LH.js → chunk-YLBAPY22.js} +50 -50
  22. package/dist/cli-bundle/chunks/register-list-query-4HIZ7EA3.js +10 -0
  23. package/dist/cli-bundle/chunks/register-mutation-W64FZ5HN.js +20 -0
  24. package/dist/cli-bundle/chunks/register-operations-C44ET7R5.js +2 -0
  25. package/dist/cli-bundle/chunks/{register-setup-NOCOOWQY.js → register-setup-634MFRXU.js} +2 -2
  26. package/dist/cli-bundle/focused-chunks/chunk-27N23W6M.js +31 -0
  27. package/dist/cli-bundle/focused-chunks/{chunk-4H5IA6AV.js → chunk-2JP7W7GO.js} +2 -2
  28. package/dist/cli-bundle/focused-chunks/chunk-2RI2FESN.js +4 -0
  29. package/dist/cli-bundle/focused-chunks/chunk-3BNVU6LC.js +8 -0
  30. package/dist/cli-bundle/focused-chunks/{chunk-2DL3MRTD.js → chunk-3DJSGWVB.js} +2 -2
  31. package/dist/cli-bundle/focused-chunks/chunk-6MLIRBLV.js +5 -0
  32. package/dist/cli-bundle/focused-chunks/chunk-6XYLENDV.js +155 -0
  33. package/dist/cli-bundle/focused-chunks/chunk-CP4K4WFS.js +2 -0
  34. package/dist/cli-bundle/focused-chunks/{chunk-TL5M2PAY.js → chunk-E3AVJ655.js} +2 -2
  35. package/dist/cli-bundle/focused-chunks/chunk-EBTNSFYE.js +2 -0
  36. package/dist/cli-bundle/focused-chunks/{chunk-J5EKAIVD.js → chunk-HC2OL2H2.js} +2 -2
  37. package/dist/cli-bundle/focused-chunks/{chunk-BKPIY34S.js → chunk-I75MANL7.js} +2 -2
  38. package/dist/cli-bundle/focused-chunks/chunk-IIS2FRKL.js +2 -0
  39. package/dist/cli-bundle/focused-chunks/chunk-NFHFZH2Z.js +2 -0
  40. package/dist/cli-bundle/focused-chunks/chunk-OGC4XSBC.js +17 -0
  41. package/dist/cli-bundle/focused-chunks/{chunk-UDAZWT25.js → chunk-OOYYKQWX.js} +4 -4
  42. package/dist/cli-bundle/focused-chunks/{chunk-23FTGO3W.js → chunk-PKPOZXLL.js} +2 -2
  43. package/dist/cli-bundle/focused-chunks/chunk-QDZUABWL.js +2 -0
  44. package/dist/cli-bundle/focused-chunks/chunk-QQX5TDFT.js +16 -0
  45. package/dist/cli-bundle/focused-chunks/{chunk-55BIB6DV.js → chunk-TEZIXMTH.js} +2 -2
  46. package/dist/cli-bundle/focused-chunks/chunk-WEH6NSL6.js +18 -0
  47. package/dist/cli-bundle/main.js +13 -13
  48. package/dist/cli-bundle/sdk-authoring.js +1 -1
  49. package/dist/cli-bundle/sdk-contracts.js +1 -1
  50. package/dist/cli-bundle/sdk-core.js +42 -41
  51. package/dist/cli-bundle/sdk-governance.js +1 -1
  52. package/dist/cli-bundle/sdk-graph.js +1 -1
  53. package/dist/cli-bundle/sdk-merge.js +1 -1
  54. package/dist/cli-bundle/sdk-query.js +1 -1
  55. package/dist/cli-bundle/sdk-runtime.js +1 -1
  56. package/dist/cli-bundle/sdk-testing.js +1 -1
  57. package/dist/cli-bundle/sdk.js +2 -2
  58. package/dist/core/checkpoint/checkpoint-gc.js +7 -12
  59. package/dist/core/extensions/activation-summary-markdown.js +5 -4
  60. package/dist/core/extensions/activation-summary.d.ts +2 -0
  61. package/dist/core/extensions/activation-summary.js +6 -2
  62. package/dist/core/extensions/capability-usage.d.ts +1 -1
  63. package/dist/core/extensions/capability-usage.js +4 -3
  64. package/dist/core/extensions/contribution-inventory.d.ts +1 -0
  65. package/dist/core/extensions/contribution-inventory.js +6 -2
  66. package/dist/core/extensions/extension-registries.js +3 -2
  67. package/dist/core/extensions/extension-types.d.ts +83 -1
  68. package/dist/core/extensions/extension-types.js +3 -2
  69. package/dist/core/extensions/loader.d.ts +2 -8
  70. package/dist/core/extensions/loader.js +53 -144
  71. package/dist/core/extensions/registration-values.d.ts +43 -0
  72. package/dist/core/extensions/registration-values.js +210 -0
  73. package/dist/core/extensions/reserved-host-flags.js +3 -2
  74. package/dist/core/extensions/runtime-registrations.d.ts +3 -1
  75. package/dist/core/extensions/runtime-registrations.js +12 -2
  76. package/dist/core/fs/fs-utils.d.ts +2 -0
  77. package/dist/core/fs/fs-utils.js +7 -3
  78. package/dist/core/history/drift-cache.js +4 -3
  79. package/dist/core/history/drift-scan.js +16 -17
  80. package/dist/core/history/history.d.ts +8 -0
  81. package/dist/core/history/history.js +57 -7
  82. package/dist/core/history/replay.d.ts +9 -1
  83. package/dist/core/history/replay.js +48 -13
  84. package/dist/core/lock/lock-gc.js +8 -13
  85. package/dist/core/lock/lock.js +5 -4
  86. package/dist/core/output/output.d.ts +2 -0
  87. package/dist/core/output/output.js +46 -14
  88. package/dist/core/search/vector-stores.js +6 -11
  89. package/dist/core/shared/command-types.d.ts +2 -0
  90. package/dist/core/shared/command-types.js +2 -2
  91. package/dist/core/shared/errors.d.ts +5 -0
  92. package/dist/core/shared/errors.js +2 -2
  93. package/dist/core/store/item-metadata-cache.d.ts +1 -1
  94. package/dist/core/store/item-metadata-cache.js +16 -9
  95. package/dist/core/telemetry/runtime.d.ts +6 -0
  96. package/dist/core/telemetry/runtime.js +101 -30
  97. package/dist/sdk/agent/command-recovery.d.ts +2 -0
  98. package/dist/sdk/agent/command-recovery.js +10 -2
  99. package/dist/sdk/annotations.js +4 -4
  100. package/dist/sdk/cli-bootstrap.d.ts +2 -0
  101. package/dist/sdk/cli-bootstrap.js +6 -2
  102. package/dist/sdk/cli-contracts/completeness.js +10 -3
  103. package/dist/sdk/cli-contracts/enum-contracts.d.ts +1 -1
  104. package/dist/sdk/cli-contracts/flag-contracts.js +9 -3
  105. package/dist/sdk/cli-contracts/registration-helpers.d.ts +20 -3
  106. package/dist/sdk/cli-contracts/registration-helpers.js +18 -12
  107. package/dist/sdk/cli-contracts/schemas/assurance-definition.d.ts +40 -6
  108. package/dist/sdk/cli-contracts/schemas/assurance-definition.js +27 -3
  109. package/dist/sdk/cli-contracts/tool-option-contracts.js +3 -2
  110. package/dist/sdk/cli-contracts/tool-parameter-tables.js +55 -5
  111. package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
  112. package/dist/sdk/cli-contracts/tool-schema.js +22 -4
  113. package/dist/sdk/cli-program.js +3 -2
  114. package/dist/sdk/comments.js +4 -3
  115. package/dist/sdk/compile-cache.js +4 -3
  116. package/dist/sdk/completion.js +16 -7
  117. package/dist/sdk/compose.d.ts +3 -1
  118. package/dist/sdk/compose.js +16 -2
  119. package/dist/sdk/context-intent-contracts.js +45 -4
  120. package/dist/sdk/context-signal-store.js +4 -3
  121. package/dist/sdk/core.d.ts +1 -0
  122. package/dist/sdk/core.js +3 -2
  123. package/dist/sdk/extension/install-runtime.js +4 -4
  124. package/dist/sdk/extension/managed-state.js +4 -3
  125. package/dist/sdk/extension-contracts.d.ts +1 -1
  126. package/dist/sdk/flag-invocation-contracts.js +5 -6
  127. package/dist/sdk/generated/generated-error-code-catalog-part-1.d.ts +8 -0
  128. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1597 -0
  129. package/dist/sdk/generated/generated-error-code-catalog-part-2.d.ts +8 -0
  130. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1608 -0
  131. package/dist/sdk/generated-error-code-catalog.js +6 -3140
  132. package/dist/sdk/governance/assurance-action.d.ts +27 -3
  133. package/dist/sdk/governance/assurance-action.js +117 -31
  134. package/dist/sdk/governance/assurance-mutation-error.d.ts +14 -0
  135. package/dist/sdk/governance/assurance-mutation-error.js +58 -0
  136. package/dist/sdk/governance/assurance-presets.d.ts +54 -0
  137. package/dist/sdk/governance/assurance-presets.js +136 -0
  138. package/dist/sdk/governance/assurance-runtime.d.ts +4 -0
  139. package/dist/sdk/governance/assurance-runtime.js +118 -14
  140. package/dist/sdk/governance/assurance.d.ts +51 -6
  141. package/dist/sdk/governance/assurance.js +321 -67
  142. package/dist/sdk/governance/gc.js +5 -11
  143. package/dist/sdk/governance/health.d.ts +3 -1
  144. package/dist/sdk/governance/health.js +24 -7
  145. package/dist/sdk/governance/validate-item-reader.js +3 -3
  146. package/dist/sdk/governance/validate.d.ts +2 -0
  147. package/dist/sdk/governance/validate.js +15 -5
  148. package/dist/sdk/graph/durable-cache.d.ts +1 -1
  149. package/dist/sdk/graph/durable-cache.js +156 -29
  150. package/dist/sdk/graph/governance-contracts.d.ts +12 -0
  151. package/dist/sdk/graph/governance-contracts.js +27 -0
  152. package/dist/sdk/graph/governance.d.ts +44 -2
  153. package/dist/sdk/graph/governance.js +156 -12
  154. package/dist/sdk/guide.js +4 -4
  155. package/dist/sdk/index.d.ts +3 -2
  156. package/dist/sdk/index.js +5 -4
  157. package/dist/sdk/lifecycle/claim.js +10 -3
  158. package/dist/sdk/linked-artifacts.d.ts +2 -0
  159. package/dist/sdk/linked-artifacts.js +29 -4
  160. package/dist/sdk/merge/driver.d.ts +2 -2
  161. package/dist/sdk/merge/driver.js +3 -3
  162. package/dist/sdk/merge/install.js +5 -4
  163. package/dist/sdk/merge/receipts.d.ts +6 -4
  164. package/dist/sdk/merge/receipts.js +7 -5
  165. package/dist/sdk/merge/three-way.d.ts +2 -2
  166. package/dist/sdk/merge/three-way.js +3 -3
  167. package/dist/sdk/output-projection.d.ts +9 -1
  168. package/dist/sdk/output-projection.js +45 -9
  169. package/dist/sdk/query/activity.d.ts +49 -2
  170. package/dist/sdk/query/activity.js +185 -38
  171. package/dist/sdk/query/get.js +19 -2
  172. package/dist/sdk/read-output-contracts.d.ts +5 -1
  173. package/dist/sdk/read-output-contracts.js +17 -2
  174. package/dist/sdk/read-output-session.d.ts +2 -0
  175. package/dist/sdk/read-output-session.js +12 -5
  176. package/dist/sdk/relationship-history.js +5 -5
  177. package/dist/sdk/runtime-primitives.d.ts +3 -3
  178. package/dist/sdk/runtime-primitives.js +5 -5
  179. package/dist/sdk/runtime-stats-options.js +3 -2
  180. package/dist/sdk/runtime.js +5 -9
  181. package/dist/sdk/schema-migration.js +4 -4
  182. package/dist/sdk/stats.d.ts +36 -3
  183. package/dist/sdk/stats.js +55 -15
  184. package/dist/sdk/test/execution.d.ts +4 -0
  185. package/dist/sdk/test/execution.js +88 -23
  186. package/dist/sdk/workspace-memory.js +4 -4
  187. package/dist/sdk/workspace-snapshot.js +8 -14
  188. package/dist/sdk/workspace-transaction-gc.js +4 -9
  189. package/dist/sdk/workspace-transaction.js +4 -4
  190. package/dist/sdk/workspace.js +4 -3
  191. package/dist/types.d.ts +2 -0
  192. package/dist/types.js +2 -2
  193. package/docs/ASSURANCE.md +170 -14
  194. package/docs/COMMANDS.md +29 -8
  195. package/docs/CONFIGURATION.md +9 -0
  196. package/docs/DEPENDENCY_KIND_CONTRACT.md +10 -8
  197. package/docs/EXTENSIONS.md +11 -12
  198. package/docs/MERGE_SAFETY.md +3 -1
  199. package/docs/OUTPUT_PROJECTION_CONTRACTS.md +26 -13
  200. package/docs/README.md +2 -0
  201. package/docs/READ_OUTPUT_CONTRACTS.md +17 -2
  202. package/docs/RELATIONSHIP_GRAPH.md +15 -0
  203. package/docs/SDK.md +17 -3
  204. package/docs/SDK_AGENT_SESSION_CONTEXT.md +12 -1
  205. package/docs/SDK_CONTEXT_EVIDENCE_CONTRACTS.md +53 -0
  206. package/docs/SDK_CONTEXT_INTEGRITY.md +28 -1
  207. package/docs/TESTING.md +24 -15
  208. package/docs/TRUSTWORTHY_CONTEXT_EVIDENCE.md +106 -0
  209. package/docs/performance/sdk-entrypoint-import-costs.md +6 -2
  210. package/marketplace.json +2 -2
  211. package/package.json +6 -6
  212. package/packages/pm-beads/package.json +1 -1
  213. package/packages/pm-calendar/package.json +1 -1
  214. package/packages/pm-command-kit/package.json +1 -1
  215. package/packages/pm-digital-twin/package.json +1 -1
  216. package/packages/pm-governance-audit/package.json +1 -1
  217. package/packages/pm-guide-shell/package.json +1 -1
  218. package/packages/pm-kanban/package.json +1 -1
  219. package/packages/pm-lifecycle-hooks/package.json +1 -1
  220. package/packages/pm-linked-test-adapters/package.json +1 -1
  221. package/packages/pm-search-advanced/package.json +1 -1
  222. package/packages/pm-templates/package.json +1 -1
  223. package/packages/pm-todos/package.json +1 -1
  224. package/packages/pm-vcs/package.json +1 -1
  225. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  226. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  227. package/sdk/public-surface.json +384 -83
  228. package/dist/cli-bundle/chunks/chunk-2OOU2AFH.js +0 -8
  229. package/dist/cli-bundle/chunks/chunk-2RA2YTHJ.js +0 -33
  230. package/dist/cli-bundle/chunks/chunk-DSKYD4LG.js +0 -13
  231. package/dist/cli-bundle/chunks/chunk-HC3GM6YY.js +0 -194
  232. package/dist/cli-bundle/chunks/chunk-IUNRVDAI.js +0 -2
  233. package/dist/cli-bundle/chunks/chunk-W7CPZGSB.js +0 -2
  234. package/dist/cli-bundle/chunks/chunk-ZXOC6HG5.js +0 -5
  235. package/dist/cli-bundle/chunks/register-list-query-HAUXVUPY.js +0 -10
  236. package/dist/cli-bundle/chunks/register-mutation-7Y5U7ABT.js +0 -20
  237. package/dist/cli-bundle/chunks/register-operations-EQX3DLAZ.js +0 -2
  238. package/dist/cli-bundle/focused-chunks/chunk-3AYKXMYX.js +0 -2
  239. package/dist/cli-bundle/focused-chunks/chunk-4AHEC74N.js +0 -2
  240. package/dist/cli-bundle/focused-chunks/chunk-4SV3ZKG3.js +0 -16
  241. package/dist/cli-bundle/focused-chunks/chunk-6JW6Z2P5.js +0 -8
  242. package/dist/cli-bundle/focused-chunks/chunk-D4PIWE6D.js +0 -2
  243. package/dist/cli-bundle/focused-chunks/chunk-DPO7TEFX.js +0 -31
  244. package/dist/cli-bundle/focused-chunks/chunk-E3KCL7AY.js +0 -5
  245. package/dist/cli-bundle/focused-chunks/chunk-ERAAFLPS.js +0 -153
  246. package/dist/cli-bundle/focused-chunks/chunk-F6E4JZKG.js +0 -2
  247. package/dist/cli-bundle/focused-chunks/chunk-FPSYO5ZG.js +0 -18
  248. package/dist/cli-bundle/focused-chunks/chunk-KDH5OX3D.js +0 -4
  249. package/dist/cli-bundle/focused-chunks/chunk-OJ3LVVIS.js +0 -2
  250. package/dist/cli-bundle/focused-chunks/chunk-VABB4L5Y.js +0 -17
@@ -7,12 +7,15 @@
7
7
  * one deterministic SDK path.
8
8
  */
9
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]="2c6c336c-abaa-53f7-94ac-ba4d5ab7c76a")}catch(e){}}();
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]="e491ddd9-60e7-51fd-b0c7-4f6e86ee56f9")}catch(e){}}();
11
+ import { createHash } from "node:crypto";
11
12
  import path from "node:path";
12
13
  import { EXIT_CODE } from "../../core/shared/constants.js";
13
14
  import { PmCliError } from "../../core/shared/errors.js";
14
- import { appendWorkspaceAuditEvent, mutateWorkspaceJsonWithHistory, readFileIfExists, readHistoryEntries, resolveAuthor, stableStringify, WORKSPACE_HISTORY_ID, getWorkspaceHistoryPath, } from "../runtime-primitives.js";
15
+ import { appendWorkspaceAuditEvent, mutateWorkspaceJsonWithHistory, readFileIfExists, readHistoryEntries, readSettings, resolveAuthor, stableStringify, WORKSPACE_HISTORY_ID, getWorkspaceHistoryPath, } from "../runtime-primitives.js";
16
+ import { resolveCanonicalRelationshipKind } from "../relationships.js";
15
17
  import { MAX_ASSURANCE_VERDICT_LIMIT } from "./assurance-limits.js";
18
+ import { AssuranceEvaluationRefusalError, AssuranceMutationRefusalError, } from "./assurance-mutation-error.js";
16
19
  /** Current serialized assurance registry format. */
17
20
  export const ASSURANCE_DOCUMENT_VERSION = 1;
18
21
  /** Supported lifecycle points that can evaluate a gate. */
@@ -43,18 +46,18 @@ const EVALUATION_CONCURRENCY = 4;
43
46
  const DEFAULT_VERDICT_LIMIT = 50;
44
47
  function requireStableId(value, field) {
45
48
  if (!ID_PATTERN.test(value)) {
46
- throw new TypeError(`${field} must be a stable lowercase id`);
49
+ throw new AssuranceMutationRefusalError(`${field} must be a stable lowercase id`);
47
50
  }
48
51
  }
49
52
  function requireFiniteNonNegative(value, field) {
50
53
  if (!Number.isFinite(value) || value < 0) {
51
- throw new TypeError(`${field} must be a finite non-negative number`);
54
+ throw new AssuranceMutationRefusalError(`${field} must be a finite non-negative number`);
52
55
  }
53
56
  }
54
57
  function boundFor(assertion) {
55
58
  const present = POLARITY_KEYS.filter((key) => assertion[key] !== undefined);
56
59
  if (present.length !== 1) {
57
- throw new TypeError(`assertion ${assertion.id} must declare exactly one polarity`);
60
+ throw new AssuranceMutationRefusalError(`assertion ${assertion.id} must declare exactly one polarity`);
58
61
  }
59
62
  const polarity = present[0];
60
63
  const raw = assertion[polarity];
@@ -79,10 +82,12 @@ function compareBound(observed, bound) {
79
82
  return false;
80
83
  }
81
84
  const numericBound = bound.value;
82
- if (bound.polarity === "ceiling" || bound.polarity === "monotone_nonincreasing") {
85
+ if (bound.polarity === "ceiling" ||
86
+ bound.polarity === "monotone_nonincreasing") {
83
87
  return observed <= numericBound;
84
88
  }
85
- if (bound.polarity === "floor" || bound.polarity === "monotone_nondecreasing") {
89
+ if (bound.polarity === "floor" ||
90
+ bound.polarity === "monotone_nondecreasing") {
86
91
  return observed >= numericBound;
87
92
  }
88
93
  return observed === 0;
@@ -97,12 +102,15 @@ function proveNegativeControl(assertion) {
97
102
  function boundUpdateIsLoosening(beforeBound, afterBound) {
98
103
  if (beforeBound.polarity !== afterBound.polarity)
99
104
  return true;
100
- if (typeof beforeBound.value === "number" && typeof afterBound.value === "number") {
101
- return (beforeBound.polarity === "ceiling" || beforeBound.polarity === "monotone_nonincreasing"
105
+ if (typeof beforeBound.value === "number" &&
106
+ typeof afterBound.value === "number") {
107
+ return beforeBound.polarity === "ceiling" ||
108
+ beforeBound.polarity === "monotone_nonincreasing"
102
109
  ? afterBound.value > beforeBound.value
103
- : beforeBound.polarity === "floor" || beforeBound.polarity === "monotone_nondecreasing"
110
+ : beforeBound.polarity === "floor" ||
111
+ beforeBound.polarity === "monotone_nondecreasing"
104
112
  ? afterBound.value < beforeBound.value
105
- : afterBound.value !== beforeBound.value);
113
+ : afterBound.value !== beforeBound.value;
106
114
  }
107
115
  if (beforeBound.polarity === "subset_of") {
108
116
  const beforeValues = beforeBound.value;
@@ -125,11 +133,28 @@ export function assuranceAssertionUpdateIsLoosening(before, after) {
125
133
  warn: 1,
126
134
  block: 2,
127
135
  };
128
- const lifetimeLoosened = (before.lifetime ?? "hold") === "hold" && (after.lifetime ?? "hold") === "retire";
136
+ const lifetimeLoosened = (before.lifetime ?? "hold") === "hold" &&
137
+ (after.lifetime ?? "hold") === "retire";
129
138
  return (boundUpdateIsLoosening(boundFor(before), boundFor(after)) ||
130
139
  enforcementRank[after.enforcement] < enforcementRank[before.enforcement] ||
131
140
  lifetimeLoosened);
132
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
+ }
133
158
  /** Validate and return one measurement declaration. */
134
159
  export function validateMeasurementDefinition(definition) {
135
160
  requireStableId(definition.id, "measurement.id");
@@ -137,14 +162,19 @@ export function validateMeasurementDefinition(definition) {
137
162
  requireFiniteNonNegative(definition.max_cost, "measurement.max_cost");
138
163
  }
139
164
  const source = definition.source;
140
- if (source.kind === "items" && source.field !== undefined && source.equals === undefined) {
141
- throw new TypeError("items source with field requires equals");
165
+ if (source.kind === "items") {
166
+ validateItemsMeasurementSource(source);
142
167
  }
143
- if (source.kind === "dependency_kind" && source.dependency_kind.trim().length === 0) {
144
- throw new TypeError("dependency_kind source requires dependency_kind");
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");
145
172
  }
146
- if ((source.kind === "graph" || source.kind === "validate" || source.kind === "health") && source.field.trim().length === 0) {
147
- throw new TypeError(`${source.kind} source requires field`);
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`);
148
178
  }
149
179
  if (source.kind === "provider") {
150
180
  requireStableId(source.provider, "provider source provider");
@@ -156,72 +186,108 @@ export function validateMeasurementDefinition(definition) {
156
186
  export function validateAssertionDefinition(definition) {
157
187
  requireStableId(definition.id, "assertion.id");
158
188
  requireStableId(definition.measurement_id, "assertion.measurement_id");
159
- if (definition.owner_item_id.trim().length === 0) {
160
- throw new TypeError("assertion.owner_item_id is required");
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");
161
192
  }
162
193
  const bound = boundFor(definition);
163
194
  if (typeof bound.value === "number" && !Number.isFinite(bound.value)) {
164
- throw new TypeError(`assertion.${bound.polarity} must be a finite number`);
195
+ throw new AssuranceMutationRefusalError(`assertion.${bound.polarity} must be a finite number`);
165
196
  }
166
- if (definition.lifetime === "retire" && !definition.retire_reason?.trim()) {
167
- throw new TypeError("retired assertion requires retire_reason");
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");
168
201
  }
169
202
  if (definition.scope.kind === "filter") {
170
203
  requireStableId(definition.scope.measurement_id, "assertion.scope.measurement_id");
171
204
  }
172
205
  const cases = definition.negative_control?.cases;
173
- if (!Array.isArray(cases) || !cases.some((entry) => entry.expected === "pass") || !cases.some((entry) => entry.expected === "fail")) {
174
- throw new TypeError("assertion negative control requires pass and fail 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");
175
210
  }
176
211
  if (!proveNegativeControl(definition)) {
177
- throw new TypeError(`assertion ${definition.id} negative control does not prove its bound`);
212
+ throw new AssuranceMutationRefusalError(`assertion ${definition.id} negative control does not prove its bound`);
178
213
  }
179
214
  return definition;
180
215
  }
216
+ function validateGateProviderPolicy(policy) {
217
+ if (!Array.isArray(policy.allowed_providers)) {
218
+ throw new AssuranceMutationRefusalError("gate provider_policy.allowed_providers must be an array");
219
+ }
220
+ for (const provider of policy.allowed_providers) {
221
+ requireStableId(provider, "gate.provider_policy.allowed_provider");
222
+ }
223
+ if (typeof policy.triggers !== "object" ||
224
+ policy.triggers === null ||
225
+ Array.isArray(policy.triggers)) {
226
+ throw new AssuranceMutationRefusalError("gate provider_policy.triggers must be an object");
227
+ }
228
+ for (const [trigger, rule] of Object.entries(policy.triggers)) {
229
+ if (!ASSURANCE_GATE_TRIGGERS.includes(trigger)) {
230
+ throw new AssuranceMutationRefusalError(`gate provider policy has unknown trigger ${trigger}`);
231
+ }
232
+ if (typeof rule !== "object" ||
233
+ rule === null ||
234
+ !["low", "medium", "high"].includes(rule.max_cost_class) ||
235
+ typeof rule.allow_network !== "boolean") {
236
+ throw new AssuranceMutationRefusalError(`gate provider policy trigger ${trigger} is invalid`);
237
+ }
238
+ }
239
+ }
181
240
  /** Validate and return one gate declaration. */
182
241
  export function validateGateDefinition(definition) {
183
242
  requireStableId(definition.id, "gate.id");
184
243
  if (definition.assertion_ids.length === 0) {
185
- throw new TypeError("gate requires at least one assertion");
244
+ throw new AssuranceMutationRefusalError("gate requires at least one assertion");
186
245
  }
187
246
  for (const id of definition.assertion_ids)
188
247
  requireStableId(id, "gate.assertion_id");
189
248
  if (definition.triggers.length === 0) {
190
- throw new TypeError("gate requires at least one trigger");
249
+ throw new AssuranceMutationRefusalError("gate requires at least one trigger");
250
+ }
251
+ if (definition.provider_policy) {
252
+ validateGateProviderPolicy(definition.provider_policy);
191
253
  }
192
254
  return definition;
193
255
  }
194
256
  /** Validate registry references, uniqueness, and declaration-local contracts. */
195
257
  export function validateAssuranceDocument(document) {
196
258
  if (document.version !== ASSURANCE_DOCUMENT_VERSION) {
197
- throw new TypeError(`unsupported assurance document version ${String(document.version)}`);
259
+ throw new AssuranceMutationRefusalError(`unsupported assurance document version ${String(document.version)}`);
198
260
  }
199
261
  const ids = new Set();
200
262
  for (const definition of document.measurements) {
201
263
  validateMeasurementDefinition(definition);
202
- if (ids.has(`measurement:${definition.id}`))
203
- throw new TypeError(`duplicate measurement ${definition.id}`);
264
+ if (ids.has(`measurement:${definition.id}`)) {
265
+ throw new AssuranceMutationRefusalError(`duplicate measurement ${definition.id}`);
266
+ }
204
267
  ids.add(`measurement:${definition.id}`);
205
268
  }
206
269
  for (const definition of document.assertions) {
207
270
  validateAssertionDefinition(definition);
208
- if (ids.has(`assertion:${definition.id}`))
209
- throw new TypeError(`duplicate assertion ${definition.id}`);
271
+ if (ids.has(`assertion:${definition.id}`)) {
272
+ throw new AssuranceMutationRefusalError(`duplicate assertion ${definition.id}`);
273
+ }
210
274
  ids.add(`assertion:${definition.id}`);
211
275
  }
212
276
  for (const definition of document.gates) {
213
277
  validateGateDefinition(definition);
214
278
  const missing = definition.assertion_ids.filter((id) => !document.assertions.some((assertionDefinition) => assertionDefinition.id === id));
215
- if (missing.length > 0)
216
- throw new TypeError(`gate ${definition.id} references missing assertions: ${missing.join(", ")}`);
217
- if (ids.has(`gate:${definition.id}`))
218
- throw new TypeError(`duplicate gate ${definition.id}`);
279
+ if (missing.length > 0) {
280
+ throw new AssuranceMutationRefusalError(`gate ${definition.id} references missing assertions: ${missing.join(", ")}`);
281
+ }
282
+ if (ids.has(`gate:${definition.id}`)) {
283
+ throw new AssuranceMutationRefusalError(`duplicate gate ${definition.id}`);
284
+ }
219
285
  ids.add(`gate:${definition.id}`);
220
286
  }
221
287
  const measurementIds = new Set(document.measurements.map((entry) => entry.id));
222
288
  for (const [measurementId, consumers] of collectMeasurementReferences(document)) {
223
289
  if (!measurementIds.has(measurementId)) {
224
- throw new TypeError(`${consumers.join(", ")} references missing measurement ${measurementId}`);
290
+ throw new AssuranceMutationRefusalError(`${consumers.join(", ")} references missing measurement ${measurementId}`);
225
291
  }
226
292
  }
227
293
  return document;
@@ -261,17 +327,60 @@ function collectMeasurementReferences(document) {
261
327
  }
262
328
  return references;
263
329
  }
330
+ function collectGateProviderIds(gate, document) {
331
+ const pending = gate.assertion_ids.flatMap((assertionId) => {
332
+ const assertion = document.assertions.find((entry) => entry.id === assertionId);
333
+ return [
334
+ assertion.measurement_id,
335
+ ...(assertion.scope.kind === "filter"
336
+ ? [assertion.scope.measurement_id]
337
+ : []),
338
+ ];
339
+ });
340
+ const visited = new Set();
341
+ const providers = new Set();
342
+ while (pending.length > 0) {
343
+ const measurementId = pending.pop();
344
+ if (visited.has(measurementId))
345
+ continue;
346
+ visited.add(measurementId);
347
+ const measurement = document.measurements.find((entry) => entry.id === measurementId);
348
+ if (measurement.source.kind === "provider") {
349
+ providers.add(measurement.source.provider);
350
+ }
351
+ else if (measurement.source.kind === "derived") {
352
+ collectExpressionMeasurementReferences(measurement.source.expression, pending);
353
+ }
354
+ }
355
+ return providers;
356
+ }
264
357
  /** Create an empty, valid assurance registry. */
265
358
  export function createEmptyAssuranceDocument() {
266
- return { version: ASSURANCE_DOCUMENT_VERSION, measurements: [], assertions: [], gates: [] };
359
+ return {
360
+ version: ASSURANCE_DOCUMENT_VERSION,
361
+ measurements: [],
362
+ assertions: [],
363
+ gates: [],
364
+ };
267
365
  }
268
366
  function itemsSourceResult(source, context) {
269
- const matches = context.items.filter((item) => (source.statuses === undefined || source.statuses.includes(item.status)) &&
367
+ const matches = context.items.filter((item) => (source.statuses === undefined ||
368
+ source.statuses.includes(item.status)) &&
270
369
  (source.types === undefined || source.types.includes(item.type)) &&
271
370
  (source.tags === undefined ||
272
371
  source.tags.every((tag) => item.tags?.includes(tag))) &&
273
372
  (source.field === undefined ||
274
- valuesEqual(item[source.field], source.equals)));
373
+ (() => {
374
+ const value = item[source.field];
375
+ if (source.state !== undefined) {
376
+ const missing = value === undefined ||
377
+ value === null ||
378
+ value === "" ||
379
+ (Array.isArray(value) && value.length === 0);
380
+ return source.state === "missing" ? missing : !missing;
381
+ }
382
+ return valuesEqual(value, source.equals);
383
+ })()));
275
384
  return {
276
385
  value: matches.length,
277
386
  population_size: context.items.length,
@@ -281,9 +390,13 @@ function itemsSourceResult(source, context) {
281
390
  }
282
391
  function dependencyKindSourceResult(source, context) {
283
392
  const contributors = [];
393
+ const requestedKind = resolveCanonicalRelationshipKind(source.dependency_kind) ??
394
+ source.dependency_kind.trim().toLowerCase();
284
395
  for (const item of context.items) {
285
396
  for (const dependency of item.dependencies ?? []) {
286
- if (dependency.kind === source.dependency_kind) {
397
+ const dependencyKind = resolveCanonicalRelationshipKind(dependency.kind) ??
398
+ dependency.kind.trim().toLowerCase();
399
+ if (dependencyKind === requestedKind) {
287
400
  contributors.push(`${item.id}->${dependency.id}`);
288
401
  }
289
402
  }
@@ -340,7 +453,13 @@ function sumCosts(costs, durationMs) {
340
453
  history_entries: total.history_entries + cost.history_entries,
341
454
  provider_calls: total.provider_calls + cost.provider_calls,
342
455
  duration_ms: durationMs,
343
- }), { units: 0, items_scanned: 0, history_entries: 0, provider_calls: 0, duration_ms: durationMs });
456
+ }), {
457
+ units: 0,
458
+ items_scanned: 0,
459
+ history_entries: 0,
460
+ provider_calls: 0,
461
+ duration_ms: durationMs,
462
+ });
344
463
  }
345
464
  function arithmeticValue(operator, values) {
346
465
  if (values.length === 0)
@@ -373,7 +492,12 @@ async function mapWithConcurrency(values, limit, transform) {
373
492
  }
374
493
  async function evaluateExpression(expression, context, definitions, stack, state) {
375
494
  if ("literal" in expression)
376
- return { value: expression.literal, costs: [], population_size: 0, contributors: [] };
495
+ return {
496
+ value: expression.literal,
497
+ costs: [],
498
+ population_size: 0,
499
+ contributors: [],
500
+ };
377
501
  if ("measurement" in expression) {
378
502
  const definition = definitions.find((entry) => entry.id === expression.measurement);
379
503
  if (!definition)
@@ -383,7 +507,12 @@ async function evaluateExpression(expression, context, definitions, stack, state
383
507
  throw new TypeError(`derived measurement ${definition.id} is not numeric`);
384
508
  const costs = state.charged.has(definition.id) ? [] : [result.cost];
385
509
  state.charged.add(definition.id);
386
- return { value: result.value, costs, population_size: result.population_size, contributors: result.contributors };
510
+ return {
511
+ value: result.value,
512
+ costs,
513
+ population_size: result.population_size,
514
+ contributors: result.contributors,
515
+ };
387
516
  }
388
517
  const operands = await mapWithConcurrency(expression.operands, EVALUATION_CONCURRENCY, (operand) => evaluateExpression(operand, context, definitions, stack, state));
389
518
  return {
@@ -401,7 +530,12 @@ async function evaluateMeasurementInternal(definition, context, definitions, sta
401
530
  let providerCalls = 0;
402
531
  if (definition.source.kind === "derived") {
403
532
  const derived = await evaluateExpression(definition.source.expression, context, definitions, [...stack, definition.id], state);
404
- result = { value: derived.value, population_size: derived.population_size, cost: 0, contributors: derived.contributors };
533
+ result = {
534
+ value: derived.value,
535
+ population_size: derived.population_size,
536
+ cost: 0,
537
+ contributors: derived.contributors,
538
+ };
405
539
  nestedCosts = derived.costs;
406
540
  }
407
541
  else {
@@ -422,16 +556,29 @@ async function evaluateMeasurementInternal(definition, context, definitions, sta
422
556
  }
423
557
  const ownCost = {
424
558
  units: result.cost,
425
- items_scanned: definition.source.kind === "items" || definition.source.kind === "dependency_kind" || definition.source.kind === "links" ? context.items.length : 0,
559
+ items_scanned: definition.source.kind === "items" ||
560
+ definition.source.kind === "dependency_kind" ||
561
+ definition.source.kind === "links"
562
+ ? context.items.length
563
+ : 0,
426
564
  history_entries: definition.source.kind === "history" ? context.history.length : 0,
427
565
  provider_calls: providerCalls,
428
566
  duration_ms: Date.now() - startedAt,
429
567
  };
430
568
  const cost = sumCosts([...nestedCosts, ownCost], Date.now() - startedAt);
431
569
  if (definition.max_cost !== undefined && cost.units > definition.max_cost) {
432
- throw new TypeError(`measurement ${definition.id} exceeded cost ceiling ${definition.max_cost} with ${cost.units}`);
570
+ throw new AssuranceEvaluationRefusalError(`measurement ${definition.id} exceeded cost ceiling ${definition.max_cost} with ${cost.units}`);
433
571
  }
434
- return { id: definition.id, value: result.value, population_size: result.population_size, cost, contributors: result.contributors ?? [] };
572
+ return {
573
+ id: definition.id,
574
+ definition_fingerprint: `sha256:${createHash("sha256")
575
+ .update(stableStringify(definition))
576
+ .digest("hex")}`,
577
+ value: result.value,
578
+ population_size: result.population_size,
579
+ cost,
580
+ contributors: result.contributors ?? [],
581
+ };
435
582
  }
436
583
  function evaluateMeasurementCached(definition, context, definitions, stack, state) {
437
584
  if (stack.includes(definition.id)) {
@@ -454,7 +601,8 @@ export async function evaluateMeasurement(definition, context, definitions = [de
454
601
  function assertionDistance(observed, bound) {
455
602
  if (typeof observed !== "number" || typeof bound.value !== "number")
456
603
  return null;
457
- if (bound.polarity === "ceiling" || bound.polarity === "monotone_nonincreasing")
604
+ if (bound.polarity === "ceiling" ||
605
+ bound.polarity === "monotone_nonincreasing")
458
606
  return bound.value - observed;
459
607
  if (bound.polarity === "floor" || bound.polarity === "monotone_nondecreasing")
460
608
  return observed - bound.value;
@@ -468,18 +616,54 @@ export function evaluateAssuranceAssertion(definition, measurement, options = {}
468
616
  return {
469
617
  assertion_id: definition.id,
470
618
  measurement_id: definition.measurement_id,
619
+ measurement_definition_fingerprint: measurement.definition_fingerprint,
471
620
  scope: definition.scope,
472
621
  population_size: measurement.population_size,
473
622
  observed: measurement.value,
474
623
  bound,
475
624
  distance: assertionDistance(measurement.value, bound),
476
- verdict: retired ? "retired" : compareBound(measurement.value, bound) ? "pass" : "fail",
625
+ verdict: retired
626
+ ? "retired"
627
+ : compareBound(measurement.value, bound)
628
+ ? "pass"
629
+ : "fail",
477
630
  enforcement: definition.enforcement,
478
631
  negative_control_proven: proveNegativeControl(definition),
479
632
  cost: measurement.cost,
480
633
  contributors: measurement.contributors,
481
634
  };
482
635
  }
636
+ function enforceGateProviderPolicy(gate, document, context, trigger) {
637
+ const providerIds = collectGateProviderIds(gate, document);
638
+ if (providerIds.size === 0)
639
+ return;
640
+ const policy = gate.provider_policy;
641
+ const triggerPolicy = policy?.triggers[trigger];
642
+ if (!policy || !triggerPolicy) {
643
+ throw new AssuranceEvaluationRefusalError(`assurance gate ${gate.id} refuses provider execution at trigger ${trigger} without an explicit provider policy`);
644
+ }
645
+ const allowedProviders = new Set(policy.allowed_providers);
646
+ const costRank = {
647
+ low: 0,
648
+ medium: 1,
649
+ high: 2,
650
+ };
651
+ for (const providerId of providerIds) {
652
+ if (!allowedProviders.has(providerId)) {
653
+ throw new AssuranceEvaluationRefusalError(`assurance gate ${gate.id} refuses provider ${providerId}`);
654
+ }
655
+ const capability = context.provider_capabilities?.[providerId];
656
+ if (!capability) {
657
+ throw new AssuranceEvaluationRefusalError(`assurance provider ${providerId} has no declared capabilities`);
658
+ }
659
+ if (costRank[capability.cost_class] > costRank[triggerPolicy.max_cost_class]) {
660
+ throw new AssuranceEvaluationRefusalError(`assurance gate ${gate.id} refuses ${capability.cost_class}-cost provider ${providerId} at trigger ${trigger}`);
661
+ }
662
+ if (capability.network && !triggerPolicy.allow_network) {
663
+ throw new AssuranceEvaluationRefusalError(`assurance gate ${gate.id} refuses network provider ${providerId} at trigger ${trigger}`);
664
+ }
665
+ }
666
+ }
483
667
  /** Evaluate a named gate into one presentation-independent verdict document. */
484
668
  export async function evaluateAssuranceGate(gateId, document, context, options) {
485
669
  validateAssuranceDocument(document);
@@ -489,6 +673,7 @@ export async function evaluateAssuranceGate(gateId, document, context, options)
489
673
  if (!gateDefinition.triggers.includes(options.trigger)) {
490
674
  throw new TypeError(`assurance gate ${gateId} does not declare trigger ${options.trigger}`);
491
675
  }
676
+ enforceGateProviderPolicy(gateDefinition, document, context, options.trigger);
492
677
  const assertions = await mapWithConcurrency(gateDefinition.assertion_ids, EVALUATION_CONCURRENCY, async (id) => {
493
678
  const assertionDefinition = document.assertions.find((entry) => entry.id === id);
494
679
  const measurementDefinition = document.measurements.find((entry) => entry.id === assertionDefinition.measurement_id);
@@ -571,7 +756,11 @@ function validateForKind(kind, definition) {
571
756
  /** Read all declarations of one kind with a stable row selector receipt. */
572
757
  export async function listAssuranceDeclarations(pmRoot, kind) {
573
758
  const items = structuredClone(collectionFor(await readDocument(pmRoot), kind));
574
- return { items, count: items.length, row_contract: { row_keys: ["items"], jq_selector: ".items[]" } };
759
+ return {
760
+ items,
761
+ count: items.length,
762
+ row_contract: { row_keys: ["items"], jq_selector: ".items[]" },
763
+ };
575
764
  }
576
765
  /** Read one named declaration or fail loudly. */
577
766
  export async function getAssuranceDeclaration(pmRoot, kind, id) {
@@ -583,7 +772,8 @@ export async function getAssuranceDeclaration(pmRoot, kind, id) {
583
772
  /** Create or update one declaration through audited singleton persistence. */
584
773
  export async function putAssuranceDeclaration(pmRoot, kind, definition, options = {}) {
585
774
  validateForKind(kind, definition);
586
- const author = resolveAuthor(options.author, "unknown");
775
+ const settings = await readSettings(pmRoot);
776
+ const author = resolveAuthor(options.author, settings.author_default);
587
777
  const mutation = await mutateWorkspaceJsonWithHistory({
588
778
  pmRoot,
589
779
  filePath: assurancePath(pmRoot),
@@ -593,16 +783,20 @@ export async function putAssuranceDeclaration(pmRoot, kind, definition, options
593
783
  lockTtlSeconds: LOCK_TTL_SECONDS,
594
784
  lockWaitMs: LOCK_WAIT_MS,
595
785
  mutate: (beforeRaw) => {
596
- const document = beforeRaw === null ? createEmptyAssuranceDocument() : parseAssuranceDocument(beforeRaw);
786
+ const document = beforeRaw === null
787
+ ? createEmptyAssuranceDocument()
788
+ : parseAssuranceDocument(beforeRaw);
597
789
  const collection = collectionFor(document, kind);
598
790
  const index = collection.findIndex((entry) => entry.id === definition.id);
599
791
  const action = index < 0 ? "created" : "updated";
600
792
  if (kind === "assertion" &&
601
793
  index >= 0 &&
602
794
  assuranceAssertionUpdateIsLoosening(collection[index], definition)) {
603
- const decision = definition.authorization_decision;
604
- if (!decision || !options.authorized_decision_ids?.includes(decision)) {
605
- throw new TypeError(`loosening assertion ${definition.id} requires a verified authorization_decision`);
795
+ const decision = definition
796
+ .authorization_decision;
797
+ if (!decision ||
798
+ !options.authorized_decision_ids?.includes(decision)) {
799
+ throw new AssuranceMutationRefusalError(`loosening assertion ${definition.id} requires a verified authorization_decision`);
606
800
  }
607
801
  }
608
802
  if (index < 0)
@@ -618,9 +812,63 @@ export async function putAssuranceDeclaration(pmRoot, kind, definition, options
618
812
  });
619
813
  return { ...mutation.result, changed: mutation.changed };
620
814
  }
815
+ /** Apply a declaration bundle atomically, idempotently, and without replacing divergent ids. */
816
+ export async function putAssuranceDeclarationBundle(pmRoot, bundle, options = {}) {
817
+ const candidate = createEmptyAssuranceDocument();
818
+ candidate.measurements = structuredClone(bundle.measurements);
819
+ candidate.assertions = structuredClone(bundle.assertions);
820
+ candidate.gates = structuredClone(bundle.gates);
821
+ validateAssuranceDocument(candidate);
822
+ const settings = await readSettings(pmRoot);
823
+ const author = resolveAuthor(options.author, settings.author_default);
824
+ const mutation = await mutateWorkspaceJsonWithHistory({
825
+ pmRoot,
826
+ filePath: assurancePath(pmRoot),
827
+ op: "assurance:bundle:apply",
828
+ author,
829
+ message: options.message,
830
+ lockTtlSeconds: LOCK_TTL_SECONDS,
831
+ lockWaitMs: LOCK_WAIT_MS,
832
+ mutate: (beforeRaw) => {
833
+ const document = beforeRaw === null
834
+ ? createEmptyAssuranceDocument()
835
+ : parseAssuranceDocument(beforeRaw);
836
+ const createdIds = [];
837
+ const unchangedIds = [];
838
+ const apply = (target, additions, kind) => {
839
+ for (const definition of additions) {
840
+ const existing = target.find((entry) => entry.id === definition.id);
841
+ if (!existing) {
842
+ target.push(structuredClone(definition));
843
+ createdIds.push(`${kind}:${definition.id}`);
844
+ continue;
845
+ }
846
+ if (stableStringify(existing) !== stableStringify(definition)) {
847
+ throw new AssuranceMutationRefusalError(`assurance bundle refuses to replace divergent ${kind} ${definition.id}`);
848
+ }
849
+ unchangedIds.push(`${kind}:${definition.id}`);
850
+ }
851
+ };
852
+ apply(document.measurements, candidate.measurements, "measurement");
853
+ apply(document.assertions, candidate.assertions, "assertion");
854
+ apply(document.gates, candidate.gates, "gate");
855
+ validateAssuranceDocument(document);
856
+ return {
857
+ raw: `${JSON.stringify(document, null, 2)}\n`,
858
+ result: {
859
+ changed: createdIds.length > 0,
860
+ created_ids: createdIds,
861
+ unchanged_ids: unchangedIds,
862
+ },
863
+ };
864
+ },
865
+ });
866
+ return { ...mutation.result, changed: mutation.changed };
867
+ }
621
868
  /** Remove an unreferenced declaration through audited singleton persistence. */
622
869
  export async function removeAssuranceDeclaration(pmRoot, kind, id, options = {}) {
623
- const author = resolveAuthor(options.author, "unknown");
870
+ const settings = await readSettings(pmRoot);
871
+ const author = resolveAuthor(options.author, settings.author_default);
624
872
  const mutation = await mutateWorkspaceJsonWithHistory({
625
873
  pmRoot,
626
874
  filePath: assurancePath(pmRoot),
@@ -630,20 +878,24 @@ export async function removeAssuranceDeclaration(pmRoot, kind, id, options = {})
630
878
  lockTtlSeconds: LOCK_TTL_SECONDS,
631
879
  lockWaitMs: LOCK_WAIT_MS,
632
880
  mutate: (beforeRaw) => {
633
- const document = beforeRaw === null ? createEmptyAssuranceDocument() : parseAssuranceDocument(beforeRaw);
881
+ const document = beforeRaw === null
882
+ ? createEmptyAssuranceDocument()
883
+ : parseAssuranceDocument(beforeRaw);
634
884
  if (kind === "measurement") {
635
885
  const consumers = collectMeasurementReferences(document).get(id);
636
886
  if (consumers && consumers.length > 0) {
637
- throw new TypeError(`assurance measurement ${id} is referenced by ${consumers.join(", ")}`);
887
+ throw new AssuranceMutationRefusalError(`assurance measurement ${id} is referenced by ${consumers.join(", ")}`);
638
888
  }
639
889
  }
640
- if (kind === "assertion" && document.gates.some((entry) => entry.assertion_ids.includes(id))) {
641
- throw new TypeError(`assurance assertion ${id} is referenced by gate`);
890
+ if (kind === "assertion" &&
891
+ document.gates.some((entry) => entry.assertion_ids.includes(id))) {
892
+ throw new AssuranceMutationRefusalError(`assurance assertion ${id} is referenced by gate`);
642
893
  }
643
894
  const collection = collectionFor(document, kind);
644
895
  const index = collection.findIndex((entry) => entry.id === id);
645
- if (index < 0)
646
- throw new TypeError(`assurance ${kind} ${id} not found`);
896
+ if (index < 0) {
897
+ throw new AssuranceMutationRefusalError(`assurance ${kind} ${id} not found`);
898
+ }
647
899
  collection.splice(index, 1);
648
900
  validateAssuranceDocument(document);
649
901
  return {
@@ -658,12 +910,14 @@ export async function removeAssuranceDeclaration(pmRoot, kind, id, options = {})
658
910
  export async function recordAssuranceVerdict(pmRoot, verdict, options = {}) {
659
911
  if (verdict.dry_run)
660
912
  throw new TypeError("dry-run assurance verdicts are not persisted");
913
+ const settings = await readSettings(pmRoot);
661
914
  await appendWorkspaceAuditEvent({
662
915
  pmRoot,
663
916
  op: "assurance:gate:verdict",
664
- author: resolveAuthor(options.author, "unknown"),
917
+ author: resolveAuthor(options.author, settings.author_default),
665
918
  context: { assurance_verdict: verdict },
666
- message: options.message ?? `Assurance gate ${verdict.gate_id} returned ${verdict.verdict}`,
919
+ message: options.message ??
920
+ `Assurance gate ${verdict.gate_id} returned ${verdict.verdict}`,
667
921
  lockTtlSeconds: LOCK_TTL_SECONDS,
668
922
  lockWaitMs: LOCK_WAIT_MS,
669
923
  });
@@ -686,4 +940,4 @@ export async function listAssuranceVerdicts(pmRoot, options = {}) {
686
940
  .slice(0, limit);
687
941
  }
688
942
  //# sourceMappingURL=assurance.js.map
689
- //# debugId=2c6c336c-abaa-53f7-94ac-ba4d5ab7c76a
943
+ //# debugId=e491ddd9-60e7-51fd-b0c7-4f6e86ee56f9