@unbrained/pm-cli 2026.9.4 → 2026.9.6
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/.agents/skills/HARNESS_COMPATIBILITY.md +1 -1
- package/.agents/skills/README.md +2 -2
- package/.agents/skills/pm-developer/SKILL.md +4 -1
- package/.agents/skills/pm-developer/references/COMMAND_PLAYBOOK.md +10 -0
- package/.agents/skills/pm-developer/references/GRAPH_AND_RELATIONSHIPS.md +3 -1
- package/.agents/skills/pm-extensions/SKILL.md +2 -2
- package/.agents/skills/pm-extensions/references/LIFECYCLE.md +3 -3
- package/.agents/skills/pm-extensions/references/TROUBLESHOOTING.md +3 -3
- package/.agents/skills/pm-user/SKILL.md +2 -0
- package/.agents/skills/pm-user/references/BACKLOG_SHAPING.md +5 -0
- package/.agents/skills/pm-user/references/WORKFLOWS.md +20 -5
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +58 -3
- package/dist/cli/commander-usage.d.ts +2 -0
- package/dist/cli/commander-usage.js +15 -12
- package/dist/cli/help-json-payload.js +12 -7
- package/dist/cli/main.js +22 -11
- package/dist/cli/register-list-query.js +4 -2
- package/dist/cli/register-mutation.js +4 -2
- package/dist/cli/register-operations.js +17 -8
- package/dist/cli-bundle/bundle-manifest.json +153 -161
- package/dist/cli-bundle/chunks/chunk-AZDFJGH6.js +9 -0
- package/dist/cli-bundle/chunks/{chunk-V72UV7QS.js → chunk-BAX2BYUW.js} +73 -73
- package/dist/cli-bundle/chunks/{chunk-HI6BHPCU.js → chunk-CKOL5U7J.js} +51 -51
- package/dist/cli-bundle/chunks/{chunk-A24WAVE6.js → chunk-JT6XF6GE.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-I2AS367G.js → chunk-JX4DH4WC.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-YW4QK2G7.js → chunk-NPN5I67J.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-OF76PSDQ.js +5 -0
- package/dist/cli-bundle/chunks/chunk-QUWFABDM.js +232 -0
- package/dist/cli-bundle/chunks/{chunk-RI4XNOYS.js → chunk-RWWBOWJ5.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-WGAOYSYH.js +40 -0
- package/dist/cli-bundle/chunks/{chunk-X6CMGV6L.js → chunk-WI2AYPA7.js} +2 -2
- package/dist/cli-bundle/chunks/{register-list-query-Z5YYSJJG.js → register-list-query-5JL52D33.js} +3 -3
- package/dist/cli-bundle/chunks/{register-mutation-ELSP6I4I.js → register-mutation-ZPG7KMVP.js} +4 -4
- package/dist/cli-bundle/chunks/register-operations-I5W3KMGB.js +2 -0
- package/dist/cli-bundle/chunks/{register-setup-E2AE72QG.js → register-setup-H4SVAVX7.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-3646BZ4W.js → chunk-3BZEOXLT.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-AL5ON6K6.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-CHC2IDXA.js +6 -0
- package/dist/cli-bundle/focused-chunks/chunk-CK6QFAPZ.js +20 -0
- package/dist/cli-bundle/focused-chunks/{chunk-NBMJD6MK.js → chunk-EZR3GHFE.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-H2WJWK7L.js +56 -0
- package/dist/cli-bundle/focused-chunks/{chunk-53IHPICX.js → chunk-LIJIA4TY.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-25UJBH4M.js → chunk-M6PBRFSH.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-AJR3OP2E.js → chunk-NTPY5GSO.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-P5O7MP5Y.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-PDMW2E36.js +177 -0
- package/dist/cli-bundle/focused-chunks/{chunk-MHKUIPJW.js → chunk-PEFABF7E.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-6PWPQKDN.js → chunk-QTVCLMSZ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-7N2TV7WZ.js → chunk-QTZRBUYA.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-SGQST3PQ.js +12 -0
- package/dist/cli-bundle/focused-chunks/chunk-UKHASMMP.js +10 -0
- package/dist/cli-bundle/focused-chunks/chunk-ZPEKQHOC.js +2 -0
- package/dist/cli-bundle/main.js +22 -15
- 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 +37 -37
- 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 -39
- 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 +9 -9
- package/dist/core/extensions/extension-types.d.ts +2 -0
- package/dist/core/extensions/extension-types.js +2 -2
- package/dist/core/extensions/module-graph-snapshot.js +5 -4
- package/dist/core/fs/host-environment-errors.d.ts +27 -0
- package/dist/core/fs/host-environment-errors.js +142 -0
- package/dist/core/history/drift-scan.d.ts +5 -0
- package/dist/core/history/drift-scan.js +16 -6
- package/dist/core/history/event-classification.d.ts +1 -1
- package/dist/core/history/event-classification.js +3 -2
- package/dist/core/history/history.js +37 -5
- package/dist/core/history/identity.d.ts +15 -0
- package/dist/core/history/identity.js +31 -0
- package/dist/core/history/projection.js +50 -17
- package/dist/core/history/read.js +6 -3
- package/dist/core/history/replay.d.ts +1 -2
- package/dist/core/history/replay.js +4 -4
- package/dist/core/history/version-address.d.ts +13 -0
- package/dist/core/history/version-address.js +33 -0
- package/dist/core/item/actionability.d.ts +21 -1
- package/dist/core/item/actionability.js +45 -10
- package/dist/core/item/id.js +8 -4
- package/dist/core/item/item-type-definition.d.ts +2 -0
- package/dist/core/item/item-type-definition.js +9 -2
- package/dist/core/item/type-registry.d.ts +5 -1
- package/dist/core/item/type-registry.js +9 -4
- package/dist/core/output/mutation-projection.js +12 -5
- package/dist/core/shared/errors.d.ts +8 -0
- package/dist/core/shared/errors.js +2 -2
- package/dist/core/store/item-metadata-query-index.js +13 -10
- package/dist/core/store/item-metadata-read-work.d.ts +19 -0
- package/dist/core/store/item-metadata-read-work.js +44 -0
- package/dist/core/store/item-store.d.ts +14 -0
- package/dist/core/store/item-store.js +52 -10
- package/dist/core/store/settings-validator.js +3 -2
- package/dist/core/validate/fix-planning.d.ts +1 -1
- package/dist/core/validate/fix-planning.js +2 -2
- package/dist/core/validate/missing-link-owners.d.ts +1 -1
- package/dist/core/validate/missing-link-owners.js +5 -3
- package/dist/core/validate/stale-file-classification.d.ts +6 -4
- package/dist/core/validate/stale-file-classification.js +20 -5
- package/dist/mcp/server.js +12 -5
- package/dist/sdk/actionability.d.ts +1 -1
- package/dist/sdk/actionability.js +3 -3
- package/dist/sdk/cli-contracts/command-aliases.js +11 -2
- package/dist/sdk/cli-contracts/completeness.js +4 -3
- package/dist/sdk/cli-contracts/flag-contracts.d.ts +2 -0
- package/dist/sdk/cli-contracts/flag-contracts.js +31 -5
- package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +21 -13
- package/dist/sdk/cli-contracts/grammar-contracts.js +11 -2
- package/dist/sdk/cli-contracts/runtime-contracts.js +5 -2
- package/dist/sdk/cli-contracts/schemas/assurance-definition.d.ts +254 -5
- package/dist/sdk/cli-contracts/schemas/assurance-definition.js +52 -2
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +30 -6
- package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
- package/dist/sdk/cli-contracts/tool-schema.js +32 -7
- package/dist/sdk/context/usage-ledger.d.ts +41 -0
- package/dist/sdk/context/usage-ledger.js +186 -0
- package/dist/sdk/context-intent-contracts.js +3 -3
- package/dist/sdk/context-usage.d.ts +18 -9
- package/dist/sdk/context-usage.js +44 -121
- package/dist/sdk/define.d.ts +1 -1
- package/dist/sdk/define.js +2 -2
- package/dist/sdk/dependency-provenance.d.ts +7 -0
- package/dist/sdk/dependency-provenance.js +14 -4
- package/dist/sdk/duplicates.d.ts +2 -0
- package/dist/sdk/duplicates.js +3 -2
- package/dist/sdk/environment/host-environment-errors.d.ts +5 -27
- package/dist/sdk/environment/host-environment-errors.js +4 -137
- package/dist/sdk/extension/install-runtime.js +9 -6
- package/dist/sdk/extension/install-snapshot.js +7 -6
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +104 -56
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +50 -2
- package/dist/sdk/governance/assurance-action.d.ts +3 -2
- package/dist/sdk/governance/assurance-action.js +23 -11
- package/dist/sdk/governance/assurance-runtime.d.ts +2 -0
- package/dist/sdk/governance/assurance-runtime.js +6 -3
- package/dist/sdk/governance/defect-recurrence-coverage.d.ts +93 -0
- package/dist/sdk/governance/defect-recurrence-coverage.js +189 -0
- package/dist/sdk/governance/defect-recurrence-graph.d.ts +33 -0
- package/dist/sdk/governance/defect-recurrence-graph.js +121 -0
- package/dist/sdk/governance/defect-recurrence.d.ts +8 -0
- package/dist/sdk/governance/defect-recurrence.js +118 -17
- package/dist/sdk/governance/health.d.ts +17 -2
- package/dist/sdk/governance/health.js +31 -10
- package/dist/sdk/governance/linked-file-report.d.ts +41 -0
- package/dist/sdk/governance/linked-file-report.js +72 -0
- package/dist/sdk/governance/validate-history-drift.js +16 -5
- package/dist/sdk/governance/validate.d.ts +5 -2
- package/dist/sdk/governance/validate.js +21 -37
- package/dist/sdk/governance.d.ts +2 -0
- package/dist/sdk/governance.js +4 -2
- package/dist/sdk/guide-topics.js +116 -2
- package/dist/sdk/history/salvage.d.ts +35 -0
- package/dist/sdk/history/salvage.js +248 -0
- package/dist/sdk/history-compact.js +32 -37
- package/dist/sdk/history-read.d.ts +3 -2
- package/dist/sdk/history-read.js +6 -3
- package/dist/sdk/history-repair.d.ts +12 -0
- package/dist/sdk/history-repair.js +60 -13
- package/dist/sdk/lifecycle/claim.d.ts +4 -0
- package/dist/sdk/lifecycle/claim.js +9 -3
- package/dist/sdk/lifecycle/close.d.ts +8 -0
- package/dist/sdk/lifecycle/close.js +16 -8
- package/dist/sdk/lifecycle/create.js +9 -12
- package/dist/sdk/lifecycle/restore.d.ts +1 -0
- package/dist/sdk/lifecycle/restore.js +19 -7
- package/dist/sdk/lifecycle/update.js +12 -12
- package/dist/sdk/lifecycle-policy.d.ts +3 -0
- package/dist/sdk/lifecycle-policy.js +2 -2
- package/dist/sdk/merge/abandoned-receipts.d.ts +15 -0
- package/dist/sdk/merge/abandoned-receipts.js +47 -0
- package/dist/sdk/merge/driver.js +4 -2
- package/dist/sdk/merge/index.d.ts +1 -0
- package/dist/sdk/merge/index.js +2 -2
- package/dist/sdk/merge/receipt-operation.d.ts +15 -0
- package/dist/sdk/merge/receipt-operation.js +97 -0
- package/dist/sdk/merge/receipt-schema.d.ts +20 -0
- package/dist/sdk/merge/receipt-schema.js +398 -0
- package/dist/sdk/merge/receipts.d.ts +16 -7
- package/dist/sdk/merge/receipts.js +54 -340
- package/dist/sdk/merge/reconcile.d.ts +2 -0
- package/dist/sdk/merge/reconcile.js +11 -4
- package/dist/sdk/output-projection.d.ts +3 -1
- package/dist/sdk/output-projection.js +11 -3
- package/dist/sdk/public-surface.json +1 -0
- package/dist/sdk/query/context.d.ts +9 -0
- package/dist/sdk/query/context.js +33 -21
- package/dist/sdk/query/duplicate-candidates.d.ts +7 -0
- package/dist/sdk/query/duplicate-candidates.js +71 -0
- package/dist/sdk/query/get.d.ts +2 -2
- package/dist/sdk/query/get.js +9 -8
- package/dist/sdk/query/history.d.ts +11 -0
- package/dist/sdk/query/history.js +34 -10
- package/dist/sdk/query/next.d.ts +15 -1
- package/dist/sdk/query/next.js +43 -20
- package/dist/sdk/query.d.ts +3 -0
- package/dist/sdk/query.js +5 -2
- package/dist/sdk/read-output-session.js +5 -3
- package/dist/sdk/runtime-primitives.d.ts +1 -1
- package/dist/sdk/runtime-primitives.js +3 -3
- package/dist/sdk/runtime.js +4 -2
- package/dist/sdk/similarity.d.ts +24 -3
- package/dist/sdk/similarity.js +50 -55
- package/dist/types.d.ts +2 -0
- package/dist/types.js +2 -2
- package/docs/AGENT_GUIDE.md +58 -1
- package/docs/BOUNDED_CONTEXT_READS.md +185 -0
- package/docs/CLOSE_EVIDENCE_RECOVERY.md +35 -0
- package/docs/COMMANDS.md +30 -8
- package/docs/CONTEXT_RELEVANCE.md +16 -4
- package/docs/DEFECT_RECURRENCE.md +80 -6
- package/docs/EXTENSIONS.md +18 -19
- package/docs/HISTORY_RECOVERY.md +146 -0
- package/docs/MERGE_SAFETY.md +50 -0
- package/docs/OUTPUT_PROJECTION_CONTRACTS.md +66 -0
- package/docs/PR_REVIEW_LOOP.md +18 -3
- package/docs/README.md +19 -1
- package/docs/RELEASING.md +31 -13
- package/docs/SDK_CONTEXT_TRUTH_CONTRACTS.md +24 -1
- package/docs/SDK_EXECUTION_CONTRACTS.md +105 -0
- package/docs/SDK_LIFECYCLE.md +27 -0
- package/docs/SDK_RUNTIME_BOUNDARIES.md +11 -0
- package/docs/TESTING.md +5 -0
- package/docs/agent-task-token-baseline.json +17 -17
- package/docs/agent-task-transcripts.json +2 -1
- package/docs/generated/FLAG_LEXICON_BUDGETS.md +11 -11
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +11 -3
- package/marketplace.json +2 -2
- package/package.json +8 -6
- 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 +10 -1
- package/packages/pm-governance-audit/extensions/governance-audit/dedupe-audit.ts +50 -65
- package/packages/pm-governance-audit/extensions/governance-audit/index.ts +2 -2
- package/packages/pm-governance-audit/extensions/governance-audit/sdk.ts +1 -0
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-claude/skills/pm-audit/SKILL.md +2 -2
- package/plugins/pm-claude/skills/pm-developer/SKILL.md +2 -2
- package/plugins/pm-claude/skills/pm-planner/SKILL.md +2 -2
- package/plugins/pm-claude/skills/pm-release/SKILL.md +2 -2
- package/plugins/pm-claude/skills/pm-workflow/SKILL.md +3 -3
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/pm-codex/skills/pm-auditor/SKILL.md +2 -2
- package/plugins/pm-codex/skills/pm-native/SKILL.md +3 -4
- package/plugins/pm-codex/skills/pm-release/SKILL.md +2 -2
- package/scripts/finalize-build.mjs +13 -1
- package/dist/cli-bundle/chunks/chunk-6XSKPNCF.js +0 -230
- package/dist/cli-bundle/chunks/chunk-OGBVU67G.js +0 -5
- package/dist/cli-bundle/chunks/chunk-TXW6VL33.js +0 -38
- package/dist/cli-bundle/chunks/chunk-VGLHECAP.js +0 -9
- package/dist/cli-bundle/chunks/register-operations-OH7R3ZT4.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-4YRAVI43.js +0 -12
- package/dist/cli-bundle/focused-chunks/chunk-5P4HVOWG.js +0 -176
- package/dist/cli-bundle/focused-chunks/chunk-636VVQ7F.js +0 -54
- package/dist/cli-bundle/focused-chunks/chunk-B3A6MJYE.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-BFDDJ2T2.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-BHUIRZWQ.js +0 -9
- package/dist/cli-bundle/focused-chunks/chunk-JADHHPY6.js +0 -6
- package/dist/cli-bundle/focused-chunks/chunk-NSKBLHVI.js +0 -20
- package/dist/cli-bundle/focused-chunks/chunk-P2GOAOQN.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-YARWJM2B.js +0 -2
- package/sdk/public-surface.json +0 -33034
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Stats } from "node:fs";
|
|
2
2
|
import { type ItemMergeConflictDecision, type ItemScalarConflictResolution, type MergePreferredSide } from "./three-way.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import type { MergeReceiptOperation } from "./receipt-operation.js";
|
|
4
|
+
import { DURABLE_VALUE_POLICY } from "./receipt-schema.js";
|
|
5
|
+
export { isSafeReceiptId } from "./receipt-schema.js";
|
|
6
6
|
/** One clone-local merge receipt with recoverable branch values. */
|
|
7
7
|
export interface MergeDecisionReceipt {
|
|
8
8
|
/** Receipt schema version. */
|
|
@@ -29,7 +29,11 @@ export interface MergeDecisionReceipt {
|
|
|
29
29
|
merged_field_hashes?: Record<string, string>;
|
|
30
30
|
/** Full recoverable scalar decisions, retained only in the clone. */
|
|
31
31
|
decisions: ItemMergeConflictDecision[];
|
|
32
|
-
/**
|
|
32
|
+
/** Immutable original Git coordinates captured while the driver runs during a rebase. */
|
|
33
|
+
operation?: MergeReceiptOperation;
|
|
34
|
+
/** Explicit audited disposition when the original Git state was restored instead of merging. */
|
|
35
|
+
settlement?: "original_git_state_restored";
|
|
36
|
+
/** Whether a reconciliation history event settled this receipt. */
|
|
33
37
|
state: "pending" | "reconciled";
|
|
34
38
|
/** Receipt creation timestamp. */
|
|
35
39
|
created_at: string;
|
|
@@ -121,7 +125,9 @@ export interface MergeReceiptInvalidEvidence {
|
|
|
121
125
|
/** Stable failure class suitable for remediation routing and graph analytics. */
|
|
122
126
|
reason: "directory_unreadable" | "candidate_not_bounded_regular_file" | "candidate_unreadable" | "candidate_invalid_json" | "schema_or_identity_invalid" | "schema_invalid" | "identity_invalid" | "copy_provenance_mismatch";
|
|
123
127
|
/** Exact validation boundary when schema or identity evidence is rejected. */
|
|
124
|
-
validation_error?: "required_fields" | "receipt_id" | "item_id" | "item_path" | "filename" | "collections" | "enums" | "timestamps" | "durable_decisions";
|
|
128
|
+
validation_error?: "required_fields" | "receipt_id" | "item_id" | "item_path" | "filename" | "collections" | "enums" | "timestamps" | "durable_decisions" | "operation";
|
|
129
|
+
/** Exact schema coordinate, excluding rejected values and user-controlled field names. */
|
|
130
|
+
validation_path?: string;
|
|
125
131
|
/** Receipt identity when the bounded filename itself is a valid receipt id. */
|
|
126
132
|
receipt_id?: string;
|
|
127
133
|
/** SHA-256 locator for an unsafe or malformed candidate filename. */
|
|
@@ -133,11 +139,13 @@ declare function prepareReceiptSettlement(params: {
|
|
|
133
139
|
receiptId: string;
|
|
134
140
|
evidenceSource: "clone_local" | "durable";
|
|
135
141
|
reconciledAt: string;
|
|
142
|
+
settlement?: "original_git_state_restored";
|
|
136
143
|
readReceipt?: (receiptPath: string) => Promise<string | null>;
|
|
137
144
|
}): Promise<{
|
|
138
145
|
path: string;
|
|
139
146
|
content: string;
|
|
140
|
-
|
|
147
|
+
fingerprints: string[];
|
|
148
|
+
modern: boolean;
|
|
141
149
|
} | null>;
|
|
142
150
|
/** Convert a raw receipt into privacy-safe history context. */
|
|
143
151
|
export declare function summarizeMergeReceipt(receipt: MergeDecisionReceipt): MergeDecisionReceiptSummary;
|
|
@@ -151,6 +159,7 @@ export declare function writeMergeReceipt(params: {
|
|
|
151
159
|
unionFields: string[];
|
|
152
160
|
mergedFieldHashes?: Record<string, string>;
|
|
153
161
|
decisions: ItemMergeConflictDecision[];
|
|
162
|
+
operation?: MergeReceiptOperation;
|
|
154
163
|
}): Promise<MergeDecisionReceipt | null>;
|
|
155
164
|
/** Distinguish a missing optional receipt store from an unreadable one. */
|
|
156
165
|
declare function receiptDirectoryFailureMeansAbsent(directory: string, error: unknown, inspectAncestor?: (ancestor: string) => Promise<Pick<Stats, "isDirectory">>): Promise<boolean>;
|
|
@@ -179,6 +188,7 @@ export declare function partitionMergeReceipts(receipts: MergeDecisionReceipt[])
|
|
|
179
188
|
/** Mark a receipt as represented by a committed merge history event. */
|
|
180
189
|
export declare function markMergeReceiptReconciled(cwd: string, receipt: MergeDecisionReceipt, options?: {
|
|
181
190
|
requireExisting?: boolean;
|
|
191
|
+
settlement?: "original_git_state_restored";
|
|
182
192
|
}): Promise<void>;
|
|
183
193
|
/** Report pending or historical clone-local merge decisions. */
|
|
184
194
|
export declare function runMergeReceiptReport(options: {
|
|
@@ -199,4 +209,3 @@ export declare const _testOnlyMergeReceipts: {
|
|
|
199
209
|
prepareReceiptSettlement: typeof prepareReceiptSettlement;
|
|
200
210
|
receiptDirectoryFailureMeansAbsent: typeof receiptDirectoryFailureMeansAbsent;
|
|
201
211
|
};
|
|
202
|
-
export {};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* value for the coordinator that performed the merge.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
10
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e7fe0fee-9519-593d-ac72-b3c7fb63563e")}catch(e){}}();
|
|
11
11
|
import { execFile } from "node:child_process";
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
13
|
import { readdir, stat } from "node:fs/promises";
|
|
@@ -15,74 +15,14 @@ import path from "node:path";
|
|
|
15
15
|
import { promisify } from "node:util";
|
|
16
16
|
import { ensureDir, isFileAbsentError, isFileMissingError, pathExists, writeFileAtomic, } from "../../core/fs/fs-utils.js";
|
|
17
17
|
import { sha256Hex, stableStringify } from "../../core/shared/serialization.js";
|
|
18
|
-
import {
|
|
19
|
-
import { hashItemScalarDecisionValue, isItemScalarMissingValue, } from "./three-way.js";
|
|
18
|
+
import { nowIso } from "../../core/shared/time.js";
|
|
19
|
+
import { encodeItemScalarDecisionValue, hashItemScalarDecisionValue, isItemScalarMissingValue, } from "./three-way.js";
|
|
20
20
|
import { readBoundedRegularFile } from "./receipt-file-boundary.js";
|
|
21
|
+
import { DURABLE_VALUE_POLICY, durableValueEvidence, isSafeReceiptId, isMergeDecisionReceipt, isPrehashedValue, normalizeLegacyReceipt, receiptCollectionValidationPath, receiptValidationError } from "./receipt-schema.js";
|
|
22
|
+
export { isSafeReceiptId } from "./receipt-schema.js";
|
|
21
23
|
const execFileAsync = promisify(execFile);
|
|
22
|
-
const RECEIPT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
|
|
23
|
-
const RECEIPT_ITEM_ID_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u;
|
|
24
|
-
const RECEIPT_FIELD_MAX = 2_048;
|
|
25
24
|
const RECEIPT_FILE_MAX_BYTES = 16 * 1024 * 1024;
|
|
26
|
-
const RECEIPT_VALUE_MAX_DEPTH = 64;
|
|
27
|
-
const RECEIPT_VALUE_MAX_NODES = 100_000;
|
|
28
25
|
const RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT = 100;
|
|
29
|
-
const RECEIPT_KEYS = new Set([
|
|
30
|
-
"version",
|
|
31
|
-
"id",
|
|
32
|
-
"item_path",
|
|
33
|
-
"item_id",
|
|
34
|
-
"requested_preference",
|
|
35
|
-
"requested_preference_applied",
|
|
36
|
-
"preferred",
|
|
37
|
-
"conflict_resolution",
|
|
38
|
-
"fields_from_theirs",
|
|
39
|
-
"union_fields",
|
|
40
|
-
"merged_field_hashes",
|
|
41
|
-
"decisions",
|
|
42
|
-
"state",
|
|
43
|
-
"created_at",
|
|
44
|
-
"reconciled_at",
|
|
45
|
-
"value_availability",
|
|
46
|
-
"value_policy",
|
|
47
|
-
"evidence_source",
|
|
48
|
-
]);
|
|
49
|
-
/** Return whether a receipt id is safe to expose in cleartext diagnostics. */
|
|
50
|
-
export function isSafeReceiptId(value) {
|
|
51
|
-
return RECEIPT_ID_PATTERN.test(value);
|
|
52
|
-
}
|
|
53
|
-
const RECEIPT_DECISION_KEYS = new Set([
|
|
54
|
-
"field",
|
|
55
|
-
"base",
|
|
56
|
-
"ours",
|
|
57
|
-
"theirs",
|
|
58
|
-
"retained",
|
|
59
|
-
"discarded",
|
|
60
|
-
"retained_side",
|
|
61
|
-
"resolution_basis",
|
|
62
|
-
]);
|
|
63
|
-
const DURABLE_VALUE_POLICY = "bounded_non_sensitive_scalars_v1";
|
|
64
|
-
const REVIEWABLE_BUILTIN_STATUSES = new Set([
|
|
65
|
-
"open",
|
|
66
|
-
"in_progress",
|
|
67
|
-
"blocked",
|
|
68
|
-
"closed",
|
|
69
|
-
"canceled",
|
|
70
|
-
]);
|
|
71
|
-
const REVIEWABLE_ORDINAL_FIELDS = new Set(["risk", "confidence", "severity"]);
|
|
72
|
-
const REVIEWABLE_ORDINAL_VALUES = new Set([
|
|
73
|
-
"low",
|
|
74
|
-
"medium",
|
|
75
|
-
"high",
|
|
76
|
-
"critical",
|
|
77
|
-
]);
|
|
78
|
-
const REVIEWABLE_DURABLE_VALUES_BY_FIELD = new Map([
|
|
79
|
-
["priority", new Set([0, 1, 2, 3, 4].map(stableStringify))],
|
|
80
|
-
["status", new Set([...REVIEWABLE_BUILTIN_STATUSES].map(stableStringify))],
|
|
81
|
-
...[...REVIEWABLE_ORDINAL_FIELDS].map((field) => [
|
|
82
|
-
field,
|
|
83
|
-
new Set([...REVIEWABLE_ORDINAL_VALUES].map(stableStringify)),
|
|
84
|
-
]),
|
|
85
|
-
]);
|
|
86
26
|
/** Resolve the clone-local receipt store without crossing a Git worktree boundary. */
|
|
87
27
|
async function resolveReceiptDirectory(cwd) {
|
|
88
28
|
try {
|
|
@@ -119,246 +59,13 @@ function durableReceiptDirectory(pmRoot) {
|
|
|
119
59
|
function receiptFileName(id) {
|
|
120
60
|
return `${id}.json`;
|
|
121
61
|
}
|
|
122
|
-
/** Narrow untrusted JSON to a non-array object before schema inspection. */
|
|
123
|
-
function isRecord(value) {
|
|
124
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
125
|
-
}
|
|
126
|
-
/** Refuse receipt extensions outside the versioned schema key set. */
|
|
127
|
-
function hasOnlyKeys(value, allowed) {
|
|
128
|
-
return Object.keys(value).every((key) => allowed.has(key));
|
|
129
|
-
}
|
|
130
|
-
/** Validate bounded dense string collections used in receipt coordinates. */
|
|
131
|
-
function isStringArray(value) {
|
|
132
|
-
return (Array.isArray(value) &&
|
|
133
|
-
value.length <= RECEIPT_FIELD_MAX &&
|
|
134
|
-
value.every((entry) => typeof entry === "string" && entry.length > 0 && entry.length <= 256));
|
|
135
|
-
}
|
|
136
|
-
/** Bound JSON depth and node count before retaining untrusted decision values. */
|
|
137
|
-
function hasBoundedJsonStructure(value) {
|
|
138
|
-
const pending = [
|
|
139
|
-
{ value, depth: 0 },
|
|
140
|
-
];
|
|
141
|
-
let nodes = 0;
|
|
142
|
-
while (pending.length > 0) {
|
|
143
|
-
const current = pending.pop();
|
|
144
|
-
nodes += 1;
|
|
145
|
-
if (nodes > RECEIPT_VALUE_MAX_NODES ||
|
|
146
|
-
current.depth > RECEIPT_VALUE_MAX_DEPTH) {
|
|
147
|
-
return false;
|
|
148
|
-
}
|
|
149
|
-
if (Array.isArray(current.value)) {
|
|
150
|
-
for (const entry of current.value) {
|
|
151
|
-
pending.push({ value: entry, depth: current.depth + 1 });
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
else if (isRecord(current.value)) {
|
|
155
|
-
for (const entry of Object.values(current.value)) {
|
|
156
|
-
pending.push({ value: entry, depth: current.depth + 1 });
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
/** Validate one clone-local scalar decision and its optional resolution evidence. */
|
|
163
|
-
function isMergeDecision(value) {
|
|
164
|
-
if (!isRecord(value))
|
|
165
|
-
return false;
|
|
166
|
-
const retainedSides = [undefined, "ours", "theirs"];
|
|
167
|
-
const resolutionBases = [
|
|
168
|
-
undefined,
|
|
169
|
-
"requested_preference",
|
|
170
|
-
"document_updated_at",
|
|
171
|
-
"stable_value_tiebreak",
|
|
172
|
-
];
|
|
173
|
-
return [
|
|
174
|
-
hasOnlyKeys(value, RECEIPT_DECISION_KEYS),
|
|
175
|
-
typeof value.field === "string",
|
|
176
|
-
typeof value.field === "string" && value.field.length > 0,
|
|
177
|
-
typeof value.field === "string" && value.field.length <= 256,
|
|
178
|
-
Object.hasOwn(value, "base"),
|
|
179
|
-
Object.hasOwn(value, "ours"),
|
|
180
|
-
Object.hasOwn(value, "theirs"),
|
|
181
|
-
Object.hasOwn(value, "retained"),
|
|
182
|
-
Object.hasOwn(value, "discarded"),
|
|
183
|
-
hasBoundedJsonStructure(value.base),
|
|
184
|
-
hasBoundedJsonStructure(value.ours),
|
|
185
|
-
hasBoundedJsonStructure(value.theirs),
|
|
186
|
-
hasBoundedJsonStructure(value.retained),
|
|
187
|
-
hasBoundedJsonStructure(value.discarded),
|
|
188
|
-
retainedSides.includes(value.retained_side),
|
|
189
|
-
resolutionBases.includes(value.resolution_basis),
|
|
190
|
-
].every(Boolean);
|
|
191
|
-
}
|
|
192
|
-
/** Validate the complete field-to-SHA-256 proof map used during repair. */
|
|
193
|
-
function isMergedFieldHashes(value) {
|
|
194
|
-
if (!isRecord(value))
|
|
195
|
-
return false;
|
|
196
|
-
const entries = Object.entries(value);
|
|
197
|
-
return (entries.length <= RECEIPT_FIELD_MAX &&
|
|
198
|
-
entries.every(([field, hash]) => field.length > 0 &&
|
|
199
|
-
field.length <= 256 &&
|
|
200
|
-
typeof hash === "string" &&
|
|
201
|
-
/^[a-f0-9]{64}$/u.test(hash)));
|
|
202
|
-
}
|
|
203
|
-
/** Require a normalized tracker item path whose basename matches the receipt item. */
|
|
204
|
-
function isSafeReceiptItemPath(value, itemId) {
|
|
205
|
-
if (typeof value !== "string" ||
|
|
206
|
-
value.length === 0 ||
|
|
207
|
-
value.length > 4_096 ||
|
|
208
|
-
value.includes("\0") ||
|
|
209
|
-
value.includes("\\") ||
|
|
210
|
-
path.posix.isAbsolute(value)) {
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
const normalized = path.posix.normalize(value);
|
|
214
|
-
return (normalized !== ".." &&
|
|
215
|
-
!normalized.startsWith("../") &&
|
|
216
|
-
path.posix.basename(normalized, path.posix.extname(normalized)) === itemId);
|
|
217
|
-
}
|
|
218
|
-
/** Validate receipt arrays and optional merged-field proof coverage. */
|
|
219
|
-
function hasValidReceiptCollections(value) {
|
|
220
|
-
return !isStringArray(value.fields_from_theirs) ||
|
|
221
|
-
!isStringArray(value.union_fields) ||
|
|
222
|
-
!Array.isArray(value.decisions) ||
|
|
223
|
-
value.decisions.length > RECEIPT_FIELD_MAX ||
|
|
224
|
-
!value.decisions.every(isMergeDecision)
|
|
225
|
-
? false
|
|
226
|
-
: value.merged_field_hashes === undefined ||
|
|
227
|
-
isMergedFieldHashes(value.merged_field_hashes);
|
|
228
|
-
}
|
|
229
|
-
/** Validate every closed-domain lifecycle and merge-policy field. */
|
|
230
|
-
function hasValidReceiptEnums(value) {
|
|
231
|
-
const preferences = [undefined, "ours", "theirs"];
|
|
232
|
-
const resolutions = [
|
|
233
|
-
undefined,
|
|
234
|
-
"preferred_side",
|
|
235
|
-
"stable_value_order",
|
|
236
|
-
"latest_document_update",
|
|
237
|
-
];
|
|
238
|
-
const availabilities = [
|
|
239
|
-
undefined,
|
|
240
|
-
"clone_local",
|
|
241
|
-
"hash_only",
|
|
242
|
-
"bounded_inline",
|
|
243
|
-
"mixed",
|
|
244
|
-
];
|
|
245
|
-
const sources = [undefined, "clone_local", "durable"];
|
|
246
|
-
return (preferences.includes(value.requested_preference) &&
|
|
247
|
-
preferences.includes(value.preferred) &&
|
|
248
|
-
(value.requested_preference_applied === undefined ||
|
|
249
|
-
typeof value.requested_preference_applied === "boolean") &&
|
|
250
|
-
resolutions.includes(value.conflict_resolution) &&
|
|
251
|
-
(value.state === "pending" || value.state === "reconciled") &&
|
|
252
|
-
availabilities.includes(value.value_availability) &&
|
|
253
|
-
(value.value_policy === undefined ||
|
|
254
|
-
value.value_policy === DURABLE_VALUE_POLICY) &&
|
|
255
|
-
sources.includes(value.evidence_source));
|
|
256
|
-
}
|
|
257
|
-
/** Validate creation and optional settlement timestamps as RFC 3339 values. */
|
|
258
|
-
function hasValidReceiptTimestamps(value) {
|
|
259
|
-
return (typeof value.created_at === "string" &&
|
|
260
|
-
isRfc3339DateTime(value.created_at) &&
|
|
261
|
-
(value.reconciled_at === undefined ||
|
|
262
|
-
(typeof value.reconciled_at === "string" &&
|
|
263
|
-
isRfc3339DateTime(value.reconciled_at))));
|
|
264
|
-
}
|
|
265
|
-
/** Validate one hash-only or policy-bounded durable decision value. */
|
|
266
|
-
function hasDurableDecisionValue(field, value) {
|
|
267
|
-
if (!isRecord(value) || isPrehashedValue(value) === undefined)
|
|
268
|
-
return false;
|
|
269
|
-
const keys = Object.keys(value);
|
|
270
|
-
if (!keys.every((key) => key === "pm_value_hash" || key === "pm_value")) {
|
|
271
|
-
return false;
|
|
272
|
-
}
|
|
273
|
-
if (!Object.hasOwn(value, "pm_value"))
|
|
274
|
-
return keys.length === 1;
|
|
275
|
-
const preview = value.pm_value;
|
|
276
|
-
const allowed = preview === null ||
|
|
277
|
-
REVIEWABLE_DURABLE_VALUES_BY_FIELD.get(field)?.has(stableStringify(preview)) === true;
|
|
278
|
-
return (allowed && hashItemScalarDecisionValue(preview) === value.pm_value_hash);
|
|
279
|
-
}
|
|
280
|
-
/** Validate durable decision redaction and the declared availability summary. */
|
|
281
|
-
function hasValidDurableDecisions(receipt) {
|
|
282
|
-
if (!receipt.decisions.every((decision) => decision.base === null &&
|
|
283
|
-
decision.ours === null &&
|
|
284
|
-
decision.theirs === null &&
|
|
285
|
-
hasDurableDecisionValue(decision.field, decision.retained) &&
|
|
286
|
-
hasDurableDecisionValue(decision.field, decision.discarded))) {
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
const inlineValueCount = receipt.decisions.reduce((count, decision) => count +
|
|
290
|
-
(isRecord(decision.retained) &&
|
|
291
|
-
Object.hasOwn(decision.retained, "pm_value")
|
|
292
|
-
? 1
|
|
293
|
-
: 0) +
|
|
294
|
-
(isRecord(decision.discarded) &&
|
|
295
|
-
Object.hasOwn(decision.discarded, "pm_value")
|
|
296
|
-
? 1
|
|
297
|
-
: 0), 0);
|
|
298
|
-
const expectedAvailability = inlineValueCount === 0
|
|
299
|
-
? "hash_only"
|
|
300
|
-
: inlineValueCount === receipt.decisions.length * 2
|
|
301
|
-
? "bounded_inline"
|
|
302
|
-
: "mixed";
|
|
303
|
-
return (receipt.value_availability === expectedAvailability &&
|
|
304
|
-
(inlineValueCount === 0 || receipt.value_policy === DURABLE_VALUE_POLICY));
|
|
305
|
-
}
|
|
306
|
-
/** Return the narrowest validation class for untrusted receipt evidence. */
|
|
307
|
-
function receiptValidationError(value, evidenceSource) {
|
|
308
|
-
if (!isRecord(value))
|
|
309
|
-
return "required_fields";
|
|
310
|
-
for (const key of [
|
|
311
|
-
"version",
|
|
312
|
-
"id",
|
|
313
|
-
"item_path",
|
|
314
|
-
"item_id",
|
|
315
|
-
"fields_from_theirs",
|
|
316
|
-
"union_fields",
|
|
317
|
-
"decisions",
|
|
318
|
-
"state",
|
|
319
|
-
"created_at",
|
|
320
|
-
]) {
|
|
321
|
-
if (!Object.hasOwn(value, key))
|
|
322
|
-
return "required_fields";
|
|
323
|
-
}
|
|
324
|
-
if (value.version !== 1 || !hasOnlyKeys(value, RECEIPT_KEYS)) {
|
|
325
|
-
return "required_fields";
|
|
326
|
-
}
|
|
327
|
-
if (typeof value.id !== "string" || !isSafeReceiptId(value.id)) {
|
|
328
|
-
return "receipt_id";
|
|
329
|
-
}
|
|
330
|
-
if (typeof value.item_id !== "string" ||
|
|
331
|
-
!RECEIPT_ITEM_ID_PATTERN.test(value.item_id)) {
|
|
332
|
-
return "item_id";
|
|
333
|
-
}
|
|
334
|
-
if (!isSafeReceiptItemPath(value.item_path, value.item_id)) {
|
|
335
|
-
return "item_path";
|
|
336
|
-
}
|
|
337
|
-
if (!hasValidReceiptCollections(value))
|
|
338
|
-
return "collections";
|
|
339
|
-
if (!hasValidReceiptEnums(value))
|
|
340
|
-
return "enums";
|
|
341
|
-
if (!hasValidReceiptTimestamps(value))
|
|
342
|
-
return "timestamps";
|
|
343
|
-
if (evidenceSource === "durable" &&
|
|
344
|
-
!hasValidDurableDecisions(value)) {
|
|
345
|
-
return "durable_decisions";
|
|
346
|
-
}
|
|
347
|
-
return null;
|
|
348
|
-
}
|
|
349
|
-
/** Validate a complete receipt against its clone-local or durable evidence policy. */
|
|
350
|
-
function isMergeDecisionReceipt(value, evidenceSource) {
|
|
351
|
-
return receiptValidationError(value, evidenceSource) === null;
|
|
352
|
-
}
|
|
353
|
-
/** Read one receipt through the no-follow bounded regular-file guard. */
|
|
354
|
-
async function readBoundedRegularReceiptFile(receiptPath) {
|
|
355
|
-
return readBoundedRegularFile(receiptPath, RECEIPT_FILE_MAX_BYTES);
|
|
356
|
-
}
|
|
357
62
|
/** Revalidate one receipt and stage its atomic reconciled-state replacement. */
|
|
358
63
|
async function prepareReceiptSettlement(params) {
|
|
359
64
|
let raw;
|
|
360
65
|
try {
|
|
361
|
-
raw =
|
|
66
|
+
raw = params.readReceipt
|
|
67
|
+
? await params.readReceipt(params.receiptPath)
|
|
68
|
+
: await readBoundedRegularFile(params.receiptPath, RECEIPT_FILE_MAX_BYTES);
|
|
362
69
|
}
|
|
363
70
|
catch (error) {
|
|
364
71
|
if (isFileAbsentError(error))
|
|
@@ -370,29 +77,32 @@ async function prepareReceiptSettlement(params) {
|
|
|
370
77
|
}
|
|
371
78
|
let parsed;
|
|
372
79
|
try {
|
|
373
|
-
parsed = JSON.parse(raw);
|
|
80
|
+
parsed = normalizeLegacyReceipt(JSON.parse(raw));
|
|
374
81
|
}
|
|
375
82
|
catch {
|
|
376
83
|
throw new Error(`Receipt ${params.receiptId} settlement source is not valid JSON.`);
|
|
377
84
|
}
|
|
378
85
|
if (!isMergeDecisionReceipt(parsed, params.evidenceSource) ||
|
|
379
86
|
parsed.id !== params.receiptId ||
|
|
87
|
+
(params.settlement !== undefined && parsed.operation === undefined) ||
|
|
380
88
|
path.basename(params.receiptPath) !== receiptFileName(parsed.id)) {
|
|
381
89
|
throw new Error(`Receipt ${params.receiptId} settlement source failed schema or identity validation.`);
|
|
382
90
|
}
|
|
383
91
|
const { evidence_source: _evidenceSource, ...persistedReceipt } = parsed;
|
|
384
92
|
return {
|
|
385
93
|
path: params.receiptPath,
|
|
386
|
-
|
|
94
|
+
fingerprints: receiptProvenanceFingerprints(parsed),
|
|
95
|
+
modern: parsed.value_policy !== undefined,
|
|
387
96
|
content: `${JSON.stringify({
|
|
388
97
|
...persistedReceipt,
|
|
389
98
|
state: "reconciled",
|
|
390
99
|
reconciled_at: params.reconciledAt,
|
|
100
|
+
...(params.settlement ? { settlement: params.settlement } : {}),
|
|
391
101
|
}, null, 2)}\n`,
|
|
392
102
|
};
|
|
393
103
|
}
|
|
394
104
|
/** Fingerprint immutable receipt provenance while excluding lifecycle state. */
|
|
395
|
-
function receiptProvenanceFingerprint(receipt) {
|
|
105
|
+
function receiptProvenanceFingerprint(receipt, legacy = false) {
|
|
396
106
|
return sha256Hex(stableStringify({
|
|
397
107
|
id: receipt.id,
|
|
398
108
|
item_id: receipt.item_id,
|
|
@@ -403,15 +113,31 @@ function receiptProvenanceFingerprint(receipt) {
|
|
|
403
113
|
fields_from_theirs: receipt.fields_from_theirs,
|
|
404
114
|
union_fields: receipt.union_fields,
|
|
405
115
|
merged_field_hashes: receipt.merged_field_hashes ?? null,
|
|
406
|
-
|
|
116
|
+
operation: receipt.operation ?? null,
|
|
117
|
+
decisions: legacy
|
|
118
|
+
? receipt.decisions.map((decision) => ({
|
|
119
|
+
field: decision.field,
|
|
120
|
+
retained_hash: sha256Hex(stableStringify(encodeItemScalarDecisionValue(decision.retained))),
|
|
121
|
+
discarded_hash: sha256Hex(stableStringify(encodeItemScalarDecisionValue(decision.discarded))),
|
|
122
|
+
}))
|
|
123
|
+
: summarizeMergeReceipt(receipt).decisions,
|
|
407
124
|
}));
|
|
408
125
|
}
|
|
126
|
+
/** Compare historical plain-scalar and current presence-domain hashes without changing stored evidence. */
|
|
127
|
+
function receiptProvenanceFingerprints(receipt) {
|
|
128
|
+
const fingerprints = [receiptProvenanceFingerprint(receipt)];
|
|
129
|
+
if (receipt.value_policy === undefined &&
|
|
130
|
+
(receipt.value_availability === undefined || receipt.value_availability === "clone_local")) {
|
|
131
|
+
fingerprints.push(receiptProvenanceFingerprint(receipt, true));
|
|
132
|
+
}
|
|
133
|
+
return fingerprints;
|
|
134
|
+
}
|
|
409
135
|
/** Merge clone-local and durable lifecycle state without losing pending work. */
|
|
410
136
|
function mergeReceiptCopyLifecycle(local, durable) {
|
|
411
|
-
if (local.state === "reconciled" && durable.state === "reconciled") {
|
|
137
|
+
if (local.state === "reconciled" && durable.state === "reconciled" && local.settlement === durable.settlement) {
|
|
412
138
|
return local;
|
|
413
139
|
}
|
|
414
|
-
const { reconciled_at: _reconciledAt, ...pendingLocal } = local;
|
|
140
|
+
const { reconciled_at: _reconciledAt, settlement: _settlement, ...pendingLocal } = local;
|
|
415
141
|
return { ...pendingLocal, state: "pending" };
|
|
416
142
|
}
|
|
417
143
|
/** Convert a raw receipt into privacy-safe history context. */
|
|
@@ -440,30 +166,6 @@ export function summarizeMergeReceipt(receipt) {
|
|
|
440
166
|
})),
|
|
441
167
|
};
|
|
442
168
|
}
|
|
443
|
-
/** Extract a structurally valid precomputed SHA-256 decision hash. */
|
|
444
|
-
function isPrehashedValue(value) {
|
|
445
|
-
if (typeof value !== "object" ||
|
|
446
|
-
value === null ||
|
|
447
|
-
Array.isArray(value) ||
|
|
448
|
-
!("pm_value_hash" in value)) {
|
|
449
|
-
return undefined;
|
|
450
|
-
}
|
|
451
|
-
const hash = value.pm_value_hash;
|
|
452
|
-
return typeof hash === "string" && /^[a-f0-9]{64}$/u.test(hash)
|
|
453
|
-
? hash
|
|
454
|
-
: undefined;
|
|
455
|
-
}
|
|
456
|
-
/** Convert a raw scalar into hashed evidence with an allowlisted preview. */
|
|
457
|
-
function durableValueEvidence(field, value) {
|
|
458
|
-
const missing = isItemScalarMissingValue(value);
|
|
459
|
-
const pmValueHash = hashItemScalarDecisionValue(missing ? undefined : value);
|
|
460
|
-
const reviewable = !missing &&
|
|
461
|
-
(value === null ||
|
|
462
|
-
REVIEWABLE_DURABLE_VALUES_BY_FIELD.get(field)?.has(stableStringify(value)) === true);
|
|
463
|
-
return reviewable
|
|
464
|
-
? { pm_value_hash: pmValueHash, pm_value: value }
|
|
465
|
-
: { pm_value_hash: pmValueHash };
|
|
466
|
-
}
|
|
467
169
|
/** Persist one item-driver outcome in the clone-local Git directory. */
|
|
468
170
|
export async function writeMergeReceipt(params) {
|
|
469
171
|
const directory = await resolveReceiptDirectory(params.cwd);
|
|
@@ -486,7 +188,15 @@ export async function writeMergeReceipt(params) {
|
|
|
486
188
|
...(params.mergedFieldHashes
|
|
487
189
|
? { merged_field_hashes: { ...params.mergedFieldHashes } }
|
|
488
190
|
: {}),
|
|
489
|
-
decisions: structuredClone(params.decisions)
|
|
191
|
+
decisions: structuredClone(params.decisions).map((decision) => ({
|
|
192
|
+
...decision,
|
|
193
|
+
base: encodeItemScalarDecisionValue(decision.base),
|
|
194
|
+
ours: encodeItemScalarDecisionValue(decision.ours),
|
|
195
|
+
theirs: encodeItemScalarDecisionValue(decision.theirs),
|
|
196
|
+
retained: encodeItemScalarDecisionValue(decision.retained),
|
|
197
|
+
discarded: encodeItemScalarDecisionValue(decision.discarded),
|
|
198
|
+
})),
|
|
199
|
+
...(params.operation ? { operation: params.operation } : {}),
|
|
490
200
|
state: "pending",
|
|
491
201
|
created_at: nowIso(),
|
|
492
202
|
value_availability: "clone_local",
|
|
@@ -552,6 +262,7 @@ async function readReceiptsFromDirectory(directory, evidenceSource) {
|
|
|
552
262
|
invalidEvidence.push({
|
|
553
263
|
evidence_source: evidenceSource,
|
|
554
264
|
reason: candidate.reason,
|
|
265
|
+
...(candidate.validation_path ? { validation_path: candidate.validation_path } : {}),
|
|
555
266
|
...(candidate.validation_error === undefined
|
|
556
267
|
? {}
|
|
557
268
|
: { validation_error: candidate.validation_error }),
|
|
@@ -601,7 +312,7 @@ async function receiptDirectoryFailureMeansAbsent(directory, error, inspectAnces
|
|
|
601
312
|
async function inspectReceiptCandidate(directory, name, evidenceSource) {
|
|
602
313
|
let raw;
|
|
603
314
|
try {
|
|
604
|
-
raw = await
|
|
315
|
+
raw = await readBoundedRegularFile(path.join(directory, name), RECEIPT_FILE_MAX_BYTES);
|
|
605
316
|
}
|
|
606
317
|
catch {
|
|
607
318
|
return { reason: "candidate_unreadable" };
|
|
@@ -610,7 +321,7 @@ async function inspectReceiptCandidate(directory, name, evidenceSource) {
|
|
|
610
321
|
return { reason: "candidate_not_bounded_regular_file" };
|
|
611
322
|
let parsed;
|
|
612
323
|
try {
|
|
613
|
-
parsed = JSON.parse(raw);
|
|
324
|
+
parsed = normalizeLegacyReceipt(JSON.parse(raw));
|
|
614
325
|
}
|
|
615
326
|
catch {
|
|
616
327
|
return { reason: "candidate_invalid_json" };
|
|
@@ -623,6 +334,7 @@ async function inspectReceiptCandidate(directory, name, evidenceSource) {
|
|
|
623
334
|
? "identity_invalid"
|
|
624
335
|
: "schema_invalid",
|
|
625
336
|
validation_error: validationError,
|
|
337
|
+
...(validationError === "collections" ? { validation_path: receiptCollectionValidationPath(parsed) } : {}),
|
|
626
338
|
};
|
|
627
339
|
}
|
|
628
340
|
const receipt = parsed;
|
|
@@ -660,8 +372,8 @@ export async function inspectMergeReceiptEvidence(cwd, options = {}) {
|
|
|
660
372
|
for (const receipt of local.receipts) {
|
|
661
373
|
const durableCopy = receipts.get(receipt.id);
|
|
662
374
|
if (durableCopy !== undefined &&
|
|
663
|
-
|
|
664
|
-
receiptProvenanceFingerprint(receipt)) {
|
|
375
|
+
!(durableCopy.value_policy === undefined ? receiptProvenanceFingerprints(receipt)
|
|
376
|
+
: [receiptProvenanceFingerprint(receipt)]).includes(receiptProvenanceFingerprint(durableCopy))) {
|
|
665
377
|
receipts.delete(receipt.id);
|
|
666
378
|
divergentCopyCount += 1;
|
|
667
379
|
if (local.invalid_evidence.length +
|
|
@@ -742,6 +454,7 @@ export async function markMergeReceiptReconciled(cwd, receipt, options = {}) {
|
|
|
742
454
|
receiptId: receipt.id,
|
|
743
455
|
evidenceSource: "clone_local",
|
|
744
456
|
reconciledAt,
|
|
457
|
+
settlement: options.settlement,
|
|
745
458
|
}),
|
|
746
459
|
...(durablePath === null
|
|
747
460
|
? []
|
|
@@ -751,6 +464,7 @@ export async function markMergeReceiptReconciled(cwd, receipt, options = {}) {
|
|
|
751
464
|
receiptId: receipt.id,
|
|
752
465
|
evidenceSource: "durable",
|
|
753
466
|
reconciledAt,
|
|
467
|
+
settlement: options.settlement,
|
|
754
468
|
}),
|
|
755
469
|
]),
|
|
756
470
|
]);
|
|
@@ -761,9 +475,9 @@ export async function markMergeReceiptReconciled(cwd, receipt, options = {}) {
|
|
|
761
475
|
}
|
|
762
476
|
return;
|
|
763
477
|
}
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
478
|
+
const expectedFingerprints = writes.some((write) => write.modern)
|
|
479
|
+
? [receiptProvenanceFingerprint(receipt)] : receiptProvenanceFingerprints(receipt);
|
|
480
|
+
if (!expectedFingerprints.some((fingerprint) => writes.every((write) => write.fingerprints.includes(fingerprint)))) {
|
|
767
481
|
throw new Error(`Receipt ${receipt.id} settlement copies disagree on immutable merge provenance.`);
|
|
768
482
|
}
|
|
769
483
|
for (const write of writes) {
|
|
@@ -809,4 +523,4 @@ export const _testOnlyMergeReceipts = {
|
|
|
809
523
|
receiptDirectoryFailureMeansAbsent,
|
|
810
524
|
};
|
|
811
525
|
//# sourceMappingURL=receipts.js.map
|
|
812
|
-
//# debugId=
|
|
526
|
+
//# debugId=e7fe0fee-9519-593d-ac72-b3c7fb63563e
|
|
@@ -29,6 +29,8 @@ export interface MergeReconcileResult {
|
|
|
29
29
|
receipts: {
|
|
30
30
|
pending_before: number;
|
|
31
31
|
reconciled: number;
|
|
32
|
+
/** Receipts proven to restore their original Git state; these do not represent applied merges. */
|
|
33
|
+
abandoned: number;
|
|
32
34
|
summaries: MergeDecisionReceiptSummary[];
|
|
33
35
|
/** Missing durable references discovered before this reconciliation. */
|
|
34
36
|
missing_history_references_before: number;
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* item-history stream, then verifies history and storage integrity in one call.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
!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]="
|
|
8
|
+
!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]="2696d75a-02f4-5c16-b77e-7798e7d020f9")}catch(e){}}();
|
|
9
|
+
import { settleAbandonedMergeReceipts } from "./abandoned-receipts.js";
|
|
9
10
|
import { readFile } from "node:fs/promises";
|
|
10
11
|
import path from "node:path";
|
|
11
12
|
import { EXIT_CODE } from "../../core/shared/constants.js";
|
|
@@ -229,8 +230,13 @@ export async function runMergeReconcile(options, global) {
|
|
|
229
230
|
global,
|
|
230
231
|
});
|
|
231
232
|
const pendingReceipts = receiptEvidence.receipts;
|
|
233
|
+
const abandonedReceiptIds = await settleAbandonedMergeReceipts({
|
|
234
|
+
cwd: gitWorkspaceRoot, receipts: pendingReceipts, dryRun,
|
|
235
|
+
author: options.author, message: options.message, global,
|
|
236
|
+
});
|
|
237
|
+
const abandonedIds = new Set(abandonedReceiptIds);
|
|
232
238
|
const receiptsByItem = new Map();
|
|
233
|
-
for (const receipt of pendingReceipts) {
|
|
239
|
+
for (const receipt of pendingReceipts.filter((entry) => !abandonedIds.has(entry.id))) {
|
|
234
240
|
const receipts = receiptsByItem.get(receipt.item_id) ?? [];
|
|
235
241
|
receipts.push(receipt);
|
|
236
242
|
receiptsByItem.set(receipt.item_id, receipts);
|
|
@@ -291,7 +297,7 @@ export async function runMergeReconcile(options, global) {
|
|
|
291
297
|
const mergeChecksGreen = validation.checks.every((check) => check.status === "ok");
|
|
292
298
|
const receiptSettlementComplete = dryRun
|
|
293
299
|
? pendingReceipts.length === 0
|
|
294
|
-
: reconciledReceiptCount === pendingReceipts.length;
|
|
300
|
+
: reconciledReceiptCount + abandonedReceiptIds.length === pendingReceipts.length;
|
|
295
301
|
const ok = [
|
|
296
302
|
repair.totals.failed === 0,
|
|
297
303
|
mergeChecksGreen,
|
|
@@ -331,6 +337,7 @@ export async function runMergeReconcile(options, global) {
|
|
|
331
337
|
receipts: {
|
|
332
338
|
pending_before: pendingReceipts.length,
|
|
333
339
|
reconciled: reconciledReceiptCount,
|
|
340
|
+
abandoned: abandonedReceiptIds.length,
|
|
334
341
|
summaries: pendingReceipts.map(summarizeMergeReceipt),
|
|
335
342
|
missing_history_references_before: missingBefore.count,
|
|
336
343
|
legacy_disposition_eligible: legacyDispositionCandidates.length,
|
|
@@ -343,4 +350,4 @@ export async function runMergeReconcile(options, global) {
|
|
|
343
350
|
};
|
|
344
351
|
}
|
|
345
352
|
//# sourceMappingURL=reconcile.js.map
|
|
346
|
-
//# debugId=
|
|
353
|
+
//# debugId=2696d75a-02f4-5c16-b77e-7798e7d020f9
|