@unbrained/pm-cli 2026.8.11 → 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 (184) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +52 -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 +80 -13
  6. package/dist/cli/main.js +34 -19
  7. package/dist/cli/register-annotations.js +56 -3
  8. package/dist/cli/register-list-query.js +10 -7
  9. package/dist/cli/register-operations.js +16 -6
  10. package/dist/cli-bundle/bundle-manifest.json +157 -157
  11. package/dist/cli-bundle/chunks/{chunk-2OOU2AFH.js → chunk-3PS7X6GS.js} +2 -2
  12. package/dist/cli-bundle/chunks/chunk-7KL6KMWF.js +13 -0
  13. package/dist/cli-bundle/chunks/{chunk-WMEZHCFI.js → chunk-BM5YJ2UW.js} +51 -43
  14. package/dist/cli-bundle/chunks/chunk-D55A5AEN.js +33 -0
  15. package/dist/cli-bundle/chunks/chunk-KIKDWECH.js +196 -0
  16. package/dist/cli-bundle/chunks/{chunk-ZU34C3LH.js → chunk-KOQMGCTA.js} +2 -2
  17. package/dist/cli-bundle/chunks/chunk-R76342NV.js +2 -0
  18. package/dist/cli-bundle/chunks/chunk-RBTK5EZV.js +5 -0
  19. package/dist/cli-bundle/chunks/{chunk-IUNRVDAI.js → chunk-VLUQOIDT.js} +2 -2
  20. package/dist/cli-bundle/chunks/{chunk-A2SJGHSI.js → chunk-WLAHPSOX.js} +2 -2
  21. package/dist/cli-bundle/chunks/register-list-query-XPCJRUUD.js +10 -0
  22. package/dist/cli-bundle/chunks/register-mutation-4RJCBKRS.js +20 -0
  23. package/dist/cli-bundle/chunks/register-operations-2OGISOVR.js +2 -0
  24. package/dist/cli-bundle/chunks/{register-setup-NOCOOWQY.js → register-setup-LLHAKP2V.js} +2 -2
  25. package/dist/cli-bundle/focused-chunks/chunk-2DALMVF7.js +5 -0
  26. package/dist/cli-bundle/focused-chunks/chunk-5OQIMELN.js +155 -0
  27. package/dist/cli-bundle/focused-chunks/chunk-5TAGTDRE.js +2 -0
  28. package/dist/cli-bundle/focused-chunks/{chunk-FPSYO5ZG.js → chunk-ECADZMVC.js} +11 -11
  29. package/dist/cli-bundle/focused-chunks/chunk-EJGXRA6B.js +2 -0
  30. package/dist/cli-bundle/focused-chunks/chunk-FFGSZCPK.js +16 -0
  31. package/dist/cli-bundle/focused-chunks/{chunk-BKPIY34S.js → chunk-GQW4BPUT.js} +2 -2
  32. package/dist/cli-bundle/focused-chunks/chunk-ISKOBJCM.js +2 -0
  33. package/dist/cli-bundle/focused-chunks/{chunk-UDAZWT25.js → chunk-JAFRIWVF.js} +2 -2
  34. package/dist/cli-bundle/focused-chunks/{chunk-2DL3MRTD.js → chunk-KHM2CS2C.js} +2 -2
  35. package/dist/cli-bundle/focused-chunks/{chunk-J5EKAIVD.js → chunk-KPSXVBDV.js} +2 -2
  36. package/dist/cli-bundle/focused-chunks/{chunk-23FTGO3W.js → chunk-KTLOKKCQ.js} +2 -2
  37. package/dist/cli-bundle/focused-chunks/chunk-LGYK3NUF.js +8 -0
  38. package/dist/cli-bundle/focused-chunks/{chunk-KDH5OX3D.js → chunk-NBLUKITQ.js} +2 -2
  39. package/dist/cli-bundle/focused-chunks/{chunk-55BIB6DV.js → chunk-NYLOUPOB.js} +2 -2
  40. package/dist/cli-bundle/focused-chunks/{chunk-DPO7TEFX.js → chunk-Q4T463SD.js} +28 -28
  41. package/dist/cli-bundle/focused-chunks/chunk-QVZVY53N.js +2 -0
  42. package/dist/cli-bundle/focused-chunks/{chunk-TL5M2PAY.js → chunk-RWPAWUOT.js} +2 -2
  43. package/dist/cli-bundle/focused-chunks/chunk-UTEWXDEQ.js +17 -0
  44. package/dist/cli-bundle/focused-chunks/{chunk-4H5IA6AV.js → chunk-YIAXMC3T.js} +2 -2
  45. package/dist/cli-bundle/focused-chunks/chunk-ZMGWAYH7.js +2 -0
  46. package/dist/cli-bundle/main.js +13 -13
  47. package/dist/cli-bundle/sdk-authoring.js +1 -1
  48. package/dist/cli-bundle/sdk-contracts.js +1 -1
  49. package/dist/cli-bundle/sdk-core.js +40 -40
  50. package/dist/cli-bundle/sdk-governance.js +1 -1
  51. package/dist/cli-bundle/sdk-graph.js +1 -1
  52. package/dist/cli-bundle/sdk-merge.js +1 -1
  53. package/dist/cli-bundle/sdk-query.js +1 -1
  54. package/dist/cli-bundle/sdk-runtime.js +1 -1
  55. package/dist/cli-bundle/sdk-testing.js +1 -1
  56. package/dist/cli-bundle/sdk.js +2 -2
  57. package/dist/core/extensions/reserved-host-flags.js +3 -2
  58. package/dist/core/history/drift-scan.js +13 -8
  59. package/dist/core/history/history.d.ts +8 -0
  60. package/dist/core/history/history.js +57 -7
  61. package/dist/core/history/replay.d.ts +9 -1
  62. package/dist/core/history/replay.js +48 -13
  63. package/dist/core/output/output.d.ts +2 -0
  64. package/dist/core/output/output.js +46 -14
  65. package/dist/core/shared/command-types.d.ts +2 -0
  66. package/dist/core/shared/command-types.js +2 -2
  67. package/dist/core/shared/errors.d.ts +5 -0
  68. package/dist/core/shared/errors.js +2 -2
  69. package/dist/core/store/item-metadata-cache.d.ts +1 -1
  70. package/dist/core/store/item-metadata-cache.js +14 -7
  71. package/dist/sdk/cli-bootstrap.d.ts +2 -0
  72. package/dist/sdk/cli-bootstrap.js +6 -2
  73. package/dist/sdk/cli-contracts/completeness.js +3 -2
  74. package/dist/sdk/cli-contracts/flag-contracts.js +5 -2
  75. package/dist/sdk/cli-contracts/registration-helpers.d.ts +20 -3
  76. package/dist/sdk/cli-contracts/registration-helpers.js +18 -12
  77. package/dist/sdk/cli-contracts/schemas/assurance-definition.d.ts +8 -6
  78. package/dist/sdk/cli-contracts/schemas/assurance-definition.js +4 -3
  79. package/dist/sdk/cli-contracts/tool-option-contracts.js +3 -2
  80. package/dist/sdk/cli-contracts/tool-parameter-tables.js +22 -2
  81. package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
  82. package/dist/sdk/cli-contracts/tool-schema.js +7 -4
  83. package/dist/sdk/cli-program.js +3 -2
  84. package/dist/sdk/completion.js +16 -7
  85. package/dist/sdk/context-intent-contracts.js +45 -4
  86. package/dist/sdk/core.d.ts +1 -0
  87. package/dist/sdk/core.js +3 -2
  88. package/dist/sdk/generated/generated-error-code-catalog-part-1.d.ts +8 -0
  89. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1585 -0
  90. package/dist/sdk/generated/generated-error-code-catalog-part-2.d.ts +8 -0
  91. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1608 -0
  92. package/dist/sdk/generated-error-code-catalog.js +6 -3140
  93. package/dist/sdk/governance/assurance-action.js +9 -7
  94. package/dist/sdk/governance/assurance-mutation-error.d.ts +7 -0
  95. package/dist/sdk/governance/assurance-mutation-error.js +35 -0
  96. package/dist/sdk/governance/assurance-runtime.js +11 -8
  97. package/dist/sdk/governance/assurance.d.ts +12 -6
  98. package/dist/sdk/governance/assurance.js +181 -66
  99. package/dist/sdk/governance/health.d.ts +3 -1
  100. package/dist/sdk/governance/health.js +21 -4
  101. package/dist/sdk/governance/validate-item-reader.js +3 -3
  102. package/dist/sdk/governance/validate.d.ts +2 -0
  103. package/dist/sdk/governance/validate.js +13 -3
  104. package/dist/sdk/graph/durable-cache.d.ts +1 -1
  105. package/dist/sdk/graph/durable-cache.js +98 -29
  106. package/dist/sdk/graph/governance.d.ts +14 -0
  107. package/dist/sdk/graph/governance.js +34 -9
  108. package/dist/sdk/index.d.ts +1 -1
  109. package/dist/sdk/index.js +3 -3
  110. package/dist/sdk/lifecycle/claim.js +10 -3
  111. package/dist/sdk/merge/driver.d.ts +2 -2
  112. package/dist/sdk/merge/driver.js +3 -3
  113. package/dist/sdk/merge/receipts.d.ts +6 -4
  114. package/dist/sdk/merge/receipts.js +7 -5
  115. package/dist/sdk/merge/three-way.d.ts +2 -2
  116. package/dist/sdk/merge/three-way.js +3 -3
  117. package/dist/sdk/output-projection.d.ts +9 -1
  118. package/dist/sdk/output-projection.js +45 -9
  119. package/dist/sdk/query/activity.d.ts +49 -2
  120. package/dist/sdk/query/activity.js +183 -37
  121. package/dist/sdk/query/get.js +19 -2
  122. package/dist/sdk/read-output-contracts.d.ts +5 -1
  123. package/dist/sdk/read-output-contracts.js +17 -2
  124. package/dist/sdk/read-output-session.d.ts +2 -0
  125. package/dist/sdk/read-output-session.js +12 -5
  126. package/dist/sdk/runtime-primitives.d.ts +3 -3
  127. package/dist/sdk/runtime-primitives.js +5 -5
  128. package/dist/sdk/runtime-stats-options.js +3 -2
  129. package/dist/sdk/runtime.js +5 -9
  130. package/dist/sdk/stats.d.ts +36 -3
  131. package/dist/sdk/stats.js +55 -15
  132. package/dist/sdk/test/execution.d.ts +4 -0
  133. package/dist/sdk/test/execution.js +86 -21
  134. package/dist/types.d.ts +2 -0
  135. package/dist/types.js +2 -2
  136. package/docs/ASSURANCE.md +17 -12
  137. package/docs/COMMANDS.md +29 -8
  138. package/docs/CONFIGURATION.md +7 -0
  139. package/docs/DEPENDENCY_KIND_CONTRACT.md +10 -8
  140. package/docs/MERGE_SAFETY.md +3 -1
  141. package/docs/OUTPUT_PROJECTION_CONTRACTS.md +26 -13
  142. package/docs/README.md +2 -0
  143. package/docs/READ_OUTPUT_CONTRACTS.md +17 -2
  144. package/docs/SDK_AGENT_SESSION_CONTEXT.md +12 -1
  145. package/docs/SDK_CONTEXT_EVIDENCE_CONTRACTS.md +53 -0
  146. package/docs/SDK_CONTEXT_INTEGRITY.md +28 -1
  147. package/docs/TRUSTWORTHY_CONTEXT_EVIDENCE.md +106 -0
  148. package/docs/performance/sdk-entrypoint-import-costs.md +6 -2
  149. package/marketplace.json +2 -2
  150. package/package.json +5 -5
  151. package/packages/pm-beads/package.json +1 -1
  152. package/packages/pm-calendar/package.json +1 -1
  153. package/packages/pm-command-kit/package.json +1 -1
  154. package/packages/pm-digital-twin/package.json +1 -1
  155. package/packages/pm-governance-audit/package.json +1 -1
  156. package/packages/pm-guide-shell/package.json +1 -1
  157. package/packages/pm-kanban/package.json +1 -1
  158. package/packages/pm-lifecycle-hooks/package.json +1 -1
  159. package/packages/pm-linked-test-adapters/package.json +1 -1
  160. package/packages/pm-search-advanced/package.json +1 -1
  161. package/packages/pm-templates/package.json +1 -1
  162. package/packages/pm-todos/package.json +1 -1
  163. package/packages/pm-vcs/package.json +1 -1
  164. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  165. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  166. package/sdk/public-surface.json +203 -52
  167. package/dist/cli-bundle/chunks/chunk-2RA2YTHJ.js +0 -33
  168. package/dist/cli-bundle/chunks/chunk-DSKYD4LG.js +0 -13
  169. package/dist/cli-bundle/chunks/chunk-HC3GM6YY.js +0 -194
  170. package/dist/cli-bundle/chunks/chunk-W7CPZGSB.js +0 -2
  171. package/dist/cli-bundle/chunks/chunk-ZXOC6HG5.js +0 -5
  172. package/dist/cli-bundle/chunks/register-list-query-HAUXVUPY.js +0 -10
  173. package/dist/cli-bundle/chunks/register-mutation-7Y5U7ABT.js +0 -20
  174. package/dist/cli-bundle/chunks/register-operations-EQX3DLAZ.js +0 -2
  175. package/dist/cli-bundle/focused-chunks/chunk-3AYKXMYX.js +0 -2
  176. package/dist/cli-bundle/focused-chunks/chunk-4AHEC74N.js +0 -2
  177. package/dist/cli-bundle/focused-chunks/chunk-4SV3ZKG3.js +0 -16
  178. package/dist/cli-bundle/focused-chunks/chunk-6JW6Z2P5.js +0 -8
  179. package/dist/cli-bundle/focused-chunks/chunk-D4PIWE6D.js +0 -2
  180. package/dist/cli-bundle/focused-chunks/chunk-E3KCL7AY.js +0 -5
  181. package/dist/cli-bundle/focused-chunks/chunk-ERAAFLPS.js +0 -153
  182. package/dist/cli-bundle/focused-chunks/chunk-F6E4JZKG.js +0 -2
  183. package/dist/cli-bundle/focused-chunks/chunk-OJ3LVVIS.js +0 -2
  184. 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]="ecc8eb89-b9e9-51fa-8573-2afb8ec801be")}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 { 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,25 +186,30 @@ 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
  }
@@ -182,46 +217,50 @@ export function validateAssertionDefinition(definition) {
182
217
  export function validateGateDefinition(definition) {
183
218
  requireStableId(definition.id, "gate.id");
184
219
  if (definition.assertion_ids.length === 0) {
185
- throw new TypeError("gate requires at least one assertion");
220
+ throw new AssuranceMutationRefusalError("gate requires at least one assertion");
186
221
  }
187
222
  for (const id of definition.assertion_ids)
188
223
  requireStableId(id, "gate.assertion_id");
189
224
  if (definition.triggers.length === 0) {
190
- throw new TypeError("gate requires at least one trigger");
225
+ throw new AssuranceMutationRefusalError("gate requires at least one trigger");
191
226
  }
192
227
  return definition;
193
228
  }
194
229
  /** Validate registry references, uniqueness, and declaration-local contracts. */
195
230
  export function validateAssuranceDocument(document) {
196
231
  if (document.version !== ASSURANCE_DOCUMENT_VERSION) {
197
- throw new TypeError(`unsupported assurance document version ${String(document.version)}`);
232
+ throw new AssuranceMutationRefusalError(`unsupported assurance document version ${String(document.version)}`);
198
233
  }
199
234
  const ids = new Set();
200
235
  for (const definition of document.measurements) {
201
236
  validateMeasurementDefinition(definition);
202
- if (ids.has(`measurement:${definition.id}`))
203
- throw new TypeError(`duplicate measurement ${definition.id}`);
237
+ if (ids.has(`measurement:${definition.id}`)) {
238
+ throw new AssuranceMutationRefusalError(`duplicate measurement ${definition.id}`);
239
+ }
204
240
  ids.add(`measurement:${definition.id}`);
205
241
  }
206
242
  for (const definition of document.assertions) {
207
243
  validateAssertionDefinition(definition);
208
- if (ids.has(`assertion:${definition.id}`))
209
- throw new TypeError(`duplicate assertion ${definition.id}`);
244
+ if (ids.has(`assertion:${definition.id}`)) {
245
+ throw new AssuranceMutationRefusalError(`duplicate assertion ${definition.id}`);
246
+ }
210
247
  ids.add(`assertion:${definition.id}`);
211
248
  }
212
249
  for (const definition of document.gates) {
213
250
  validateGateDefinition(definition);
214
251
  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}`);
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
+ }
219
258
  ids.add(`gate:${definition.id}`);
220
259
  }
221
260
  const measurementIds = new Set(document.measurements.map((entry) => entry.id));
222
261
  for (const [measurementId, consumers] of collectMeasurementReferences(document)) {
223
262
  if (!measurementIds.has(measurementId)) {
224
- throw new TypeError(`${consumers.join(", ")} references missing measurement ${measurementId}`);
263
+ throw new AssuranceMutationRefusalError(`${consumers.join(", ")} references missing measurement ${measurementId}`);
225
264
  }
226
265
  }
227
266
  return document;
@@ -263,15 +302,31 @@ function collectMeasurementReferences(document) {
263
302
  }
264
303
  /** Create an empty, valid assurance registry. */
265
304
  export function createEmptyAssuranceDocument() {
266
- return { version: ASSURANCE_DOCUMENT_VERSION, measurements: [], assertions: [], gates: [] };
305
+ return {
306
+ version: ASSURANCE_DOCUMENT_VERSION,
307
+ measurements: [],
308
+ assertions: [],
309
+ gates: [],
310
+ };
267
311
  }
268
312
  function itemsSourceResult(source, context) {
269
- const matches = context.items.filter((item) => (source.statuses === undefined || source.statuses.includes(item.status)) &&
313
+ const matches = context.items.filter((item) => (source.statuses === undefined ||
314
+ source.statuses.includes(item.status)) &&
270
315
  (source.types === undefined || source.types.includes(item.type)) &&
271
316
  (source.tags === undefined ||
272
317
  source.tags.every((tag) => item.tags?.includes(tag))) &&
273
318
  (source.field === undefined ||
274
- valuesEqual(item[source.field], source.equals)));
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
+ })()));
275
330
  return {
276
331
  value: matches.length,
277
332
  population_size: context.items.length,
@@ -281,9 +336,13 @@ function itemsSourceResult(source, context) {
281
336
  }
282
337
  function dependencyKindSourceResult(source, context) {
283
338
  const contributors = [];
339
+ const requestedKind = resolveCanonicalRelationshipKind(source.dependency_kind) ??
340
+ source.dependency_kind.trim().toLowerCase();
284
341
  for (const item of context.items) {
285
342
  for (const dependency of item.dependencies ?? []) {
286
- if (dependency.kind === source.dependency_kind) {
343
+ const dependencyKind = resolveCanonicalRelationshipKind(dependency.kind) ??
344
+ dependency.kind.trim().toLowerCase();
345
+ if (dependencyKind === requestedKind) {
287
346
  contributors.push(`${item.id}->${dependency.id}`);
288
347
  }
289
348
  }
@@ -340,7 +399,13 @@ function sumCosts(costs, durationMs) {
340
399
  history_entries: total.history_entries + cost.history_entries,
341
400
  provider_calls: total.provider_calls + cost.provider_calls,
342
401
  duration_ms: durationMs,
343
- }), { units: 0, items_scanned: 0, history_entries: 0, provider_calls: 0, duration_ms: durationMs });
402
+ }), {
403
+ units: 0,
404
+ items_scanned: 0,
405
+ history_entries: 0,
406
+ provider_calls: 0,
407
+ duration_ms: durationMs,
408
+ });
344
409
  }
345
410
  function arithmeticValue(operator, values) {
346
411
  if (values.length === 0)
@@ -373,7 +438,12 @@ async function mapWithConcurrency(values, limit, transform) {
373
438
  }
374
439
  async function evaluateExpression(expression, context, definitions, stack, state) {
375
440
  if ("literal" in expression)
376
- return { value: expression.literal, costs: [], population_size: 0, contributors: [] };
441
+ return {
442
+ value: expression.literal,
443
+ costs: [],
444
+ population_size: 0,
445
+ contributors: [],
446
+ };
377
447
  if ("measurement" in expression) {
378
448
  const definition = definitions.find((entry) => entry.id === expression.measurement);
379
449
  if (!definition)
@@ -383,7 +453,12 @@ async function evaluateExpression(expression, context, definitions, stack, state
383
453
  throw new TypeError(`derived measurement ${definition.id} is not numeric`);
384
454
  const costs = state.charged.has(definition.id) ? [] : [result.cost];
385
455
  state.charged.add(definition.id);
386
- return { value: result.value, costs, population_size: result.population_size, contributors: result.contributors };
456
+ return {
457
+ value: result.value,
458
+ costs,
459
+ population_size: result.population_size,
460
+ contributors: result.contributors,
461
+ };
387
462
  }
388
463
  const operands = await mapWithConcurrency(expression.operands, EVALUATION_CONCURRENCY, (operand) => evaluateExpression(operand, context, definitions, stack, state));
389
464
  return {
@@ -401,7 +476,12 @@ async function evaluateMeasurementInternal(definition, context, definitions, sta
401
476
  let providerCalls = 0;
402
477
  if (definition.source.kind === "derived") {
403
478
  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 };
479
+ result = {
480
+ value: derived.value,
481
+ population_size: derived.population_size,
482
+ cost: 0,
483
+ contributors: derived.contributors,
484
+ };
405
485
  nestedCosts = derived.costs;
406
486
  }
407
487
  else {
@@ -422,7 +502,11 @@ async function evaluateMeasurementInternal(definition, context, definitions, sta
422
502
  }
423
503
  const ownCost = {
424
504
  units: result.cost,
425
- items_scanned: definition.source.kind === "items" || definition.source.kind === "dependency_kind" || definition.source.kind === "links" ? context.items.length : 0,
505
+ items_scanned: definition.source.kind === "items" ||
506
+ definition.source.kind === "dependency_kind" ||
507
+ definition.source.kind === "links"
508
+ ? context.items.length
509
+ : 0,
426
510
  history_entries: definition.source.kind === "history" ? context.history.length : 0,
427
511
  provider_calls: providerCalls,
428
512
  duration_ms: Date.now() - startedAt,
@@ -431,7 +515,16 @@ async function evaluateMeasurementInternal(definition, context, definitions, sta
431
515
  if (definition.max_cost !== undefined && cost.units > definition.max_cost) {
432
516
  throw new TypeError(`measurement ${definition.id} exceeded cost ceiling ${definition.max_cost} with ${cost.units}`);
433
517
  }
434
- return { id: definition.id, value: result.value, population_size: result.population_size, cost, contributors: result.contributors ?? [] };
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
+ };
435
528
  }
436
529
  function evaluateMeasurementCached(definition, context, definitions, stack, state) {
437
530
  if (stack.includes(definition.id)) {
@@ -454,7 +547,8 @@ export async function evaluateMeasurement(definition, context, definitions = [de
454
547
  function assertionDistance(observed, bound) {
455
548
  if (typeof observed !== "number" || typeof bound.value !== "number")
456
549
  return null;
457
- if (bound.polarity === "ceiling" || bound.polarity === "monotone_nonincreasing")
550
+ if (bound.polarity === "ceiling" ||
551
+ bound.polarity === "monotone_nonincreasing")
458
552
  return bound.value - observed;
459
553
  if (bound.polarity === "floor" || bound.polarity === "monotone_nondecreasing")
460
554
  return observed - bound.value;
@@ -468,12 +562,17 @@ export function evaluateAssuranceAssertion(definition, measurement, options = {}
468
562
  return {
469
563
  assertion_id: definition.id,
470
564
  measurement_id: definition.measurement_id,
565
+ measurement_definition_fingerprint: measurement.definition_fingerprint,
471
566
  scope: definition.scope,
472
567
  population_size: measurement.population_size,
473
568
  observed: measurement.value,
474
569
  bound,
475
570
  distance: assertionDistance(measurement.value, bound),
476
- verdict: retired ? "retired" : compareBound(measurement.value, bound) ? "pass" : "fail",
571
+ verdict: retired
572
+ ? "retired"
573
+ : compareBound(measurement.value, bound)
574
+ ? "pass"
575
+ : "fail",
477
576
  enforcement: definition.enforcement,
478
577
  negative_control_proven: proveNegativeControl(definition),
479
578
  cost: measurement.cost,
@@ -571,7 +670,11 @@ function validateForKind(kind, definition) {
571
670
  /** Read all declarations of one kind with a stable row selector receipt. */
572
671
  export async function listAssuranceDeclarations(pmRoot, kind) {
573
672
  const items = structuredClone(collectionFor(await readDocument(pmRoot), kind));
574
- return { items, count: items.length, row_contract: { row_keys: ["items"], jq_selector: ".items[]" } };
673
+ return {
674
+ items,
675
+ count: items.length,
676
+ row_contract: { row_keys: ["items"], jq_selector: ".items[]" },
677
+ };
575
678
  }
576
679
  /** Read one named declaration or fail loudly. */
577
680
  export async function getAssuranceDeclaration(pmRoot, kind, id) {
@@ -583,7 +686,8 @@ export async function getAssuranceDeclaration(pmRoot, kind, id) {
583
686
  /** Create or update one declaration through audited singleton persistence. */
584
687
  export async function putAssuranceDeclaration(pmRoot, kind, definition, options = {}) {
585
688
  validateForKind(kind, definition);
586
- const author = resolveAuthor(options.author, "unknown");
689
+ const settings = await readSettings(pmRoot);
690
+ const author = resolveAuthor(options.author, settings.author_default);
587
691
  const mutation = await mutateWorkspaceJsonWithHistory({
588
692
  pmRoot,
589
693
  filePath: assurancePath(pmRoot),
@@ -593,16 +697,20 @@ export async function putAssuranceDeclaration(pmRoot, kind, definition, options
593
697
  lockTtlSeconds: LOCK_TTL_SECONDS,
594
698
  lockWaitMs: LOCK_WAIT_MS,
595
699
  mutate: (beforeRaw) => {
596
- const document = beforeRaw === null ? createEmptyAssuranceDocument() : parseAssuranceDocument(beforeRaw);
700
+ const document = beforeRaw === null
701
+ ? createEmptyAssuranceDocument()
702
+ : parseAssuranceDocument(beforeRaw);
597
703
  const collection = collectionFor(document, kind);
598
704
  const index = collection.findIndex((entry) => entry.id === definition.id);
599
705
  const action = index < 0 ? "created" : "updated";
600
706
  if (kind === "assertion" &&
601
707
  index >= 0 &&
602
708
  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`);
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`);
606
714
  }
607
715
  }
608
716
  if (index < 0)
@@ -620,7 +728,8 @@ export async function putAssuranceDeclaration(pmRoot, kind, definition, options
620
728
  }
621
729
  /** Remove an unreferenced declaration through audited singleton persistence. */
622
730
  export async function removeAssuranceDeclaration(pmRoot, kind, id, options = {}) {
623
- const author = resolveAuthor(options.author, "unknown");
731
+ const settings = await readSettings(pmRoot);
732
+ const author = resolveAuthor(options.author, settings.author_default);
624
733
  const mutation = await mutateWorkspaceJsonWithHistory({
625
734
  pmRoot,
626
735
  filePath: assurancePath(pmRoot),
@@ -630,20 +739,24 @@ export async function removeAssuranceDeclaration(pmRoot, kind, id, options = {})
630
739
  lockTtlSeconds: LOCK_TTL_SECONDS,
631
740
  lockWaitMs: LOCK_WAIT_MS,
632
741
  mutate: (beforeRaw) => {
633
- const document = beforeRaw === null ? createEmptyAssuranceDocument() : parseAssuranceDocument(beforeRaw);
742
+ const document = beforeRaw === null
743
+ ? createEmptyAssuranceDocument()
744
+ : parseAssuranceDocument(beforeRaw);
634
745
  if (kind === "measurement") {
635
746
  const consumers = collectMeasurementReferences(document).get(id);
636
747
  if (consumers && consumers.length > 0) {
637
- throw new TypeError(`assurance measurement ${id} is referenced by ${consumers.join(", ")}`);
748
+ throw new AssuranceMutationRefusalError(`assurance measurement ${id} is referenced by ${consumers.join(", ")}`);
638
749
  }
639
750
  }
640
- if (kind === "assertion" && document.gates.some((entry) => entry.assertion_ids.includes(id))) {
641
- throw new TypeError(`assurance assertion ${id} is referenced by gate`);
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`);
642
754
  }
643
755
  const collection = collectionFor(document, kind);
644
756
  const index = collection.findIndex((entry) => entry.id === id);
645
- if (index < 0)
646
- throw new TypeError(`assurance ${kind} ${id} not found`);
757
+ if (index < 0) {
758
+ throw new AssuranceMutationRefusalError(`assurance ${kind} ${id} not found`);
759
+ }
647
760
  collection.splice(index, 1);
648
761
  validateAssuranceDocument(document);
649
762
  return {
@@ -658,12 +771,14 @@ export async function removeAssuranceDeclaration(pmRoot, kind, id, options = {})
658
771
  export async function recordAssuranceVerdict(pmRoot, verdict, options = {}) {
659
772
  if (verdict.dry_run)
660
773
  throw new TypeError("dry-run assurance verdicts are not persisted");
774
+ const settings = await readSettings(pmRoot);
661
775
  await appendWorkspaceAuditEvent({
662
776
  pmRoot,
663
777
  op: "assurance:gate:verdict",
664
- author: resolveAuthor(options.author, "unknown"),
778
+ author: resolveAuthor(options.author, settings.author_default),
665
779
  context: { assurance_verdict: verdict },
666
- message: options.message ?? `Assurance gate ${verdict.gate_id} returned ${verdict.verdict}`,
780
+ message: options.message ??
781
+ `Assurance gate ${verdict.gate_id} returned ${verdict.verdict}`,
667
782
  lockTtlSeconds: LOCK_TTL_SECONDS,
668
783
  lockWaitMs: LOCK_WAIT_MS,
669
784
  });
@@ -686,4 +801,4 @@ export async function listAssuranceVerdicts(pmRoot, options = {}) {
686
801
  .slice(0, limit);
687
802
  }
688
803
  //# sourceMappingURL=assurance.js.map
689
- //# debugId=2c6c336c-abaa-53f7-94ac-ba4d5ab7c76a
804
+ //# debugId=ecc8eb89-b9e9-51fa-8573-2afb8ec801be
@@ -15,6 +15,8 @@ export interface HealthCheck {
15
15
  name: "settings" | "directories" | "settings_values" | "telemetry" | "extensions" | "storage" | "locks" | "integrity" | "history_drift" | "vectorization";
16
16
  /** Lifecycle state reported for status. */
17
17
  status: HealthStatus;
18
+ /** Boolean companion to status for direct agent and assurance predicates. */
19
+ ok: boolean;
18
20
  /** Value that configures or reports details for this contract. */
19
21
  details: Record<string, unknown>;
20
22
  }
@@ -113,7 +115,7 @@ interface ExtensionHealthTriageSummary {
113
115
  top_warnings: string[];
114
116
  remediation: string[];
115
117
  }
116
- /** Advisory warnings are surfaced for visibility but never flip overall health to not-ok. Telemetry is opt-out, non-critical observability: a queued/unreachable telemetry endpoint or corrupt local telemetry state is not a project-health failure and must not block agents that gate on `pm health` `ok`. History over-compaction-threshold warnings are likewise advisory maintenance hints — a deep stream is healthy, just a candidate for `pm history-compact`. */
118
+ /** Advisory warnings are surfaced for visibility but never flip overall health to not-ok. Telemetry is opt-out, non-critical observability: a queued/unreachable telemetry endpoint or corrupt local telemetry state is not a project-health failure and must not block agents that gate on `pm health` `ok`. History over-compaction-threshold warnings are likewise advisory maintenance hints — a deep stream is healthy, just a candidate for `pm history-compact`. Invalid legacy provenance values remain immutable diagnostic evidence: write-time resolvers reject new invalid values, while health reports the bounded aggregate without requiring truthful history to be rewritten. */
117
119
  declare function isAdvisoryHealthWarning(warning: string, requireMergeDrivers?: boolean): boolean;
118
120
  declare function warningCode(value: string): string;
119
121
  declare function normalizeExtensionNameForMatch(value: string): string;