@unbrained/pm-cli 2026.9.2 → 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 +44 -0
- package/dist/cli/after-command-context-usage.js +16 -3
- package/dist/cli-bundle/bundle-manifest.json +151 -151
- package/dist/cli-bundle/chunks/chunk-6XSKPNCF.js +230 -0
- package/dist/cli-bundle/chunks/chunk-A24WAVE6.js +8 -0
- package/dist/cli-bundle/chunks/{chunk-TAG4VC22.js → chunk-HI6BHPCU.js} +27 -27
- package/dist/cli-bundle/chunks/{chunk-Y7VOMSZT.js → chunk-I2AS367G.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-X4RCGLTX.js → chunk-OGBVU67G.js} +5 -5
- package/dist/cli-bundle/chunks/{chunk-DTGWEL23.js → chunk-RI4XNOYS.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-TXW6VL33.js +38 -0
- package/dist/cli-bundle/chunks/{chunk-FZ4Z2J4F.js → chunk-V72UV7QS.js} +76 -79
- package/dist/cli-bundle/chunks/chunk-VGLHECAP.js +9 -0
- package/dist/cli-bundle/chunks/{chunk-T6H5BZCP.js → chunk-X6CMGV6L.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-3WICXLVT.js → chunk-YW4QK2G7.js} +2 -2
- package/dist/cli-bundle/chunks/{register-list-query-XYNX4ASQ.js → register-list-query-Z5YYSJJG.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-WFQ7MJTX.js → register-mutation-ELSP6I4I.js} +2 -2
- package/dist/cli-bundle/chunks/{register-operations-JVTI7JPP.js → register-operations-OH7R3ZT4.js} +2 -2
- package/dist/cli-bundle/chunks/{register-setup-KVNC5KPL.js → register-setup-E2AE72QG.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-MEASX544.js → chunk-25UJBH4M.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-SVXJYD6G.js → chunk-3646BZ4W.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-4YRAVI43.js +12 -0
- package/dist/cli-bundle/focused-chunks/chunk-53IHPICX.js +3 -0
- 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-4O4CJAR6.js → chunk-6PWPQKDN.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-7N2TV7WZ.js +8 -0
- package/dist/cli-bundle/focused-chunks/chunk-AJR3OP2E.js +3 -0
- package/dist/cli-bundle/focused-chunks/chunk-B3A6MJYE.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-BFDDJ2T2.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-BHUIRZWQ.js +9 -0
- package/dist/cli-bundle/focused-chunks/chunk-JADHHPY6.js +6 -0
- package/dist/cli-bundle/focused-chunks/{chunk-KB5OFSFD.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-BEVHCEWG.js → chunk-P2GOAOQN.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-YARWJM2B.js +2 -0
- package/dist/cli-bundle/main.js +15 -15
- 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 +43 -36
- 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/event-classification.d.ts +21 -0
- package/dist/core/history/event-classification.js +109 -0
- package/dist/core/history/event-index.d.ts +14 -3
- package/dist/core/history/event-index.js +543 -88
- package/dist/core/history/history.d.ts +37 -1
- package/dist/core/history/history.js +320 -71
- package/dist/core/history/replay.js +68 -34
- package/dist/core/history/workspace-history.js +7 -6
- package/dist/core/lock/lock.d.ts +32 -2
- package/dist/core/lock/lock.js +161 -20
- package/dist/core/output/output.js +31 -7
- package/dist/core/shared/author.d.ts +3 -1
- package/dist/core/shared/author.js +111 -23
- package/dist/core/shared/time.d.ts +2 -0
- package/dist/core/shared/time.js +8 -2
- package/dist/core/store/item-metadata-query-index.js +5 -5
- package/dist/sdk/context/recency.d.ts +9 -0
- package/dist/sdk/context/recency.js +34 -0
- package/dist/sdk/context/usage-egress.d.ts +9 -0
- package/dist/sdk/context/usage-egress.js +30 -0
- package/dist/sdk/context-intent-contracts.js +7 -3
- package/dist/sdk/context-relevance.d.ts +23 -0
- package/dist/sdk/context-relevance.js +94 -23
- package/dist/sdk/context-signal-store.d.ts +7 -3
- package/dist/sdk/context-signal-store.js +155 -42
- package/dist/sdk/context-usage.d.ts +54 -0
- package/dist/sdk/context-usage.js +245 -16
- 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 -2
- 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 +4 -0
- package/dist/sdk/index.js +6 -2
- package/dist/sdk/init.d.ts +2 -0
- package/dist/sdk/init.js +18 -9
- 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/query/context.d.ts +3 -1
- package/dist/sdk/query/context.js +21 -9
- package/dist/sdk/query/next.js +18 -9
- package/dist/sdk/runtime-input.d.ts +2 -0
- package/dist/sdk/runtime-input.js +25 -2
- package/dist/sdk/runtime-primitives.d.ts +2 -2
- package/dist/sdk/runtime-primitives.js +4 -4
- package/dist/sdk/runtime.d.ts +1 -0
- package/dist/sdk/runtime.js +17 -26
- package/dist/types.d.ts +27 -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/CONTEXT_RELEVANCE.md +44 -1
- package/docs/MERGE_SAFETY.md +119 -23
- package/docs/PERFORMANCE.md +6 -3
- package/docs/README.md +1 -0
- package/docs/READ_OUTPUT_CONTRACTS.md +9 -1
- package/docs/SDK.md +11 -8
- package/docs/SDK_CONTEXT.md +7 -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 +5 -3
- package/marketplace.json +2 -2
- package/package.json +5 -5
- 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 +773 -46
- package/dist/cli-bundle/chunks/chunk-GOACULJW.js +0 -13
- package/dist/cli-bundle/chunks/chunk-NOPL37X4.js +0 -205
- package/dist/cli-bundle/chunks/chunk-UDW64J2W.js +0 -8
- package/dist/cli-bundle/chunks/chunk-ZLJIFSOE.js +0 -35
- package/dist/cli-bundle/focused-chunks/chunk-242AXYB2.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-2FEZ5SKM.js +0 -3
- package/dist/cli-bundle/focused-chunks/chunk-6GCRSLPG.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-7VATS5QY.js +0 -16
- package/dist/cli-bundle/focused-chunks/chunk-JEFKLWMM.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-MKHG3T76.js +0 -20
- package/dist/cli-bundle/focused-chunks/chunk-O67VDLOT.js +0 -51
- package/dist/cli-bundle/focused-chunks/chunk-PF3E6Q36.js +0 -8
- package/dist/cli-bundle/focused-chunks/chunk-QDFJIRKN.js +0 -4
- package/dist/cli-bundle/focused-chunks/chunk-RMRCQPWU.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-S4U5CN5Q.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-T2LXXSOD.js +0 -158
- package/dist/cli-bundle/focused-chunks/chunk-Z2USIBR2.js +0 -5
|
@@ -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,18 +4,20 @@
|
|
|
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
|
-
import { ITEM_METADATA_KEY_ORDER } from "../shared/constants.js";
|
|
9
|
+
import { EXIT_CODE, ITEM_METADATA_KEY_ORDER } from "../shared/constants.js";
|
|
10
10
|
import { runActiveServiceOverride } from "../extensions/index.js";
|
|
11
11
|
import { appendLineAtomic } from "../fs/fs-utils.js";
|
|
12
12
|
import { canonicalDocument } from "../item/item-format.js";
|
|
13
13
|
import { toItemRecord } from "../item/item-record.js";
|
|
14
14
|
import { orderObject, sha256Hex, stableStringify, } from "../shared/serialization.js";
|
|
15
|
-
import { nowIso } from "../shared/time.js";
|
|
15
|
+
import { isMillisecondPrecisionRfc3339DateTime, nowIso, } from "../shared/time.js";
|
|
16
16
|
import { diagnoseAgentIdentity, resolveHistoryAgentIdentity, resolveHistoryAuthorSource, } from "../shared/author.js";
|
|
17
|
-
import {
|
|
17
|
+
import { appendHistoryEntryWithEventIndex, removeHistoryEventIndexForHistoryPath, } from "./event-index.js";
|
|
18
|
+
import { classifyHistoryEvent } from "./event-classification.js";
|
|
18
19
|
import { invalidateHistoryDriftCacheForPath } from "./drift-cache.js";
|
|
20
|
+
import { PmCliError } from "../shared/errors.js";
|
|
19
21
|
const EMPTY_LEGACY_HASH_DOCUMENT = {
|
|
20
22
|
front_matter: {},
|
|
21
23
|
body: "",
|
|
@@ -24,6 +26,242 @@ const EMPTY_LEGACY_HASH_DOCUMENT = {
|
|
|
24
26
|
export const CURRENT_HISTORY_ITEM_HASH_VERSION = 3;
|
|
25
27
|
/** Hash epochs understood by this runtime. */
|
|
26
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
|
+
}
|
|
27
265
|
function compareOptionalStrings(left, right) {
|
|
28
266
|
return (left ?? "").localeCompare(right ?? "");
|
|
29
267
|
}
|
|
@@ -42,7 +280,8 @@ function compareLegacyLinkedTests(left, right) {
|
|
|
42
280
|
compareOptionalStrings(left.command, right.command),
|
|
43
281
|
compareOptionalNumbers(left.timeout_seconds, right.timeout_seconds),
|
|
44
282
|
compareOptionalStrings(left.pm_context_mode, right.pm_context_mode),
|
|
45
|
-
Number(Boolean(left.shared_host_safe)) -
|
|
283
|
+
Number(Boolean(left.shared_host_safe)) -
|
|
284
|
+
Number(Boolean(right.shared_host_safe)),
|
|
46
285
|
compareJsonValues(left.env_clear, right.env_clear, []),
|
|
47
286
|
compareJsonValues(left.env_set, right.env_set, {}),
|
|
48
287
|
compareJsonValues(left.assert_stdout_contains, right.assert_stdout_contains, []),
|
|
@@ -75,31 +314,17 @@ function isDefinedPointerPath(document, path) {
|
|
|
75
314
|
}
|
|
76
315
|
let cursor = document;
|
|
77
316
|
for (const segment of segments) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
cursor = next;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (typeof cursor !== "object" || cursor === null) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
if (!Object.prototype.hasOwnProperty.call(cursor, segment)) {
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
const next = cursor[segment];
|
|
100
|
-
if (next === undefined) {
|
|
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)
|
|
101
327
|
return false;
|
|
102
|
-
}
|
|
103
328
|
cursor = next;
|
|
104
329
|
}
|
|
105
330
|
return true;
|
|
@@ -222,13 +447,14 @@ export function createHistoryEntry(params) {
|
|
|
222
447
|
const provenanceOutcomes = agentIdentity.harness
|
|
223
448
|
? Object.fromEntries(Object.entries(diagnoseAgentIdentity().provenance_outcomes).filter(([, outcome]) => outcome.resolver !== undefined && outcome.status !== "resolved"))
|
|
224
449
|
: undefined;
|
|
225
|
-
const context = provenanceOutcomes === undefined ||
|
|
450
|
+
const context = provenanceOutcomes === undefined ||
|
|
451
|
+
Object.keys(provenanceOutcomes).length === 0
|
|
226
452
|
? params.context
|
|
227
453
|
: {
|
|
228
454
|
...params.context,
|
|
229
455
|
agent_provenance_outcomes: provenanceOutcomes,
|
|
230
456
|
};
|
|
231
|
-
|
|
457
|
+
const entry = {
|
|
232
458
|
ts: params.nowIso,
|
|
233
459
|
author: params.author,
|
|
234
460
|
author_source: params.authorSource ?? resolveHistoryAuthorSource(params.author),
|
|
@@ -243,9 +469,7 @@ export function createHistoryEntry(params) {
|
|
|
243
469
|
...(agentIdentity.provenance
|
|
244
470
|
? { agent_provenance: agentIdentity.provenance }
|
|
245
471
|
: {}),
|
|
246
|
-
...(agentIdentity.episode
|
|
247
|
-
? { agent_episode: agentIdentity.episode }
|
|
248
|
-
: {}),
|
|
472
|
+
...(agentIdentity.episode ? { agent_episode: agentIdentity.episode } : {}),
|
|
249
473
|
op: params.op,
|
|
250
474
|
patch,
|
|
251
475
|
before_hash: sha256Hex(stableStringify(beforeHashCanonical)),
|
|
@@ -254,17 +478,37 @@ export function createHistoryEntry(params) {
|
|
|
254
478
|
message: params.message === undefined ? undefined : params.message,
|
|
255
479
|
...(context === undefined ? {} : { context }),
|
|
256
480
|
};
|
|
481
|
+
return sealHistoryRecord({
|
|
482
|
+
...entry,
|
|
483
|
+
event_class: classifyHistoryEvent(entry),
|
|
484
|
+
});
|
|
257
485
|
}
|
|
258
486
|
function isRecord(value) {
|
|
259
487
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
260
488
|
}
|
|
489
|
+
function invalidHistoryTimestamp(message) {
|
|
490
|
+
return new PmCliError(message, EXIT_CODE.GENERIC_FAILURE, {
|
|
491
|
+
code: "history_timestamp_invalid",
|
|
492
|
+
});
|
|
493
|
+
}
|
|
261
494
|
function fallbackHistoryTimestamp(entry) {
|
|
262
495
|
const ts = entry.ts.trim();
|
|
263
|
-
|
|
496
|
+
if (ts.length === 0)
|
|
497
|
+
return nowIso();
|
|
498
|
+
if (!isMillisecondPrecisionRfc3339DateTime(ts)) {
|
|
499
|
+
throw invalidHistoryTimestamp("History timestamp must be a valid RFC 3339 date-time with millisecond precision");
|
|
500
|
+
}
|
|
501
|
+
if (ts !== entry.ts) {
|
|
502
|
+
throw invalidHistoryTimestamp("History timestamp must not contain surrounding whitespace");
|
|
503
|
+
}
|
|
504
|
+
return entry.ts;
|
|
264
505
|
}
|
|
265
506
|
function withHistoryTimestamp(value, fallbackTs) {
|
|
266
507
|
const ts = value.ts;
|
|
267
508
|
if (typeof ts === "string" && ts.trim().length > 0) {
|
|
509
|
+
if (!isMillisecondPrecisionRfc3339DateTime(ts)) {
|
|
510
|
+
throw invalidHistoryTimestamp("History timestamp must be a valid RFC 3339 date-time with millisecond precision");
|
|
511
|
+
}
|
|
268
512
|
return value;
|
|
269
513
|
}
|
|
270
514
|
return { ...value, ts: fallbackTs };
|
|
@@ -272,14 +516,16 @@ function withHistoryTimestamp(value, fallbackTs) {
|
|
|
272
516
|
function serializeHistoryLine(value, fallbackEntry) {
|
|
273
517
|
const fallbackTs = fallbackHistoryTimestamp(fallbackEntry);
|
|
274
518
|
if (typeof value === "string") {
|
|
519
|
+
let parsed;
|
|
275
520
|
try {
|
|
276
|
-
|
|
277
|
-
if (isRecord(parsed)) {
|
|
278
|
-
return JSON.stringify(withHistoryTimestamp(parsed, fallbackTs));
|
|
279
|
-
}
|
|
521
|
+
parsed = JSON.parse(value);
|
|
280
522
|
}
|
|
281
523
|
catch {
|
|
282
524
|
// Non-JSON extension lines are preserved for compatibility.
|
|
525
|
+
return value;
|
|
526
|
+
}
|
|
527
|
+
if (isRecord(parsed)) {
|
|
528
|
+
return JSON.stringify(withHistoryTimestamp(parsed, fallbackTs));
|
|
283
529
|
}
|
|
284
530
|
return value;
|
|
285
531
|
}
|
|
@@ -288,45 +534,48 @@ function serializeHistoryLine(value, fallbackEntry) {
|
|
|
288
534
|
}
|
|
289
535
|
return JSON.stringify(value);
|
|
290
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
|
+
}
|
|
291
558
|
/** Implements append history entry for the public runtime surface of this module. */
|
|
292
559
|
export async function appendHistoryEntry(historyPath, entry) {
|
|
293
560
|
const override = await runActiveServiceOverride("history_append", {
|
|
294
561
|
history_path: historyPath,
|
|
295
562
|
entry,
|
|
296
563
|
});
|
|
297
|
-
if (override.handled
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
if (typeof override.result === "string") {
|
|
302
|
-
await appendLineAtomic(historyPath, serializeHistoryLine(override.result, entry));
|
|
303
|
-
await invalidateHistoryDriftCacheForPath(historyPath);
|
|
304
|
-
await removeHistoryEventIndexForHistoryPath(historyPath);
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
if (typeof override.result === "object" && override.result !== null) {
|
|
308
|
-
const record = override.result;
|
|
309
|
-
if (record.skip === true) {
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
const nextHistoryPath = typeof record.history_path === "string"
|
|
313
|
-
? record.history_path
|
|
314
|
-
: historyPath;
|
|
315
|
-
if (typeof record.line === "string") {
|
|
316
|
-
await appendLineAtomic(nextHistoryPath, serializeHistoryLine(record.line, entry));
|
|
317
|
-
await invalidateHistoryDriftCacheForPath(nextHistoryPath);
|
|
318
|
-
await removeHistoryEventIndexForHistoryPath(nextHistoryPath);
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
await appendLineAtomic(nextHistoryPath, serializeHistoryLine(record.entry ?? entry, entry));
|
|
322
|
-
await invalidateHistoryDriftCacheForPath(nextHistoryPath);
|
|
323
|
-
await removeHistoryEventIndexForHistoryPath(nextHistoryPath);
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
564
|
+
if (override.handled &&
|
|
565
|
+
(await applyHistoryAppendOverride(historyPath, entry, override.result))) {
|
|
566
|
+
return;
|
|
326
567
|
}
|
|
327
|
-
|
|
568
|
+
const timestampedEntry = entry.ts.trim().length > 0
|
|
569
|
+
? entry
|
|
570
|
+
: { ...entry, ts: fallbackHistoryTimestamp(entry) };
|
|
571
|
+
const normalizedEntry = sealHistoryRecord({
|
|
572
|
+
...timestampedEntry,
|
|
573
|
+
event_class: classifyHistoryEvent(timestampedEntry),
|
|
574
|
+
});
|
|
575
|
+
await appendHistoryEntryWithEventIndex(historyPath, normalizedEntry, async () => {
|
|
576
|
+
await appendLineAtomic(historyPath, serializeHistoryLine(normalizedEntry, normalizedEntry));
|
|
577
|
+
});
|
|
328
578
|
await invalidateHistoryDriftCacheForPath(historyPath);
|
|
329
|
-
await updateHistoryEventIndexAfterAppend(historyPath, entry);
|
|
330
579
|
}
|
|
331
580
|
/** Public contract for test only, shared by SDK and presentation-layer consumers. */
|
|
332
581
|
export const _testOnly = {
|
|
@@ -334,4 +583,4 @@ export const _testOnly = {
|
|
|
334
583
|
isDefinedPointerPath,
|
|
335
584
|
};
|
|
336
585
|
//# sourceMappingURL=history.js.map
|
|
337
|
-
//# debugId=
|
|
586
|
+
//# debugId=bf6a9831-134c-5dc2-a3e2-74dca00cf22c
|