@unbrained/pm-cli 2026.8.24 → 2026.8.26
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 +46 -0
- package/dist/cli-bundle/bundle-manifest.json +149 -149
- package/dist/cli-bundle/chunks/chunk-E2GCFJSU.js +35 -0
- package/dist/cli-bundle/chunks/{chunk-TEQDR5KI.js → chunk-ES25LX3D.js} +62 -62
- package/dist/cli-bundle/chunks/{chunk-I5Q7QSZX.js → chunk-FRDWWB6R.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-X7WWQDYQ.js → chunk-ICQ3RVIY.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-YOLLRCLY.js → chunk-IV64RJVE.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-L22NP5MA.js → chunk-KBFP3E4E.js} +86 -86
- package/dist/cli-bundle/chunks/{chunk-AGUL3HUT.js → chunk-MVYLQ67M.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-DJNJBWAQ.js → chunk-S4U76VZF.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-IW72J5FW.js → chunk-UKBCRPA2.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-WRHJ3MB6.js +8 -0
- package/dist/cli-bundle/chunks/{chunk-ZAVCS2GH.js → chunk-ZNRLJ54C.js} +60 -60
- package/dist/cli-bundle/chunks/{register-list-query-37A4P6O4.js → register-list-query-EMCPMICY.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-TBJDHYIT.js → register-mutation-OJ67ABCB.js} +3 -3
- package/dist/cli-bundle/chunks/{register-operations-WRZNEBUM.js → register-operations-H2GLP7LT.js} +2 -2
- package/dist/cli-bundle/chunks/{register-setup-H4PDLACD.js → register-setup-GLZAHLVI.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-IMWTYKII.js → chunk-4K2II4TV.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-4XNH2HM7.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-GJR6YEMR.js → chunk-57XY346D.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-4WP7LNE3.js → chunk-66VGB23P.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-72T6JGAE.js +50 -0
- package/dist/cli-bundle/focused-chunks/{chunk-BFA6KKCI.js → chunk-7I23XGWO.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-6Z24ZMU3.js → chunk-7YCDTCBC.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-O4ZXNJMT.js → chunk-A644DUFQ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-FXDLT6FL.js +16 -0
- package/dist/cli-bundle/focused-chunks/{chunk-RASVBJX5.js → chunk-IBHXMFE7.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-LMKG3DFE.js +5 -0
- package/dist/cli-bundle/focused-chunks/{chunk-6MUTDXMP.js → chunk-LV5N3LK5.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-CWCN5RLO.js → chunk-MMXUPDDJ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-WZLSSGEU.js → chunk-OHIHZ7HS.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-P2E6LDAE.js +19 -0
- package/dist/cli-bundle/focused-chunks/{chunk-IZNRRCU3.js → chunk-TMJDFHVD.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-QBP5FSBX.js → chunk-UYBA57GY.js} +4 -4
- package/dist/cli-bundle/focused-chunks/{chunk-MNPECIRL.js → chunk-YO3ZF3FI.js} +2 -2
- package/dist/cli-bundle/main.js +2 -2
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +2 -2
- package/dist/cli-bundle/sdk-core.js +41 -41
- 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 +32 -33
- 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 +7 -6
- package/dist/core/diagnostics/remediation.js +17 -2
- package/dist/core/extensions/manifest-schema.d.ts +20 -0
- package/dist/core/extensions/manifest-schema.js +28 -10
- package/dist/mcp/http-server.d.ts +60 -0
- package/dist/mcp/http-server.js +451 -0
- package/dist/mcp/legacy-adapter.d.ts +50 -0
- package/dist/mcp/legacy-adapter.js +64 -0
- package/dist/mcp/server.d.ts +42 -9
- package/dist/mcp/server.js +457 -58
- package/dist/sdk/agent/refusal-closure-census.d.ts +6 -2
- package/dist/sdk/agent/refusal-closure-census.js +16 -8
- package/dist/sdk/cli-contracts/runtime-contracts.d.ts +10 -0
- package/dist/sdk/cli-contracts/runtime-contracts.js +20 -3
- package/dist/sdk/compose.d.ts +4 -1
- package/dist/sdk/compose.js +23 -36
- package/dist/sdk/extension/author-manifest.d.ts +22 -0
- package/dist/sdk/extension/author-manifest.js +93 -0
- package/dist/sdk/extension.js +6 -3
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +67 -28
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +30 -3
- package/dist/sdk/governance/health.js +103 -11
- package/dist/sdk/governance/validate.js +15 -9
- package/dist/sdk/history-repair.d.ts +24 -0
- package/dist/sdk/history-repair.js +145 -4
- package/dist/sdk/index.d.ts +6 -0
- package/dist/sdk/index.js +8 -2
- package/dist/sdk/mcp/authorization.d.ts +134 -0
- package/dist/sdk/mcp/authorization.js +405 -0
- package/dist/sdk/mcp/interactions.d.ts +118 -0
- package/dist/sdk/mcp/interactions.js +337 -0
- package/dist/sdk/mcp/protocol.d.ts +142 -0
- package/dist/sdk/mcp/protocol.js +174 -0
- package/dist/sdk/mcp/subscriptions.d.ts +65 -0
- package/dist/sdk/mcp/subscriptions.js +212 -0
- package/dist/sdk/mcp/tasks.d.ts +107 -0
- package/dist/sdk/mcp/tasks.js +431 -0
- package/dist/sdk/mcp/transport.d.ts +30 -0
- package/dist/sdk/mcp/transport.js +261 -0
- package/dist/sdk/merge/driver.js +25 -4
- package/dist/sdk/merge/receipt-file-boundary.d.ts +21 -0
- package/dist/sdk/merge/receipt-file-boundary.js +47 -0
- package/dist/sdk/merge/receipts.d.ts +40 -1
- package/dist/sdk/merge/receipts.js +343 -36
- package/dist/sdk/merge/reconcile.js +117 -47
- package/dist/sdk/read-output/continuation.d.ts +15 -2
- package/dist/sdk/read-output/continuation.js +86 -6
- package/dist/sdk/read-output-contracts.d.ts +4 -1
- package/dist/sdk/read-output-contracts.js +9 -6
- package/dist/sdk/runtime-primitives.d.ts +1 -1
- package/dist/sdk/runtime-primitives.js +3 -3
- package/dist/sdk/test/execution.d.ts +6 -0
- package/dist/sdk/test/execution.js +32 -3
- package/docs/AGENT_PROVENANCE_ADR.md +6 -4
- package/docs/AGENT_RUNTIME_PRIMITIVES.md +6 -5
- package/docs/CLAUDE_CODE_PLUGIN.md +12 -5
- package/docs/DIAGNOSTIC_OUTPUT_CONTRACTS.md +8 -0
- package/docs/EXTENSIONS.md +3 -4
- package/docs/MCP_2026_07_28.md +138 -0
- package/docs/MCP_2026_07_28_CONFORMANCE.md +30 -0
- package/docs/MCP_REMOTE_TRANSPORT_SECURITY.md +180 -0
- package/docs/MERGE_SAFETY.md +34 -5
- package/docs/OUTPUT_PROJECTION_CONTRACTS.md +13 -1
- package/docs/README.md +4 -0
- package/docs/READ_OUTPUT_CONTRACTS.md +25 -2
- package/docs/RELEASING.md +3 -2
- package/docs/SDK.md +22 -1
- package/docs/SDK_AGENT_SESSION_CONTEXT.md +18 -13
- package/docs/SDK_MCP_INTERACTIONS.md +227 -0
- package/docs/TESTING.md +4 -0
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +13 -8
- package/marketplace.json +2 -2
- package/package.json +7 -5
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/scripts/finalize-build.mjs +1 -0
- package/sdk/public-surface.json +762 -15
- package/dist/cli-bundle/chunks/chunk-MKE6MD2K.js +0 -36
- package/dist/cli-bundle/chunks/chunk-NXBFO63A.js +0 -8
- package/dist/cli-bundle/focused-chunks/chunk-FCTT4CZU.js +0 -5
- package/dist/cli-bundle/focused-chunks/chunk-Q63PSLO4.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-QV5UP27O.js +0 -51
- package/dist/cli-bundle/focused-chunks/chunk-RPRLENVA.js +0 -16
- package/dist/cli-bundle/focused-chunks/chunk-TOFVXBWA.js +0 -19
|
@@ -7,16 +7,49 @@
|
|
|
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]="145a6d31-5e14-5b0b-95fb-6f837d11e2f4")}catch(e){}}();
|
|
11
11
|
import { execFile } from "node:child_process";
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
|
-
import { readdir
|
|
13
|
+
import { readdir } from "node:fs/promises";
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import { promisify } from "node:util";
|
|
16
|
-
import { ensureDir, pathExists, writeFileAtomic, } from "../../core/fs/fs-utils.js";
|
|
16
|
+
import { ensureDir, isFileAbsentError, pathExists, writeFileAtomic, } from "../../core/fs/fs-utils.js";
|
|
17
17
|
import { sha256Hex, stableStringify } from "../../core/shared/serialization.js";
|
|
18
|
-
import { nowIso } from "../../core/shared/time.js";
|
|
18
|
+
import { isRfc3339DateTime, nowIso } from "../../core/shared/time.js";
|
|
19
|
+
import { readBoundedRegularFile } from "./receipt-file-boundary.js";
|
|
19
20
|
const execFileAsync = promisify(execFile);
|
|
21
|
+
const RECEIPT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
|
|
22
|
+
const RECEIPT_ITEM_ID_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u;
|
|
23
|
+
const RECEIPT_FIELD_MAX = 2_048;
|
|
24
|
+
const RECEIPT_FILE_MAX_BYTES = 16 * 1024 * 1024;
|
|
25
|
+
const RECEIPT_VALUE_MAX_DEPTH = 64;
|
|
26
|
+
const RECEIPT_VALUE_MAX_NODES = 100_000;
|
|
27
|
+
const RECEIPT_KEYS = new Set([
|
|
28
|
+
"version",
|
|
29
|
+
"id",
|
|
30
|
+
"item_path",
|
|
31
|
+
"item_id",
|
|
32
|
+
"requested_preference",
|
|
33
|
+
"preferred",
|
|
34
|
+
"conflict_resolution",
|
|
35
|
+
"fields_from_theirs",
|
|
36
|
+
"union_fields",
|
|
37
|
+
"merged_field_hashes",
|
|
38
|
+
"decisions",
|
|
39
|
+
"state",
|
|
40
|
+
"created_at",
|
|
41
|
+
"reconciled_at",
|
|
42
|
+
"value_availability",
|
|
43
|
+
"evidence_source",
|
|
44
|
+
]);
|
|
45
|
+
const RECEIPT_DECISION_KEYS = new Set([
|
|
46
|
+
"field",
|
|
47
|
+
"base",
|
|
48
|
+
"ours",
|
|
49
|
+
"theirs",
|
|
50
|
+
"retained",
|
|
51
|
+
"discarded",
|
|
52
|
+
]);
|
|
20
53
|
async function resolveReceiptDirectory(cwd) {
|
|
21
54
|
try {
|
|
22
55
|
const { stdout } = await execFileAsync("git", [
|
|
@@ -49,6 +82,200 @@ function durableReceiptDirectory(pmRoot) {
|
|
|
49
82
|
function receiptFileName(id) {
|
|
50
83
|
return `${id}.json`;
|
|
51
84
|
}
|
|
85
|
+
function isRecord(value) {
|
|
86
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
87
|
+
}
|
|
88
|
+
function hasOnlyKeys(value, allowed) {
|
|
89
|
+
return Object.keys(value).every((key) => allowed.has(key));
|
|
90
|
+
}
|
|
91
|
+
function isStringArray(value) {
|
|
92
|
+
return (Array.isArray(value) &&
|
|
93
|
+
value.length <= RECEIPT_FIELD_MAX &&
|
|
94
|
+
value.every((entry) => typeof entry === "string" && entry.length > 0 && entry.length <= 256));
|
|
95
|
+
}
|
|
96
|
+
function hasBoundedJsonStructure(value) {
|
|
97
|
+
const pending = [
|
|
98
|
+
{ value, depth: 0 },
|
|
99
|
+
];
|
|
100
|
+
let nodes = 0;
|
|
101
|
+
while (pending.length > 0) {
|
|
102
|
+
const current = pending.pop();
|
|
103
|
+
nodes += 1;
|
|
104
|
+
if (nodes > RECEIPT_VALUE_MAX_NODES ||
|
|
105
|
+
current.depth > RECEIPT_VALUE_MAX_DEPTH) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(current.value)) {
|
|
109
|
+
for (const entry of current.value) {
|
|
110
|
+
pending.push({ value: entry, depth: current.depth + 1 });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if (isRecord(current.value)) {
|
|
114
|
+
for (const entry of Object.values(current.value)) {
|
|
115
|
+
pending.push({ value: entry, depth: current.depth + 1 });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
function isMergeDecision(value) {
|
|
122
|
+
return (isRecord(value) &&
|
|
123
|
+
hasOnlyKeys(value, RECEIPT_DECISION_KEYS) &&
|
|
124
|
+
typeof value.field === "string" &&
|
|
125
|
+
value.field.length > 0 &&
|
|
126
|
+
value.field.length <= 256 &&
|
|
127
|
+
Object.hasOwn(value, "base") &&
|
|
128
|
+
Object.hasOwn(value, "ours") &&
|
|
129
|
+
Object.hasOwn(value, "theirs") &&
|
|
130
|
+
Object.hasOwn(value, "retained") &&
|
|
131
|
+
Object.hasOwn(value, "discarded") &&
|
|
132
|
+
hasBoundedJsonStructure(value.base) &&
|
|
133
|
+
hasBoundedJsonStructure(value.ours) &&
|
|
134
|
+
hasBoundedJsonStructure(value.theirs) &&
|
|
135
|
+
hasBoundedJsonStructure(value.retained) &&
|
|
136
|
+
hasBoundedJsonStructure(value.discarded));
|
|
137
|
+
}
|
|
138
|
+
function isMergedFieldHashes(value) {
|
|
139
|
+
if (!isRecord(value))
|
|
140
|
+
return false;
|
|
141
|
+
const entries = Object.entries(value);
|
|
142
|
+
return (entries.length <= RECEIPT_FIELD_MAX &&
|
|
143
|
+
entries.every(([field, hash]) => field.length > 0 &&
|
|
144
|
+
field.length <= 256 &&
|
|
145
|
+
typeof hash === "string" &&
|
|
146
|
+
/^[a-f0-9]{64}$/u.test(hash)));
|
|
147
|
+
}
|
|
148
|
+
function isSafeReceiptItemPath(value, itemId) {
|
|
149
|
+
if (typeof value !== "string" ||
|
|
150
|
+
value.length === 0 ||
|
|
151
|
+
value.length > 4_096 ||
|
|
152
|
+
value.includes("\0") ||
|
|
153
|
+
value.includes("\\") ||
|
|
154
|
+
path.posix.isAbsolute(value)) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
const normalized = path.posix.normalize(value);
|
|
158
|
+
return (normalized !== ".." &&
|
|
159
|
+
!normalized.startsWith("../") &&
|
|
160
|
+
path.posix.basename(normalized, path.posix.extname(normalized)) === itemId);
|
|
161
|
+
}
|
|
162
|
+
function hasValidReceiptIdentity(value) {
|
|
163
|
+
return !hasOnlyKeys(value, RECEIPT_KEYS) || value.version !== 1
|
|
164
|
+
? false
|
|
165
|
+
: typeof value.id === "string" &&
|
|
166
|
+
RECEIPT_ID_PATTERN.test(value.id) &&
|
|
167
|
+
typeof value.item_id === "string" &&
|
|
168
|
+
RECEIPT_ITEM_ID_PATTERN.test(value.item_id) &&
|
|
169
|
+
isSafeReceiptItemPath(value.item_path, value.item_id);
|
|
170
|
+
}
|
|
171
|
+
function hasValidReceiptCollections(value) {
|
|
172
|
+
return !isStringArray(value.fields_from_theirs) ||
|
|
173
|
+
!isStringArray(value.union_fields) ||
|
|
174
|
+
!Array.isArray(value.decisions) ||
|
|
175
|
+
value.decisions.length > RECEIPT_FIELD_MAX ||
|
|
176
|
+
!value.decisions.every(isMergeDecision)
|
|
177
|
+
? false
|
|
178
|
+
: value.merged_field_hashes === undefined ||
|
|
179
|
+
isMergedFieldHashes(value.merged_field_hashes);
|
|
180
|
+
}
|
|
181
|
+
function hasValidReceiptEnums(value) {
|
|
182
|
+
const preferences = [undefined, "ours", "theirs"];
|
|
183
|
+
const resolutions = [undefined, "preferred_side", "stable_value_order"];
|
|
184
|
+
const availabilities = [undefined, "clone_local", "hash_only"];
|
|
185
|
+
const sources = [undefined, "clone_local", "durable"];
|
|
186
|
+
return (preferences.includes(value.requested_preference) &&
|
|
187
|
+
preferences.includes(value.preferred) &&
|
|
188
|
+
resolutions.includes(value.conflict_resolution) &&
|
|
189
|
+
(value.state === "pending" || value.state === "reconciled") &&
|
|
190
|
+
availabilities.includes(value.value_availability) &&
|
|
191
|
+
sources.includes(value.evidence_source));
|
|
192
|
+
}
|
|
193
|
+
function hasValidReceiptTimestamps(value) {
|
|
194
|
+
return (typeof value.created_at === "string" &&
|
|
195
|
+
isRfc3339DateTime(value.created_at) &&
|
|
196
|
+
(value.reconciled_at === undefined ||
|
|
197
|
+
(typeof value.reconciled_at === "string" &&
|
|
198
|
+
isRfc3339DateTime(value.reconciled_at))));
|
|
199
|
+
}
|
|
200
|
+
function hasHashOnlyDurableDecisions(receipt) {
|
|
201
|
+
return (receipt.value_availability === "hash_only" &&
|
|
202
|
+
receipt.decisions.every((decision) => decision.base === null &&
|
|
203
|
+
decision.ours === null &&
|
|
204
|
+
decision.theirs === null &&
|
|
205
|
+
isExactPrehashedValue(decision.retained) &&
|
|
206
|
+
isExactPrehashedValue(decision.discarded)));
|
|
207
|
+
}
|
|
208
|
+
function isMergeDecisionReceipt(value, evidenceSource) {
|
|
209
|
+
if (!isRecord(value) ||
|
|
210
|
+
!hasValidReceiptIdentity(value) ||
|
|
211
|
+
!hasValidReceiptCollections(value) ||
|
|
212
|
+
!hasValidReceiptEnums(value) ||
|
|
213
|
+
!hasValidReceiptTimestamps(value)) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
const receipt = value;
|
|
217
|
+
return evidenceSource !== "durable" || hasHashOnlyDurableDecisions(receipt);
|
|
218
|
+
}
|
|
219
|
+
async function readBoundedRegularReceiptFile(receiptPath) {
|
|
220
|
+
return readBoundedRegularFile(receiptPath, RECEIPT_FILE_MAX_BYTES);
|
|
221
|
+
}
|
|
222
|
+
async function prepareReceiptSettlement(params) {
|
|
223
|
+
let raw;
|
|
224
|
+
try {
|
|
225
|
+
raw = await (params.readReceipt ?? readBoundedRegularReceiptFile)(params.receiptPath);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (isFileAbsentError(error))
|
|
229
|
+
return null;
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
if (raw === null) {
|
|
233
|
+
throw new Error(`Receipt ${params.receiptId} settlement source is not a bounded regular file.`);
|
|
234
|
+
}
|
|
235
|
+
let parsed;
|
|
236
|
+
try {
|
|
237
|
+
parsed = JSON.parse(raw);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
throw new Error(`Receipt ${params.receiptId} settlement source is not valid JSON.`);
|
|
241
|
+
}
|
|
242
|
+
if (!isMergeDecisionReceipt(parsed, params.evidenceSource) ||
|
|
243
|
+
parsed.id !== params.receiptId ||
|
|
244
|
+
path.basename(params.receiptPath) !== receiptFileName(parsed.id)) {
|
|
245
|
+
throw new Error(`Receipt ${params.receiptId} settlement source failed schema or identity validation.`);
|
|
246
|
+
}
|
|
247
|
+
const { evidence_source: _evidenceSource, ...persistedReceipt } = parsed;
|
|
248
|
+
return {
|
|
249
|
+
path: params.receiptPath,
|
|
250
|
+
fingerprint: receiptProvenanceFingerprint(parsed),
|
|
251
|
+
content: `${JSON.stringify({
|
|
252
|
+
...persistedReceipt,
|
|
253
|
+
state: "reconciled",
|
|
254
|
+
reconciled_at: params.reconciledAt,
|
|
255
|
+
}, null, 2)}\n`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function receiptProvenanceFingerprint(receipt) {
|
|
259
|
+
return sha256Hex(stableStringify({
|
|
260
|
+
id: receipt.id,
|
|
261
|
+
item_id: receipt.item_id,
|
|
262
|
+
item_path: receipt.item_path,
|
|
263
|
+
created_at: receipt.created_at,
|
|
264
|
+
requested_preference: receipt.requested_preference ?? receipt.preferred ?? "ours",
|
|
265
|
+
conflict_resolution: receipt.conflict_resolution ?? "preferred_side",
|
|
266
|
+
fields_from_theirs: receipt.fields_from_theirs,
|
|
267
|
+
union_fields: receipt.union_fields,
|
|
268
|
+
merged_field_hashes: receipt.merged_field_hashes ?? null,
|
|
269
|
+
decisions: summarizeMergeReceipt(receipt).decisions,
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
function mergeReceiptCopyLifecycle(local, durable) {
|
|
273
|
+
if (local.state === "reconciled" && durable.state === "reconciled") {
|
|
274
|
+
return local;
|
|
275
|
+
}
|
|
276
|
+
const { reconciled_at: _reconciledAt, ...pendingLocal } = local;
|
|
277
|
+
return { ...pendingLocal, state: "pending" };
|
|
278
|
+
}
|
|
52
279
|
/** Convert a raw receipt into privacy-safe history context. */
|
|
53
280
|
export function summarizeMergeReceipt(receipt) {
|
|
54
281
|
return {
|
|
@@ -81,6 +308,11 @@ function isPrehashedValue(value) {
|
|
|
81
308
|
? hash
|
|
82
309
|
: undefined;
|
|
83
310
|
}
|
|
311
|
+
function isExactPrehashedValue(value) {
|
|
312
|
+
return (isRecord(value) &&
|
|
313
|
+
Object.keys(value).length === 1 &&
|
|
314
|
+
isPrehashedValue(value) !== undefined);
|
|
315
|
+
}
|
|
84
316
|
/** Persist one item-driver outcome in the clone-local Git directory. */
|
|
85
317
|
export async function writeMergeReceipt(params) {
|
|
86
318
|
const directory = await resolveReceiptDirectory(params.cwd);
|
|
@@ -103,6 +335,9 @@ export async function writeMergeReceipt(params) {
|
|
|
103
335
|
conflict_resolution: params.conflictResolution ?? "preferred_side",
|
|
104
336
|
fields_from_theirs: [...params.fieldsFromTheirs],
|
|
105
337
|
union_fields: [...params.unionFields],
|
|
338
|
+
...(params.mergedFieldHashes
|
|
339
|
+
? { merged_field_hashes: { ...params.mergedFieldHashes } }
|
|
340
|
+
: {}),
|
|
106
341
|
decisions: structuredClone(params.decisions),
|
|
107
342
|
state: "pending",
|
|
108
343
|
created_at: nowIso(),
|
|
@@ -131,53 +366,86 @@ export async function writeMergeReceipt(params) {
|
|
|
131
366
|
}
|
|
132
367
|
return receipt;
|
|
133
368
|
}
|
|
134
|
-
async function readReceiptsFromDirectory(directory,
|
|
135
|
-
if (!(await pathExists(directory)))
|
|
136
|
-
return [];
|
|
369
|
+
async function readReceiptsFromDirectory(directory, evidenceSource) {
|
|
370
|
+
if (!(await pathExists(directory))) {
|
|
371
|
+
return { receipts: [], invalid_evidence_count: 0 };
|
|
372
|
+
}
|
|
137
373
|
let names;
|
|
138
374
|
try {
|
|
139
375
|
names = await readdir(directory);
|
|
140
376
|
}
|
|
141
377
|
catch {
|
|
142
|
-
return [];
|
|
378
|
+
return { receipts: [], invalid_evidence_count: 1 };
|
|
143
379
|
}
|
|
144
380
|
const receipts = [];
|
|
381
|
+
let invalidEvidenceCount = 0;
|
|
145
382
|
for (const name of names.sort((left, right) => left.localeCompare(right))) {
|
|
146
383
|
if (!name.endsWith(".json"))
|
|
147
384
|
continue;
|
|
148
385
|
try {
|
|
149
|
-
const
|
|
150
|
-
const
|
|
386
|
+
const receiptPath = path.join(directory, name);
|
|
387
|
+
const raw = await readBoundedRegularReceiptFile(receiptPath);
|
|
388
|
+
if (raw === null) {
|
|
389
|
+
invalidEvidenceCount += 1;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
const parsed = JSON.parse(raw);
|
|
393
|
+
if (!isMergeDecisionReceipt(parsed, evidenceSource) ||
|
|
394
|
+
name !== receiptFileName(parsed.id)) {
|
|
395
|
+
invalidEvidenceCount += 1;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const { preferred: legacyPreference, evidence_source: _serializedEvidenceSource, ...receiptWithoutRuntimeKeys } = parsed;
|
|
151
399
|
const normalizedReceipt = {
|
|
152
|
-
...
|
|
400
|
+
...receiptWithoutRuntimeKeys,
|
|
153
401
|
requested_preference: parsed.requested_preference ?? legacyPreference ?? "ours",
|
|
154
402
|
conflict_resolution: parsed.conflict_resolution ?? "preferred_side",
|
|
403
|
+
evidence_source: evidenceSource,
|
|
155
404
|
};
|
|
156
|
-
|
|
157
|
-
(options.includeReconciled || normalizedReceipt.state === "pending") &&
|
|
158
|
-
(options.includeLossless !== false ||
|
|
159
|
-
normalizedReceipt.decisions.length > 0)) {
|
|
160
|
-
receipts.push(normalizedReceipt);
|
|
161
|
-
}
|
|
405
|
+
receipts.push(normalizedReceipt);
|
|
162
406
|
}
|
|
163
407
|
catch {
|
|
164
|
-
|
|
408
|
+
invalidEvidenceCount += 1;
|
|
165
409
|
}
|
|
166
410
|
}
|
|
167
|
-
return receipts;
|
|
411
|
+
return { receipts, invalid_evidence_count: invalidEvidenceCount };
|
|
168
412
|
}
|
|
169
|
-
/**
|
|
170
|
-
export async function
|
|
413
|
+
/** Inspect valid receipts and count invalid evidence without returning untrusted file contents. */
|
|
414
|
+
export async function inspectMergeReceiptEvidence(cwd, options = {}) {
|
|
171
415
|
const directory = await resolveReceiptDirectory(cwd);
|
|
172
416
|
const local = directory === null
|
|
173
|
-
? []
|
|
174
|
-
: await readReceiptsFromDirectory(directory,
|
|
417
|
+
? { receipts: [], invalid_evidence_count: 0 }
|
|
418
|
+
: await readReceiptsFromDirectory(directory, "clone_local");
|
|
175
419
|
const trackerRoot = options.pmRoot ?? path.join(cwd, ".agents", "pm");
|
|
176
|
-
const durable = await readReceiptsFromDirectory(durableReceiptDirectory(trackerRoot),
|
|
177
|
-
const receipts = new Map(durable.map((receipt) => [receipt.id, receipt]));
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
420
|
+
const durable = await readReceiptsFromDirectory(durableReceiptDirectory(trackerRoot), "durable");
|
|
421
|
+
const receipts = new Map(durable.receipts.map((receipt) => [receipt.id, receipt]));
|
|
422
|
+
let divergentCopyCount = 0;
|
|
423
|
+
for (const receipt of local.receipts) {
|
|
424
|
+
const durableCopy = receipts.get(receipt.id);
|
|
425
|
+
if (durableCopy !== undefined &&
|
|
426
|
+
receiptProvenanceFingerprint(durableCopy) !==
|
|
427
|
+
receiptProvenanceFingerprint(receipt)) {
|
|
428
|
+
receipts.delete(receipt.id);
|
|
429
|
+
divergentCopyCount += 1;
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
receipts.set(receipt.id, durableCopy === undefined
|
|
433
|
+
? receipt
|
|
434
|
+
: mergeReceiptCopyLifecycle(receipt, durableCopy));
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
receipts: [...receipts.values()]
|
|
438
|
+
.filter((receipt) => (options.includeReconciled || receipt.state === "pending") &&
|
|
439
|
+
(options.includeLossless !== false || receipt.decisions.length > 0))
|
|
440
|
+
.sort((left, right) => left.created_at.localeCompare(right.created_at)),
|
|
441
|
+
invalid_evidence_count: local.invalid_evidence_count +
|
|
442
|
+
durable.invalid_evidence_count +
|
|
443
|
+
divergentCopyCount,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
/** Read clone-local receipts with explicit reconciled/lossless classification controls. */
|
|
447
|
+
export async function listMergeReceipts(cwd, options = {}) {
|
|
448
|
+
return (await inspectMergeReceiptEvidence(cwd, options)).receipts;
|
|
181
449
|
}
|
|
182
450
|
/** Split merge provenance into receipts with discarded values and receipts whose composition was lossless. */
|
|
183
451
|
export function partitionMergeReceipts(receipts) {
|
|
@@ -191,19 +459,56 @@ export function partitionMergeReceipts(receipts) {
|
|
|
191
459
|
return { pendingDecisions, lossless };
|
|
192
460
|
}
|
|
193
461
|
/** Mark a receipt as represented by a committed merge history event. */
|
|
194
|
-
export async function markMergeReceiptReconciled(cwd, receipt) {
|
|
462
|
+
export async function markMergeReceiptReconciled(cwd, receipt, options = {}) {
|
|
195
463
|
const directory = await resolveReceiptDirectory(cwd);
|
|
196
464
|
if (directory === null) {
|
|
465
|
+
if (options.requireExisting === true) {
|
|
466
|
+
throw new Error(`Receipt ${receipt.id} disappeared before settlement.`);
|
|
467
|
+
}
|
|
197
468
|
return;
|
|
198
469
|
}
|
|
199
|
-
|
|
470
|
+
const receiptEvidenceSource = receipt.evidence_source ?? "clone_local";
|
|
471
|
+
if (!isMergeDecisionReceipt(receipt, receiptEvidenceSource)) {
|
|
472
|
+
throw new Error("Merge receipt trusted settlement input failed schema validation.");
|
|
473
|
+
}
|
|
474
|
+
const reconciledAt = nowIso();
|
|
475
|
+
const localPath = path.join(directory, receiptFileName(receipt.id));
|
|
200
476
|
const trackerRoot = await resolveTrackerRootFromItemPath(cwd, receipt.item_path);
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
477
|
+
const durablePath = trackerRoot === null
|
|
478
|
+
? null
|
|
479
|
+
: path.join(durableReceiptDirectory(trackerRoot), receiptFileName(receipt.id));
|
|
480
|
+
const prepared = await Promise.all([
|
|
481
|
+
prepareReceiptSettlement({
|
|
482
|
+
receiptPath: localPath,
|
|
483
|
+
receiptId: receipt.id,
|
|
484
|
+
evidenceSource: "clone_local",
|
|
485
|
+
reconciledAt,
|
|
486
|
+
}),
|
|
487
|
+
...(durablePath === null
|
|
488
|
+
? []
|
|
489
|
+
: [
|
|
490
|
+
prepareReceiptSettlement({
|
|
491
|
+
receiptPath: durablePath,
|
|
492
|
+
receiptId: receipt.id,
|
|
493
|
+
evidenceSource: "durable",
|
|
494
|
+
reconciledAt,
|
|
495
|
+
}),
|
|
496
|
+
]),
|
|
497
|
+
]);
|
|
498
|
+
const writes = prepared.filter((entry) => entry !== null);
|
|
499
|
+
if (writes.length === 0) {
|
|
500
|
+
if (options.requireExisting === true) {
|
|
501
|
+
throw new Error(`Receipt ${receipt.id} disappeared before settlement.`);
|
|
206
502
|
}
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const expectedFingerprint = receiptProvenanceFingerprint(receipt);
|
|
506
|
+
if (new Set([expectedFingerprint, ...writes.map((write) => write.fingerprint)])
|
|
507
|
+
.size !== 1) {
|
|
508
|
+
throw new Error(`Receipt ${receipt.id} settlement copies disagree on immutable merge provenance.`);
|
|
509
|
+
}
|
|
510
|
+
for (const write of writes) {
|
|
511
|
+
await writeFileAtomic(write.path, write.content);
|
|
207
512
|
}
|
|
208
513
|
}
|
|
209
514
|
/** Report pending or historical clone-local merge decisions. */
|
|
@@ -219,5 +524,7 @@ export async function runMergeReceiptReport(options) {
|
|
|
219
524
|
generated_at: nowIso(),
|
|
220
525
|
};
|
|
221
526
|
}
|
|
527
|
+
/** Test-only seams for deterministic receipt-boundary fault coverage. */
|
|
528
|
+
export const _testOnlyMergeReceipts = { prepareReceiptSettlement };
|
|
222
529
|
//# sourceMappingURL=receipts.js.map
|
|
223
|
-
//# debugId=
|
|
530
|
+
//# debugId=145a6d31-5e14-5b0b-95fb-6f837d11e2f4
|
|
@@ -1,10 +1,76 @@
|
|
|
1
1
|
|
|
2
|
-
!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]="
|
|
2
|
+
!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]="c3ec7d9d-f276-5e97-8e97-507925e5d33a")}catch(e){}}();
|
|
3
3
|
import { EXIT_CODE } from "../../core/shared/constants.js";
|
|
4
4
|
import { PmCliError } from "../../core/shared/errors.js";
|
|
5
|
+
import { resolvePmRoot } from "../../core/store/paths.js";
|
|
5
6
|
import { runHistoryRepair, runHistoryRepairAll, } from "../history-repair.js";
|
|
6
7
|
import { runValidate } from "../governance/validate.js";
|
|
7
|
-
import {
|
|
8
|
+
import { inspectMergeReceiptEvidence, markMergeReceiptReconciled, partitionMergeReceipts, summarizeMergeReceipt, } from "./receipts.js";
|
|
9
|
+
import { findGitWorkspaceRoot } from "./install.js";
|
|
10
|
+
import { mapWithFixedConcurrency } from "../extension/concurrency.js";
|
|
11
|
+
const RECEIPT_ONLY_REPAIR_CONCURRENCY = 4;
|
|
12
|
+
async function runReceiptOnlyRepair(params) {
|
|
13
|
+
try {
|
|
14
|
+
return {
|
|
15
|
+
status: "fulfilled",
|
|
16
|
+
value: await runHistoryRepair(params.id, {
|
|
17
|
+
dryRun: params.dryRun,
|
|
18
|
+
author: params.options.author ?? params.global.author,
|
|
19
|
+
message: params.options.message ??
|
|
20
|
+
"record field-aware branch merge provenance",
|
|
21
|
+
force: params.options.force,
|
|
22
|
+
auditOperation: "merge_reconcile",
|
|
23
|
+
auditContext: params.auditContext,
|
|
24
|
+
forceAuditEntry: true,
|
|
25
|
+
mergeReceiptProof: params.mergeReceiptProof,
|
|
26
|
+
}, params.global),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (reason) {
|
|
30
|
+
return { status: "rejected", reason };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function appendReceiptOnlyResults(params) {
|
|
34
|
+
params.results.forEach((settled, index) => {
|
|
35
|
+
const id = params.receiptOnlyIds[index];
|
|
36
|
+
if (settled.status === "fulfilled") {
|
|
37
|
+
const outcome = settled.value.changed ? "repaired" : "skipped_clean";
|
|
38
|
+
params.repair.streams.push({
|
|
39
|
+
id,
|
|
40
|
+
outcome,
|
|
41
|
+
entries_rehashed: settled.value.history.entries_rehashed,
|
|
42
|
+
entries_patch_repaired: settled.value.history.entries_patch_repaired,
|
|
43
|
+
reconciled_with_item: settled.value.history.reconciled_with_item,
|
|
44
|
+
...(settled.value.merge_receipt_proof
|
|
45
|
+
? { merge_receipt_proof: settled.value.merge_receipt_proof }
|
|
46
|
+
: {}),
|
|
47
|
+
warnings: settled.value.warnings,
|
|
48
|
+
});
|
|
49
|
+
params.repair.totals[outcome] += 1;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
params.repair.streams.push({
|
|
53
|
+
id,
|
|
54
|
+
outcome: "failed",
|
|
55
|
+
error: settled.reason instanceof Error
|
|
56
|
+
? settled.reason.message
|
|
57
|
+
: String(settled.reason),
|
|
58
|
+
});
|
|
59
|
+
params.repair.totals.failed += 1;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async function settleProvenReceipts(params) {
|
|
63
|
+
if (params.dryRun || params.repair.totals.failed > 0)
|
|
64
|
+
return 0;
|
|
65
|
+
const trustedReceiptIds = new Set(params.repair.streams.flatMap((stream) => stream.merge_receipt_proof?.trusted
|
|
66
|
+
? stream.merge_receipt_proof.receipt_ids
|
|
67
|
+
: []));
|
|
68
|
+
const receiptsToSettle = params.force
|
|
69
|
+
? params.pendingReceipts
|
|
70
|
+
: params.pendingReceipts.filter((receipt) => trustedReceiptIds.has(receipt.id));
|
|
71
|
+
await Promise.all(receiptsToSettle.map((receipt) => markMergeReceiptReconciled(params.gitWorkspaceRoot ?? process.cwd(), receipt, { requireExisting: true })));
|
|
72
|
+
return receiptsToSettle.length;
|
|
73
|
+
}
|
|
8
74
|
/**
|
|
9
75
|
* Preview or apply post-merge history reconciliation and immediately validate
|
|
10
76
|
* the two merge-critical invariants. The default remains explicit and safe:
|
|
@@ -13,9 +79,22 @@ import { listMergeReceipts, markMergeReceiptReconciled, partitionMergeReceipts,
|
|
|
13
79
|
*/
|
|
14
80
|
export async function runMergeReconcile(options, global) {
|
|
15
81
|
const dryRun = options.dryRun === true;
|
|
16
|
-
const
|
|
82
|
+
const pmRoot = resolvePmRoot(process.cwd(), global.path);
|
|
83
|
+
const gitWorkspaceRoot = await findGitWorkspaceRoot(pmRoot);
|
|
84
|
+
const receiptEvidence = await inspectMergeReceiptEvidence(gitWorkspaceRoot ?? process.cwd(), {
|
|
17
85
|
includeLossless: true,
|
|
86
|
+
pmRoot,
|
|
18
87
|
});
|
|
88
|
+
if (receiptEvidence.invalid_evidence_count > 0) {
|
|
89
|
+
throw new PmCliError(`Merge reconciliation refused ${receiptEvidence.invalid_evidence_count} invalid receipt evidence file(s).`, EXIT_CODE.CONFLICT, {
|
|
90
|
+
code: "merge_receipt_evidence_invalid",
|
|
91
|
+
required: "Every clone-local and durable receipt candidate must pass bounded-file, schema, and identity validation.",
|
|
92
|
+
recovery: {
|
|
93
|
+
suggested_retry: "pm health --check-only --full",
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const pendingReceipts = receiptEvidence.receipts;
|
|
19
98
|
const { pendingDecisions: discardedReceipts } = partitionMergeReceipts(pendingReceipts);
|
|
20
99
|
if (!dryRun && options.force !== true && discardedReceipts.length > 0) {
|
|
21
100
|
throw new PmCliError(`Merge reconciliation would accept ${discardedReceipts.length} receipt(s) containing discarded scalar values. Review pm merge report and rerun with --force only after deciding which values to retain or re-apply.`, EXIT_CODE.CONFLICT, {
|
|
@@ -29,11 +108,13 @@ export async function runMergeReconcile(options, global) {
|
|
|
29
108
|
},
|
|
30
109
|
});
|
|
31
110
|
}
|
|
32
|
-
const receiptsByItem =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
111
|
+
const receiptsByItem = new Map();
|
|
112
|
+
for (const receipt of pendingReceipts) {
|
|
113
|
+
const receipts = receiptsByItem.get(receipt.item_id) ?? [];
|
|
114
|
+
receipts.push(receipt);
|
|
115
|
+
receiptsByItem.set(receipt.item_id, receipts);
|
|
116
|
+
}
|
|
117
|
+
const auditContextById = Object.fromEntries([...receiptsByItem].map(([id, receipts]) => [
|
|
37
118
|
id,
|
|
38
119
|
{
|
|
39
120
|
merge: {
|
|
@@ -41,6 +122,10 @@ export async function runMergeReconcile(options, global) {
|
|
|
41
122
|
},
|
|
42
123
|
},
|
|
43
124
|
]));
|
|
125
|
+
const mergeReceiptProofById = Object.fromEntries([...receiptsByItem].map(([id, receipts]) => [
|
|
126
|
+
id,
|
|
127
|
+
{ gitWorkspaceRoot, receipts },
|
|
128
|
+
]));
|
|
44
129
|
const repair = await runHistoryRepairAll({
|
|
45
130
|
dryRun,
|
|
46
131
|
author: options.author ?? global.author,
|
|
@@ -49,51 +134,36 @@ export async function runMergeReconcile(options, global) {
|
|
|
49
134
|
force: options.force,
|
|
50
135
|
auditOperation: "merge_reconcile",
|
|
51
136
|
auditContextById,
|
|
137
|
+
...(pendingReceipts.length > 0 ? { mergeReceiptProofById } : {}),
|
|
52
138
|
}, global);
|
|
53
139
|
const representedIds = new Set(repair.streams.map((stream) => stream.id));
|
|
54
|
-
const receiptOnlyIds =
|
|
55
|
-
const receiptOnlyResults = await
|
|
140
|
+
const receiptOnlyIds = [...receiptsByItem.keys()].filter((id) => !representedIds.has(id));
|
|
141
|
+
const receiptOnlyResults = await mapWithFixedConcurrency(receiptOnlyIds, RECEIPT_ONLY_REPAIR_CONCURRENCY, (id) => runReceiptOnlyRepair({
|
|
142
|
+
id,
|
|
56
143
|
dryRun,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
force: options.force,
|
|
60
|
-
auditOperation: "merge_reconcile",
|
|
144
|
+
options,
|
|
145
|
+
global,
|
|
61
146
|
auditContext: auditContextById[id],
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
});
|
|
76
|
-
repair.totals[outcome] += 1;
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
repair.streams.push({
|
|
80
|
-
id,
|
|
81
|
-
outcome: "failed",
|
|
82
|
-
error: settled.reason instanceof Error
|
|
83
|
-
? settled.reason.message
|
|
84
|
-
: String(settled.reason),
|
|
85
|
-
});
|
|
86
|
-
repair.totals.failed += 1;
|
|
87
|
-
}
|
|
147
|
+
mergeReceiptProof: mergeReceiptProofById[id],
|
|
148
|
+
}));
|
|
149
|
+
appendReceiptOnlyResults({
|
|
150
|
+
receiptOnlyIds,
|
|
151
|
+
results: receiptOnlyResults,
|
|
152
|
+
repair,
|
|
153
|
+
});
|
|
154
|
+
const reconciledReceiptCount = await settleProvenReceipts({
|
|
155
|
+
dryRun,
|
|
156
|
+
force: options.force === true,
|
|
157
|
+
pendingReceipts,
|
|
158
|
+
repair,
|
|
159
|
+
gitWorkspaceRoot,
|
|
88
160
|
});
|
|
89
|
-
let reconciledReceiptCount = 0;
|
|
90
|
-
if (!dryRun && repair.totals.failed === 0) {
|
|
91
|
-
await Promise.all(pendingReceipts.map((receipt) => markMergeReceiptReconciled(process.cwd(), receipt)));
|
|
92
|
-
reconciledReceiptCount = pendingReceipts.length;
|
|
93
|
-
}
|
|
94
161
|
const validation = await runValidate({ checkHistoryDrift: true, checkStorageIntegrity: true }, global);
|
|
95
162
|
const mergeChecksGreen = validation.checks.every((check) => check.status === "ok");
|
|
96
|
-
const
|
|
163
|
+
const receiptSettlementComplete = dryRun
|
|
164
|
+
? pendingReceipts.length === 0
|
|
165
|
+
: reconciledReceiptCount === pendingReceipts.length;
|
|
166
|
+
const ok = repair.totals.failed === 0 && mergeChecksGreen && receiptSettlementComplete;
|
|
97
167
|
const guidance = dryRun
|
|
98
168
|
? [
|
|
99
169
|
"Review repair.streams, then rerun pm merge reconcile without --dry-run to apply audited repairs.",
|
|
@@ -123,4 +193,4 @@ export async function runMergeReconcile(options, global) {
|
|
|
123
193
|
};
|
|
124
194
|
}
|
|
125
195
|
//# sourceMappingURL=reconcile.js.map
|
|
126
|
-
//# debugId=
|
|
196
|
+
//# debugId=c3ec7d9d-f276-5e97-8e97-507925e5d33a
|