@unbrained/pm-cli 2026.9.3 → 2026.9.4

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 (120) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +27 -0
  3. package/dist/cli-bundle/bundle-manifest.json +146 -146
  4. package/dist/cli-bundle/chunks/{chunk-E5RBBCXO.js → chunk-6XSKPNCF.js} +75 -75
  5. package/dist/cli-bundle/chunks/chunk-A24WAVE6.js +8 -0
  6. package/dist/cli-bundle/chunks/{chunk-DKGP3QLE.js → chunk-HI6BHPCU.js} +2 -2
  7. package/dist/cli-bundle/chunks/{chunk-Y4PJ6HPK.js → chunk-I2AS367G.js} +2 -2
  8. package/dist/cli-bundle/chunks/{chunk-IEHHE6GA.js → chunk-OGBVU67G.js} +2 -2
  9. package/dist/cli-bundle/chunks/{chunk-EDPIS7EB.js → chunk-RI4XNOYS.js} +2 -2
  10. package/dist/cli-bundle/chunks/chunk-TXW6VL33.js +38 -0
  11. package/dist/cli-bundle/chunks/{chunk-LENC656S.js → chunk-V72UV7QS.js} +76 -79
  12. package/dist/cli-bundle/chunks/chunk-VGLHECAP.js +9 -0
  13. package/dist/cli-bundle/chunks/{chunk-34HASX3S.js → chunk-X6CMGV6L.js} +2 -2
  14. package/dist/cli-bundle/chunks/{chunk-UPPM2FOZ.js → chunk-YW4QK2G7.js} +2 -2
  15. package/dist/cli-bundle/chunks/{register-list-query-W2ZTOMKP.js → register-list-query-Z5YYSJJG.js} +2 -2
  16. package/dist/cli-bundle/chunks/{register-mutation-RVND5NCG.js → register-mutation-ELSP6I4I.js} +2 -2
  17. package/dist/cli-bundle/chunks/{register-operations-TTROIVC4.js → register-operations-OH7R3ZT4.js} +2 -2
  18. package/dist/cli-bundle/chunks/{register-setup-D3ZFHY3L.js → register-setup-E2AE72QG.js} +2 -2
  19. package/dist/cli-bundle/focused-chunks/{chunk-Q45NHPF2.js → chunk-25UJBH4M.js} +2 -2
  20. package/dist/cli-bundle/focused-chunks/{chunk-4HJFY4MB.js → chunk-3646BZ4W.js} +2 -2
  21. package/dist/cli-bundle/focused-chunks/{chunk-TXE4TSHM.js → chunk-4YRAVI43.js} +2 -2
  22. package/dist/cli-bundle/focused-chunks/{chunk-M5M6SVHS.js → chunk-53IHPICX.js} +2 -2
  23. package/dist/cli-bundle/focused-chunks/chunk-5P4HVOWG.js +176 -0
  24. package/dist/cli-bundle/focused-chunks/chunk-636VVQ7F.js +54 -0
  25. package/dist/cli-bundle/focused-chunks/{chunk-D7KXFBXV.js → chunk-6PWPQKDN.js} +2 -2
  26. package/dist/cli-bundle/focused-chunks/{chunk-S27XMBIN.js → chunk-7N2TV7WZ.js} +2 -2
  27. package/dist/cli-bundle/focused-chunks/{chunk-5RFXUPN5.js → chunk-AJR3OP2E.js} +2 -2
  28. package/dist/cli-bundle/focused-chunks/{chunk-KZ2NB3ET.js → chunk-B3A6MJYE.js} +2 -2
  29. package/dist/cli-bundle/focused-chunks/{chunk-BWKHEIVF.js → chunk-BFDDJ2T2.js} +2 -2
  30. package/dist/cli-bundle/focused-chunks/{chunk-4WR46RMG.js → chunk-BHUIRZWQ.js} +8 -8
  31. package/dist/cli-bundle/focused-chunks/{chunk-4RRHC3S4.js → chunk-JADHHPY6.js} +2 -2
  32. package/dist/cli-bundle/focused-chunks/{chunk-GT7MXDLW.js → chunk-MHKUIPJW.js} +2 -2
  33. package/dist/cli-bundle/focused-chunks/chunk-NBMJD6MK.js +2 -0
  34. package/dist/cli-bundle/focused-chunks/chunk-NSKBLHVI.js +20 -0
  35. package/dist/cli-bundle/focused-chunks/{chunk-OL5EH6I3.js → chunk-P2GOAOQN.js} +2 -2
  36. package/dist/cli-bundle/focused-chunks/{chunk-UCLO3XDU.js → chunk-YARWJM2B.js} +2 -2
  37. package/dist/cli-bundle/main.js +2 -2
  38. package/dist/cli-bundle/sdk-authoring.js +1 -1
  39. package/dist/cli-bundle/sdk-contracts.js +1 -1
  40. package/dist/cli-bundle/sdk-core.js +28 -31
  41. package/dist/cli-bundle/sdk-governance.js +1 -1
  42. package/dist/cli-bundle/sdk-graph.js +1 -1
  43. package/dist/cli-bundle/sdk-merge.js +42 -37
  44. package/dist/cli-bundle/sdk-query.js +1 -1
  45. package/dist/cli-bundle/sdk-runtime.js +1 -1
  46. package/dist/cli-bundle/sdk-testing.js +1 -1
  47. package/dist/cli-bundle/sdk.js +10 -10
  48. package/dist/core/diagnostics/remediation.js +4 -4
  49. package/dist/core/history/history.d.ts +37 -1
  50. package/dist/core/history/history.js +278 -58
  51. package/dist/core/history/replay.js +68 -34
  52. package/dist/core/lock/lock.d.ts +10 -0
  53. package/dist/core/lock/lock.js +42 -3
  54. package/dist/core/shared/author.d.ts +3 -1
  55. package/dist/core/shared/author.js +111 -23
  56. package/dist/sdk/environment/agent-environment.d.ts +296 -0
  57. package/dist/sdk/environment/agent-environment.js +467 -0
  58. package/dist/sdk/environment/project-runtime-compatibility.d.ts +19 -1
  59. package/dist/sdk/environment/project-runtime-compatibility.js +118 -21
  60. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +14 -2
  61. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +14 -14
  62. package/dist/sdk/governance/health.js +13 -44
  63. package/dist/sdk/governance/merge-receipt-history.d.ts +62 -0
  64. package/dist/sdk/governance/merge-receipt-history.js +207 -0
  65. package/dist/sdk/governance/provenance-health.js +9 -3
  66. package/dist/sdk/history-compact.js +14 -5
  67. package/dist/sdk/history-redact.js +58 -23
  68. package/dist/sdk/history-repair.js +16 -6
  69. package/dist/sdk/index.d.ts +2 -0
  70. package/dist/sdk/index.js +4 -2
  71. package/dist/sdk/merge/driver.d.ts +3 -1
  72. package/dist/sdk/merge/driver.js +9 -9
  73. package/dist/sdk/merge/index.d.ts +1 -1
  74. package/dist/sdk/merge/index.js +3 -3
  75. package/dist/sdk/merge/receipts.d.ts +17 -6
  76. package/dist/sdk/merge/receipts.js +246 -75
  77. package/dist/sdk/merge/reconcile.d.ts +9 -7
  78. package/dist/sdk/merge/reconcile.js +175 -12
  79. package/dist/sdk/merge/three-way.d.ts +22 -2
  80. package/dist/sdk/merge/three-way.js +92 -25
  81. package/dist/sdk/runtime-primitives.d.ts +2 -2
  82. package/dist/sdk/runtime-primitives.js +4 -4
  83. package/dist/types.d.ts +25 -0
  84. package/dist/types.js +2 -2
  85. package/docs/AGENT_ENVIRONMENTS.md +47 -0
  86. package/docs/AGENT_PROVENANCE_ADR.md +17 -2
  87. package/docs/CONTEXT_INTEGRITY_CONTRACTS.md +38 -1
  88. package/docs/MERGE_SAFETY.md +119 -23
  89. package/docs/README.md +1 -0
  90. package/docs/SDK_CONTEXT_EVIDENCE_CONTRACTS.md +24 -2
  91. package/docs/SDK_RUNTIME_BOUNDARIES.md +17 -1
  92. package/docs/examples/sdk-agent-environment/README.md +14 -0
  93. package/docs/examples/sdk-agent-environment/package.json +8 -0
  94. package/docs/examples/sdk-agent-environment/run-agent-environment.mjs +154 -0
  95. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +4 -3
  96. package/marketplace.json +2 -2
  97. package/package.json +2 -2
  98. package/packages/pm-beads/package.json +1 -1
  99. package/packages/pm-calendar/package.json +1 -1
  100. package/packages/pm-command-kit/package.json +1 -1
  101. package/packages/pm-digital-twin/package.json +1 -1
  102. package/packages/pm-governance-audit/package.json +1 -1
  103. package/packages/pm-guide-shell/package.json +1 -1
  104. package/packages/pm-kanban/package.json +1 -1
  105. package/packages/pm-lifecycle-hooks/package.json +1 -1
  106. package/packages/pm-linked-test-adapters/package.json +1 -1
  107. package/packages/pm-search-advanced/package.json +1 -1
  108. package/packages/pm-templates/package.json +1 -1
  109. package/packages/pm-todos/package.json +1 -1
  110. package/packages/pm-vcs/package.json +1 -1
  111. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  112. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  113. package/sdk/public-surface.json +534 -24
  114. package/dist/cli-bundle/chunks/chunk-AY324UJ5.js +0 -9
  115. package/dist/cli-bundle/chunks/chunk-L7B7343B.js +0 -8
  116. package/dist/cli-bundle/chunks/chunk-LISESA7W.js +0 -35
  117. package/dist/cli-bundle/focused-chunks/chunk-3ZBI35HU.js +0 -176
  118. package/dist/cli-bundle/focused-chunks/chunk-GHCZ3YFR.js +0 -20
  119. package/dist/cli-bundle/focused-chunks/chunk-KBLZRU62.js +0 -2
  120. package/dist/cli-bundle/focused-chunks/chunk-QKHCSUGJ.js +0 -51
@@ -19,7 +19,7 @@
19
19
  */
20
20
  /** Registry of every non-extension `pm health` / `pm validate` warning code and its remediation. Extension findings keep their richer, contextual `details.triage.remediation` produced by the extension health triage and are intentionally excluded here (see pm-0hnu: "all non-extension checks"). */
21
21
 
22
- !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]="24a69725-363a-59e1-96c6-1e1ca079a311")}catch(e){}}();
22
+ !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]="8e57e03e-3c60-5c1b-b455-9318df8027f6")}catch(e){}}();
23
23
  export const REMEDIATION_REGISTRY = Object.freeze([
24
24
  // --- pm health: directories ---
25
25
  {
@@ -205,8 +205,8 @@ export const REMEDIATION_REGISTRY = Object.freeze([
205
205
  },
206
206
  {
207
207
  code: "merge_receipt_history_reference_missing",
208
- command: "pm health --check-only --full --json",
209
- summary: "One or more append-only history coordinates reference receipt evidence that is no longer present; inspect the bounded item, line, and privacy-safe receipt locator, then recover the named evidence from an authoritative clone or backup without rewriting history.",
208
+ command: "pm merge reconcile --dry-run",
209
+ summary: "Review the bounded coordinates and recover available evidence first; for exact pre-durable clone-local-only references, rerun with --force so pm appends an audited disposition and proves the health finding is cleared without rewriting history.",
210
210
  },
211
211
  {
212
212
  code: "validate_merge_decisions_unreviewed",
@@ -504,4 +504,4 @@ export function buildRemediationCommands(warnings) {
504
504
  return Object.values(buildRemediationMap(warnings));
505
505
  }
506
506
  //# sourceMappingURL=remediation.js.map
507
- //# debugId=24a69725-363a-59e1-96c6-1e1ca079a311
507
+ //# debugId=8e57e03e-3c60-5c1b-b455-9318df8027f6
@@ -1,11 +1,47 @@
1
1
  import { type AuthorSource } from "../shared/author.js";
2
- import type { HistoryEntry, ItemDocument } from "../../types/index.js";
2
+ import type { HistoryEntry, HistoryPatchOp, ItemDocument } from "../../types/index.js";
3
3
  /** Hash epoch for the current canonical item surface written into immutable history. */
4
4
  export declare const CURRENT_HISTORY_ITEM_HASH_VERSION: 3;
5
5
  /** Hash epochs understood by this runtime. */
6
6
  export declare const SUPPORTED_HISTORY_ITEM_HASH_VERSIONS: readonly [1, 2, 3];
7
+ /** Record-integrity epoch written beside new immutable history events. */
8
+ export declare const CURRENT_HISTORY_RECORD_HASH_VERSION: 1;
9
+ /** Record-integrity epochs understood by this runtime. */
10
+ export declare const SUPPORTED_HISTORY_RECORD_HASH_VERSIONS: readonly [1];
7
11
  /** Restricts item hash versions accepted by history replay. */
8
12
  export type HistoryItemHashVersion = (typeof SUPPORTED_HISTORY_ITEM_HASH_VERSIONS)[number];
13
+ /** Restricts immutable-record hash versions accepted by history replay. */
14
+ export type HistoryRecordHashVersion = (typeof SUPPORTED_HISTORY_RECORD_HASH_VERSIONS)[number];
15
+ /** Canonicalize and hash every immutable record field except the hash value itself. */
16
+ export declare function hashHistoryRecord(entry: Omit<HistoryEntry, "record_hash">): string;
17
+ /** Hash one patch representation without retaining its potentially sensitive values. */
18
+ export declare function hashHistoryPatch(patch: readonly HistoryPatchOp[]): string;
19
+ /** Hash an ordered history prefix for compact checkpoint provenance. */
20
+ export declare function hashHistoryStream(entries: readonly HistoryEntry[]): string;
21
+ /** Attach the current immutable-record integrity envelope to one history event. */
22
+ export declare function sealHistoryRecord(entry: HistoryEntry): HistoryEntry;
23
+ /** Verify record-level attribution coverage while accepting explicit legacy item-state-only entries. */
24
+ export declare function verifyHistoryRecordHash(entry: HistoryEntry): {
25
+ ok: true;
26
+ coverage: "record_and_item_state" | "item_state_only";
27
+ } | {
28
+ ok: false;
29
+ error: "incomplete_record_hash_envelope" | "record_hash_mismatch" | "unsupported_record_hash_version";
30
+ };
31
+ /** Reseal a maintenance rewrite and retain the anchors and patch digest it replaced. */
32
+ export declare function resealHistoryRewrite(original: HistoryEntry, rewritten: HistoryEntry, options?: Readonly<{
33
+ retainOriginalPatch?: boolean;
34
+ retainPriorRecord?: boolean;
35
+ retainPriorRecordHash?: boolean;
36
+ }>): HistoryEntry;
37
+ /** Verify that retained rewrite evidence reconstructs every available prior record envelope. */
38
+ export declare function verifyHistoryRewriteEvidence(entry: HistoryEntry): {
39
+ ok: true;
40
+ coverage: "complete" | "digest_only" | "legacy_anchor_only" | "none";
41
+ } | {
42
+ ok: false;
43
+ error: "rewrite_evidence_invalid" | "rewrite_evidence_patch_hash_mismatch" | "rewrite_evidence_record_hash_mismatch";
44
+ };
9
45
  declare function decodeJsonPointer(path: string): string[];
10
46
  declare function isDefinedPointerPath(document: unknown, path: string): boolean;
11
47
  /** Implements hash document for the public runtime surface of this module. */
@@ -4,7 +4,7 @@
4
4
  * Implements append-only history and replay behavior for History.
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]="1f78448d-a042-588a-84ce-369bd9ce8726")}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]="bf6a9831-134c-5dc2-a3e2-74dca00cf22c")}catch(e){}}();
8
8
  import jsonPatch from "fast-json-patch";
9
9
  import { EXIT_CODE, ITEM_METADATA_KEY_ORDER } from "../shared/constants.js";
10
10
  import { runActiveServiceOverride } from "../extensions/index.js";
@@ -26,6 +26,242 @@ const EMPTY_LEGACY_HASH_DOCUMENT = {
26
26
  export const CURRENT_HISTORY_ITEM_HASH_VERSION = 3;
27
27
  /** Hash epochs understood by this runtime. */
28
28
  export const SUPPORTED_HISTORY_ITEM_HASH_VERSIONS = [1, 2, 3];
29
+ /** Record-integrity epoch written beside new immutable history events. */
30
+ export const CURRENT_HISTORY_RECORD_HASH_VERSION = 1;
31
+ /** Record-integrity epochs understood by this runtime. */
32
+ export const SUPPORTED_HISTORY_RECORD_HASH_VERSIONS = [1];
33
+ /** Canonicalize and hash every immutable record field except the hash value itself. */
34
+ export function hashHistoryRecord(entry) {
35
+ return sha256Hex(stableStringify(entry));
36
+ }
37
+ /** Hash one patch representation without retaining its potentially sensitive values. */
38
+ export function hashHistoryPatch(patch) {
39
+ return sha256Hex(stableStringify(patch));
40
+ }
41
+ /** Hash an ordered history prefix for compact checkpoint provenance. */
42
+ export function hashHistoryStream(entries) {
43
+ return sha256Hex(stableStringify(entries));
44
+ }
45
+ /** Attach the current immutable-record integrity envelope to one history event. */
46
+ export function sealHistoryRecord(entry) {
47
+ const sealed = {
48
+ ...entry,
49
+ record_hash_version: CURRENT_HISTORY_RECORD_HASH_VERSION,
50
+ };
51
+ const { record_hash: _recordHash, ...canonical } = sealed;
52
+ return { ...sealed, record_hash: hashHistoryRecord(canonical) };
53
+ }
54
+ /** Verify record-level attribution coverage while accepting explicit legacy item-state-only entries. */
55
+ export function verifyHistoryRecordHash(entry) {
56
+ if (entry.record_hash_version === undefined &&
57
+ entry.record_hash === undefined) {
58
+ return { ok: true, coverage: "item_state_only" };
59
+ }
60
+ if (entry.record_hash_version === undefined ||
61
+ entry.record_hash === undefined) {
62
+ return { ok: false, error: "incomplete_record_hash_envelope" };
63
+ }
64
+ if (!SUPPORTED_HISTORY_RECORD_HASH_VERSIONS.includes(entry.record_hash_version)) {
65
+ return { ok: false, error: "unsupported_record_hash_version" };
66
+ }
67
+ const { record_hash: _recordHash, ...canonical } = entry;
68
+ return hashHistoryRecord(canonical) === entry.record_hash
69
+ ? { ok: true, coverage: "record_and_item_state" }
70
+ : { ok: false, error: "record_hash_mismatch" };
71
+ }
72
+ /** Build the retained coordinate and optional exact-record rewrite evidence. */
73
+ function createHistoryRewriteEvidence(original, options) {
74
+ const evidence = {
75
+ before_hash: original.before_hash,
76
+ after_hash: original.after_hash,
77
+ patch_hash: hashHistoryPatch(original.patch),
78
+ };
79
+ if (original.item_hash_version !== undefined) {
80
+ evidence.item_hash_version = original.item_hash_version;
81
+ }
82
+ if (options.retainOriginalPatch) {
83
+ evidence.patch = structuredClone(original.patch);
84
+ }
85
+ if (options.retainPriorRecordHash !== false &&
86
+ original.record_hash_version !== undefined &&
87
+ original.record_hash !== undefined) {
88
+ evidence.record_hash_version = original.record_hash_version;
89
+ evidence.record_hash = original.record_hash;
90
+ evidence.record = structuredClone(original);
91
+ }
92
+ return evidence;
93
+ }
94
+ /** Reseal a maintenance rewrite and retain the anchors and patch digest it replaced. */
95
+ export function resealHistoryRewrite(original, rewritten, options = {}) {
96
+ const originalRecordVerification = verifyHistoryRecordHash(original);
97
+ if (!originalRecordVerification.ok) {
98
+ throw new TypeError(originalRecordVerification.error);
99
+ }
100
+ const originalPatchHash = hashHistoryPatch(original.patch);
101
+ const retainsRewriteEvidence = options.retainPriorRecord === true ||
102
+ original.before_hash !== rewritten.before_hash ||
103
+ original.after_hash !== rewritten.after_hash ||
104
+ original.item_hash_version !== rewritten.item_hash_version ||
105
+ originalPatchHash !== hashHistoryPatch(rewritten.patch);
106
+ const sealed = sealHistoryRecord({
107
+ ...rewritten,
108
+ ...(retainsRewriteEvidence
109
+ ? {
110
+ reanchor_evidence: [
111
+ ...(rewritten.reanchor_evidence ?? []),
112
+ createHistoryRewriteEvidence(original, options),
113
+ ],
114
+ }
115
+ : {}),
116
+ });
117
+ const evidenceVerification = verifyHistoryRewriteEvidence(sealed);
118
+ if (!evidenceVerification.ok) {
119
+ throw new TypeError(evidenceVerification.error);
120
+ }
121
+ return sealed;
122
+ }
123
+ /** Return whether runtime data is a dense list of structurally valid patch operations. */
124
+ function isHistoryPatch(value) {
125
+ if (!Array.isArray(value) || Object.keys(value).length !== value.length) {
126
+ return false;
127
+ }
128
+ return value.every((operation) => {
129
+ if (!isRecord(operation) || typeof operation.path !== "string") {
130
+ return false;
131
+ }
132
+ if (operation.op === "move" || operation.op === "copy") {
133
+ return typeof operation.from === "string";
134
+ }
135
+ if (["add", "replace", "test"].includes(operation.op)) {
136
+ return Object.hasOwn(operation, "value");
137
+ }
138
+ return operation.op === "remove";
139
+ });
140
+ }
141
+ /** Return whether a retained record has the minimum immutable event shape. */
142
+ function isRetainedHistoryRecordShape(value) {
143
+ return (isRecord(value) &&
144
+ typeof value.ts === "string" &&
145
+ typeof value.author === "string" &&
146
+ typeof value.op === "string" &&
147
+ typeof value.before_hash === "string" &&
148
+ typeof value.after_hash === "string" &&
149
+ isHistoryPatch(value.patch));
150
+ }
151
+ /** Return whether optional retained record-hash fields form a complete pair. */
152
+ function hasCompleteRetainedRecordHashEnvelope(evidence) {
153
+ const hasVersion = evidence.record_hash_version !== undefined;
154
+ const hasHash = evidence.record_hash !== undefined;
155
+ return hasVersion === hasHash;
156
+ }
157
+ /** Return whether runtime evidence has the bounded shape needed for verification. */
158
+ function isHistoryReanchorEvidence(evidence) {
159
+ if (!isRecord(evidence) ||
160
+ !hasCompleteRetainedRecordHashEnvelope(evidence) ||
161
+ typeof evidence.before_hash !== "string" ||
162
+ typeof evidence.after_hash !== "string" ||
163
+ typeof evidence.patch_hash !== "string" ||
164
+ !/^[a-f0-9]{64}$/u.test(evidence.patch_hash) ||
165
+ (evidence.patch !== undefined && !isHistoryPatch(evidence.patch)) ||
166
+ (evidence.record_hash_version !== undefined &&
167
+ typeof evidence.record_hash_version !== "number") ||
168
+ (evidence.record_hash !== undefined &&
169
+ (typeof evidence.record_hash !== "string" ||
170
+ !/^[a-f0-9]{64}$/u.test(evidence.record_hash)))) {
171
+ return false;
172
+ }
173
+ return (evidence.record === undefined ||
174
+ isRetainedHistoryRecordShape(evidence.record));
175
+ }
176
+ /** Return whether a retained exact record agrees with its summary coordinates. */
177
+ function matchesRetainedHistoryRecord(evidence) {
178
+ const record = evidence.record;
179
+ return (record !== undefined &&
180
+ record.before_hash === evidence.before_hash &&
181
+ record.after_hash === evidence.after_hash &&
182
+ record.item_hash_version === evidence.item_hash_version &&
183
+ record.record_hash_version === evidence.record_hash_version &&
184
+ record.record_hash === evidence.record_hash &&
185
+ hashHistoryPatch(record.patch) === evidence.patch_hash);
186
+ }
187
+ /** Reconstruct one prior record represented by retained rewrite evidence. */
188
+ function reconstructPriorHistoryRecord(current, evidence, priorEvidence) {
189
+ if (evidence.patch &&
190
+ hashHistoryPatch(evidence.patch) !== evidence.patch_hash) {
191
+ return { kind: "patch_hash_mismatch" };
192
+ }
193
+ if (evidence.record) {
194
+ if (!matchesRetainedHistoryRecord(evidence)) {
195
+ return { kind: "record_hash_mismatch" };
196
+ }
197
+ return { kind: "record", entry: evidence.record };
198
+ }
199
+ const priorPatch = evidence.patch ?? current.patch;
200
+ if (hashHistoryPatch(priorPatch) !== evidence.patch_hash) {
201
+ return { kind: "digest_only" };
202
+ }
203
+ const priorRecord = {
204
+ ...current,
205
+ before_hash: evidence.before_hash,
206
+ after_hash: evidence.after_hash,
207
+ patch: priorPatch,
208
+ ...(evidence.item_hash_version === undefined
209
+ ? {}
210
+ : { item_hash_version: evidence.item_hash_version }),
211
+ ...(evidence.record_hash_version === undefined
212
+ ? {}
213
+ : { record_hash_version: evidence.record_hash_version }),
214
+ ...(evidence.record_hash === undefined
215
+ ? {}
216
+ : { record_hash: evidence.record_hash }),
217
+ ...(priorEvidence.length === 0 ? {} : { reanchor_evidence: priorEvidence }),
218
+ };
219
+ if (priorEvidence.length === 0)
220
+ delete priorRecord.reanchor_evidence;
221
+ if (evidence.item_hash_version === undefined) {
222
+ delete priorRecord.item_hash_version;
223
+ }
224
+ return evidence.record_hash_version === undefined ||
225
+ evidence.record_hash === undefined
226
+ ? { kind: "legacy", entry: priorRecord }
227
+ : { kind: "record", entry: priorRecord };
228
+ }
229
+ /** Verify that retained rewrite evidence reconstructs every available prior record envelope. */
230
+ export function verifyHistoryRewriteEvidence(entry) {
231
+ const evidenceEntries = entry.reanchor_evidence;
232
+ if (evidenceEntries === undefined)
233
+ return { ok: true, coverage: "none" };
234
+ if (!Array.isArray(evidenceEntries)) {
235
+ return { ok: false, error: "rewrite_evidence_invalid" };
236
+ }
237
+ if (evidenceEntries.length === 0)
238
+ return { ok: true, coverage: "none" };
239
+ let reconstructed = entry;
240
+ for (let index = evidenceEntries.length - 1; index >= 0; index -= 1) {
241
+ const evidence = evidenceEntries[index];
242
+ if (!(index in evidenceEntries) || !isHistoryReanchorEvidence(evidence)) {
243
+ return { ok: false, error: "rewrite_evidence_invalid" };
244
+ }
245
+ const prior = reconstructPriorHistoryRecord(reconstructed, evidence, evidenceEntries.slice(0, index));
246
+ if (prior.kind === "patch_hash_mismatch") {
247
+ return { ok: false, error: "rewrite_evidence_patch_hash_mismatch" };
248
+ }
249
+ if (prior.kind === "record_hash_mismatch") {
250
+ return { ok: false, error: "rewrite_evidence_record_hash_mismatch" };
251
+ }
252
+ if (prior.kind === "digest_only") {
253
+ return { ok: true, coverage: "digest_only" };
254
+ }
255
+ if (prior.kind === "legacy") {
256
+ return { ok: true, coverage: "legacy_anchor_only" };
257
+ }
258
+ if (!verifyHistoryRecordHash(prior.entry).ok) {
259
+ return { ok: false, error: "rewrite_evidence_record_hash_mismatch" };
260
+ }
261
+ reconstructed = prior.entry;
262
+ }
263
+ return { ok: true, coverage: "complete" };
264
+ }
29
265
  function compareOptionalStrings(left, right) {
30
266
  return (left ?? "").localeCompare(right ?? "");
31
267
  }
@@ -78,31 +314,17 @@ function isDefinedPointerPath(document, path) {
78
314
  }
79
315
  let cursor = document;
80
316
  for (const segment of segments) {
81
- if (Array.isArray(cursor)) {
82
- if (segment === "-" || !/^(0|[1-9]\d*)$/.test(segment)) {
83
- return false;
84
- }
85
- const index = Number(segment);
86
- if (!Number.isInteger(index) || index < 0 || index >= cursor.length) {
87
- return false;
88
- }
89
- const next = cursor[index];
90
- if (next === undefined) {
91
- return false;
92
- }
93
- cursor = next;
94
- continue;
95
- }
96
- if (typeof cursor !== "object" || cursor === null) {
97
- return false;
98
- }
99
- if (!Object.prototype.hasOwnProperty.call(cursor, segment)) {
317
+ const arrayIndex = /^(0|[1-9]\d*)$/.test(segment) ? Number(segment) : -1;
318
+ const next = Array.isArray(cursor)
319
+ ? arrayIndex >= 0 && arrayIndex < cursor.length
320
+ ? cursor[arrayIndex]
321
+ : undefined
322
+ : isRecord(cursor) &&
323
+ Object.prototype.hasOwnProperty.call(cursor, segment)
324
+ ? cursor[segment]
325
+ : undefined;
326
+ if (next === undefined)
100
327
  return false;
101
- }
102
- const next = cursor[segment];
103
- if (next === undefined) {
104
- return false;
105
- }
106
328
  cursor = next;
107
329
  }
108
330
  return true;
@@ -256,7 +478,10 @@ export function createHistoryEntry(params) {
256
478
  message: params.message === undefined ? undefined : params.message,
257
479
  ...(context === undefined ? {} : { context }),
258
480
  };
259
- return { ...entry, event_class: classifyHistoryEvent(entry) };
481
+ return sealHistoryRecord({
482
+ ...entry,
483
+ event_class: classifyHistoryEvent(entry),
484
+ });
260
485
  }
261
486
  function isRecord(value) {
262
487
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -309,49 +534,44 @@ function serializeHistoryLine(value, fallbackEntry) {
309
534
  }
310
535
  return JSON.stringify(value);
311
536
  }
537
+ /** Apply one extension-provided append decision and report whether it consumed the write. */
538
+ async function applyHistoryAppendOverride(historyPath, entry, result) {
539
+ if (result === false)
540
+ return true;
541
+ if (typeof result === "string") {
542
+ await appendLineAtomic(historyPath, serializeHistoryLine(result, entry));
543
+ await invalidateHistoryDriftCacheForPath(historyPath);
544
+ await removeHistoryEventIndexForHistoryPath(historyPath);
545
+ return true;
546
+ }
547
+ if (typeof result !== "object" || result === null)
548
+ return false;
549
+ const record = result;
550
+ if (record.skip === true)
551
+ return true;
552
+ const nextHistoryPath = typeof record.history_path === "string" ? record.history_path : historyPath;
553
+ await appendLineAtomic(nextHistoryPath, serializeHistoryLine(typeof record.line === "string" ? record.line : (record.entry ?? entry), entry));
554
+ await invalidateHistoryDriftCacheForPath(nextHistoryPath);
555
+ await removeHistoryEventIndexForHistoryPath(nextHistoryPath);
556
+ return true;
557
+ }
312
558
  /** Implements append history entry for the public runtime surface of this module. */
313
559
  export async function appendHistoryEntry(historyPath, entry) {
314
560
  const override = await runActiveServiceOverride("history_append", {
315
561
  history_path: historyPath,
316
562
  entry,
317
563
  });
318
- if (override.handled) {
319
- if (override.result === false) {
320
- return;
321
- }
322
- if (typeof override.result === "string") {
323
- await appendLineAtomic(historyPath, serializeHistoryLine(override.result, entry));
324
- await invalidateHistoryDriftCacheForPath(historyPath);
325
- await removeHistoryEventIndexForHistoryPath(historyPath);
326
- return;
327
- }
328
- if (typeof override.result === "object" && override.result !== null) {
329
- const record = override.result;
330
- if (record.skip === true) {
331
- return;
332
- }
333
- const nextHistoryPath = typeof record.history_path === "string"
334
- ? record.history_path
335
- : historyPath;
336
- if (typeof record.line === "string") {
337
- await appendLineAtomic(nextHistoryPath, serializeHistoryLine(record.line, entry));
338
- await invalidateHistoryDriftCacheForPath(nextHistoryPath);
339
- await removeHistoryEventIndexForHistoryPath(nextHistoryPath);
340
- return;
341
- }
342
- await appendLineAtomic(nextHistoryPath, serializeHistoryLine(record.entry ?? entry, entry));
343
- await invalidateHistoryDriftCacheForPath(nextHistoryPath);
344
- await removeHistoryEventIndexForHistoryPath(nextHistoryPath);
345
- return;
346
- }
564
+ if (override.handled &&
565
+ (await applyHistoryAppendOverride(historyPath, entry, override.result))) {
566
+ return;
347
567
  }
348
568
  const timestampedEntry = entry.ts.trim().length > 0
349
569
  ? entry
350
570
  : { ...entry, ts: fallbackHistoryTimestamp(entry) };
351
- const normalizedEntry = {
571
+ const normalizedEntry = sealHistoryRecord({
352
572
  ...timestampedEntry,
353
573
  event_class: classifyHistoryEvent(timestampedEntry),
354
- };
574
+ });
355
575
  await appendHistoryEntryWithEventIndex(historyPath, normalizedEntry, async () => {
356
576
  await appendLineAtomic(historyPath, serializeHistoryLine(normalizedEntry, normalizedEntry));
357
577
  });
@@ -363,4 +583,4 @@ export const _testOnly = {
363
583
  isDefinedPointerPath,
364
584
  };
365
585
  //# sourceMappingURL=history.js.map
366
- //# debugId=1f78448d-a042-588a-84ce-369bd9ce8726
586
+ //# debugId=bf6a9831-134c-5dc2-a3e2-74dca00cf22c
@@ -4,13 +4,13 @@
4
4
  * Implements append-only history and replay behavior for Replay.
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]="c9488ed4-8228-5e7c-9717-69dd7a3275c4")}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]="ce8dfbdf-8471-5843-8179-5608140330e1")}catch(e){}}();
8
8
  import jsonPatch from "fast-json-patch";
9
9
  import { ITEM_METADATA_KEY_ORDER } from "../shared/constants.js";
10
10
  import { canonicalDocument } from "../item/item-format.js";
11
11
  import { toItemRecord } from "../item/item-record.js";
12
12
  import { orderObject, sha256Hex, stableStringify, } from "../shared/serialization.js";
13
- import { CURRENT_HISTORY_ITEM_HASH_VERSION, SUPPORTED_HISTORY_ITEM_HASH_VERSIONS, hashDocumentVerificationCandidates, } from "./history.js";
13
+ import { CURRENT_HISTORY_ITEM_HASH_VERSION, SUPPORTED_HISTORY_ITEM_HASH_VERSIONS, hashDocumentVerificationCandidates, resealHistoryRewrite, verifyHistoryRecordHash, verifyHistoryRewriteEvidence, } from "./history.js";
14
14
  /** Public contract for empty replay document, shared by SDK and presentation-layer consumers. */
15
15
  export const EMPTY_REPLAY_DOCUMENT = {
16
16
  metadata: {},
@@ -165,6 +165,31 @@ function historyEntryHashCandidates(explicitVersion, authoritativeVersion) {
165
165
  ? [...SUPPORTED_HISTORY_ITEM_HASH_VERSIONS]
166
166
  : [authoritativeVersion];
167
167
  }
168
+ /** Return the first record or retained-rewrite integrity failure for one entry. */
169
+ function historyEntryIntegrityError(entry) {
170
+ const recordVerification = verifyHistoryRecordHash(entry);
171
+ if (!recordVerification.ok)
172
+ return recordVerification.error;
173
+ const rewriteVerification = verifyHistoryRewriteEvidence(entry);
174
+ return rewriteVerification.ok ? undefined : rewriteVerification.error;
175
+ }
176
+ /** Validate immutable metadata and apply one patch for chain verification. */
177
+ function verifyHistoryEntryInput(entry, replay) {
178
+ const integrityError = historyEntryIntegrityError(entry);
179
+ if (integrityError)
180
+ return { ok: false, error: integrityError };
181
+ const applied = tryApplyReplayPatch(replay, entry.patch);
182
+ return applied.ok
183
+ ? { ok: true, document: applied.document }
184
+ : { ok: false, error: "patch_apply_failed" };
185
+ }
186
+ /** Refuse any entry whose current or retained immutable envelope is invalid. */
187
+ function assertHistoryEntryIntegrity(entry, index) {
188
+ const integrityError = historyEntryIntegrityError(entry);
189
+ if (integrityError) {
190
+ throw new TypeError(`${integrityError}:entry_${index + 1}`);
191
+ }
192
+ }
168
193
  /** Deterministically verify a history chain: each entry's before_hash must equal the prior replayed after_hash, the patch must strictly apply, and the recorded after_hash must equal the replayed result. */
169
194
  /** Verify a chain and report the explicit or auto-detected item hash epoch. */
170
195
  export function verifyHistoryChainWithVersion(entries) {
@@ -179,18 +204,15 @@ export function verifyHistoryChainWithVersion(entries) {
179
204
  if (unsupportedVersion) {
180
205
  errors.push(`verify_failed:unsupported_item_hash_version:${String(explicitVersion)}:entry_${index + 1}`);
181
206
  }
182
- const applied = tryApplyReplayPatch(replay, entry.patch);
183
- if (!applied.ok) {
207
+ const input = verifyHistoryEntryInput(entry, replay);
208
+ if (!input.ok) {
184
209
  return {
185
210
  ok: false,
186
- errors: [
187
- ...errors,
188
- `verify_failed:patch_apply_failed:entry_${index + 1}`,
189
- ],
211
+ errors: [...errors, `verify_failed:${input.error}:entry_${index + 1}`],
190
212
  };
191
213
  }
192
214
  if (unsupportedVersion) {
193
- replay = applied.document;
215
+ replay = input.document;
194
216
  detectedVersion = undefined;
195
217
  authoritativeExplicitVersion = undefined;
196
218
  continue;
@@ -201,7 +223,7 @@ export function verifyHistoryChainWithVersion(entries) {
201
223
  const candidates = historyEntryHashCandidates(explicitVersion, authoritativeExplicitVersion);
202
224
  const candidateMatches = candidates.map((version) => ({
203
225
  version,
204
- match: matchReplayHashCandidates(replay, applied.document, version, entry),
226
+ match: matchReplayHashCandidates(replay, input.document, version, entry),
205
227
  }));
206
228
  const matchingVersion = candidateMatches.find(({ match }) => match.pairIndex >= 0);
207
229
  if (matchingVersion === undefined) {
@@ -215,7 +237,7 @@ export function verifyHistoryChainWithVersion(entries) {
215
237
  };
216
238
  }
217
239
  const version = matchingVersion.version;
218
- replay = applied.document;
240
+ replay = input.document;
219
241
  detectedVersion = version;
220
242
  authoritativeExplicitVersion =
221
243
  explicitVersion ??
@@ -304,6 +326,27 @@ export function resolveHistoryRepairItemHashVersion(entries) {
304
326
  }
305
327
  return CURRENT_HISTORY_ITEM_HASH_VERSION;
306
328
  }
329
+ /** Apply one patch strictly or return its deterministic lenient repair projection. */
330
+ function applyReanchorPatch(replay, entry) {
331
+ const strict = tryApplyReplayPatch(replay, entry.patch);
332
+ if (strict.ok) {
333
+ return {
334
+ next: strict.document,
335
+ patch: entry.patch,
336
+ repaired: false,
337
+ converted: 0,
338
+ skipped: 0,
339
+ };
340
+ }
341
+ const lenient = lenientApplyReplayPatch(replay, entry.patch);
342
+ return {
343
+ next: lenient.document,
344
+ patch: jsonPatch.compare(replay, lenient.document),
345
+ repaired: true,
346
+ converted: lenient.convertedReplaceToAdd,
347
+ skipped: lenient.skippedOps,
348
+ };
349
+ }
307
350
  /** Re-anchor a drifted history chain: replay every entry from empty, recompute the before/after hashes, and only rewrite a patch when the original op set no longer strictly applies (legacy drift). Clean entries keep their patch verbatim so the on-disk diff stays minimal. The returned chain verifies via verifyHistoryChain. */
308
351
  export function reanchorHistoryEntries(entries, itemHashVersion = resolveHistoryRepairItemHashVersion(entries), options = {}) {
309
352
  const unsupportedIndex = entries.findIndex((entry) => isUnsupportedExplicitHistoryItemHashVersion(entry.item_hash_version));
@@ -322,27 +365,12 @@ export function reanchorHistoryEntries(entries, itemHashVersion = resolveHistory
322
365
  entries.some((entry) => entry.item_hash_version !== undefined);
323
366
  for (let index = 0; index < entries.length; index += 1) {
324
367
  const entry = entries[index];
325
- const strict = tryApplyReplayPatch(replay, entry.patch);
326
- let next;
327
- let outPatch;
328
- let patchRepaired = false;
329
- let entryConverted = 0;
330
- let entrySkipped = 0;
331
- if (strict.ok) {
332
- next = strict.document;
333
- outPatch = entry.patch;
334
- }
335
- else {
336
- const lenient = lenientApplyReplayPatch(replay, entry.patch);
337
- next = lenient.document;
338
- outPatch = jsonPatch.compare(replay, next);
339
- patchRepaired = true;
340
- entryConverted = lenient.convertedReplaceToAdd;
341
- entrySkipped = lenient.skippedOps;
342
- convertedReplaceToAdd += entryConverted;
343
- skippedOps += entrySkipped;
344
- entriesPatchRepaired += 1;
345
- }
368
+ assertHistoryEntryIntegrity(entry, index);
369
+ const patchApplication = applyReanchorPatch(replay, entry);
370
+ const { next, patch: outPatch, repaired: patchRepaired, converted: entryConverted, skipped: entrySkipped, } = patchApplication;
371
+ convertedReplaceToAdd += entryConverted;
372
+ skippedOps += entrySkipped;
373
+ entriesPatchRepaired += Number(patchRepaired);
346
374
  const hashMatch = matchReplayHashCandidates(replay, next, itemHashVersion, entry);
347
375
  if (semanticIndex === undefined || options.continuousHashSurface !== true) {
348
376
  semanticIndex = [
@@ -361,7 +389,7 @@ export function reanchorHistoryEntries(entries, itemHashVersion = resolveHistory
361
389
  if (rehashed) {
362
390
  entriesRehashed += 1;
363
391
  }
364
- const rewrittenEntry = {
392
+ let rewrittenEntry = {
365
393
  ...entry,
366
394
  patch: outPatch,
367
395
  before_hash: beforeHash,
@@ -373,6 +401,12 @@ export function reanchorHistoryEntries(entries, itemHashVersion = resolveHistory
373
401
  else {
374
402
  delete rewrittenEntry.item_hash_version;
375
403
  }
404
+ const versionChanged = rewrittenEntry.item_hash_version !== entry.item_hash_version;
405
+ if (rehashed || patchRepaired || versionChanged) {
406
+ rewrittenEntry = resealHistoryRewrite(entry, rewrittenEntry, {
407
+ retainOriginalPatch: patchRepaired,
408
+ });
409
+ }
376
410
  rewritten.push(rewrittenEntry);
377
411
  details.push({
378
412
  index: index + 1,
@@ -403,4 +437,4 @@ export function historyEntriesToRaw(entries) {
403
437
  return `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`;
404
438
  }
405
439
  //# sourceMappingURL=replay.js.map
406
- //# debugId=c9488ed4-8228-5e7c-9717-69dd7a3275c4
440
+ //# debugId=ce8dfbdf-8471-5843-8179-5608140330e1
@@ -25,6 +25,15 @@ export interface ProcessIdentity {
25
25
  declare function parseLockInfo(raw: string): LockReadResult;
26
26
  declare function readLockInfo(lockPath: string, emitReadHook?: boolean): Promise<LockReadResult>;
27
27
  declare function isErrno(error: unknown, code: string): boolean;
28
+ /**
29
+ * Classify exclusive-create failures that prove another lock lifecycle is in
30
+ * flight. Windows can report `EPERM`, `EACCES`, or `EBUSY` while a just-unlinked
31
+ * lock is still delete-pending; treating those as fatal makes bounded lock
32
+ * waiting fail nondeterministically. Existing paths are contention on every
33
+ * host. A missing or temporarily unstatable Windows path is contention only
34
+ * when its parent remains writable, preserving genuine permission failures.
35
+ */
36
+ declare function isLockCreateContention(error: unknown, lockPath: string, platform?: NodeJS.Platform): Promise<boolean>;
28
37
  declare function buildLockPayload(id: string, owner: string, ttlSeconds: number, token?: string): LockInfo;
29
38
  declare function isStaleLock(info: LockInfo | null, ttlSeconds: number): boolean;
30
39
  declare function lockOwnerSuffix(info: LockInfo | null): string;
@@ -57,6 +66,7 @@ export declare const _testOnly: {
57
66
  parseLockInfo: typeof parseLockInfo;
58
67
  readLockInfo: typeof readLockInfo;
59
68
  isErrno: typeof isErrno;
69
+ isLockCreateContention: typeof isLockCreateContention;
60
70
  buildLockPayload: typeof buildLockPayload;
61
71
  isStaleLock: typeof isStaleLock;
62
72
  lockOwnerSuffix: typeof lockOwnerSuffix;