@unbrained/pm-cli 2026.9.3 → 2026.9.5
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 +52 -0
- 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 +15 -6
- package/dist/cli/register-list-query.js +4 -2
- package/dist/cli/register-mutation.js +4 -2
- package/dist/cli/register-operations.js +6 -2
- package/dist/cli-bundle/bundle-manifest.json +154 -154
- package/dist/cli-bundle/chunks/{chunk-EDPIS7EB.js → chunk-3HBDTJRK.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-66VPKFP2.js +5 -0
- package/dist/cli-bundle/chunks/{chunk-Y4PJ6HPK.js → chunk-HUAQITVW.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-DKGP3QLE.js → chunk-IBYREGSP.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-LUI4E32I.js +231 -0
- package/dist/cli-bundle/chunks/{chunk-UPPM2FOZ.js → chunk-QPZHQY2O.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-34HASX3S.js → chunk-SF6SM6U5.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-SVK5Y64D.js +40 -0
- package/dist/cli-bundle/chunks/chunk-V5GBD5KV.js +9 -0
- package/dist/cli-bundle/chunks/{chunk-LENC656S.js → chunk-WWAEGW4V.js} +67 -70
- package/dist/cli-bundle/chunks/chunk-ZRAQS3UA.js +8 -0
- package/dist/cli-bundle/chunks/{register-list-query-W2ZTOMKP.js → register-list-query-AYKGODWT.js} +3 -3
- package/dist/cli-bundle/chunks/{register-mutation-RVND5NCG.js → register-mutation-YZYG53SQ.js} +4 -4
- package/dist/cli-bundle/chunks/register-operations-KFSCC7QV.js +2 -0
- package/dist/cli-bundle/chunks/{register-setup-D3ZFHY3L.js → register-setup-UJCRVGBG.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-24IG6ONG.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-M5M6SVHS.js → chunk-3ASU6WGI.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-3ITLIBKS.js +56 -0
- package/dist/cli-bundle/focused-chunks/chunk-3NQD5YDG.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-S27XMBIN.js → chunk-4OSHLNUE.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-65N6JHAN.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-5RFXUPN5.js → chunk-B2TTQGFG.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-GUBM3RGY.js +20 -0
- package/dist/cli-bundle/focused-chunks/{chunk-Q45NHPF2.js → chunk-H5FOGAVD.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-JX3RCVCX.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-D7KXFBXV.js → chunk-KWWCBWFO.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-MV752SUC.js +12 -0
- package/dist/cli-bundle/focused-chunks/chunk-PKIPMCFS.js +6 -0
- package/dist/cli-bundle/focused-chunks/{chunk-4HJFY4MB.js → chunk-QL66QNWG.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-RJMR2JIF.js +9 -0
- package/dist/cli-bundle/focused-chunks/chunk-WIUPWIZR.js +177 -0
- package/dist/cli-bundle/focused-chunks/chunk-WOCIPMW7.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-GT7MXDLW.js → chunk-YEF5PQQ2.js} +2 -2
- package/dist/cli-bundle/main.js +22 -15
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +1 -1
- package/dist/cli-bundle/sdk-core.js +29 -32
- 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 +45 -37
- package/dist/cli-bundle/sdk-query.js +1 -1
- package/dist/cli-bundle/sdk-runtime.js +1 -1
- package/dist/cli-bundle/sdk-testing.js +1 -1
- package/dist/cli-bundle/sdk.js +10 -10
- package/dist/core/diagnostics/remediation.js +4 -4
- package/dist/core/extensions/extension-types.d.ts +2 -0
- package/dist/core/extensions/extension-types.js +2 -2
- 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.d.ts +37 -1
- package/dist/core/history/history.js +311 -59
- 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 +70 -36
- 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/lock/lock.d.ts +10 -0
- package/dist/core/lock/lock.js +42 -3
- package/dist/core/shared/author.d.ts +3 -1
- package/dist/core/shared/author.js +111 -23
- package/dist/core/shared/errors.d.ts +8 -0
- package/dist/core/shared/errors.js +2 -2
- package/dist/core/store/item-store.d.ts +14 -0
- package/dist/core/store/item-store.js +48 -10
- package/dist/core/store/settings-validator.js +3 -2
- 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.js +29 -5
- package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +19 -12
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +14 -2
- package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
- package/dist/sdk/cli-contracts/tool-schema.js +19 -6
- package/dist/sdk/context-intent-contracts.js +3 -3
- package/dist/sdk/context-usage.js +9 -11
- package/dist/sdk/define.d.ts +1 -1
- package/dist/sdk/define.js +2 -2
- package/dist/sdk/environment/agent-environment.d.ts +296 -0
- package/dist/sdk/environment/agent-environment.js +467 -0
- package/dist/sdk/environment/project-runtime-compatibility.d.ts +19 -1
- package/dist/sdk/environment/project-runtime-compatibility.js +118 -21
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +98 -38
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +50 -2
- package/dist/sdk/governance/health.js +13 -44
- package/dist/sdk/governance/merge-receipt-history.d.ts +62 -0
- package/dist/sdk/governance/merge-receipt-history.js +207 -0
- package/dist/sdk/governance/provenance-health.js +9 -3
- package/dist/sdk/governance/validate-history-drift.js +16 -5
- 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 +41 -37
- package/dist/sdk/history-read.d.ts +3 -2
- package/dist/sdk/history-read.js +6 -3
- package/dist/sdk/history-redact.js +58 -23
- package/dist/sdk/history-repair.d.ts +5 -0
- package/dist/sdk/history-repair.js +28 -6
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.js +4 -2
- package/dist/sdk/lifecycle/claim.d.ts +4 -0
- package/dist/sdk/lifecycle/claim.js +9 -3
- package/dist/sdk/lifecycle/restore.d.ts +1 -0
- package/dist/sdk/lifecycle/restore.js +19 -7
- package/dist/sdk/merge/driver.d.ts +3 -1
- package/dist/sdk/merge/driver.js +9 -9
- package/dist/sdk/merge/index.d.ts +1 -1
- package/dist/sdk/merge/index.js +3 -3
- package/dist/sdk/merge/receipts.d.ts +17 -6
- package/dist/sdk/merge/receipts.js +246 -75
- package/dist/sdk/merge/reconcile.d.ts +9 -7
- package/dist/sdk/merge/reconcile.js +175 -12
- package/dist/sdk/merge/three-way.d.ts +22 -2
- package/dist/sdk/merge/three-way.js +92 -25
- package/dist/sdk/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/get.d.ts +2 -2
- package/dist/sdk/query/get.js +4 -3
- 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 +1 -0
- package/dist/sdk/query.js +3 -2
- package/dist/sdk/runtime-primitives.d.ts +3 -3
- package/dist/sdk/runtime-primitives.js +5 -5
- package/dist/types.d.ts +27 -0
- package/dist/types.js +2 -2
- package/docs/AGENT_ENVIRONMENTS.md +47 -0
- package/docs/AGENT_GUIDE.md +36 -0
- package/docs/AGENT_PROVENANCE_ADR.md +17 -2
- package/docs/CONTEXT_INTEGRITY_CONTRACTS.md +38 -1
- package/docs/HISTORY_RECOVERY.md +146 -0
- package/docs/MERGE_SAFETY.md +119 -23
- package/docs/README.md +2 -0
- package/docs/RELEASING.md +25 -11
- package/docs/SDK_CONTEXT_EVIDENCE_CONTRACTS.md +24 -2
- package/docs/SDK_CONTEXT_TRUTH_CONTRACTS.md +24 -1
- package/docs/SDK_EXECUTION_CONTRACTS.md +105 -0
- package/docs/SDK_RUNTIME_BOUNDARIES.md +17 -1
- 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/examples/sdk-agent-environment/README.md +14 -0
- package/docs/examples/sdk-agent-environment/package.json +8 -0
- package/docs/examples/sdk-agent-environment/run-agent-environment.mjs +154 -0
- package/docs/generated/FLAG_LEXICON_BUDGETS.md +10 -10
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +12 -3
- package/marketplace.json +2 -2
- package/package.json +5 -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/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-AY324UJ5.js +0 -9
- package/dist/cli-bundle/chunks/chunk-E5RBBCXO.js +0 -230
- package/dist/cli-bundle/chunks/chunk-IEHHE6GA.js +0 -5
- package/dist/cli-bundle/chunks/chunk-L7B7343B.js +0 -8
- package/dist/cli-bundle/chunks/chunk-LISESA7W.js +0 -35
- package/dist/cli-bundle/chunks/register-operations-TTROIVC4.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-3ZBI35HU.js +0 -176
- package/dist/cli-bundle/focused-chunks/chunk-4RRHC3S4.js +0 -6
- package/dist/cli-bundle/focused-chunks/chunk-4WR46RMG.js +0 -9
- package/dist/cli-bundle/focused-chunks/chunk-BWKHEIVF.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-GHCZ3YFR.js +0 -20
- package/dist/cli-bundle/focused-chunks/chunk-KBLZRU62.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-KZ2NB3ET.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-OL5EH6I3.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-QKHCSUGJ.js +0 -51
- package/dist/cli-bundle/focused-chunks/chunk-TXE4TSHM.js +0 -12
- package/dist/cli-bundle/focused-chunks/chunk-UCLO3XDU.js +0 -2
- package/sdk/public-surface.json +0 -32524
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
/** Registry of every non-extension `pm health` / `pm validate` warning code and its remediation. Extension findings keep their richer, contextual `details.triage.remediation` produced by the extension health triage and are intentionally excluded here (see pm-0hnu: "all non-extension checks"). */
|
|
21
21
|
|
|
22
|
-
!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]="
|
|
22
|
+
!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]="8e57e03e-3c60-5c1b-b455-9318df8027f6")}catch(e){}}();
|
|
23
23
|
export const REMEDIATION_REGISTRY = Object.freeze([
|
|
24
24
|
// --- pm health: directories ---
|
|
25
25
|
{
|
|
@@ -205,8 +205,8 @@ export const REMEDIATION_REGISTRY = Object.freeze([
|
|
|
205
205
|
},
|
|
206
206
|
{
|
|
207
207
|
code: "merge_receipt_history_reference_missing",
|
|
208
|
-
command: "pm
|
|
209
|
-
summary: "
|
|
208
|
+
command: "pm merge reconcile --dry-run",
|
|
209
|
+
summary: "Review the bounded coordinates and recover available evidence first; for exact pre-durable clone-local-only references, rerun with --force so pm appends an audited disposition and proves the health finding is cleared without rewriting history.",
|
|
210
210
|
},
|
|
211
211
|
{
|
|
212
212
|
code: "validate_merge_decisions_unreviewed",
|
|
@@ -504,4 +504,4 @@ export function buildRemediationCommands(warnings) {
|
|
|
504
504
|
return Object.values(buildRemediationMap(warnings));
|
|
505
505
|
}
|
|
506
506
|
//# sourceMappingURL=remediation.js.map
|
|
507
|
-
//# debugId=
|
|
507
|
+
//# debugId=8e57e03e-3c60-5c1b-b455-9318df8027f6
|
|
@@ -839,6 +839,8 @@ export interface SchemaItemTypeDefinition {
|
|
|
839
839
|
description?: string;
|
|
840
840
|
/** Initial status assigned when the contributed item type is created. */
|
|
841
841
|
default_status?: string;
|
|
842
|
+
/** Dispatch role inherited by next, context, and automatic claims. Gates track outcomes; human roles require decisions. */
|
|
843
|
+
execution_role?: "agent" | "human" | "gate";
|
|
842
844
|
/** Value that configures or reports required create fields for this contract. */
|
|
843
845
|
required_create_fields?: string[];
|
|
844
846
|
/** Value that configures or reports required create repeatables for this contract. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Public contract for known extension capabilities, shared by SDK and presentation-layer consumers. */
|
|
2
2
|
|
|
3
|
-
!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]="
|
|
3
|
+
!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]="a2f05fd9-1331-5e75-b8c1-42ad6deb425e")}catch(e){}}();
|
|
4
4
|
export const KNOWN_EXTENSION_CAPABILITIES = [
|
|
5
5
|
"commands",
|
|
6
6
|
"renderers",
|
|
@@ -100,4 +100,4 @@ export function createDefaultExtensionGovernancePolicy() {
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
//# sourceMappingURL=extension-types.js.map
|
|
103
|
-
//# debugId=
|
|
103
|
+
//# debugId=a2f05fd9-1331-5e75-b8c1-42ad6deb425e
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { type HistoryIdentityDiscontinuity } from "./identity.js";
|
|
1
2
|
import type { ItemMetadata } from "../../types/index.js";
|
|
2
3
|
/** Documents the drift scan result payload exchanged by command, SDK, and package integrations. */
|
|
3
4
|
export interface DriftScanResult {
|
|
5
|
+
/** Repeated genesis ordinals and observed sequence, keyed by affected item. */
|
|
6
|
+
identityDiscontinuities?: Array<HistoryIdentityDiscontinuity & {
|
|
7
|
+
item_id: string;
|
|
8
|
+
}>;
|
|
4
9
|
/** Value that configures or reports missing streams for this contract. */
|
|
5
10
|
missingStreams: string[];
|
|
6
11
|
/** Value that configures or reports unreadable streams for this contract. */
|
|
@@ -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]="80648bfe-2436-5b5a-8a16-b9c2875f95fd")}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,12 +7,14 @@ import { getHistoryPath } from "../store/paths.js";
|
|
|
7
7
|
import { isFileMissingError, writeFileAtomic } from "../fs/fs-utils.js";
|
|
8
8
|
import { CURRENT_HISTORY_ITEM_HASH_VERSION, SUPPORTED_HISTORY_ITEM_HASH_VERSIONS, hashDocumentVerificationCandidates, } from "./history.js";
|
|
9
9
|
import { verifyHistoryChainWithVersion } from "./replay.js";
|
|
10
|
+
import { findHistoryIdentityDiscontinuities, } from "./identity.js";
|
|
10
11
|
import { getWorkspaceHistoryPath, inspectWorkspaceHistoryState, WORKSPACE_HISTORY_ID, } from "./workspace-history.js";
|
|
11
|
-
const DRIFT_CACHE_VERSION =
|
|
12
|
+
const DRIFT_CACHE_VERSION = 10;
|
|
12
13
|
const DRIFT_CACHE_FILENAME = "history-drift-cache.json";
|
|
13
14
|
function getDriftCachePath(pmRoot) {
|
|
14
15
|
return path.join(pmRoot, "runtime", DRIFT_CACHE_FILENAME);
|
|
15
16
|
}
|
|
17
|
+
/** Discard unreadable or stale cache envelopes, including prior verification semantics. */
|
|
16
18
|
async function loadDriftCache(pmRoot) {
|
|
17
19
|
try {
|
|
18
20
|
const raw = await fs.readFile(getDriftCachePath(pmRoot), "utf8");
|
|
@@ -123,6 +125,7 @@ async function verifyHistoryStream(historyPath) {
|
|
|
123
125
|
: verification.item_hash_version;
|
|
124
126
|
return {
|
|
125
127
|
latestAfterHash,
|
|
128
|
+
identityDiscontinuities: findHistoryIdentityDiscontinuities(entries),
|
|
126
129
|
chainOk: verification.ok,
|
|
127
130
|
versionSkew,
|
|
128
131
|
latestHashComparable: latestHashVersion !== undefined &&
|
|
@@ -153,15 +156,15 @@ async function loadFreshStreamVerification(itemId, historyPath, accumulator) {
|
|
|
153
156
|
return null;
|
|
154
157
|
}
|
|
155
158
|
}
|
|
159
|
+
/** Reverify invalid streams and content-sensitive cache hits; reuse only compatible healthy evidence. */
|
|
156
160
|
async function resolveStreamVerification(params) {
|
|
157
161
|
const cachedContentHash = typeof params.cached?.content_hash === "string" &&
|
|
158
162
|
params.cached.content_hash.length > 0
|
|
159
163
|
? params.cached.content_hash
|
|
160
164
|
: undefined;
|
|
161
165
|
const canUseCache = cacheMetadataMatches(params.cached, params.stat) &&
|
|
162
|
-
cachedContentHash !== undefined
|
|
163
|
-
|
|
164
|
-
if (!canUseCache || !params.cached) {
|
|
166
|
+
cachedContentHash !== undefined;
|
|
167
|
+
if (!canUseCache || !params.cached || !params.cached.chain_ok) {
|
|
165
168
|
return {
|
|
166
169
|
verification: await loadFreshStreamVerification(params.itemId, params.historyPath, params.accumulator),
|
|
167
170
|
cacheDirty: true,
|
|
@@ -183,6 +186,7 @@ async function resolveStreamVerification(params) {
|
|
|
183
186
|
}
|
|
184
187
|
return {
|
|
185
188
|
verification: {
|
|
189
|
+
identityDiscontinuities: [],
|
|
186
190
|
latestAfterHash: params.cached.latest_after_hash,
|
|
187
191
|
chainOk: params.cached.chain_ok,
|
|
188
192
|
versionSkew: params.cached.version_skew === true,
|
|
@@ -217,6 +221,12 @@ async function scanItemHistory(pmRoot, item, cache, accumulator) {
|
|
|
217
221
|
});
|
|
218
222
|
if (!resolved.verification)
|
|
219
223
|
return resolved.cacheDirty;
|
|
224
|
+
if (resolved.verification.identityDiscontinuities.length > 0) {
|
|
225
|
+
(accumulator.identityDiscontinuities ??= []).push(...resolved.verification.identityDiscontinuities.map((finding) => ({
|
|
226
|
+
...finding,
|
|
227
|
+
item_id: item.id,
|
|
228
|
+
})));
|
|
229
|
+
}
|
|
220
230
|
if (resolved.verification.versionSkew) {
|
|
221
231
|
accumulator.versionSkews.push(item.id);
|
|
222
232
|
}
|
|
@@ -330,4 +340,4 @@ export async function scanHistoryDrift(pmRoot, items, options = {}) {
|
|
|
330
340
|
return { ...accumulator, driftedItems };
|
|
331
341
|
}
|
|
332
342
|
//# sourceMappingURL=drift-scan.js.map
|
|
333
|
-
//# debugId=
|
|
343
|
+
//# debugId=80648bfe-2436-5b5a-8a16-b9c2875f95fd
|
|
@@ -10,7 +10,7 @@ export declare const HISTORY_EVENT_CLASSIFICATION_VERSION: 1;
|
|
|
10
10
|
/** Semantic effect of an immutable history event on project recency. */
|
|
11
11
|
export type HistoryEventClass = "substantive" | "maintenance";
|
|
12
12
|
/** Operations whose meaning is always governance or storage maintenance. */
|
|
13
|
-
export declare const MAINTENANCE_HISTORY_OPERATIONS: readonly ["docs_add", "files_add", "history:author-acknowledge", "history_author_acknowledge", "history_compact", "history_compact_baseline", "history_redact", "history_repair", "normalize", "release", "test_run_track", "tests_add", "tests_remove", "update_audit", "update_ownership_bypass"];
|
|
13
|
+
export declare const MAINTENANCE_HISTORY_OPERATIONS: readonly ["docs_add", "files_add", "history:author-acknowledge", "history_author_acknowledge", "history_compact", "history_compact_baseline", "history_redact", "history_repair", "history_salvage", "normalize", "release", "test_run_track", "tests_add", "tests_remove", "update_audit", "update_ownership_bypass"];
|
|
14
14
|
/** Operations whose meaning always advances lifecycle or working context. */
|
|
15
15
|
export declare const SUBSTANTIVE_HISTORY_OPERATIONS: readonly ["append", "cancel", "claim", "close", "comment_add", "create", "learning_add", "note_add", "reopen"];
|
|
16
16
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Current version of the public history-event classification policy. */
|
|
2
2
|
|
|
3
|
-
!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]="
|
|
3
|
+
!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]="9ad6614e-44bc-549c-915c-6177c3a43c1f")}catch(e){}}();
|
|
4
4
|
export const HISTORY_EVENT_CLASSIFICATION_VERSION = 1;
|
|
5
5
|
/** Operations whose meaning is always governance or storage maintenance. */
|
|
6
6
|
export const MAINTENANCE_HISTORY_OPERATIONS = [
|
|
@@ -12,6 +12,7 @@ export const MAINTENANCE_HISTORY_OPERATIONS = [
|
|
|
12
12
|
"history_compact_baseline",
|
|
13
13
|
"history_redact",
|
|
14
14
|
"history_repair",
|
|
15
|
+
"history_salvage",
|
|
15
16
|
"normalize",
|
|
16
17
|
"release",
|
|
17
18
|
"test_run_track",
|
|
@@ -106,4 +107,4 @@ export function classifyHistoryEvent(entry) {
|
|
|
106
107
|
: "substantive";
|
|
107
108
|
}
|
|
108
109
|
//# sourceMappingURL=event-classification.js.map
|
|
109
|
-
//# debugId=
|
|
110
|
+
//# debugId=9ad6614e-44bc-549c-915c-6177c3a43c1f
|
|
@@ -1,11 +1,47 @@
|
|
|
1
1
|
import { type AuthorSource } from "../shared/author.js";
|
|
2
|
-
import type { HistoryEntry, ItemDocument } from "../../types/index.js";
|
|
2
|
+
import type { HistoryEntry, HistoryPatchOp, ItemDocument } from "../../types/index.js";
|
|
3
3
|
/** Hash epoch for the current canonical item surface written into immutable history. */
|
|
4
4
|
export declare const CURRENT_HISTORY_ITEM_HASH_VERSION: 3;
|
|
5
5
|
/** Hash epochs understood by this runtime. */
|
|
6
6
|
export declare const SUPPORTED_HISTORY_ITEM_HASH_VERSIONS: readonly [1, 2, 3];
|
|
7
|
+
/** Record-integrity epoch written beside new immutable history events. */
|
|
8
|
+
export declare const CURRENT_HISTORY_RECORD_HASH_VERSION: 1;
|
|
9
|
+
/** Record-integrity epochs understood by this runtime. */
|
|
10
|
+
export declare const SUPPORTED_HISTORY_RECORD_HASH_VERSIONS: readonly [1];
|
|
7
11
|
/** Restricts item hash versions accepted by history replay. */
|
|
8
12
|
export type HistoryItemHashVersion = (typeof SUPPORTED_HISTORY_ITEM_HASH_VERSIONS)[number];
|
|
13
|
+
/** Restricts immutable-record hash versions accepted by history replay. */
|
|
14
|
+
export type HistoryRecordHashVersion = (typeof SUPPORTED_HISTORY_RECORD_HASH_VERSIONS)[number];
|
|
15
|
+
/** Canonicalize and hash every immutable record field except the hash value itself. */
|
|
16
|
+
export declare function hashHistoryRecord(entry: Omit<HistoryEntry, "record_hash">): string;
|
|
17
|
+
/** Hash one patch representation without retaining its potentially sensitive values. */
|
|
18
|
+
export declare function hashHistoryPatch(patch: readonly HistoryPatchOp[]): string;
|
|
19
|
+
/** Hash an ordered history prefix for compact checkpoint provenance. */
|
|
20
|
+
export declare function hashHistoryStream(entries: readonly HistoryEntry[]): string;
|
|
21
|
+
/** Attach the current immutable-record integrity envelope to one history event. */
|
|
22
|
+
export declare function sealHistoryRecord(entry: HistoryEntry): HistoryEntry;
|
|
23
|
+
/** Verify record-level attribution coverage while accepting explicit legacy item-state-only entries. */
|
|
24
|
+
export declare function verifyHistoryRecordHash(entry: HistoryEntry): {
|
|
25
|
+
ok: true;
|
|
26
|
+
coverage: "record_and_item_state" | "item_state_only";
|
|
27
|
+
} | {
|
|
28
|
+
ok: false;
|
|
29
|
+
error: "incomplete_record_hash_envelope" | "record_hash_mismatch" | "unsupported_record_hash_version";
|
|
30
|
+
};
|
|
31
|
+
/** Reseal a maintenance rewrite and retain the anchors and patch digest it replaced. */
|
|
32
|
+
export declare function resealHistoryRewrite(original: HistoryEntry, rewritten: HistoryEntry, options?: Readonly<{
|
|
33
|
+
retainOriginalPatch?: boolean;
|
|
34
|
+
retainPriorRecord?: boolean;
|
|
35
|
+
retainPriorRecordHash?: boolean;
|
|
36
|
+
}>): HistoryEntry;
|
|
37
|
+
/** Verify that retained rewrite evidence reconstructs every available prior record envelope. */
|
|
38
|
+
export declare function verifyHistoryRewriteEvidence(entry: HistoryEntry): {
|
|
39
|
+
ok: true;
|
|
40
|
+
coverage: "complete" | "digest_only" | "legacy_anchor_only" | "none";
|
|
41
|
+
} | {
|
|
42
|
+
ok: false;
|
|
43
|
+
error: "rewrite_evidence_invalid" | "rewrite_evidence_patch_hash_mismatch" | "rewrite_evidence_record_hash_mismatch";
|
|
44
|
+
};
|
|
9
45
|
declare function decodeJsonPointer(path: string): string[];
|
|
10
46
|
declare function isDefinedPointerPath(document: unknown, path: string): boolean;
|
|
11
47
|
/** Implements hash document for the public runtime surface of this module. */
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* Implements append-only history and replay behavior for History.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
7
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a5449fec-c5b4-5cf4-9a7f-b7c5dc7161c2")}catch(e){}}();
|
|
8
8
|
import jsonPatch from "fast-json-patch";
|
|
9
|
+
import fs from "node:fs/promises";
|
|
10
|
+
import path from "node:path";
|
|
9
11
|
import { EXIT_CODE, ITEM_METADATA_KEY_ORDER } from "../shared/constants.js";
|
|
10
12
|
import { runActiveServiceOverride } from "../extensions/index.js";
|
|
11
13
|
import { appendLineAtomic } from "../fs/fs-utils.js";
|
|
@@ -26,6 +28,242 @@ const EMPTY_LEGACY_HASH_DOCUMENT = {
|
|
|
26
28
|
export const CURRENT_HISTORY_ITEM_HASH_VERSION = 3;
|
|
27
29
|
/** Hash epochs understood by this runtime. */
|
|
28
30
|
export const SUPPORTED_HISTORY_ITEM_HASH_VERSIONS = [1, 2, 3];
|
|
31
|
+
/** Record-integrity epoch written beside new immutable history events. */
|
|
32
|
+
export const CURRENT_HISTORY_RECORD_HASH_VERSION = 1;
|
|
33
|
+
/** Record-integrity epochs understood by this runtime. */
|
|
34
|
+
export const SUPPORTED_HISTORY_RECORD_HASH_VERSIONS = [1];
|
|
35
|
+
/** Canonicalize and hash every immutable record field except the hash value itself. */
|
|
36
|
+
export function hashHistoryRecord(entry) {
|
|
37
|
+
return sha256Hex(stableStringify(entry));
|
|
38
|
+
}
|
|
39
|
+
/** Hash one patch representation without retaining its potentially sensitive values. */
|
|
40
|
+
export function hashHistoryPatch(patch) {
|
|
41
|
+
return sha256Hex(stableStringify(patch));
|
|
42
|
+
}
|
|
43
|
+
/** Hash an ordered history prefix for compact checkpoint provenance. */
|
|
44
|
+
export function hashHistoryStream(entries) {
|
|
45
|
+
return sha256Hex(stableStringify(entries));
|
|
46
|
+
}
|
|
47
|
+
/** Attach the current immutable-record integrity envelope to one history event. */
|
|
48
|
+
export function sealHistoryRecord(entry) {
|
|
49
|
+
const sealed = {
|
|
50
|
+
...entry,
|
|
51
|
+
record_hash_version: CURRENT_HISTORY_RECORD_HASH_VERSION,
|
|
52
|
+
};
|
|
53
|
+
const { record_hash: _recordHash, ...canonical } = sealed;
|
|
54
|
+
return { ...sealed, record_hash: hashHistoryRecord(canonical) };
|
|
55
|
+
}
|
|
56
|
+
/** Verify record-level attribution coverage while accepting explicit legacy item-state-only entries. */
|
|
57
|
+
export function verifyHistoryRecordHash(entry) {
|
|
58
|
+
if (entry.record_hash_version === undefined &&
|
|
59
|
+
entry.record_hash === undefined) {
|
|
60
|
+
return { ok: true, coverage: "item_state_only" };
|
|
61
|
+
}
|
|
62
|
+
if (entry.record_hash_version === undefined ||
|
|
63
|
+
entry.record_hash === undefined) {
|
|
64
|
+
return { ok: false, error: "incomplete_record_hash_envelope" };
|
|
65
|
+
}
|
|
66
|
+
if (!SUPPORTED_HISTORY_RECORD_HASH_VERSIONS.includes(entry.record_hash_version)) {
|
|
67
|
+
return { ok: false, error: "unsupported_record_hash_version" };
|
|
68
|
+
}
|
|
69
|
+
const { record_hash: _recordHash, ...canonical } = entry;
|
|
70
|
+
return hashHistoryRecord(canonical) === entry.record_hash
|
|
71
|
+
? { ok: true, coverage: "record_and_item_state" }
|
|
72
|
+
: { ok: false, error: "record_hash_mismatch" };
|
|
73
|
+
}
|
|
74
|
+
/** Build the retained coordinate and optional exact-record rewrite evidence. */
|
|
75
|
+
function createHistoryRewriteEvidence(original, options) {
|
|
76
|
+
const evidence = {
|
|
77
|
+
before_hash: original.before_hash,
|
|
78
|
+
after_hash: original.after_hash,
|
|
79
|
+
patch_hash: hashHistoryPatch(original.patch),
|
|
80
|
+
};
|
|
81
|
+
if (original.item_hash_version !== undefined) {
|
|
82
|
+
evidence.item_hash_version = original.item_hash_version;
|
|
83
|
+
}
|
|
84
|
+
if (options.retainOriginalPatch) {
|
|
85
|
+
evidence.patch = structuredClone(original.patch);
|
|
86
|
+
}
|
|
87
|
+
if (options.retainPriorRecordHash !== false &&
|
|
88
|
+
original.record_hash_version !== undefined &&
|
|
89
|
+
original.record_hash !== undefined) {
|
|
90
|
+
evidence.record_hash_version = original.record_hash_version;
|
|
91
|
+
evidence.record_hash = original.record_hash;
|
|
92
|
+
evidence.record = structuredClone(original);
|
|
93
|
+
}
|
|
94
|
+
return evidence;
|
|
95
|
+
}
|
|
96
|
+
/** Reseal a maintenance rewrite and retain the anchors and patch digest it replaced. */
|
|
97
|
+
export function resealHistoryRewrite(original, rewritten, options = {}) {
|
|
98
|
+
const originalRecordVerification = verifyHistoryRecordHash(original);
|
|
99
|
+
if (!originalRecordVerification.ok) {
|
|
100
|
+
throw new TypeError(originalRecordVerification.error);
|
|
101
|
+
}
|
|
102
|
+
const originalPatchHash = hashHistoryPatch(original.patch);
|
|
103
|
+
const retainsRewriteEvidence = options.retainPriorRecord === true ||
|
|
104
|
+
original.before_hash !== rewritten.before_hash ||
|
|
105
|
+
original.after_hash !== rewritten.after_hash ||
|
|
106
|
+
original.item_hash_version !== rewritten.item_hash_version ||
|
|
107
|
+
originalPatchHash !== hashHistoryPatch(rewritten.patch);
|
|
108
|
+
const sealed = sealHistoryRecord({
|
|
109
|
+
...rewritten,
|
|
110
|
+
...(retainsRewriteEvidence
|
|
111
|
+
? {
|
|
112
|
+
reanchor_evidence: [
|
|
113
|
+
...(rewritten.reanchor_evidence ?? []),
|
|
114
|
+
createHistoryRewriteEvidence(original, options),
|
|
115
|
+
],
|
|
116
|
+
}
|
|
117
|
+
: {}),
|
|
118
|
+
});
|
|
119
|
+
const evidenceVerification = verifyHistoryRewriteEvidence(sealed);
|
|
120
|
+
if (!evidenceVerification.ok) {
|
|
121
|
+
throw new TypeError(evidenceVerification.error);
|
|
122
|
+
}
|
|
123
|
+
return sealed;
|
|
124
|
+
}
|
|
125
|
+
/** Return whether runtime data is a dense list of structurally valid patch operations. */
|
|
126
|
+
function isHistoryPatch(value) {
|
|
127
|
+
if (!Array.isArray(value) || Object.keys(value).length !== value.length) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
return value.every((operation) => {
|
|
131
|
+
if (!isRecord(operation) || typeof operation.path !== "string") {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
if (operation.op === "move" || operation.op === "copy") {
|
|
135
|
+
return typeof operation.from === "string";
|
|
136
|
+
}
|
|
137
|
+
if (["add", "replace", "test"].includes(operation.op)) {
|
|
138
|
+
return Object.hasOwn(operation, "value");
|
|
139
|
+
}
|
|
140
|
+
return operation.op === "remove";
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/** Return whether a retained record has the minimum immutable event shape. */
|
|
144
|
+
function isRetainedHistoryRecordShape(value) {
|
|
145
|
+
return (isRecord(value) &&
|
|
146
|
+
typeof value.ts === "string" &&
|
|
147
|
+
typeof value.author === "string" &&
|
|
148
|
+
typeof value.op === "string" &&
|
|
149
|
+
typeof value.before_hash === "string" &&
|
|
150
|
+
typeof value.after_hash === "string" &&
|
|
151
|
+
isHistoryPatch(value.patch));
|
|
152
|
+
}
|
|
153
|
+
/** Return whether optional retained record-hash fields form a complete pair. */
|
|
154
|
+
function hasCompleteRetainedRecordHashEnvelope(evidence) {
|
|
155
|
+
const hasVersion = evidence.record_hash_version !== undefined;
|
|
156
|
+
const hasHash = evidence.record_hash !== undefined;
|
|
157
|
+
return hasVersion === hasHash;
|
|
158
|
+
}
|
|
159
|
+
/** Return whether runtime evidence has the bounded shape needed for verification. */
|
|
160
|
+
function isHistoryReanchorEvidence(evidence) {
|
|
161
|
+
if (!isRecord(evidence) ||
|
|
162
|
+
!hasCompleteRetainedRecordHashEnvelope(evidence) ||
|
|
163
|
+
typeof evidence.before_hash !== "string" ||
|
|
164
|
+
typeof evidence.after_hash !== "string" ||
|
|
165
|
+
typeof evidence.patch_hash !== "string" ||
|
|
166
|
+
!/^[a-f0-9]{64}$/u.test(evidence.patch_hash) ||
|
|
167
|
+
(evidence.patch !== undefined && !isHistoryPatch(evidence.patch)) ||
|
|
168
|
+
(evidence.record_hash_version !== undefined &&
|
|
169
|
+
typeof evidence.record_hash_version !== "number") ||
|
|
170
|
+
(evidence.record_hash !== undefined &&
|
|
171
|
+
(typeof evidence.record_hash !== "string" ||
|
|
172
|
+
!/^[a-f0-9]{64}$/u.test(evidence.record_hash)))) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
return (evidence.record === undefined ||
|
|
176
|
+
isRetainedHistoryRecordShape(evidence.record));
|
|
177
|
+
}
|
|
178
|
+
/** Return whether a retained exact record agrees with its summary coordinates. */
|
|
179
|
+
function matchesRetainedHistoryRecord(evidence) {
|
|
180
|
+
const record = evidence.record;
|
|
181
|
+
return (record !== undefined &&
|
|
182
|
+
record.before_hash === evidence.before_hash &&
|
|
183
|
+
record.after_hash === evidence.after_hash &&
|
|
184
|
+
record.item_hash_version === evidence.item_hash_version &&
|
|
185
|
+
record.record_hash_version === evidence.record_hash_version &&
|
|
186
|
+
record.record_hash === evidence.record_hash &&
|
|
187
|
+
hashHistoryPatch(record.patch) === evidence.patch_hash);
|
|
188
|
+
}
|
|
189
|
+
/** Reconstruct one prior record represented by retained rewrite evidence. */
|
|
190
|
+
function reconstructPriorHistoryRecord(current, evidence, priorEvidence) {
|
|
191
|
+
if (evidence.patch &&
|
|
192
|
+
hashHistoryPatch(evidence.patch) !== evidence.patch_hash) {
|
|
193
|
+
return { kind: "patch_hash_mismatch" };
|
|
194
|
+
}
|
|
195
|
+
if (evidence.record) {
|
|
196
|
+
if (!matchesRetainedHistoryRecord(evidence)) {
|
|
197
|
+
return { kind: "record_hash_mismatch" };
|
|
198
|
+
}
|
|
199
|
+
return { kind: "record", entry: evidence.record };
|
|
200
|
+
}
|
|
201
|
+
const priorPatch = evidence.patch ?? current.patch;
|
|
202
|
+
if (hashHistoryPatch(priorPatch) !== evidence.patch_hash) {
|
|
203
|
+
return { kind: "digest_only" };
|
|
204
|
+
}
|
|
205
|
+
const priorRecord = {
|
|
206
|
+
...current,
|
|
207
|
+
before_hash: evidence.before_hash,
|
|
208
|
+
after_hash: evidence.after_hash,
|
|
209
|
+
patch: priorPatch,
|
|
210
|
+
...(evidence.item_hash_version === undefined
|
|
211
|
+
? {}
|
|
212
|
+
: { item_hash_version: evidence.item_hash_version }),
|
|
213
|
+
...(evidence.record_hash_version === undefined
|
|
214
|
+
? {}
|
|
215
|
+
: { record_hash_version: evidence.record_hash_version }),
|
|
216
|
+
...(evidence.record_hash === undefined
|
|
217
|
+
? {}
|
|
218
|
+
: { record_hash: evidence.record_hash }),
|
|
219
|
+
...(priorEvidence.length === 0 ? {} : { reanchor_evidence: priorEvidence }),
|
|
220
|
+
};
|
|
221
|
+
if (priorEvidence.length === 0)
|
|
222
|
+
delete priorRecord.reanchor_evidence;
|
|
223
|
+
if (evidence.item_hash_version === undefined) {
|
|
224
|
+
delete priorRecord.item_hash_version;
|
|
225
|
+
}
|
|
226
|
+
return evidence.record_hash_version === undefined ||
|
|
227
|
+
evidence.record_hash === undefined
|
|
228
|
+
? { kind: "legacy", entry: priorRecord }
|
|
229
|
+
: { kind: "record", entry: priorRecord };
|
|
230
|
+
}
|
|
231
|
+
/** Verify that retained rewrite evidence reconstructs every available prior record envelope. */
|
|
232
|
+
export function verifyHistoryRewriteEvidence(entry) {
|
|
233
|
+
const evidenceEntries = entry.reanchor_evidence;
|
|
234
|
+
if (evidenceEntries === undefined)
|
|
235
|
+
return { ok: true, coverage: "none" };
|
|
236
|
+
if (!Array.isArray(evidenceEntries)) {
|
|
237
|
+
return { ok: false, error: "rewrite_evidence_invalid" };
|
|
238
|
+
}
|
|
239
|
+
if (evidenceEntries.length === 0)
|
|
240
|
+
return { ok: true, coverage: "none" };
|
|
241
|
+
let reconstructed = entry;
|
|
242
|
+
for (let index = evidenceEntries.length - 1; index >= 0; index -= 1) {
|
|
243
|
+
const evidence = evidenceEntries[index];
|
|
244
|
+
if (!(index in evidenceEntries) || !isHistoryReanchorEvidence(evidence)) {
|
|
245
|
+
return { ok: false, error: "rewrite_evidence_invalid" };
|
|
246
|
+
}
|
|
247
|
+
const prior = reconstructPriorHistoryRecord(reconstructed, evidence, evidenceEntries.slice(0, index));
|
|
248
|
+
if (prior.kind === "patch_hash_mismatch") {
|
|
249
|
+
return { ok: false, error: "rewrite_evidence_patch_hash_mismatch" };
|
|
250
|
+
}
|
|
251
|
+
if (prior.kind === "record_hash_mismatch") {
|
|
252
|
+
return { ok: false, error: "rewrite_evidence_record_hash_mismatch" };
|
|
253
|
+
}
|
|
254
|
+
if (prior.kind === "digest_only") {
|
|
255
|
+
return { ok: true, coverage: "digest_only" };
|
|
256
|
+
}
|
|
257
|
+
if (prior.kind === "legacy") {
|
|
258
|
+
return { ok: true, coverage: "legacy_anchor_only" };
|
|
259
|
+
}
|
|
260
|
+
if (!verifyHistoryRecordHash(prior.entry).ok) {
|
|
261
|
+
return { ok: false, error: "rewrite_evidence_record_hash_mismatch" };
|
|
262
|
+
}
|
|
263
|
+
reconstructed = prior.entry;
|
|
264
|
+
}
|
|
265
|
+
return { ok: true, coverage: "complete" };
|
|
266
|
+
}
|
|
29
267
|
function compareOptionalStrings(left, right) {
|
|
30
268
|
return (left ?? "").localeCompare(right ?? "");
|
|
31
269
|
}
|
|
@@ -78,31 +316,17 @@ function isDefinedPointerPath(document, path) {
|
|
|
78
316
|
}
|
|
79
317
|
let cursor = document;
|
|
80
318
|
for (const segment of segments) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
cursor = next;
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
if (typeof cursor !== "object" || cursor === null) {
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
if (!Object.prototype.hasOwnProperty.call(cursor, segment)) {
|
|
319
|
+
const arrayIndex = /^(0|[1-9]\d*)$/.test(segment) ? Number(segment) : -1;
|
|
320
|
+
const next = Array.isArray(cursor)
|
|
321
|
+
? arrayIndex >= 0 && arrayIndex < cursor.length
|
|
322
|
+
? cursor[arrayIndex]
|
|
323
|
+
: undefined
|
|
324
|
+
: isRecord(cursor) &&
|
|
325
|
+
Object.prototype.hasOwnProperty.call(cursor, segment)
|
|
326
|
+
? cursor[segment]
|
|
327
|
+
: undefined;
|
|
328
|
+
if (next === undefined)
|
|
100
329
|
return false;
|
|
101
|
-
}
|
|
102
|
-
const next = cursor[segment];
|
|
103
|
-
if (next === undefined) {
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
330
|
cursor = next;
|
|
107
331
|
}
|
|
108
332
|
return true;
|
|
@@ -256,7 +480,10 @@ export function createHistoryEntry(params) {
|
|
|
256
480
|
message: params.message === undefined ? undefined : params.message,
|
|
257
481
|
...(context === undefined ? {} : { context }),
|
|
258
482
|
};
|
|
259
|
-
return {
|
|
483
|
+
return sealHistoryRecord({
|
|
484
|
+
...entry,
|
|
485
|
+
event_class: classifyHistoryEvent(entry),
|
|
486
|
+
});
|
|
260
487
|
}
|
|
261
488
|
function isRecord(value) {
|
|
262
489
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -309,51 +536,76 @@ function serializeHistoryLine(value, fallbackEntry) {
|
|
|
309
536
|
}
|
|
310
537
|
return JSON.stringify(value);
|
|
311
538
|
}
|
|
539
|
+
/** Apply one extension-provided append decision and report whether it consumed the write. */
|
|
540
|
+
async function applyHistoryAppendOverride(historyPath, entry, result) {
|
|
541
|
+
if (result === false)
|
|
542
|
+
return true;
|
|
543
|
+
if (typeof result === "string") {
|
|
544
|
+
const line = serializeHistoryLine(result, entry);
|
|
545
|
+
await appendHistoryLineWithIdentityReservation(historyPath, line);
|
|
546
|
+
await invalidateHistoryDriftCacheForPath(historyPath);
|
|
547
|
+
await removeHistoryEventIndexForHistoryPath(historyPath);
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
if (typeof result !== "object" || result === null)
|
|
551
|
+
return false;
|
|
552
|
+
const record = result;
|
|
553
|
+
if (record.skip === true)
|
|
554
|
+
return true;
|
|
555
|
+
const nextHistoryPath = typeof record.history_path === "string" ? record.history_path : historyPath;
|
|
556
|
+
const line = serializeHistoryLine(typeof record.line === "string" ? record.line : (record.entry ?? entry), entry);
|
|
557
|
+
await appendHistoryLineWithIdentityReservation(nextHistoryPath, line);
|
|
558
|
+
await invalidateHistoryDriftCacheForPath(nextHistoryPath);
|
|
559
|
+
await removeHistoryEventIndexForHistoryPath(nextHistoryPath);
|
|
560
|
+
return true;
|
|
561
|
+
}
|
|
562
|
+
/** Exclusively create genesis streams at their effective destination; never remove a failed partial reservation. */
|
|
563
|
+
async function appendHistoryLineWithIdentityReservation(historyPath, line) {
|
|
564
|
+
let record;
|
|
565
|
+
try {
|
|
566
|
+
record = JSON.parse(line);
|
|
567
|
+
}
|
|
568
|
+
catch {
|
|
569
|
+
// Legacy service overrides may provide non-JSON lines; verification diagnoses them.
|
|
570
|
+
record = null;
|
|
571
|
+
}
|
|
572
|
+
if (!isRecord(record) || record.op !== "create") {
|
|
573
|
+
await appendLineAtomic(historyPath, line);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
await fs.mkdir(path.dirname(historyPath), { recursive: true });
|
|
577
|
+
try {
|
|
578
|
+
await fs.writeFile(historyPath, `${line}\n`, { encoding: "utf8", flag: "wx" });
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
if (error.code !== "EEXIST")
|
|
582
|
+
throw error;
|
|
583
|
+
throw new PmCliError("This item identity is reserved by an existing history stream.", EXIT_CODE.CONFLICT, {
|
|
584
|
+
code: "item_identity_reserved",
|
|
585
|
+
required: "Choose a new item ID or restore the original item from its history.",
|
|
586
|
+
nextSteps: ["Run pm history <id> --full, then pm restore <id> <version> to recover the original item."],
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
312
590
|
/** Implements append history entry for the public runtime surface of this module. */
|
|
313
591
|
export async function appendHistoryEntry(historyPath, entry) {
|
|
314
592
|
const override = await runActiveServiceOverride("history_append", {
|
|
315
593
|
history_path: historyPath,
|
|
316
594
|
entry,
|
|
317
595
|
});
|
|
318
|
-
if (override.handled
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
if (typeof override.result === "string") {
|
|
323
|
-
await appendLineAtomic(historyPath, serializeHistoryLine(override.result, entry));
|
|
324
|
-
await invalidateHistoryDriftCacheForPath(historyPath);
|
|
325
|
-
await removeHistoryEventIndexForHistoryPath(historyPath);
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
if (typeof override.result === "object" && override.result !== null) {
|
|
329
|
-
const record = override.result;
|
|
330
|
-
if (record.skip === true) {
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
const nextHistoryPath = typeof record.history_path === "string"
|
|
334
|
-
? record.history_path
|
|
335
|
-
: historyPath;
|
|
336
|
-
if (typeof record.line === "string") {
|
|
337
|
-
await appendLineAtomic(nextHistoryPath, serializeHistoryLine(record.line, entry));
|
|
338
|
-
await invalidateHistoryDriftCacheForPath(nextHistoryPath);
|
|
339
|
-
await removeHistoryEventIndexForHistoryPath(nextHistoryPath);
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
await appendLineAtomic(nextHistoryPath, serializeHistoryLine(record.entry ?? entry, entry));
|
|
343
|
-
await invalidateHistoryDriftCacheForPath(nextHistoryPath);
|
|
344
|
-
await removeHistoryEventIndexForHistoryPath(nextHistoryPath);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
596
|
+
if (override.handled &&
|
|
597
|
+
(await applyHistoryAppendOverride(historyPath, entry, override.result))) {
|
|
598
|
+
return;
|
|
347
599
|
}
|
|
348
600
|
const timestampedEntry = entry.ts.trim().length > 0
|
|
349
601
|
? entry
|
|
350
602
|
: { ...entry, ts: fallbackHistoryTimestamp(entry) };
|
|
351
|
-
const normalizedEntry = {
|
|
603
|
+
const normalizedEntry = sealHistoryRecord({
|
|
352
604
|
...timestampedEntry,
|
|
353
605
|
event_class: classifyHistoryEvent(timestampedEntry),
|
|
354
|
-
};
|
|
606
|
+
});
|
|
355
607
|
await appendHistoryEntryWithEventIndex(historyPath, normalizedEntry, async () => {
|
|
356
|
-
await
|
|
608
|
+
await appendHistoryLineWithIdentityReservation(historyPath, serializeHistoryLine(normalizedEntry, normalizedEntry));
|
|
357
609
|
});
|
|
358
610
|
await invalidateHistoryDriftCacheForPath(historyPath);
|
|
359
611
|
}
|
|
@@ -363,4 +615,4 @@ export const _testOnly = {
|
|
|
363
615
|
isDefinedPointerPath,
|
|
364
616
|
};
|
|
365
617
|
//# sourceMappingURL=history.js.map
|
|
366
|
-
//# debugId=
|
|
618
|
+
//# debugId=a5449fec-c5b4-5cf4-9a7f-b7c5dc7161c2
|