@unbrained/pm-cli 2026.9.4 → 2026.9.6

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 (277) hide show
  1. package/.agents/skills/HARNESS_COMPATIBILITY.md +1 -1
  2. package/.agents/skills/README.md +2 -2
  3. package/.agents/skills/pm-developer/SKILL.md +4 -1
  4. package/.agents/skills/pm-developer/references/COMMAND_PLAYBOOK.md +10 -0
  5. package/.agents/skills/pm-developer/references/GRAPH_AND_RELATIONSHIPS.md +3 -1
  6. package/.agents/skills/pm-extensions/SKILL.md +2 -2
  7. package/.agents/skills/pm-extensions/references/LIFECYCLE.md +3 -3
  8. package/.agents/skills/pm-extensions/references/TROUBLESHOOTING.md +3 -3
  9. package/.agents/skills/pm-user/SKILL.md +2 -0
  10. package/.agents/skills/pm-user/references/BACKLOG_SHAPING.md +5 -0
  11. package/.agents/skills/pm-user/references/WORKFLOWS.md +20 -5
  12. package/.claude-plugin/marketplace.json +2 -2
  13. package/CHANGELOG.md +58 -3
  14. package/dist/cli/commander-usage.d.ts +2 -0
  15. package/dist/cli/commander-usage.js +15 -12
  16. package/dist/cli/help-json-payload.js +12 -7
  17. package/dist/cli/main.js +22 -11
  18. package/dist/cli/register-list-query.js +4 -2
  19. package/dist/cli/register-mutation.js +4 -2
  20. package/dist/cli/register-operations.js +17 -8
  21. package/dist/cli-bundle/bundle-manifest.json +153 -161
  22. package/dist/cli-bundle/chunks/chunk-AZDFJGH6.js +9 -0
  23. package/dist/cli-bundle/chunks/{chunk-V72UV7QS.js → chunk-BAX2BYUW.js} +73 -73
  24. package/dist/cli-bundle/chunks/{chunk-HI6BHPCU.js → chunk-CKOL5U7J.js} +51 -51
  25. package/dist/cli-bundle/chunks/{chunk-A24WAVE6.js → chunk-JT6XF6GE.js} +2 -2
  26. package/dist/cli-bundle/chunks/{chunk-I2AS367G.js → chunk-JX4DH4WC.js} +2 -2
  27. package/dist/cli-bundle/chunks/{chunk-YW4QK2G7.js → chunk-NPN5I67J.js} +2 -2
  28. package/dist/cli-bundle/chunks/chunk-OF76PSDQ.js +5 -0
  29. package/dist/cli-bundle/chunks/chunk-QUWFABDM.js +232 -0
  30. package/dist/cli-bundle/chunks/{chunk-RI4XNOYS.js → chunk-RWWBOWJ5.js} +2 -2
  31. package/dist/cli-bundle/chunks/chunk-WGAOYSYH.js +40 -0
  32. package/dist/cli-bundle/chunks/{chunk-X6CMGV6L.js → chunk-WI2AYPA7.js} +2 -2
  33. package/dist/cli-bundle/chunks/{register-list-query-Z5YYSJJG.js → register-list-query-5JL52D33.js} +3 -3
  34. package/dist/cli-bundle/chunks/{register-mutation-ELSP6I4I.js → register-mutation-ZPG7KMVP.js} +4 -4
  35. package/dist/cli-bundle/chunks/register-operations-I5W3KMGB.js +2 -0
  36. package/dist/cli-bundle/chunks/{register-setup-E2AE72QG.js → register-setup-H4SVAVX7.js} +2 -2
  37. package/dist/cli-bundle/focused-chunks/{chunk-3646BZ4W.js → chunk-3BZEOXLT.js} +2 -2
  38. package/dist/cli-bundle/focused-chunks/chunk-AL5ON6K6.js +2 -0
  39. package/dist/cli-bundle/focused-chunks/chunk-CHC2IDXA.js +6 -0
  40. package/dist/cli-bundle/focused-chunks/chunk-CK6QFAPZ.js +20 -0
  41. package/dist/cli-bundle/focused-chunks/{chunk-NBMJD6MK.js → chunk-EZR3GHFE.js} +2 -2
  42. package/dist/cli-bundle/focused-chunks/chunk-H2WJWK7L.js +56 -0
  43. package/dist/cli-bundle/focused-chunks/{chunk-53IHPICX.js → chunk-LIJIA4TY.js} +2 -2
  44. package/dist/cli-bundle/focused-chunks/{chunk-25UJBH4M.js → chunk-M6PBRFSH.js} +2 -2
  45. package/dist/cli-bundle/focused-chunks/{chunk-AJR3OP2E.js → chunk-NTPY5GSO.js} +2 -2
  46. package/dist/cli-bundle/focused-chunks/chunk-P5O7MP5Y.js +2 -0
  47. package/dist/cli-bundle/focused-chunks/chunk-PDMW2E36.js +177 -0
  48. package/dist/cli-bundle/focused-chunks/{chunk-MHKUIPJW.js → chunk-PEFABF7E.js} +2 -2
  49. package/dist/cli-bundle/focused-chunks/{chunk-6PWPQKDN.js → chunk-QTVCLMSZ.js} +2 -2
  50. package/dist/cli-bundle/focused-chunks/{chunk-7N2TV7WZ.js → chunk-QTZRBUYA.js} +2 -2
  51. package/dist/cli-bundle/focused-chunks/chunk-SGQST3PQ.js +12 -0
  52. package/dist/cli-bundle/focused-chunks/chunk-UKHASMMP.js +10 -0
  53. package/dist/cli-bundle/focused-chunks/chunk-ZPEKQHOC.js +2 -0
  54. package/dist/cli-bundle/main.js +22 -15
  55. package/dist/cli-bundle/sdk-authoring.js +1 -1
  56. package/dist/cli-bundle/sdk-contracts.js +2 -2
  57. package/dist/cli-bundle/sdk-core.js +37 -37
  58. package/dist/cli-bundle/sdk-governance.js +1 -1
  59. package/dist/cli-bundle/sdk-graph.js +1 -1
  60. package/dist/cli-bundle/sdk-merge.js +42 -39
  61. package/dist/cli-bundle/sdk-query.js +1 -1
  62. package/dist/cli-bundle/sdk-runtime.js +1 -1
  63. package/dist/cli-bundle/sdk-testing.js +1 -1
  64. package/dist/cli-bundle/sdk.js +9 -9
  65. package/dist/core/extensions/extension-types.d.ts +2 -0
  66. package/dist/core/extensions/extension-types.js +2 -2
  67. package/dist/core/extensions/module-graph-snapshot.js +5 -4
  68. package/dist/core/fs/host-environment-errors.d.ts +27 -0
  69. package/dist/core/fs/host-environment-errors.js +142 -0
  70. package/dist/core/history/drift-scan.d.ts +5 -0
  71. package/dist/core/history/drift-scan.js +16 -6
  72. package/dist/core/history/event-classification.d.ts +1 -1
  73. package/dist/core/history/event-classification.js +3 -2
  74. package/dist/core/history/history.js +37 -5
  75. package/dist/core/history/identity.d.ts +15 -0
  76. package/dist/core/history/identity.js +31 -0
  77. package/dist/core/history/projection.js +50 -17
  78. package/dist/core/history/read.js +6 -3
  79. package/dist/core/history/replay.d.ts +1 -2
  80. package/dist/core/history/replay.js +4 -4
  81. package/dist/core/history/version-address.d.ts +13 -0
  82. package/dist/core/history/version-address.js +33 -0
  83. package/dist/core/item/actionability.d.ts +21 -1
  84. package/dist/core/item/actionability.js +45 -10
  85. package/dist/core/item/id.js +8 -4
  86. package/dist/core/item/item-type-definition.d.ts +2 -0
  87. package/dist/core/item/item-type-definition.js +9 -2
  88. package/dist/core/item/type-registry.d.ts +5 -1
  89. package/dist/core/item/type-registry.js +9 -4
  90. package/dist/core/output/mutation-projection.js +12 -5
  91. package/dist/core/shared/errors.d.ts +8 -0
  92. package/dist/core/shared/errors.js +2 -2
  93. package/dist/core/store/item-metadata-query-index.js +13 -10
  94. package/dist/core/store/item-metadata-read-work.d.ts +19 -0
  95. package/dist/core/store/item-metadata-read-work.js +44 -0
  96. package/dist/core/store/item-store.d.ts +14 -0
  97. package/dist/core/store/item-store.js +52 -10
  98. package/dist/core/store/settings-validator.js +3 -2
  99. package/dist/core/validate/fix-planning.d.ts +1 -1
  100. package/dist/core/validate/fix-planning.js +2 -2
  101. package/dist/core/validate/missing-link-owners.d.ts +1 -1
  102. package/dist/core/validate/missing-link-owners.js +5 -3
  103. package/dist/core/validate/stale-file-classification.d.ts +6 -4
  104. package/dist/core/validate/stale-file-classification.js +20 -5
  105. package/dist/mcp/server.js +12 -5
  106. package/dist/sdk/actionability.d.ts +1 -1
  107. package/dist/sdk/actionability.js +3 -3
  108. package/dist/sdk/cli-contracts/command-aliases.js +11 -2
  109. package/dist/sdk/cli-contracts/completeness.js +4 -3
  110. package/dist/sdk/cli-contracts/flag-contracts.d.ts +2 -0
  111. package/dist/sdk/cli-contracts/flag-contracts.js +31 -5
  112. package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +21 -13
  113. package/dist/sdk/cli-contracts/grammar-contracts.js +11 -2
  114. package/dist/sdk/cli-contracts/runtime-contracts.js +5 -2
  115. package/dist/sdk/cli-contracts/schemas/assurance-definition.d.ts +254 -5
  116. package/dist/sdk/cli-contracts/schemas/assurance-definition.js +52 -2
  117. package/dist/sdk/cli-contracts/tool-parameter-tables.js +30 -6
  118. package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
  119. package/dist/sdk/cli-contracts/tool-schema.js +32 -7
  120. package/dist/sdk/context/usage-ledger.d.ts +41 -0
  121. package/dist/sdk/context/usage-ledger.js +186 -0
  122. package/dist/sdk/context-intent-contracts.js +3 -3
  123. package/dist/sdk/context-usage.d.ts +18 -9
  124. package/dist/sdk/context-usage.js +44 -121
  125. package/dist/sdk/define.d.ts +1 -1
  126. package/dist/sdk/define.js +2 -2
  127. package/dist/sdk/dependency-provenance.d.ts +7 -0
  128. package/dist/sdk/dependency-provenance.js +14 -4
  129. package/dist/sdk/duplicates.d.ts +2 -0
  130. package/dist/sdk/duplicates.js +3 -2
  131. package/dist/sdk/environment/host-environment-errors.d.ts +5 -27
  132. package/dist/sdk/environment/host-environment-errors.js +4 -137
  133. package/dist/sdk/extension/install-runtime.js +9 -6
  134. package/dist/sdk/extension/install-snapshot.js +7 -6
  135. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +104 -56
  136. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +50 -2
  137. package/dist/sdk/governance/assurance-action.d.ts +3 -2
  138. package/dist/sdk/governance/assurance-action.js +23 -11
  139. package/dist/sdk/governance/assurance-runtime.d.ts +2 -0
  140. package/dist/sdk/governance/assurance-runtime.js +6 -3
  141. package/dist/sdk/governance/defect-recurrence-coverage.d.ts +93 -0
  142. package/dist/sdk/governance/defect-recurrence-coverage.js +189 -0
  143. package/dist/sdk/governance/defect-recurrence-graph.d.ts +33 -0
  144. package/dist/sdk/governance/defect-recurrence-graph.js +121 -0
  145. package/dist/sdk/governance/defect-recurrence.d.ts +8 -0
  146. package/dist/sdk/governance/defect-recurrence.js +118 -17
  147. package/dist/sdk/governance/health.d.ts +17 -2
  148. package/dist/sdk/governance/health.js +31 -10
  149. package/dist/sdk/governance/linked-file-report.d.ts +41 -0
  150. package/dist/sdk/governance/linked-file-report.js +72 -0
  151. package/dist/sdk/governance/validate-history-drift.js +16 -5
  152. package/dist/sdk/governance/validate.d.ts +5 -2
  153. package/dist/sdk/governance/validate.js +21 -37
  154. package/dist/sdk/governance.d.ts +2 -0
  155. package/dist/sdk/governance.js +4 -2
  156. package/dist/sdk/guide-topics.js +116 -2
  157. package/dist/sdk/history/salvage.d.ts +35 -0
  158. package/dist/sdk/history/salvage.js +248 -0
  159. package/dist/sdk/history-compact.js +32 -37
  160. package/dist/sdk/history-read.d.ts +3 -2
  161. package/dist/sdk/history-read.js +6 -3
  162. package/dist/sdk/history-repair.d.ts +12 -0
  163. package/dist/sdk/history-repair.js +60 -13
  164. package/dist/sdk/lifecycle/claim.d.ts +4 -0
  165. package/dist/sdk/lifecycle/claim.js +9 -3
  166. package/dist/sdk/lifecycle/close.d.ts +8 -0
  167. package/dist/sdk/lifecycle/close.js +16 -8
  168. package/dist/sdk/lifecycle/create.js +9 -12
  169. package/dist/sdk/lifecycle/restore.d.ts +1 -0
  170. package/dist/sdk/lifecycle/restore.js +19 -7
  171. package/dist/sdk/lifecycle/update.js +12 -12
  172. package/dist/sdk/lifecycle-policy.d.ts +3 -0
  173. package/dist/sdk/lifecycle-policy.js +2 -2
  174. package/dist/sdk/merge/abandoned-receipts.d.ts +15 -0
  175. package/dist/sdk/merge/abandoned-receipts.js +47 -0
  176. package/dist/sdk/merge/driver.js +4 -2
  177. package/dist/sdk/merge/index.d.ts +1 -0
  178. package/dist/sdk/merge/index.js +2 -2
  179. package/dist/sdk/merge/receipt-operation.d.ts +15 -0
  180. package/dist/sdk/merge/receipt-operation.js +97 -0
  181. package/dist/sdk/merge/receipt-schema.d.ts +20 -0
  182. package/dist/sdk/merge/receipt-schema.js +398 -0
  183. package/dist/sdk/merge/receipts.d.ts +16 -7
  184. package/dist/sdk/merge/receipts.js +54 -340
  185. package/dist/sdk/merge/reconcile.d.ts +2 -0
  186. package/dist/sdk/merge/reconcile.js +11 -4
  187. package/dist/sdk/output-projection.d.ts +3 -1
  188. package/dist/sdk/output-projection.js +11 -3
  189. package/dist/sdk/public-surface.json +1 -0
  190. package/dist/sdk/query/context.d.ts +9 -0
  191. package/dist/sdk/query/context.js +33 -21
  192. package/dist/sdk/query/duplicate-candidates.d.ts +7 -0
  193. package/dist/sdk/query/duplicate-candidates.js +71 -0
  194. package/dist/sdk/query/get.d.ts +2 -2
  195. package/dist/sdk/query/get.js +9 -8
  196. package/dist/sdk/query/history.d.ts +11 -0
  197. package/dist/sdk/query/history.js +34 -10
  198. package/dist/sdk/query/next.d.ts +15 -1
  199. package/dist/sdk/query/next.js +43 -20
  200. package/dist/sdk/query.d.ts +3 -0
  201. package/dist/sdk/query.js +5 -2
  202. package/dist/sdk/read-output-session.js +5 -3
  203. package/dist/sdk/runtime-primitives.d.ts +1 -1
  204. package/dist/sdk/runtime-primitives.js +3 -3
  205. package/dist/sdk/runtime.js +4 -2
  206. package/dist/sdk/similarity.d.ts +24 -3
  207. package/dist/sdk/similarity.js +50 -55
  208. package/dist/types.d.ts +2 -0
  209. package/dist/types.js +2 -2
  210. package/docs/AGENT_GUIDE.md +58 -1
  211. package/docs/BOUNDED_CONTEXT_READS.md +185 -0
  212. package/docs/CLOSE_EVIDENCE_RECOVERY.md +35 -0
  213. package/docs/COMMANDS.md +30 -8
  214. package/docs/CONTEXT_RELEVANCE.md +16 -4
  215. package/docs/DEFECT_RECURRENCE.md +80 -6
  216. package/docs/EXTENSIONS.md +18 -19
  217. package/docs/HISTORY_RECOVERY.md +146 -0
  218. package/docs/MERGE_SAFETY.md +50 -0
  219. package/docs/OUTPUT_PROJECTION_CONTRACTS.md +66 -0
  220. package/docs/PR_REVIEW_LOOP.md +18 -3
  221. package/docs/README.md +19 -1
  222. package/docs/RELEASING.md +31 -13
  223. package/docs/SDK_CONTEXT_TRUTH_CONTRACTS.md +24 -1
  224. package/docs/SDK_EXECUTION_CONTRACTS.md +105 -0
  225. package/docs/SDK_LIFECYCLE.md +27 -0
  226. package/docs/SDK_RUNTIME_BOUNDARIES.md +11 -0
  227. package/docs/TESTING.md +5 -0
  228. package/docs/agent-task-token-baseline.json +17 -17
  229. package/docs/agent-task-transcripts.json +2 -1
  230. package/docs/generated/FLAG_LEXICON_BUDGETS.md +11 -11
  231. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +11 -3
  232. package/marketplace.json +2 -2
  233. package/package.json +8 -6
  234. package/packages/pm-beads/package.json +1 -1
  235. package/packages/pm-calendar/package.json +1 -1
  236. package/packages/pm-command-kit/package.json +1 -1
  237. package/packages/pm-digital-twin/package.json +1 -1
  238. package/packages/pm-governance-audit/README.md +10 -1
  239. package/packages/pm-governance-audit/extensions/governance-audit/dedupe-audit.ts +50 -65
  240. package/packages/pm-governance-audit/extensions/governance-audit/index.ts +2 -2
  241. package/packages/pm-governance-audit/extensions/governance-audit/sdk.ts +1 -0
  242. package/packages/pm-governance-audit/package.json +1 -1
  243. package/packages/pm-guide-shell/package.json +1 -1
  244. package/packages/pm-kanban/package.json +1 -1
  245. package/packages/pm-lifecycle-hooks/package.json +1 -1
  246. package/packages/pm-linked-test-adapters/package.json +1 -1
  247. package/packages/pm-search-advanced/package.json +1 -1
  248. package/packages/pm-templates/package.json +1 -1
  249. package/packages/pm-todos/package.json +1 -1
  250. package/packages/pm-vcs/package.json +1 -1
  251. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  252. package/plugins/pm-claude/skills/pm-audit/SKILL.md +2 -2
  253. package/plugins/pm-claude/skills/pm-developer/SKILL.md +2 -2
  254. package/plugins/pm-claude/skills/pm-planner/SKILL.md +2 -2
  255. package/plugins/pm-claude/skills/pm-release/SKILL.md +2 -2
  256. package/plugins/pm-claude/skills/pm-workflow/SKILL.md +3 -3
  257. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  258. package/plugins/pm-codex/skills/pm-auditor/SKILL.md +2 -2
  259. package/plugins/pm-codex/skills/pm-native/SKILL.md +3 -4
  260. package/plugins/pm-codex/skills/pm-release/SKILL.md +2 -2
  261. package/scripts/finalize-build.mjs +13 -1
  262. package/dist/cli-bundle/chunks/chunk-6XSKPNCF.js +0 -230
  263. package/dist/cli-bundle/chunks/chunk-OGBVU67G.js +0 -5
  264. package/dist/cli-bundle/chunks/chunk-TXW6VL33.js +0 -38
  265. package/dist/cli-bundle/chunks/chunk-VGLHECAP.js +0 -9
  266. package/dist/cli-bundle/chunks/register-operations-OH7R3ZT4.js +0 -2
  267. package/dist/cli-bundle/focused-chunks/chunk-4YRAVI43.js +0 -12
  268. package/dist/cli-bundle/focused-chunks/chunk-5P4HVOWG.js +0 -176
  269. package/dist/cli-bundle/focused-chunks/chunk-636VVQ7F.js +0 -54
  270. package/dist/cli-bundle/focused-chunks/chunk-B3A6MJYE.js +0 -2
  271. package/dist/cli-bundle/focused-chunks/chunk-BFDDJ2T2.js +0 -2
  272. package/dist/cli-bundle/focused-chunks/chunk-BHUIRZWQ.js +0 -9
  273. package/dist/cli-bundle/focused-chunks/chunk-JADHHPY6.js +0 -6
  274. package/dist/cli-bundle/focused-chunks/chunk-NSKBLHVI.js +0 -20
  275. package/dist/cli-bundle/focused-chunks/chunk-P2GOAOQN.js +0 -2
  276. package/dist/cli-bundle/focused-chunks/chunk-YARWJM2B.js +0 -2
  277. package/sdk/public-surface.json +0 -33034
@@ -1,8 +1,8 @@
1
1
  import type { Stats } from "node:fs";
2
2
  import { type ItemMergeConflictDecision, type ItemScalarConflictResolution, type MergePreferredSide } from "./three-way.js";
3
- /** Return whether a receipt id is safe to expose in cleartext diagnostics. */
4
- export declare function isSafeReceiptId(value: string): boolean;
5
- declare const DURABLE_VALUE_POLICY: "bounded_non_sensitive_scalars_v1";
3
+ import type { MergeReceiptOperation } from "./receipt-operation.js";
4
+ import { DURABLE_VALUE_POLICY } from "./receipt-schema.js";
5
+ export { isSafeReceiptId } from "./receipt-schema.js";
6
6
  /** One clone-local merge receipt with recoverable branch values. */
7
7
  export interface MergeDecisionReceipt {
8
8
  /** Receipt schema version. */
@@ -29,7 +29,11 @@ export interface MergeDecisionReceipt {
29
29
  merged_field_hashes?: Record<string, string>;
30
30
  /** Full recoverable scalar decisions, retained only in the clone. */
31
31
  decisions: ItemMergeConflictDecision[];
32
- /** Whether a merge reconciliation history event consumed this receipt. */
32
+ /** Immutable original Git coordinates captured while the driver runs during a rebase. */
33
+ operation?: MergeReceiptOperation;
34
+ /** Explicit audited disposition when the original Git state was restored instead of merging. */
35
+ settlement?: "original_git_state_restored";
36
+ /** Whether a reconciliation history event settled this receipt. */
33
37
  state: "pending" | "reconciled";
34
38
  /** Receipt creation timestamp. */
35
39
  created_at: string;
@@ -121,7 +125,9 @@ export interface MergeReceiptInvalidEvidence {
121
125
  /** Stable failure class suitable for remediation routing and graph analytics. */
122
126
  reason: "directory_unreadable" | "candidate_not_bounded_regular_file" | "candidate_unreadable" | "candidate_invalid_json" | "schema_or_identity_invalid" | "schema_invalid" | "identity_invalid" | "copy_provenance_mismatch";
123
127
  /** Exact validation boundary when schema or identity evidence is rejected. */
124
- validation_error?: "required_fields" | "receipt_id" | "item_id" | "item_path" | "filename" | "collections" | "enums" | "timestamps" | "durable_decisions";
128
+ validation_error?: "required_fields" | "receipt_id" | "item_id" | "item_path" | "filename" | "collections" | "enums" | "timestamps" | "durable_decisions" | "operation";
129
+ /** Exact schema coordinate, excluding rejected values and user-controlled field names. */
130
+ validation_path?: string;
125
131
  /** Receipt identity when the bounded filename itself is a valid receipt id. */
126
132
  receipt_id?: string;
127
133
  /** SHA-256 locator for an unsafe or malformed candidate filename. */
@@ -133,11 +139,13 @@ declare function prepareReceiptSettlement(params: {
133
139
  receiptId: string;
134
140
  evidenceSource: "clone_local" | "durable";
135
141
  reconciledAt: string;
142
+ settlement?: "original_git_state_restored";
136
143
  readReceipt?: (receiptPath: string) => Promise<string | null>;
137
144
  }): Promise<{
138
145
  path: string;
139
146
  content: string;
140
- fingerprint: string;
147
+ fingerprints: string[];
148
+ modern: boolean;
141
149
  } | null>;
142
150
  /** Convert a raw receipt into privacy-safe history context. */
143
151
  export declare function summarizeMergeReceipt(receipt: MergeDecisionReceipt): MergeDecisionReceiptSummary;
@@ -151,6 +159,7 @@ export declare function writeMergeReceipt(params: {
151
159
  unionFields: string[];
152
160
  mergedFieldHashes?: Record<string, string>;
153
161
  decisions: ItemMergeConflictDecision[];
162
+ operation?: MergeReceiptOperation;
154
163
  }): Promise<MergeDecisionReceipt | null>;
155
164
  /** Distinguish a missing optional receipt store from an unreadable one. */
156
165
  declare function receiptDirectoryFailureMeansAbsent(directory: string, error: unknown, inspectAncestor?: (ancestor: string) => Promise<Pick<Stats, "isDirectory">>): Promise<boolean>;
@@ -179,6 +188,7 @@ export declare function partitionMergeReceipts(receipts: MergeDecisionReceipt[])
179
188
  /** Mark a receipt as represented by a committed merge history event. */
180
189
  export declare function markMergeReceiptReconciled(cwd: string, receipt: MergeDecisionReceipt, options?: {
181
190
  requireExisting?: boolean;
191
+ settlement?: "original_git_state_restored";
182
192
  }): Promise<void>;
183
193
  /** Report pending or historical clone-local merge decisions. */
184
194
  export declare function runMergeReceiptReport(options: {
@@ -199,4 +209,3 @@ export declare const _testOnlyMergeReceipts: {
199
209
  prepareReceiptSettlement: typeof prepareReceiptSettlement;
200
210
  receiptDirectoryFailureMeansAbsent: typeof receiptDirectoryFailureMeansAbsent;
201
211
  };
202
- export {};
@@ -7,7 +7,7 @@
7
7
  * value for the coordinator that performed the merge.
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]="dfdd6f10-e11f-59e8-a16f-507dc40d5b31")}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]="e7fe0fee-9519-593d-ac72-b3c7fb63563e")}catch(e){}}();
11
11
  import { execFile } from "node:child_process";
12
12
  import { randomUUID } from "node:crypto";
13
13
  import { readdir, stat } from "node:fs/promises";
@@ -15,74 +15,14 @@ import path from "node:path";
15
15
  import { promisify } from "node:util";
16
16
  import { ensureDir, isFileAbsentError, isFileMissingError, pathExists, writeFileAtomic, } from "../../core/fs/fs-utils.js";
17
17
  import { sha256Hex, stableStringify } from "../../core/shared/serialization.js";
18
- import { isRfc3339DateTime, nowIso } from "../../core/shared/time.js";
19
- import { hashItemScalarDecisionValue, isItemScalarMissingValue, } from "./three-way.js";
18
+ import { nowIso } from "../../core/shared/time.js";
19
+ import { encodeItemScalarDecisionValue, hashItemScalarDecisionValue, isItemScalarMissingValue, } from "./three-way.js";
20
20
  import { readBoundedRegularFile } from "./receipt-file-boundary.js";
21
+ import { DURABLE_VALUE_POLICY, durableValueEvidence, isSafeReceiptId, isMergeDecisionReceipt, isPrehashedValue, normalizeLegacyReceipt, receiptCollectionValidationPath, receiptValidationError } from "./receipt-schema.js";
22
+ export { isSafeReceiptId } from "./receipt-schema.js";
21
23
  const execFileAsync = promisify(execFile);
22
- const RECEIPT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
23
- const RECEIPT_ITEM_ID_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u;
24
- const RECEIPT_FIELD_MAX = 2_048;
25
24
  const RECEIPT_FILE_MAX_BYTES = 16 * 1024 * 1024;
26
- const RECEIPT_VALUE_MAX_DEPTH = 64;
27
- const RECEIPT_VALUE_MAX_NODES = 100_000;
28
25
  const RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT = 100;
29
- const RECEIPT_KEYS = new Set([
30
- "version",
31
- "id",
32
- "item_path",
33
- "item_id",
34
- "requested_preference",
35
- "requested_preference_applied",
36
- "preferred",
37
- "conflict_resolution",
38
- "fields_from_theirs",
39
- "union_fields",
40
- "merged_field_hashes",
41
- "decisions",
42
- "state",
43
- "created_at",
44
- "reconciled_at",
45
- "value_availability",
46
- "value_policy",
47
- "evidence_source",
48
- ]);
49
- /** Return whether a receipt id is safe to expose in cleartext diagnostics. */
50
- export function isSafeReceiptId(value) {
51
- return RECEIPT_ID_PATTERN.test(value);
52
- }
53
- const RECEIPT_DECISION_KEYS = new Set([
54
- "field",
55
- "base",
56
- "ours",
57
- "theirs",
58
- "retained",
59
- "discarded",
60
- "retained_side",
61
- "resolution_basis",
62
- ]);
63
- const DURABLE_VALUE_POLICY = "bounded_non_sensitive_scalars_v1";
64
- const REVIEWABLE_BUILTIN_STATUSES = new Set([
65
- "open",
66
- "in_progress",
67
- "blocked",
68
- "closed",
69
- "canceled",
70
- ]);
71
- const REVIEWABLE_ORDINAL_FIELDS = new Set(["risk", "confidence", "severity"]);
72
- const REVIEWABLE_ORDINAL_VALUES = new Set([
73
- "low",
74
- "medium",
75
- "high",
76
- "critical",
77
- ]);
78
- const REVIEWABLE_DURABLE_VALUES_BY_FIELD = new Map([
79
- ["priority", new Set([0, 1, 2, 3, 4].map(stableStringify))],
80
- ["status", new Set([...REVIEWABLE_BUILTIN_STATUSES].map(stableStringify))],
81
- ...[...REVIEWABLE_ORDINAL_FIELDS].map((field) => [
82
- field,
83
- new Set([...REVIEWABLE_ORDINAL_VALUES].map(stableStringify)),
84
- ]),
85
- ]);
86
26
  /** Resolve the clone-local receipt store without crossing a Git worktree boundary. */
87
27
  async function resolveReceiptDirectory(cwd) {
88
28
  try {
@@ -119,246 +59,13 @@ function durableReceiptDirectory(pmRoot) {
119
59
  function receiptFileName(id) {
120
60
  return `${id}.json`;
121
61
  }
122
- /** Narrow untrusted JSON to a non-array object before schema inspection. */
123
- function isRecord(value) {
124
- return typeof value === "object" && value !== null && !Array.isArray(value);
125
- }
126
- /** Refuse receipt extensions outside the versioned schema key set. */
127
- function hasOnlyKeys(value, allowed) {
128
- return Object.keys(value).every((key) => allowed.has(key));
129
- }
130
- /** Validate bounded dense string collections used in receipt coordinates. */
131
- function isStringArray(value) {
132
- return (Array.isArray(value) &&
133
- value.length <= RECEIPT_FIELD_MAX &&
134
- value.every((entry) => typeof entry === "string" && entry.length > 0 && entry.length <= 256));
135
- }
136
- /** Bound JSON depth and node count before retaining untrusted decision values. */
137
- function hasBoundedJsonStructure(value) {
138
- const pending = [
139
- { value, depth: 0 },
140
- ];
141
- let nodes = 0;
142
- while (pending.length > 0) {
143
- const current = pending.pop();
144
- nodes += 1;
145
- if (nodes > RECEIPT_VALUE_MAX_NODES ||
146
- current.depth > RECEIPT_VALUE_MAX_DEPTH) {
147
- return false;
148
- }
149
- if (Array.isArray(current.value)) {
150
- for (const entry of current.value) {
151
- pending.push({ value: entry, depth: current.depth + 1 });
152
- }
153
- }
154
- else if (isRecord(current.value)) {
155
- for (const entry of Object.values(current.value)) {
156
- pending.push({ value: entry, depth: current.depth + 1 });
157
- }
158
- }
159
- }
160
- return true;
161
- }
162
- /** Validate one clone-local scalar decision and its optional resolution evidence. */
163
- function isMergeDecision(value) {
164
- if (!isRecord(value))
165
- return false;
166
- const retainedSides = [undefined, "ours", "theirs"];
167
- const resolutionBases = [
168
- undefined,
169
- "requested_preference",
170
- "document_updated_at",
171
- "stable_value_tiebreak",
172
- ];
173
- return [
174
- hasOnlyKeys(value, RECEIPT_DECISION_KEYS),
175
- typeof value.field === "string",
176
- typeof value.field === "string" && value.field.length > 0,
177
- typeof value.field === "string" && value.field.length <= 256,
178
- Object.hasOwn(value, "base"),
179
- Object.hasOwn(value, "ours"),
180
- Object.hasOwn(value, "theirs"),
181
- Object.hasOwn(value, "retained"),
182
- Object.hasOwn(value, "discarded"),
183
- hasBoundedJsonStructure(value.base),
184
- hasBoundedJsonStructure(value.ours),
185
- hasBoundedJsonStructure(value.theirs),
186
- hasBoundedJsonStructure(value.retained),
187
- hasBoundedJsonStructure(value.discarded),
188
- retainedSides.includes(value.retained_side),
189
- resolutionBases.includes(value.resolution_basis),
190
- ].every(Boolean);
191
- }
192
- /** Validate the complete field-to-SHA-256 proof map used during repair. */
193
- function isMergedFieldHashes(value) {
194
- if (!isRecord(value))
195
- return false;
196
- const entries = Object.entries(value);
197
- return (entries.length <= RECEIPT_FIELD_MAX &&
198
- entries.every(([field, hash]) => field.length > 0 &&
199
- field.length <= 256 &&
200
- typeof hash === "string" &&
201
- /^[a-f0-9]{64}$/u.test(hash)));
202
- }
203
- /** Require a normalized tracker item path whose basename matches the receipt item. */
204
- function isSafeReceiptItemPath(value, itemId) {
205
- if (typeof value !== "string" ||
206
- value.length === 0 ||
207
- value.length > 4_096 ||
208
- value.includes("\0") ||
209
- value.includes("\\") ||
210
- path.posix.isAbsolute(value)) {
211
- return false;
212
- }
213
- const normalized = path.posix.normalize(value);
214
- return (normalized !== ".." &&
215
- !normalized.startsWith("../") &&
216
- path.posix.basename(normalized, path.posix.extname(normalized)) === itemId);
217
- }
218
- /** Validate receipt arrays and optional merged-field proof coverage. */
219
- function hasValidReceiptCollections(value) {
220
- return !isStringArray(value.fields_from_theirs) ||
221
- !isStringArray(value.union_fields) ||
222
- !Array.isArray(value.decisions) ||
223
- value.decisions.length > RECEIPT_FIELD_MAX ||
224
- !value.decisions.every(isMergeDecision)
225
- ? false
226
- : value.merged_field_hashes === undefined ||
227
- isMergedFieldHashes(value.merged_field_hashes);
228
- }
229
- /** Validate every closed-domain lifecycle and merge-policy field. */
230
- function hasValidReceiptEnums(value) {
231
- const preferences = [undefined, "ours", "theirs"];
232
- const resolutions = [
233
- undefined,
234
- "preferred_side",
235
- "stable_value_order",
236
- "latest_document_update",
237
- ];
238
- const availabilities = [
239
- undefined,
240
- "clone_local",
241
- "hash_only",
242
- "bounded_inline",
243
- "mixed",
244
- ];
245
- const sources = [undefined, "clone_local", "durable"];
246
- return (preferences.includes(value.requested_preference) &&
247
- preferences.includes(value.preferred) &&
248
- (value.requested_preference_applied === undefined ||
249
- typeof value.requested_preference_applied === "boolean") &&
250
- resolutions.includes(value.conflict_resolution) &&
251
- (value.state === "pending" || value.state === "reconciled") &&
252
- availabilities.includes(value.value_availability) &&
253
- (value.value_policy === undefined ||
254
- value.value_policy === DURABLE_VALUE_POLICY) &&
255
- sources.includes(value.evidence_source));
256
- }
257
- /** Validate creation and optional settlement timestamps as RFC 3339 values. */
258
- function hasValidReceiptTimestamps(value) {
259
- return (typeof value.created_at === "string" &&
260
- isRfc3339DateTime(value.created_at) &&
261
- (value.reconciled_at === undefined ||
262
- (typeof value.reconciled_at === "string" &&
263
- isRfc3339DateTime(value.reconciled_at))));
264
- }
265
- /** Validate one hash-only or policy-bounded durable decision value. */
266
- function hasDurableDecisionValue(field, value) {
267
- if (!isRecord(value) || isPrehashedValue(value) === undefined)
268
- return false;
269
- const keys = Object.keys(value);
270
- if (!keys.every((key) => key === "pm_value_hash" || key === "pm_value")) {
271
- return false;
272
- }
273
- if (!Object.hasOwn(value, "pm_value"))
274
- return keys.length === 1;
275
- const preview = value.pm_value;
276
- const allowed = preview === null ||
277
- REVIEWABLE_DURABLE_VALUES_BY_FIELD.get(field)?.has(stableStringify(preview)) === true;
278
- return (allowed && hashItemScalarDecisionValue(preview) === value.pm_value_hash);
279
- }
280
- /** Validate durable decision redaction and the declared availability summary. */
281
- function hasValidDurableDecisions(receipt) {
282
- if (!receipt.decisions.every((decision) => decision.base === null &&
283
- decision.ours === null &&
284
- decision.theirs === null &&
285
- hasDurableDecisionValue(decision.field, decision.retained) &&
286
- hasDurableDecisionValue(decision.field, decision.discarded))) {
287
- return false;
288
- }
289
- const inlineValueCount = receipt.decisions.reduce((count, decision) => count +
290
- (isRecord(decision.retained) &&
291
- Object.hasOwn(decision.retained, "pm_value")
292
- ? 1
293
- : 0) +
294
- (isRecord(decision.discarded) &&
295
- Object.hasOwn(decision.discarded, "pm_value")
296
- ? 1
297
- : 0), 0);
298
- const expectedAvailability = inlineValueCount === 0
299
- ? "hash_only"
300
- : inlineValueCount === receipt.decisions.length * 2
301
- ? "bounded_inline"
302
- : "mixed";
303
- return (receipt.value_availability === expectedAvailability &&
304
- (inlineValueCount === 0 || receipt.value_policy === DURABLE_VALUE_POLICY));
305
- }
306
- /** Return the narrowest validation class for untrusted receipt evidence. */
307
- function receiptValidationError(value, evidenceSource) {
308
- if (!isRecord(value))
309
- return "required_fields";
310
- for (const key of [
311
- "version",
312
- "id",
313
- "item_path",
314
- "item_id",
315
- "fields_from_theirs",
316
- "union_fields",
317
- "decisions",
318
- "state",
319
- "created_at",
320
- ]) {
321
- if (!Object.hasOwn(value, key))
322
- return "required_fields";
323
- }
324
- if (value.version !== 1 || !hasOnlyKeys(value, RECEIPT_KEYS)) {
325
- return "required_fields";
326
- }
327
- if (typeof value.id !== "string" || !isSafeReceiptId(value.id)) {
328
- return "receipt_id";
329
- }
330
- if (typeof value.item_id !== "string" ||
331
- !RECEIPT_ITEM_ID_PATTERN.test(value.item_id)) {
332
- return "item_id";
333
- }
334
- if (!isSafeReceiptItemPath(value.item_path, value.item_id)) {
335
- return "item_path";
336
- }
337
- if (!hasValidReceiptCollections(value))
338
- return "collections";
339
- if (!hasValidReceiptEnums(value))
340
- return "enums";
341
- if (!hasValidReceiptTimestamps(value))
342
- return "timestamps";
343
- if (evidenceSource === "durable" &&
344
- !hasValidDurableDecisions(value)) {
345
- return "durable_decisions";
346
- }
347
- return null;
348
- }
349
- /** Validate a complete receipt against its clone-local or durable evidence policy. */
350
- function isMergeDecisionReceipt(value, evidenceSource) {
351
- return receiptValidationError(value, evidenceSource) === null;
352
- }
353
- /** Read one receipt through the no-follow bounded regular-file guard. */
354
- async function readBoundedRegularReceiptFile(receiptPath) {
355
- return readBoundedRegularFile(receiptPath, RECEIPT_FILE_MAX_BYTES);
356
- }
357
62
  /** Revalidate one receipt and stage its atomic reconciled-state replacement. */
358
63
  async function prepareReceiptSettlement(params) {
359
64
  let raw;
360
65
  try {
361
- raw = await (params.readReceipt ?? readBoundedRegularReceiptFile)(params.receiptPath);
66
+ raw = params.readReceipt
67
+ ? await params.readReceipt(params.receiptPath)
68
+ : await readBoundedRegularFile(params.receiptPath, RECEIPT_FILE_MAX_BYTES);
362
69
  }
363
70
  catch (error) {
364
71
  if (isFileAbsentError(error))
@@ -370,29 +77,32 @@ async function prepareReceiptSettlement(params) {
370
77
  }
371
78
  let parsed;
372
79
  try {
373
- parsed = JSON.parse(raw);
80
+ parsed = normalizeLegacyReceipt(JSON.parse(raw));
374
81
  }
375
82
  catch {
376
83
  throw new Error(`Receipt ${params.receiptId} settlement source is not valid JSON.`);
377
84
  }
378
85
  if (!isMergeDecisionReceipt(parsed, params.evidenceSource) ||
379
86
  parsed.id !== params.receiptId ||
87
+ (params.settlement !== undefined && parsed.operation === undefined) ||
380
88
  path.basename(params.receiptPath) !== receiptFileName(parsed.id)) {
381
89
  throw new Error(`Receipt ${params.receiptId} settlement source failed schema or identity validation.`);
382
90
  }
383
91
  const { evidence_source: _evidenceSource, ...persistedReceipt } = parsed;
384
92
  return {
385
93
  path: params.receiptPath,
386
- fingerprint: receiptProvenanceFingerprint(parsed),
94
+ fingerprints: receiptProvenanceFingerprints(parsed),
95
+ modern: parsed.value_policy !== undefined,
387
96
  content: `${JSON.stringify({
388
97
  ...persistedReceipt,
389
98
  state: "reconciled",
390
99
  reconciled_at: params.reconciledAt,
100
+ ...(params.settlement ? { settlement: params.settlement } : {}),
391
101
  }, null, 2)}\n`,
392
102
  };
393
103
  }
394
104
  /** Fingerprint immutable receipt provenance while excluding lifecycle state. */
395
- function receiptProvenanceFingerprint(receipt) {
105
+ function receiptProvenanceFingerprint(receipt, legacy = false) {
396
106
  return sha256Hex(stableStringify({
397
107
  id: receipt.id,
398
108
  item_id: receipt.item_id,
@@ -403,15 +113,31 @@ function receiptProvenanceFingerprint(receipt) {
403
113
  fields_from_theirs: receipt.fields_from_theirs,
404
114
  union_fields: receipt.union_fields,
405
115
  merged_field_hashes: receipt.merged_field_hashes ?? null,
406
- decisions: summarizeMergeReceipt(receipt).decisions,
116
+ operation: receipt.operation ?? null,
117
+ decisions: legacy
118
+ ? receipt.decisions.map((decision) => ({
119
+ field: decision.field,
120
+ retained_hash: sha256Hex(stableStringify(encodeItemScalarDecisionValue(decision.retained))),
121
+ discarded_hash: sha256Hex(stableStringify(encodeItemScalarDecisionValue(decision.discarded))),
122
+ }))
123
+ : summarizeMergeReceipt(receipt).decisions,
407
124
  }));
408
125
  }
126
+ /** Compare historical plain-scalar and current presence-domain hashes without changing stored evidence. */
127
+ function receiptProvenanceFingerprints(receipt) {
128
+ const fingerprints = [receiptProvenanceFingerprint(receipt)];
129
+ if (receipt.value_policy === undefined &&
130
+ (receipt.value_availability === undefined || receipt.value_availability === "clone_local")) {
131
+ fingerprints.push(receiptProvenanceFingerprint(receipt, true));
132
+ }
133
+ return fingerprints;
134
+ }
409
135
  /** Merge clone-local and durable lifecycle state without losing pending work. */
410
136
  function mergeReceiptCopyLifecycle(local, durable) {
411
- if (local.state === "reconciled" && durable.state === "reconciled") {
137
+ if (local.state === "reconciled" && durable.state === "reconciled" && local.settlement === durable.settlement) {
412
138
  return local;
413
139
  }
414
- const { reconciled_at: _reconciledAt, ...pendingLocal } = local;
140
+ const { reconciled_at: _reconciledAt, settlement: _settlement, ...pendingLocal } = local;
415
141
  return { ...pendingLocal, state: "pending" };
416
142
  }
417
143
  /** Convert a raw receipt into privacy-safe history context. */
@@ -440,30 +166,6 @@ export function summarizeMergeReceipt(receipt) {
440
166
  })),
441
167
  };
442
168
  }
443
- /** Extract a structurally valid precomputed SHA-256 decision hash. */
444
- function isPrehashedValue(value) {
445
- if (typeof value !== "object" ||
446
- value === null ||
447
- Array.isArray(value) ||
448
- !("pm_value_hash" in value)) {
449
- return undefined;
450
- }
451
- const hash = value.pm_value_hash;
452
- return typeof hash === "string" && /^[a-f0-9]{64}$/u.test(hash)
453
- ? hash
454
- : undefined;
455
- }
456
- /** Convert a raw scalar into hashed evidence with an allowlisted preview. */
457
- function durableValueEvidence(field, value) {
458
- const missing = isItemScalarMissingValue(value);
459
- const pmValueHash = hashItemScalarDecisionValue(missing ? undefined : value);
460
- const reviewable = !missing &&
461
- (value === null ||
462
- REVIEWABLE_DURABLE_VALUES_BY_FIELD.get(field)?.has(stableStringify(value)) === true);
463
- return reviewable
464
- ? { pm_value_hash: pmValueHash, pm_value: value }
465
- : { pm_value_hash: pmValueHash };
466
- }
467
169
  /** Persist one item-driver outcome in the clone-local Git directory. */
468
170
  export async function writeMergeReceipt(params) {
469
171
  const directory = await resolveReceiptDirectory(params.cwd);
@@ -486,7 +188,15 @@ export async function writeMergeReceipt(params) {
486
188
  ...(params.mergedFieldHashes
487
189
  ? { merged_field_hashes: { ...params.mergedFieldHashes } }
488
190
  : {}),
489
- decisions: structuredClone(params.decisions),
191
+ decisions: structuredClone(params.decisions).map((decision) => ({
192
+ ...decision,
193
+ base: encodeItemScalarDecisionValue(decision.base),
194
+ ours: encodeItemScalarDecisionValue(decision.ours),
195
+ theirs: encodeItemScalarDecisionValue(decision.theirs),
196
+ retained: encodeItemScalarDecisionValue(decision.retained),
197
+ discarded: encodeItemScalarDecisionValue(decision.discarded),
198
+ })),
199
+ ...(params.operation ? { operation: params.operation } : {}),
490
200
  state: "pending",
491
201
  created_at: nowIso(),
492
202
  value_availability: "clone_local",
@@ -552,6 +262,7 @@ async function readReceiptsFromDirectory(directory, evidenceSource) {
552
262
  invalidEvidence.push({
553
263
  evidence_source: evidenceSource,
554
264
  reason: candidate.reason,
265
+ ...(candidate.validation_path ? { validation_path: candidate.validation_path } : {}),
555
266
  ...(candidate.validation_error === undefined
556
267
  ? {}
557
268
  : { validation_error: candidate.validation_error }),
@@ -601,7 +312,7 @@ async function receiptDirectoryFailureMeansAbsent(directory, error, inspectAnces
601
312
  async function inspectReceiptCandidate(directory, name, evidenceSource) {
602
313
  let raw;
603
314
  try {
604
- raw = await readBoundedRegularReceiptFile(path.join(directory, name));
315
+ raw = await readBoundedRegularFile(path.join(directory, name), RECEIPT_FILE_MAX_BYTES);
605
316
  }
606
317
  catch {
607
318
  return { reason: "candidate_unreadable" };
@@ -610,7 +321,7 @@ async function inspectReceiptCandidate(directory, name, evidenceSource) {
610
321
  return { reason: "candidate_not_bounded_regular_file" };
611
322
  let parsed;
612
323
  try {
613
- parsed = JSON.parse(raw);
324
+ parsed = normalizeLegacyReceipt(JSON.parse(raw));
614
325
  }
615
326
  catch {
616
327
  return { reason: "candidate_invalid_json" };
@@ -623,6 +334,7 @@ async function inspectReceiptCandidate(directory, name, evidenceSource) {
623
334
  ? "identity_invalid"
624
335
  : "schema_invalid",
625
336
  validation_error: validationError,
337
+ ...(validationError === "collections" ? { validation_path: receiptCollectionValidationPath(parsed) } : {}),
626
338
  };
627
339
  }
628
340
  const receipt = parsed;
@@ -660,8 +372,8 @@ export async function inspectMergeReceiptEvidence(cwd, options = {}) {
660
372
  for (const receipt of local.receipts) {
661
373
  const durableCopy = receipts.get(receipt.id);
662
374
  if (durableCopy !== undefined &&
663
- receiptProvenanceFingerprint(durableCopy) !==
664
- receiptProvenanceFingerprint(receipt)) {
375
+ !(durableCopy.value_policy === undefined ? receiptProvenanceFingerprints(receipt)
376
+ : [receiptProvenanceFingerprint(receipt)]).includes(receiptProvenanceFingerprint(durableCopy))) {
665
377
  receipts.delete(receipt.id);
666
378
  divergentCopyCount += 1;
667
379
  if (local.invalid_evidence.length +
@@ -742,6 +454,7 @@ export async function markMergeReceiptReconciled(cwd, receipt, options = {}) {
742
454
  receiptId: receipt.id,
743
455
  evidenceSource: "clone_local",
744
456
  reconciledAt,
457
+ settlement: options.settlement,
745
458
  }),
746
459
  ...(durablePath === null
747
460
  ? []
@@ -751,6 +464,7 @@ export async function markMergeReceiptReconciled(cwd, receipt, options = {}) {
751
464
  receiptId: receipt.id,
752
465
  evidenceSource: "durable",
753
466
  reconciledAt,
467
+ settlement: options.settlement,
754
468
  }),
755
469
  ]),
756
470
  ]);
@@ -761,9 +475,9 @@ export async function markMergeReceiptReconciled(cwd, receipt, options = {}) {
761
475
  }
762
476
  return;
763
477
  }
764
- const expectedFingerprint = receiptProvenanceFingerprint(receipt);
765
- if (new Set([expectedFingerprint, ...writes.map((write) => write.fingerprint)])
766
- .size !== 1) {
478
+ const expectedFingerprints = writes.some((write) => write.modern)
479
+ ? [receiptProvenanceFingerprint(receipt)] : receiptProvenanceFingerprints(receipt);
480
+ if (!expectedFingerprints.some((fingerprint) => writes.every((write) => write.fingerprints.includes(fingerprint)))) {
767
481
  throw new Error(`Receipt ${receipt.id} settlement copies disagree on immutable merge provenance.`);
768
482
  }
769
483
  for (const write of writes) {
@@ -809,4 +523,4 @@ export const _testOnlyMergeReceipts = {
809
523
  receiptDirectoryFailureMeansAbsent,
810
524
  };
811
525
  //# sourceMappingURL=receipts.js.map
812
- //# debugId=dfdd6f10-e11f-59e8-a16f-507dc40d5b31
526
+ //# debugId=e7fe0fee-9519-593d-ac72-b3c7fb63563e
@@ -29,6 +29,8 @@ export interface MergeReconcileResult {
29
29
  receipts: {
30
30
  pending_before: number;
31
31
  reconciled: number;
32
+ /** Receipts proven to restore their original Git state; these do not represent applied merges. */
33
+ abandoned: number;
32
34
  summaries: MergeDecisionReceiptSummary[];
33
35
  /** Missing durable references discovered before this reconciliation. */
34
36
  missing_history_references_before: number;
@@ -5,7 +5,8 @@
5
5
  * item-history stream, then verifies history and storage integrity in one call.
6
6
  */
7
7
 
8
- !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]="3a3372be-37e4-578a-8be9-9ad7bc585dbd")}catch(e){}}();
8
+ !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]="2696d75a-02f4-5c16-b77e-7798e7d020f9")}catch(e){}}();
9
+ import { settleAbandonedMergeReceipts } from "./abandoned-receipts.js";
9
10
  import { readFile } from "node:fs/promises";
10
11
  import path from "node:path";
11
12
  import { EXIT_CODE } from "../../core/shared/constants.js";
@@ -229,8 +230,13 @@ export async function runMergeReconcile(options, global) {
229
230
  global,
230
231
  });
231
232
  const pendingReceipts = receiptEvidence.receipts;
233
+ const abandonedReceiptIds = await settleAbandonedMergeReceipts({
234
+ cwd: gitWorkspaceRoot, receipts: pendingReceipts, dryRun,
235
+ author: options.author, message: options.message, global,
236
+ });
237
+ const abandonedIds = new Set(abandonedReceiptIds);
232
238
  const receiptsByItem = new Map();
233
- for (const receipt of pendingReceipts) {
239
+ for (const receipt of pendingReceipts.filter((entry) => !abandonedIds.has(entry.id))) {
234
240
  const receipts = receiptsByItem.get(receipt.item_id) ?? [];
235
241
  receipts.push(receipt);
236
242
  receiptsByItem.set(receipt.item_id, receipts);
@@ -291,7 +297,7 @@ export async function runMergeReconcile(options, global) {
291
297
  const mergeChecksGreen = validation.checks.every((check) => check.status === "ok");
292
298
  const receiptSettlementComplete = dryRun
293
299
  ? pendingReceipts.length === 0
294
- : reconciledReceiptCount === pendingReceipts.length;
300
+ : reconciledReceiptCount + abandonedReceiptIds.length === pendingReceipts.length;
295
301
  const ok = [
296
302
  repair.totals.failed === 0,
297
303
  mergeChecksGreen,
@@ -331,6 +337,7 @@ export async function runMergeReconcile(options, global) {
331
337
  receipts: {
332
338
  pending_before: pendingReceipts.length,
333
339
  reconciled: reconciledReceiptCount,
340
+ abandoned: abandonedReceiptIds.length,
334
341
  summaries: pendingReceipts.map(summarizeMergeReceipt),
335
342
  missing_history_references_before: missingBefore.count,
336
343
  legacy_disposition_eligible: legacyDispositionCandidates.length,
@@ -343,4 +350,4 @@ export async function runMergeReconcile(options, global) {
343
350
  };
344
351
  }
345
352
  //# sourceMappingURL=reconcile.js.map
346
- //# debugId=3a3372be-37e4-578a-8be9-9ad7bc585dbd
353
+ //# debugId=2696d75a-02f4-5c16-b77e-7798e7d020f9