@unbrained/pm-cli 2026.9.3 → 2026.9.5

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 (228) 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 +52 -0
  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 +15 -6
  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 +6 -2
  21. package/dist/cli-bundle/bundle-manifest.json +154 -154
  22. package/dist/cli-bundle/chunks/{chunk-EDPIS7EB.js → chunk-3HBDTJRK.js} +2 -2
  23. package/dist/cli-bundle/chunks/chunk-66VPKFP2.js +5 -0
  24. package/dist/cli-bundle/chunks/{chunk-Y4PJ6HPK.js → chunk-HUAQITVW.js} +2 -2
  25. package/dist/cli-bundle/chunks/{chunk-DKGP3QLE.js → chunk-IBYREGSP.js} +2 -2
  26. package/dist/cli-bundle/chunks/chunk-LUI4E32I.js +231 -0
  27. package/dist/cli-bundle/chunks/{chunk-UPPM2FOZ.js → chunk-QPZHQY2O.js} +2 -2
  28. package/dist/cli-bundle/chunks/{chunk-34HASX3S.js → chunk-SF6SM6U5.js} +2 -2
  29. package/dist/cli-bundle/chunks/chunk-SVK5Y64D.js +40 -0
  30. package/dist/cli-bundle/chunks/chunk-V5GBD5KV.js +9 -0
  31. package/dist/cli-bundle/chunks/{chunk-LENC656S.js → chunk-WWAEGW4V.js} +67 -70
  32. package/dist/cli-bundle/chunks/chunk-ZRAQS3UA.js +8 -0
  33. package/dist/cli-bundle/chunks/{register-list-query-W2ZTOMKP.js → register-list-query-AYKGODWT.js} +3 -3
  34. package/dist/cli-bundle/chunks/{register-mutation-RVND5NCG.js → register-mutation-YZYG53SQ.js} +4 -4
  35. package/dist/cli-bundle/chunks/register-operations-KFSCC7QV.js +2 -0
  36. package/dist/cli-bundle/chunks/{register-setup-D3ZFHY3L.js → register-setup-UJCRVGBG.js} +2 -2
  37. package/dist/cli-bundle/focused-chunks/chunk-24IG6ONG.js +2 -0
  38. package/dist/cli-bundle/focused-chunks/{chunk-M5M6SVHS.js → chunk-3ASU6WGI.js} +2 -2
  39. package/dist/cli-bundle/focused-chunks/chunk-3ITLIBKS.js +56 -0
  40. package/dist/cli-bundle/focused-chunks/chunk-3NQD5YDG.js +2 -0
  41. package/dist/cli-bundle/focused-chunks/{chunk-S27XMBIN.js → chunk-4OSHLNUE.js} +2 -2
  42. package/dist/cli-bundle/focused-chunks/chunk-65N6JHAN.js +2 -0
  43. package/dist/cli-bundle/focused-chunks/{chunk-5RFXUPN5.js → chunk-B2TTQGFG.js} +2 -2
  44. package/dist/cli-bundle/focused-chunks/chunk-GUBM3RGY.js +20 -0
  45. package/dist/cli-bundle/focused-chunks/{chunk-Q45NHPF2.js → chunk-H5FOGAVD.js} +2 -2
  46. package/dist/cli-bundle/focused-chunks/chunk-JX3RCVCX.js +2 -0
  47. package/dist/cli-bundle/focused-chunks/{chunk-D7KXFBXV.js → chunk-KWWCBWFO.js} +2 -2
  48. package/dist/cli-bundle/focused-chunks/chunk-MV752SUC.js +12 -0
  49. package/dist/cli-bundle/focused-chunks/chunk-PKIPMCFS.js +6 -0
  50. package/dist/cli-bundle/focused-chunks/{chunk-4HJFY4MB.js → chunk-QL66QNWG.js} +2 -2
  51. package/dist/cli-bundle/focused-chunks/chunk-RJMR2JIF.js +9 -0
  52. package/dist/cli-bundle/focused-chunks/chunk-WIUPWIZR.js +177 -0
  53. package/dist/cli-bundle/focused-chunks/chunk-WOCIPMW7.js +2 -0
  54. package/dist/cli-bundle/focused-chunks/{chunk-GT7MXDLW.js → chunk-YEF5PQQ2.js} +2 -2
  55. package/dist/cli-bundle/main.js +22 -15
  56. package/dist/cli-bundle/sdk-authoring.js +1 -1
  57. package/dist/cli-bundle/sdk-contracts.js +1 -1
  58. package/dist/cli-bundle/sdk-core.js +29 -32
  59. package/dist/cli-bundle/sdk-governance.js +1 -1
  60. package/dist/cli-bundle/sdk-graph.js +1 -1
  61. package/dist/cli-bundle/sdk-merge.js +45 -37
  62. package/dist/cli-bundle/sdk-query.js +1 -1
  63. package/dist/cli-bundle/sdk-runtime.js +1 -1
  64. package/dist/cli-bundle/sdk-testing.js +1 -1
  65. package/dist/cli-bundle/sdk.js +10 -10
  66. package/dist/core/diagnostics/remediation.js +4 -4
  67. package/dist/core/extensions/extension-types.d.ts +2 -0
  68. package/dist/core/extensions/extension-types.js +2 -2
  69. package/dist/core/history/drift-scan.d.ts +5 -0
  70. package/dist/core/history/drift-scan.js +16 -6
  71. package/dist/core/history/event-classification.d.ts +1 -1
  72. package/dist/core/history/event-classification.js +3 -2
  73. package/dist/core/history/history.d.ts +37 -1
  74. package/dist/core/history/history.js +311 -59
  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 +70 -36
  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/lock/lock.d.ts +10 -0
  91. package/dist/core/lock/lock.js +42 -3
  92. package/dist/core/shared/author.d.ts +3 -1
  93. package/dist/core/shared/author.js +111 -23
  94. package/dist/core/shared/errors.d.ts +8 -0
  95. package/dist/core/shared/errors.js +2 -2
  96. package/dist/core/store/item-store.d.ts +14 -0
  97. package/dist/core/store/item-store.js +48 -10
  98. package/dist/core/store/settings-validator.js +3 -2
  99. package/dist/sdk/actionability.d.ts +1 -1
  100. package/dist/sdk/actionability.js +3 -3
  101. package/dist/sdk/cli-contracts/command-aliases.js +11 -2
  102. package/dist/sdk/cli-contracts/completeness.js +4 -3
  103. package/dist/sdk/cli-contracts/flag-contracts.js +29 -5
  104. package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +19 -12
  105. package/dist/sdk/cli-contracts/tool-parameter-tables.js +14 -2
  106. package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
  107. package/dist/sdk/cli-contracts/tool-schema.js +19 -6
  108. package/dist/sdk/context-intent-contracts.js +3 -3
  109. package/dist/sdk/context-usage.js +9 -11
  110. package/dist/sdk/define.d.ts +1 -1
  111. package/dist/sdk/define.js +2 -2
  112. package/dist/sdk/environment/agent-environment.d.ts +296 -0
  113. package/dist/sdk/environment/agent-environment.js +467 -0
  114. package/dist/sdk/environment/project-runtime-compatibility.d.ts +19 -1
  115. package/dist/sdk/environment/project-runtime-compatibility.js +118 -21
  116. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +98 -38
  117. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +50 -2
  118. package/dist/sdk/governance/health.js +13 -44
  119. package/dist/sdk/governance/merge-receipt-history.d.ts +62 -0
  120. package/dist/sdk/governance/merge-receipt-history.js +207 -0
  121. package/dist/sdk/governance/provenance-health.js +9 -3
  122. package/dist/sdk/governance/validate-history-drift.js +16 -5
  123. package/dist/sdk/guide-topics.js +116 -2
  124. package/dist/sdk/history/salvage.d.ts +35 -0
  125. package/dist/sdk/history/salvage.js +248 -0
  126. package/dist/sdk/history-compact.js +41 -37
  127. package/dist/sdk/history-read.d.ts +3 -2
  128. package/dist/sdk/history-read.js +6 -3
  129. package/dist/sdk/history-redact.js +58 -23
  130. package/dist/sdk/history-repair.d.ts +5 -0
  131. package/dist/sdk/history-repair.js +28 -6
  132. package/dist/sdk/index.d.ts +2 -0
  133. package/dist/sdk/index.js +4 -2
  134. package/dist/sdk/lifecycle/claim.d.ts +4 -0
  135. package/dist/sdk/lifecycle/claim.js +9 -3
  136. package/dist/sdk/lifecycle/restore.d.ts +1 -0
  137. package/dist/sdk/lifecycle/restore.js +19 -7
  138. package/dist/sdk/merge/driver.d.ts +3 -1
  139. package/dist/sdk/merge/driver.js +9 -9
  140. package/dist/sdk/merge/index.d.ts +1 -1
  141. package/dist/sdk/merge/index.js +3 -3
  142. package/dist/sdk/merge/receipts.d.ts +17 -6
  143. package/dist/sdk/merge/receipts.js +246 -75
  144. package/dist/sdk/merge/reconcile.d.ts +9 -7
  145. package/dist/sdk/merge/reconcile.js +175 -12
  146. package/dist/sdk/merge/three-way.d.ts +22 -2
  147. package/dist/sdk/merge/three-way.js +92 -25
  148. package/dist/sdk/output-projection.d.ts +3 -1
  149. package/dist/sdk/output-projection.js +11 -3
  150. package/dist/sdk/public-surface.json +1 -0
  151. package/dist/sdk/query/context.d.ts +9 -0
  152. package/dist/sdk/query/context.js +33 -21
  153. package/dist/sdk/query/get.d.ts +2 -2
  154. package/dist/sdk/query/get.js +4 -3
  155. package/dist/sdk/query/history.d.ts +11 -0
  156. package/dist/sdk/query/history.js +34 -10
  157. package/dist/sdk/query/next.d.ts +15 -1
  158. package/dist/sdk/query/next.js +43 -20
  159. package/dist/sdk/query.d.ts +1 -0
  160. package/dist/sdk/query.js +3 -2
  161. package/dist/sdk/runtime-primitives.d.ts +3 -3
  162. package/dist/sdk/runtime-primitives.js +5 -5
  163. package/dist/types.d.ts +27 -0
  164. package/dist/types.js +2 -2
  165. package/docs/AGENT_ENVIRONMENTS.md +47 -0
  166. package/docs/AGENT_GUIDE.md +36 -0
  167. package/docs/AGENT_PROVENANCE_ADR.md +17 -2
  168. package/docs/CONTEXT_INTEGRITY_CONTRACTS.md +38 -1
  169. package/docs/HISTORY_RECOVERY.md +146 -0
  170. package/docs/MERGE_SAFETY.md +119 -23
  171. package/docs/README.md +2 -0
  172. package/docs/RELEASING.md +25 -11
  173. package/docs/SDK_CONTEXT_EVIDENCE_CONTRACTS.md +24 -2
  174. package/docs/SDK_CONTEXT_TRUTH_CONTRACTS.md +24 -1
  175. package/docs/SDK_EXECUTION_CONTRACTS.md +105 -0
  176. package/docs/SDK_RUNTIME_BOUNDARIES.md +17 -1
  177. package/docs/TESTING.md +5 -0
  178. package/docs/agent-task-token-baseline.json +17 -17
  179. package/docs/agent-task-transcripts.json +2 -1
  180. package/docs/examples/sdk-agent-environment/README.md +14 -0
  181. package/docs/examples/sdk-agent-environment/package.json +8 -0
  182. package/docs/examples/sdk-agent-environment/run-agent-environment.mjs +154 -0
  183. package/docs/generated/FLAG_LEXICON_BUDGETS.md +10 -10
  184. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +12 -3
  185. package/marketplace.json +2 -2
  186. package/package.json +5 -6
  187. package/packages/pm-beads/package.json +1 -1
  188. package/packages/pm-calendar/package.json +1 -1
  189. package/packages/pm-command-kit/package.json +1 -1
  190. package/packages/pm-digital-twin/package.json +1 -1
  191. package/packages/pm-governance-audit/package.json +1 -1
  192. package/packages/pm-guide-shell/package.json +1 -1
  193. package/packages/pm-kanban/package.json +1 -1
  194. package/packages/pm-lifecycle-hooks/package.json +1 -1
  195. package/packages/pm-linked-test-adapters/package.json +1 -1
  196. package/packages/pm-search-advanced/package.json +1 -1
  197. package/packages/pm-templates/package.json +1 -1
  198. package/packages/pm-todos/package.json +1 -1
  199. package/packages/pm-vcs/package.json +1 -1
  200. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  201. package/plugins/pm-claude/skills/pm-audit/SKILL.md +2 -2
  202. package/plugins/pm-claude/skills/pm-developer/SKILL.md +2 -2
  203. package/plugins/pm-claude/skills/pm-planner/SKILL.md +2 -2
  204. package/plugins/pm-claude/skills/pm-release/SKILL.md +2 -2
  205. package/plugins/pm-claude/skills/pm-workflow/SKILL.md +3 -3
  206. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  207. package/plugins/pm-codex/skills/pm-auditor/SKILL.md +2 -2
  208. package/plugins/pm-codex/skills/pm-native/SKILL.md +3 -4
  209. package/plugins/pm-codex/skills/pm-release/SKILL.md +2 -2
  210. package/scripts/finalize-build.mjs +13 -1
  211. package/dist/cli-bundle/chunks/chunk-AY324UJ5.js +0 -9
  212. package/dist/cli-bundle/chunks/chunk-E5RBBCXO.js +0 -230
  213. package/dist/cli-bundle/chunks/chunk-IEHHE6GA.js +0 -5
  214. package/dist/cli-bundle/chunks/chunk-L7B7343B.js +0 -8
  215. package/dist/cli-bundle/chunks/chunk-LISESA7W.js +0 -35
  216. package/dist/cli-bundle/chunks/register-operations-TTROIVC4.js +0 -2
  217. package/dist/cli-bundle/focused-chunks/chunk-3ZBI35HU.js +0 -176
  218. package/dist/cli-bundle/focused-chunks/chunk-4RRHC3S4.js +0 -6
  219. package/dist/cli-bundle/focused-chunks/chunk-4WR46RMG.js +0 -9
  220. package/dist/cli-bundle/focused-chunks/chunk-BWKHEIVF.js +0 -2
  221. package/dist/cli-bundle/focused-chunks/chunk-GHCZ3YFR.js +0 -20
  222. package/dist/cli-bundle/focused-chunks/chunk-KBLZRU62.js +0 -2
  223. package/dist/cli-bundle/focused-chunks/chunk-KZ2NB3ET.js +0 -2
  224. package/dist/cli-bundle/focused-chunks/chunk-OL5EH6I3.js +0 -2
  225. package/dist/cli-bundle/focused-chunks/chunk-QKHCSUGJ.js +0 -51
  226. package/dist/cli-bundle/focused-chunks/chunk-TXE4TSHM.js +0 -12
  227. package/dist/cli-bundle/focused-chunks/chunk-UCLO3XDU.js +0 -2
  228. package/sdk/public-surface.json +0 -32524
@@ -0,0 +1,248 @@
1
+ /**
2
+ * @module sdk/history/salvage
3
+ * Recover an invalid JSONL suffix while preserving every verified prefix byte.
4
+ */
5
+
6
+ !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]="49af8ec4-dd6b-56ef-8833-9c776fa35c85")}catch(e){}}();
7
+ import { isUtf8 } from "node:buffer";
8
+ import { readFile } from "node:fs/promises";
9
+ import { isFileMissingError, readFileIfExists, } from "../../core/fs/fs-utils.js";
10
+ import { acquireLock } from "../../core/lock/lock.js";
11
+ import { createHistoryEntry, sealHistoryRecord, } from "../../core/history/history.js";
12
+ import { checkHistoryRewriteOwnership, writeHistoryRawWithRollback, } from "../../core/history/history-rewrite.js";
13
+ import { replayHistoryToTarget } from "../../core/history/projection.js";
14
+ import { replayToItemDocument, verifyHistoryChainWithVersion, } from "../../core/history/replay.js";
15
+ import { findFirstMergeConflictMarker } from "../../core/shared/conflict-markers.js";
16
+ import { EXIT_CODE } from "../../core/shared/constants.js";
17
+ import { PmCliError } from "../../core/shared/errors.js";
18
+ import { sha256Hex } from "../../core/shared/serialization.js";
19
+ import { nowIso } from "../../core/shared/time.js";
20
+ import { runActiveOnReadHooks, runActiveOnWriteHooks, } from "../../core/extensions/index.js";
21
+ import { locateItem, readLocatedItemSnapshot, } from "../../core/store/item-store.js";
22
+ /** Decode a whole record or identify an invalid suffix without hiding a valid record. */
23
+ function decodeSalvageLine(line) {
24
+ let parsed;
25
+ try {
26
+ parsed = JSON.parse(line);
27
+ }
28
+ catch {
29
+ // A complete record followed by crash padding is not a disposable line.
30
+ // Refuse instead of silently dropping that record along with the padding.
31
+ if (line.trimStart().startsWith("{") && line.includes("}")) {
32
+ throw new PmCliError("History salvage cannot discard a potentially complete record with trailing corruption.", EXIT_CODE.CONFLICT, { code: "history_salvage_refused" });
33
+ }
34
+ return null;
35
+ }
36
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
37
+ throw new PmCliError("History salvage would conceal interior corruption or a malformed record.", EXIT_CODE.CONFLICT, { code: "history_salvage_refused" });
38
+ }
39
+ const entry = parsed;
40
+ if (!Array.isArray(entry.patch) ||
41
+ ![
42
+ entry.ts,
43
+ entry.author,
44
+ entry.op,
45
+ entry.before_hash,
46
+ entry.after_hash,
47
+ ].every((field) => typeof field === "string")) {
48
+ throw new PmCliError("History salvage requires complete history records before the invalid tail.", EXIT_CODE.CONFLICT, { code: "history_salvage_refused" });
49
+ }
50
+ return entry;
51
+ }
52
+ /**
53
+ * Select a hash-verified prefix. Any parseable JSON after the first invalid
54
+ * line makes the corruption interior and therefore unsalvageable by truncation.
55
+ */
56
+ export function inspectHistoryTail(raw) {
57
+ if (findFirstMergeConflictMarker(raw)) {
58
+ throw new PmCliError("Resolve history merge conflicts before salvage.", EXIT_CODE.CONFLICT, { code: "history_salvage_refused" });
59
+ }
60
+ const entries = [];
61
+ let offset = 0;
62
+ let invalidOffset;
63
+ let invalidLine = 0;
64
+ for (const [index, line] of raw.split("\n").entries()) {
65
+ if (!line.trim()) {
66
+ offset += line.length + 1;
67
+ continue;
68
+ }
69
+ const parsed = decodeSalvageLine(line);
70
+ if (parsed === null) {
71
+ if (invalidOffset === undefined) {
72
+ invalidOffset = offset;
73
+ invalidLine = index + 1;
74
+ }
75
+ offset += line.length + 1;
76
+ continue;
77
+ }
78
+ if (invalidOffset !== undefined) {
79
+ throw new PmCliError("History salvage would discard a record or conceal interior corruption.", EXIT_CODE.CONFLICT, {
80
+ code: "history_salvage_refused",
81
+ required: "Recover the damaged record from version control or a backup; no bytes were changed.",
82
+ });
83
+ }
84
+ entries.push(parsed);
85
+ offset += line.length + 1;
86
+ }
87
+ const verification = verifyHistoryChainWithVersion(entries);
88
+ if (!verification.ok || verification.item_hash_version === undefined) {
89
+ throw new PmCliError("History salvage requires a nonempty verified prefix.", EXIT_CODE.CONFLICT, {
90
+ code: "history_salvage_refused",
91
+ verification_errors: verification.errors,
92
+ required: "Recover an intact history prefix from version control or a backup.",
93
+ });
94
+ }
95
+ return {
96
+ entries,
97
+ itemHashVersion: verification.item_hash_version,
98
+ prefix: invalidOffset === undefined ? raw : raw.slice(0, invalidOffset),
99
+ receipt: invalidOffset === undefined
100
+ ? null
101
+ : {
102
+ first_invalid_line: invalidLine,
103
+ discarded_bytes: Buffer.byteLength(raw.slice(invalidOffset)),
104
+ discarded_sha256: sha256Hex(raw.slice(invalidOffset)),
105
+ retained_entries: entries.length,
106
+ },
107
+ };
108
+ }
109
+ /** Prefer the live item's owner; history supplies ownership only when its file is unreadable. */
110
+ async function readSalvageOwnership(subject, settings, fallback) {
111
+ if (!subject.located)
112
+ return { itemRaw: null, document: fallback };
113
+ const snapshot = await readLocatedItemSnapshot(subject.located, {
114
+ schema: settings.schema,
115
+ });
116
+ return { itemRaw: snapshot.raw, document: snapshot.document ?? fallback };
117
+ }
118
+ /** Refuse lossy decoding so suffix receipts and rollback always describe original bytes. */
119
+ async function readSalvageStream(historyPath) {
120
+ let bytes;
121
+ try {
122
+ bytes = await readFile(historyPath);
123
+ }
124
+ catch (error) {
125
+ if (isFileMissingError(error))
126
+ return null;
127
+ throw error;
128
+ }
129
+ if (!isUtf8(bytes)) {
130
+ throw new PmCliError("History salvage requires UTF-8 input; recover binary corruption from a byte-preserving backup.", EXIT_CODE.CONFLICT, { code: "history_salvage_refused" });
131
+ }
132
+ return bytes.toString("utf8");
133
+ }
134
+ /** Salvage one explicitly selected stream under its normal ownership and lock policy. */
135
+ export async function salvageHistoryTail(params) {
136
+ const { subject, settings, options, author } = params;
137
+ if ([
138
+ options.normalizeProvenance,
139
+ options.auditOperation,
140
+ options.auditContext,
141
+ options.auditContextById,
142
+ options.forceAuditEntry,
143
+ options.mergeReceiptProof,
144
+ options.mergeReceiptProofById,
145
+ ].some(Boolean)) {
146
+ throw new PmCliError("Tail salvage cannot be combined with other repair modes.", EXIT_CODE.USAGE);
147
+ }
148
+ const raw = await readSalvageStream(subject.historyPath);
149
+ if (raw === null)
150
+ throw new PmCliError(`No history stream exists for ${subject.id}.`, EXIT_CODE.NOT_FOUND);
151
+ await runActiveOnReadHooks({ path: subject.historyPath, scope: "project" });
152
+ const selected = inspectHistoryTail(raw);
153
+ const replay = replayHistoryToTarget(selected.entries, selected.entries.length - 1);
154
+ const document = replayToItemDocument(replay);
155
+ if (typeof replay.metadata.id === "string" &&
156
+ replay.metadata.id !== subject.id) {
157
+ throw new PmCliError("History belongs to another item; salvage refused.", EXIT_CODE.CONFLICT, { code: "history_salvage_refused" });
158
+ }
159
+ const ownership = await readSalvageOwnership(subject, settings, document);
160
+ const warnings = checkHistoryRewriteOwnership({
161
+ itemDocument: ownership.document,
162
+ subjectId: subject.id,
163
+ author,
164
+ force: options.force,
165
+ settings,
166
+ });
167
+ const version = selected.itemHashVersion;
168
+ const dryRun = options.dryRun === true;
169
+ const changed = selected.receipt !== null;
170
+ if (changed && !dryRun) {
171
+ const release = await acquireLock(params.pmRoot, subject.id, settings.locks.ttl_seconds, author, Boolean(options.force), settings.governance.force_required_for_stale_lock, settings.locks.wait_ms);
172
+ try {
173
+ const currentRaw = await readSalvageStream(subject.historyPath);
174
+ const located = await locateItem(params.pmRoot, subject.id, settings.id_prefix, settings.item_format, params.typeRegistry.type_to_folder);
175
+ const currentItem = located
176
+ ? await readFileIfExists(located.itemPath)
177
+ : null;
178
+ if (currentRaw !== raw || currentItem !== ownership.itemRaw) {
179
+ throw new PmCliError("History or item changed while waiting for salvage lock; retry.", EXIT_CODE.CONFLICT);
180
+ }
181
+ const audit = sealHistoryRecord({
182
+ ...createHistoryEntry({
183
+ nowIso: nowIso(),
184
+ author,
185
+ op: "history_salvage",
186
+ before: document,
187
+ after: document,
188
+ message: options.message,
189
+ context: { history_salvage: selected.receipt },
190
+ }),
191
+ before_hash: selected.entries.at(-1).after_hash,
192
+ after_hash: selected.entries.at(-1).after_hash,
193
+ item_hash_version: version,
194
+ });
195
+ await writeHistoryRawWithRollback({
196
+ historyPath: subject.historyPath,
197
+ historyRawUnderLock: raw,
198
+ nextHistoryRaw: `${selected.prefix}${JSON.stringify(audit)}\n`,
199
+ });
200
+ warnings.push(...(await runActiveOnWriteHooks({
201
+ path: subject.historyPath,
202
+ scope: "project",
203
+ op: "history_salvage:history",
204
+ })));
205
+ }
206
+ finally {
207
+ await release();
208
+ }
209
+ }
210
+ return {
211
+ id: subject.id,
212
+ dry_run: dryRun,
213
+ changed,
214
+ history: {
215
+ path: subject.historyPath,
216
+ entries_scanned: selected.entries.length,
217
+ chain_drift_before: false,
218
+ entries_rehashed: 0,
219
+ entries_patch_repaired: 0,
220
+ converted_replace_to_add: 0,
221
+ skipped_ops: 0,
222
+ reconciled_with_item: false,
223
+ audit_entry_added: changed && !dryRun,
224
+ verify_ok: true,
225
+ verify_errors: [],
226
+ item_hash_version_before: version,
227
+ item_hash_version_after: version,
228
+ version_disposition: "preserved",
229
+ },
230
+ item: {
231
+ exists: subject.located !== null,
232
+ path: subject.located?.itemPath ?? null,
233
+ matched_chain_before: null,
234
+ },
235
+ provenance_normalization: {
236
+ requested: false,
237
+ changed: false,
238
+ events_changed: 0,
239
+ observations_removed: 0,
240
+ invalid_values: [],
241
+ },
242
+ ...(selected.receipt ? { salvage: selected.receipt } : {}),
243
+ warnings,
244
+ generated_at: nowIso(),
245
+ };
246
+ }
247
+ //# sourceMappingURL=salvage.js.map
248
+ //# debugId=49af8ec4-dd6b-56ef-8833-9c776fa35c85
@@ -4,16 +4,17 @@
4
4
  * Implements the pm history compact command surface and its agent-facing runtime behavior.
5
5
  */
6
6
 
7
- !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]="31152621-df59-54a5-a28d-63119e734e6e")}catch(e){}}();
7
+ !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]="26c39ecd-5d72-5cc9-b22b-6abc33fbf4ac")}catch(e){}}();
8
8
  import { assertInitializedTracker } from "./environment/tracker-preflight.js";
9
9
  import fs from "node:fs/promises";
10
10
  import path from "node:path";
11
- import { createHistoryEntry } from "../core/history/history.js";
11
+ import { createHistoryEntry, hashHistoryStream, sealHistoryRecord, SUPPORTED_HISTORY_ITEM_HASH_VERSIONS, } from "../core/history/history.js";
12
12
  import { selectHistoryCompactBulkTargets, } from "../core/history/history-compact-bulk.js";
13
13
  import { executeHistoryRewrite, writeHistoryRawWithRollback, } from "../core/history/history-rewrite.js";
14
- import { cloneEmptyReplayDocument, historyEntriesToRaw, replayHash, replayToItemDocument, toReplayDocument, verifyHistoryChain, } from "../core/history/replay.js";
14
+ import { cloneEmptyReplayDocument, historyEntriesToRaw, replayHashVerificationCandidates, replayToItemDocument, toReplayDocument, verifyHistoryChain, } from "../core/history/replay.js";
15
15
  import { applyHistoryPatch } from "../core/history/projection.js";
16
16
  import { readHistoryEntries } from "../core/history/read.js";
17
+ import { historyVersionOffset } from "../core/history/version-address.js";
17
18
  import { resolveItemTypeRegistry } from "../core/item/type-registry.js";
18
19
  import { lifecycleClassifierFromStatusRegistry } from "../core/governance/metadata-coverage.js";
19
20
  import { resolveRuntimeStatusRegistry } from "../core/schema/runtime-schema.js";
@@ -29,6 +30,7 @@ import { resolvePmRoot } from "../core/store/paths.js";
29
30
  import { readSettings } from "../core/store/settings.js";
30
31
  import { resolveAuthor } from "../core/shared/author.js";
31
32
  import { resolveHistorySubject } from "./history-redact.js";
33
+ /** Translate a durable version or timestamp boundary into physical compacted and retained counts. */
32
34
  function parseBeforeBoundary(before, entries) {
33
35
  if (before === undefined) {
34
36
  return {
@@ -44,12 +46,13 @@ function parseBeforeBoundary(before, entries) {
44
46
  }
45
47
  if (/^\d+$/.test(raw)) {
46
48
  const version = Number.parseInt(raw, 10);
49
+ const offset = historyVersionOffset(entries);
47
50
  if (!Number.isSafeInteger(version) ||
48
- version < 1 ||
49
- version > entries.length + 1) {
50
- throw new PmCliError(`history-compact --before version must be between 1 and ${entries.length + 1}.`, EXIT_CODE.USAGE);
51
+ version < offset + 1 ||
52
+ version > offset + entries.length + 1) {
53
+ throw new PmCliError(`history-compact --before version must be between ${offset + 1} and ${offset + entries.length + 1}.`, EXIT_CODE.USAGE);
51
54
  }
52
- const compactCount = Math.max(0, Math.min(entries.length, version - 1));
55
+ const compactCount = version - offset - 1;
53
56
  return {
54
57
  kind: "version",
55
58
  raw,
@@ -79,18 +82,13 @@ function parseBeforeBoundary(before, entries) {
79
82
  retainedCount: entries.length - compactCount,
80
83
  };
81
84
  }
85
+ /** Fold the already verified stream once, capturing its compaction checkpoint. */
82
86
  function replayHistoryAndResolveCheckpoint(entries, compactCount) {
83
87
  let replay = cloneEmptyReplayDocument();
84
88
  let checkpoint = cloneEmptyReplayDocument();
85
89
  for (let index = 0; index < entries.length; index += 1) {
86
90
  const entry = entries[index];
87
- if (replayHash(replay) !== entry.before_hash) {
88
- throw new PmCliError(`history-compact detected before-hash drift at entry ${index + 1}; run pm history-repair first.`, EXIT_CODE.CONFLICT);
89
- }
90
91
  replay = applyHistoryPatch(replay, entry.patch, index + 1, entry.op);
91
- if (replayHash(replay) !== entry.after_hash) {
92
- throw new PmCliError(`history-compact detected after-hash drift at entry ${index + 1}; run pm history-repair first.`, EXIT_CODE.CONFLICT);
93
- }
94
92
  if (index + 1 === compactCount) {
95
93
  checkpoint = structuredClone(replay);
96
94
  }
@@ -100,23 +98,7 @@ function replayHistoryAndResolveCheckpoint(entries, compactCount) {
100
98
  finalReplay: replay,
101
99
  };
102
100
  }
103
- function reanchorRetainedEntries(retainedEntries, seed, retainedEntryOffset) {
104
- let replay = structuredClone(seed);
105
- const rewritten = [];
106
- for (const [index, entry] of retainedEntries.entries()) {
107
- const beforeHash = replayHash(replay);
108
- replay = applyHistoryPatch(replay, entry.patch, retainedEntryOffset + index + 1, entry.op);
109
- rewritten.push({
110
- ...entry,
111
- before_hash: beforeHash,
112
- after_hash: replayHash(replay),
113
- });
114
- }
115
- return {
116
- entries: rewritten,
117
- finalReplay: replay,
118
- };
119
- }
101
+ /** Snapshot the live item and compare it with the stream's supported historical hash surfaces. */
120
102
  async function loadHistoryCompactCurrentItem(subject, settings, historyEntries) {
121
103
  const loadedItem = subject.located
122
104
  ? await readLocatedItem(subject.located, { schema: settings.schema })
@@ -128,11 +110,12 @@ async function loadHistoryCompactCurrentItem(subject, settings, historyEntries)
128
110
  matchedChainBefore: null,
129
111
  };
130
112
  }
113
+ const lastEntry = historyEntries[historyEntries.length - 1];
131
114
  return {
132
115
  loadedItem,
133
116
  currentItemRawBeforeLock: loadedItem.raw,
134
- matchedChainBefore: replayHash(toReplayDocument(loadedItem.document)) ===
135
- historyEntries[historyEntries.length - 1]?.after_hash,
117
+ matchedChainBefore: SUPPORTED_HISTORY_ITEM_HASH_VERSIONS.some((version) => (lastEntry.item_hash_version === undefined || lastEntry.item_hash_version === version) &&
118
+ replayHashVerificationCandidates(toReplayDocument(loadedItem.document), version).includes(lastEntry.after_hash)),
136
119
  };
137
120
  }
138
121
  function buildHistoryCompactMessage(options, boundary) {
@@ -151,6 +134,7 @@ function buildHistoryCompactBaselineMessage(boundary) {
151
134
  }
152
135
  return `history-compact baseline snapshot before ${boundary.raw}.`;
153
136
  }
137
+ /** Replace the pruned prefix with a durable-addressed checkpoint while retaining original records and hash semantics. */
154
138
  function buildHistoryCompactEntries(params) {
155
139
  if (!params.changed) {
156
140
  return {
@@ -161,16 +145,35 @@ function buildHistoryCompactEntries(params) {
161
145
  }
162
146
  const { checkpoint, finalReplay } = replayHistoryAndResolveCheckpoint(params.historyEntries, params.boundary.compactCount);
163
147
  const retained = params.historyEntries.slice(params.boundary.compactCount);
164
- const reanchored = reanchorRetainedEntries(retained, checkpoint, params.boundary.compactCount);
148
+ const offset = historyVersionOffset(params.historyEntries, true);
165
149
  const baselineEntry = createHistoryEntry({
166
- nowIso: nowIso(),
150
+ nowIso: params.historyEntries[params.boundary.compactCount - 1].ts,
167
151
  author: params.author,
168
152
  op: "history_compact_baseline",
169
153
  before: replayToItemDocument(cloneEmptyReplayDocument()),
170
154
  after: replayToItemDocument(checkpoint),
171
155
  message: buildHistoryCompactBaselineMessage(params.boundary),
156
+ context: {
157
+ history_compaction: {
158
+ contract_version: 1,
159
+ version_offset: offset === null ? null : offset + params.boundary.compactCount - 1,
160
+ checkpoint_timestamp_semantics: "last_compacted_entry",
161
+ pruned_entry_count: params.boundary.compactCount,
162
+ pruned_stream_digest: hashHistoryStream(params.historyEntries.slice(0, params.boundary.compactCount)),
163
+ retained_proof: "stream_digest_and_checkpoint",
164
+ limitation: "individual_pruned_entries_require_pre_compaction_stream",
165
+ },
166
+ },
172
167
  });
173
- const rewrittenEntries = [baselineEntry, ...reanchored.entries];
168
+ const checkpointEntry = params.historyEntries[params.boundary.compactCount - 1];
169
+ // Preserve the checkpoint's epoch and semantic hash variant. Do not introduce
170
+ // an explicit epoch into an unversioned stream: it would pin later legacy reads.
171
+ const rewrittenEntries = [sealHistoryRecord({
172
+ ...baselineEntry,
173
+ before_hash: params.historyEntries[0].before_hash,
174
+ after_hash: checkpointEntry.after_hash,
175
+ item_hash_version: checkpointEntry.item_hash_version,
176
+ }), ...retained];
174
177
  if (!params.dryRun) {
175
178
  rewrittenEntries.push(createHistoryEntry({
176
179
  nowIso: nowIso(),
@@ -270,7 +273,8 @@ export async function runHistoryCompact(id, options, global) {
270
273
  rewrittenEntries,
271
274
  })));
272
275
  }
273
- const firstRetainedEntry = boundary.retainedCount > 0 ? boundary.compactCount + 1 : null;
276
+ const offset = historyVersionOffset(historyEntries, true);
277
+ const firstRetainedEntry = boundary.retainedCount > 0 && offset !== null ? offset + boundary.compactCount + 1 : null;
274
278
  return {
275
279
  id: subject.id,
276
280
  dry_run: dryRun,
@@ -521,4 +525,4 @@ export async function runHistoryCompactBulk(options, global) {
521
525
  };
522
526
  }
523
527
  //# sourceMappingURL=history-compact.js.map
524
- //# debugId=31152621-df59-54a5-a28d-63119e734e6e
528
+ //# debugId=26c39ecd-5d72-5cc9-b22b-6abc33fbf4ac
@@ -1,6 +1,7 @@
1
1
  import { applyHistoryPatch, ensureMaterializedHistoryTarget, extractPatchFailureContext, replayHistoryToTarget, resolveHistoryTarget, type ResolvedHistoryTarget } from "../core/history/projection.js";
2
2
  import { readHistoryEntries } from "../core/history/read.js";
3
3
  import type { HistoryEntry, ItemDocument } from "../types/index.js";
4
+ export { findHistoryIdentityDiscontinuities, type HistoryIdentityDiscontinuity, } from "../core/history/identity.js";
4
5
  /** Workspace resolution controls accepted by {@link getItemAt}. */
5
6
  export interface GetItemAtOptions {
6
7
  /** Explicit tracker root, equivalent to the CLI global `--pm-path`. */
@@ -14,8 +15,8 @@ export interface GetItemAtResult {
14
15
  document: ItemDocument;
15
16
  /** Always true, preventing consumers from confusing this with current state. */
16
17
  reconstructed: true;
17
- /** One-based version included in the projection. */
18
- as_of_version: number;
18
+ /** Durable one-based version; null when a legacy checkpoint lost its mapping. */
19
+ as_of_version: number | null;
19
20
  /** Timestamp of the final included history entry. */
20
21
  as_of_timestamp: string;
21
22
  /** Normalized target resolution metadata shared with restore. */
@@ -7,11 +7,12 @@
7
7
  * systems without acquiring locks or appending history.
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]="f1ea4209-59d4-53f0-a8ea-2e060e385d14")}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]="6fe766e2-93e8-5653-beb5-16bd92efae51")}catch(e){}}();
11
11
  import { assertInitializedTracker } from "./environment/tracker-preflight.js";
12
12
  import { getActiveExtensionRegistrations } from "../core/extensions/index.js";
13
13
  import { applyHistoryPatch, ensureMaterializedHistoryTarget, extractPatchFailureContext, replayHistoryToTarget, resolveHistoryTarget, } from "../core/history/projection.js";
14
14
  import { readHistoryEntries } from "../core/history/read.js";
15
+ import { historyVersionOffset } from "../core/history/version-address.js";
15
16
  import { hashDocument, hashDocumentVerificationCandidates, hashEmptyDocument, } from "../core/history/history.js";
16
17
  import { replayToCanonicalItemDocument, verifyHistoryChainWithVersion, } from "../core/history/replay.js";
17
18
  import { resolveItemTypeRegistry } from "../core/item/type-registry.js";
@@ -20,6 +21,7 @@ import { PmCliError } from "../core/shared/errors.js";
20
21
  import { resolvePmRoot } from "../core/store/paths.js";
21
22
  import { readSettings } from "../core/store/settings.js";
22
23
  import { resolveHistorySubject } from "./history-redact.js";
24
+ export { findHistoryIdentityDiscontinuities, } from "../core/history/identity.js";
23
25
  /**
24
26
  * Verify a history chain and optionally compare it with a current document.
25
27
  *
@@ -80,10 +82,11 @@ export async function getItemAt(id, target, options = {}) {
80
82
  throw new PmCliError(`History target resolved to item ${document.metadata.id}, expected ${subject.id}.`, EXIT_CODE.GENERIC_FAILURE);
81
83
  }
82
84
  const targetEntry = history[resolvedTarget.historyIndex];
85
+ const offset = historyVersionOffset(history, true);
83
86
  return {
84
87
  document,
85
88
  reconstructed: true,
86
- as_of_version: resolvedTarget.historyIndex + 1,
89
+ as_of_version: offset === null ? null : offset + resolvedTarget.historyIndex + 1,
87
90
  as_of_timestamp: targetEntry.ts,
88
91
  target: resolvedTarget,
89
92
  history_length: history.length,
@@ -91,4 +94,4 @@ export async function getItemAt(id, target, options = {}) {
91
94
  }
92
95
  export { applyHistoryPatch, ensureMaterializedHistoryTarget, extractPatchFailureContext, readHistoryEntries, replayHistoryToTarget, resolveHistoryTarget, };
93
96
  //# sourceMappingURL=history-read.js.map
94
- //# debugId=f1ea4209-59d4-53f0-a8ea-2e060e385d14
97
+ //# debugId=6fe766e2-93e8-5653-beb5-16bd92efae51
@@ -4,11 +4,11 @@
4
4
  * Implements the pm history redact command surface and its agent-facing runtime behavior.
5
5
  */
6
6
 
7
- !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]="05b945f2-4f96-5f88-98ff-84cc856d0d57")}catch(e){}}();
7
+ !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]="bd1e86b0-6696-5fba-8dd4-b9636bf3ee60")}catch(e){}}();
8
8
  import { assertInitializedTracker } from "./environment/tracker-preflight.js";
9
9
  import fs from "node:fs/promises";
10
10
  import { pathExists, readFileIfExists, writeFileAtomic, } from "../core/fs/fs-utils.js";
11
- import { createHistoryEntry } from "../core/history/history.js";
11
+ import { createHistoryEntry, resealHistoryRewrite, verifyHistoryRecordHash, } from "../core/history/history.js";
12
12
  import { invalidateHistoryDriftCacheForPath } from "../core/history/drift-cache.js";
13
13
  import { executeHistoryRewrite } from "../core/history/history-rewrite.js";
14
14
  import { EMPTY_REPLAY_DOCUMENT, historyEntriesToRaw, replayHash, replayToItemDocument, verifyHistoryChain, } from "../core/history/replay.js";
@@ -189,6 +189,22 @@ function redactUnknownValue(value, rules, replacement) {
189
189
  replacements: 0,
190
190
  };
191
191
  }
192
+ function redactHistoryPatch(patch, rules, replacement) {
193
+ let replacements = 0;
194
+ const redactedPatch = patch.map((operation) => {
195
+ /* c8 ignore start -- patch operations without `value` are covered in lower-level patch adapters. */
196
+ if (!Object.prototype.hasOwnProperty.call(operation, "value")) {
197
+ return operation;
198
+ }
199
+ /* c8 ignore stop */
200
+ const redactedValue = redactUnknownValue(operation.value, rules, replacement);
201
+ replacements += redactedValue.replacements;
202
+ return redactedValue.replacements > 0
203
+ ? { ...operation, value: redactedValue.value }
204
+ : operation;
205
+ });
206
+ return { patch: redactedPatch, replacements };
207
+ }
192
208
  function inspectHistoryIntegrity(entries) {
193
209
  let replay = structuredClone(EMPTY_REPLAY_DOCUMENT);
194
210
  let hashMismatchesBefore = 0;
@@ -214,6 +230,7 @@ function inspectHistoryIntegrity(entries) {
214
230
  function redactHistoryEntry(entry, rules, replacement) {
215
231
  let replacements = 0;
216
232
  let changed = false;
233
+ let reanchorEvidenceRedacted = false;
217
234
  let nextMessage = entry.message;
218
235
  if (typeof entry.message === "string") {
219
236
  const redactedMessage = redactStringValue(entry.message, rules, replacement);
@@ -223,31 +240,41 @@ function redactHistoryEntry(entry, rules, replacement) {
223
240
  changed = true;
224
241
  }
225
242
  }
226
- const nextPatch = entry.patch.map((operation) => {
227
- /* c8 ignore start -- patch operations without `value` are covered in lower-level patch adapters. */
228
- if (!Object.prototype.hasOwnProperty.call(operation, "value")) {
229
- return operation;
230
- }
231
- /* c8 ignore stop */
232
- const redactedValue = redactUnknownValue(operation.value, rules, replacement);
233
- replacements += redactedValue.replacements;
234
- if (redactedValue.replacements > 0) {
235
- changed = true;
236
- return {
237
- ...operation,
238
- value: redactedValue.value,
239
- };
240
- }
241
- return operation;
243
+ const redactedPatch = redactHistoryPatch(entry.patch, rules, replacement);
244
+ replacements += redactedPatch.replacements;
245
+ changed ||= redactedPatch.replacements > 0;
246
+ const nextReanchorEvidence = entry.reanchor_evidence?.map((evidence) => {
247
+ const redactedEvidencePatch = evidence.patch
248
+ ? redactHistoryPatch(evidence.patch, rules, replacement)
249
+ : { patch: undefined, replacements: 0 };
250
+ const redactedEvidenceRecord = evidence.record
251
+ ? redactUnknownValue(evidence.record, rules, replacement)
252
+ : { value: undefined, replacements: 0 };
253
+ replacements += redactedEvidencePatch.replacements;
254
+ replacements += redactedEvidenceRecord.replacements;
255
+ const evidenceChanged = redactedEvidencePatch.replacements > 0 ||
256
+ redactedEvidenceRecord.replacements > 0;
257
+ changed ||= evidenceChanged;
258
+ if (!evidenceChanged)
259
+ return evidence;
260
+ reanchorEvidenceRedacted = true;
261
+ const { patch: _sensitivePatch, record: _sensitiveRecord, record_hash: _priorRecordHash, record_hash_version: _priorRecordHashVersion, ...digestOnlyEvidence } = evidence;
262
+ return redactedEvidencePatch.replacements > 0
263
+ ? digestOnlyEvidence
264
+ : { ...digestOnlyEvidence, patch: evidence.patch };
242
265
  });
243
266
  return {
244
267
  entry: {
245
268
  ...entry,
246
269
  message: nextMessage,
247
- patch: nextPatch,
270
+ patch: redactedPatch.patch,
271
+ ...(nextReanchorEvidence
272
+ ? { reanchor_evidence: nextReanchorEvidence }
273
+ : {}),
248
274
  },
249
275
  replacements,
250
276
  changed,
277
+ reanchorEvidenceRedacted,
251
278
  };
252
279
  }
253
280
  function rewriteHistoryEntries(entries, rules, replacement) {
@@ -256,7 +283,12 @@ function rewriteHistoryEntries(entries, rules, replacement) {
256
283
  let replacements = 0;
257
284
  const rewrittenEntries = [];
258
285
  for (let index = 0; index < entries.length; index += 1) {
259
- const redacted = redactHistoryEntry(entries[index], rules, replacement);
286
+ const original = entries[index];
287
+ const recordVerification = verifyHistoryRecordHash(original);
288
+ if (!recordVerification.ok) {
289
+ throw new PmCliError(`history-redact refuses invalid immutable record ${index + 1} (${recordVerification.error}).`, EXIT_CODE.GENERIC_FAILURE);
290
+ }
291
+ const redacted = redactHistoryEntry(original, rules, replacement);
260
292
  replacements += redacted.replacements;
261
293
  if (redacted.changed) {
262
294
  entriesChanged += 1;
@@ -264,11 +296,14 @@ function rewriteHistoryEntries(entries, rules, replacement) {
264
296
  const beforeHash = replayHash(replay);
265
297
  replay = applyHistoryPatch(replay, redacted.entry.patch, index + 1, redacted.entry.op);
266
298
  const afterHash = replayHash(replay);
267
- rewrittenEntries.push({
299
+ rewrittenEntries.push(resealHistoryRewrite(original, {
268
300
  ...redacted.entry,
269
301
  before_hash: beforeHash,
270
302
  after_hash: afterHash,
271
- });
303
+ }, {
304
+ retainPriorRecord: redacted.changed,
305
+ retainPriorRecordHash: !redacted.changed,
306
+ }));
272
307
  }
273
308
  return {
274
309
  entries: rewrittenEntries,
@@ -606,4 +641,4 @@ export const _testOnly = {
606
641
  redactUnknownValue,
607
642
  };
608
643
  //# sourceMappingURL=history-redact.js.map
609
- //# debugId=05b945f2-4f96-5f88-98ff-84cc856d0d57
644
+ //# debugId=bd1e86b0-6696-5fba-8dd4-b9636bf3ee60
@@ -1,3 +1,4 @@
1
+ import { type HistorySalvageReceipt } from "./history/salvage.js";
1
2
  import { type ReplayDocument } from "../core/history/replay.js";
2
3
  import type { GlobalOptions } from "../core/shared/command-types.js";
3
4
  import type { HistoryEntry } from "../types/index.js";
@@ -5,6 +6,8 @@ import { type MergeDecisionReceipt } from "./merge/receipts.js";
5
6
  import { type ProvenanceNormalizationReceipt } from "./governance/provenance-health.js";
6
7
  /** Documents the history repair command options payload exchanged by command, SDK, and package integrations. */
7
8
  export interface HistoryRepairCommandOptions {
9
+ /** Remove only an invalid trailing suffix after a verified prefix; never re-anchor or reconcile. */
10
+ salvageTail?: boolean;
8
11
  /** Value that configures or reports dry run for this contract. */
9
12
  dryRun?: boolean;
10
13
  /** Value that configures or reports author for this contract. */
@@ -36,6 +39,8 @@ export interface HistoryRepairCommandOptions {
36
39
  }
37
40
  /** Documents the history repair result payload exchanged by command, SDK, and package integrations. */
38
41
  export interface HistoryRepairResult {
42
+ /** Present when invalid trailing bytes were selected for explicit salvage. */
43
+ salvage?: HistorySalvageReceipt;
39
44
  /** Stable identifier used to reference this record across commands and storage. */
40
45
  id: string;
41
46
  /** Value that configures or reports dry run for this contract. */