@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +27 -0
- package/dist/cli-bundle/bundle-manifest.json +146 -146
- package/dist/cli-bundle/chunks/{chunk-E5RBBCXO.js → chunk-6XSKPNCF.js} +75 -75
- package/dist/cli-bundle/chunks/chunk-A24WAVE6.js +8 -0
- package/dist/cli-bundle/chunks/{chunk-DKGP3QLE.js → chunk-HI6BHPCU.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-Y4PJ6HPK.js → chunk-I2AS367G.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-IEHHE6GA.js → chunk-OGBVU67G.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-EDPIS7EB.js → chunk-RI4XNOYS.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-TXW6VL33.js +38 -0
- package/dist/cli-bundle/chunks/{chunk-LENC656S.js → chunk-V72UV7QS.js} +76 -79
- package/dist/cli-bundle/chunks/chunk-VGLHECAP.js +9 -0
- package/dist/cli-bundle/chunks/{chunk-34HASX3S.js → chunk-X6CMGV6L.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-UPPM2FOZ.js → chunk-YW4QK2G7.js} +2 -2
- package/dist/cli-bundle/chunks/{register-list-query-W2ZTOMKP.js → register-list-query-Z5YYSJJG.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-RVND5NCG.js → register-mutation-ELSP6I4I.js} +2 -2
- package/dist/cli-bundle/chunks/{register-operations-TTROIVC4.js → register-operations-OH7R3ZT4.js} +2 -2
- package/dist/cli-bundle/chunks/{register-setup-D3ZFHY3L.js → register-setup-E2AE72QG.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-Q45NHPF2.js → chunk-25UJBH4M.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-4HJFY4MB.js → chunk-3646BZ4W.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-TXE4TSHM.js → chunk-4YRAVI43.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-M5M6SVHS.js → chunk-53IHPICX.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-5P4HVOWG.js +176 -0
- package/dist/cli-bundle/focused-chunks/chunk-636VVQ7F.js +54 -0
- package/dist/cli-bundle/focused-chunks/{chunk-D7KXFBXV.js → chunk-6PWPQKDN.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-S27XMBIN.js → chunk-7N2TV7WZ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-5RFXUPN5.js → chunk-AJR3OP2E.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-KZ2NB3ET.js → chunk-B3A6MJYE.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-BWKHEIVF.js → chunk-BFDDJ2T2.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-4WR46RMG.js → chunk-BHUIRZWQ.js} +8 -8
- package/dist/cli-bundle/focused-chunks/{chunk-4RRHC3S4.js → chunk-JADHHPY6.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-GT7MXDLW.js → chunk-MHKUIPJW.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-NBMJD6MK.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-NSKBLHVI.js +20 -0
- package/dist/cli-bundle/focused-chunks/{chunk-OL5EH6I3.js → chunk-P2GOAOQN.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-UCLO3XDU.js → chunk-YARWJM2B.js} +2 -2
- package/dist/cli-bundle/main.js +2 -2
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +1 -1
- package/dist/cli-bundle/sdk-core.js +28 -31
- package/dist/cli-bundle/sdk-governance.js +1 -1
- package/dist/cli-bundle/sdk-graph.js +1 -1
- package/dist/cli-bundle/sdk-merge.js +42 -37
- package/dist/cli-bundle/sdk-query.js +1 -1
- package/dist/cli-bundle/sdk-runtime.js +1 -1
- package/dist/cli-bundle/sdk-testing.js +1 -1
- package/dist/cli-bundle/sdk.js +10 -10
- package/dist/core/diagnostics/remediation.js +4 -4
- package/dist/core/history/history.d.ts +37 -1
- package/dist/core/history/history.js +278 -58
- package/dist/core/history/replay.js +68 -34
- package/dist/core/lock/lock.d.ts +10 -0
- package/dist/core/lock/lock.js +42 -3
- package/dist/core/shared/author.d.ts +3 -1
- package/dist/core/shared/author.js +111 -23
- package/dist/sdk/environment/agent-environment.d.ts +296 -0
- package/dist/sdk/environment/agent-environment.js +467 -0
- package/dist/sdk/environment/project-runtime-compatibility.d.ts +19 -1
- package/dist/sdk/environment/project-runtime-compatibility.js +118 -21
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +14 -2
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +14 -14
- package/dist/sdk/governance/health.js +13 -44
- package/dist/sdk/governance/merge-receipt-history.d.ts +62 -0
- package/dist/sdk/governance/merge-receipt-history.js +207 -0
- package/dist/sdk/governance/provenance-health.js +9 -3
- package/dist/sdk/history-compact.js +14 -5
- package/dist/sdk/history-redact.js +58 -23
- package/dist/sdk/history-repair.js +16 -6
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.js +4 -2
- package/dist/sdk/merge/driver.d.ts +3 -1
- package/dist/sdk/merge/driver.js +9 -9
- package/dist/sdk/merge/index.d.ts +1 -1
- package/dist/sdk/merge/index.js +3 -3
- package/dist/sdk/merge/receipts.d.ts +17 -6
- package/dist/sdk/merge/receipts.js +246 -75
- package/dist/sdk/merge/reconcile.d.ts +9 -7
- package/dist/sdk/merge/reconcile.js +175 -12
- package/dist/sdk/merge/three-way.d.ts +22 -2
- package/dist/sdk/merge/three-way.js +92 -25
- package/dist/sdk/runtime-primitives.d.ts +2 -2
- package/dist/sdk/runtime-primitives.js +4 -4
- package/dist/types.d.ts +25 -0
- package/dist/types.js +2 -2
- package/docs/AGENT_ENVIRONMENTS.md +47 -0
- package/docs/AGENT_PROVENANCE_ADR.md +17 -2
- package/docs/CONTEXT_INTEGRITY_CONTRACTS.md +38 -1
- package/docs/MERGE_SAFETY.md +119 -23
- package/docs/README.md +1 -0
- package/docs/SDK_CONTEXT_EVIDENCE_CONTRACTS.md +24 -2
- package/docs/SDK_RUNTIME_BOUNDARIES.md +17 -1
- package/docs/examples/sdk-agent-environment/README.md +14 -0
- package/docs/examples/sdk-agent-environment/package.json +8 -0
- package/docs/examples/sdk-agent-environment/run-agent-environment.mjs +154 -0
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +4 -3
- package/marketplace.json +2 -2
- package/package.json +2 -2
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/sdk/public-surface.json +534 -24
- package/dist/cli-bundle/chunks/chunk-AY324UJ5.js +0 -9
- package/dist/cli-bundle/chunks/chunk-L7B7343B.js +0 -8
- package/dist/cli-bundle/chunks/chunk-LISESA7W.js +0 -35
- package/dist/cli-bundle/focused-chunks/chunk-3ZBI35HU.js +0 -176
- package/dist/cli-bundle/focused-chunks/chunk-GHCZ3YFR.js +0 -20
- package/dist/cli-bundle/focused-chunks/chunk-KBLZRU62.js +0 -2
- 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]="
|
|
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
|
|
209
|
-
summary: "
|
|
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=
|
|
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]="
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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 {
|
|
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
|
-
|
|
320
|
-
|
|
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=
|
|
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]="
|
|
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
|
|
183
|
-
if (!
|
|
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 =
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
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=
|
|
440
|
+
//# debugId=ce8dfbdf-8471-5843-8179-5608140330e1
|
package/dist/core/lock/lock.d.ts
CHANGED
|
@@ -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;
|