@unbrained/pm-cli 2026.9.10 → 2026.9.12
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 +53 -11
- package/dist/cli/command-namespaces.d.ts +2 -2
- package/dist/cli/command-namespaces.js +1 -1
- package/dist/cli/error-guidance.d.ts +10 -0
- package/dist/cli/error-guidance.js +1 -1
- package/dist/cli/extension-command-help.js +3 -3
- package/dist/cli/main.d.ts +4 -0
- package/dist/cli/main.js +2 -2
- package/dist/cli/option-values.d.ts +3 -0
- package/dist/cli/option-values.js +2 -0
- package/dist/cli/register-history-maintenance.js +2 -1
- package/dist/cli/register-list-query.js +2 -2
- package/dist/cli/register-mutation.d.ts +5 -0
- package/dist/cli/register-mutation.js +2 -16
- package/dist/cli/schema/registration.d.ts +4 -0
- package/dist/cli/schema/registration.js +16 -0
- package/dist/cli-bundle/bundle-manifest.json +156 -156
- package/dist/cli-bundle/chunks/{chunk-NKOYVKFM.js → chunk-2JAY4LFI.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-I6XT3DLQ.js → chunk-3ZJDGTYC.js} +53 -53
- package/dist/cli-bundle/chunks/chunk-67H4PKQ5.js +3 -0
- package/dist/cli-bundle/chunks/{chunk-EOCKPCEY.js → chunk-7H33WPDF.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-7KF6HCRC.js +20 -0
- package/dist/cli-bundle/chunks/chunk-DEGRLB6Y.js +235 -0
- package/dist/cli-bundle/chunks/chunk-GKY655T7.js +6 -0
- package/dist/cli-bundle/chunks/chunk-I6VQUHK6.js +8 -0
- package/dist/cli-bundle/chunks/chunk-PTX2JRFR.js +2 -0
- package/dist/cli-bundle/chunks/{chunk-PYUITRSV.js → chunk-TAGDYARO.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-X6POOZKD.js +29 -0
- package/dist/cli-bundle/chunks/{chunk-4SFBH77G.js → chunk-YU75JRTW.js} +156 -142
- package/dist/cli-bundle/chunks/{register-list-query-CUIW7SSE.js → register-list-query-KEZPDR6X.js} +6 -6
- package/dist/cli-bundle/chunks/register-mutation-HEXA3ZDM.js +20 -0
- package/dist/cli-bundle/chunks/{register-operations-XIZPLPQY.js → register-operations-TEZT65JF.js} +2 -2
- package/dist/cli-bundle/chunks/{register-setup-JVGPCAIL.js → register-setup-IIL74WUG.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-ZAWAL2CG.js → chunk-2BQ4JSJQ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-5CRND7UA.js +12 -0
- package/dist/cli-bundle/focused-chunks/chunk-5PSYV3AT.js +164 -0
- package/dist/cli-bundle/focused-chunks/{chunk-SL5YVVPV.js → chunk-7EKXAUKF.js} +53 -52
- package/dist/cli-bundle/focused-chunks/chunk-BIOWOU6S.js +30 -0
- package/dist/cli-bundle/focused-chunks/chunk-EIXGDI77.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-GAKFECOQ.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-HFAW3FJQ.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-U5SHHU7E.js → chunk-IURELABV.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-KK7KSXUP.js +8 -0
- package/dist/cli-bundle/focused-chunks/{chunk-ZDNAWWQI.js → chunk-LWJBBM6X.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-MFVABYEU.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-7N5MRONP.js → chunk-PERRTPIY.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-FHUARRMA.js → chunk-SDQOOL34.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-M36SF4GE.js → chunk-TTRB47KM.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-VWMP6W7Y.js +20 -0
- package/dist/cli-bundle/focused-chunks/chunk-YNBACVQ2.js +2 -0
- package/dist/cli-bundle/main.js +21 -21
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +2 -2
- package/dist/cli-bundle/sdk-core.js +29 -29
- 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 -42
- 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/history/digest.d.ts +12 -0
- package/dist/core/history/digest.js +2 -0
- package/dist/core/history/drift-scan.js +1 -1
- package/dist/core/history/history.d.ts +5 -3
- package/dist/core/history/history.js +2 -2
- package/dist/core/history/operation-contract.d.ts +30 -0
- package/dist/core/history/operation-contract.js +2 -0
- package/dist/core/history/replay.d.ts +3 -2
- package/dist/core/history/replay.js +1 -1
- package/dist/core/history/tombstone.d.ts +3 -0
- package/dist/core/history/tombstone.js +2 -0
- package/dist/core/item/id.d.ts +8 -7
- package/dist/core/item/id.js +1 -1
- package/dist/core/output/mutation-projection.d.ts +1 -0
- package/dist/core/output/mutation-projection.js +1 -1
- package/dist/core/shared/command-line.d.ts +10 -0
- package/dist/core/shared/command-line.js +2 -0
- package/dist/core/shared/errors.d.ts +13 -0
- package/dist/core/shared/errors.js +1 -1
- package/dist/core/store/item-store.d.ts +5 -1
- package/dist/core/store/item-store.js +1 -1
- package/dist/core/telemetry/runtime.d.ts +1 -0
- package/dist/core/telemetry/runtime.js +2 -2
- package/dist/sdk/agent/command-recovery.js +1 -1
- package/dist/sdk/agent/item-addressing.js +1 -1
- package/dist/sdk/agent/refusal-corpus-contracts.js +1 -1
- package/dist/sdk/agent-capability-contracts.js +1 -1
- package/dist/sdk/cli-bootstrap.d.ts +2 -1
- package/dist/sdk/cli-bootstrap.js +1 -1
- package/dist/sdk/cli-contracts/command-aliases.d.ts +2 -0
- package/dist/sdk/cli-contracts/command-aliases.js +1 -1
- package/dist/sdk/cli-contracts/enum-contracts.d.ts +1 -1
- package/dist/sdk/cli-contracts/enum-contracts.js +1 -1
- package/dist/sdk/cli-contracts/flag-contracts.d.ts +2 -0
- package/dist/sdk/cli-contracts/flag-contracts.js +1 -1
- package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +1 -1
- package/dist/sdk/cli-contracts/grammar-contracts.js +1 -1
- package/dist/sdk/cli-contracts/runtime-contracts.d.ts +3 -0
- package/dist/sdk/cli-contracts/runtime-contracts.js +1 -1
- package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
- package/dist/sdk/cli-contracts/tool-schema.js +1 -1
- package/dist/sdk/cli-contracts.d.ts +1 -1
- package/dist/sdk/cli-contracts.js +1 -1
- package/dist/sdk/command-line.d.ts +1 -6
- package/dist/sdk/command-line.js +1 -1
- package/dist/sdk/comments.js +1 -1
- package/dist/sdk/completion.d.ts +2 -0
- package/dist/sdk/completion.js +26 -14
- package/dist/sdk/contracts.d.ts +2 -0
- package/dist/sdk/contracts.js +1 -1
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -1
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1 -1
- package/dist/sdk/governance/storage-integrity.js +1 -1
- package/dist/sdk/governance/workflow-completeness.js +1 -1
- package/dist/sdk/governance/workflow-policy.js +2 -2
- package/dist/sdk/governance.d.ts +3 -0
- package/dist/sdk/governance.js +1 -1
- package/dist/sdk/history/attestation-command.d.ts +22 -0
- package/dist/sdk/history/attestation-command.js +3 -0
- package/dist/sdk/history/attestation-contract.d.ts +103 -0
- package/dist/sdk/history/attestation-contract.js +2 -0
- package/dist/sdk/history/attestation-scan.d.ts +16 -0
- package/dist/sdk/history/attestation-scan.js +2 -0
- package/dist/sdk/history/attestation.d.ts +8 -0
- package/dist/sdk/history/attestation.js +2 -0
- package/dist/sdk/history/salvage.js +1 -1
- package/dist/sdk/history-compact.js +1 -1
- package/dist/sdk/history-read.js +1 -1
- package/dist/sdk/history-redact.js +1 -1
- package/dist/sdk/history-repair.js +1 -1
- package/dist/sdk/index.d.ts +3 -1
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/learnings.js +1 -1
- package/dist/sdk/lifecycle/claim.d.ts +2 -0
- package/dist/sdk/lifecycle/claim.js +1 -1
- package/dist/sdk/lifecycle/mcp-actions.d.ts +4 -0
- package/dist/sdk/lifecycle/mcp-actions.js +1 -1
- package/dist/sdk/lifecycle/task-composition.d.ts +2 -0
- package/dist/sdk/lifecycle/task-composition.js +1 -1
- package/dist/sdk/merge/reconcile.js +1 -1
- package/dist/sdk/merge/three-way.js +2 -2
- package/dist/sdk/notes.js +1 -1
- package/dist/sdk/public-surface.json +1 -1
- package/dist/sdk/query/projection-contracts.d.ts +1 -1
- package/dist/sdk/query/projection-contracts.js +1 -1
- package/dist/sdk/read-output-budget.d.ts +4 -10
- package/dist/sdk/read-output-budget.js +1 -1
- package/dist/sdk/read-output-contracts.d.ts +1 -1
- package/dist/sdk/read-output-contracts.js +1 -1
- package/dist/sdk/read-output-session.d.ts +1 -1
- package/dist/sdk/read-output-session.js +1 -1
- package/dist/sdk/runtime-input.js +1 -1
- package/dist/sdk/runtime.d.ts +3 -0
- package/dist/sdk/runtime.js +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/types.js +1 -1
- package/docs/AGENT_UX_CONTRACTS.md +8 -0
- package/docs/ARCHITECTURE.md +7 -0
- package/docs/CLI_GRAMMAR.md +23 -0
- package/docs/HISTORY_ATTESTATION.md +164 -0
- package/docs/HISTORY_OPERATIONS.md +46 -0
- package/docs/HISTORY_RECOVERY.md +27 -2
- package/docs/LIFECYCLE_COMMANDS.md +19 -2
- package/docs/MERGE_SAFETY.md +64 -3
- package/docs/README.md +1 -0
- package/docs/READ_OUTPUT_CONTRACTS.md +13 -0
- package/docs/WORKFLOW_POLICIES.md +47 -3
- package/docs/generated/AGENT_CAPABILITY_ROUTING.md +4 -4
- package/docs/generated/AGENT_COMMAND_SURFACE.md +5 -0
- package/docs/generated/FLAG_LEXICON_BUDGETS.md +5 -0
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +10 -7
- package/marketplace.json +2 -2
- package/package.json +4 -4
- 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/README.md +31 -7
- package/packages/pm-governance-audit/extensions/governance-audit/index.ts +6 -3
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/extensions/guide-shell/runtime.ts +7 -19
- 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/README.md +9 -5
- package/packages/pm-search-advanced/extensions/search-advanced/index.ts +3 -2
- 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/dist/cli-bundle/chunks/chunk-74RGSPLL.js +0 -2
- package/dist/cli-bundle/chunks/chunk-7WIHZI2M.js +0 -5
- package/dist/cli-bundle/chunks/chunk-GLQMKUGP.js +0 -2
- package/dist/cli-bundle/chunks/chunk-MACGGTLQ.js +0 -29
- package/dist/cli-bundle/chunks/chunk-P6BEYAON.js +0 -8
- package/dist/cli-bundle/chunks/chunk-TSWEX72T.js +0 -20
- package/dist/cli-bundle/chunks/chunk-YKQTETJI.js +0 -235
- package/dist/cli-bundle/chunks/register-mutation-5F5CYUWZ.js +0 -20
- package/dist/cli-bundle/focused-chunks/chunk-26O2WB73.js +0 -10
- package/dist/cli-bundle/focused-chunks/chunk-5YU75QLV.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-B2CMFYRZ.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-CRY5MHEI.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-F6WKFXBT.js +0 -8
- package/dist/cli-bundle/focused-chunks/chunk-OROZM37I.js +0 -177
- package/dist/cli-bundle/focused-chunks/chunk-PNGYYQY7.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-TPOL4TWE.js +0 -9
- package/dist/cli-bundle/focused-chunks/chunk-XSMA2W6Q.js +0 -12
- package/dist/cli-bundle/focused-chunks/chunk-ZJHYQOTM.js +0 -20
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/** @module sdk/history/attestation-contract
|
|
2
|
+
* Portable, payload-free commitments to the exact bytes of history streams.
|
|
3
|
+
*/
|
|
4
|
+
import type { HistoryHashAlgorithm } from "../../core/history/digest.js";
|
|
5
|
+
/** Wire format, hashing rules and trust boundary for detached history proofs. */
|
|
6
|
+
export declare const PM_HISTORY_ATTESTATION_CONTRACT: {
|
|
7
|
+
readonly format: "pm-history-attestation";
|
|
8
|
+
readonly version: 1;
|
|
9
|
+
readonly stream_hash: "exact_file_bytes";
|
|
10
|
+
readonly rollup_hash: "stable_json_without_workspace_digest";
|
|
11
|
+
readonly ordering: "stream_id_code_unit_ascending";
|
|
12
|
+
readonly consistency: "per_stream_snapshot_with_change_detection";
|
|
13
|
+
readonly trust: "independently_retained_bundle";
|
|
14
|
+
};
|
|
15
|
+
/** Last maintenance event, bounded independently of stream length. */
|
|
16
|
+
export interface HistoryAttestationMaintenance {
|
|
17
|
+
/** Maintenance operation recorded in the stream. */
|
|
18
|
+
op: string;
|
|
19
|
+
/** Timestamp claimed by the recorded event; not trusted wall-clock proof. */
|
|
20
|
+
ts: string;
|
|
21
|
+
/** Record digest when the event carries whole-record integrity. */
|
|
22
|
+
record_hash: string | null;
|
|
23
|
+
}
|
|
24
|
+
/** Constant-size commitment for one item, deleted-item or workspace stream. */
|
|
25
|
+
export interface HistoryAttestationStream {
|
|
26
|
+
/** Filename stem; no path components are allowed. */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Number of nonblank JSONL records. */
|
|
29
|
+
entries: number;
|
|
30
|
+
/** Exact byte length, including whitespace and line endings. */
|
|
31
|
+
bytes: number;
|
|
32
|
+
/** Digest of the complete file using the bundle's algorithm. */
|
|
33
|
+
stream_digest: string;
|
|
34
|
+
/** Latest item-state anchor, absent for an empty stream. */
|
|
35
|
+
latest_after_hash: string | null;
|
|
36
|
+
/** Latest whole-record digest, absent for legacy or empty streams. */
|
|
37
|
+
latest_record_hash: string | null;
|
|
38
|
+
/** Algorithm used by the latest entry, absent for empty streams. */
|
|
39
|
+
latest_hash_algorithm: HistoryHashAlgorithm | null;
|
|
40
|
+
/** Number of retained rewrite evidence records across the stream. */
|
|
41
|
+
rewrite_count: number;
|
|
42
|
+
/** SHA-256 commitment to the pruned prefix, when a compact checkpoint exists. */
|
|
43
|
+
checkpoint_digest: string | null;
|
|
44
|
+
/** Last maintenance operation visible in this snapshot. */
|
|
45
|
+
last_maintenance: HistoryAttestationMaintenance | null;
|
|
46
|
+
}
|
|
47
|
+
/** Versioned detached proof; no item content, actors or filesystem roots are embedded. */
|
|
48
|
+
export interface HistoryAttestation {
|
|
49
|
+
/** Identifies the public wire format. */
|
|
50
|
+
format: typeof PM_HISTORY_ATTESTATION_CONTRACT.format;
|
|
51
|
+
/** Format revision governing canonicalization and verification. */
|
|
52
|
+
version: 1;
|
|
53
|
+
/** Algorithm for stream byte commitments and the workspace rollup. */
|
|
54
|
+
hash_algorithm: HistoryHashAlgorithm;
|
|
55
|
+
/** Export timestamp bound by the rollup; an assertion, not an external timestamp. */
|
|
56
|
+
generated_at: string;
|
|
57
|
+
/** Sorted complete inventory of retained history streams. */
|
|
58
|
+
streams: HistoryAttestationStream[];
|
|
59
|
+
/** Digest of every other field using deterministic stable JSON. */
|
|
60
|
+
workspace_digest: string;
|
|
61
|
+
}
|
|
62
|
+
/** Workspace selection without settings, extensions, locks or derived caches. */
|
|
63
|
+
export interface HistoryAttestationOptions {
|
|
64
|
+
/** Explicit tracker root containing the history directory. */
|
|
65
|
+
pmRoot?: string;
|
|
66
|
+
/** Directory used for ordinary tracker discovery when pmRoot is omitted. */
|
|
67
|
+
cwd?: string;
|
|
68
|
+
}
|
|
69
|
+
/** Optional reproducibility controls for proof export. */
|
|
70
|
+
export interface ExportAttestationOptions extends HistoryAttestationOptions {
|
|
71
|
+
/** Defaults to SHA-256; independent of each stream's record algorithm. */
|
|
72
|
+
hashAlgorithm?: HistoryHashAlgorithm;
|
|
73
|
+
/** RFC 3339 timestamp for reproducible exports; defaults to current time. */
|
|
74
|
+
generatedAt?: string;
|
|
75
|
+
}
|
|
76
|
+
/** Exact comparison against an independently retained proof. */
|
|
77
|
+
export interface HistoryAttestationVerification {
|
|
78
|
+
/** True only when all attested streams verify and the complete inventory matches. */
|
|
79
|
+
ok: boolean;
|
|
80
|
+
/** Rollup from the validated input bundle. */
|
|
81
|
+
workspace_digest: string;
|
|
82
|
+
/** Number of current streams inspected, including added streams. */
|
|
83
|
+
streams_checked: number;
|
|
84
|
+
/** Streams whose bytes differ, even when their rewritten chains remain valid. */
|
|
85
|
+
changed_streams: string[];
|
|
86
|
+
/** Attested streams absent from the current inventory. */
|
|
87
|
+
missing_streams: string[];
|
|
88
|
+
/** Current streams absent from the attested inventory. */
|
|
89
|
+
added_streams: string[];
|
|
90
|
+
/** Streams that cannot be decoded, read or verified. */
|
|
91
|
+
invalid_streams: string[];
|
|
92
|
+
/** Digest transitions with bounded maintenance context; never silently accepted as unchanged. */
|
|
93
|
+
transitions: Array<{
|
|
94
|
+
/** Changed stream identity. */
|
|
95
|
+
id: string;
|
|
96
|
+
/** Previously attested exact-byte digest. */
|
|
97
|
+
before_digest: string;
|
|
98
|
+
/** Current exact-byte digest, or null if unreadable. */
|
|
99
|
+
after_digest: string | null;
|
|
100
|
+
/** Current maintenance marker, useful when reviewing an intentional rewrite. */
|
|
101
|
+
last_maintenance: HistoryAttestationMaintenance | null;
|
|
102
|
+
}>;
|
|
103
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!(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]="63c96190-0df9-5803-aa61-b973493a7277")}catch(e2){}})();const PM_HISTORY_ATTESTATION_CONTRACT={format:"pm-history-attestation",version:1,stream_hash:"exact_file_bytes",rollup_hash:"stable_json_without_workspace_digest",ordering:"stream_id_code_unit_ascending",consistency:"per_stream_snapshot_with_change_detection",trust:"independently_retained_bundle"};export{PM_HISTORY_ATTESTATION_CONTRACT};
|
|
2
|
+
//# sourceMappingURL=attestation-contract.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type HistoryHashAlgorithm } from "../../core/history/digest.js";
|
|
2
|
+
import type { HistoryAttestationStream } from "./attestation-contract.js";
|
|
3
|
+
/** Snapshot row retained even when a stream is malformed or unreadable. */
|
|
4
|
+
export interface AttestationScanRow {
|
|
5
|
+
/** Filename stem from the complete directory inventory. */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Byte digest when reading succeeded. */
|
|
8
|
+
digest: string | null;
|
|
9
|
+
/** Verified payload-free stream summary, or null on invalid input. */
|
|
10
|
+
stream: HistoryAttestationStream | null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Capture all retained streams and reject observed concurrent changes. This is
|
|
14
|
+
* a per-stream snapshot, not a transaction across unrelated history writers.
|
|
15
|
+
*/
|
|
16
|
+
export declare function scanAttestationStreams(pmRoot: string, algorithm: HistoryHashAlgorithm): Promise<AttestationScanRow[]>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!(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]="d0001f37-7941-5cad-8950-2c6925056d10")}catch(e2){}})();import{createHash}from"node:crypto";import{isUtf8}from"node:buffer";import fs from"node:fs/promises";import path from"node:path";import{isFileMissingError}from"../../core/fs/fs-utils.js";import{resolveHistoryHashAlgorithm}from"../../core/history/digest.js";import{verifyHistoryChain}from"../../core/history/replay.js";import{stableStringify}from"../../core/shared/serialization.js";async function historyNames(directory){try{return(await fs.readdir(directory)).filter(name=>name.endsWith(".jsonl")).sort()}catch(error){if(isFileMissingError(error))return[];throw error}}function summarizeStream(id,entries,bytes,digest){const latest=entries.at(-1);let maintenance;for(let index=entries.length-1;index>=0;index-=1){if(["history_compact","history_compact_baseline","history_redact","history_repair","history_salvage","merge_reconcile"].includes(entries[index].op)){maintenance=entries[index];break}}const checkpoint=entries[0]?.context?.history_compaction;const checkpointDigest=typeof checkpoint==="object"&&checkpoint!==null&&"pruned_stream_digest"in checkpoint&&typeof checkpoint.pruned_stream_digest==="string"?checkpoint.pruned_stream_digest:null;return{id,entries:entries.length,bytes,stream_digest:digest,latest_after_hash:latest?.after_hash??null,latest_record_hash:latest?.record_hash??null,latest_hash_algorithm:latest?resolveHistoryHashAlgorithm(latest.hash_algorithm):null,rewrite_count:entries.reduce((count,entry)=>count+(entry.reanchor_evidence?.length??0),0),checkpoint_digest:checkpointDigest,last_maintenance:maintenance?{op:maintenance.op,ts:maintenance.ts,record_hash:maintenance.record_hash??null}:null}}function decodeAttestedStream(id,bytes,digest){try{if(!/^[A-Za-z0-9_-]+$/u.test(id))return null;if(!isUtf8(bytes))return null;const entries=[];for(const line of bytes.toString("utf8").split(/\r?\n/u)){if(!line.trim())continue;const entry=JSON.parse(line);if(typeof entry!=="object"||entry===null||Array.isArray(entry))return null;const record=entry;if(![record.op,record.author,record.ts,record.before_hash,record.after_hash].every(field=>typeof field==="string")||!Array.isArray(record.patch))return null;entries.push(record)}return verifyHistoryChain(entries).ok?summarizeStream(id,entries,bytes.length,digest):null}catch{return null}}async function scanAttestationStreams(pmRoot,algorithm){if(!(await fs.stat(pmRoot)).isDirectory())throw new TypeError("attestation tracker root must be a directory");const directory=path.join(pmRoot,"history");const names=await historyNames(directory);const observations=new Map;const rows=[];for(const name of names){const id=name.slice(0,-6);const row={id,digest:null,stream:null};rows.push(row);const filename=path.join(directory,name);try{const before=await fs.lstat(filename);observations.set(name,stableStringify([before.ino,before.size,before.mtimeMs,before.ctimeMs]));if(!before.isFile())continue;const bytes=await fs.readFile(filename);row.digest=createHash(algorithm).update(bytes).digest("hex");row.stream=decodeAttestedStream(id,bytes,row.digest)}catch{}}if(stableStringify(names)!==stableStringify(await historyNames(directory)))throw new TypeError("attestation history inventory changed during read; retry");for(const[name,observed]of observations){const after=await fs.lstat(path.join(directory,name));if(observed!==stableStringify([after.ino,after.size,after.mtimeMs,after.ctimeMs]))throw new TypeError("attestation history stream changed during read; retry")}return rows}export{scanAttestationStreams};
|
|
2
|
+
//# sourceMappingURL=attestation-scan.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ExportAttestationOptions, type HistoryAttestation, type HistoryAttestationOptions, type HistoryAttestationVerification } from "./attestation-contract.js";
|
|
2
|
+
export * from "./attestation-contract.js";
|
|
3
|
+
/** Validate untrusted JSON and its rollup before treating it as an expected commitment. */
|
|
4
|
+
export declare function parseHistoryAttestation(value: unknown): HistoryAttestation;
|
|
5
|
+
/** Export every retained item/workspace stream; refuse incomplete or invalid evidence. */
|
|
6
|
+
export declare function exportAttestation(options?: ExportAttestationOptions): Promise<HistoryAttestation>;
|
|
7
|
+
/** Compare exact bytes with a trusted detached bundle; append and maintenance changes remain visible. */
|
|
8
|
+
export declare function verifyAttestation(bundleInput: unknown, options?: HistoryAttestationOptions): Promise<HistoryAttestationVerification>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!(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]="0a4b3fc5-70bb-5ec0-82b5-a7cdd03c09ea")}catch(e2){}})();import{historyDigest,resolveHistoryHashAlgorithm}from"../../core/history/digest.js";import{stableStringify}from"../../core/shared/serialization.js";import{isMillisecondPrecisionRfc3339DateTime,nowIso}from"../../core/shared/time.js";import{resolvePmRoot}from"../../core/store/paths.js";import{scanAttestationStreams}from"./attestation-scan.js";import{PM_HISTORY_ATTESTATION_CONTRACT}from"./attestation-contract.js";export*from"./attestation-contract.js";function isObject(value){return typeof value==="object"&&value!==null&&!Array.isArray(value)}function hasFields(value,fields){return Object.keys(value).length===fields.length&&fields.every(field=>Object.hasOwn(value,field))}function isDigest(value,algorithm){return typeof value==="string"&&(algorithm==="sha256"?/^[a-f0-9]{64}$/u:/^[a-f0-9]{128}$/u).test(value)}function validStreamAnchors(value){if(value.entries===0){return[value.latest_hash_algorithm,value.latest_after_hash,value.latest_record_hash,value.checkpoint_digest,value.last_maintenance].every(field=>field===null)&&value.rewrite_count===0}if(value.latest_hash_algorithm!=="sha256"&&value.latest_hash_algorithm!=="sha512")return false;return isDigest(value.latest_after_hash,value.latest_hash_algorithm)&&(value.latest_record_hash===null||isDigest(value.latest_record_hash,value.latest_hash_algorithm))}function validMaintenance(value){if(value===null)return true;if(!isObject(value)||!hasFields(value,["op","ts","record_hash"]))return false;return typeof value.op==="string"&&["history_compact","history_compact_baseline","history_redact","history_repair","history_salvage","merge_reconcile"].includes(value.op)&&typeof value.ts==="string"&&isMillisecondPrecisionRfc3339DateTime(value.ts)&&(value.record_hash===null||isDigest(value.record_hash,"sha256")||isDigest(value.record_hash,"sha512"))}function isStream(value,algorithm){if(!isObject(value)||!hasFields(value,["id","entries","bytes","stream_digest","latest_after_hash","latest_record_hash","latest_hash_algorithm","rewrite_count","checkpoint_digest","last_maintenance"]))return false;return typeof value.id==="string"&&/^[A-Za-z0-9_-]+$/u.test(value.id)&&[value.entries,value.bytes,value.rewrite_count].every(count=>typeof count==="number"&&Number.isSafeInteger(count)&&count>=0)&&isDigest(value.stream_digest,algorithm)&&validStreamAnchors(value)&&(value.checkpoint_digest===null||isDigest(value.checkpoint_digest,"sha256"))&&validMaintenance(value.last_maintenance)}function parseHistoryAttestation(value){if(!isObject(value)||!hasFields(value,["format","version","hash_algorithm","generated_at","streams","workspace_digest"]))throw new TypeError("Invalid history attestation bundle");if(value.format!==PM_HISTORY_ATTESTATION_CONTRACT.format||value.version!==1||typeof value.generated_at!=="string"||!isMillisecondPrecisionRfc3339DateTime(value.generated_at)||!Array.isArray(value.streams)||value.hash_algorithm!=="sha256"&&value.hash_algorithm!=="sha512")throw new TypeError("Invalid history attestation format");const algorithm=value.hash_algorithm;let previous;for(const stream of value.streams){if(!isStream(stream,algorithm))throw new TypeError("Invalid history attestation stream");const id=stream.id;if(previous!==void 0&&id<=previous)throw new TypeError("Invalid history attestation stream order");previous=id}const{workspace_digest:expected,...payload}=value;if(historyDigest(stableStringify(payload),algorithm)!==expected)throw new TypeError("History attestation rollup mismatch");return value}async function exportAttestation(options={}){const algorithm=resolveHistoryHashAlgorithm(options.hashAlgorithm);const generatedAt=options.generatedAt??nowIso();if(!isMillisecondPrecisionRfc3339DateTime(generatedAt))throw new TypeError("Invalid history attestation timestamp");const rows=await scanAttestationStreams(resolvePmRoot(options.cwd??process.cwd(),options.pmRoot),algorithm);const invalid=rows.filter(row=>row.stream===null);if(invalid.length>0)throw new TypeError(`Cannot export history attestation: invalid streams ${invalid.map(row=>row.id).join(", ")}`);const payload={format:PM_HISTORY_ATTESTATION_CONTRACT.format,version:1,hash_algorithm:algorithm,generated_at:generatedAt,streams:rows.map(row=>row.stream)};return parseHistoryAttestation({...payload,workspace_digest:historyDigest(stableStringify(payload),algorithm)})}async function verifyAttestation(bundleInput,options={}){const bundle=parseHistoryAttestation(bundleInput);const rows=await scanAttestationStreams(resolvePmRoot(options.cwd??process.cwd(),options.pmRoot),bundle.hash_algorithm);const current=new Map(rows.map(row=>[row.id,row]));const expected=new Map(bundle.streams.map(stream=>[stream.id,stream]));const missing=bundle.streams.filter(stream=>!current.has(stream.id)).map(stream=>stream.id);const added=rows.filter(row=>!expected.has(row.id)).map(row=>row.id);const invalid=rows.filter(row=>row.stream===null).map(row=>row.id);const transitions=[];for(const stream of bundle.streams){const row=current.get(stream.id);if(row&&(row.digest!==stream.stream_digest||stableStringify(row.stream)!==stableStringify(stream)))transitions.push({id:stream.id,before_digest:stream.stream_digest,after_digest:row.digest,last_maintenance:row.stream?.last_maintenance??null})}return{ok:missing.length+added.length+invalid.length+transitions.length===0,workspace_digest:bundle.workspace_digest,streams_checked:rows.length,changed_streams:transitions.map(transition=>transition.id),missing_streams:missing,added_streams:added,invalid_streams:invalid,transitions}}export{exportAttestation,parseHistoryAttestation,verifyAttestation};
|
|
2
|
+
//# sourceMappingURL=attestation.js.map
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
!(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]="
|
|
1
|
+
!(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]="c1778165-2763-5eab-80f3-d52e08547fc2")}catch(e2){}})();import{isUtf8}from"node:buffer";import{resolveHistoryHashAlgorithm}from"../../core/history/digest.js";import{readFile}from"node:fs/promises";import{isFileMissingError,readFileIfExists}from"../../core/fs/fs-utils.js";import{acquireLock}from"../../core/lock/lock.js";import{createHistoryEntry,sealHistoryRecord}from"../../core/history/history.js";import{checkHistoryRewriteOwnership,writeHistoryRawWithRollback}from"../../core/history/history-rewrite.js";import{replayHistoryToTarget}from"../../core/history/projection.js";import{replayToItemDocument,verifyHistoryChainWithVersion}from"../../core/history/replay.js";import{findFirstMergeConflictMarker}from"../../core/shared/conflict-markers.js";import{EXIT_CODE}from"../../core/shared/constants.js";import{PmCliError}from"../../core/shared/errors.js";import{sha256Hex}from"../../core/shared/serialization.js";import{nowIso}from"../../core/shared/time.js";import{runActiveOnReadHooks,runActiveOnWriteHooks}from"../../core/extensions/index.js";import{locateItem,readLocatedItemSnapshot}from"../../core/store/item-store.js";function decodeSalvageLine(line){let parsed;try{parsed=JSON.parse(line)}catch{if(line.trimStart().startsWith("{")&&line.includes("}")){throw new PmCliError("History salvage cannot discard a potentially complete record with trailing corruption.",EXIT_CODE.CONFLICT,{code:"history_salvage_refused"})}return null}if(typeof parsed!=="object"||parsed===null||Array.isArray(parsed)){throw new PmCliError("History salvage would conceal interior corruption or a malformed record.",EXIT_CODE.CONFLICT,{code:"history_salvage_refused"})}const entry=parsed;if(!Array.isArray(entry.patch)||![entry.ts,entry.author,entry.op,entry.before_hash,entry.after_hash].every(field=>typeof field==="string")){throw new PmCliError("History salvage requires complete history records before the invalid tail.",EXIT_CODE.CONFLICT,{code:"history_salvage_refused"})}return entry}function inspectHistoryTail(raw){if(findFirstMergeConflictMarker(raw)){throw new PmCliError("Resolve history merge conflicts before salvage.",EXIT_CODE.CONFLICT,{code:"history_salvage_refused"})}const entries=[];let offset=0;let invalidOffset;let invalidLine=0;for(const[index,line]of raw.split("\n").entries()){if(!line.trim()){offset+=line.length+1;continue}const parsed=decodeSalvageLine(line);if(parsed===null){if(invalidOffset===void 0){invalidOffset=offset;invalidLine=index+1}offset+=line.length+1;continue}if(invalidOffset!==void 0){throw new PmCliError("History salvage would discard a record or conceal interior corruption.",EXIT_CODE.CONFLICT,{code:"history_salvage_refused",required:"Recover the damaged record from version control or a backup; no bytes were changed."})}entries.push(parsed);offset+=line.length+1}const verification=verifyHistoryChainWithVersion(entries);if(!verification.ok||verification.item_hash_version===void 0){throw new PmCliError("History salvage requires a nonempty verified prefix.",EXIT_CODE.CONFLICT,{code:"history_salvage_refused",verification_errors:verification.errors,required:"Recover an intact history prefix from version control or a backup."})}return{entries,itemHashVersion:verification.item_hash_version,prefix:invalidOffset===void 0?raw:raw.slice(0,invalidOffset),receipt:invalidOffset===void 0?null:{first_invalid_line:invalidLine,discarded_bytes:Buffer.byteLength(raw.slice(invalidOffset)),discarded_sha256:sha256Hex(raw.slice(invalidOffset)),retained_entries:entries.length}}}async function readSalvageOwnership(subject,settings,fallback){if(!subject.located)return{itemRaw:null,document:fallback};const snapshot=await readLocatedItemSnapshot(subject.located,{schema:settings.schema});return{itemRaw:snapshot.raw,document:snapshot.document??fallback}}async function readSalvageStream(historyPath){let bytes;try{bytes=await readFile(historyPath)}catch(error){if(isFileMissingError(error))return null;throw error}if(!isUtf8(bytes)){throw new PmCliError("History salvage requires UTF-8 input; recover binary corruption from a byte-preserving backup.",EXIT_CODE.CONFLICT,{code:"history_salvage_refused"})}return bytes.toString("utf8")}async function salvageHistoryTail(params){const{subject,settings,options,author}=params;if([options.normalizeProvenance,options.auditOperation,options.auditContext,options.auditContextById,options.forceAuditEntry,options.mergeReceiptProof,options.mergeReceiptProofById].some(Boolean)){throw new PmCliError("Tail salvage cannot be combined with other repair modes.",EXIT_CODE.USAGE)}const raw=await readSalvageStream(subject.historyPath);if(raw===null)throw new PmCliError(`No history stream exists for ${subject.id}.`,EXIT_CODE.NOT_FOUND);await runActiveOnReadHooks({path:subject.historyPath,scope:"project"});const selected=inspectHistoryTail(raw);const replay=replayHistoryToTarget(selected.entries,selected.entries.length-1);const document=replayToItemDocument(replay);if(typeof replay.metadata.id==="string"&&replay.metadata.id!==subject.id){throw new PmCliError("History belongs to another item; salvage refused.",EXIT_CODE.CONFLICT,{code:"history_salvage_refused"})}const ownership=await readSalvageOwnership(subject,settings,document);const warnings=checkHistoryRewriteOwnership({itemDocument:ownership.document,subjectId:subject.id,author,force:options.force,settings});const version=selected.itemHashVersion;const dryRun=options.dryRun===true;const changed=selected.receipt!==null;if(changed&&!dryRun){const release=await acquireLock(params.pmRoot,subject.id,settings.locks.ttl_seconds,author,Boolean(options.force),settings.governance.force_required_for_stale_lock,settings.locks.wait_ms);try{const currentRaw=await readSalvageStream(subject.historyPath);const located=await locateItem(params.pmRoot,subject.id,settings.id_prefix,settings.item_format,params.typeRegistry.type_to_folder);const currentItem=located?await readFileIfExists(located.itemPath):null;if(currentRaw!==raw||currentItem!==ownership.itemRaw){throw new PmCliError("History or item changed while waiting for salvage lock; retry.",EXIT_CODE.CONFLICT)}const audit=sealHistoryRecord({...createHistoryEntry({hashAlgorithm:resolveHistoryHashAlgorithm(selected.entries.at(-1).hash_algorithm),nowIso:nowIso(),author,op:"history_salvage",before:document,after:document,message:options.message,context:{history_salvage:selected.receipt}}),before_hash:selected.entries.at(-1).after_hash,after_hash:selected.entries.at(-1).after_hash,item_hash_version:version});await writeHistoryRawWithRollback({historyPath:subject.historyPath,historyRawUnderLock:raw,nextHistoryRaw:`${selected.prefix}${JSON.stringify(audit)}
|
|
2
2
|
`});warnings.push(...await runActiveOnWriteHooks({path:subject.historyPath,scope:"project",op:"history_salvage:history"}))}finally{await release()}}return{id:subject.id,dry_run:dryRun,changed,history:{path:subject.historyPath,entries_scanned:selected.entries.length,chain_drift_before:false,entries_rehashed:0,entries_patch_repaired:0,converted_replace_to_add:0,skipped_ops:0,reconciled_with_item:false,audit_entry_added:changed&&!dryRun,verify_ok:true,verify_errors:[],item_hash_version_before:version,item_hash_version_after:version,version_disposition:"preserved"},item:{exists:subject.located!==null,path:subject.located?.itemPath??null,matched_chain_before:null},provenance_normalization:{requested:false,changed:false,events_changed:0,observations_removed:0,invalid_values:[]},...selected.receipt?{salvage:selected.receipt}:{},warnings,generated_at:nowIso()}}export{inspectHistoryTail,salvageHistoryTail};
|
|
3
3
|
//# sourceMappingURL=salvage.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!(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]="d6824edc-ca22-5900-b43b-645135e2562d")}catch(e2){}})();import fs from"node:fs/promises";import path from"node:path";import{createHistoryEntry,hashHistoryStream,sealHistoryRecord,SUPPORTED_HISTORY_ITEM_HASH_VERSIONS}from"../core/history/history.js";import{selectHistoryCompactBulkTargets}from"../core/history/history-compact-bulk.js";import{cloneEmptyReplayDocument,replayHashVerificationCandidates,replayToItemDocument,toReplayDocument}from"../core/history/replay.js";import{applyHistoryPatch}from"../core/history/projection.js";import{historyVersionOffset}from"../core/history/version-address.js";import{lifecycleClassifierFromStatusRegistry}from"../core/governance/metadata-coverage.js";import{resolveRuntimeStatusRegistry}from"../core/schema/runtime-schema.js";import{listAllItemMetadataLight}from"../core/store/item-store.js";import{pathExists}from"../core/fs/fs-utils.js";import{normalizeBulkIdsValue,parseBulkIdsText}from"../core/io/bulk-ids-input.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{nowIso}from"../core/shared/time.js";import{runActiveOnReadHooks}from"../core/extensions/index.js";import{resolveHistoryWorkspace,resolveHistorySubject}from"./history/subject.js";import{runHistoryMaintenance}from"./history/maintenance.js";function parseBeforeBoundary(before,entries){if(before===void 0){return{kind:"default",raw:null,compactCount:entries.length,retainedCount:0}}const raw=before.trim();if(raw.length===0){throw new PmCliError("history-compact --before requires a non-empty value.",EXIT_CODE.USAGE)}if(/^\d+$/.test(raw)){const version=Number.parseInt(raw,10);const offset=historyVersionOffset(entries);if(!Number.isSafeInteger(version)||version<offset+1||version>offset+entries.length+1){throw new PmCliError(`history-compact --before version must be between ${offset+1} and ${offset+entries.length+1}.`,EXIT_CODE.USAGE)}const compactCount2=version-offset-1;return{kind:"version",raw,compactCount:compactCount2,retainedCount:entries.length-compactCount2}}const beforeTs=Date.parse(raw);if(!Number.isFinite(beforeTs)){throw new PmCliError(`Invalid history-compact --before value "${before}". Use a version number or ISO timestamp.`,EXIT_CODE.USAGE)}let compactCount=0;for(const[index,entry]of entries.entries()){const entryTs=Date.parse(entry.ts);if(!Number.isFinite(entryTs)){throw new PmCliError(`History for this item contains invalid timestamp at entry ${index+1}.`,EXIT_CODE.GENERIC_FAILURE)}if(entryTs>=beforeTs){break}compactCount+=1}return{kind:"timestamp",raw,compactCount,retainedCount:entries.length-compactCount}}function replayHistoryAndResolveCheckpoint(entries,compactCount){let replay=cloneEmptyReplayDocument();let checkpoint=cloneEmptyReplayDocument();for(let index=0;index<entries.length;index+=1){const entry=entries[index];replay=applyHistoryPatch(replay,entry.patch,index+1,entry.op);if(index+1===compactCount){checkpoint=structuredClone(replay)}}return{checkpoint,finalReplay:replay}}function describeHistoryCompactCurrentItem(loadedItem,historyEntries){if(!loadedItem){return{loadedItem:null,currentItemRawBeforeLock:null,matchedChainBefore:null}}const lastEntry=historyEntries[historyEntries.length-1];return{loadedItem,currentItemRawBeforeLock:loadedItem.raw,matchedChainBefore:SUPPORTED_HISTORY_ITEM_HASH_VERSIONS.some(version=>(lastEntry.item_hash_version===void 0||lastEntry.item_hash_version===version)&&replayHashVerificationCandidates(toReplayDocument(loadedItem.document),version).includes(lastEntry.after_hash))}}function buildHistoryCompactMessage(options,boundary){if(typeof options.message==="string"&&options.message.trim().length>0){return options.message}if(boundary.raw===null){return`history-compact compacted full stream (${boundary.compactCount} entr${boundary.compactCount===1?"y":"ies"}).`}return`history-compact compacted ${boundary.compactCount} entr${boundary.compactCount===1?"y":"ies"} before ${boundary.raw}.`}function buildHistoryCompactBaselineMessage(boundary){if(boundary.raw===null){return`history-compact baseline snapshot after compacting ${boundary.compactCount} entr${boundary.compactCount===1?"y":"ies"}.`}return`history-compact baseline snapshot before ${boundary.raw}.`}function buildHistoryCompactEntries(params){if(!params.changed){return{rewrittenEntries:params.historyEntries,baselineEntryAdded:false,auditEntryAdded:false}}const{checkpoint,finalReplay}=replayHistoryAndResolveCheckpoint(params.historyEntries,params.boundary.compactCount);const retained=params.historyEntries.slice(params.boundary.compactCount);const offset=historyVersionOffset(params.historyEntries,true);const baselineEntry=createHistoryEntry({nowIso:params.historyEntries[params.boundary.compactCount-1].ts,author:params.author,op:"history_compact_baseline",before:replayToItemDocument(cloneEmptyReplayDocument()),after:replayToItemDocument(checkpoint),message:buildHistoryCompactBaselineMessage(params.boundary),context:{history_compaction:{contract_version:1,version_offset:offset===null?null:offset+params.boundary.compactCount-1,checkpoint_timestamp_semantics:"last_compacted_entry",pruned_entry_count:params.boundary.compactCount,pruned_stream_digest:hashHistoryStream(params.historyEntries.slice(0,params.boundary.compactCount)),retained_proof:"stream_digest_and_checkpoint",limitation:"individual_pruned_entries_require_pre_compaction_stream"}}});const checkpointEntry=params.historyEntries[params.boundary.compactCount-1];const rewrittenEntries=[sealHistoryRecord({...baselineEntry,before_hash:params.historyEntries[0].before_hash,after_hash:checkpointEntry.after_hash,item_hash_version:checkpointEntry.item_hash_version}),...retained];if(!params.dryRun){rewrittenEntries.push(createHistoryEntry({nowIso:nowIso(),author:params.author,op:"history_compact",before:replayToItemDocument(finalReplay),after:replayToItemDocument(finalReplay),message:buildHistoryCompactMessage(params.options,params.boundary)}))}return{rewrittenEntries,baselineEntryAdded:true,auditEntryAdded:!params.dryRun}}async function runHistoryCompact(id,options,global2){const{pmRoot,settings,typeRegistry}=await resolveHistoryWorkspace(global2.path);const subject=await resolveHistorySubject(pmRoot,id,settings,typeRegistry.type_to_folder);return runHistoryMaintenance({pmRoot,subject,settings,typeRegistry,operation:"history-compact",options,transform:snapshot=>buildHistoryCompactPlan(subject,options,snapshot)})}function buildHistoryCompactPlan(subject,options,snapshot){const historyPath=subject.historyPath;const historyEntries=snapshot.entries;const chainVerification=snapshot.verification;if(!chainVerification.ok){throw new PmCliError(`history-compact requires a valid history chain (${chainVerification.errors.join(", ")}). Run pm history-repair ${subject.id} first.`,EXIT_CODE.CONFLICT)}const boundary=parseBeforeBoundary(options.before,historyEntries);const changed=boundary.compactCount>0;const dryRun=Boolean(options.dryRun);const author=snapshot.author;const warnings=[];if(!changed){warnings.push("history_compact_noop_before_boundary")}const currentItem=describeHistoryCompactCurrentItem(snapshot.loadedItem,historyEntries);if(currentItem.matchedChainBefore===false){warnings.push("history_compact_item_chain_mismatch")}const{rewrittenEntries,baselineEntryAdded,auditEntryAdded}=buildHistoryCompactEntries({historyEntries,boundary,changed,dryRun,author,options});const offset=historyVersionOffset(historyEntries,true);const firstRetainedEntry=boundary.retainedCount>0&&offset!==null?offset+boundary.compactCount+1:null;return{changed,rewrittenEntries,report:({verification:rewrittenVerify,warnings:writeWarnings})=>({id:subject.id,dry_run:dryRun,changed,compact_boundary:{kind:boundary.kind,before:boundary.raw,entries_compacted:boundary.compactCount,entries_retained:boundary.retainedCount,first_retained_entry:firstRetainedEntry},history:{path:historyPath,entries_scanned:historyEntries.length,entries_after:rewrittenEntries.length,baseline_entry_added:baselineEntryAdded,audit_entry_added:auditEntryAdded,verify_ok:rewrittenVerify.ok,verify_errors:rewrittenVerify.errors},item:{exists:subject.located!==null,path:subject.located?.itemPath??null,matched_chain_before:currentItem.matchedChainBefore},warnings:[...new Set([...warnings,...writeWarnings])].sort((left,right)=>left.localeCompare(right)),generated_at:nowIso()})}}const HISTORY_COMPACT_BULK_DEFAULT_MIN_ENTRIES=3;function normalizeHistoryCompactBulkOptions(options){for(const key of["allOver","minEntries"]){const value=options[key];if(value!==void 0&&(!Number.isSafeInteger(value)||value<0)){throw new PmCliError(`history-compact ${key} must be a non-negative safe integer.`,EXIT_CODE.USAGE)}}const normalizedIdsText=normalizeBulkIdsValue(options.ids);if(options.ids!==void 0&&normalizedIdsText===""){throw new PmCliError("history-compact ids requires at least one valid item ID.",EXIT_CODE.USAGE,{code:"bulk_ids_input_empty",required:"Provide one or more string or finite numeric IDs; non-finite and unsupported selector values are rejected."})}return{...options,ids:normalizedIdsText===void 0?void 0:parseBulkIdsText(normalizedIdsText)}}function assertHistoryCompactTarget(id,bulk){const hasIds=bulk.ids!==void 0&&bulk.ids.length>0;const hasScan=bulk.allOver!==void 0||bulk.scope!==void 0;const selectorCount=(id!==void 0?1:0)+(hasIds?1:0)+(hasScan?1:0);if(selectorCount===0){throw new PmCliError("history-compact: provide an item <id>, or a bulk selector (--ids, --all-over <N>, --closed, or --all-streams).",EXIT_CODE.USAGE)}if(id!==void 0&&(hasIds||hasScan)){throw new PmCliError("history-compact: <id> and bulk selectors (--ids/--all-over/--closed/--all-streams) are mutually exclusive; pass one item id, or use bulk mode without a positional id.",EXIT_CODE.USAGE)}if(hasIds&&hasScan){throw new PmCliError("history-compact: --ids is mutually exclusive with --all-over/--closed/--all-streams; pass an explicit id list or a scan selector, not both.",EXIT_CODE.USAGE)}}function countHistoryStreamEntries(raw){let count=0;for(const line of raw.split(/\r?\n/)){if(line.trim().length>0){count+=1}}return count}async function collectHistoryCompactBulkCandidates(params){const statusRegistry=resolveRuntimeStatusRegistry(params.settings.schema);const classifier=lifecycleClassifierFromStatusRegistry(statusRegistry);const items=await listAllItemMetadataLight(params.pmRoot,params.settings.item_format,params.typeToFolder,void 0,params.settings.schema);const bucketById=new Map(items.map(item=>[item.id,classifier.classify(item.status)]));const historyDir=path.join(params.pmRoot,"history");const candidates=[];const preselectionErrors=[];if(!await pathExists(historyDir)){return{candidates,preselectionErrors}}const historyFiles=(await fs.readdir(historyDir)).filter(entry=>entry.endsWith(".jsonl")).sort((left,right)=>left.localeCompare(right));for(const fileName of historyFiles){const historyPath=path.join(historyDir,fileName);const id=fileName.slice(0,-".jsonl".length);try{const raw=await fs.readFile(historyPath,"utf8");await runActiveOnReadHooks({path:historyPath,scope:"project"});candidates.push({id,entries:countHistoryStreamEntries(raw),bucket:bucketById.get(id)??null})}catch(error){preselectionErrors.push({id,outcome:"errored",entries_before:0,entries_after:null,skip_reason:null,changed:false,error:error instanceof Error?error.message:String(error)})}}return{candidates,preselectionErrors}}async function runHistoryCompactBulkRow(params){if(!params.row.selected){return{selected:false,compacted:false,errored:false,result:{id:params.row.id,outcome:"skipped",entries_before:params.row.entries,entries_after:null,skip_reason:params.row.skip_reason,changed:false,error:null}}}try{const result=await runHistoryCompact(params.row.id,{dryRun:params.dryRun,author:params.options.author,message:params.options.message,force:params.options.force},params.global);return{selected:true,compacted:true,errored:false,result:{id:params.row.id,outcome:"compacted",entries_before:params.row.entries,entries_after:result.history.entries_after,skip_reason:null,changed:result.changed,error:null}}}catch(error){return{selected:true,compacted:false,errored:true,result:{id:params.row.id,outcome:"errored",entries_before:params.row.entries,entries_after:null,skip_reason:null,changed:false,error:error instanceof Error?error.message:String(error)}}}}async function runHistoryCompactBulk(options,global2){const normalizedOptions=normalizeHistoryCompactBulkOptions(options);const{pmRoot,settings,typeRegistry}=await resolveHistoryWorkspace(global2.path);const{candidates,preselectionErrors}=await collectHistoryCompactBulkCandidates({pmRoot,settings,typeToFolder:typeRegistry.type_to_folder});const mode=normalizedOptions.ids!==void 0&&normalizedOptions.ids.length>0?"ids":"scan";const minEntries=options.minEntries??HISTORY_COMPACT_BULK_DEFAULT_MIN_ENTRIES;const policy=settings.history.compact_policy;const policyThresholdApplied=mode==="scan"&&options.allOver===void 0&&policy.enabled;const allOver=options.allOver??(policyThresholdApplied?policy.max_entries:void 0);const selection=selectHistoryCompactBulkTargets(candidates,{ids:normalizedOptions.ids,scope:options.scope,minEntries,allOver});const dryRun=Boolean(options.dryRun);const results=[...preselectionErrors];const totals={streams_considered:candidates.length+preselectionErrors.length,selected:0,items_compacted:0,items_skipped:0,items_errored:preselectionErrors.length};for(const row of selection){const outcome=await runHistoryCompactBulkRow({row,options:normalizedOptions,dryRun,global:global2});results.push(outcome.result);if(!outcome.selected){totals.items_skipped+=1;continue}totals.selected+=1;if(outcome.compacted){totals.items_compacted+=1}if(outcome.errored){totals.items_errored+=1}}return{bulk:true,dry_run:dryRun,mode,scope:options.scope??null,criteria:{min_entries:minEntries,all_over:allOver??null,policy_threshold_applied:policyThresholdApplied},totals,results,generated_at:nowIso()}}export{HISTORY_COMPACT_BULK_DEFAULT_MIN_ENTRIES,assertHistoryCompactTarget,runHistoryCompact,runHistoryCompactBulk};
|
|
1
|
+
!(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]="1dede7b7-eeea-520d-97ce-a71687d93bc2")}catch(e2){}})();import fs from"node:fs/promises";import{resolveHistoryHashAlgorithm}from"../core/history/digest.js";import path from"node:path";import{createHistoryEntry,hashHistoryStream,sealHistoryRecord,SUPPORTED_HISTORY_ITEM_HASH_VERSIONS}from"../core/history/history.js";import{selectHistoryCompactBulkTargets}from"../core/history/history-compact-bulk.js";import{cloneEmptyReplayDocument,replayHashVerificationCandidates,replayToItemDocument,toReplayDocument}from"../core/history/replay.js";import{applyHistoryPatch}from"../core/history/projection.js";import{historyVersionOffset}from"../core/history/version-address.js";import{lifecycleClassifierFromStatusRegistry}from"../core/governance/metadata-coverage.js";import{resolveRuntimeStatusRegistry}from"../core/schema/runtime-schema.js";import{listAllItemMetadataLight}from"../core/store/item-store.js";import{pathExists}from"../core/fs/fs-utils.js";import{normalizeBulkIdsValue,parseBulkIdsText}from"../core/io/bulk-ids-input.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{nowIso}from"../core/shared/time.js";import{runActiveOnReadHooks}from"../core/extensions/index.js";import{resolveHistoryWorkspace,resolveHistorySubject}from"./history/subject.js";import{runHistoryMaintenance}from"./history/maintenance.js";function parseBeforeBoundary(before,entries){if(before===void 0){return{kind:"default",raw:null,compactCount:entries.length,retainedCount:0}}const raw=before.trim();if(raw.length===0){throw new PmCliError("history-compact --before requires a non-empty value.",EXIT_CODE.USAGE)}if(/^\d+$/.test(raw)){const version=Number.parseInt(raw,10);const offset=historyVersionOffset(entries);if(!Number.isSafeInteger(version)||version<offset+1||version>offset+entries.length+1){throw new PmCliError(`history-compact --before version must be between ${offset+1} and ${offset+entries.length+1}.`,EXIT_CODE.USAGE)}const compactCount2=version-offset-1;return{kind:"version",raw,compactCount:compactCount2,retainedCount:entries.length-compactCount2}}const beforeTs=Date.parse(raw);if(!Number.isFinite(beforeTs)){throw new PmCliError(`Invalid history-compact --before value "${before}". Use a version number or ISO timestamp.`,EXIT_CODE.USAGE)}let compactCount=0;for(const[index,entry]of entries.entries()){const entryTs=Date.parse(entry.ts);if(!Number.isFinite(entryTs)){throw new PmCliError(`History for this item contains invalid timestamp at entry ${index+1}.`,EXIT_CODE.GENERIC_FAILURE)}if(entryTs>=beforeTs){break}compactCount+=1}return{kind:"timestamp",raw,compactCount,retainedCount:entries.length-compactCount}}function replayHistoryAndResolveCheckpoint(entries,compactCount){let replay=cloneEmptyReplayDocument();let checkpoint=cloneEmptyReplayDocument();for(let index=0;index<entries.length;index+=1){const entry=entries[index];replay=applyHistoryPatch(replay,entry.patch,index+1,entry.op);if(index+1===compactCount){checkpoint=structuredClone(replay)}}return{checkpoint,finalReplay:replay}}function describeHistoryCompactCurrentItem(loadedItem,historyEntries){if(!loadedItem){return{loadedItem:null,currentItemRawBeforeLock:null,matchedChainBefore:null}}const lastEntry=historyEntries[historyEntries.length-1];return{loadedItem,currentItemRawBeforeLock:loadedItem.raw,matchedChainBefore:SUPPORTED_HISTORY_ITEM_HASH_VERSIONS.some(version=>(lastEntry.item_hash_version===void 0||lastEntry.item_hash_version===version)&&replayHashVerificationCandidates(toReplayDocument(loadedItem.document),version,resolveHistoryHashAlgorithm(lastEntry.hash_algorithm)).includes(lastEntry.after_hash))}}function buildHistoryCompactMessage(options,boundary){if(typeof options.message==="string"&&options.message.trim().length>0){return options.message}if(boundary.raw===null){return`history-compact compacted full stream (${boundary.compactCount} entr${boundary.compactCount===1?"y":"ies"}).`}return`history-compact compacted ${boundary.compactCount} entr${boundary.compactCount===1?"y":"ies"} before ${boundary.raw}.`}function buildHistoryCompactBaselineMessage(boundary){if(boundary.raw===null){return`history-compact baseline snapshot after compacting ${boundary.compactCount} entr${boundary.compactCount===1?"y":"ies"}.`}return`history-compact baseline snapshot before ${boundary.raw}.`}function buildHistoryCompactEntries(params){if(!params.changed){return{rewrittenEntries:params.historyEntries,baselineEntryAdded:false,auditEntryAdded:false}}const{checkpoint,finalReplay}=replayHistoryAndResolveCheckpoint(params.historyEntries,params.boundary.compactCount);const retained=params.historyEntries.slice(params.boundary.compactCount);const offset=historyVersionOffset(params.historyEntries,true);const checkpointEntry=params.historyEntries[params.boundary.compactCount-1];const baselineEntry=createHistoryEntry({hashAlgorithm:resolveHistoryHashAlgorithm(checkpointEntry.hash_algorithm),nowIso:params.historyEntries[params.boundary.compactCount-1].ts,author:params.author,op:"history_compact_baseline",before:replayToItemDocument(cloneEmptyReplayDocument()),after:replayToItemDocument(checkpoint),message:buildHistoryCompactBaselineMessage(params.boundary),context:{history_compaction:{contract_version:1,version_offset:offset===null?null:offset+params.boundary.compactCount-1,checkpoint_timestamp_semantics:"last_compacted_entry",pruned_entry_count:params.boundary.compactCount,pruned_stream_digest:hashHistoryStream(params.historyEntries.slice(0,params.boundary.compactCount)),retained_proof:"stream_digest_and_checkpoint",limitation:"individual_pruned_entries_require_pre_compaction_stream"}}});const rewrittenEntries=[sealHistoryRecord({...baselineEntry,before_hash:replayHashVerificationCandidates(cloneEmptyReplayDocument(),checkpointEntry.item_hash_version??1,resolveHistoryHashAlgorithm(checkpointEntry.hash_algorithm))[0],after_hash:checkpointEntry.after_hash,item_hash_version:checkpointEntry.item_hash_version}),...retained];if(!params.dryRun){rewrittenEntries.push(createHistoryEntry({nowIso:nowIso(),author:params.author,op:"history_compact",before:replayToItemDocument(finalReplay),after:replayToItemDocument(finalReplay),message:buildHistoryCompactMessage(params.options,params.boundary)}))}return{rewrittenEntries,baselineEntryAdded:true,auditEntryAdded:!params.dryRun}}async function runHistoryCompact(id,options,global2){const{pmRoot,settings,typeRegistry}=await resolveHistoryWorkspace(global2.path);const subject=await resolveHistorySubject(pmRoot,id,settings,typeRegistry.type_to_folder);return runHistoryMaintenance({pmRoot,subject,settings,typeRegistry,operation:"history-compact",options,transform:snapshot=>buildHistoryCompactPlan(subject,options,snapshot)})}function buildHistoryCompactPlan(subject,options,snapshot){const historyPath=subject.historyPath;const historyEntries=snapshot.entries;const chainVerification=snapshot.verification;if(!chainVerification.ok){throw new PmCliError(`history-compact requires a valid history chain (${chainVerification.errors.join(", ")}). Run pm history-repair ${subject.id} first.`,EXIT_CODE.CONFLICT)}const boundary=parseBeforeBoundary(options.before,historyEntries);const changed=boundary.compactCount>0;const dryRun=Boolean(options.dryRun);const author=snapshot.author;const warnings=[];if(!changed){warnings.push("history_compact_noop_before_boundary")}const currentItem=describeHistoryCompactCurrentItem(snapshot.loadedItem,historyEntries);if(currentItem.matchedChainBefore===false){warnings.push("history_compact_item_chain_mismatch")}const{rewrittenEntries,baselineEntryAdded,auditEntryAdded}=buildHistoryCompactEntries({historyEntries,boundary,changed,dryRun,author,options});const offset=historyVersionOffset(historyEntries,true);const firstRetainedEntry=boundary.retainedCount>0&&offset!==null?offset+boundary.compactCount+1:null;return{changed,rewrittenEntries,report:({verification:rewrittenVerify,warnings:writeWarnings})=>({id:subject.id,dry_run:dryRun,changed,compact_boundary:{kind:boundary.kind,before:boundary.raw,entries_compacted:boundary.compactCount,entries_retained:boundary.retainedCount,first_retained_entry:firstRetainedEntry},history:{path:historyPath,entries_scanned:historyEntries.length,entries_after:rewrittenEntries.length,baseline_entry_added:baselineEntryAdded,audit_entry_added:auditEntryAdded,verify_ok:rewrittenVerify.ok,verify_errors:rewrittenVerify.errors},item:{exists:subject.located!==null,path:subject.located?.itemPath??null,matched_chain_before:currentItem.matchedChainBefore},warnings:[...new Set([...warnings,...writeWarnings])].sort((left,right)=>left.localeCompare(right)),generated_at:nowIso()})}}const HISTORY_COMPACT_BULK_DEFAULT_MIN_ENTRIES=3;function normalizeHistoryCompactBulkOptions(options){for(const key of["allOver","minEntries"]){const value=options[key];if(value!==void 0&&(!Number.isSafeInteger(value)||value<0)){throw new PmCliError(`history-compact ${key} must be a non-negative safe integer.`,EXIT_CODE.USAGE)}}const normalizedIdsText=normalizeBulkIdsValue(options.ids);if(options.ids!==void 0&&normalizedIdsText===""){throw new PmCliError("history-compact ids requires at least one valid item ID.",EXIT_CODE.USAGE,{code:"bulk_ids_input_empty",required:"Provide one or more string or finite numeric IDs; non-finite and unsupported selector values are rejected."})}return{...options,ids:normalizedIdsText===void 0?void 0:parseBulkIdsText(normalizedIdsText)}}function assertHistoryCompactTarget(id,bulk){const hasIds=bulk.ids!==void 0&&bulk.ids.length>0;const hasScan=bulk.allOver!==void 0||bulk.scope!==void 0;const selectorCount=(id!==void 0?1:0)+(hasIds?1:0)+(hasScan?1:0);if(selectorCount===0){throw new PmCliError("history-compact: provide an item <id>, or a bulk selector (--ids, --all-over <N>, --closed, or --all-streams).",EXIT_CODE.USAGE)}if(id!==void 0&&(hasIds||hasScan)){throw new PmCliError("history-compact: <id> and bulk selectors (--ids/--all-over/--closed/--all-streams) are mutually exclusive; pass one item id, or use bulk mode without a positional id.",EXIT_CODE.USAGE)}if(hasIds&&hasScan){throw new PmCliError("history-compact: --ids is mutually exclusive with --all-over/--closed/--all-streams; pass an explicit id list or a scan selector, not both.",EXIT_CODE.USAGE)}}function countHistoryStreamEntries(raw){let count=0;for(const line of raw.split(/\r?\n/)){if(line.trim().length>0){count+=1}}return count}async function collectHistoryCompactBulkCandidates(params){const statusRegistry=resolveRuntimeStatusRegistry(params.settings.schema);const classifier=lifecycleClassifierFromStatusRegistry(statusRegistry);const items=await listAllItemMetadataLight(params.pmRoot,params.settings.item_format,params.typeToFolder,void 0,params.settings.schema);const bucketById=new Map(items.map(item=>[item.id,classifier.classify(item.status)]));const historyDir=path.join(params.pmRoot,"history");const candidates=[];const preselectionErrors=[];if(!await pathExists(historyDir)){return{candidates,preselectionErrors}}const historyFiles=(await fs.readdir(historyDir)).filter(entry=>entry.endsWith(".jsonl")).sort((left,right)=>left.localeCompare(right));for(const fileName of historyFiles){const historyPath=path.join(historyDir,fileName);const id=fileName.slice(0,-".jsonl".length);try{const raw=await fs.readFile(historyPath,"utf8");await runActiveOnReadHooks({path:historyPath,scope:"project"});candidates.push({id,entries:countHistoryStreamEntries(raw),bucket:bucketById.get(id)??null})}catch(error){preselectionErrors.push({id,outcome:"errored",entries_before:0,entries_after:null,skip_reason:null,changed:false,error:error instanceof Error?error.message:String(error)})}}return{candidates,preselectionErrors}}async function runHistoryCompactBulkRow(params){if(!params.row.selected){return{selected:false,compacted:false,errored:false,result:{id:params.row.id,outcome:"skipped",entries_before:params.row.entries,entries_after:null,skip_reason:params.row.skip_reason,changed:false,error:null}}}try{const result=await runHistoryCompact(params.row.id,{dryRun:params.dryRun,author:params.options.author,message:params.options.message,force:params.options.force},params.global);return{selected:true,compacted:true,errored:false,result:{id:params.row.id,outcome:"compacted",entries_before:params.row.entries,entries_after:result.history.entries_after,skip_reason:null,changed:result.changed,error:null}}}catch(error){return{selected:true,compacted:false,errored:true,result:{id:params.row.id,outcome:"errored",entries_before:params.row.entries,entries_after:null,skip_reason:null,changed:false,error:error instanceof Error?error.message:String(error)}}}}async function runHistoryCompactBulk(options,global2){const normalizedOptions=normalizeHistoryCompactBulkOptions(options);const{pmRoot,settings,typeRegistry}=await resolveHistoryWorkspace(global2.path);const{candidates,preselectionErrors}=await collectHistoryCompactBulkCandidates({pmRoot,settings,typeToFolder:typeRegistry.type_to_folder});const mode=normalizedOptions.ids!==void 0&&normalizedOptions.ids.length>0?"ids":"scan";const minEntries=options.minEntries??HISTORY_COMPACT_BULK_DEFAULT_MIN_ENTRIES;const policy=settings.history.compact_policy;const policyThresholdApplied=mode==="scan"&&options.allOver===void 0&&policy.enabled;const allOver=options.allOver??(policyThresholdApplied?policy.max_entries:void 0);const selection=selectHistoryCompactBulkTargets(candidates,{ids:normalizedOptions.ids,scope:options.scope,minEntries,allOver});const dryRun=Boolean(options.dryRun);const results=[...preselectionErrors];const totals={streams_considered:candidates.length+preselectionErrors.length,selected:0,items_compacted:0,items_skipped:0,items_errored:preselectionErrors.length};for(const row of selection){const outcome=await runHistoryCompactBulkRow({row,options:normalizedOptions,dryRun,global:global2});results.push(outcome.result);if(!outcome.selected){totals.items_skipped+=1;continue}totals.selected+=1;if(outcome.compacted){totals.items_compacted+=1}if(outcome.errored){totals.items_errored+=1}}return{bulk:true,dry_run:dryRun,mode,scope:options.scope??null,criteria:{min_entries:minEntries,all_over:allOver??null,policy_threshold_applied:policyThresholdApplied},totals,results,generated_at:nowIso()}}export{HISTORY_COMPACT_BULK_DEFAULT_MIN_ENTRIES,assertHistoryCompactTarget,runHistoryCompact,runHistoryCompactBulk};
|
|
2
2
|
//# sourceMappingURL=history-compact.js.map
|
package/dist/sdk/history-read.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!(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]="
|
|
1
|
+
!(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]="b6ef0b23-e861-50ec-b7b3-751863936631")}catch(e2){}})();import{resolveHistoryHashAlgorithm}from"../core/history/digest.js";import{assertInitializedTracker}from"./environment/tracker-preflight.js";import{getActiveExtensionRegistrations}from"../core/extensions/index.js";import{applyHistoryPatch,ensureMaterializedHistoryTarget,extractPatchFailureContext,replayHistoryToTarget,resolveHistoryTarget}from"../core/history/projection.js";import{readHistoryEntries}from"../core/history/read.js";import{historyVersionOffset}from"../core/history/version-address.js";import{hashDocument,hashDocumentVerificationCandidates,hashEmptyDocument}from"../core/history/history.js";import{replayToCanonicalItemDocument,verifyHistoryChainWithVersion}from"../core/history/replay.js";import{resolveItemTypeRegistry}from"../core/item/type-registry.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{resolvePmRoot}from"../core/store/paths.js";import{readSettings}from"../core/store/settings.js";import{resolveHistorySubject}from"./history/subject.js";import{findHistoryIdentityDiscontinuities}from"../core/history/identity.js";function verifyHistoryEntries(history,currentDocument){const verification=verifyHistoryChainWithVersion(history);const latestAfterHash=history.length>0?history[history.length-1].after_hash:hashEmptyDocument();const currentItemHashCandidates=currentDocument===void 0||verification.item_hash_version===void 0?void 0:hashDocumentVerificationCandidates(currentDocument,verification.item_hash_version,resolveHistoryHashAlgorithm(history.at(-1)?.hash_algorithm));const currentItemHash=currentDocument===void 0?void 0:currentItemHashCandidates===void 0?hashDocument(currentDocument):currentItemHashCandidates[Math.max(0,currentItemHashCandidates.indexOf(latestAfterHash))];const currentMatchesLatest=currentItemHash===void 0?void 0:currentItemHash===latestAfterHash;const errors=[...verification.errors];if(currentMatchesLatest===false){errors.push("verify_failed:current_item_hash_mismatch")}return{ok:errors.length===0,entries:history.length,errors,latest_after_hash:history.length>0?history[history.length-1].after_hash:void 0,current_item_hash:currentItemHash,current_matches_latest:currentMatchesLatest}}async function getItemAt(id,target,options={}){const pmRoot=resolvePmRoot(options.cwd??process.cwd(),options.pmRoot);await assertInitializedTracker(pmRoot);const settings=await readSettings(pmRoot);const typeRegistry=resolveItemTypeRegistry(settings,getActiveExtensionRegistrations());const subject=await resolveHistorySubject(pmRoot,id,settings,typeRegistry.type_to_folder);const history=await readHistoryEntries(subject.historyPath,subject.id);if(history.length===0){throw new PmCliError(`No history exists for ${subject.id}; point-in-time reads are unavailable.`,EXIT_CODE.NOT_FOUND)}const resolvedTarget=resolveHistoryTarget(target,history);const replay=ensureMaterializedHistoryTarget(replayHistoryToTarget(history,resolvedTarget.historyIndex),resolvedTarget);const document=replayToCanonicalItemDocument(replay,{schema:settings.schema});if(document.metadata.id!==subject.id){throw new PmCliError(`History target resolved to item ${document.metadata.id}, expected ${subject.id}.`,EXIT_CODE.GENERIC_FAILURE)}const targetEntry=history[resolvedTarget.historyIndex];const offset=historyVersionOffset(history,true);return{document,reconstructed:true,as_of_version:offset===null?null:offset+resolvedTarget.historyIndex+1,as_of_timestamp:targetEntry.ts,target:resolvedTarget,history_length:history.length}}export{applyHistoryPatch,ensureMaterializedHistoryTarget,extractPatchFailureContext,findHistoryIdentityDiscontinuities,getItemAt,readHistoryEntries,replayHistoryToTarget,resolveHistoryTarget,verifyHistoryEntries};
|
|
2
2
|
//# sourceMappingURL=history-read.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!(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]="d562ab68-1891-54cc-8256-57cc1121b9ce")}catch(e2){}})();import{resolveHistoryWorkspace,resolveHistorySubject}from"./history/subject.js";import{resolveHistorySubject as resolveHistorySubject2}from"./history/subject.js";import fs from"node:fs/promises";import{writeFileAtomic}from"../core/fs/fs-utils.js";import{createHistoryEntry,resealHistoryRewrite,verifyHistoryRecordHash}from"../core/history/history.js";import{invalidateHistoryDriftCacheForPath}from"../core/history/drift-cache.js";import{runHistoryMaintenance}from"./history/maintenance.js";import{EMPTY_REPLAY_DOCUMENT,historyEntriesToRaw,replayHash,replayToItemDocument}from"../core/history/replay.js";import{applyHistoryPatch}from"../core/history/projection.js";import{canonicalDocument,serializeItemDocument}from"../core/item/item-format.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{nowIso}from"../core/shared/time.js";import{runActiveOnWriteHooks}from"../core/extensions/index.js";import{acquireItemMetadataDerivedIndexLock,refreshItemMetadataDerivedIndex}from"../core/store/item-metadata-cache.js";import{getItemPath}from"../core/store/paths.js";function normalizeStringArrayInput(value){if(Array.isArray(value)){return value}if(typeof value==="string"){return[value]}return[]}function normalizeRegexFlags(flags){const unique=[];for(const token of flags){if(!unique.includes(token)){unique.push(token)}}if(!unique.includes("g")){unique.push("g")}return unique.join("")}function parseRegexRule(spec){const trimmed=spec.trim();if(trimmed.length===0){throw new PmCliError("history-redact --regex requires a non-empty pattern.",EXIT_CODE.USAGE)}let source=trimmed;let flags="g";if(trimmed.startsWith("/")&&trimmed.length>1){const slashIndex=trimmed.lastIndexOf("/");if(slashIndex>0){source=trimmed.slice(1,slashIndex);flags=normalizeRegexFlags(trimmed.slice(slashIndex+1))}}if(source.length===0){throw new PmCliError("history-redact --regex cannot use an empty pattern.",EXIT_CODE.USAGE)}try{new RegExp(source,flags)}catch{throw new PmCliError("Invalid --regex value; provide a valid JavaScript regular expression.",EXIT_CODE.USAGE)}return{kind:"regex",source,flags}}function buildRedactionRules(literalInput,regexInput){const literalRules=[...new Set(normalizeStringArrayInput(literalInput).map(entry=>entry.trim()))].filter(entry=>entry.length>0).map(entry=>({kind:"literal",value:entry}));const regexRules=[...new Set(normalizeStringArrayInput(regexInput).map(entry=>entry.trim()))].filter(entry=>entry.length>0).map(parseRegexRule);const rules=[...literalRules,...regexRules];if(rules.length===0){throw new PmCliError("history-redact requires at least one matcher via --literal or --regex.",EXIT_CODE.USAGE,{code:"missing_required_argument",required:"Provide --literal <value> and/or --regex <pattern>.",examples:['pm history-redact pm-a1b2 --literal "[redacted_path_prefix]/private"','pm history-redact pm-a1b2 --regex "/192\\\\.168\\\\.[0-9.]+/g" --replacement "[scrubbed_ip]"']})}return rules}function applyLiteralRule(value,literal,replacement){if(literal.length===0){return{value,replacements:0}}let cursor=0;let replacements=0;while(cursor<=value.length){const index=value.indexOf(literal,cursor);if(index===-1){break}replacements+=1;cursor=index+Math.max(1,literal.length)}if(replacements===0){return{value,replacements:0}}return{value:value.split(literal).join(replacement),replacements}}function applyRegexRule(value,rule,replacement){const regex=new RegExp(rule.source,rule.flags);const matches=[...value.matchAll(regex)];if(matches.length===0){return{value,replacements:0}}return{value:value.replace(regex,replacement),replacements:matches.length}}function redactStringValue(value,rules,replacement){let next=value;let replacements=0;for(const rule of rules){const result=rule.kind==="literal"?applyLiteralRule(next,rule.value,replacement):applyRegexRule(next,rule,replacement);next=result.value;replacements+=result.replacements}return{value:next,replacements}}function redactUnknownValue(value,rules,replacement){if(typeof value==="string"){return redactStringValue(value,rules,replacement)}if(Array.isArray(value)){let replacements=0;const nextValues=value.map(entry=>{const redacted=redactUnknownValue(entry,rules,replacement);replacements+=redacted.replacements;return redacted.value});return{value:nextValues,replacements}}if(typeof value==="object"&&value!==null){let replacements=0;const nextRecord={};for(const[key,entry]of Object.entries(value)){const redacted=redactUnknownValue(entry,rules,replacement);replacements+=redacted.replacements;nextRecord[key]=redacted.value}return{value:nextRecord,replacements}}return{value,replacements:0}}function redactHistoryPatch(patch,rules,replacement){let replacements=0;const redactedPatch=patch.map(operation=>{if(!Object.prototype.hasOwnProperty.call(operation,"value")){return operation}const redactedValue=redactUnknownValue(operation.value,rules,replacement);replacements+=redactedValue.replacements;return redactedValue.replacements>0?{...operation,value:redactedValue.value}:operation});return{patch:redactedPatch,replacements}}function inspectHistoryIntegrity(entries){let replay=structuredClone(EMPTY_REPLAY_DOCUMENT);let hashMismatchesBefore=0;let hashMismatchesAfter=0;for(let index=0;index<entries.length;index+=1){const entry=entries[index];if(replayHash(replay)!==entry.before_hash){hashMismatchesBefore+=1}replay=applyHistoryPatch(replay,entry.patch,index+1,entry.op);if(replayHash(replay)!==entry.after_hash){hashMismatchesAfter+=1}}return{hashMismatchesBefore,hashMismatchesAfter,finalDocument:replay}}function redactHistoryEntry(entry,rules,replacement){let replacements=0;let changed=false;let reanchorEvidenceRedacted=false;let nextMessage=entry.message;if(typeof entry.message==="string"){const redactedMessage=redactStringValue(entry.message,rules,replacement);nextMessage=redactedMessage.value;replacements+=redactedMessage.replacements;if(redactedMessage.replacements>0){changed=true}}const redactedPatch=redactHistoryPatch(entry.patch,rules,replacement);replacements+=redactedPatch.replacements;changed||=redactedPatch.replacements>0;const nextReanchorEvidence=entry.reanchor_evidence?.map(evidence=>{const redactedEvidencePatch=evidence.patch?redactHistoryPatch(evidence.patch,rules,replacement):{patch:void 0,replacements:0};const redactedEvidenceRecord=evidence.record?redactUnknownValue(evidence.record,rules,replacement):{value:void 0,replacements:0};replacements+=redactedEvidencePatch.replacements;replacements+=redactedEvidenceRecord.replacements;const evidenceChanged=redactedEvidencePatch.replacements>0||redactedEvidenceRecord.replacements>0;changed||=evidenceChanged;if(!evidenceChanged)return evidence;reanchorEvidenceRedacted=true;const{patch:_sensitivePatch,record:_sensitiveRecord,record_hash:_priorRecordHash,record_hash_version:_priorRecordHashVersion,...digestOnlyEvidence}=evidence;return redactedEvidencePatch.replacements>0?digestOnlyEvidence:{...digestOnlyEvidence,patch:evidence.patch}});return{entry:{...entry,message:nextMessage,patch:redactedPatch.patch,...nextReanchorEvidence?{reanchor_evidence:nextReanchorEvidence}:{}},replacements,changed,reanchorEvidenceRedacted}}function rewriteHistoryEntries(entries,rules,replacement){let replay=structuredClone(EMPTY_REPLAY_DOCUMENT);let entriesChanged=0;let replacements=0;const rewrittenEntries=[];for(let index=0;index<entries.length;index+=1){const original=entries[index];const recordVerification=verifyHistoryRecordHash(original);if(!recordVerification.ok){throw new PmCliError(`history-redact refuses invalid immutable record ${index+1} (${recordVerification.error}).`,EXIT_CODE.GENERIC_FAILURE)}const redacted=redactHistoryEntry(original,rules,replacement);replacements+=redacted.replacements;if(redacted.changed){entriesChanged+=1}const beforeHash=replayHash(replay);replay=applyHistoryPatch(replay,redacted.entry.patch,index+1,redacted.entry.op);const afterHash=replayHash(replay);rewrittenEntries.push(resealHistoryRewrite(original,{...redacted.entry,before_hash:beforeHash,after_hash:afterHash},{retainPriorRecord:redacted.changed,retainPriorRecordHash:!redacted.changed}))}return{entries:rewrittenEntries,finalDocument:replay,entriesChanged,replacements}}function hasItemMetadata(replay){return Object.keys(replay.metadata).length>0}function resolveHistoryRedactNextItem(params){if(!hasItemMetadata(params.finalDocument)){return{raw:null,path:null,document:null}}const canonical=canonicalDocument(replayToItemDocument(params.finalDocument),{schema:params.settings.schema});if(canonical.metadata.id!==params.subject.id){throw new PmCliError("history-redact would change the item id; narrow your patterns.",EXIT_CODE.USAGE)}return{document:canonical,path:getItemPath(params.pmRoot,canonical.metadata.type,params.subject.id,"toon",params.typeToFolder),raw:serializeItemDocument(canonical,{format:"toon",schema:params.settings.schema})}}function buildHistoryRedactMessage(options,rewritten){if(typeof options.message==="string"&&options.message.trim().length>0){return options.message}return`history-redact replaced ${rewritten.replacements} match(es) across ${rewritten.entriesChanged} entr${rewritten.entriesChanged===1?"y":"ies"}.`}function buildHistoryRedactEntries(params){const rewrittenEntries=[...params.rewritten.entries];if(params.dryRun||!params.changed){return{rewrittenEntries,auditEntryAdded:false}}const finalDocument=params.nextItemDocument??replayToItemDocument(params.rewritten.finalDocument);rewrittenEntries.push(createHistoryEntry({nowIso:nowIso(),author:params.author,op:"history_redact",before:finalDocument,after:finalDocument,message:params.message}));return{rewrittenEntries,auditEntryAdded:true}}function createHistoryRedactTransaction(params){let derivedIndexWarnings=[];return{applyRewrite:async({historyRawUnderLock})=>{const releaseDerivedIndexLock=await acquireItemMetadataDerivedIndexLock(params.pmRoot,params.author);const affectedItemPaths=new Set;if(params.currentItem.path){affectedItemPaths.add(params.currentItem.path)}if(params.nextItem.path){affectedItemPaths.add(params.nextItem.path)}const itemSnapshots=new Map;if(params.currentItem.path&¶ms.currentItem.raw!==null){itemSnapshots.set(params.currentItem.path,params.currentItem.raw)}let rethrowReleaseFailure=()=>{};try{try{if(params.nextItem.path&¶ms.nextItem.raw!==null&¶ms.nextItem.raw!==params.currentItem.raw){await writeFileAtomic(params.nextItem.path,params.nextItem.raw)}if(params.currentItem.path&&(!params.nextItem.path||params.nextItem.path!==params.currentItem.path)){await fs.rm(params.currentItem.path,{force:true})}await writeFileAtomic(params.subject.historyPath,historyEntriesToRaw(params.rewrittenEntries))}catch(error){try{await rollbackHistoryRedactRewrite(params.subject.historyPath,historyRawUnderLock,affectedItemPaths,itemSnapshots)}finally{await invalidateHistoryDriftCacheForPath(params.subject.historyPath)}throw error}await invalidateHistoryDriftCacheForPath(params.subject.historyPath);const itemPath=params.nextItem.path??params.currentItem.path;if(itemPath){derivedIndexWarnings=await refreshItemMetadataDerivedIndex({pmRoot:params.pmRoot,preferredFormat:params.settings.item_format,typeToFolder:params.typeRegistry.type_to_folder,schema:params.settings.schema,itemPath,...params.currentItem.path?{previousItemPath:params.currentItem.path}:{},document:params.nextItem.document})}}finally{await releaseDerivedIndexLock().catch(error=>{rethrowReleaseFailure=()=>{throw error}})}rethrowReleaseFailure()},afterWrite:async()=>[...derivedIndexWarnings,...await runHistoryRedactWriteHooks(params.subject.historyPath,[params.nextItem.path,params.currentItem.path])]}}async function rollbackHistoryRedactRewrite(historyPath,historyRawUnderLock,affectedItemPaths,itemSnapshots){if(historyRawUnderLock===null){await fs.rm(historyPath,{force:true})}else{await writeFileAtomic(historyPath,historyRawUnderLock)}for(const itemPath of affectedItemPaths){const snapshot=itemSnapshots.get(itemPath);if(snapshot===void 0){await fs.rm(itemPath,{force:true})}else{await writeFileAtomic(itemPath,snapshot)}}}async function runHistoryRedactWriteHooks(historyPath,itemHookPaths){const hookWarnings=[];const uniqueItemHookPaths=new Set(itemHookPaths.filter(itemPath=>itemPath!==null));for(const itemHookPath of uniqueItemHookPaths){hookWarnings.push(...await runActiveOnWriteHooks({path:itemHookPath,scope:"project",op:"history_redact"}))}hookWarnings.push(...await runActiveOnWriteHooks({path:historyPath,scope:"project",op:"history_redact:history"}));return hookWarnings}async function runHistoryRedact(id,options,global2){const{pmRoot,settings,typeRegistry}=await resolveHistoryWorkspace(global2.path);const replacementInput=options.replacement??"";const replacementIsDefault=replacementInput.length===0;const replacement=replacementIsDefault?"[redacted]":replacementInput;const rules=buildRedactionRules(options.literal,options.regex);const subject=await resolveHistorySubject(pmRoot,id,settings,typeRegistry.type_to_folder);return runHistoryMaintenance({pmRoot,subject,settings,typeRegistry,operation:"history-redact",options,transform:snapshot=>buildHistoryRedactPlan({pmRoot,subject,settings,typeRegistry,options,rules,replacement,replacementIsDefault},snapshot)})}function buildHistoryRedactPlan(params,snapshot){const{pmRoot,subject,settings,typeRegistry,options,rules,replacement,replacementIsDefault}=params;const historyEntries=snapshot.entries;const integritySnapshot=inspectHistoryIntegrity(historyEntries);const rewritten=rewriteHistoryEntries(historyEntries,rules,replacement);const preexistingHashMismatches=integritySnapshot.hashMismatchesBefore+integritySnapshot.hashMismatchesAfter;const dryRun=Boolean(options.dryRun);const changed=rewritten.replacements>0;const warnings=[];if(preexistingHashMismatches>0){warnings.push(`history_redact_preexisting_hash_mismatches:${preexistingHashMismatches}`)}if(!changed){warnings.push("history_redact_no_matches")}const currentItem={raw:snapshot.loadedItem?.raw??null,path:subject.located?.itemPath??null,document:snapshot.loadedItem?.document??null};const nextItem=resolveHistoryRedactNextItem({pmRoot,subject,settings,typeToFolder:typeRegistry.type_to_folder,finalDocument:rewritten.finalDocument});const itemChanged=(currentItem.path??null)!==(nextItem.path??null)||(currentItem.raw??null)!==(nextItem.raw??null);const author=snapshot.author;const redactionMessage=buildHistoryRedactMessage(options,rewritten);const{rewrittenEntries,auditEntryAdded}=buildHistoryRedactEntries({rewritten,dryRun,changed,nextItemDocument:nextItem.document,author,message:redactionMessage});return{changed,rewrittenEntries,...createHistoryRedactTransaction({pmRoot,subject,settings,typeRegistry,currentItem,nextItem,author,rewrittenEntries}),report:({verification:historyVerify,warnings:writeWarnings})=>({id:subject.id,dry_run:dryRun,changed,patterns:{literal_count:rules.filter(rule=>rule.kind==="literal").length,regex_count:rules.filter(rule=>rule.kind==="regex").length,total_count:rules.length,replacement_is_default:replacementIsDefault},history:{path:subject.historyPath,entries_scanned:historyEntries.length,entries_changed:rewritten.entriesChanged,replacements:rewritten.replacements,hash_mismatches_before:integritySnapshot.hashMismatchesBefore,hash_mismatches_after:integritySnapshot.hashMismatchesAfter,preexisting_hash_mismatches:preexistingHashMismatches,audit_entry_added:auditEntryAdded,verify_ok:historyVerify.ok,verify_errors:historyVerify.errors},item:{existed_before:currentItem.path!==null,exists_after:nextItem.path!==null,path_before:currentItem.path,path_after:nextItem.path,changed:itemChanged},warnings:[...new Set([...warnings,...writeWarnings])].sort((left,right)=>left.localeCompare(right)),generated_at:nowIso()})}}const _testOnly={applyLiteralRule,applyRegexRule,buildRedactionRules,hasItemMetadata,normalizeRegexFlags,normalizeStringArrayInput,parseRegexRule,redactStringValue,redactUnknownValue};export{_testOnly,resolveHistorySubject2 as resolveHistorySubject,runHistoryRedact};
|
|
1
|
+
!(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]="9d9d625b-152d-5417-8702-6e0e45839500")}catch(e2){}})();import{resolveHistoryWorkspace,resolveHistorySubject}from"./history/subject.js";import{resolveHistorySubject as resolveHistorySubject2}from"./history/subject.js";import fs from"node:fs/promises";import{resolveHistoryHashAlgorithm}from"../core/history/digest.js";import{writeFileAtomic}from"../core/fs/fs-utils.js";import{createHistoryEntry,resealHistoryRewrite,verifyHistoryRecordHash}from"../core/history/history.js";import{invalidateHistoryDriftCacheForPath}from"../core/history/drift-cache.js";import{runHistoryMaintenance}from"./history/maintenance.js";import{EMPTY_REPLAY_DOCUMENT,historyEntriesToRaw,replayHash,replayToItemDocument}from"../core/history/replay.js";import{applyHistoryPatch}from"../core/history/projection.js";import{canonicalDocument,serializeItemDocument}from"../core/item/item-format.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{nowIso}from"../core/shared/time.js";import{runActiveOnWriteHooks}from"../core/extensions/index.js";import{acquireItemMetadataDerivedIndexLock,refreshItemMetadataDerivedIndex}from"../core/store/item-metadata-cache.js";import{getItemPath}from"../core/store/paths.js";function normalizeStringArrayInput(value){if(Array.isArray(value)){return value}if(typeof value==="string"){return[value]}return[]}function normalizeRegexFlags(flags){const unique=[];for(const token of flags){if(!unique.includes(token)){unique.push(token)}}if(!unique.includes("g")){unique.push("g")}return unique.join("")}function parseRegexRule(spec){const trimmed=spec.trim();if(trimmed.length===0){throw new PmCliError("history-redact --regex requires a non-empty pattern.",EXIT_CODE.USAGE)}let source=trimmed;let flags="g";if(trimmed.startsWith("/")&&trimmed.length>1){const slashIndex=trimmed.lastIndexOf("/");if(slashIndex>0){source=trimmed.slice(1,slashIndex);flags=normalizeRegexFlags(trimmed.slice(slashIndex+1))}}if(source.length===0){throw new PmCliError("history-redact --regex cannot use an empty pattern.",EXIT_CODE.USAGE)}try{new RegExp(source,flags)}catch{throw new PmCliError("Invalid --regex value; provide a valid JavaScript regular expression.",EXIT_CODE.USAGE)}return{kind:"regex",source,flags}}function buildRedactionRules(literalInput,regexInput){const literalRules=[...new Set(normalizeStringArrayInput(literalInput).map(entry=>entry.trim()))].filter(entry=>entry.length>0).map(entry=>({kind:"literal",value:entry}));const regexRules=[...new Set(normalizeStringArrayInput(regexInput).map(entry=>entry.trim()))].filter(entry=>entry.length>0).map(parseRegexRule);const rules=[...literalRules,...regexRules];if(rules.length===0){throw new PmCliError("history-redact requires at least one matcher via --literal or --regex.",EXIT_CODE.USAGE,{code:"missing_required_argument",required:"Provide --literal <value> and/or --regex <pattern>.",examples:['pm history-redact pm-a1b2 --literal "[redacted_path_prefix]/private"','pm history-redact pm-a1b2 --regex "/192\\\\.168\\\\.[0-9.]+/g" --replacement "[scrubbed_ip]"']})}return rules}function applyLiteralRule(value,literal,replacement){if(literal.length===0){return{value,replacements:0}}let cursor=0;let replacements=0;while(cursor<=value.length){const index=value.indexOf(literal,cursor);if(index===-1){break}replacements+=1;cursor=index+Math.max(1,literal.length)}if(replacements===0){return{value,replacements:0}}return{value:value.split(literal).join(replacement),replacements}}function applyRegexRule(value,rule,replacement){const regex=new RegExp(rule.source,rule.flags);const matches=[...value.matchAll(regex)];if(matches.length===0){return{value,replacements:0}}return{value:value.replace(regex,replacement),replacements:matches.length}}function redactStringValue(value,rules,replacement){let next=value;let replacements=0;for(const rule of rules){const result=rule.kind==="literal"?applyLiteralRule(next,rule.value,replacement):applyRegexRule(next,rule,replacement);next=result.value;replacements+=result.replacements}return{value:next,replacements}}function redactUnknownValue(value,rules,replacement){if(typeof value==="string"){return redactStringValue(value,rules,replacement)}if(Array.isArray(value)){let replacements=0;const nextValues=value.map(entry=>{const redacted=redactUnknownValue(entry,rules,replacement);replacements+=redacted.replacements;return redacted.value});return{value:nextValues,replacements}}if(typeof value==="object"&&value!==null){let replacements=0;const nextRecord={};for(const[key,entry]of Object.entries(value)){const redacted=redactUnknownValue(entry,rules,replacement);replacements+=redacted.replacements;nextRecord[key]=redacted.value}return{value:nextRecord,replacements}}return{value,replacements:0}}function redactHistoryPatch(patch,rules,replacement){let replacements=0;const redactedPatch=patch.map(operation=>{if(!Object.prototype.hasOwnProperty.call(operation,"value")){return operation}const redactedValue=redactUnknownValue(operation.value,rules,replacement);replacements+=redactedValue.replacements;return redactedValue.replacements>0?{...operation,value:redactedValue.value}:operation});return{patch:redactedPatch,replacements}}function inspectHistoryIntegrity(entries){let replay=structuredClone(EMPTY_REPLAY_DOCUMENT);let hashMismatchesBefore=0;let hashMismatchesAfter=0;for(let index=0;index<entries.length;index+=1){const entry=entries[index];if(replayHash(replay,void 0,resolveHistoryHashAlgorithm(entry.hash_algorithm))!==entry.before_hash){hashMismatchesBefore+=1}replay=applyHistoryPatch(replay,entry.patch,index+1,entry.op);if(replayHash(replay,void 0,resolveHistoryHashAlgorithm(entry.hash_algorithm))!==entry.after_hash){hashMismatchesAfter+=1}}return{hashMismatchesBefore,hashMismatchesAfter,finalDocument:replay}}function redactHistoryEntry(entry,rules,replacement){let replacements=0;let changed=false;let reanchorEvidenceRedacted=false;let nextMessage=entry.message;if(typeof entry.message==="string"){const redactedMessage=redactStringValue(entry.message,rules,replacement);nextMessage=redactedMessage.value;replacements+=redactedMessage.replacements;if(redactedMessage.replacements>0){changed=true}}const redactedPatch=redactHistoryPatch(entry.patch,rules,replacement);replacements+=redactedPatch.replacements;changed||=redactedPatch.replacements>0;const nextReanchorEvidence=entry.reanchor_evidence?.map(evidence=>{const redactedEvidencePatch=evidence.patch?redactHistoryPatch(evidence.patch,rules,replacement):{patch:void 0,replacements:0};const redactedEvidenceRecord=evidence.record?redactUnknownValue(evidence.record,rules,replacement):{value:void 0,replacements:0};replacements+=redactedEvidencePatch.replacements;replacements+=redactedEvidenceRecord.replacements;const evidenceChanged=redactedEvidencePatch.replacements>0||redactedEvidenceRecord.replacements>0;changed||=evidenceChanged;if(!evidenceChanged)return evidence;reanchorEvidenceRedacted=true;const{patch:_sensitivePatch,record:_sensitiveRecord,record_hash:_priorRecordHash,record_hash_version:_priorRecordHashVersion,...digestOnlyEvidence}=evidence;return redactedEvidencePatch.replacements>0?digestOnlyEvidence:{...digestOnlyEvidence,patch:evidence.patch}});return{entry:{...entry,message:nextMessage,patch:redactedPatch.patch,...nextReanchorEvidence?{reanchor_evidence:nextReanchorEvidence}:{}},replacements,changed,reanchorEvidenceRedacted}}function rewriteHistoryEntries(entries,rules,replacement){let replay=structuredClone(EMPTY_REPLAY_DOCUMENT);let entriesChanged=0;let replacements=0;const rewrittenEntries=[];for(let index=0;index<entries.length;index+=1){const original=entries[index];const recordVerification=verifyHistoryRecordHash(original);if(!recordVerification.ok){throw new PmCliError(`history-redact refuses invalid immutable record ${index+1} (${recordVerification.error}).`,EXIT_CODE.GENERIC_FAILURE)}const redacted=redactHistoryEntry(original,rules,replacement);replacements+=redacted.replacements;if(redacted.changed){entriesChanged+=1}const beforeHash=replayHash(replay,void 0,resolveHistoryHashAlgorithm(original.hash_algorithm));replay=applyHistoryPatch(replay,redacted.entry.patch,index+1,redacted.entry.op);const afterHash=replayHash(replay,void 0,resolveHistoryHashAlgorithm(original.hash_algorithm));rewrittenEntries.push(resealHistoryRewrite(original,{...redacted.entry,before_hash:beforeHash,after_hash:afterHash},{retainPriorRecord:redacted.changed,retainPriorRecordHash:!redacted.changed}))}return{entries:rewrittenEntries,finalDocument:replay,entriesChanged,replacements}}function hasItemMetadata(replay){return Object.keys(replay.metadata).length>0}function resolveHistoryRedactNextItem(params){if(!hasItemMetadata(params.finalDocument)){return{raw:null,path:null,document:null}}const canonical=canonicalDocument(replayToItemDocument(params.finalDocument),{schema:params.settings.schema});if(canonical.metadata.id!==params.subject.id){throw new PmCliError("history-redact would change the item id; narrow your patterns.",EXIT_CODE.USAGE)}return{document:canonical,path:getItemPath(params.pmRoot,canonical.metadata.type,params.subject.id,"toon",params.typeToFolder),raw:serializeItemDocument(canonical,{format:"toon",schema:params.settings.schema})}}function buildHistoryRedactMessage(options,rewritten){if(typeof options.message==="string"&&options.message.trim().length>0){return options.message}return`history-redact replaced ${rewritten.replacements} match(es) across ${rewritten.entriesChanged} entr${rewritten.entriesChanged===1?"y":"ies"}.`}function buildHistoryRedactEntries(params){const rewrittenEntries=[...params.rewritten.entries];if(params.dryRun||!params.changed){return{rewrittenEntries,auditEntryAdded:false}}const finalDocument=params.nextItemDocument??replayToItemDocument(params.rewritten.finalDocument);rewrittenEntries.push(createHistoryEntry({nowIso:nowIso(),author:params.author,op:"history_redact",before:finalDocument,after:finalDocument,message:params.message}));return{rewrittenEntries,auditEntryAdded:true}}function createHistoryRedactTransaction(params){let derivedIndexWarnings=[];return{applyRewrite:async({historyRawUnderLock})=>{const releaseDerivedIndexLock=await acquireItemMetadataDerivedIndexLock(params.pmRoot,params.author);const affectedItemPaths=new Set;if(params.currentItem.path){affectedItemPaths.add(params.currentItem.path)}if(params.nextItem.path){affectedItemPaths.add(params.nextItem.path)}const itemSnapshots=new Map;if(params.currentItem.path&¶ms.currentItem.raw!==null){itemSnapshots.set(params.currentItem.path,params.currentItem.raw)}let rethrowReleaseFailure=()=>{};try{try{if(params.nextItem.path&¶ms.nextItem.raw!==null&¶ms.nextItem.raw!==params.currentItem.raw){await writeFileAtomic(params.nextItem.path,params.nextItem.raw)}if(params.currentItem.path&&(!params.nextItem.path||params.nextItem.path!==params.currentItem.path)){await fs.rm(params.currentItem.path,{force:true})}await writeFileAtomic(params.subject.historyPath,historyEntriesToRaw(params.rewrittenEntries))}catch(error){try{await rollbackHistoryRedactRewrite(params.subject.historyPath,historyRawUnderLock,affectedItemPaths,itemSnapshots)}finally{await invalidateHistoryDriftCacheForPath(params.subject.historyPath)}throw error}await invalidateHistoryDriftCacheForPath(params.subject.historyPath);const itemPath=params.nextItem.path??params.currentItem.path;if(itemPath){derivedIndexWarnings=await refreshItemMetadataDerivedIndex({pmRoot:params.pmRoot,preferredFormat:params.settings.item_format,typeToFolder:params.typeRegistry.type_to_folder,schema:params.settings.schema,itemPath,...params.currentItem.path?{previousItemPath:params.currentItem.path}:{},document:params.nextItem.document})}}finally{await releaseDerivedIndexLock().catch(error=>{rethrowReleaseFailure=()=>{throw error}})}rethrowReleaseFailure()},afterWrite:async()=>[...derivedIndexWarnings,...await runHistoryRedactWriteHooks(params.subject.historyPath,[params.nextItem.path,params.currentItem.path])]}}async function rollbackHistoryRedactRewrite(historyPath,historyRawUnderLock,affectedItemPaths,itemSnapshots){if(historyRawUnderLock===null){await fs.rm(historyPath,{force:true})}else{await writeFileAtomic(historyPath,historyRawUnderLock)}for(const itemPath of affectedItemPaths){const snapshot=itemSnapshots.get(itemPath);if(snapshot===void 0){await fs.rm(itemPath,{force:true})}else{await writeFileAtomic(itemPath,snapshot)}}}async function runHistoryRedactWriteHooks(historyPath,itemHookPaths){const hookWarnings=[];const uniqueItemHookPaths=new Set(itemHookPaths.filter(itemPath=>itemPath!==null));for(const itemHookPath of uniqueItemHookPaths){hookWarnings.push(...await runActiveOnWriteHooks({path:itemHookPath,scope:"project",op:"history_redact"}))}hookWarnings.push(...await runActiveOnWriteHooks({path:historyPath,scope:"project",op:"history_redact:history"}));return hookWarnings}async function runHistoryRedact(id,options,global2){const{pmRoot,settings,typeRegistry}=await resolveHistoryWorkspace(global2.path);const replacementInput=options.replacement??"";const replacementIsDefault=replacementInput.length===0;const replacement=replacementIsDefault?"[redacted]":replacementInput;const rules=buildRedactionRules(options.literal,options.regex);const subject=await resolveHistorySubject(pmRoot,id,settings,typeRegistry.type_to_folder);return runHistoryMaintenance({pmRoot,subject,settings,typeRegistry,operation:"history-redact",options,transform:snapshot=>buildHistoryRedactPlan({pmRoot,subject,settings,typeRegistry,options,rules,replacement,replacementIsDefault},snapshot)})}function buildHistoryRedactPlan(params,snapshot){const{pmRoot,subject,settings,typeRegistry,options,rules,replacement,replacementIsDefault}=params;const historyEntries=snapshot.entries;const integritySnapshot=inspectHistoryIntegrity(historyEntries);const rewritten=rewriteHistoryEntries(historyEntries,rules,replacement);const preexistingHashMismatches=integritySnapshot.hashMismatchesBefore+integritySnapshot.hashMismatchesAfter;const dryRun=Boolean(options.dryRun);const changed=rewritten.replacements>0;const warnings=[];if(preexistingHashMismatches>0){warnings.push(`history_redact_preexisting_hash_mismatches:${preexistingHashMismatches}`)}if(!changed){warnings.push("history_redact_no_matches")}const currentItem={raw:snapshot.loadedItem?.raw??null,path:subject.located?.itemPath??null,document:snapshot.loadedItem?.document??null};const nextItem=resolveHistoryRedactNextItem({pmRoot,subject,settings,typeToFolder:typeRegistry.type_to_folder,finalDocument:rewritten.finalDocument});const itemChanged=(currentItem.path??null)!==(nextItem.path??null)||(currentItem.raw??null)!==(nextItem.raw??null);const author=snapshot.author;const redactionMessage=buildHistoryRedactMessage(options,rewritten);const{rewrittenEntries,auditEntryAdded}=buildHistoryRedactEntries({rewritten,dryRun,changed,nextItemDocument:nextItem.document,author,message:redactionMessage});return{changed,rewrittenEntries,...createHistoryRedactTransaction({pmRoot,subject,settings,typeRegistry,currentItem,nextItem,author,rewrittenEntries}),report:({verification:historyVerify,warnings:writeWarnings})=>({id:subject.id,dry_run:dryRun,changed,patterns:{literal_count:rules.filter(rule=>rule.kind==="literal").length,regex_count:rules.filter(rule=>rule.kind==="regex").length,total_count:rules.length,replacement_is_default:replacementIsDefault},history:{path:subject.historyPath,entries_scanned:historyEntries.length,entries_changed:rewritten.entriesChanged,replacements:rewritten.replacements,hash_mismatches_before:integritySnapshot.hashMismatchesBefore,hash_mismatches_after:integritySnapshot.hashMismatchesAfter,preexisting_hash_mismatches:preexistingHashMismatches,audit_entry_added:auditEntryAdded,verify_ok:historyVerify.ok,verify_errors:historyVerify.errors},item:{existed_before:currentItem.path!==null,exists_after:nextItem.path!==null,path_before:currentItem.path,path_after:nextItem.path,changed:itemChanged},warnings:[...new Set([...warnings,...writeWarnings])].sort((left,right)=>left.localeCompare(right)),generated_at:nowIso()})}}const _testOnly={applyLiteralRule,applyRegexRule,buildRedactionRules,hasItemMetadata,normalizeRegexFlags,normalizeStringArrayInput,parseRegexRule,redactStringValue,redactUnknownValue};export{_testOnly,resolveHistorySubject2 as resolveHistorySubject,runHistoryRedact};
|
|
2
2
|
//# sourceMappingURL=history-redact.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!(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]="fc12fe93-9e80-5501-a4ee-3b93eb8b0585")}catch(e2){}})();import{salvageHistoryTail}from"./history/salvage.js";import{realpath}from"node:fs/promises";import path from"node:path";import jsonPatch from"fast-json-patch";import{patchPathToChangedField}from"../core/history/history-diff.js";import{normalizeReplayPatchOps,reanchorHistoryEntries,replayHashVerificationCandidates,resolveHistoryRepairItemHashVersion,toReplayDocument,verifyHistoryChainWithVersion}from"../core/history/replay.js";import{sealHistoryRecord}from"../core/history/history.js";import{classifyHistoryEvent}from"../core/history/event-classification.js";import{scanHistoryDrift}from"../core/history/drift-scan.js";import{invalidateHistoryDriftCache}from"../core/history/drift-cache.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{sha256Hex,stableStringify}from"../core/shared/serialization.js";import{resolveAuthor}from"../core/shared/author.js";import{nowIso}from"../core/shared/time.js";import{listAllItemMetadataWithBody}from"../core/store/item-store.js";import{resolveHistoryWorkspace,resolveHistorySubject}from"./history/subject.js";import{runHistoryMaintenance}from"./history/maintenance.js";import{isOriginalGitStateRestored}from"./merge/receipt-operation.js";import{inspectMergeReceiptEvidence,summarizeMergeReceipt}from"./merge/receipts.js";import{encodeItemScalarDecisionValue,hashItemScalarDecisionValue}from"./merge/three-way.js";import{listInvalidProvenanceHistoryStreamIds,normalizeInvalidHistoryProvenance}from"./governance/provenance-health.js";function replayFieldValue(document,field){return field==="body"?document.body:document.metadata[field]}function isReplayValuePreserved(replayed,reconciled){if(replayed===void 0||Object.is(replayed,reconciled)){return true}if(Array.isArray(replayed)){if(!Array.isArray(reconciled)){return false}const matched=new Set;return replayed.every(expected=>{const index=reconciled.findIndex((candidate,candidateIndex)=>!matched.has(candidateIndex)&&isReplayValuePreserved(expected,candidate));if(index<0){return false}matched.add(index);return true})}if(replayed===null||reconciled===null||typeof replayed!=="object"||typeof reconciled!=="object"||Array.isArray(reconciled)){return false}const reconciledRecord=reconciled;return Object.entries(replayed).every(([key,value])=>Object.hasOwn(reconciledRecord,key)&&isReplayValuePreserved(value,reconciledRecord[key]))}function collectReconciliationEvents(entries,fields){const remaining=new Set(fields);const events=[];for(let index=entries.length-1;index>=0&&remaining.size>0;index-=1){const entry=entries[index];const entryFields=new Set;for(const op of normalizeReplayPatchOps(entry.patch)){entryFields.add(patchPathToChangedField(op.path));if(op.from){entryFields.add(patchPathToChangedField(op.from))}}const overlap=[...remaining].filter(field=>entryFields.has(field));if(overlap.length===0){continue}for(const field of overlap){remaining.delete(field)}events.push({index:index+1,ts:entry.ts,author:entry.author,op:entry.op,fields:overlap.sort((left,right)=>left.localeCompare(right))})}return events.reverse()}function analyzeReconciliationDiscard(entries,finalReplay,currentItemReplay){const reconcilePatch=jsonPatch.compare(finalReplay,currentItemReplay);if(reconcilePatch.length===0){return void 0}const reconciledFields=new Set;for(const op of reconcilePatch){reconciledFields.add(patchPathToChangedField(op.path))}const preservedFields=new Set([...reconciledFields].filter(field=>isReplayValuePreserved(replayFieldValue(finalReplay,field),replayFieldValue(currentItemReplay,field))));const revertedFields=new Set([...reconciledFields].filter(field=>!preservedFields.has(field)));const discardedEvents=collectReconciliationEvents(entries,revertedFields);const preservedEvents=collectReconciliationEvents(entries,preservedFields);const sortedReconciledFields=[...reconciledFields].sort((left,right)=>left.localeCompare(right));const sortedRevertedFields=[...revertedFields].sort((left,right)=>left.localeCompare(right));const sortedPreservedFields=[...preservedFields].sort((left,right)=>left.localeCompare(right));const discardedAuthors=[...new Set(discardedEvents.map(event=>event.author))].sort((left,right)=>left.localeCompare(right));const preservedAuthors=[...new Set(preservedEvents.map(event=>event.author))].sort((left,right)=>left.localeCompare(right));return{reconciled_field_count:reconciledFields.size,reconciled_fields:sortedReconciledFields,reverted_field_count:revertedFields.size,reverted_fields:sortedRevertedFields,discarded_events:discardedEvents,discarded_authors:discardedAuthors,preserved_field_count:preservedFields.size,preserved_fields:sortedPreservedFields,preserved_events:preservedEvents,preserved_authors:preservedAuthors,recovery_hint:discardedEvents.length>0?"Reconciliation keeps the on-disk item and overwrites these replayed mutations. To recover one, inspect it with pm history <id> --diff --field <field>, then re-apply the lost value with pm update.":null}}async function canonicalRealPath(value){try{return await realpath(value)}catch{return null}}async function verifyReceiptAgainstSnapshot(params){const receiptPath=await canonicalRealPath(path.resolve(params.gitWorkspaceRoot,params.receipt.item_path));if(receiptPath!==params.currentPath)return null;const declaredFields=[...new Set([...params.receipt.fields_from_theirs,...params.receipt.union_fields,...params.receipt.decisions.map(decision=>decision.field)])].sort();const hashes=params.receipt.merged_field_hashes;if(!hashes||declaredFields.length===0)return null;const hashFields=Object.keys(hashes).sort();if(declaredFields.length!==hashFields.length||declaredFields.some((field,index)=>field!==hashFields[index])){return null}const currentMetadata=params.currentItemReplay.metadata;const valuesMatch=declaredFields.every(field=>{const value=field==="body"?params.currentItemReplay.body:currentMetadata[field];return[hashItemScalarDecisionValue(value),sha256Hex(stableStringify(encodeItemScalarDecisionValue(value)))].includes(hashes[field])});return valuesMatch?{receipt:params.receipt,declaredFields}:null}async function verifyMergeReceiptProof(params){if(params.proof.gitWorkspaceRoot===null){return{trusted:false,reason:"git_workspace_unavailable",receipt_ids:[]}}if(params.itemPath===null||params.currentItemReplay===null){return{trusted:false,reason:"item_path_unavailable",receipt_ids:[]}}const itemReceipts=params.proof.receipts.filter(receipt=>receipt.item_id===params.id);if(itemReceipts.length===0){return{trusted:false,reason:"no_item_receipts",receipt_ids:[]}}const currentPath=await realpath(params.itemPath);const trustedReceipts=[];for(const receipt of itemReceipts){const trusted=await verifyReceiptAgainstSnapshot({receipt,gitWorkspaceRoot:params.proof.gitWorkspaceRoot,currentPath,currentItemReplay:params.currentItemReplay});if(trusted)trustedReceipts.push(trusted)}const trustedFields=new Set(trustedReceipts.flatMap(({declaredFields})=>declaredFields));const reconciliationFields=params.reconciliation?.reconciled_fields;if(trustedReceipts.length>0&&(reconciliationFields===void 0||reconciliationFields.length>0&&reconciliationFields.every(field=>trustedFields.has(field)))){return{trusted:true,reason:"trusted_merge_driver_hash_evidence",receipt_ids:trustedReceipts.map(({receipt})=>receipt.id)}}return{trusted:false,reason:"no_receipt_set_proves_snapshot",receipt_ids:[]}}async function assertHistoryRepairAbandonmentProof(proof,context,chainOk,reanchor){if(proof===void 0)return;const{receipt,gitWorkspaceRoot}=proof;const canonicalPath=context.currentItemPath===null?null:path.relative(gitWorkspaceRoot,context.currentItemPath).split(path.sep).join("/");if(!chainOk||reanchor.entriesRehashed!==0||reanchor.entriesPatchRepaired!==0||context.matchedChainBefore!==true||context.currentItemRawBeforeLock===null||canonicalPath!==receipt.item_path||receipt.operation===void 0||!await isOriginalGitStateRestored(gitWorkspaceRoot,receipt.item_path,receipt.operation,context.currentItemRawBeforeLock)){throw new PmCliError("Restored-origin receipt evidence no longer proves the exact clean item snapshot.",EXIT_CODE.CONFLICT,{code:"merge_reconcile_receipt_evidence_untrusted",recovery:{suggested_retry:"pm merge reconcile --dry-run"}})}}async function resolveHistoryRepairMergeEvidence(params){const requestedReceiptIds=new Set(params.options.mergeReceiptProof?.receipts.filter(receipt=>receipt.item_id===params.subjectId).map(receipt=>receipt.id)??[]);const authoritativeReceiptEvidence=params.options.mergeReceiptProof?await inspectMergeReceiptEvidence(params.options.mergeReceiptProof.gitWorkspaceRoot??process.cwd(),{includeReconciled:true,includeLossless:true,pmRoot:params.pmRoot}):void 0;const subjectMergeReceipts=authoritativeReceiptEvidence?.receipts.filter(receipt=>receipt.item_id===params.subjectId&&requestedReceiptIds.has(receipt.id))??[];const mergeReceiptProof=params.options.mergeReceiptProof?await verifyMergeReceiptProof({proof:{...params.options.mergeReceiptProof,receipts:subjectMergeReceipts},id:params.subjectId,itemPath:params.itemReplayContext.currentItemPath,currentItemReplay:params.itemReplayContext.currentItemReplay,reconciliation:params.reconciliation}):void 0;if(mergeReceiptProof&&!mergeReceiptProof.trusted&¶ms.options.force!==true){throw new PmCliError(`Merge receipt evidence for ${params.subjectId} does not prove the exact item snapshot (${mergeReceiptProof.reason}).`,EXIT_CODE.CONFLICT,{code:"merge_reconcile_receipt_evidence_untrusted",required:"Use authoritative clone-local or durable receipt evidence whose canonical path, declared fields, and merged-value hashes match the current item, or review the mismatch and rerun with --force.",recovery:{suggested_retry:"pm merge reconcile --dry-run"}})}const provenMergeReceipts=mergeReceiptProof?.trusted?subjectMergeReceipts.filter(receipt=>mergeReceiptProof.receipt_ids.includes(receipt.id)):[];return{mergeReceiptProof,effectiveAuditContext:provenMergeReceipts.length>0&¶ms.options.force!==true?{...params.options.auditContext,merge:{receipts:provenMergeReceipts.map(summarizeMergeReceipt)}}:params.options.auditContext}}function describeHistoryRepairItemReplay(subject,snapshot,itemHashVersion){const currentItemPath=subject.located?.itemPath??null;const loadedItem=snapshot.loadedItem;const historyEntries=snapshot.entries;if(!loadedItem){return{currentItemReplay:null,currentItemPath,matchedChainBefore:null,currentItemRawBeforeLock:null,loadedItem:null}}const currentItemReplay=toReplayDocument(loadedItem.document);const lastOriginalAfterHash=historyEntries[historyEntries.length-1].after_hash;return{currentItemReplay,currentItemPath,matchedChainBefore:replayHashVerificationCandidates(currentItemReplay,itemHashVersion).includes(lastOriginalAfterHash),currentItemRawBeforeLock:loadedItem.raw,loadedItem}}function buildHistoryRepairMessage(params){if(typeof params.message==="string"&¶ms.message.trim().length>0){return params.message}return`history-repair re-anchored ${params.entriesRehashed} entr${params.entriesRehashed===1?"y":"ies"}${params.entriesPatchRepaired>0?`, repaired ${params.entriesPatchRepaired} patch(es)`:""}${params.reconcileNeeded?", reconciled chain with on-disk item":""}.`}function buildHistoryRepairEntries(params){const rewrittenEntries=[...params.reanchorEntries];if(!params.changed){return{rewrittenEntries,auditEntryAdded:false}}const afterReplay=params.reconcileNeeded&¶ms.currentItemReplay?params.currentItemReplay:params.finalReplay;const beforeHashes=replayHashVerificationCandidates(params.finalReplay,params.itemHashVersion);const previousAfterHash=params.reanchorEntries[params.reanchorEntries.length-1].after_hash;const semanticIndex=beforeHashes.indexOf(previousAfterHash);const afterHashes=replayHashVerificationCandidates(afterReplay,params.itemHashVersion);const auditEntry={ts:nowIso(),author:params.author,op:params.auditOperation,patch:params.reconcileNeeded&¶ms.currentItemReplay?jsonPatch.compare(params.finalReplay,params.currentItemReplay):[],before_hash:beforeHashes[semanticIndex],after_hash:afterHashes[semanticIndex],message:params.message,...params.explicitItemHashVersion?{item_hash_version:params.itemHashVersion}:{},...params.auditContext===void 0?{}:{context:params.auditContext}};rewrittenEntries.push(sealHistoryRecord({...auditEntry,event_class:classifyHistoryEvent(auditEntry)}));return{rewrittenEntries,auditEntryAdded:true}}function collectHistoryRepairWarnings(changed,skippedOps,reconciliation){const warnings=[];if(!changed){warnings.push("history_repair_no_changes")}if(skippedOps>0){warnings.push(`history_repair_skipped_unresolvable_ops:${skippedOps}`)}if(reconciliation&&reconciliation.discarded_events.length>0){warnings.push(`history_repair_reconcile_discards_events:${reconciliation.discarded_events.length}`,`history_repair_discarded_authors:${reconciliation.discarded_authors.join(",")}`)}return warnings}async function runHistoryRepair(id,options,global2){const{pmRoot,settings,typeRegistry}=await resolveHistoryWorkspace(global2.path);const subject=await resolveHistorySubject(pmRoot,id,settings,typeRegistry.type_to_folder);if(options.salvageTail===true){return salvageHistoryTail({pmRoot,subject,settings,typeRegistry,options,author:resolveAuthor(options.author,settings.author_default)})}return runHistoryMaintenance({pmRoot,subject,settings,typeRegistry,operation:"history-repair",options,transform:snapshot=>buildHistoryRepairPlan({pmRoot,subject,options},snapshot)})}function shouldAppendForcedHistoryAudit(options,historyEntries){const alreadyRecorded=options.mergeAbandonmentProof!==void 0&&options.auditContext!==void 0&&historyEntries.some(entry=>entry.op==="merge_reconcile"&&stableStringify(entry.context)===stableStringify(options.auditContext));return options.forceAuditEntry===true&&!alreadyRecorded}async function buildHistoryRepairPlan(params,snapshot){const{pmRoot,subject,options}=params;const historyEntries=snapshot.entries;const chainBefore=snapshot.verification;const chainVersionBefore=verifyHistoryChainWithVersion(historyEntries);const provenanceNormalization=options.normalizeProvenance?normalizeInvalidHistoryProvenance(historyEntries):{entries:historyEntries,receipt:{changed:false,events_changed:0,observations_removed:0,invalid_values:[]}};const itemHashVersion=resolveHistoryRepairItemHashVersion(provenanceNormalization.entries);const explicitVersionsBefore=new Set(provenanceNormalization.entries.map(entry=>entry.item_hash_version).filter(version=>version!==void 0));const reanchor=reanchorHistoryEntries(provenanceNormalization.entries,itemHashVersion);const itemReplayContext=describeHistoryRepairItemReplay(subject,snapshot,itemHashVersion);await assertHistoryRepairAbandonmentProof(options.mergeAbandonmentProof,itemReplayContext,chainBefore.ok,reanchor);const finalReplay=reanchor.finalDocument;const finalReplayHashes=replayHashVerificationCandidates(finalReplay,itemHashVersion);let reconcileNeeded=false;if(itemReplayContext.currentItemReplay!==null){const currentItemReplayHashes=replayHashVerificationCandidates(itemReplayContext.currentItemReplay,itemHashVersion);reconcileNeeded=!finalReplayHashes.some((hash,hashIndex)=>hash===currentItemReplayHashes[hashIndex])}const reconciliation=reconcileNeeded&&itemReplayContext.currentItemReplay?analyzeReconciliationDiscard(reanchor.entries,finalReplay,itemReplayContext.currentItemReplay):void 0;const{mergeReceiptProof,effectiveAuditContext}=await resolveHistoryRepairMergeEvidence({options,pmRoot,subjectId:subject.id,itemReplayContext,reconciliation});const changed=[reanchor.entriesRehashed>0,reanchor.entriesPatchRepaired>0,reconcileNeeded,shouldAppendForcedHistoryAudit(options,historyEntries),provenanceNormalization.receipt.changed].some(Boolean);const author=snapshot.author;const dryRun=Boolean(options.dryRun);const repairMessage=buildHistoryRepairMessage({message:options.message,entriesRehashed:reanchor.entriesRehashed,entriesPatchRepaired:reanchor.entriesPatchRepaired,reconcileNeeded});const{rewrittenEntries,auditEntryAdded}=buildHistoryRepairEntries({reanchorEntries:reanchor.entries,changed,reconcileNeeded,currentItemReplay:itemReplayContext.currentItemReplay,finalReplay,author,message:repairMessage,auditOperation:options.auditOperation??"history_repair",auditContext:provenanceNormalization.receipt.changed?{...effectiveAuditContext,provenance_normalization:provenanceNormalization.receipt}:effectiveAuditContext,itemHashVersion,explicitItemHashVersion:reanchor.explicitItemHashVersion});const warnings=collectHistoryRepairWarnings(changed,reanchor.skippedOps,reconciliation);return{changed,rewrittenEntries,beforeWrite:async()=>assertHistoryRepairAbandonmentProof(options.mergeAbandonmentProof,itemReplayContext,chainBefore.ok,reanchor),report:({verification:historyVerify,warnings:writeWarnings})=>({id:subject.id,dry_run:dryRun,changed,history:{path:subject.historyPath,entries_scanned:historyEntries.length,chain_drift_before:!chainBefore.ok,entries_rehashed:reanchor.entriesRehashed,entries_patch_repaired:reanchor.entriesPatchRepaired,converted_replace_to_add:reanchor.convertedReplaceToAdd,skipped_ops:reanchor.skippedOps,reconciled_with_item:reconcileNeeded,audit_entry_added:auditEntryAdded,verify_ok:historyVerify.ok,verify_errors:historyVerify.errors,item_hash_version_before:chainVersionBefore.item_hash_version??null,item_hash_version_after:itemHashVersion,version_disposition:explicitVersionsBefore.size>1?"selected_for_ambiguous_stream":"preserved"},item:{exists:itemReplayContext.currentItemPath!==null,path:itemReplayContext.currentItemPath,matched_chain_before:itemReplayContext.matchedChainBefore},provenance_normalization:{requested:options.normalizeProvenance===true,...provenanceNormalization.receipt},...reconciliation?{reconciliation}:{},...mergeReceiptProof?{merge_receipt_proof:mergeReceiptProof}:{},warnings:[...new Set([...warnings,...writeWarnings])].sort((left,right)=>left.localeCompare(right)),generated_at:nowIso()})}}function assertHistoryRepairTarget(id,all){if(all&&id!==void 0){throw new PmCliError("history-repair: <id> and --all are mutually exclusive; pass an item id to repair one stream or --all to repair every drifted stream.",EXIT_CODE.USAGE)}if(!all&&id===void 0){throw new PmCliError("history-repair: provide an item <id> or pass --all to repair every drifted stream.",EXIT_CODE.USAGE)}}async function runHistoryRepairAll(options,global2){if(options.salvageTail===true){throw new PmCliError("Tail salvage requires one explicit item ID; --all is not supported.",EXIT_CODE.USAGE)}const{pmRoot,settings,typeRegistry}=await resolveHistoryWorkspace(global2.path);const itemReadWarnings=[];const items=await listAllItemMetadataWithBody(pmRoot,settings.item_format,typeRegistry.type_to_folder,itemReadWarnings,settings.schema,{forceSourceScan:true});const drift=await scanHistoryDrift(pmRoot,items);const invalidProvenanceIds=options.normalizeProvenance?await listInvalidProvenanceHistoryStreamIds(pmRoot):[];const targetedIds=[...new Set([...drift.driftedItems,...invalidProvenanceIds])].sort((left,right)=>left.localeCompare(right));const streams=[];const repairWarnings=[];const totals={repaired:0,skipped_clean:0,failed:0};for(const driftedId of targetedIds){try{const result=await runHistoryRepair(driftedId,{...options,auditContext:options.auditContextById?.[driftedId],auditContextById:void 0,mergeReceiptProof:options.mergeReceiptProofById?.[driftedId]??options.mergeReceiptProof,mergeReceiptProofById:void 0},global2);const outcome=result.changed?"repaired":"skipped_clean";totals[outcome]+=1;repairWarnings.push(...result.warnings.map(warning=>`${driftedId}:${warning}`));streams.push({id:driftedId,outcome,entries_rehashed:result.history.entries_rehashed,entries_patch_repaired:result.history.entries_patch_repaired,reconciled_with_item:result.history.reconciled_with_item,...result.reconciliation?{reconciliation:result.reconciliation}:{},...result.merge_receipt_proof?{merge_receipt_proof:result.merge_receipt_proof}:{},warnings:result.warnings})}catch(error){totals.failed+=1;streams.push({id:driftedId,outcome:"failed",error:error instanceof Error?error.message:String(error)})}}if(!options.dryRun){await invalidateHistoryDriftCache(pmRoot)}return{all:true,dry_run:Boolean(options.dryRun),scanned_streams:items.length,drifted_streams:drift.driftedItems.length,provenance_invalid_streams:invalidProvenanceIds.length,streams,totals,warnings:[...new Set([...itemReadWarnings,...repairWarnings])].sort((left,right)=>left.localeCompare(right)),generated_at:nowIso()}}export{analyzeReconciliationDiscard,assertHistoryRepairTarget,runHistoryRepair,runHistoryRepairAll};
|
|
1
|
+
!(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]="55e22181-7bbf-5fed-965c-219fe465ed72")}catch(e2){}})();import{salvageHistoryTail}from"./history/salvage.js";import{resolveHistoryHashAlgorithm}from"../core/history/digest.js";import{realpath}from"node:fs/promises";import path from"node:path";import jsonPatch from"fast-json-patch";import{patchPathToChangedField}from"../core/history/history-diff.js";import{normalizeReplayPatchOps,reanchorHistoryEntries,replayHashVerificationCandidates,resolveHistoryRepairItemHashVersion,toReplayDocument,verifyHistoryChainWithVersion}from"../core/history/replay.js";import{sealHistoryRecord}from"../core/history/history.js";import{classifyHistoryEvent}from"../core/history/event-classification.js";import{scanHistoryDrift}from"../core/history/drift-scan.js";import{invalidateHistoryDriftCache}from"../core/history/drift-cache.js";import{EXIT_CODE}from"../core/shared/constants.js";import{PmCliError}from"../core/shared/errors.js";import{sha256Hex,stableStringify}from"../core/shared/serialization.js";import{resolveAuthor}from"../core/shared/author.js";import{nowIso}from"../core/shared/time.js";import{listAllItemMetadataWithBody}from"../core/store/item-store.js";import{resolveHistoryWorkspace,resolveHistorySubject}from"./history/subject.js";import{runHistoryMaintenance}from"./history/maintenance.js";import{isOriginalGitStateRestored}from"./merge/receipt-operation.js";import{inspectMergeReceiptEvidence,summarizeMergeReceipt}from"./merge/receipts.js";import{encodeItemScalarDecisionValue,hashItemScalarDecisionValue}from"./merge/three-way.js";import{listInvalidProvenanceHistoryStreamIds,normalizeInvalidHistoryProvenance}from"./governance/provenance-health.js";function replayFieldValue(document,field){return field==="body"?document.body:document.metadata[field]}function isReplayValuePreserved(replayed,reconciled){if(replayed===void 0||Object.is(replayed,reconciled)){return true}if(Array.isArray(replayed)){if(!Array.isArray(reconciled)){return false}const matched=new Set;return replayed.every(expected=>{const index=reconciled.findIndex((candidate,candidateIndex)=>!matched.has(candidateIndex)&&isReplayValuePreserved(expected,candidate));if(index<0){return false}matched.add(index);return true})}if(replayed===null||reconciled===null||typeof replayed!=="object"||typeof reconciled!=="object"||Array.isArray(reconciled)){return false}const reconciledRecord=reconciled;return Object.entries(replayed).every(([key,value])=>Object.hasOwn(reconciledRecord,key)&&isReplayValuePreserved(value,reconciledRecord[key]))}function collectReconciliationEvents(entries,fields){const remaining=new Set(fields);const events=[];for(let index=entries.length-1;index>=0&&remaining.size>0;index-=1){const entry=entries[index];const entryFields=new Set;for(const op of normalizeReplayPatchOps(entry.patch)){entryFields.add(patchPathToChangedField(op.path));if(op.from){entryFields.add(patchPathToChangedField(op.from))}}const overlap=[...remaining].filter(field=>entryFields.has(field));if(overlap.length===0){continue}for(const field of overlap){remaining.delete(field)}events.push({index:index+1,ts:entry.ts,author:entry.author,op:entry.op,fields:overlap.sort((left,right)=>left.localeCompare(right))})}return events.reverse()}function analyzeReconciliationDiscard(entries,finalReplay,currentItemReplay){const reconcilePatch=jsonPatch.compare(finalReplay,currentItemReplay);if(reconcilePatch.length===0){return void 0}const reconciledFields=new Set;for(const op of reconcilePatch){reconciledFields.add(patchPathToChangedField(op.path))}const preservedFields=new Set([...reconciledFields].filter(field=>isReplayValuePreserved(replayFieldValue(finalReplay,field),replayFieldValue(currentItemReplay,field))));const revertedFields=new Set([...reconciledFields].filter(field=>!preservedFields.has(field)));const discardedEvents=collectReconciliationEvents(entries,revertedFields);const preservedEvents=collectReconciliationEvents(entries,preservedFields);const sortedReconciledFields=[...reconciledFields].sort((left,right)=>left.localeCompare(right));const sortedRevertedFields=[...revertedFields].sort((left,right)=>left.localeCompare(right));const sortedPreservedFields=[...preservedFields].sort((left,right)=>left.localeCompare(right));const discardedAuthors=[...new Set(discardedEvents.map(event=>event.author))].sort((left,right)=>left.localeCompare(right));const preservedAuthors=[...new Set(preservedEvents.map(event=>event.author))].sort((left,right)=>left.localeCompare(right));return{reconciled_field_count:reconciledFields.size,reconciled_fields:sortedReconciledFields,reverted_field_count:revertedFields.size,reverted_fields:sortedRevertedFields,discarded_events:discardedEvents,discarded_authors:discardedAuthors,preserved_field_count:preservedFields.size,preserved_fields:sortedPreservedFields,preserved_events:preservedEvents,preserved_authors:preservedAuthors,recovery_hint:discardedEvents.length>0?"Reconciliation keeps the on-disk item and overwrites these replayed mutations. To recover one, inspect it with pm history <id> --diff --field <field>, then re-apply the lost value with pm update.":null}}async function canonicalRealPath(value){try{return await realpath(value)}catch{return null}}async function verifyReceiptAgainstSnapshot(params){const receiptPath=await canonicalRealPath(path.resolve(params.gitWorkspaceRoot,params.receipt.item_path));if(receiptPath!==params.currentPath)return null;const declaredFields=[...new Set([...params.receipt.fields_from_theirs,...params.receipt.union_fields,...params.receipt.decisions.map(decision=>decision.field)])].sort();const hashes=params.receipt.merged_field_hashes;if(!hashes||declaredFields.length===0)return null;const hashFields=Object.keys(hashes).sort();if(declaredFields.length!==hashFields.length||declaredFields.some((field,index)=>field!==hashFields[index])){return null}const currentMetadata=params.currentItemReplay.metadata;const valuesMatch=declaredFields.every(field=>{const value=field==="body"?params.currentItemReplay.body:currentMetadata[field];return[hashItemScalarDecisionValue(value),sha256Hex(stableStringify(encodeItemScalarDecisionValue(value)))].includes(hashes[field])});return valuesMatch?{receipt:params.receipt,declaredFields}:null}async function verifyMergeReceiptProof(params){if(params.proof.gitWorkspaceRoot===null){return{trusted:false,reason:"git_workspace_unavailable",receipt_ids:[]}}if(params.itemPath===null||params.currentItemReplay===null){return{trusted:false,reason:"item_path_unavailable",receipt_ids:[]}}const itemReceipts=params.proof.receipts.filter(receipt=>receipt.item_id===params.id);if(itemReceipts.length===0){return{trusted:false,reason:"no_item_receipts",receipt_ids:[]}}const currentPath=await realpath(params.itemPath);const trustedReceipts=[];for(const receipt of itemReceipts){const trusted=await verifyReceiptAgainstSnapshot({receipt,gitWorkspaceRoot:params.proof.gitWorkspaceRoot,currentPath,currentItemReplay:params.currentItemReplay});if(trusted)trustedReceipts.push(trusted)}const trustedFields=new Set(trustedReceipts.flatMap(({declaredFields})=>declaredFields));const reconciliationFields=params.reconciliation?.reconciled_fields;if(trustedReceipts.length>0&&(reconciliationFields===void 0||reconciliationFields.length>0&&reconciliationFields.every(field=>trustedFields.has(field)))){return{trusted:true,reason:"trusted_merge_driver_hash_evidence",receipt_ids:trustedReceipts.map(({receipt})=>receipt.id)}}return{trusted:false,reason:"no_receipt_set_proves_snapshot",receipt_ids:[]}}async function assertHistoryRepairAbandonmentProof(proof,context,chainOk,reanchor){if(proof===void 0)return;const{receipt,gitWorkspaceRoot}=proof;const canonicalPath=context.currentItemPath===null?null:path.relative(gitWorkspaceRoot,context.currentItemPath).split(path.sep).join("/");if(!chainOk||reanchor.entriesRehashed!==0||reanchor.entriesPatchRepaired!==0||context.matchedChainBefore!==true||context.currentItemRawBeforeLock===null||canonicalPath!==receipt.item_path||receipt.operation===void 0||!await isOriginalGitStateRestored(gitWorkspaceRoot,receipt.item_path,receipt.operation,context.currentItemRawBeforeLock)){throw new PmCliError("Restored-origin receipt evidence no longer proves the exact clean item snapshot.",EXIT_CODE.CONFLICT,{code:"merge_reconcile_receipt_evidence_untrusted",recovery:{suggested_retry:"pm merge reconcile --dry-run"}})}}async function resolveHistoryRepairMergeEvidence(params){const requestedReceiptIds=new Set(params.options.mergeReceiptProof?.receipts.filter(receipt=>receipt.item_id===params.subjectId).map(receipt=>receipt.id)??[]);const authoritativeReceiptEvidence=params.options.mergeReceiptProof?await inspectMergeReceiptEvidence(params.options.mergeReceiptProof.gitWorkspaceRoot??process.cwd(),{includeReconciled:true,includeLossless:true,pmRoot:params.pmRoot}):void 0;const subjectMergeReceipts=authoritativeReceiptEvidence?.receipts.filter(receipt=>receipt.item_id===params.subjectId&&requestedReceiptIds.has(receipt.id))??[];const mergeReceiptProof=params.options.mergeReceiptProof?await verifyMergeReceiptProof({proof:{...params.options.mergeReceiptProof,receipts:subjectMergeReceipts},id:params.subjectId,itemPath:params.itemReplayContext.currentItemPath,currentItemReplay:params.itemReplayContext.currentItemReplay,reconciliation:params.reconciliation}):void 0;if(mergeReceiptProof&&!mergeReceiptProof.trusted&¶ms.options.force!==true){throw new PmCliError(`Merge receipt evidence for ${params.subjectId} does not prove the exact item snapshot (${mergeReceiptProof.reason}).`,EXIT_CODE.CONFLICT,{code:"merge_reconcile_receipt_evidence_untrusted",required:"Use authoritative clone-local or durable receipt evidence whose canonical path, declared fields, and merged-value hashes match the current item, or review the mismatch and rerun with --force.",recovery:{suggested_retry:"pm merge reconcile --dry-run"}})}const provenMergeReceipts=mergeReceiptProof?.trusted?subjectMergeReceipts.filter(receipt=>mergeReceiptProof.receipt_ids.includes(receipt.id)):[];return{mergeReceiptProof,effectiveAuditContext:provenMergeReceipts.length>0&¶ms.options.force!==true?{...params.options.auditContext,merge:{receipts:provenMergeReceipts.map(summarizeMergeReceipt)}}:params.options.auditContext}}function describeHistoryRepairItemReplay(subject,snapshot,itemHashVersion){const currentItemPath=subject.located?.itemPath??null;const loadedItem=snapshot.loadedItem;const historyEntries=snapshot.entries;if(!loadedItem){return{currentItemReplay:null,currentItemPath,matchedChainBefore:null,currentItemRawBeforeLock:null,loadedItem:null}}const currentItemReplay=toReplayDocument(loadedItem.document);const lastOriginalAfterHash=historyEntries[historyEntries.length-1].after_hash;return{currentItemReplay,currentItemPath,matchedChainBefore:replayHashVerificationCandidates(currentItemReplay,itemHashVersion,resolveHistoryHashAlgorithm(historyEntries.at(-1)?.hash_algorithm)).includes(lastOriginalAfterHash),currentItemRawBeforeLock:loadedItem.raw,loadedItem}}function buildHistoryRepairMessage(params){if(typeof params.message==="string"&¶ms.message.trim().length>0){return params.message}return`history-repair re-anchored ${params.entriesRehashed} entr${params.entriesRehashed===1?"y":"ies"}${params.entriesPatchRepaired>0?`, repaired ${params.entriesPatchRepaired} patch(es)`:""}${params.reconcileNeeded?", reconciled chain with on-disk item":""}.`}function buildHistoryRepairEntries(params){const rewrittenEntries=[...params.reanchorEntries];if(!params.changed){return{rewrittenEntries,auditEntryAdded:false}}const afterReplay=params.reconcileNeeded&¶ms.currentItemReplay?params.currentItemReplay:params.finalReplay;const algorithm=resolveHistoryHashAlgorithm(params.reanchorEntries.at(-1)?.hash_algorithm);const beforeHashes=replayHashVerificationCandidates(params.finalReplay,params.itemHashVersion,algorithm);const previousAfterHash=params.reanchorEntries[params.reanchorEntries.length-1].after_hash;const semanticIndex=beforeHashes.indexOf(previousAfterHash);const afterHashes=replayHashVerificationCandidates(afterReplay,params.itemHashVersion,algorithm);const auditEntry={hash_algorithm:algorithm,ts:nowIso(),author:params.author,op:params.auditOperation,patch:params.reconcileNeeded&¶ms.currentItemReplay?jsonPatch.compare(params.finalReplay,params.currentItemReplay):[],before_hash:beforeHashes[semanticIndex],after_hash:afterHashes[semanticIndex],message:params.message,...params.explicitItemHashVersion?{item_hash_version:params.itemHashVersion}:{},...params.auditContext===void 0?{}:{context:params.auditContext}};rewrittenEntries.push(sealHistoryRecord({...auditEntry,event_class:classifyHistoryEvent(auditEntry)}));return{rewrittenEntries,auditEntryAdded:true}}function collectHistoryRepairWarnings(changed,skippedOps,reconciliation){const warnings=[];if(!changed){warnings.push("history_repair_no_changes")}if(skippedOps>0){warnings.push(`history_repair_skipped_unresolvable_ops:${skippedOps}`)}if(reconciliation&&reconciliation.discarded_events.length>0){warnings.push(`history_repair_reconcile_discards_events:${reconciliation.discarded_events.length}`,`history_repair_discarded_authors:${reconciliation.discarded_authors.join(",")}`)}return warnings}async function runHistoryRepair(id,options,global2){const{pmRoot,settings,typeRegistry}=await resolveHistoryWorkspace(global2.path);const subject=await resolveHistorySubject(pmRoot,id,settings,typeRegistry.type_to_folder);if(options.salvageTail===true){return salvageHistoryTail({pmRoot,subject,settings,typeRegistry,options,author:resolveAuthor(options.author,settings.author_default)})}return runHistoryMaintenance({pmRoot,subject,settings,typeRegistry,operation:"history-repair",options,transform:snapshot=>buildHistoryRepairPlan({pmRoot,subject,options},snapshot)})}function shouldAppendForcedHistoryAudit(options,historyEntries){const alreadyRecorded=options.mergeAbandonmentProof!==void 0&&options.auditContext!==void 0&&historyEntries.some(entry=>entry.op==="merge_reconcile"&&stableStringify(entry.context)===stableStringify(options.auditContext));return options.forceAuditEntry===true&&!alreadyRecorded}async function buildHistoryRepairPlan(params,snapshot){const{pmRoot,subject,options}=params;const historyEntries=snapshot.entries;const chainBefore=snapshot.verification;const chainVersionBefore=verifyHistoryChainWithVersion(historyEntries);const provenanceNormalization=options.normalizeProvenance?normalizeInvalidHistoryProvenance(historyEntries):{entries:historyEntries,receipt:{changed:false,events_changed:0,observations_removed:0,invalid_values:[]}};const itemHashVersion=resolveHistoryRepairItemHashVersion(provenanceNormalization.entries);const explicitVersionsBefore=new Set(provenanceNormalization.entries.map(entry=>entry.item_hash_version).filter(version=>version!==void 0));const reanchor=reanchorHistoryEntries(provenanceNormalization.entries,itemHashVersion);const itemReplayContext=describeHistoryRepairItemReplay(subject,snapshot,itemHashVersion);await assertHistoryRepairAbandonmentProof(options.mergeAbandonmentProof,itemReplayContext,chainBefore.ok,reanchor);const finalReplay=reanchor.finalDocument;const finalReplayHashes=replayHashVerificationCandidates(finalReplay,itemHashVersion);let reconcileNeeded=false;if(itemReplayContext.currentItemReplay!==null){const currentItemReplayHashes=replayHashVerificationCandidates(itemReplayContext.currentItemReplay,itemHashVersion);reconcileNeeded=!finalReplayHashes.some((hash,hashIndex)=>hash===currentItemReplayHashes[hashIndex])}const reconciliation=reconcileNeeded&&itemReplayContext.currentItemReplay?analyzeReconciliationDiscard(reanchor.entries,finalReplay,itemReplayContext.currentItemReplay):void 0;const{mergeReceiptProof,effectiveAuditContext}=await resolveHistoryRepairMergeEvidence({options,pmRoot,subjectId:subject.id,itemReplayContext,reconciliation});const changed=[reanchor.entriesRehashed>0,reanchor.entriesPatchRepaired>0,reconcileNeeded,shouldAppendForcedHistoryAudit(options,historyEntries),provenanceNormalization.receipt.changed].some(Boolean);const author=snapshot.author;const dryRun=Boolean(options.dryRun);const repairMessage=buildHistoryRepairMessage({message:options.message,entriesRehashed:reanchor.entriesRehashed,entriesPatchRepaired:reanchor.entriesPatchRepaired,reconcileNeeded});const{rewrittenEntries,auditEntryAdded}=buildHistoryRepairEntries({reanchorEntries:reanchor.entries,changed,reconcileNeeded,currentItemReplay:itemReplayContext.currentItemReplay,finalReplay,author,message:repairMessage,auditOperation:options.auditOperation??"history_repair",auditContext:provenanceNormalization.receipt.changed?{...effectiveAuditContext,provenance_normalization:provenanceNormalization.receipt}:effectiveAuditContext,itemHashVersion,explicitItemHashVersion:reanchor.explicitItemHashVersion});const warnings=collectHistoryRepairWarnings(changed,reanchor.skippedOps,reconciliation);return{changed,rewrittenEntries,beforeWrite:async()=>assertHistoryRepairAbandonmentProof(options.mergeAbandonmentProof,itemReplayContext,chainBefore.ok,reanchor),report:({verification:historyVerify,warnings:writeWarnings})=>({id:subject.id,dry_run:dryRun,changed,history:{path:subject.historyPath,entries_scanned:historyEntries.length,chain_drift_before:!chainBefore.ok,entries_rehashed:reanchor.entriesRehashed,entries_patch_repaired:reanchor.entriesPatchRepaired,converted_replace_to_add:reanchor.convertedReplaceToAdd,skipped_ops:reanchor.skippedOps,reconciled_with_item:reconcileNeeded,audit_entry_added:auditEntryAdded,verify_ok:historyVerify.ok,verify_errors:historyVerify.errors,item_hash_version_before:chainVersionBefore.item_hash_version??null,item_hash_version_after:itemHashVersion,version_disposition:explicitVersionsBefore.size>1?"selected_for_ambiguous_stream":"preserved"},item:{exists:itemReplayContext.currentItemPath!==null,path:itemReplayContext.currentItemPath,matched_chain_before:itemReplayContext.matchedChainBefore},provenance_normalization:{requested:options.normalizeProvenance===true,...provenanceNormalization.receipt},...reconciliation?{reconciliation}:{},...mergeReceiptProof?{merge_receipt_proof:mergeReceiptProof}:{},warnings:[...new Set([...warnings,...writeWarnings])].sort((left,right)=>left.localeCompare(right)),generated_at:nowIso()})}}function assertHistoryRepairTarget(id,all){if(all&&id!==void 0){throw new PmCliError("history-repair: <id> and --all are mutually exclusive; pass an item id to repair one stream or --all to repair every drifted stream.",EXIT_CODE.USAGE)}if(!all&&id===void 0){throw new PmCliError("history-repair: provide an item <id> or pass --all to repair every drifted stream.",EXIT_CODE.USAGE)}}async function runHistoryRepairAll(options,global2){if(options.salvageTail===true){throw new PmCliError("Tail salvage requires one explicit item ID; --all is not supported.",EXIT_CODE.USAGE)}const{pmRoot,settings,typeRegistry}=await resolveHistoryWorkspace(global2.path);const itemReadWarnings=[];const items=await listAllItemMetadataWithBody(pmRoot,settings.item_format,typeRegistry.type_to_folder,itemReadWarnings,settings.schema,{forceSourceScan:true});const drift=await scanHistoryDrift(pmRoot,items);const invalidProvenanceIds=options.normalizeProvenance?await listInvalidProvenanceHistoryStreamIds(pmRoot):[];const targetedIds=[...new Set([...drift.driftedItems,...invalidProvenanceIds])].sort((left,right)=>left.localeCompare(right));const streams=[];const repairWarnings=[];const totals={repaired:0,skipped_clean:0,failed:0};for(const driftedId of targetedIds){try{const result=await runHistoryRepair(driftedId,{...options,auditContext:options.auditContextById?.[driftedId],auditContextById:void 0,mergeReceiptProof:options.mergeReceiptProofById?.[driftedId]??options.mergeReceiptProof,mergeReceiptProofById:void 0},global2);const outcome=result.changed?"repaired":"skipped_clean";totals[outcome]+=1;repairWarnings.push(...result.warnings.map(warning=>`${driftedId}:${warning}`));streams.push({id:driftedId,outcome,entries_rehashed:result.history.entries_rehashed,entries_patch_repaired:result.history.entries_patch_repaired,reconciled_with_item:result.history.reconciled_with_item,...result.reconciliation?{reconciliation:result.reconciliation}:{},...result.merge_receipt_proof?{merge_receipt_proof:result.merge_receipt_proof}:{},warnings:result.warnings})}catch(error){totals.failed+=1;streams.push({id:driftedId,outcome:"failed",error:error instanceof Error?error.message:String(error)})}}if(!options.dryRun){await invalidateHistoryDriftCache(pmRoot)}return{all:true,dry_run:Boolean(options.dryRun),scanned_streams:items.length,drifted_streams:drift.driftedItems.length,provenance_invalid_streams:invalidProvenanceIds.length,streams,totals,warnings:[...new Set([...itemReadWarnings,...repairWarnings])].sort((left,right)=>left.localeCompare(right)),generated_at:nowIso()}}export{analyzeReconciliationDiscard,assertHistoryRepairTarget,runHistoryRepair,runHistoryRepairAll};
|
|
2
2
|
//# sourceMappingURL=history-repair.js.map
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Defines public SDK APIs and package-author helpers for Index.
|
|
5
5
|
*/
|
|
6
|
+
export type { CompletionRuntimeConfig } from "./completion.js";
|
|
6
7
|
export { PM_COMMAND_CAPABILITY_CONTRACTS, PM_COMMAND_VISIBILITY_CONTRACTS, PM_MCP_PROMPT_CONTRACTS, PM_MCP_RESOURCE_CONTRACTS, PM_MCP_TOOL_COMMAND_CONTRACTS, listPmCommandsForFamily, listPmCommandsForTier, listPmMcpToolsForProfile, renderPmCommandVisibilityMarkdown, resolvePmCommandCapabilityFamily, resolvePmCommandVisibilityTier, } from "./agent-capability-contracts.js";
|
|
7
8
|
export * from "./mcp/protocol.js";
|
|
8
9
|
export * from "./mcp/interactions.js";
|
|
@@ -26,7 +27,7 @@ export { PM_REPRODUCIBLE_PROCESS_ENV, createReproducibleProcessRunner, resolveRe
|
|
|
26
27
|
export { PM_WORKSPACE_RECIPE_SCHEMA, defineWorkspaceRecipe, executeWorkspaceRecipe, runWithWorkspaceRecipe, type WorkspaceRecipe, type WorkspaceRecipeJsonValue, type WorkspaceRecipeOperation, } from "./workspace-recipe.js";
|
|
27
28
|
export { SNAPSHOT_SCHEMA, WORKSPACE_SNAPSHOT_ACTIONS, createWorkspaceSnapshot, deleteWorkspaceSnapshot, inspectWorkspaceSnapshot, listWorkspaceSnapshots, planWorkspaceSnapshotRestore, restoreWorkspaceSnapshot, restoreWorkspaceSnapshotWithRecovery, type CreateWorkspaceSnapshotResult, type RestoreWorkspaceSnapshotOptions, type RestoreWorkspaceSnapshotResult, type WorkspaceSnapshotHistoryImpact, type WorkspaceSnapshotManifest, type WorkspaceSnapshotReference, type WorkspaceSnapshotRestorePlan, } from "./workspace-snapshot.js";
|
|
28
29
|
export { flushTelemetryQueue } from "./telemetry-flush.js";
|
|
29
|
-
export { PM_HISTORY_COMMAND_ALIASES, PM_CONTEXT_OPS_COMMAND_ALIASES, PM_BULK_LIFECYCLE_COMMAND_ALIASES, PM_RELOCATED_COMMAND_ALIASES, PM_NAMESPACED_COMMAND_ALIASES, resolvePmCommandOperation, resolvePmHistoryOperation } from "./cli-contracts.js";
|
|
30
|
+
export { PM_HISTORY_COMMAND_ALIASES, PM_CONTEXT_OPS_COMMAND_ALIASES, PM_BULK_LIFECYCLE_COMMAND_ALIASES, PM_RELOCATED_COMMAND_ALIASES, PM_NAMESPACED_COMMAND_ALIASES, findPmNamespacedCommand, resolvePmCommandOperation, resolvePmHistoryOperation } from "./cli-contracts.js";
|
|
30
31
|
export { EXECUTABLE_COMMAND_ALIASES, PM_COMMAND_ALIAS_CONTRACTS, renderPmCommandAliasMigrationHint, resolvePmCommandAlias, type PmCommandAliasContract, type PmCommandAliasLifecycle, type PmCommandAliasRegistration, applyBootstrapPagerPolicy, coalesceRepeatedListFlags, listAliasPluralKeys, mergeLinkedTestTwoTokenEntries, normalizeBootstrapInvocation, normalizeLegacyExtensionActionSyntax, parseBootstrapCommandName, parseBootstrapGlobalOptions, parseBootstrapHelpRequest, parseBootstrapTypeValue, stripGlobalBootstrapTokens, type BootstrapGlobalOptions, type BootstrapHelpRequest, type BootstrapInvocationNormalizationResult, type BootstrapNormalizationEvent, } from "./cli-bootstrap.js";
|
|
31
32
|
export { quoteCommandArg, quoteWindowsCommandArg, renderPmCommand, } from "./command-line.js";
|
|
32
33
|
export * from "./agent/command-recovery.js";
|
|
@@ -105,6 +106,7 @@ export * from "./duplicates.js";
|
|
|
105
106
|
export * from "./lifecycle/reopen.js";
|
|
106
107
|
export * from "./history-compact.js";
|
|
107
108
|
export * from "./history-read.js";
|
|
109
|
+
export * from "../core/history/operation-contract.js";
|
|
108
110
|
export * from "./history/maintenance.js";
|
|
109
111
|
export { readHistorySnapshot, type HistoryStreamSnapshot } from "../core/history/read.js";
|
|
110
112
|
export * from "./history-provenance.js";
|