@unbrained/pm-cli 2026.8.13 → 2026.8.15
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 +165 -113
- package/dist/cli/commander-usage.js +17 -4
- package/dist/cli/error-guidance.d.ts +2 -0
- package/dist/cli/error-guidance.js +52 -25
- package/dist/cli/main.js +40 -3
- package/dist/cli/register-mutation.js +4 -2
- package/dist/cli/register-operations.js +5 -5
- package/dist/cli-bundle/bundle-manifest.json +146 -178
- package/dist/cli-bundle/chunks/{chunk-F7FJYGK3.js → chunk-24GAV7XL.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-AIM6VT2N.js +13 -0
- package/dist/cli-bundle/chunks/chunk-DSWDLTDR.js +197 -0
- package/dist/cli-bundle/chunks/{chunk-Q47C67TQ.js → chunk-J42EGKDK.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-KIEGUBYQ.js +2 -0
- package/dist/cli-bundle/chunks/{chunk-3ZMOWAFM.js → chunk-KLKYLSDX.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-QGTUPEU5.js → chunk-LJHOAVGI.js} +59 -59
- package/dist/cli-bundle/chunks/chunk-MEM2ZS3O.js +5 -0
- package/dist/cli-bundle/chunks/chunk-QHPCJWEJ.js +35 -0
- package/dist/cli-bundle/chunks/{chunk-YLBAPY22.js → chunk-WLPDLNEZ.js} +2 -2
- package/dist/cli-bundle/chunks/{register-list-query-4HIZ7EA3.js → register-list-query-CT3LFDDN.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-W64FZ5HN.js → register-mutation-NWEVA5YS.js} +3 -3
- package/dist/cli-bundle/chunks/register-operations-AHIAG5KM.js +2 -0
- package/dist/cli-bundle/chunks/{register-setup-634MFRXU.js → register-setup-ZJZFVAKE.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-4X57RE4U.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-6XKF3PGV.js +18 -0
- package/dist/cli-bundle/focused-chunks/chunk-7NJ4QYQ2.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-I75MANL7.js → chunk-EXZA4257.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-ITAUHSO6.js +50 -0
- package/dist/cli-bundle/focused-chunks/{chunk-OOYYKQWX.js → chunk-KLVUMNXJ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-2RI2FESN.js → chunk-KT5XMYSX.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-E3AVJ655.js → chunk-KTKH4IMG.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-M67F5AIN.js +8 -0
- package/dist/cli-bundle/focused-chunks/{chunk-3DJSGWVB.js → chunk-NSPYTG6G.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-OKA6ELTL.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-PUISBOPP.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-QLNMCESQ.js +155 -0
- package/dist/cli-bundle/focused-chunks/{chunk-IIS2FRKL.js → chunk-SLKC6QLJ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-U65UC4G3.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-WTSER4WJ.js +16 -0
- package/dist/cli-bundle/focused-chunks/{chunk-2JP7W7GO.js → chunk-ZMFXQ7VF.js} +2 -2
- package/dist/cli-bundle/main.js +13 -13
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +1 -1
- package/dist/cli-bundle/sdk-core.js +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 +110 -1
- 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 +2 -2
- package/dist/core/diagnostics/remediation.js +7 -2
- package/dist/core/extensions/reserved-host-flags.js +3 -2
- package/dist/core/history/drift-scan.d.ts +6 -0
- package/dist/core/history/drift-scan.js +74 -50
- package/dist/core/history/replay.d.ts +14 -1
- package/dist/core/history/replay.js +45 -8
- package/dist/core/history/workspace-history.d.ts +59 -0
- package/dist/core/history/workspace-history.js +197 -10
- package/dist/core/output/output.d.ts +2 -0
- package/dist/core/output/output.js +13 -4
- package/dist/core/shared/command-types.d.ts +2 -0
- package/dist/core/shared/command-types.js +2 -2
- package/dist/core/shared/errors.d.ts +12 -0
- package/dist/core/shared/errors.js +2 -2
- package/dist/mcp/tool-definitions.js +9 -5
- package/dist/sdk/agent/refusal-reachability.d.ts +61 -0
- package/dist/sdk/agent/refusal-reachability.js +91 -3
- package/dist/sdk/cli-bootstrap.d.ts +2 -0
- package/dist/sdk/cli-bootstrap.js +7 -2
- package/dist/sdk/cli-contracts/completeness.js +3 -2
- package/dist/sdk/cli-contracts/flag-contracts.js +3 -2
- package/dist/sdk/cli-contracts/registration-helpers.js +3 -2
- package/dist/sdk/cli-contracts/runtime-contracts.d.ts +11 -1
- package/dist/sdk/cli-contracts/runtime-contracts.js +176 -65
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +15 -5
- package/dist/sdk/cli-contracts/tool-schema.js +10 -3
- package/dist/sdk/cli-program.js +4 -3
- package/dist/sdk/context-relevance.js +12 -4
- package/dist/sdk/core.d.ts +2 -2
- package/dist/sdk/core.js +3 -3
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +26 -2
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +50 -26
- package/dist/sdk/governance/assurance-action.d.ts +7 -1
- package/dist/sdk/governance/assurance-action.js +7 -6
- package/dist/sdk/governance/assurance-mutation-error.d.ts +42 -1
- package/dist/sdk/governance/assurance-mutation-error.js +27 -3
- package/dist/sdk/governance/assurance-runtime.js +12 -11
- package/dist/sdk/governance/assurance.d.ts +9 -0
- package/dist/sdk/governance/assurance.js +82 -16
- package/dist/sdk/governance/extension-host-version.d.ts +25 -0
- package/dist/sdk/governance/extension-host-version.js +105 -0
- package/dist/sdk/governance/health.d.ts +32 -1
- package/dist/sdk/governance/health.js +132 -28
- package/dist/sdk/governance/provenance-health.d.ts +27 -0
- package/dist/sdk/governance/provenance-health.js +95 -3
- package/dist/sdk/governance/validate-history-drift.d.ts +7 -0
- package/dist/sdk/governance/validate-history-drift.js +68 -0
- package/dist/sdk/governance/validate.js +9 -52
- package/dist/sdk/governance.d.ts +6 -0
- package/dist/sdk/governance.js +8 -2
- package/dist/sdk/graph/assembly.d.ts +23 -0
- package/dist/sdk/graph/assembly.js +54 -2
- package/dist/sdk/graph/durable-cache.js +50 -20
- package/dist/sdk/graph/governance-contracts.d.ts +1 -1
- package/dist/sdk/graph/governance-contracts.js +4 -2
- package/dist/sdk/graph/governance.d.ts +21 -1
- package/dist/sdk/graph/governance.js +94 -32
- package/dist/sdk/graph/index.d.ts +2 -2
- package/dist/sdk/graph/index.js +4 -4
- package/dist/sdk/graph/mutation-advisory.js +15 -7
- package/dist/sdk/graph/remediation.js +12 -2
- package/dist/sdk/history-read.d.ts +8 -0
- package/dist/sdk/history-read.js +2 -2
- package/dist/sdk/history-repair.d.ts +13 -1
- package/dist/sdk/history-repair.js +62 -18
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +3 -3
- package/dist/sdk/linked-artifacts.js +16 -5
- package/dist/sdk/pagination.d.ts +15 -0
- package/dist/sdk/pagination.js +9 -5
- package/dist/sdk/query/context.d.ts +5 -1
- package/dist/sdk/query/context.js +14 -5
- package/dist/sdk/query/history.js +20 -3
- package/dist/sdk/read-output/continuation.d.ts +11 -0
- package/dist/sdk/read-output/continuation.js +132 -0
- package/dist/sdk/read-output-budget.d.ts +3 -1
- package/dist/sdk/read-output-budget.js +24 -13
- package/dist/sdk/read-output-contracts.d.ts +127 -7
- package/dist/sdk/read-output-contracts.js +381 -35
- package/dist/sdk/read-output-rows.d.ts +13 -0
- package/dist/sdk/read-output-rows.js +60 -2
- package/dist/sdk/runtime-input.js +4 -2
- package/dist/sdk/runtime-primitives.d.ts +1 -1
- package/dist/sdk/runtime-primitives.js +3 -3
- package/dist/sdk/runtime.js +5 -3
- package/docs/ASSURANCE.md +68 -2
- package/docs/COMMANDS.md +39 -4
- package/docs/READ_OUTPUT_CONTRACTS.md +85 -8
- package/docs/RELATIONSHIP_GRAPH.md +20 -5
- package/docs/RELEASING.md +42 -5
- package/docs/SDK.md +12 -5
- package/docs/SDK_CONTEXT_INTEGRITY.md +94 -1
- package/docs/TESTING.md +23 -1
- package/docs/performance/sdk-entrypoint-import-costs.md +20 -13
- package/marketplace.json +2 -2
- package/package.json +7 -7
- package/packages/pm-beads/extensions/beads/index.ts +1 -0
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/extensions/calendar/index.ts +1 -0
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/extensions/digital-twin/index.ts +7 -0
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/extensions/governance-audit/index.ts +4 -0
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/extensions/guide-shell/index.ts +5 -0
- 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/extensions/linked-test-adapters/index.ts +6 -0
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/extensions/search-advanced/index.ts +2 -0
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/extensions/templates/index.ts +4 -0
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/extensions/todos/index.ts +2 -0
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/extensions/vcs/index.ts +7 -0
- 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/bundle-cli.mjs +17 -4
- package/sdk/public-surface.json +1283 -157
- package/dist/cli-bundle/chunks/chunk-DEDU3DG7.js +0 -196
- package/dist/cli-bundle/chunks/chunk-DL5M5J3V.js +0 -13
- package/dist/cli-bundle/chunks/chunk-HIUZVCMN.js +0 -5
- package/dist/cli-bundle/chunks/chunk-IMGIN7R6.js +0 -2
- package/dist/cli-bundle/chunks/chunk-TF3TPKPR.js +0 -34
- package/dist/cli-bundle/chunks/register-operations-C44ET7R5.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-27N23W6M.js +0 -31
- package/dist/cli-bundle/focused-chunks/chunk-3BNVU6LC.js +0 -8
- package/dist/cli-bundle/focused-chunks/chunk-6MLIRBLV.js +0 -5
- package/dist/cli-bundle/focused-chunks/chunk-6XYLENDV.js +0 -155
- package/dist/cli-bundle/focused-chunks/chunk-CP4K4WFS.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-EBTNSFYE.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-HC2OL2H2.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-NFHFZH2Z.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-OGC4XSBC.js +0 -17
- package/dist/cli-bundle/focused-chunks/chunk-PKPOZXLL.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-QDZUABWL.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-QQX5TDFT.js +0 -16
- package/dist/cli-bundle/focused-chunks/chunk-TEZIXMTH.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-WEH6NSL6.js +0 -18
|
@@ -1,5 +1,5 @@
|
|
|
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]="32538934-2cbb-50fe-b721-ad9197cef7d8")}catch(e){}}();
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import path from "node:path";
|
|
@@ -7,7 +7,7 @@ import { getHistoryPath } from "../store/paths.js";
|
|
|
7
7
|
import { isFileMissingError, writeFileAtomic } from "../fs/fs-utils.js";
|
|
8
8
|
import { CURRENT_HISTORY_ITEM_HASH_VERSION, hashDocumentForVersion, } from "./history.js";
|
|
9
9
|
import { verifyHistoryChainWithVersion } from "./replay.js";
|
|
10
|
-
import { getWorkspaceHistoryPath, WORKSPACE_HISTORY_ID, } from "./workspace-history.js";
|
|
10
|
+
import { getWorkspaceHistoryPath, inspectWorkspaceHistoryState, WORKSPACE_HISTORY_ID, } from "./workspace-history.js";
|
|
11
11
|
const DRIFT_CACHE_VERSION = 4;
|
|
12
12
|
const DRIFT_CACHE_FILENAME = "history-drift-cache.json";
|
|
13
13
|
function getDriftCachePath(pmRoot) {
|
|
@@ -173,6 +173,61 @@ async function resolveStreamVerification(params) {
|
|
|
173
173
|
cacheDirty: true,
|
|
174
174
|
};
|
|
175
175
|
}
|
|
176
|
+
/** Scan one item stream and return whether its cache entry needs persistence. */
|
|
177
|
+
async function scanItemHistory(pmRoot, item, cache, accumulator) {
|
|
178
|
+
const historyPath = getHistoryPath(pmRoot, item.id);
|
|
179
|
+
let stat;
|
|
180
|
+
try {
|
|
181
|
+
stat = await fs.stat(historyPath);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
(isFileMissingError(error)
|
|
185
|
+
? accumulator.missingStreams
|
|
186
|
+
: accumulator.unreadableStreams).push(item.id);
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
const resolved = await resolveStreamVerification({
|
|
190
|
+
itemId: item.id,
|
|
191
|
+
historyPath,
|
|
192
|
+
stat,
|
|
193
|
+
cached: cache.previousEntries[item.id],
|
|
194
|
+
verifyCacheHitByContent: cache.verifyCacheHitByContent,
|
|
195
|
+
accumulator,
|
|
196
|
+
});
|
|
197
|
+
if (!resolved.verification)
|
|
198
|
+
return resolved.cacheDirty;
|
|
199
|
+
if (!resolved.verification.chainOk)
|
|
200
|
+
accumulator.chainMismatches.push(item.id);
|
|
201
|
+
cache.nextEntries[item.id] = {
|
|
202
|
+
mtime_ms: stat.mtimeMs,
|
|
203
|
+
ctime_ms: stat.ctimeMs,
|
|
204
|
+
size: stat.size,
|
|
205
|
+
content_hash: resolved.verification.contentHash,
|
|
206
|
+
latest_after_hash: resolved.verification.latestAfterHash,
|
|
207
|
+
chain_ok: resolved.verification.chainOk,
|
|
208
|
+
item_hash_version: resolved.verification.itemHashVersion,
|
|
209
|
+
};
|
|
210
|
+
const { body, ...itemMetadata } = item;
|
|
211
|
+
const currentHash = hashDocumentForVersion({ metadata: itemMetadata, body }, resolved.verification.itemHashVersion);
|
|
212
|
+
if (currentHash !== resolved.verification.latestAfterHash)
|
|
213
|
+
accumulator.hashMismatches.push(item.id);
|
|
214
|
+
return resolved.cacheDirty;
|
|
215
|
+
}
|
|
216
|
+
/** Compare the verified workspace stream with every governed singleton file. */
|
|
217
|
+
async function scanWorkspaceStateAgreement(pmRoot, accumulator) {
|
|
218
|
+
if (accumulator.unreadableStreams.includes(WORKSPACE_HISTORY_ID) ||
|
|
219
|
+
accumulator.chainMismatches.includes(WORKSPACE_HISTORY_ID))
|
|
220
|
+
return;
|
|
221
|
+
try {
|
|
222
|
+
const agreement = await inspectWorkspaceHistoryState(pmRoot);
|
|
223
|
+
accumulator.workspaceStateMismatches.push(...agreement.mismatched_documents);
|
|
224
|
+
accumulator.workspaceStateMissing.push(...agreement.missing_documents);
|
|
225
|
+
accumulator.workspaceStateUnreadable.push(...agreement.unreadable_documents);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
accumulator.unreadableStreams.push(WORKSPACE_HISTORY_ID);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
176
231
|
/**
|
|
177
232
|
* Scan every item's history stream for drift (missing/unreadable streams, broken
|
|
178
233
|
* hash chains, and item/history hash mismatches).
|
|
@@ -189,6 +244,9 @@ export async function scanHistoryDrift(pmRoot, items, options = {}) {
|
|
|
189
244
|
unreadableStreams: [],
|
|
190
245
|
hashMismatches: [],
|
|
191
246
|
chainMismatches: [],
|
|
247
|
+
workspaceStateMismatches: [],
|
|
248
|
+
workspaceStateMissing: [],
|
|
249
|
+
workspaceStateUnreadable: [],
|
|
192
250
|
};
|
|
193
251
|
const cache = await loadDriftCache(pmRoot);
|
|
194
252
|
const previousEntries = cache?.entries ?? {};
|
|
@@ -197,57 +255,18 @@ export async function scanHistoryDrift(pmRoot, items, options = {}) {
|
|
|
197
255
|
// Metadata mode does not open a stat-matched stream, so unreadable-after-stat
|
|
198
256
|
// failures are intentionally deferred to strict validate/history-repair scans.
|
|
199
257
|
const verifyCacheHitByContent = options.cacheHitVerification !== "metadata";
|
|
258
|
+
const cacheState = {
|
|
259
|
+
previousEntries,
|
|
260
|
+
nextEntries,
|
|
261
|
+
verifyCacheHitByContent,
|
|
262
|
+
};
|
|
200
263
|
for (const item of items) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
stat = await fs.stat(historyPath);
|
|
205
|
-
}
|
|
206
|
-
catch (error) {
|
|
207
|
-
if (isFileMissingError(error)) {
|
|
208
|
-
accumulator.missingStreams.push(item.id);
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
accumulator.unreadableStreams.push(item.id);
|
|
212
|
-
}
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
const cached = previousEntries[item.id];
|
|
216
|
-
const resolved = await resolveStreamVerification({
|
|
217
|
-
itemId: item.id,
|
|
218
|
-
historyPath,
|
|
219
|
-
stat,
|
|
220
|
-
cached,
|
|
221
|
-
verifyCacheHitByContent,
|
|
222
|
-
accumulator,
|
|
223
|
-
});
|
|
224
|
-
cacheDirty ||= resolved.cacheDirty;
|
|
225
|
-
if (!resolved.verification) {
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
if (!resolved.verification.chainOk) {
|
|
229
|
-
accumulator.chainMismatches.push(item.id);
|
|
230
|
-
}
|
|
231
|
-
nextEntries[item.id] = {
|
|
232
|
-
mtime_ms: stat.mtimeMs,
|
|
233
|
-
ctime_ms: stat.ctimeMs,
|
|
234
|
-
size: stat.size,
|
|
235
|
-
content_hash: resolved.verification.contentHash,
|
|
236
|
-
latest_after_hash: resolved.verification.latestAfterHash,
|
|
237
|
-
chain_ok: resolved.verification.chainOk,
|
|
238
|
-
item_hash_version: resolved.verification.itemHashVersion,
|
|
239
|
-
};
|
|
240
|
-
const { body, ...itemMetadata } = item;
|
|
241
|
-
const currentHash = hashDocumentForVersion({
|
|
242
|
-
metadata: itemMetadata,
|
|
243
|
-
body,
|
|
244
|
-
}, resolved.verification.itemHashVersion);
|
|
245
|
-
if (currentHash !== resolved.verification.latestAfterHash) {
|
|
246
|
-
accumulator.hashMismatches.push(item.id);
|
|
247
|
-
}
|
|
264
|
+
if (await scanItemHistory(pmRoot, item, cacheState, accumulator))
|
|
265
|
+
cacheDirty = true;
|
|
248
266
|
}
|
|
249
267
|
const workspaceCacheDirty = await scanWorkspaceHistory(pmRoot, previousEntries, nextEntries, verifyCacheHitByContent, accumulator);
|
|
250
268
|
cacheDirty ||= workspaceCacheDirty;
|
|
269
|
+
await scanWorkspaceStateAgreement(pmRoot, accumulator);
|
|
251
270
|
if (cacheDirty ||
|
|
252
271
|
Object.keys(previousEntries).length !== Object.keys(nextEntries).length) {
|
|
253
272
|
const cachePath = getDriftCachePath(pmRoot);
|
|
@@ -265,9 +284,14 @@ export async function scanHistoryDrift(pmRoot, items, options = {}) {
|
|
|
265
284
|
...accumulator.unreadableStreams,
|
|
266
285
|
...accumulator.hashMismatches,
|
|
267
286
|
...accumulator.chainMismatches,
|
|
287
|
+
...(accumulator.workspaceStateMismatches.length > 0 ||
|
|
288
|
+
accumulator.workspaceStateMissing.length > 0 ||
|
|
289
|
+
accumulator.workspaceStateUnreadable.length > 0
|
|
290
|
+
? [WORKSPACE_HISTORY_ID]
|
|
291
|
+
: []),
|
|
268
292
|
]),
|
|
269
293
|
].sort((a, b) => a.localeCompare(b));
|
|
270
294
|
return { ...accumulator, driftedItems };
|
|
271
295
|
}
|
|
272
296
|
//# sourceMappingURL=drift-scan.js.map
|
|
273
|
-
//# debugId=
|
|
297
|
+
//# debugId=32538934-2cbb-50fe-b721-ad9197cef7d8
|
|
@@ -93,8 +93,21 @@ export interface ReanchorResult {
|
|
|
93
93
|
skippedOps: number;
|
|
94
94
|
/** Value that configures or reports details for this contract. */
|
|
95
95
|
details: ReanchorEntryDetail[];
|
|
96
|
+
/** Hash epoch retained while rebuilding the stream. */
|
|
97
|
+
itemHashVersion: HistoryItemHashVersion;
|
|
98
|
+
/** Whether rewritten entries explicitly carry the hash epoch field. */
|
|
99
|
+
explicitItemHashVersion: boolean;
|
|
96
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the hash epoch a repair must retain.
|
|
103
|
+
*
|
|
104
|
+
* A valid stream is authoritative even when legacy entries omit the version
|
|
105
|
+
* field. For a broken stream, one consistent explicit epoch remains
|
|
106
|
+
* authoritative; fully implicit legacy streams stay on epoch 1. Only an
|
|
107
|
+
* irreconcilably mixed stream falls forward to the current epoch.
|
|
108
|
+
*/
|
|
109
|
+
export declare function resolveHistoryRepairItemHashVersion(entries: HistoryEntry[]): HistoryItemHashVersion;
|
|
97
110
|
/** Re-anchor a drifted history chain: replay every entry from empty, recompute the before/after hashes, and only rewrite a patch when the original op set no longer strictly applies (legacy drift). Clean entries keep their patch verbatim so the on-disk diff stays minimal. The returned chain verifies via verifyHistoryChain. */
|
|
98
|
-
export declare function reanchorHistoryEntries(entries: HistoryEntry[]): ReanchorResult;
|
|
111
|
+
export declare function reanchorHistoryEntries(entries: HistoryEntry[], itemHashVersion?: 1 | 2): ReanchorResult;
|
|
99
112
|
/** Implements history entries to raw for the public runtime surface of this module. */
|
|
100
113
|
export declare function historyEntriesToRaw(entries: HistoryEntry[]): string;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Implements append-only history and replay behavior for Replay.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
7
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0c37a722-fda7-523e-991c-f8abfb7a1495")}catch(e){}}();
|
|
8
8
|
import jsonPatch from "fast-json-patch";
|
|
9
9
|
import { ITEM_METADATA_KEY_ORDER } from "../shared/constants.js";
|
|
10
10
|
import { canonicalDocument } from "../item/item-format.js";
|
|
@@ -219,8 +219,35 @@ export function lenientApplyReplayPatch(current, patch) {
|
|
|
219
219
|
};
|
|
220
220
|
return { document, convertedReplaceToAdd, skippedOps };
|
|
221
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Resolve the hash epoch a repair must retain.
|
|
224
|
+
*
|
|
225
|
+
* A valid stream is authoritative even when legacy entries omit the version
|
|
226
|
+
* field. For a broken stream, one consistent explicit epoch remains
|
|
227
|
+
* authoritative; fully implicit legacy streams stay on epoch 1. Only an
|
|
228
|
+
* irreconcilably mixed stream falls forward to the current epoch.
|
|
229
|
+
*/
|
|
230
|
+
export function resolveHistoryRepairItemHashVersion(entries) {
|
|
231
|
+
const verified = verifyHistoryChainWithVersion(entries);
|
|
232
|
+
if (verified.ok && verified.item_hash_version !== undefined) {
|
|
233
|
+
return verified.item_hash_version;
|
|
234
|
+
}
|
|
235
|
+
const explicitVersions = [
|
|
236
|
+
...new Set(entries
|
|
237
|
+
.map((entry) => entry.item_hash_version)
|
|
238
|
+
.filter((version) => version !== undefined &&
|
|
239
|
+
SUPPORTED_HISTORY_ITEM_HASH_VERSIONS.includes(version))),
|
|
240
|
+
];
|
|
241
|
+
if (explicitVersions.length === 1) {
|
|
242
|
+
return explicitVersions[0];
|
|
243
|
+
}
|
|
244
|
+
if (explicitVersions.length === 0) {
|
|
245
|
+
return 1;
|
|
246
|
+
}
|
|
247
|
+
return CURRENT_HISTORY_ITEM_HASH_VERSION;
|
|
248
|
+
}
|
|
222
249
|
/** Re-anchor a drifted history chain: replay every entry from empty, recompute the before/after hashes, and only rewrite a patch when the original op set no longer strictly applies (legacy drift). Clean entries keep their patch verbatim so the on-disk diff stays minimal. The returned chain verifies via verifyHistoryChain. */
|
|
223
|
-
export function reanchorHistoryEntries(entries) {
|
|
250
|
+
export function reanchorHistoryEntries(entries, itemHashVersion = resolveHistoryRepairItemHashVersion(entries)) {
|
|
224
251
|
const unsupportedIndex = entries.findIndex((entry) => entry.item_hash_version !== undefined &&
|
|
225
252
|
!SUPPORTED_HISTORY_ITEM_HASH_VERSIONS.includes(entry.item_hash_version));
|
|
226
253
|
if (unsupportedIndex >= 0) {
|
|
@@ -233,9 +260,11 @@ export function reanchorHistoryEntries(entries) {
|
|
|
233
260
|
let entriesPatchRepaired = 0;
|
|
234
261
|
let convertedReplaceToAdd = 0;
|
|
235
262
|
let skippedOps = 0;
|
|
263
|
+
const explicitItemHashVersion = itemHashVersion !== 1 ||
|
|
264
|
+
entries.some((entry) => entry.item_hash_version !== undefined);
|
|
236
265
|
for (let index = 0; index < entries.length; index += 1) {
|
|
237
266
|
const entry = entries[index];
|
|
238
|
-
const beforeHash = replayHash(replay,
|
|
267
|
+
const beforeHash = replayHash(replay, itemHashVersion);
|
|
239
268
|
const strict = tryApplyReplayPatch(replay, entry.patch);
|
|
240
269
|
let next;
|
|
241
270
|
let outPatch;
|
|
@@ -257,18 +286,24 @@ export function reanchorHistoryEntries(entries) {
|
|
|
257
286
|
skippedOps += entrySkipped;
|
|
258
287
|
entriesPatchRepaired += 1;
|
|
259
288
|
}
|
|
260
|
-
const afterHash = replayHash(next,
|
|
289
|
+
const afterHash = replayHash(next, itemHashVersion);
|
|
261
290
|
const rehashed = beforeHash !== entry.before_hash || afterHash !== entry.after_hash;
|
|
262
291
|
if (rehashed) {
|
|
263
292
|
entriesRehashed += 1;
|
|
264
293
|
}
|
|
265
|
-
|
|
294
|
+
const rewrittenEntry = {
|
|
266
295
|
...entry,
|
|
267
296
|
patch: outPatch,
|
|
268
297
|
before_hash: beforeHash,
|
|
269
298
|
after_hash: afterHash,
|
|
270
|
-
|
|
271
|
-
|
|
299
|
+
};
|
|
300
|
+
if (explicitItemHashVersion) {
|
|
301
|
+
rewrittenEntry.item_hash_version = itemHashVersion;
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
delete rewrittenEntry.item_hash_version;
|
|
305
|
+
}
|
|
306
|
+
rewritten.push(rewrittenEntry);
|
|
272
307
|
details.push({
|
|
273
308
|
index: index + 1,
|
|
274
309
|
rehashed,
|
|
@@ -286,6 +321,8 @@ export function reanchorHistoryEntries(entries) {
|
|
|
286
321
|
convertedReplaceToAdd,
|
|
287
322
|
skippedOps,
|
|
288
323
|
details,
|
|
324
|
+
itemHashVersion,
|
|
325
|
+
explicitItemHashVersion,
|
|
289
326
|
};
|
|
290
327
|
}
|
|
291
328
|
/** Implements history entries to raw for the public runtime surface of this module. */
|
|
@@ -296,4 +333,4 @@ export function historyEntriesToRaw(entries) {
|
|
|
296
333
|
return `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`;
|
|
297
334
|
}
|
|
298
335
|
//# sourceMappingURL=replay.js.map
|
|
299
|
-
//# debugId=
|
|
336
|
+
//# debugId=0c37a722-fda7-523e-991c-f8abfb7a1495
|
|
@@ -76,6 +76,47 @@ export interface WorkspaceAuditEventOptions {
|
|
|
76
76
|
/** Maximum lock wait in milliseconds. */
|
|
77
77
|
lockWaitMs: number;
|
|
78
78
|
}
|
|
79
|
+
/** Agreement between the replayed workspace stream and governed singleton files. */
|
|
80
|
+
export interface WorkspaceHistoryStateAgreement {
|
|
81
|
+
/** Whether every governed singleton is readable and equals its replayed value. */
|
|
82
|
+
ok: boolean;
|
|
83
|
+
/** Number of singleton documents represented by the latest replayed state. */
|
|
84
|
+
document_count: number;
|
|
85
|
+
/** Governed documents whose on-disk JSON equals the replayed value. */
|
|
86
|
+
matching_documents: string[];
|
|
87
|
+
/** Governed documents whose on-disk JSON differs from the replayed value. */
|
|
88
|
+
mismatched_documents: string[];
|
|
89
|
+
/** Governed documents absent from disk. */
|
|
90
|
+
missing_documents: string[];
|
|
91
|
+
/** Governed documents that cannot be read as safe workspace-local JSON. */
|
|
92
|
+
unreadable_documents: string[];
|
|
93
|
+
}
|
|
94
|
+
/** Authorized append-only reconciliation of one out-of-band singleton state. */
|
|
95
|
+
export interface WorkspaceJsonReconciliationOptions extends Omit<WorkspaceJsonWriteOptions, "raw" | "recordCreation"> {
|
|
96
|
+
/** Closed Decision item authorizing acceptance of the on-disk state. */
|
|
97
|
+
authorizationDecision: string;
|
|
98
|
+
}
|
|
99
|
+
/** Restore one governed singleton from a recorded workspace history version. */
|
|
100
|
+
export interface WorkspaceJsonRestoreOptions extends Omit<WorkspaceJsonWriteOptions, "raw" | "recordCreation"> {
|
|
101
|
+
/** One-based workspace history version whose document value is restored. */
|
|
102
|
+
targetVersion: number;
|
|
103
|
+
}
|
|
104
|
+
/** Receipt returned after a singleton is restored from workspace history. */
|
|
105
|
+
export interface WorkspaceJsonRestoreResult {
|
|
106
|
+
/** Tracker-relative singleton identity. */
|
|
107
|
+
document_path: string;
|
|
108
|
+
/** One-based workspace history version used as the restore source. */
|
|
109
|
+
restored_from_version: number;
|
|
110
|
+
/** Whether the on-disk singleton and latest history state changed. */
|
|
111
|
+
changed: boolean;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Compare every singleton represented by the verified workspace stream with
|
|
115
|
+
* its current on-disk JSON. Hash-chain verification proves recorded ordering;
|
|
116
|
+
* this state-agreement check separately proves that every current singleton
|
|
117
|
+
* mutation is represented by that chain.
|
|
118
|
+
*/
|
|
119
|
+
export declare function inspectWorkspaceHistoryState(pmRoot: string): Promise<WorkspaceHistoryStateAgreement>;
|
|
79
120
|
/**
|
|
80
121
|
* Append one workspace document mutation under the dedicated workspace-history
|
|
81
122
|
* lock. Existing history is verified and replayed before the new entry is
|
|
@@ -103,3 +144,21 @@ export declare function mutateWorkspaceJsonWithHistory<Result>(params: Workspace
|
|
|
103
144
|
* under the workspace-history lock, with compensation if history append fails.
|
|
104
145
|
*/
|
|
105
146
|
export declare function writeWorkspaceJsonWithHistory(params: WorkspaceJsonWriteOptions): Promise<boolean>;
|
|
147
|
+
/**
|
|
148
|
+
* Append an authorized reconciliation event that adopts a reviewed on-disk
|
|
149
|
+
* singleton after an out-of-band change. The existing stream is never edited:
|
|
150
|
+
* its latest replayed state becomes the event before-state and the reviewed
|
|
151
|
+
* singleton becomes the after-state, with the authorizing Decision recorded in
|
|
152
|
+
* structured context.
|
|
153
|
+
*/
|
|
154
|
+
export declare function reconcileWorkspaceJsonHistory(options: WorkspaceJsonReconciliationOptions): Promise<{
|
|
155
|
+
changed: boolean;
|
|
156
|
+
entry?: HistoryEntry;
|
|
157
|
+
historyPath: string;
|
|
158
|
+
}>;
|
|
159
|
+
/**
|
|
160
|
+
* Restore one governed JSON singleton to the value recorded at a one-based
|
|
161
|
+
* workspace history version, then append the restore as a new history state so
|
|
162
|
+
* replay remains forward-only and independently verifiable.
|
|
163
|
+
*/
|
|
164
|
+
export declare function restoreWorkspaceJsonFromHistory(options: WorkspaceJsonRestoreOptions): Promise<WorkspaceJsonRestoreResult>;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* HistoryEntry format used by item mutations.
|
|
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]="0f666995-78f8-5741-ae0c-0d792feea599")}catch(e){}}();
|
|
9
9
|
import fs from "node:fs/promises";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { readFileIfExists, writeFileAtomic } from "../fs/fs-utils.js";
|
|
@@ -37,14 +37,83 @@ function workspaceDocument(documents, timestamp) {
|
|
|
37
37
|
};
|
|
38
38
|
return { metadata, body: "" };
|
|
39
39
|
}
|
|
40
|
-
function replayWorkspaceEntries(entries) {
|
|
40
|
+
function replayWorkspaceEntries(entries, count = entries.length) {
|
|
41
41
|
let replay = cloneEmptyReplayDocument();
|
|
42
|
-
for (const entry of entries) {
|
|
42
|
+
for (const entry of entries.slice(0, count)) {
|
|
43
43
|
const applied = tryApplyReplayPatch(replay, entry.patch);
|
|
44
44
|
replay = applied.document;
|
|
45
45
|
}
|
|
46
46
|
return replayToItemDocument(replay);
|
|
47
47
|
}
|
|
48
|
+
/** Read the governed document map from one replayed workspace document. */
|
|
49
|
+
function workspaceDocuments(document) {
|
|
50
|
+
return "documents" in document.metadata &&
|
|
51
|
+
typeof document.metadata.documents === "object" &&
|
|
52
|
+
document.metadata.documents !== null &&
|
|
53
|
+
!Array.isArray(document.metadata.documents)
|
|
54
|
+
? document.metadata.documents
|
|
55
|
+
: {};
|
|
56
|
+
}
|
|
57
|
+
/** Resolve a governed singleton path and reject paths outside the tracker root. */
|
|
58
|
+
function resolveGovernedDocumentPath(pmRoot, filePath) {
|
|
59
|
+
const absolutePath = path.resolve(filePath);
|
|
60
|
+
const documentPath = path.relative(pmRoot, absolutePath).replaceAll("\\", "/");
|
|
61
|
+
if (documentPath.length === 0 ||
|
|
62
|
+
documentPath === ".." ||
|
|
63
|
+
documentPath.startsWith("../") ||
|
|
64
|
+
path.isAbsolute(documentPath)) {
|
|
65
|
+
throw new TypeError("Workspace history document path must stay inside the tracker root");
|
|
66
|
+
}
|
|
67
|
+
return { documentPath, absolutePath };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Compare every singleton represented by the verified workspace stream with
|
|
71
|
+
* its current on-disk JSON. Hash-chain verification proves recorded ordering;
|
|
72
|
+
* this state-agreement check separately proves that every current singleton
|
|
73
|
+
* mutation is represented by that chain.
|
|
74
|
+
*/
|
|
75
|
+
export async function inspectWorkspaceHistoryState(pmRoot) {
|
|
76
|
+
const entries = await readHistoryEntries(getWorkspaceHistoryPath(pmRoot), WORKSPACE_HISTORY_ID);
|
|
77
|
+
const verification = verifyHistoryChain(entries);
|
|
78
|
+
if (!verification.ok) {
|
|
79
|
+
throw new TypeError(`Workspace history verification failed: ${verification.errors.join(", ")}`);
|
|
80
|
+
}
|
|
81
|
+
const documents = workspaceDocuments(entries.length === 0
|
|
82
|
+
? EMPTY_CANONICAL_DOCUMENT
|
|
83
|
+
: replayWorkspaceEntries(entries));
|
|
84
|
+
const matching = [];
|
|
85
|
+
const mismatched = [];
|
|
86
|
+
const missing = [];
|
|
87
|
+
const unreadable = [];
|
|
88
|
+
for (const documentPath of Object.keys(documents).sort()) {
|
|
89
|
+
let raw;
|
|
90
|
+
try {
|
|
91
|
+
const resolved = resolveGovernedDocumentPath(pmRoot, path.resolve(pmRoot, documentPath));
|
|
92
|
+
raw = await readFileIfExists(resolved.absolutePath);
|
|
93
|
+
if (raw === null) {
|
|
94
|
+
missing.push(documentPath);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const current = JSON.parse(raw);
|
|
98
|
+
(stableStringify(current) === stableStringify(documents[documentPath])
|
|
99
|
+
? matching
|
|
100
|
+
: mismatched).push(documentPath);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
unreadable.push(documentPath);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
ok: mismatched.length === 0 &&
|
|
108
|
+
missing.length === 0 &&
|
|
109
|
+
unreadable.length === 0,
|
|
110
|
+
document_count: Object.keys(documents).length,
|
|
111
|
+
matching_documents: matching,
|
|
112
|
+
mismatched_documents: mismatched,
|
|
113
|
+
missing_documents: missing,
|
|
114
|
+
unreadable_documents: unreadable,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
48
117
|
/**
|
|
49
118
|
* Append one workspace document mutation while the caller holds the dedicated
|
|
50
119
|
* workspace-history lock.
|
|
@@ -66,12 +135,7 @@ async function appendWorkspaceHistoryChangeLocked(change) {
|
|
|
66
135
|
const beforeDocument = entries.length === 0
|
|
67
136
|
? EMPTY_CANONICAL_DOCUMENT
|
|
68
137
|
: replayWorkspaceEntries(entries);
|
|
69
|
-
const priorDocuments =
|
|
70
|
-
typeof beforeDocument.metadata.documents === "object" &&
|
|
71
|
-
beforeDocument.metadata.documents !== null &&
|
|
72
|
-
!Array.isArray(beforeDocument.metadata.documents)
|
|
73
|
-
? beforeDocument.metadata.documents
|
|
74
|
-
: {};
|
|
138
|
+
const priorDocuments = workspaceDocuments(beforeDocument);
|
|
75
139
|
const recordedBefore = priorDocuments[change.documentPath];
|
|
76
140
|
if (recordedBefore !== undefined &&
|
|
77
141
|
stableStringify(recordedBefore) !== stableStringify(change.before)) {
|
|
@@ -220,5 +284,128 @@ export async function writeWorkspaceJsonWithHistory(params) {
|
|
|
220
284
|
await release();
|
|
221
285
|
}
|
|
222
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Append an authorized reconciliation event that adopts a reviewed on-disk
|
|
289
|
+
* singleton after an out-of-band change. The existing stream is never edited:
|
|
290
|
+
* its latest replayed state becomes the event before-state and the reviewed
|
|
291
|
+
* singleton becomes the after-state, with the authorizing Decision recorded in
|
|
292
|
+
* structured context.
|
|
293
|
+
*/
|
|
294
|
+
export async function reconcileWorkspaceJsonHistory(options) {
|
|
295
|
+
const authorizationDecision = options.authorizationDecision.trim();
|
|
296
|
+
if (!authorizationDecision) {
|
|
297
|
+
throw new TypeError("Workspace history reconciliation requires an authorization decision");
|
|
298
|
+
}
|
|
299
|
+
const release = await acquireLock(options.pmRoot, "workspace-history", options.lockTtlSeconds, options.author, false, false, options.lockWaitMs);
|
|
300
|
+
try {
|
|
301
|
+
const { documentPath, absolutePath } = resolveGovernedDocumentPath(options.pmRoot, options.filePath);
|
|
302
|
+
const historyPath = getWorkspaceHistoryPath(options.pmRoot);
|
|
303
|
+
const entries = await readHistoryEntries(historyPath, WORKSPACE_HISTORY_ID);
|
|
304
|
+
const verification = verifyHistoryChain(entries);
|
|
305
|
+
if (!verification.ok) {
|
|
306
|
+
throw new TypeError(`Workspace history verification failed: ${verification.errors.join(", ")}`);
|
|
307
|
+
}
|
|
308
|
+
const beforeDocument = replayWorkspaceEntries(entries);
|
|
309
|
+
const priorDocuments = workspaceDocuments(beforeDocument);
|
|
310
|
+
if (!Object.hasOwn(priorDocuments, documentPath)) {
|
|
311
|
+
throw new TypeError(`Workspace history does not govern document ${documentPath}`);
|
|
312
|
+
}
|
|
313
|
+
const raw = await readFileIfExists(absolutePath);
|
|
314
|
+
if (raw === null) {
|
|
315
|
+
throw new TypeError(`Workspace history document is missing: ${documentPath}`);
|
|
316
|
+
}
|
|
317
|
+
const current = JSON.parse(raw);
|
|
318
|
+
if (stableStringify(priorDocuments[documentPath]) === stableStringify(current)) {
|
|
319
|
+
return { changed: false, historyPath };
|
|
320
|
+
}
|
|
321
|
+
const timestamp = new Date().toISOString();
|
|
322
|
+
const entry = createHistoryEntry({
|
|
323
|
+
nowIso: timestamp,
|
|
324
|
+
author: options.author,
|
|
325
|
+
op: options.op,
|
|
326
|
+
before: beforeDocument,
|
|
327
|
+
after: workspaceDocument({ ...priorDocuments, [documentPath]: current }, beforeDocument.metadata.created_at),
|
|
328
|
+
message: options.message,
|
|
329
|
+
context: {
|
|
330
|
+
reason: "out_of_band_workspace_state_reconciliation",
|
|
331
|
+
document_path: documentPath,
|
|
332
|
+
authorization_decision: authorizationDecision,
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
await appendHistoryEntry(historyPath, entry);
|
|
336
|
+
return { changed: true, entry, historyPath };
|
|
337
|
+
}
|
|
338
|
+
finally {
|
|
339
|
+
await release();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Restore one governed JSON singleton to the value recorded at a one-based
|
|
344
|
+
* workspace history version, then append the restore as a new history state so
|
|
345
|
+
* replay remains forward-only and independently verifiable.
|
|
346
|
+
*/
|
|
347
|
+
export async function restoreWorkspaceJsonFromHistory(options) {
|
|
348
|
+
const release = await acquireLock(options.pmRoot, "workspace-history", options.lockTtlSeconds, options.author, false, false, options.lockWaitMs);
|
|
349
|
+
try {
|
|
350
|
+
const { documentPath, absolutePath } = resolveGovernedDocumentPath(options.pmRoot, options.filePath);
|
|
351
|
+
const entries = await readHistoryEntries(getWorkspaceHistoryPath(options.pmRoot), WORKSPACE_HISTORY_ID);
|
|
352
|
+
if (!Number.isInteger(options.targetVersion) ||
|
|
353
|
+
options.targetVersion < 1 ||
|
|
354
|
+
options.targetVersion > entries.length) {
|
|
355
|
+
throw new TypeError(`Invalid workspace history target version: ${String(options.targetVersion)}`);
|
|
356
|
+
}
|
|
357
|
+
const verification = verifyHistoryChain(entries);
|
|
358
|
+
if (!verification.ok) {
|
|
359
|
+
throw new TypeError(`Workspace history verification failed: ${verification.errors.join(", ")}`);
|
|
360
|
+
}
|
|
361
|
+
const target = workspaceDocuments(replayWorkspaceEntries(entries, options.targetVersion))[documentPath];
|
|
362
|
+
if (target === undefined) {
|
|
363
|
+
throw new TypeError(`Workspace history version ${options.targetVersion} does not contain ${documentPath}`);
|
|
364
|
+
}
|
|
365
|
+
const beforeRaw = await readFileIfExists(absolutePath);
|
|
366
|
+
const targetRaw = `${JSON.stringify(target, null, 2)}\n`;
|
|
367
|
+
if (beforeRaw === targetRaw)
|
|
368
|
+
return {
|
|
369
|
+
document_path: documentPath,
|
|
370
|
+
restored_from_version: options.targetVersion,
|
|
371
|
+
changed: false,
|
|
372
|
+
};
|
|
373
|
+
const beforeDocument = replayWorkspaceEntries(entries);
|
|
374
|
+
const timestamp = new Date().toISOString();
|
|
375
|
+
const entry = createHistoryEntry({
|
|
376
|
+
nowIso: timestamp,
|
|
377
|
+
author: options.author,
|
|
378
|
+
op: options.op,
|
|
379
|
+
before: beforeDocument,
|
|
380
|
+
after: workspaceDocument({ ...workspaceDocuments(beforeDocument), [documentPath]: target }, beforeDocument.metadata.created_at),
|
|
381
|
+
message: options.message,
|
|
382
|
+
context: {
|
|
383
|
+
reason: "workspace_state_restore",
|
|
384
|
+
document_path: documentPath,
|
|
385
|
+
restored_from_version: options.targetVersion,
|
|
386
|
+
replaced_out_of_band_state: true,
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
await writeFileAtomic(absolutePath, targetRaw);
|
|
390
|
+
try {
|
|
391
|
+
await appendHistoryEntry(getWorkspaceHistoryPath(options.pmRoot), entry);
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
if (beforeRaw === null)
|
|
395
|
+
await fs.rm(absolutePath, { force: true });
|
|
396
|
+
else
|
|
397
|
+
await writeFileAtomic(absolutePath, beforeRaw);
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
document_path: documentPath,
|
|
402
|
+
restored_from_version: options.targetVersion,
|
|
403
|
+
changed: true,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
finally {
|
|
407
|
+
await release();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
223
410
|
//# sourceMappingURL=workspace-history.js.map
|
|
224
|
-
//# debugId=
|
|
411
|
+
//# debugId=0f666995-78f8-5741-ae0c-0d792feea599
|
|
@@ -24,6 +24,8 @@ export interface OutputOptions {
|
|
|
24
24
|
outputFormat?: "toon" | "json";
|
|
25
25
|
/** Caller-carried cross-call budget and served-item state. */
|
|
26
26
|
outputSession?: string;
|
|
27
|
+
/** Opaque budget-compaction continuation cursor. */
|
|
28
|
+
outputCursor?: string;
|
|
27
29
|
/** Include machine-readable row location and row-encoding metadata. */
|
|
28
30
|
outputRowContract?: boolean;
|
|
29
31
|
/** Fallback output format used when callers do not provide an override. */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Formats compact human and machine output for Output.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
7
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="bccd5918-1a51-5258-8d66-1960c51523cf")}catch(e){}}();
|
|
8
8
|
import { getActiveCommandContext, runActiveCommandOverride, runActiveRendererOverride, runActiveServiceOverrideSync, setActiveCommandResult, } from "../extensions/index.js";
|
|
9
9
|
import { EXIT_CODE } from "../shared/constants.js";
|
|
10
10
|
import { isHostOutputSuppressed } from "./output-control.js";
|
|
@@ -366,12 +366,21 @@ function formatEffectiveOutput(effectiveResult, nativeOutput, options) {
|
|
|
366
366
|
["outputBudget", options.outputBudget],
|
|
367
367
|
["outputFormat", options.outputFormat],
|
|
368
368
|
["outputSession", options.outputSession],
|
|
369
|
+
["outputCursor", options.outputCursor],
|
|
369
370
|
["outputRowContract", options.outputRowContract],
|
|
370
371
|
].filter((entry) => entry[1] !== undefined)),
|
|
371
372
|
};
|
|
372
373
|
const format = resolveReadOutputEncoding(command ?? "", commandOptions) ??
|
|
373
374
|
resolveOutputFormat(options);
|
|
374
|
-
const
|
|
375
|
+
const resolvedCommandOptions = {
|
|
376
|
+
...commandOptions,
|
|
377
|
+
resolvedOutputFormat: format,
|
|
378
|
+
};
|
|
379
|
+
const effectiveOptions = {
|
|
380
|
+
...options,
|
|
381
|
+
command,
|
|
382
|
+
commandOptions: resolvedCommandOptions,
|
|
383
|
+
};
|
|
375
384
|
const service = resolveOutputService(effectiveResult, nativeOutput, effectiveOptions, format);
|
|
376
385
|
if (service.rendered !== null)
|
|
377
386
|
return service.rendered;
|
|
@@ -379,7 +388,7 @@ function formatEffectiveOutput(effectiveResult, nativeOutput, options) {
|
|
|
379
388
|
const projectedOutputResult = command === "test"
|
|
380
389
|
? projectLinkedTestEvidence(outputResult, options.lean === true)
|
|
381
390
|
: outputResult;
|
|
382
|
-
const intentOutputResult = attachReadOutputContracts(command,
|
|
391
|
+
const intentOutputResult = attachReadOutputContracts(command, resolvedCommandOptions, projectedOutputResult);
|
|
383
392
|
const renderResolvedOutput = (value) => {
|
|
384
393
|
if (format === "toon") {
|
|
385
394
|
const markdownDefault = renderDefaultMarkdownResult(value);
|
|
@@ -456,4 +465,4 @@ export const outputTestOnly = {
|
|
|
456
465
|
renderToonValue,
|
|
457
466
|
};
|
|
458
467
|
//# sourceMappingURL=output.js.map
|
|
459
|
-
//# debugId=
|
|
468
|
+
//# debugId=bccd5918-1a51-5258-8d66-1960c51523cf
|
|
@@ -29,6 +29,8 @@ export interface GlobalOptions {
|
|
|
29
29
|
outputFormat?: "toon" | "json";
|
|
30
30
|
/** Caller-carried cross-call budget and served-item state. */
|
|
31
31
|
outputSession?: string;
|
|
32
|
+
/** Opaque budget-compaction continuation cursor. */
|
|
33
|
+
outputCursor?: string;
|
|
32
34
|
/** Include the machine-readable row selector and TOON encoding contract. */
|
|
33
35
|
outputRowContract?: boolean;
|
|
34
36
|
/** Filesystem path used for path resolution. */
|