@unbrained/pm-cli 2026.10.25 → 2026.10.27
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 +1 -1
- package/CHANGELOG.md +44 -1
- package/dist/cli/error-guidance.d.ts +5 -0
- package/dist/cli/error-guidance.js +2 -2
- package/dist/cli/register-list-query.js +5 -5
- package/dist/cli/register-mutation.js +2 -2
- package/dist/cli-bundle/bundle-manifest.json +1 -1
- package/dist/cli-bundle/chunks/{chunk-O3ZTGRIH.js → chunk-25MTQAHX.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-YCCGZHOR.js → chunk-2XUDAQK5.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-VAEB6IUQ.js → chunk-32QK43XG.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-RD6UW3MW.js → chunk-34OQP43K.js} +3 -3
- package/dist/cli-bundle/chunks/{chunk-TLMXSYJA.js → chunk-57YPOOED.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-5PTMQBKJ.js +30 -0
- package/dist/cli-bundle/chunks/{chunk-FNZT4BTE.js → chunk-EKV5GP6P.js} +86 -86
- package/dist/cli-bundle/chunks/{chunk-LNKPH2DD.js → chunk-ER7RLJW5.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-F26MRQZY.js +6 -0
- package/dist/cli-bundle/chunks/chunk-H434DKYU.js +2 -0
- package/dist/cli-bundle/chunks/{chunk-MVJGGCGQ.js → chunk-HIX2OMEZ.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-4GD6QOP6.js → chunk-NNIYQHYT.js} +3 -3
- package/dist/cli-bundle/chunks/chunk-O3LPT34G.js +20 -0
- package/dist/cli-bundle/chunks/{chunk-3CYXE6BV.js → chunk-ONC4BBQP.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-UI3FD3DW.js → chunk-OQOEBWHU.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-Q55KZDNF.js +2 -0
- package/dist/cli-bundle/chunks/chunk-QQYLNHZK.js +128 -0
- package/dist/cli-bundle/chunks/chunk-SPPHDVZE.js +2 -0
- package/dist/cli-bundle/chunks/chunk-UHKN6TJM.js +114 -0
- package/dist/cli-bundle/chunks/{chunk-7T3H24HN.js → chunk-X5TMR7KC.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-EYCVAAIP.js → chunk-XERBUA6A.js} +2 -2
- package/dist/cli-bundle/chunks/indexed-receipt-blobs-63WYG2PY.js +4 -0
- package/dist/cli-bundle/chunks/{mutation-admission-747C6IUR.js → mutation-admission-L3A6FSZ7.js} +2 -2
- package/dist/cli-bundle/chunks/{register-list-query-COST6KRV.js → register-list-query-YSQ7UTFI.js} +9 -9
- package/dist/cli-bundle/chunks/register-mutation-6HRXYASW.js +20 -0
- package/dist/cli-bundle/chunks/{register-operations-S4LKLUBJ.js → register-operations-4O3YOYXF.js} +2 -2
- package/dist/cli-bundle/chunks/{register-setup-3ITZXPOL.js → register-setup-3WAXNDMY.js} +2 -2
- package/dist/cli-bundle/chunks/{settlement-hook-5IVCF6ZY.js → settlement-hook-CRW6PVQK.js} +2 -2
- package/dist/cli-bundle/chunks/{stage-3UUIYROS.js → stage-I46WD4EN.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-U6HXIF6G.js → chunk-4TXZBGOR.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-22727JYL.js → chunk-B266DDBJ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-Y4JHHY24.js → chunk-BH7RE2ZY.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-ZYCOMRFT.js → chunk-E5C24MCV.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-EGN2HFRU.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-HWD63G54.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-J67PFM3O.js +91 -0
- package/dist/cli-bundle/focused-chunks/{chunk-46KDJH3K.js → chunk-KTXU5WU6.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-MVY6JDIH.js +12 -0
- package/dist/cli-bundle/focused-chunks/{chunk-NC5HUGHU.js → chunk-NLZUI234.js} +4 -4
- package/dist/cli-bundle/focused-chunks/{chunk-B5JOVH5F.js → chunk-R2MTHS2H.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-SM2KGD4J.js +79 -0
- package/dist/cli-bundle/focused-chunks/{chunk-HMYDUEQK.js → chunk-SYL73MEI.js} +3 -3
- package/dist/cli-bundle/focused-chunks/{chunk-77NU7NPN.js → chunk-TAFMNTA5.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-74KC7FUQ.js → chunk-TGUOEOKP.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-U4UBQBS7.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-URWYYJ7K.js → chunk-WDKZB3V6.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-XK3XMZM4.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-3M5FZCMC.js → chunk-Y57A4ZDI.js} +55 -55
- package/dist/cli-bundle/focused-chunks/chunk-YC6NCPLK.js +22 -0
- package/dist/cli-bundle/focused-chunks/{chunk-7DEZT4JN.js → chunk-YQKLKRMW.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-3FM75SBJ.js → chunk-YW5GHQN2.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-ZA6AEYIM.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-ZH7GTEWL.js +3 -0
- package/dist/cli-bundle/focused-chunks/indexed-receipt-blobs-GAFC52LQ.js +4 -0
- package/dist/cli-bundle/focused-chunks/{mutation-admission-BQBQXILV.js → mutation-admission-ENBFMV2X.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{settlement-hook-XVS6P2XI.js → settlement-hook-PUYJJDFL.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{stage-VSIL4MTZ.js → stage-MAWZTM2H.js} +2 -2
- package/dist/cli-bundle/main.js +4 -4
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +2 -2
- package/dist/cli-bundle/sdk-core.js +33 -33
- 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 +57 -57
- package/dist/cli-bundle/sdk-query.js +1 -1
- package/dist/cli-bundle/sdk-runtime.js +1 -1
- package/dist/cli-bundle/sdk-testing.js +1 -1
- package/dist/cli-bundle/sdk.js +9 -9
- package/dist/core/extensions/extension-types.d.ts +3 -0
- package/dist/core/extensions/extension-types.js +1 -1
- package/dist/core/history/event-index.d.ts +6 -0
- package/dist/core/history/event-index.js +7 -6
- package/dist/core/history/read.d.ts +3 -1
- package/dist/core/history/read.js +1 -1
- package/dist/core/output/mutation-projection.js +1 -1
- package/dist/core/policy/workflow-policy-store.js +1 -1
- package/dist/core/shared/errors.d.ts +9 -0
- package/dist/core/shared/errors.js +1 -1
- package/dist/core/shared/secret-rules.d.ts +17 -0
- package/dist/core/shared/secret-rules.js +2 -1
- package/dist/core/store/item-mutation-precondition.d.ts +8 -0
- package/dist/core/store/item-mutation-precondition.js +1 -1
- package/dist/core/store/item-store.js +1 -1
- package/dist/mcp/tool-definitions.js +1 -1
- package/dist/sdk/cli-contracts/commander-mutation-options.js +1 -1
- package/dist/sdk/cli-contracts/completeness.js +1 -1
- package/dist/sdk/cli-contracts/flag-contracts.js +1 -1
- package/dist/sdk/cli-contracts/flag-lexicon-contracts.js +1 -1
- package/dist/sdk/cli-contracts/grammar-contracts.js +1 -1
- package/dist/sdk/cli-contracts/registration-helpers.d.ts +1 -1
- package/dist/sdk/cli-contracts/registration-helpers.js +1 -1
- package/dist/sdk/cli-contracts/tool-option-contracts.d.ts +4 -0
- package/dist/sdk/cli-contracts/tool-option-contracts.js +1 -1
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +1 -1
- package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
- package/dist/sdk/cli-contracts/tool-schema.js +1 -1
- package/dist/sdk/context-signal-store.js +2 -2
- package/dist/sdk/core.d.ts +1 -1
- package/dist/sdk/core.js +1 -1
- package/dist/sdk/extension-command-context.js +2 -2
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -1
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1 -1
- package/dist/sdk/index.d.ts +2 -1
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/item-transaction.js +1 -1
- package/dist/sdk/lifecycle/close-many.d.ts +3 -0
- package/dist/sdk/lifecycle/close-many.js +1 -1
- package/dist/sdk/lifecycle/close.d.ts +3 -1
- package/dist/sdk/lifecycle/close.js +1 -1
- package/dist/sdk/lifecycle/create.d.ts +2 -0
- package/dist/sdk/lifecycle/create.js +1 -1
- package/dist/sdk/lifecycle/plan.d.ts +2 -0
- package/dist/sdk/lifecycle/plan.js +1 -1
- package/dist/sdk/lifecycle/restore.js +1 -1
- package/dist/sdk/lifecycle-policy.d.ts +11 -0
- package/dist/sdk/lifecycle-policy.js +1 -1
- package/dist/sdk/merge/indexed-receipt-blobs.d.ts +3 -1
- package/dist/sdk/merge/indexed-receipt-blobs.js +2 -2
- package/dist/sdk/merge/receipt-file-boundary.d.ts +2 -0
- package/dist/sdk/merge/receipt-file-boundary.js +1 -1
- package/dist/sdk/merge/receipts.js +2 -2
- package/dist/sdk/mutation-events.d.ts +6 -0
- package/dist/sdk/mutation-events.js +1 -1
- package/dist/sdk/mutation-guard.d.ts +6 -1
- package/dist/sdk/mutation-guard.js +1 -1
- package/dist/sdk/output/fit-envelope.d.ts +98 -0
- package/dist/sdk/output/fit-envelope.js +2 -0
- package/dist/sdk/public-surface.json +1 -1
- package/dist/sdk/query.d.ts +1 -0
- package/dist/sdk/query.js +1 -1
- package/dist/sdk/read-output-session.d.ts +1 -1
- package/dist/sdk/read-output-session.js +1 -1
- package/dist/sdk/runtime-extended-actions.js +1 -1
- package/dist/sdk/runtime-input.js +1 -1
- package/dist/sdk/runtime-primitives.d.ts +1 -1
- package/dist/sdk/runtime-primitives.js +1 -1
- package/dist/sdk/runtime.d.ts +2 -2
- package/dist/sdk/runtime.js +1 -1
- package/dist/sdk/scheduling-shortcuts.d.ts +2 -0
- package/dist/sdk/scheduling-shortcuts.js +1 -1
- package/dist/sdk/test/execution.js +2 -2
- package/dist/sdk/test/receipt-index-snapshot.d.ts +2 -0
- package/dist/sdk/test/receipt-index-snapshot.js +2 -0
- package/dist/sdk/test/workspace-snapshot.d.ts +1 -1
- package/dist/sdk/test/workspace-snapshot.js +1 -1
- package/dist/sdk/workspace-transaction.d.ts +3 -1
- package/dist/sdk/workspace-transaction.js +2 -2
- package/docs/COMMANDS.md +2 -1
- package/docs/MUTATION_INTEGRITY.md +33 -0
- package/docs/PERFORMANCE.md +21 -6
- package/docs/QUALITY_EVIDENCE.md +9 -0
- package/docs/READ_OUTPUT_CONTRACTS.md +55 -1
- package/docs/RELEASING.md +11 -0
- package/docs/SDK.md +88 -6
- package/docs/SDK_CONTEXT_COORDINATION.md +35 -4
- package/docs/TESTING.md +14 -1
- package/docs/WORKFLOW_POLICIES.md +14 -1
- package/docs/agent-task-token-baseline.json +1 -1
- package/docs/agent-task-transcripts.json +1 -1
- package/docs/generated/FLAG_LEXICON_BUDGETS.md +4 -4
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +5 -3
- package/marketplace.json +1 -1
- package/package.json +1 -1
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/extensions/calendar/shortcuts.ts +2 -2
- 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/package.json +1 -1
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/pm-codex/package.json +1 -1
- package/plugins/pm-codex/plugin.json +1 -1
- package/runtime-dependencies.json +1 -1
- package/dist/cli-bundle/chunks/chunk-444VPGQT.js +0 -20
- package/dist/cli-bundle/chunks/chunk-5ZPT67NJ.js +0 -127
- package/dist/cli-bundle/chunks/chunk-7C3GCLOO.js +0 -30
- package/dist/cli-bundle/chunks/chunk-HMJSFWHK.js +0 -2
- package/dist/cli-bundle/chunks/chunk-RLXK2S7P.js +0 -2
- package/dist/cli-bundle/chunks/chunk-RQKDANP7.js +0 -113
- package/dist/cli-bundle/chunks/chunk-RUKSEZE7.js +0 -2
- package/dist/cli-bundle/chunks/chunk-XYLBOXSH.js +0 -6
- package/dist/cli-bundle/chunks/indexed-receipt-blobs-47I6KKDO.js +0 -4
- package/dist/cli-bundle/chunks/register-mutation-JZVVJUHY.js +0 -20
- package/dist/cli-bundle/focused-chunks/chunk-5LBZR6YW.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-IVLIUPVV.js +0 -12
- package/dist/cli-bundle/focused-chunks/chunk-KIIGME23.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-LWJ2X75I.js +0 -78
- package/dist/cli-bundle/focused-chunks/chunk-MT5TYMEU.js +0 -3
- package/dist/cli-bundle/focused-chunks/chunk-NI5SG2M2.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-QH5KMIYZ.js +0 -91
- package/dist/cli-bundle/focused-chunks/chunk-VVRXF4JO.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-W7MCCFYV.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-WMPDY5ZU.js +0 -21
- package/dist/cli-bundle/focused-chunks/indexed-receipt-blobs-YCLN2ZZG.js +0 -4
|
@@ -10,6 +10,7 @@ import type { RelationshipEdge,RelationshipGraph,RelationshipKindDefinition,Rela
|
|
|
10
10
|
import type { RelationshipImpactAnalysis } from "../../sdk/relationship-analytics.js";
|
|
11
11
|
import type { PmClient } from "../../sdk/runtime.js";
|
|
12
12
|
import type { GetItemAtResult } from "../../sdk/history-read.js";
|
|
13
|
+
import type { fitOutputEnvelope } from "../../sdk/output/fit-envelope.js";
|
|
13
14
|
import type { RelationshipEvent,RelationshipEventInput,RelationshipEventStore } from "../../sdk/relationship-history.js";
|
|
14
15
|
import type { CommitWorkspaceTransactionOptions,WorkspaceTransactionCommitResult } from "../../sdk/workspace-transaction.js";
|
|
15
16
|
import type { KnownItemFieldType } from "./item-field-types.js";
|
|
@@ -572,6 +573,8 @@ sdk?:ExtensionCommandSdk;
|
|
|
572
573
|
}
|
|
573
574
|
/** Runtime SDK services injected into every extension command invocation. */
|
|
574
575
|
export interface ExtensionCommandSdk {
|
|
576
|
+
/** Fit complete package envelopes without runtime package resolution. */
|
|
577
|
+
fitOutputEnvelope:typeof fitOutputEnvelope;
|
|
575
578
|
/** Tracker-bound client using the invocation author and workspace. */
|
|
576
579
|
client:PmClient;
|
|
577
580
|
/** Classify a host-native or structurally compatible missing-item error. */
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
1
|
+
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e3d7a4d0-05dc-548e-aeb8-410638ce734a")}catch{}})();const KNOWN_EXTENSION_CAPABILITIES=["commands","renderers","hooks","schema","importers","search","parser","preflight","services"],EXTENSION_CAPABILITY_CONTRACT_VERSION=2,EXTENSION_CAPABILITY_LEGACY_ALIASES=Object.freeze({migration:"schema",validation:"schema"}),EXTENSION_CAPABILITY_CONTRACT=Object.freeze({version:2,capabilities:[...KNOWN_EXTENSION_CAPABILITIES],legacy_aliases:{...EXTENSION_CAPABILITY_LEGACY_ALIASES}}),KNOWN_EXTENSION_POLICY_MODES=["off","warn","enforce"],KNOWN_EXTENSION_TRUST_MODES=["off","warn","enforce"],KNOWN_EXTENSION_SANDBOX_PROFILES=["none","restricted","strict"],KNOWN_PM_MAX_VERSION_EXCEEDED_MODES=["block","warn"],KNOWN_EXTENSION_POLICY_SURFACES=["commands.override","commands.handler","hooks.beforecommand","hooks.beforemutation","hooks.aftercommand","hooks.onwrite","hooks.onread","hooks.onindex","schema.flags","schema.itemfields","schema.itemtypes","schema.relationshipkinds","schema.migrations","schema.profiles","parser.override","preflight.override","services.override","renderers.override","importers.importer","importers.exporter","search.provider","search.vectorstore","services.assuranceprovider"],KNOWN_EXTENSION_SERVICE_NAMES=["output_format","error_format","help_format","lock_acquire","lock_release","history_append","item_store_write","item_store_delete","context_relevance","command_result"];function createDefaultExtensionGovernancePolicy(){return{mode:"off",trust_mode:"off",require_provenance:!1,trusted_extensions:[],default_sandbox_profile:"none",allowed_extensions:[],blocked_extensions:[],allowed_capabilities:[],blocked_capabilities:[],allowed_surfaces:[],blocked_surfaces:[],allowed_commands:[],blocked_commands:[],allowed_actions:[],blocked_actions:[],allowed_services:[],blocked_services:[],extension_overrides:[]}}export{EXTENSION_CAPABILITY_CONTRACT,EXTENSION_CAPABILITY_CONTRACT_VERSION,EXTENSION_CAPABILITY_LEGACY_ALIASES,KNOWN_EXTENSION_CAPABILITIES,KNOWN_EXTENSION_POLICY_MODES,KNOWN_EXTENSION_POLICY_SURFACES,KNOWN_EXTENSION_SANDBOX_PROFILES,KNOWN_EXTENSION_SERVICE_NAMES,KNOWN_EXTENSION_TRUST_MODES,KNOWN_PM_MAX_VERSION_EXCEEDED_MODES,createDefaultExtensionGovernancePolicy};
|
|
2
2
|
//# sourceMappingURL=extension-types.js.map
|
|
@@ -10,9 +10,13 @@ stream_id:string;
|
|
|
10
10
|
stream_offset:number;
|
|
11
11
|
/** Authoritative history entry. */
|
|
12
12
|
entry:HistoryEntry;
|
|
13
|
+
/** Exact recorded values retained by authoritative verification before timestamp normalization. */
|
|
14
|
+
recorded_entry?:HistoryEntry;
|
|
13
15
|
}
|
|
14
16
|
/** Index-native filters and continuation bounds for mutation events. */
|
|
15
17
|
export interface HistoryEventIndexQuery {
|
|
18
|
+
/** Verify every selected authoritative chain before filtering or pagination. */
|
|
19
|
+
verify?:boolean;
|
|
16
20
|
/** Return events strictly after this timestamp. */
|
|
17
21
|
after_ts?:string;
|
|
18
22
|
/** Stream tie-breaker paired with `after_ts`. */
|
|
@@ -47,6 +51,8 @@ export interface HistoryEventIndexQueryResult {
|
|
|
47
51
|
events:IndexedHistoryEvent[];
|
|
48
52
|
/** Whether another matching event exists. */
|
|
49
53
|
has_more:boolean;
|
|
54
|
+
/** Number of complete authoritative streams verified, when requested. */
|
|
55
|
+
verified_streams?:number;
|
|
50
56
|
}
|
|
51
57
|
/** Latest substantive immutable event for each requested history stream. */
|
|
52
58
|
export type LatestSubstantiveHistoryEvents = Readonly<Record<string,IndexedHistoryEvent>>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
1
|
+
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="c5f096f6-4af4-568e-89ef-1aa0a8718973")}catch{}})();import fs from"node:fs/promises";import path from"node:path";import{createHash,randomUUID}from"node:crypto";import{createRequire}from"node:module";import{setImmediate as yieldToEventLoop}from"node:timers/promises";import{isFileMissingError}from"../fs/fs-utils.js";import{acquireLock,captureProcessIdentity,isProcessIdentityAlive,hasUnexpiredLockOwner}from"../lock/lock.js";import{EXIT_CODE}from"../shared/constants.js";import{PmCliError}from"../shared/errors.js";import{isMillisecondPrecisionRfc3339DateTime}from"../shared/time.js";import{readHistorySnapshot}from"./read.js";import{verifyHistoryChainWithVersion}from"./replay.js";import{classifyHistoryEvent}from"./event-classification.js";import{loadDatabaseSync,loadStableDatabaseSync}from"../store/sqlite-runtime.js";const EVENT_INDEX_FILENAME="history-event-index.sqlite",EVENT_INDEX_VERSION="7",AUTHORITATIVE_HISTORY_CACHE_LIMIT=8,HISTORY_STREAM_CONTENT_READ_CONCURRENCY=32,HISTORY_EVENT_INDEX_LOCK_ID="history-event-index",HISTORY_EVENT_INDEX_APPEND_OWNER_PREFIX="history-event-index:append:",HISTORY_EVENT_INDEX_REPAIR_COOLDOWN_MS=5e3,HISTORY_EVENT_INDEX_LOCK_TTL_SECONDS=300,HISTORY_EVENT_INDEX_LOCK_WAIT_MS=3e4,HISTORY_EVENT_INDEX_INVALIDATION_LOCK_ID="history-event-index-invalidation",HISTORY_EVENT_INDEX_INVALIDATION_DIRECTORY="history-event-index-invalidations",HISTORY_EVENT_INDEX_INVALIDATION_MAX_UNVERIFIED_AGE_MS=HISTORY_EVENT_INDEX_LOCK_TTL_SECONDS*2*1e3,HISTORY_INDEX_CHANGED=Symbol("history-index-changed"),authoritativeHistoryCaches=new Map;function eventIndexPath(pmRoot){return path.join(pmRoot,"runtime",EVENT_INDEX_FILENAME)}function refusedHistoryRepairPath(pmRoot){return path.join(pmRoot,"runtime","history-event-index-repair-deferred")}async function hasRecentRefusedHistoryRepair(pmRoot){try{const deferredAt=Number(await fs.readFile(refusedHistoryRepairPath(pmRoot),"utf8")),age=Date.now()-deferredAt;return age>=0&&age<HISTORY_EVENT_INDEX_REPAIR_COOLDOWN_MS}catch{return!1}}async function deferAutomaticHistoryRepair(pmRoot){try{await fs.writeFile(refusedHistoryRepairPath(pmRoot),`${Date.now()}
|
|
2
2
|
`)}catch{}}function historyEventIndexInvalidationDirectory(pmRoot){return path.join(pmRoot,"runtime",HISTORY_EVENT_INDEX_INVALIDATION_DIRECTORY)}async function listHistoryEventIndexInvalidations(pmRoot){const entries=await fs.readdir(historyEventIndexInvalidationDirectory(pmRoot)).catch(error=>{if(isFileMissingError(error))return[];throw error});return{pending:entries.filter(name=>name.endsWith(".pending")).sort(),committed:entries.filter(name=>name.endsWith(".committed")).sort()}}async function recoverAbandonedHistoryEventIndexInvalidations(pmRoot){const invalidations=await listHistoryEventIndexInvalidations(pmRoot);return{pending:(await Promise.all(invalidations.pending.map(async name=>{const owner=/^(\d+)-(unknown|\d+)-(\d+)-.+\.pending$/u.exec(name);if(owner!==null){const processStartIdentity=owner[2];if(await isProcessIdentityAlive({pid:Number(owner[1]),...processStartIdentity==="unknown"?{}:{process_start_identity:processStartIdentity}},Math.max(0,Date.now()-Number(owner[3])),HISTORY_EVENT_INDEX_INVALIDATION_MAX_UNVERIFIED_AGE_MS))return name}return await fs.rm(path.join(historyEventIndexInvalidationDirectory(pmRoot),name),{force:!0}),null}))).filter(name=>name!==null),committed:invalidations.committed}}async function withHistoryEventIndexInvalidationLock(pmRoot,operation,waitMs=HISTORY_EVENT_INDEX_LOCK_WAIT_MS){const release=await acquireLock(pmRoot,HISTORY_EVENT_INDEX_INVALIDATION_LOCK_ID,HISTORY_EVENT_INDEX_LOCK_TTL_SECONDS,`history-event-index-invalidation:${process.pid}`,!1,!1,waitMs,waitMs===0?{maxWaitMs:0}:void 0);try{return await operation()}finally{await release()}}async function beginHistoryEventIndexInvalidation(pmRoot){const directory=historyEventIndexInvalidationDirectory(pmRoot);await fs.mkdir(directory,{recursive:!0});const token=randomUUID(),processIdentity=await captureProcessIdentity(),pendingPath=path.join(directory,`${processIdentity.pid}-${processIdentity.process_start_identity??"unknown"}-${Date.now()}-${token}.pending`);return await fs.writeFile(pendingPath,`pending
|
|
3
3
|
`,{flag:"wx"}),{pendingPath,committedPath:path.join(directory,`${token}.committed`)}}function sameInvalidationNames(left,right){return left.length===right.length&&left.every((name,index)=>name===right[index])}async function invalidateHistoryEventIndex(pmRoot){try{await fs.rm(eventIndexPath(pmRoot),{force:!0})}catch{}}async function recordHistoryEventIndexInvalidation(pmRoot,retainCache){const directory=historyEventIndexInvalidationDirectory(pmRoot);await fs.mkdir(directory,{recursive:!0}),await fs.writeFile(path.join(directory,`${randomUUID()}${retainCache?".contended":""}.committed`),`committed
|
|
4
|
-
`,{flag:"wx"}),retainCache||await invalidateHistoryEventIndex(pmRoot)}function hasContendedHistoryEventIndexInvalidation(invalidations){return invalidations.committed.some(name=>name.endsWith(".contended.committed"))}function hasNativeHistoryEventIndexContention(pmRoot,Database){let database;try{return database=new Database(eventIndexPath(pmRoot)),database.exec("PRAGMA busy_timeout = 0"),database.exec("BEGIN EXCLUSIVE"),!1}catch(error){return isHistoryIndexSqliteContention(error)}finally{database?.close()}}async function historyEventIndexAppendIsActive(pmRoot,invalidations){return invalidations.pending.length>0||await hasUnexpiredLockOwner(pmRoot,HISTORY_EVENT_INDEX_LOCK_ID,HISTORY_EVENT_INDEX_APPEND_OWNER_PREFIX)}async function withHistoryEventIndexLock(pmRoot,operation,waitMs=HISTORY_EVENT_INDEX_LOCK_WAIT_MS,purpose="rebuild"){const release=await acquireLock(pmRoot,HISTORY_EVENT_INDEX_LOCK_ID,HISTORY_EVENT_INDEX_LOCK_TTL_SECONDS,`history-event-index:${purpose}:${process.pid}`,!1,!1,waitMs,waitMs===0?{maxWaitMs:0}:void 0);try{return await operation()}finally{await release()}}async function readAuthoritativeHistorySnapshot(pmRoot){const historyRoot=path.join(pmRoot,"history"),entries=await fs.readdir(historyRoot,{withFileTypes:!0}).catch(error=>{if(typeof error=="object"&&error!==null&&"code"in error&&error.code==="ENOENT")return[];throw error}),
|
|
4
|
+
`,{flag:"wx"}),retainCache||await invalidateHistoryEventIndex(pmRoot)}function hasContendedHistoryEventIndexInvalidation(invalidations){return invalidations.committed.some(name=>name.endsWith(".contended.committed"))}function hasNativeHistoryEventIndexContention(pmRoot,Database){let database;try{return database=new Database(eventIndexPath(pmRoot)),database.exec("PRAGMA busy_timeout = 0"),database.exec("BEGIN EXCLUSIVE"),!1}catch(error){return isHistoryIndexSqliteContention(error)}finally{database?.close()}}async function historyEventIndexAppendIsActive(pmRoot,invalidations){return invalidations.pending.length>0||await hasUnexpiredLockOwner(pmRoot,HISTORY_EVENT_INDEX_LOCK_ID,HISTORY_EVENT_INDEX_APPEND_OWNER_PREFIX)}async function withHistoryEventIndexLock(pmRoot,operation,waitMs=HISTORY_EVENT_INDEX_LOCK_WAIT_MS,purpose="rebuild"){const release=await acquireLock(pmRoot,HISTORY_EVENT_INDEX_LOCK_ID,HISTORY_EVENT_INDEX_LOCK_TTL_SECONDS,`history-event-index:${purpose}:${process.pid}`,!1,!1,waitMs,waitMs===0?{maxWaitMs:0}:void 0);try{return await operation()}finally{await release()}}async function readAuthoritativeHistorySnapshot(pmRoot,requestedStreamIds,verify=!1){const historyRoot=path.join(pmRoot,"history"),entries=await fs.readdir(historyRoot,{withFileTypes:!0}).catch(error=>{if(typeof error=="object"&&error!==null&&"code"in error&&error.code==="ENOENT")return[];throw error}),base=authoritativeHistoryCaches.get(pmRoot)??{streams:new Map,ordered_events:[]},streams=new Map(base.streams);let orderedEvents=base.ordered_events;const presentStreams=new Set;let changed=!authoritativeHistoryCaches.has(pmRoot);for(const historyFile of entries.filter(entry=>entry.isFile()&&entry.name.endsWith(".jsonl")).sort((left,right)=>left.name.localeCompare(right.name))){const streamId=historyFile.name.slice(0,-6);presentStreams.add(streamId);const historyPath=path.join(historyRoot,historyFile.name);if(!historyStreamIsSelected(streamId,requestedStreamIds))continue;const stream=await readChangedHistoryStream(historyPath,streamId,streams.get(streamId),verify);stream!==void 0&&(streams.set(streamId,stream),changed=!0)}for(const streamId of streams.keys())presentStreams.has(streamId)||(streams.delete(streamId),changed=!0);changed&&(orderedEvents=[...streams.values()].flatMap(stream=>stream.events).sort(compareHistoryEventPosition));const cached={streams,ordered_events:orderedEvents};if(authoritativeHistoryCaches.delete(pmRoot),authoritativeHistoryCaches.set(pmRoot,cached),authoritativeHistoryCaches.size>AUTHORITATIVE_HISTORY_CACHE_LIMIT){const oldestRoot=authoritativeHistoryCaches.keys().next().value;authoritativeHistoryCaches.delete(oldestRoot)}return{events:cached.ordered_events.filter(event=>historyStreamIsSelected(event.stream_id,requestedStreamIds)).map(event=>projectRecordedHistoryEvent(event,verify)),stream_receipts:new Map([...cached.streams].filter(([streamId])=>historyStreamIsSelected(streamId,requestedStreamIds)).map(([streamId,stream])=>[streamId,{byte_size:stream.byte_size,content_hash:stream.signature}]))}}function projectRecordedHistoryEvent(event,verify){return verify||event.recorded_entry===void 0?event:{stream_id:event.stream_id,stream_offset:event.stream_offset,entry:event.entry}}function historyStreamIsSelected(streamId,requested){return!requested?.length||requested.includes(streamId)}async function readChangedHistoryStream(historyPath,streamId,previous,verify){if(!verify&&previous!==void 0&&previous.byte_size===(await fs.stat(historyPath)).size){const signature=createHash("sha256").update(await fs.readFile(historyPath)).digest("hex");if(previous.signature===signature)return}return readIndexableHistoryStream(historyPath,streamId,verify)}async function readIndexableHistoryStream(historyPath,streamId,verify){const snapshot=await readHistorySnapshot(historyPath,streamId);if(verify){const result=verifyHistoryChainWithVersion(snapshot.entries);if(!result.ok)throw new PmCliError(`Authoritative history integrity verification failed for ${streamId}.`,EXIT_CODE.GENERIC_FAILURE,{code:"event_history_integrity_failed",why:result.errors.join(", "),nextSteps:[`Run pm history ${streamId} --verify to inspect the authoritative chain before retrying.`]})}const raw=snapshot.raw_bytes??new Uint8Array;return{signature:createHash("sha256").update(raw).digest("hex"),byte_size:raw.byteLength,events:normalizeIndexableHistoryEntries(snapshot.entries,streamId).map((entry,streamOffset)=>({stream_id:streamId,stream_offset:streamOffset,entry,...verify?{recorded_entry:snapshot.entries[streamOffset]}:{}}))}}async function readIndexableHistoryEntries(historyPath,streamId){return normalizeIndexableHistoryEntries((await readHistorySnapshot(historyPath,streamId)).entries,streamId)}function normalizeIndexableHistoryEntries(entries,streamId){for(const[index,entry]of entries.entries())if(typeof entry.ts!="string"||!isMillisecondPrecisionRfc3339DateTime(entry.ts.trim()))throw new PmCliError(`History for ${streamId} contains an invalid or sub-millisecond timestamp at line ${index+1}. Repair or restore the history stream and retry.`,EXIT_CODE.GENERIC_FAILURE,{code:"history_timestamp_invalid"});return entries.map(entry=>({...entry,ts:new Date(entry.ts.trim()).toISOString()}))}let RuntimeDatabaseSync;function resolveDatabaseSync(){return RuntimeDatabaseSync!==void 0||(RuntimeDatabaseSync=loadStableDatabaseSync(process.versions.node,createRequire(import.meta.url))),RuntimeDatabaseSync}function createSchema(database){database.exec(`
|
|
5
5
|
PRAGMA journal_mode = OFF;
|
|
6
6
|
PRAGMA synchronous = OFF;
|
|
7
7
|
CREATE TABLE metadata (
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
) STRICT;
|
|
23
23
|
CREATE TABLE streams (
|
|
24
24
|
stream_id TEXT PRIMARY KEY,
|
|
25
|
-
byte_size INTEGER NOT NULL CHECK(byte_size >= 0)
|
|
25
|
+
byte_size INTEGER NOT NULL CHECK(byte_size >= 0),
|
|
26
|
+
content_hash TEXT NOT NULL
|
|
26
27
|
) STRICT;
|
|
27
28
|
CREATE INDEX events_order
|
|
28
29
|
ON events(ts, stream_id, stream_offset);
|
|
@@ -39,11 +40,11 @@
|
|
|
39
40
|
`),database.prepare("INSERT INTO metadata(key, value) VALUES ('version', ?)").run(EVENT_INDEX_VERSION)}function insertEvent(database,event){const entry={...event.entry,ts:new Date(event.entry.ts.trim()).toISOString()};database.prepare(`INSERT INTO events(
|
|
40
41
|
stream_id, stream_offset, ts, author, agent_harness,
|
|
41
42
|
agent_instance, op, event_class, entry_json
|
|
42
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.stream_id,event.stream_offset,entry.ts,entry.author,entry.agent_harness??null,entry.agent_instance??null,entry.op,classifyHistoryEvent(entry),JSON.stringify(entry))}function parseIndexedHistoryEvent(row){const entry=JSON.parse(String(row.entry_json));return typeof entry.ts!="string"||!isMillisecondPrecisionRfc3339DateTime(entry.ts.trim())?null:{stream_id:String(row.stream_id),stream_offset:Number(row.stream_offset),entry:{...entry,ts:new Date(entry.ts.trim()).toISOString()}}}function
|
|
43
|
-
ON CONFLICT(stream_id) DO UPDATE SET byte_size = excluded.byte_size`).run(streamId,byteSize)}async function rebuildHistoryEventIndex(pmRoot,options={}){const Database=resolveDatabaseSync();return Database?withHistoryEventIndexLock(pmRoot,async()=>{let invalidations;try{invalidations=await withHistoryEventIndexInvalidationLock(pmRoot,()=>recoverAbandonedHistoryEventIndexInvalidations(pmRoot),options.lockWaitMs)}catch(error){if(error instanceof PmCliError&&error.code==="lock_conflict")return!1;throw error}if(invalidations.pending.length>0||hasContendedHistoryEventIndexInvalidation(invalidations)&&hasNativeHistoryEventIndexContention(pmRoot,Database))return!1;const targetPath=eventIndexPath(pmRoot);await fs.mkdir(path.dirname(targetPath),{recursive:!0});const temporaryPath=`${targetPath}.${randomUUID()}.tmp`;let database;try{database=new Database(temporaryPath),createSchema(database),database.exec("BEGIN IMMEDIATE");const authoritative=await readAuthoritativeHistorySnapshot(pmRoot);await populateHistoryEventIndex(database,authoritative),database.exec("COMMIT"),database.close(),database=void 0;const published=await withHistoryEventIndexInvalidationLock(pmRoot,async()=>{const current=await recoverAbandonedHistoryEventIndexInvalidations(pmRoot);return current.pending.length>0||!sameInvalidationNames(invalidations.committed,current.committed)||hasContendedHistoryEventIndexInvalidation(current)&&hasNativeHistoryEventIndexContention(pmRoot,Database)?!1:(await fs.rm(targetPath,{force:!0}),await fs.rename(temporaryPath,targetPath),!0)},options.lockWaitMs);return published&&await Promise.all(invalidations.committed.map(name=>fs.rm(path.join(historyEventIndexInvalidationDirectory(pmRoot),name),{force:!0}))),published||(await deferAutomaticHistoryRepair(pmRoot),await fs.rm(temporaryPath,{force:!0})),published}catch(error){throw await deferAutomaticHistoryRepair(pmRoot),database?.close(),await fs.rm(temporaryPath,{force:!0}),error}},options.lockWaitMs):!1}async function populateHistoryEventIndex(database,snapshot){let inserted=0;for(const event of snapshot.events)insertEvent(database,event),++inserted%500===0&&await yieldToEventLoop();for(const[streamId,byteSize]of snapshot.stream_byte_sizes)upsertStreamByteSize(database,streamId,byteSize),++inserted%500===0&&await yieldToEventLoop()}function matchesSet(value,accepted){return!accepted||accepted.length===0||accepted.includes(value)}function compareHistoryEventPosition(left,right){return left.entry.ts.localeCompare(right.entry.ts)||left.stream_id.localeCompare(right.stream_id)||left.stream_offset-right.stream_offset}function canonicalHistoryQueryTimestamp(value){const milliseconds=Date.parse(value);return Number.isFinite(milliseconds)?new Date(milliseconds).toISOString():value}function isInsideHistoryEventWindow(event,query){if(query.after_ts!==void 0&&query.after_stream_id!==void 0&&query.after_stream_offset!==void 0){if(compareHistoryEventPosition(event,{stream_id:query.after_stream_id,stream_offset:query.after_stream_offset,entry:{...event.entry,ts:canonicalHistoryQueryTimestamp(query.after_ts)}})<=0)return!1}else if(query.since_ts!==void 0&&event.entry.ts<canonicalHistoryQueryTimestamp(query.since_ts))return!1;return!0}function matchesHistoryEventHarness(event,query){return!query.harnesses||query.harnesses.length===0||query.harnesses.includes(event.entry.agent_harness??"")?!0:event.entry.agent_harness===void 0&&(query.harness_alias_authors??[]).includes(event.entry.author)}function matchesHistoryEventQuery(event,query){return!isInsideHistoryEventWindow(event,query)||!matchesSet(event.entry.op,query.ops)||!matchesSet(event.entry.author,query.authors)||!matchesSet(event.entry.agent_instance??"",query.agent_instances)||!matchesSet(event.stream_id,query.stream_ids)||!matchesHistoryEventHarness(event,query)?!1:(query.provenance??[]).every(predicate=>{const value=event.entry.agent_provenance?.[predicate.dimension]?.value;return value!==void 0&&predicate.values.includes(value)})}async function queryHistoryEventStreams(pmRoot,query){const limit=Math.max(0,Math.floor(query.limit)),rows=(await readAuthoritativeHistoryEvents(pmRoot)).filter(event=>matchesHistoryEventQuery(event,query)).slice(0,limit+1);return{events:rows.slice(0,query.limit),has_more:rows.length>query.limit}}async function historyEventIndexHasAppendBase(database,historyPath){const stored=database.prepare("SELECT byte_size FROM streams WHERE stream_id = ?").get(path.basename(historyPath,".jsonl"));let actualSize=0;try{actualSize=(await fs.stat(historyPath)).size}catch(error){if(!isFileMissingError(error))throw error}return Number(stored?.byte_size??0)===actualSize}async function appendHistoryEntryWithEventIndex(historyPath,entry,append){const pmRoot=path.dirname(path.dirname(historyPath)),Database=resolveDatabaseSync();let authoritativeAppendCommitted=!1;const appendAuthoritative=async()=>{await append(),authoritativeAppendCommitted=!0};if(!Database){await appendUnindexedHistory(pmRoot,appendAuthoritative);return}try{await withHistoryEventIndexLock(pmRoot,async()=>{const targetPath=eventIndexPath(pmRoot);try{await fs.access(targetPath)}catch{await appendAuthoritative();return}let database;try{if(database=new Database(targetPath),database.prepare("SELECT value FROM metadata WHERE key = 'version'").get()?.value!==EVENT_INDEX_VERSION)throw new TypeError("Unsupported history event index version");if(!await historyEventIndexHasAppendBase(database,historyPath))throw new TypeError("Stale history event index append base")}catch(error){if(database?.close(),isHistoryIndexSqliteContention(error))throw error;await recordHistoryEventIndexInvalidation(pmRoot,!0),await appendAuthoritative();return}try{database.exec("PRAGMA busy_timeout = 0"),database.exec("BEGIN IMMEDIATE")}catch(error){if(database.close(),isHistoryIndexSqliteContention(error)||!(error instanceof Error)||error.code!=="ERR_SQLITE_ERROR")throw error;await recordHistoryEventIndexInvalidation(pmRoot,!1),await appendAuthoritative();return}let appended=!1;try{await appendAuthoritative(),appended=!0;const streamId=path.basename(historyPath,".jsonl"),offset=database.prepare("SELECT COALESCE(MAX(stream_offset), -1) + 1 AS value FROM events WHERE stream_id = ?").get(streamId);insertEvent(database,{stream_id:streamId,stream_offset:Number(offset.value),entry}),upsertStreamByteSize(database,streamId,(await fs.stat(historyPath)).size),database.exec("COMMIT"),database.close()}catch(error){if(database?.close(),!appended)throw error;await recordHistoryEventIndexInvalidation(pmRoot,isHistoryIndexSqliteContention(error))}},0,"append")}catch(error){if(authoritativeAppendCommitted){await invalidateHistoryEventIndex(pmRoot);return}if(!isHistoryIndexWriteContention(error))throw error;await appendUnindexedHistory(pmRoot,appendAuthoritative,isHistoryIndexSqliteContention(error))}}async function appendUnindexedHistory(pmRoot,append,nativeContention=!1){let authoritativeAppendCommitted=!1,pendingMarkerPath;try{await withHistoryEventIndexInvalidationLock(pmRoot,async()=>{const contended=nativeContention||hasContendedHistoryEventIndexInvalidation(await listHistoryEventIndexInvalidations(pmRoot)),marker=await beginHistoryEventIndexInvalidation(pmRoot);pendingMarkerPath=marker.pendingPath;try{await append(),authoritativeAppendCommitted=!0}catch(appendError){throw await fs.rm(marker.pendingPath,{force:!0}),appendError}await fs.rename(marker.pendingPath,contended?marker.committedPath.replace(/\.committed$/u,".contended.committed"):marker.committedPath),contended||await invalidateHistoryEventIndex(pmRoot)})}catch(fallbackError){if(authoritativeAppendCommitted){await invalidateHistoryEventIndex(pmRoot);try{await fs.rm(pendingMarkerPath,{force:!0})}catch{}return}throw fallbackError}}async function removeHistoryEventIndexForHistoryPath(historyPath){await fs.rm(eventIndexPath(path.dirname(path.dirname(historyPath))),{force:!0})}function appendSetPredicate(clauses,parameters,column,values){!values||values.length===0||(clauses.push(`${column} IN (${values.map(()=>"?").join(", ")})`),parameters.push(...values))}function appendHarnessPredicate(clauses,parameters,harnesses,aliasAuthors){if(!harnesses||harnesses.length===0)return;const harnessPlaceholders=harnesses.map(()=>"?").join(", "),aliases=aliasAuthors??[];if(aliases.length>0){clauses.push(`(agent_harness IN (${harnessPlaceholders}) OR (agent_harness IS NULL AND author IN (${aliases.map(()=>"?").join(", ")})))`),parameters.push(...harnesses,...aliases);return}clauses.push(`agent_harness IN (${harnessPlaceholders})`),parameters.push(...harnesses)}async function readHistoryEventIndexSnapshot(pmRoot,Database,requestedStreamIds,read){let database;try{const invalidations=await listHistoryEventIndexInvalidations(pmRoot);if(await historyEventIndexAppendIsActive(pmRoot,invalidations))return HISTORY_INDEX_CHANGED;const before=await historyEventIndexSignature(pmRoot);if(invalidations.committed.length>0)return hasContendedHistoryEventIndexInvalidation(invalidations)&&hasNativeHistoryEventIndexContention(pmRoot,Database)?HISTORY_INDEX_CHANGED:null;if(database=new Database(eventIndexPath(pmRoot),{readOnly:!0}),database.exec("BEGIN"),database.prepare("SELECT value FROM metadata WHERE key = 'version'").get()?.value!==EVENT_INDEX_VERSION)return null;const result=read(database);if(result===null)return null;const storedSizes=readHistoryEventIndexStreamSizes(database,requestedStreamIds),completed=database;database=void 0,completed.close();const streamIds=await historyIndexValidationStreamIds([...storedSizes.keys()],pmRoot,requestedStreamIds),sizesMatch=await historyIndexMatchesStreamSizes(storedSizes,pmRoot,streamIds),current=await listHistoryEventIndexInvalidations(pmRoot);return current.committed.length>0||await historyEventIndexAppendIsActive(pmRoot,current)||before!==await historyEventIndexSignature(pmRoot)?HISTORY_INDEX_CHANGED:sizesMatch?result:null}catch(error){return isHistoryIndexSqliteContention(error)?HISTORY_INDEX_CHANGED:null}finally{database?.close()}}function isHistoryIndexSqliteContention(error){return typeof error=="object"&&error!==null&&"errcode"in error&&[5,6].includes(error.errcode)}function isHistoryIndexWriteContention(error){return isHistoryIndexSqliteContention(error)||error instanceof PmCliError&&error.code==="lock_conflict"}function readHistoryEventIndexStreamSizes(database,requestedStreamIds){const storedSizes=new Map,ids=requestedStreamIds&&requestedStreamIds.length>0?[...new Set(requestedStreamIds)]:void 0;if(ids===void 0)for(const row of database.prepare("SELECT stream_id, byte_size FROM streams").all())storedSizes.set(String(row.stream_id),Number(row.byte_size));else for(let start=0;start<ids.length;start+=500){const chunk=ids.slice(start,start+500);for(const row of database.prepare(`SELECT stream_id, byte_size FROM streams WHERE stream_id IN (${chunk.map(()=>"?").join(", ")})`).all(...chunk))storedSizes.set(String(row.stream_id),Number(row.byte_size))}return storedSizes}async function historyEventIndexSignature(pmRoot){const stats=await fs.stat(eventIndexPath(pmRoot),{bigint:!0});return[stats.dev,stats.ino,stats.size,stats.mtimeNs,stats.ctimeNs].join(":")}async function readHistoryEventIndexPage(pmRoot,query){const Database=resolveDatabaseSync();if(!Database)return{result:null,repair_needed:!1};const clauses=[],parameters=[];if(query.after_ts!==void 0&&query.after_stream_id!==void 0&&query.after_stream_offset!==void 0){clauses.push("(ts > ? OR (ts = ? AND stream_id > ?) OR (ts = ? AND stream_id = ? AND stream_offset > ?))");const afterTimestamp=canonicalHistoryQueryTimestamp(query.after_ts);parameters.push(afterTimestamp,afterTimestamp,query.after_stream_id,afterTimestamp,query.after_stream_id,query.after_stream_offset)}else query.since_ts!==void 0&&(clauses.push("ts >= ?"),parameters.push(canonicalHistoryQueryTimestamp(query.since_ts)));appendSetPredicate(clauses,parameters,"op",query.ops),appendSetPredicate(clauses,parameters,"author",query.authors),appendHarnessPredicate(clauses,parameters,query.harnesses,query.harness_alias_authors),appendSetPredicate(clauses,parameters,"agent_instance",query.agent_instances);for(const predicate of query.provenance??[])clauses.push(`json_extract(entry_json, '$.agent_provenance.${predicate.dimension}.value') IN (${predicate.values.map(()=>"?").join(", ")})`),parameters.push(...predicate.values);appendSetPredicate(clauses,parameters,"stream_id",query.stream_ids);const where=clauses.length===0?"":` WHERE ${clauses.join(" AND ")}`,attempt=await readHistoryEventIndexSnapshot(pmRoot,Database,query.stream_ids,database=>{const rows=database.prepare(`SELECT stream_id, stream_offset, entry_json
|
|
43
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.stream_id,event.stream_offset,entry.ts,entry.author,entry.agent_harness??null,entry.agent_instance??null,entry.op,classifyHistoryEvent(entry),JSON.stringify(entry))}function parseIndexedHistoryEvent(row){const entry=JSON.parse(String(row.entry_json));return typeof entry.ts!="string"||!isMillisecondPrecisionRfc3339DateTime(entry.ts.trim())?null:{stream_id:String(row.stream_id),stream_offset:Number(row.stream_offset),entry:{...entry,ts:new Date(entry.ts.trim()).toISOString()}}}function upsertStreamContentReceipt(database,streamId,receipt){database.prepare(`INSERT INTO streams(stream_id, byte_size, content_hash) VALUES (?, ?, ?)
|
|
44
|
+
ON CONFLICT(stream_id) DO UPDATE SET byte_size = excluded.byte_size, content_hash = excluded.content_hash`).run(streamId,receipt.byte_size,receipt.content_hash)}async function rebuildHistoryEventIndex(pmRoot,options={}){const Database=resolveDatabaseSync();return Database?withHistoryEventIndexLock(pmRoot,async()=>{let invalidations;try{invalidations=await withHistoryEventIndexInvalidationLock(pmRoot,()=>recoverAbandonedHistoryEventIndexInvalidations(pmRoot),options.lockWaitMs)}catch(error){if(error instanceof PmCliError&&error.code==="lock_conflict")return!1;throw error}if(invalidations.pending.length>0||hasContendedHistoryEventIndexInvalidation(invalidations)&&hasNativeHistoryEventIndexContention(pmRoot,Database))return!1;const targetPath=eventIndexPath(pmRoot);await fs.mkdir(path.dirname(targetPath),{recursive:!0});const temporaryPath=`${targetPath}.${randomUUID()}.tmp`;let database;try{database=new Database(temporaryPath),createSchema(database),database.exec("BEGIN IMMEDIATE");const authoritative=await readAuthoritativeHistorySnapshot(pmRoot);await populateHistoryEventIndex(database,authoritative),database.exec("COMMIT"),database.close(),database=void 0;const published=await withHistoryEventIndexInvalidationLock(pmRoot,async()=>{const current=await recoverAbandonedHistoryEventIndexInvalidations(pmRoot);return current.pending.length>0||!sameInvalidationNames(invalidations.committed,current.committed)||hasContendedHistoryEventIndexInvalidation(current)&&hasNativeHistoryEventIndexContention(pmRoot,Database)?!1:(await fs.rm(targetPath,{force:!0}),await fs.rename(temporaryPath,targetPath),!0)},options.lockWaitMs);return published&&await Promise.all(invalidations.committed.map(name=>fs.rm(path.join(historyEventIndexInvalidationDirectory(pmRoot),name),{force:!0}))),published||(await deferAutomaticHistoryRepair(pmRoot),await fs.rm(temporaryPath,{force:!0})),published}catch(error){throw await deferAutomaticHistoryRepair(pmRoot),database?.close(),await fs.rm(temporaryPath,{force:!0}),error}},options.lockWaitMs):!1}async function populateHistoryEventIndex(database,snapshot){let inserted=0;for(const event of snapshot.events)insertEvent(database,event),++inserted%500===0&&await yieldToEventLoop();for(const[streamId,receipt]of snapshot.stream_receipts)upsertStreamContentReceipt(database,streamId,receipt),++inserted%500===0&&await yieldToEventLoop()}function matchesSet(value,accepted){return!accepted||accepted.length===0||accepted.includes(value)}function compareHistoryEventPosition(left,right){return left.entry.ts.localeCompare(right.entry.ts)||left.stream_id.localeCompare(right.stream_id)||left.stream_offset-right.stream_offset}function canonicalHistoryQueryTimestamp(value){const milliseconds=Date.parse(value);return Number.isFinite(milliseconds)?new Date(milliseconds).toISOString():value}function isInsideHistoryEventWindow(event,query){if(query.after_ts!==void 0&&query.after_stream_id!==void 0&&query.after_stream_offset!==void 0){if(compareHistoryEventPosition(event,{stream_id:query.after_stream_id,stream_offset:query.after_stream_offset,entry:{...event.entry,ts:canonicalHistoryQueryTimestamp(query.after_ts)}})<=0)return!1}else if(query.since_ts!==void 0&&event.entry.ts<canonicalHistoryQueryTimestamp(query.since_ts))return!1;return!0}function matchesHistoryEventHarness(event,query){return!query.harnesses||query.harnesses.length===0||query.harnesses.includes(event.entry.agent_harness??"")?!0:event.entry.agent_harness===void 0&&(query.harness_alias_authors??[]).includes(event.entry.author)}function matchesHistoryEventQuery(event,query){return!isInsideHistoryEventWindow(event,query)||!matchesSet(event.entry.op,query.ops)||!matchesSet(event.entry.author,query.authors)||!matchesSet(event.entry.agent_instance??"",query.agent_instances)||!matchesSet(event.stream_id,query.stream_ids)||!matchesHistoryEventHarness(event,query)?!1:(query.provenance??[]).every(predicate=>{const value=event.entry.agent_provenance?.[predicate.dimension]?.value;return value!==void 0&&predicate.values.includes(value)})}async function queryHistoryEventStreams(pmRoot,query){const limit=Math.max(0,Math.floor(query.limit)),snapshot=await readAuthoritativeHistorySnapshot(pmRoot,query.stream_ids,query.verify),rows=snapshot.events.filter(event=>matchesHistoryEventQuery(event,query)).slice(0,limit+1);return{events:rows.slice(0,query.limit),has_more:rows.length>query.limit,...query.verify?{verified_streams:snapshot.stream_receipts.size}:{}}}async function historyEventIndexHasAppendBase(database,historyPath){const stored=database.prepare("SELECT byte_size, content_hash FROM streams WHERE stream_id = ?").get(path.basename(historyPath,".jsonl"));let actualSize=0;try{actualSize=(await fs.stat(historyPath)).size}catch(error){if(!isFileMissingError(error))throw error}if(Number(stored?.byte_size??0)!==actualSize)return!1;const current=await readHistoryStreamContentReceipt(path.dirname(path.dirname(historyPath)),path.basename(historyPath,".jsonl"));return stored===void 0?current?.byte_size===0:current?.content_hash===stored.content_hash}async function appendHistoryEntryWithEventIndex(historyPath,entry,append){const pmRoot=path.dirname(path.dirname(historyPath)),Database=resolveDatabaseSync();let authoritativeAppendCommitted=!1;const appendAuthoritative=async()=>{await append(),authoritativeAppendCommitted=!0};if(!Database){await appendUnindexedHistory(pmRoot,appendAuthoritative);return}try{await withHistoryEventIndexLock(pmRoot,async()=>{const targetPath=eventIndexPath(pmRoot);try{await fs.access(targetPath)}catch{await appendAuthoritative();return}let database;try{if(database=new Database(targetPath),database.prepare("SELECT value FROM metadata WHERE key = 'version'").get()?.value!==EVENT_INDEX_VERSION)throw new TypeError("Unsupported history event index version");if(!await historyEventIndexHasAppendBase(database,historyPath))throw new TypeError("Stale history event index append base")}catch(error){if(database?.close(),isHistoryIndexSqliteContention(error))throw error;await recordHistoryEventIndexInvalidation(pmRoot,!0),await appendAuthoritative();return}try{database.exec("PRAGMA busy_timeout = 0"),database.exec("BEGIN IMMEDIATE")}catch(error){if(database.close(),isHistoryIndexSqliteContention(error)||!(error instanceof Error)||error.code!=="ERR_SQLITE_ERROR")throw error;await recordHistoryEventIndexInvalidation(pmRoot,!1),await appendAuthoritative();return}let appended=!1;try{await appendAuthoritative(),appended=!0;const streamId=path.basename(historyPath,".jsonl"),offset=database.prepare("SELECT COALESCE(MAX(stream_offset), -1) + 1 AS value FROM events WHERE stream_id = ?").get(streamId);insertEvent(database,{stream_id:streamId,stream_offset:Number(offset.value),entry}),upsertStreamContentReceipt(database,streamId,await readHistoryStreamContentReceipt(pmRoot,streamId)),database.exec("COMMIT"),database.close()}catch(error){if(database?.close(),!appended)throw error;await recordHistoryEventIndexInvalidation(pmRoot,isHistoryIndexSqliteContention(error))}},0,"append")}catch(error){if(authoritativeAppendCommitted){await invalidateHistoryEventIndex(pmRoot);return}if(!isHistoryIndexWriteContention(error))throw error;await appendUnindexedHistory(pmRoot,appendAuthoritative,isHistoryIndexSqliteContention(error))}}async function appendUnindexedHistory(pmRoot,append,nativeContention=!1){let authoritativeAppendCommitted=!1,pendingMarkerPath;try{await withHistoryEventIndexInvalidationLock(pmRoot,async()=>{const contended=nativeContention||hasContendedHistoryEventIndexInvalidation(await listHistoryEventIndexInvalidations(pmRoot)),marker=await beginHistoryEventIndexInvalidation(pmRoot);pendingMarkerPath=marker.pendingPath;try{await append(),authoritativeAppendCommitted=!0}catch(appendError){throw await fs.rm(marker.pendingPath,{force:!0}),appendError}await fs.rename(marker.pendingPath,contended?marker.committedPath.replace(/\.committed$/u,".contended.committed"):marker.committedPath),contended||await invalidateHistoryEventIndex(pmRoot)})}catch(fallbackError){if(authoritativeAppendCommitted){await invalidateHistoryEventIndex(pmRoot);try{await fs.rm(pendingMarkerPath,{force:!0})}catch{}return}throw fallbackError}}async function removeHistoryEventIndexForHistoryPath(historyPath){await fs.rm(eventIndexPath(path.dirname(path.dirname(historyPath))),{force:!0})}function appendSetPredicate(clauses,parameters,column,values){!values||values.length===0||(clauses.push(`${column} IN (${values.map(()=>"?").join(", ")})`),parameters.push(...values))}function appendHarnessPredicate(clauses,parameters,harnesses,aliasAuthors){if(!harnesses||harnesses.length===0)return;const harnessPlaceholders=harnesses.map(()=>"?").join(", "),aliases=aliasAuthors??[];if(aliases.length>0){clauses.push(`(agent_harness IN (${harnessPlaceholders}) OR (agent_harness IS NULL AND author IN (${aliases.map(()=>"?").join(", ")})))`),parameters.push(...harnesses,...aliases);return}clauses.push(`agent_harness IN (${harnessPlaceholders})`),parameters.push(...harnesses)}async function readHistoryEventIndexSnapshot(pmRoot,Database,requestedStreamIds,read){let database;try{const invalidations=await listHistoryEventIndexInvalidations(pmRoot);if(await historyEventIndexAppendIsActive(pmRoot,invalidations))return HISTORY_INDEX_CHANGED;const before=await historyEventIndexSignature(pmRoot);if(invalidations.committed.length>0)return hasContendedHistoryEventIndexInvalidation(invalidations)&&hasNativeHistoryEventIndexContention(pmRoot,Database)?HISTORY_INDEX_CHANGED:null;if(database=new Database(eventIndexPath(pmRoot),{readOnly:!0}),database.exec("BEGIN"),database.prepare("SELECT value FROM metadata WHERE key = 'version'").get()?.value!==EVENT_INDEX_VERSION)return null;const result=read(database);if(result===null)return null;const storedSizes=readHistoryEventIndexStreamReceipts(database,requestedStreamIds),completed=database;database=void 0,completed.close();const streamIds=await historyIndexValidationStreamIds([...storedSizes.keys()],pmRoot,requestedStreamIds),sizesMatch=await historyIndexMatchesStreamContents(storedSizes,pmRoot,streamIds),current=await listHistoryEventIndexInvalidations(pmRoot);return current.committed.length>0||await historyEventIndexAppendIsActive(pmRoot,current)||before!==await historyEventIndexSignature(pmRoot)?HISTORY_INDEX_CHANGED:sizesMatch?result:null}catch(error){return isHistoryIndexSqliteContention(error)?HISTORY_INDEX_CHANGED:null}finally{database?.close()}}function isHistoryIndexSqliteContention(error){return typeof error=="object"&&error!==null&&"errcode"in error&&[5,6].includes(error.errcode)}function isHistoryIndexWriteContention(error){return isHistoryIndexSqliteContention(error)||error instanceof PmCliError&&error.code==="lock_conflict"}function readHistoryEventIndexStreamReceipts(database,requestedStreamIds){const storedSizes=new Map,ids=requestedStreamIds&&requestedStreamIds.length>0?[...new Set(requestedStreamIds)]:void 0;if(ids===void 0)for(const row of database.prepare("SELECT stream_id, byte_size, content_hash FROM streams").all())storedSizes.set(String(row.stream_id),{byte_size:Number(row.byte_size),content_hash:String(row.content_hash)});else for(let start=0;start<ids.length;start+=500){const chunk=ids.slice(start,start+500);for(const row of database.prepare(`SELECT stream_id, byte_size, content_hash FROM streams WHERE stream_id IN (${chunk.map(()=>"?").join(", ")})`).all(...chunk))storedSizes.set(String(row.stream_id),{byte_size:Number(row.byte_size),content_hash:String(row.content_hash)})}return storedSizes}async function historyEventIndexSignature(pmRoot){const stats=await fs.stat(eventIndexPath(pmRoot),{bigint:!0});return[stats.dev,stats.ino,stats.size,stats.mtimeNs,stats.ctimeNs].join(":")}async function readHistoryEventIndexPage(pmRoot,query){if(query.verify)return{result:await queryHistoryEventStreams(pmRoot,query),repair_needed:!1};const Database=resolveDatabaseSync();if(!Database)return{result:null,repair_needed:!1};const clauses=[],parameters=[];if(query.after_ts!==void 0&&query.after_stream_id!==void 0&&query.after_stream_offset!==void 0){clauses.push("(ts > ? OR (ts = ? AND stream_id > ?) OR (ts = ? AND stream_id = ? AND stream_offset > ?))");const afterTimestamp=canonicalHistoryQueryTimestamp(query.after_ts);parameters.push(afterTimestamp,afterTimestamp,query.after_stream_id,afterTimestamp,query.after_stream_id,query.after_stream_offset)}else query.since_ts!==void 0&&(clauses.push("ts >= ?"),parameters.push(canonicalHistoryQueryTimestamp(query.since_ts)));appendSetPredicate(clauses,parameters,"op",query.ops),appendSetPredicate(clauses,parameters,"author",query.authors),appendHarnessPredicate(clauses,parameters,query.harnesses,query.harness_alias_authors),appendSetPredicate(clauses,parameters,"agent_instance",query.agent_instances);for(const predicate of query.provenance??[])clauses.push(`json_extract(entry_json, '$.agent_provenance.${predicate.dimension}.value') IN (${predicate.values.map(()=>"?").join(", ")})`),parameters.push(...predicate.values);appendSetPredicate(clauses,parameters,"stream_id",query.stream_ids);const where=clauses.length===0?"":` WHERE ${clauses.join(" AND ")}`,attempt=await readHistoryEventIndexSnapshot(pmRoot,Database,query.stream_ids,database=>{const rows=database.prepare(`SELECT stream_id, stream_offset, entry_json
|
|
44
45
|
FROM events${where}
|
|
45
46
|
ORDER BY ts, stream_id, stream_offset
|
|
46
|
-
LIMIT ?`).all(...parameters,Math.max(0,Math.floor(query.limit))+1),events=rows.slice(0,query.limit).map(parseIndexedHistoryEvent);return events.includes(null)?null:{events,has_more:rows.length>query.limit}});return{result:attempt===HISTORY_INDEX_CHANGED?null:attempt,repair_needed:attempt===null&&!await hasRecentRefusedHistoryRepair(pmRoot)}}async function queryHistoryEventIndex(pmRoot,query){return(await readHistoryEventIndexPage(pmRoot,query)).result}function collectLatestSubstantiveEvents(events,requested){const latest=Object.create(null);for(const event of events){if(!requested.has(event.stream_id)||classifyHistoryEvent(event.entry)!=="substantive")continue;const current=latest[event.stream_id];(current===void 0||compareHistoryEventPosition(current,event)<0)&&(latest[event.stream_id]=event)}return latest}async function
|
|
47
|
+
LIMIT ?`).all(...parameters,Math.max(0,Math.floor(query.limit))+1),events=rows.slice(0,query.limit).map(parseIndexedHistoryEvent);return events.includes(null)?null:{events,has_more:rows.length>query.limit}});return{result:attempt===HISTORY_INDEX_CHANGED?null:attempt,repair_needed:attempt===null&&!await hasRecentRefusedHistoryRepair(pmRoot)}}async function queryHistoryEventIndex(pmRoot,query){return(await readHistoryEventIndexPage(pmRoot,query)).result}function collectLatestSubstantiveEvents(events,requested){const latest=Object.create(null);for(const event of events){if(!requested.has(event.stream_id)||classifyHistoryEvent(event.entry)!=="substantive")continue;const current=latest[event.stream_id];(current===void 0||compareHistoryEventPosition(current,event)<0)&&(latest[event.stream_id]=event)}return latest}async function readHistoryStreamContentReceipt(pmRoot,streamId){if(path.basename(streamId)!==streamId)return null;try{const historyPath=path.join(pmRoot,"history",`${streamId}.jsonl`),stats=await fs.stat(historyPath),raw=await fs.readFile(historyPath);return stats.size!==raw.length?null:{byte_size:raw.length,content_hash:createHash("sha256").update(raw).digest("hex")}}catch(error){if(isFileMissingError(error))return{byte_size:0,content_hash:createHash("sha256").digest("hex")};throw error}}async function historyIndexMatchesStreamContents(storedSizes,pmRoot,streamIds){for(let start=0;start<streamIds.length;start+=HISTORY_STREAM_CONTENT_READ_CONCURRENCY){const chunk=streamIds.slice(start,start+HISTORY_STREAM_CONTENT_READ_CONCURRENCY),currentSizes=await Promise.all(chunk.map(streamId=>readHistoryStreamContentReceipt(pmRoot,streamId)));for(const[index,currentSize]of currentSizes.entries()){if(currentSize===null)return!1;const streamId=chunk[index],storedSize=storedSizes.get(streamId);if(!(currentSize.byte_size===0&&storedSize===void 0)&&(currentSize.byte_size!==storedSize?.byte_size||currentSize.content_hash!==storedSize.content_hash))return!1}}return!0}async function historyIndexValidationStreamIds(indexed,pmRoot,requestedStreamIds){if(requestedStreamIds&&requestedStreamIds.length>0)return[...new Set(requestedStreamIds.filter(streamId=>streamId.trim().length>0))];const authoritative=await fs.readdir(path.join(pmRoot,"history"),{withFileTypes:!0}).then(entries=>entries.filter(entry=>entry.isFile()&&entry.name.endsWith(".jsonl")).map(entry=>entry.name.slice(0,-6))).catch(error=>{if(isFileMissingError(error))return[];throw error});return[...new Set([...indexed,...authoritative])]}async function readIndexedLatestSubstantiveEvents(Database,pmRoot,streamIds,requested){return Database?readHistoryEventIndexSnapshot(pmRoot,Database,streamIds,database=>{const indexed=[];for(let start=0;start<streamIds.length;start+=500){const chunk=streamIds.slice(start,start+500),parsed=database.prepare(`SELECT stream_id, stream_offset, entry_json
|
|
47
48
|
FROM (
|
|
48
49
|
SELECT stream_id, stream_offset, entry_json,
|
|
49
50
|
ROW_NUMBER() OVER (
|
|
@@ -3,7 +3,9 @@ import type { HistoryEntry } from "../../types/index.js";
|
|
|
3
3
|
export interface HistoryStreamSnapshot {
|
|
4
4
|
/** Null means the stream did not exist; an empty string means it existed without entries. */
|
|
5
5
|
raw:string | null;
|
|
6
|
-
/**
|
|
6
|
+
/** Original file bytes, before UTF-8 decoding; null means the stream did not exist. */
|
|
7
|
+
raw_bytes:Uint8Array | null;
|
|
8
|
+
/** Entries decoded from `raw_bytes`, after read hooks run. */
|
|
7
9
|
entries:HistoryEntry[];
|
|
8
10
|
}
|
|
9
11
|
/** Read one history stream once so replay input and the concurrency snapshot cannot disagree. */
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
1
|
+
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="58661057-fa80-5571-9be7-b949723c274b")}catch{}})();import fs from"node:fs/promises";import{isFileAbsentError}from"../fs/fs-utils.js";import{runActiveOnReadHooks}from"../extensions/index.js";import{EXIT_CODE}from"../shared/constants.js";import{findFirstMergeConflictMarker}from"../shared/conflict-markers.js";import{PmCliError}from"../shared/errors.js";async function readHistorySnapshot(historyPath,itemId){const rawBytes=await fs.readFile(historyPath).catch(error=>{if(isFileAbsentError(error))return null;throw error}),raw=rawBytes?.toString("utf8")??null;if(raw===null)return{raw,raw_bytes:rawBytes,entries:[]};if(await runActiveOnReadHooks({path:historyPath,scope:"project"}),raw.trim()==="")return{raw,raw_bytes:rawBytes,entries:[]};const conflictMarker=findFirstMergeConflictMarker(raw);if(conflictMarker)throw new PmCliError(`History for ${itemId} contains merge conflict markers at line ${conflictMarker.line} (${conflictMarker.marker}). Resolve <<<<<<< ======= >>>>>>> markers and retry.`,EXIT_CODE.GENERIC_FAILURE,{code:"history_merge_conflict_markers_detected",required:"Repair the history stream by resolving merge-conflict markers.",why:"Conflict markers break JSONL parsing and invalidate deterministic audit history.",examples:[`pm history ${itemId}`,`pm restore ${itemId} <timestamp-or-version>`],nextSteps:["Resolve or restore the history file, then rerun the command."]});const entries=[];for(const[index,rawLine]of raw.split(/\r?\n/).entries()){const line=rawLine.trim();if(line)try{entries.push(JSON.parse(line))}catch{throw new PmCliError(`History for ${itemId} contains invalid JSON at line ${index+1}. Repair or restore the history stream and retry.`,EXIT_CODE.GENERIC_FAILURE,{code:"history_json_invalid",nextSteps:[`Run pm history-repair ${itemId} --salvage-tail --dry-run to check whether only an invalid suffix is damaged.`]})}}return{raw,raw_bytes:rawBytes,entries}}async function readHistoryEntries(historyPath,itemId){return(await readHistorySnapshot(historyPath,itemId)).entries}export{readHistoryEntries,readHistorySnapshot};
|
|
2
2
|
//# sourceMappingURL=read.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
1
|
+
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="439cf503-a832-58a3-ba19-db44de57b573")}catch{}})();import{unicodeSafePrefix}from"../shared/text-normalization.js";const CHANGED_FIELDS_KEY="changed_fields",CHANGED_FIELD_COUNT_KEY="changed_field_count",ROWS_KEY="rows",UPDATE_MANY_MUTATION_MODES=new Set(["apply","rollback"]),DELETE_OUTCOMES=new Set(["deleted","would_delete"]),COMPACT_RECURRENCE_TEXT_LIMIT=256,COMPOUND_MUTATION_STEPS={start_task:["claim","update"],pause_task:["update","release"],close_task:["close","release"]};function projectCompoundMutation(result,options){if(!isPlainObject(result)||typeof result.action!="string"||typeof result.id!="string"||result.id.length===0)return null;const steps=Object.hasOwn(COMPOUND_MUTATION_STEPS,result.action)?COMPOUND_MUTATION_STEPS[result.action]:void 0;if(!steps||!steps.every(step=>{const value=result[step];return isPlainObject(value)&&isPlainObject(value.item)&&value.item.id===result.id&&Array.isArray(value.changed_fields)&&value.changed_fields.every(field=>typeof field=="string")}))return null;const final=result[steps.at(-1)],identity=projectIdOnlyResult(final);return options.idOnly?identity:!options.compactEnvelope&&options.changedFields!=="compact"?null:{...result,...options.compactEnvelope?{...identity,changed_field_count:new Set(steps.flatMap(step=>result[step].changed_fields)).size}:{},...Object.fromEntries(steps.map(step=>[step,projectMutationResult(result[step],options)]))}}function truncateCompactRecurrenceText(value){return typeof value!="string"||value.length<=COMPACT_RECURRENCE_TEXT_LIMIT?value:`${unicodeSafePrefix(value,COMPACT_RECURRENCE_TEXT_LIMIT-1)}\u2026`}function projectCompactRecurrence(value){if(!isPlainObject(value))return null;const recurrence={...value};return recurrence.reason=truncateCompactRecurrenceText(recurrence.reason),isPlainObject(recurrence.previous_terminal)&&(recurrence.previous_terminal=Object.fromEntries(Object.entries(recurrence.previous_terminal).map(([key,entry])=>[key,truncateCompactRecurrenceText(entry)]))),recurrence}function isPlainObject(value){if(typeof value!="object"||value===null||Array.isArray(value))return!1;const proto=Object.getPrototypeOf(value);return proto===null||proto===Object.prototype}function compactOwnChangedFields(envelope){const changedFields=envelope[CHANGED_FIELDS_KEY];if(!Array.isArray(changedFields))return null;const projected=Object.getPrototypeOf(envelope)===null?Object.create(null):{};for(const[key,value]of Object.entries(envelope))key!==CHANGED_FIELDS_KEY&&(projected[key]=value);return projected[CHANGED_FIELD_COUNT_KEY]=changedFields.length,projected}function isMutationEnvelope(value){return"item"in value&&Array.isArray(value[CHANGED_FIELDS_KEY])}function isUpdateManyMutationEnvelope(value){return typeof value.mode=="string"&&UPDATE_MANY_MUTATION_MODES.has(value.mode)&&Array.isArray(value[ROWS_KEY])}function replaceRows(envelope,rows){const projected=Object.getPrototypeOf(envelope)===null?Object.create(null):{};return Object.assign(projected,envelope),projected[ROWS_KEY]=rows,projected}function projectIdOnlyResult(result){if(!isPlainObject(result))return null;const subject=isPlainObject(result.item)?result.item:isPlainObject(result.plan)?result.plan:null;if(subject===null)return null;const id=typeof subject.id=="string"?subject.id:void 0,status=typeof result.outcome=="string"&&DELETE_OUTCOMES.has(result.outcome)?result.outcome:typeof subject.status=="string"?subject.status:void 0;return id?status?{id,status,...typeof result.deleted=="boolean"?{deleted:result.deleted}:{}}:{id}:null}function projectCompactMutationEnvelope(result,includeItem){if(!isPlainObject(result))return null;if(isPlainObject(result.plan)&&isPlainObject(result.mutation_receipt)&&result.mutation_receipt.kind==="plan_mutation"&&result.mutation_receipt.id===result.plan.id&&result.mutation_receipt.action===result.action)return result.mutation_receipt;if(!includeItem||!isPlainObject(result.item)||typeof result.item.id!="string")return null;const changedFields=result[CHANGED_FIELDS_KEY];if(!Array.isArray(changedFields))return null;const compact={id:result.item.id,...typeof result.outcome=="string"&&DELETE_OUTCOMES.has(result.outcome)?{status:result.outcome}:typeof result.item.status=="string"?{status:result.item.status}:{},...typeof result.deleted=="boolean"?{deleted:result.deleted}:{},...typeof result.previous_status=="string"?{previous_status:result.previous_status}:{},changed_field_count:changedFields.length};return appendCompactMutationEvidence(result,result.item,compact),compact}function appendCompactMutationEvidence(result,item,compact){const closeReason=typeof result.close_reason=="string"?result.close_reason:typeof item.close_reason=="string"?item.close_reason:void 0;closeReason!==void 0&&(compact.close_reason=closeReason);const recurrence=projectCompactRecurrence(result.recurrence);recurrence!==null&&(compact.recurrence=recurrence),Array.isArray(result.warnings)&&result.warnings.length>0&&(compact.warnings=result.warnings);for(const key of["recovery","follow_up","handoff","completion"])isPlainObject(result[key])&&(compact[key]=result[key]);if(typeof result.claimed_by=="string"||typeof result.released_by=="string")for(const key of["claimed_by","released_by","previous_assignee","forced","skipped","available","attempts","suggestions"])Object.hasOwn(result,key)&&(compact[key]=result[key])}function compactUpdateManyRows(envelope){const rows=isUpdateManyMutationEnvelope(envelope)?envelope[ROWS_KEY]:void 0;if(!Array.isArray(rows))return{projected:envelope,changed:!1};let rowsChanged=!1;const nextRows=rows.map(row=>{if(!isPlainObject(row))return row;const compactedRow=compactOwnChangedFields(row);return compactedRow?(rowsChanged=!0,compactedRow):row});return rowsChanged?{projected:replaceRows(envelope,nextRows),changed:!0}:{projected:envelope,changed:!1}}function projectMutationResult(result,options={}){const compound=projectCompoundMutation(result,options);if(compound!==null)return compound;if(options.idOnly===!0){const idOnly=projectIdOnlyResult(result);if(idOnly!==null)return idOnly}if(options.compactEnvelope===!0||options.changedFields==="compact"){const compact=projectCompactMutationEnvelope(result,options.compactEnvelope===!0);if(compact!==null)return compact}if((options.changedFields??"full")==="full"||!isPlainObject(result))return result;let changed=!1,projected=result;const compactedTop=isMutationEnvelope(result)?compactOwnChangedFields(result):null;compactedTop!==null&&(projected=compactedTop,changed=!0);const rowProjection=compactUpdateManyRows(projected);return rowProjection.changed&&(projected=rowProjection.projected,changed=!0),changed?projected:result}export{projectMutationResult};
|
|
2
2
|
//# sourceMappingURL=mutation-projection.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
1
|
+
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="697f1dbe-8315-5901-ab11-e0c5211dbdbf")}catch{}})();import fs from"node:fs/promises";import path from"node:path";import{renderPmCommand}from"../shared/command-line.js";import{canonicalizeCommandOptionKey,commandOptionFlagLabel}from"../item/type-registry.js";import{isUtf8}from"node:buffer";import{isFileMissingError}from"../fs/fs-utils.js";import{appendWorkspaceAuditEvent}from"../history/workspace-history.js";import{verifyHistoryChain}from"../history/replay.js";import{PmCliError}from"../shared/errors.js";import{EXIT_CODE}from"../shared/constants.js";import{getHistoryPath}from"../store/paths.js";import{evaluateWorkflowPolicies,parseWorkflowPolicyDocument,workflowPolicyApplies}from"./workflow-policy.js";const MAX_WORKFLOW_POLICY_BYTES=1048576,MAX_WORKFLOW_APPROVAL_HISTORY_BYTES=4194304;async function readBoundedPolicyFile(file,maxBytes){let ancestor=path.dirname(file);for(;ancestor!==path.parse(file).root;){const stats=await fs.stat(ancestor).catch(error=>{if(isFileMissingError(error))return null;throw error});if(stats!==null){if(!stats.isDirectory())throw new PmCliError("Workflow policy storage ancestor must be a directory.",EXIT_CODE.CONFLICT,{code:"workflow_policy_input_unreadable",nextSteps:["Restore the policy or approval history directory before retrying."]});break}ancestor=path.dirname(ancestor)}const handle=await fs.open(file,"r").catch(error=>{if(isFileMissingError(error))return null;throw error});if(handle===null)return null;try{const size=(await handle.stat()).size,buffer=Buffer.alloc(Math.min(maxBytes,size)+1);let offset=0;for(;offset<buffer.length;){const read=await handle.read(buffer,offset,buffer.length-offset,offset);if(read.bytesRead===0)break;offset+=read.bytesRead}if(offset===buffer.length||!isUtf8(buffer.subarray(0,offset)))throw new PmCliError("Workflow policy input exceeds its byte bound or is not valid UTF-8.",EXIT_CODE.CONFLICT,{code:"workflow_policy_input_unreadable",nextSteps:["Inspect the policy and approval history; reduce the declaration or use verified history compaction before retrying."]});return buffer.toString("utf8",0,offset)}finally{await handle.close()}}function decodeWorkflowPolicyDocument(raw){if(raw===null)return{version:1,enforcement:"advise",policies:[]};let value;try{value=JSON.parse(raw)}catch{throw new PmCliError("schema/policies.json must contain valid JSON.",EXIT_CODE.CONFLICT,{code:"workflow_policy_registry_invalid",nextSteps:["Restore valid policy JSON before retrying the mutation."]})}return parseWorkflowPolicyDocument(value)}async function readWorkflowPolicies(pmRoot){return decodeWorkflowPolicyDocument(await readBoundedPolicyFile(path.join(pmRoot,"schema","policies.json"),MAX_WORKFLOW_POLICY_BYTES))}async function readWorkflowApprovals(pmRoot,id){const raw=await readBoundedPolicyFile(getHistoryPath(pmRoot,id),MAX_WORKFLOW_APPROVAL_HISTORY_BYTES);if(raw===null)return[];let entries;try{if(entries=raw.split(/\r?\n/).filter(line=>line.trim()).map(line=>JSON.parse(line)),!verifyHistoryChain(entries).ok)throw new Error("invalid chain")}catch{throw new PmCliError("Approval evidence requires a valid item history chain.",EXIT_CODE.CONFLICT,{code:"workflow_policy_approval_history_invalid",nextSteps:[`Run pm history ${id} --verify before recording or using an approval.`]})}return entries.flatMap(entry=>{const approval=entry.context?.workflow_approval;if(entry.op!=="policy_approve"||entry.record_hash_version!==1||typeof entry.record_hash!="string"||typeof approval!="object"||approval===null)return[];const record=approval;return typeof record.policy_fingerprint!="string"||typeof record.content_fingerprint!="string"?[]:[{policy_fingerprint:record.policy_fingerprint,content_fingerprint:record.content_fingerprint,author:entry.author}]})}async function evaluateWorkspacePolicies(pmRoot,document,input){const needsApproval=input.before?.status!==input.after?.status&&document.policies.some(policy=>policy.rule.kind==="approval"&&workflowPolicyApplies(policy,input)),id=input.before?.id??input.after?.id,approvals=needsApproval&&typeof id=="string"?await readWorkflowApprovals(pmRoot,id):[];return evaluateWorkflowPolicies(document,{...input,approvals})}async function enforceWorkflowMutation(params){const document=await readWorkflowPolicies(params.pmRoot),result=await evaluateWorkspacePolicies(params.pmRoot,document,{operation:params.operation,author:params.author,before:params.before?{...params.before.metadata,body:params.before.body}:null,after:params.after?{...params.after.metadata,body:params.after.body}:null});if(result.allowed)return result;const refused=result.decisions.filter(decision=>!decision.satisfied&&decision.effect==="refuse"),sideEffects=[];let auditNotice="";if(!params.dryRun)try{const audit=await appendWorkspaceAuditEvent({pmRoot:params.pmRoot,op:"policy_refused",author:params.author,context:{item_id:params.after?.metadata.id??params.before?.metadata.id,operation:params.operation,workflow_policies:result.decisions},message:"Workflow policy refused a proposed item mutation.",lockTtlSeconds:params.settings.locks.ttl_seconds,lockWaitMs:params.settings.locks.wait_ms});sideEffects.push({path:"history/_workspace.jsonl",op:"policy_refused",record_hash:audit.entry.record_hash}),auditNotice=" Audit appended to tracker-relative history/_workspace.jsonl; retain it with the workspace changes. Each refused attempt records its own audit event."}catch(cause){const error=new PmCliError("Workflow policy refusal could not be recorded in workspace history.",EXIT_CODE.CONFLICT,{code:"workflow_policy_audit_failed",nextSteps:["Resolve workspace-history lock or storage failures, then retry the refused mutation; no item changes were written."]});throw error.cause=cause,error}const violations=refused.slice(0,3).map(decision=>({policy_id:decision.policy_id,rule:decision.rule,description:document.policies.find(policy=>policy.id===decision.policy_id).description,missing_fields:decision.missing_fields})),missing=[...new Set(violations.flatMap(decision=>decision.missing_fields))],summary=violations.map(decision=>`${decision.policy_id} (${decision.rule})`).join(", "),remaining=refused.length>3?` and ${refused.length-3} more`:"",itemId=params.after?.metadata.id??params.before.metadata.id,fieldSteps=missing.map(field=>{if(params.before===null)return`Supply ${field} in the original ${params.operation} request, then retry; the item was not created.`;const option=canonicalizeCommandOptionKey("update",field);return option===void 0?`Supply ${field} using its declared SDK or package mutation contract, then retry.`:renderPmCommand(["update",itemId,commandOptionFlagLabel("update",option),`<${field}>`,"--pm-path",path.resolve(params.pmRoot)])});throw new PmCliError(`Workflow policy refused ${params.operation}: ${summary}${remaining}.${missing.length?` Missing: ${missing.join(", ")}.`:""}${auditNotice}`,EXIT_CODE.CONFLICT,{code:"workflow_policy_refused",side_effects:sideEffects,reason:violations.map(decision=>decision.policy_id).join(","),policy_violations:violations,policy_violation_count:refused.length,required:missing.length?`Supply missing fields: ${missing.join(", ")}, then retry the refused mutation.`:"Satisfy the declared workflow policy before retrying.",why:violations.map(decision=>decision.description).filter(Boolean).join(" ")||void 0,recovery:{missing_required_fields:missing},nextSteps:[...fieldSteps,...new Set(refused.slice(0,3).filter(decision=>decision.missing_fields.length===0).map(decision=>decision.remediation)),...refused.length>3?[`Inspect all policies with pm schema policies before retrying ${params.operation}.`]:[]]})}export{MAX_WORKFLOW_APPROVAL_HISTORY_BYTES,MAX_WORKFLOW_POLICY_BYTES,decodeWorkflowPolicyDocument,enforceWorkflowMutation,evaluateWorkspacePolicies,readWorkflowApprovals,readWorkflowPolicies};
|
|
2
2
|
//# sourceMappingURL=workflow-policy-store.js.map
|
|
@@ -93,6 +93,15 @@ next_best_command?:string;
|
|
|
93
93
|
}
|
|
94
94
|
/** Documents the pm cli error context payload exchanged by command, SDK, and package integrations. */
|
|
95
95
|
export interface PmCliErrorContext {
|
|
96
|
+
/** Durable writes completed before refusal; paths are relative to the selected tracker. */
|
|
97
|
+
side_effects?:Array<{
|
|
98
|
+
/** Tracker-relative audit stream that was appended. */
|
|
99
|
+
path:string;
|
|
100
|
+
/** Immutable operation written to that stream. */
|
|
101
|
+
op:string;
|
|
102
|
+
/** Sealed record identity when supplied by the history writer. */
|
|
103
|
+
record_hash?:string;
|
|
104
|
+
}>;
|
|
96
105
|
/** Admission completed before any mutation; omitted when writes may have occurred. */
|
|
97
106
|
changed?:false;
|
|
98
107
|
/** Value that configures or reports code for this contract. */
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
1
|
+
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="85f8d656-86eb-52a9-8160-70b60b9da511")}catch{}})();class PmCliError extends Error{exitCode;code;context;constructor(message,exitCode,context={}){super(message),this.name="PmCliError",this.exitCode=exitCode,this.context=context,this.code=context.code}}export{PmCliError};
|
|
2
2
|
//# sourceMappingURL=errors.js.map
|
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
* Single credential and private-context detector inventory for mutation preflight
|
|
4
4
|
* and repository scanning. Diagnostics must never include matched text.
|
|
5
5
|
*/
|
|
6
|
+
/** Safe transformation identities; neither original nor decoded values escape. */
|
|
7
|
+
export type SecretNormalizationKind = "percent_decode" | "json_unescape" | "unicode_fold";
|
|
8
|
+
/** Original source location and optional inspection provenance. */
|
|
9
|
+
export interface SecretRuleMatch {
|
|
10
|
+
/** UTF-16 source offset; normalized detections point to the original line start. */
|
|
11
|
+
index:number;
|
|
12
|
+
/** Safe transformations used to reveal a coordinate. */
|
|
13
|
+
normalizations?:SecretNormalizationKind[];
|
|
14
|
+
/** Remaining encoded text requires review after the finite pass budget. */
|
|
15
|
+
normalization_incomplete?:true;
|
|
16
|
+
}
|
|
17
|
+
/** URI schemes cannot masquerade as native UNC roots; explicit file URLs remain supported. */
|
|
18
|
+
export declare const PRIVATE_HOME_PATH_PATTERN:RegExp;
|
|
19
|
+
/** Native matches keep exact offsets; canonical matches retain their original source line. */
|
|
20
|
+
export declare function findPrivateHomePathMatches(content:string,regex:RegExp):Generator<SecretRuleMatch>;
|
|
6
21
|
/** One detector and any explicit repository-only scope exception. */
|
|
7
22
|
export interface SecretRule {
|
|
8
23
|
/** Repository diagnostic identifier. */
|
|
@@ -106,5 +121,7 @@ readonly regex:RegExp;
|
|
|
106
121
|
readonly mutationOnlyReason:"Generic assignments in source code are not credentials; preserve the existing mutation heuristic without treating source identifiers as secrets.";
|
|
107
122
|
readonly minimumEntropy:3.5;
|
|
108
123
|
}];
|
|
124
|
+
/** Share matching and normalization, returning locations rather than matched values. */
|
|
125
|
+
export declare function findSecretRuleMatches(rule:SecretRule,content:string):Generator<SecretRuleMatch>;
|
|
109
126
|
/** Accept a pattern match, applying the declared entropy predicate when present. */
|
|
110
127
|
export declare function acceptsSecretMatch(rule:SecretRule,match:RegExpMatchArray):boolean;
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
1
|
+
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="88eb8555-e098-5279-801e-3b76d0dd0eae")}catch{}})();const COMPONENT=`[^\\s/\\\\<>"'\\x60()\\[\\]{},;?=:#*]+`,SEPARATOR="[/\\\\]+",ACCOUNT="[\\p{L}\\p{N}._@-]+",ACCOUNT_PATH=ACCOUNT+"(?:"+SEPARATOR+COMPONENT+")*",ROOT_HOME="(?:(?:home|users)"+SEPARATOR+ACCOUNT_PATH+"|root"+SEPARATOR+ACCOUNT_PATH+")",UNIX_HOME="[/\\\\]"+ROOT_HOME,MULTI_ROOT_HOME="[/\\\\]{2,}"+ROOT_HOME,WINDOWS_HOME="/?[a-z]:"+SEPARATOR+"users"+SEPARATOR+ACCOUNT_PATH,SHARE_COMPONENT=COMPONENT+"(?: +"+COMPONENT+")*",SHARED_PROFILE="(?:"+SHARE_COMPONENT+SEPARATOR+")?users"+SEPARATOR+ACCOUNT_PATH,UNC_HOME="[/\\\\]{2,}"+COMPONENT+SEPARATOR+SHARED_PROFILE,DEVICE_VOLUME="(?:[a-z]:|volume\\{[a-f0-9-]+\\}|"+COMPONENT+")",DEVICE_HOME="[/\\\\]{2}[?.]"+SEPARATOR+"(?:unc"+SEPARATOR+COMPONENT+SEPARATOR+SHARED_PROFILE+"|"+DEVICE_VOLUME+SEPARATOR+"users"+SEPARATOR+ACCOUNT_PATH+")",FILE_HOME="file:(?:"+DEVICE_HOME+"|(?:[/\\\\](?![/\\\\])|[/\\\\]{3,}|[/\\\\]{2}localhost"+SEPARATOR+")(?:"+ROOT_HOME+"|[a-z]:"+SEPARATOR+"users"+SEPARATOR+ACCOUNT_PATH+")|[/\\\\]{2}(?:[a-z]:"+SEPARATOR+"users"+SEPARATOR+ACCOUNT_PATH+"|"+COMPONENT+SEPARATOR+"(?:"+ROOT_HOME+"|"+SHARED_PROFILE+")))",PRIVATE_HOME_PATH_PATTERN=new RegExp("(?:^|[^\\p{L}\\p{N}_./\\\\~])(?:"+UNIX_HOME+"|"+WINDOWS_HOME+"|"+FILE_HOME+")|(?:^|[^\\p{L}\\p{N}_:./\\\\~])(?:"+MULTI_ROOT_HOME+"|"+UNC_HOME+"|"+DEVICE_HOME+")","giu"),MAX_NORMALIZATION_PASSES=4,REMAINING_ENCODING=/%[a-f0-9]{2}|\\(?:u[a-f0-9]{4}|x[a-f0-9]{2}|\/)/iu,utf8=new TextDecoder("utf-8");function percentView(value){return value.replace(/(?:%[a-f0-9]{2})+/giu,run=>utf8.decode(Uint8Array.from(run.matchAll(/%([a-f0-9]{2})/giu),match=>Number.parseInt(match[1],16)))).replace(/%(?![a-f0-9]{2})[a-z0-9]{0,2}/giu,"\uFFFD")}function escapedView(value){return value.replace(/\\+(?:u([a-f0-9]{4})|x([a-f0-9]{2})|(\/))?/giu,(match,unicode,byte,slash)=>{const code=unicode??byte;return code===void 0?slash??match:String.fromCharCode(Number.parseInt(code,16))})}function unicodeView(value){return value.normalize("NFKC").replace(/[\p{Bidi_Control}\u200b-\u200f\u2060-\u206f\ufeff]/gu,"").replace(/[\u2215\u2044]/gu,"/").replace(/[\u043e\u041e]/gu,"o").replace(/[\u0455\u0405]/gu,"s")}function nextNormalizedView(view,steps){for(const[kind,transform]of[["percent_decode",percentView],["json_unescape",escapedView],["unicode_fold",unicodeView]]){const transformed=transform(view);transformed!==view&&!steps.includes(kind)&&steps.push(kind),view=transformed}return view}function*normalizedHomePathMatches(line,regex,detected){let view=line;const steps=[];for(let pass=0;pass<MAX_NORMALIZATION_PASSES;pass+=1){const previous=view;if(view=nextNormalizedView(view,steps),!detected){regex.lastIndex=0;for(const _match of view.matchAll(regex))detected=!0,yield{index:0,normalizations:[...steps]}}if(view===previous)break;pass===MAX_NORMALIZATION_PASSES-1&&REMAINING_ENCODING.test(view)&&(yield{index:0,normalizations:[...steps],normalization_incomplete:!0})}}function*findPrivateHomePathMatches(content,regex){let offset=0;for(const line of content.split(`
|
|
2
|
+
`)){regex.lastIndex=0;let rawFound=!1;for(const match of line.matchAll(regex))rawFound=!0,yield{index:offset+match.index};for(const match of normalizedHomePathMatches(line,regex,rawFound))yield{...match,index:offset};offset+=line.length+1}}const SECRET_RULES=[{name:"private-key",mutationRule:"private_key",regex:/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/g},{name:"github-token",mutationRule:"github_token",regex:/\b(?:gh[opsru]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})\b/g},{name:"npm-token",mutationRule:"npm_token",regex:/\bnpm_[A-Za-z0-9]{20,}\b/g},{name:"aws-access-key",mutationRule:"aws_access_key",regex:/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g},{name:"google-api-key",mutationRule:"google_api_key",regex:/\bAIza[0-9A-Za-z_-]{35}\b/g},{name:"slack-token",mutationRule:"slack_token",regex:/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g},{name:"anthropic-api-key",mutationRule:"anthropic_api_key",regex:/\bsk-ant-[A-Za-z0-9_-]{20,}\b/g},{name:"openai-api-key",mutationRule:"openai_api_key",regex:/\bsk-(?!ant-)[A-Za-z0-9_-]{20,}\b/g},{name:"gitlab-token",mutationRule:"gitlab_token",regex:/\bglpat-[A-Za-z0-9_.-]{20,}\b/g},{name:"jwt-like-token",mutationRule:"jwt_like_token",regex:/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g},{name:"npm-auth-token-assignment",mutationRule:"npm_auth_token_assignment",regex:/_authToken\s*=\s*\S+/g},{name:"sentry-user-token",mutationRule:"sentry_user_token",regex:/\bsntryu_[A-Za-z0-9]{20,}\b/g},{name:"sentry-org-token",mutationRule:"sentry_org_token",regex:/\bsntrys_[A-Za-z0-9_-]{20,}\b/g},{name:"private-ip",mutationRule:"private_ip",regex:/\b(?:10\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)|172\.(?:1[6-9]|2\d|3[01])\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)|192\.168\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d))\b/g},{name:"private-ssh-target",mutationRule:"private_ssh_target",regex:/\b(?:ssh|scp|rsync)\s+[A-Za-z0-9._-]+@(?:10\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)|172\.(?:1[6-9]|2\d|3[01])\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)|192\.168\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d))\b/g},{name:"sshpass-inline-password",mutationRule:"sshpass_inline_password",regex:/\bsshpass\s+-p\s+["'][^"']{6,}["']/g},{name:"password-assignment",mutationRule:"password_assignment",regex:/\b(?:password|passphrase)\s*[:=]\s*(?:(?!<|\$\{|\$\(|config\.auth\.password\s*(?:\|\|\s*(?:''|"")|\?\s*unescape\(encodeURIComponent\(config\.auth\.password\)\)\s*:\s*(?:''|""))(?=[ \t]*(?:\/\*(?:[^*]|\*(?!\/))*\*\/[ \t]*)*(?:;|$|\/\/[^\r\n\u2028\u2029]*(?:\r\n)?$)))[A-Za-z0-9!@#$%^&*._+\-=]{8,}|"(?!<|\$\{|\$\()[^"]{8,}"|'(?!<|\$\{|\$\()[^']{8,}')/gi},{name:"absolute-home-path",mutationRule:"absolute_home_path",regex:PRIVATE_HOME_PATH_PATTERN,excludeFiles:/^(?:tests\/|examples\/)/},{name:"user-at-private-host",mutationRule:"user_at_private_host",regex:/\b[A-Za-z0-9._-]+@(?:10\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)|172\.(?:1[6-9]|2\d|3[01])\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)|192\.168\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.(?:25[0-5]|2[0-4]\d|[01]?\d?\d))\b/g,excludeFiles:/^(?:tests\/|examples\/)/},{name:"high-entropy-assignment",mutationRule:"high_entropy_assignment",regex:/\b(?:token|secret|password|passwd|api[_-]?key)\s*[:=]\s*["']?([A-Za-z0-9+/=_-]{24,})/gi,mutationOnlyReason:"Generic assignments in source code are not credentials; preserve the existing mutation heuristic without treating source identifiers as secrets.",minimumEntropy:3.5}];function*findSecretRuleMatches(rule,content){if(rule.mutationRule==="absolute_home_path"){yield*findPrivateHomePathMatches(content,rule.regex);return}rule.regex.lastIndex=0;for(const match of content.matchAll(rule.regex))acceptsSecretMatch(rule,match)&&(yield{index:match.index})}function acceptsSecretMatch(rule,match){if(rule.minimumEntropy===void 0)return!0;const value=match[1],counts=new Map;for(const character of value)counts.set(character,(counts.get(character)??0)+1);let entropy=0;for(const count of counts.values()){const probability=count/value.length;entropy-=probability*Math.log2(probability)}return entropy>=rule.minimumEntropy}export{PRIVATE_HOME_PATH_PATTERN,SECRET_RULES,acceptsSecretMatch,findPrivateHomePathMatches,findSecretRuleMatches};
|
|
2
3
|
//# sourceMappingURL=secret-rules.js.map
|
|
@@ -3,9 +3,17 @@ interface ItemMutationPrecondition {
|
|
|
3
3
|
pmRoot:string;
|
|
4
4
|
id:string;
|
|
5
5
|
validate:(document:ItemDocument) => void;
|
|
6
|
+
/** Advance the guard only after this operation durably commits under the item lock. */
|
|
7
|
+
committed?:(document:ItemDocument) => void;
|
|
8
|
+
/** Immutable attribution for every internal write to this target. */
|
|
9
|
+
historyContext?:Record<string,unknown>;
|
|
6
10
|
}
|
|
7
11
|
/** Scope a target-specific precondition to an awaited mutation without affecting concurrent callers. */
|
|
8
12
|
export declare function runWithItemMutationPrecondition<T>(condition:ItemMutationPrecondition,operation:() => Promise<T>):Promise<T>;
|
|
9
13
|
/** Evaluate matching preconditions only after the item lock has been acquired and its document read. */
|
|
10
14
|
export declare function assertItemMutationPreconditions(pmRoot:string,document:ItemDocument):void;
|
|
15
|
+
/** Notify matching scoped guards of their own committed document before releasing the item lock. */
|
|
16
|
+
export declare function advanceItemMutationPreconditions(pmRoot:string,document:ItemDocument):void;
|
|
17
|
+
/** Collect target-scoped immutable attribution without attributing another caller's writes. */
|
|
18
|
+
export declare function itemMutationPreconditionHistoryContext(pmRoot:string,document:ItemDocument):Record<string,unknown>;
|
|
11
19
|
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
1
|
+
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="39bf765c-1374-5ba5-b8a9-44137f80c976")}catch{}})();import{AsyncLocalStorage}from"node:async_hooks";import path from"node:path";const preconditions=new AsyncLocalStorage;function runWithItemMutationPrecondition(condition,operation){return preconditions.run([...preconditions.getStore()??[],{...condition,pmRoot:path.resolve(condition.pmRoot)}],operation)}function assertItemMutationPreconditions(pmRoot,document){for(const condition of preconditions.getStore()??[])condition.pmRoot===path.resolve(pmRoot)&&condition.id===document.metadata.id&&condition.validate(document)}function advanceItemMutationPreconditions(pmRoot,document){for(const condition of preconditions.getStore()??[])condition.pmRoot===path.resolve(pmRoot)&&condition.id===document.metadata.id&&condition.committed?.(document)}function itemMutationPreconditionHistoryContext(pmRoot,document){return Object.assign({},...(preconditions.getStore()??[]).filter(condition=>condition.pmRoot===path.resolve(pmRoot)&&condition.id===document.metadata.id).map(condition=>condition.historyContext))}export{advanceItemMutationPreconditions,assertItemMutationPreconditions,itemMutationPreconditionHistoryContext,runWithItemMutationPrecondition};
|
|
2
2
|
//# sourceMappingURL=item-mutation-precondition.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3a19f8b4-d2d8-5133-b320-281980d64da6")}catch{}})();import fs from"node:fs/promises";import{buildDeletedItemError}from"../history/tombstone.js";import{normalizeHistoryOperationForWrite}from"../history/operation-contract.js";import{enforceWorkflowMutation}from"../policy/workflow-policy-store.js";import{recordItemMetadataEnumeration}from"./item-metadata-read-work.js";import{assertItemMutationPreconditions}from"./item-mutation-precondition.js";import{checkpointMutationHistoryContext}from"../checkpoint/mutation-context.js";import path from"node:path";import{getActiveExtensionRegistrations,projectAfterCommandItemSnapshot,recordAfterCommandAffectedItem,runActiveOnReadHooks,runActiveBeforeMutationHooks,runActiveOnWriteHooks,runActiveServiceOverride}from"../extensions/index.js";import{collectRegisteredItemFieldNames}from"../extensions/item-fields.js";import{EMPTY_CANONICAL_DOCUMENT,EXIT_CODE,TYPE_TO_FOLDER}from"../shared/constants.js";import{PmCliError}from"../shared/errors.js";import{levenshteinDistanceWithinLimit}from"../shared/levenshtein.js";import{appendHistoryEntry,createHistoryEntry}from"../history/history.js";import{enforceHistoryStreamPolicyForItem}from"../history/history-stream-policy.js";import{canonicalDocument,parseItemDocument,serializeItemDocument}from"../item/item-format.js";import{resolveItemTypeRegistry}from"../item/type-registry.js";import{effectiveItemFormatVersion,requiredItemFormatVersion}from"../item/item-format-version.js";import{assertProjectRuntimeFormatGuard}from"../../sdk/environment/project-runtime-compatibility.js";import{acquireLockLease}from"../lock/lock.js";import{isFileMissingError,writeFileAtomic}from"../fs/fs-utils.js";import{isUtf8}from"node:buffer";import{normalizeItemId,normalizeRawItemId}from"../item/id.js";import{acquireItemMetadataDerivedIndexLock,listAllDocumentCandidatesCached,listAllDocumentsCached,listAllDocumentsCachedLight,refreshItemMetadataDerivedIndex}from"./item-metadata-cache.js";import{getHistoryPath,getItemPath,ITEM_FILE_EXTENSIONS,resolveWorkspaceRoot}from"./paths.js";import{resolveGovernanceKnobs}from"./settings.js";import{assertReadableTrackerRoot}from"./tracker-preflight.js";import{resolveClaimPrincipal}from"../shared/author.js";import{nowIso}from"../shared/time.js";async function fileExists(targetPath){try{return await fs.access(targetPath),!0}catch{return!1}}function isErrno(error,code){return typeof error=="object"&&error!==null&&"code"in error&&error.code===code}function appendWarning(warnings,warning){warnings&&(warnings.includes(warning)||warnings.push(warning))}function resolveActiveExtensionFieldNames(explicit){return explicit??collectRegisteredItemFieldNames(getActiveExtensionRegistrations())}function resolveItemFormatSearchOrder(preferredFormat){return preferredFormat==="toon"?["toon","json_markdown"]:preferredFormat==="json_markdown"?["json_markdown","toon"]:["toon","json_markdown"]}async function locateItem(pmRoot,rawId,idPrefix="pm-",preferredFormat,typeToFolder=TYPE_TO_FOLDER){const trimmedRawId=rawId.trim(),exactRawId=trimmedRawId.startsWith("#")?trimmedRawId.slice(1):trimmedRawId;if(exactRawId.length===0)return null;const normalizedId=normalizeItemId(rawId,idPrefix),rawNormalizedId=normalizeRawItemId(rawId),candidateIds=[...new Set([exactRawId,normalizedId,rawNormalizedId])].filter(candidateId=>candidateId.length>0),entries=Object.entries(typeToFolder),searchOrder=resolveItemFormatSearchOrder(preferredFormat);for(const candidateId of candidateIds){const matches=new Map;for(const[type]of entries)for(const itemFormat of searchOrder){const itemPath=getItemPath(pmRoot,type,candidateId,itemFormat,typeToFolder);await fileExists(itemPath)&&matches.set(itemPath,matches.get(itemPath)??{id:candidateId,type,itemPath,item_format:itemFormat})}if(matches.size>1){const paths=[...matches.keys()].map(itemPath=>path.relative(pmRoot,itemPath).split(path.sep).join("/")).sort();throw new PmCliError(`Item ${candidateId} has ambiguous identity across ${paths.join(", ")}. Preserve both documents and inspect pm ops validate --check-storage-integrity before resolving the collision.`,EXIT_CODE.CONFLICT,{code:"item_identity_ambiguous",item_id:candidateId,paths})}for(const match of matches.values())return match}return null}async function readLocatedItem(item,options={}){const snapshot=await readLocatedItemSnapshot(item,options);if(snapshot.error!==null)throw snapshot.error;return{raw:snapshot.raw,document:snapshot.document}}async function readLocatedItemSnapshot(item,options={}){const bytes=await fs.readFile(item.itemPath);if(!isUtf8(bytes))throw new PmCliError("Item contains invalid UTF-8; preserve a binary backup before recovering it.",EXIT_CODE.CONFLICT,{code:"item_document_encoding_invalid",item_id:item.id,item_path:item.itemPath,byte_length:bytes.length,empty:!1});const raw=bytes.toString("utf8");await runActiveOnReadHooks({path:item.itemPath,scope:"project"});let document;try{document=parseItemDocument(raw,{format:item.item_format,requireSupportedFormat:!0,schema:options.schema,extensionFieldNames:resolveActiveExtensionFieldNames(options.extensionFieldNames),onWarning:warning=>appendWarning(options.warnings,warning)})}catch(error){if(error instanceof PmCliError&&error.code==="item_format_version_unsupported")throw new PmCliError(`Item ${item.id}: ${error.message}`,error.exitCode,{...error.context,item_id:item.id,item_path:item.itemPath});if(error instanceof PmCliError&&error.code==="item_document_invalid")return{raw,document:null,error:new PmCliError(`Cannot read ${item.id} at ${item.itemPath}${bytes.length===0?" (zero-length file)":""}: ${error.message}`,error.exitCode,{...error.context,item_id:item.id,item_path:item.itemPath,byte_length:bytes.length,empty:bytes.length===0,nextSteps:[`Run pm history ${item.id} --full, then pm restore ${item.id} <version> to recover a recorded state.`]})};throw error}return{raw,document,error:null}}async function listAllItemMetadata(pmRoot,preferredFormat,typeToFolder=TYPE_TO_FOLDER,warnings,schema){await assertReadableTrackerRoot(pmRoot);const documents=await listAllDocumentsCached(pmRoot,preferredFormat,typeToFolder,warnings,schema);return recordItemMetadataEnumeration(documents.length),documents.map(document=>document.metadata)}async function listAllItemMetadataLight(pmRoot,preferredFormat,typeToFolder=TYPE_TO_FOLDER,warnings,schema){await assertReadableTrackerRoot(pmRoot);const documents=await listAllDocumentsCachedLight(pmRoot,preferredFormat,typeToFolder,warnings,schema);return recordItemMetadataEnumeration(documents.length),documents.map(document=>document.metadata)}async function listAllItemMetadataWithBody(pmRoot,preferredFormat,typeToFolder=TYPE_TO_FOLDER,warnings,schema,options={}){await assertReadableTrackerRoot(pmRoot);const candidates=await listAllDocumentCandidatesCached(pmRoot,preferredFormat,typeToFolder,warnings,schema,{includeBody:!0,forceSourceScan:options.forceSourceScan});return recordItemMetadataEnumeration(candidates.length),candidates.map(candidate=>({...candidate.metadata,body:candidate.body}))}function createMutationGuardSdk(params){return{async get(id){const located=await locateItem(params.pmRoot,id,params.settings.id_prefix,params.settings.item_format,params.typeToFolder);if(!located)return null;const{document}=await readLocatedItem(located,{schema:params.settings.schema});return{item:document.metadata,body:document.body}},list:()=>listAllItemMetadata(params.pmRoot,params.settings.item_format,params.typeToFolder,void 0,params.settings.schema)}}async function listKnownItemIds(pmRoot,typeToFolder){const folders=new Set(Object.values(typeToFolder)),allIds=[];return await Promise.all([...folders].map(async folder=>{try{const entries=await fs.readdir(path.join(pmRoot,folder));for(const entry of entries)for(const ext of ITEM_FILE_EXTENSIONS)if(entry.toLowerCase().endsWith(ext)){allIds.push(entry.slice(0,-ext.length));break}}catch{}})),allIds}async function buildDidYouMeanSuggestions(pmRoot,badId,idPrefix,typeToFolder){const normalized=normalizeItemId(badId,idPrefix),ids=await listKnownItemIds(pmRoot,typeToFolder);if(ids.length===0)return[];const limit=Math.max(3,Math.floor(normalized.length/2));return ids.map(id=>({id,distance:levenshteinDistanceWithinLimit(id,normalized,limit)})).filter(entry=>entry.distance!==null).sort((left,right)=>left.distance-right.distance).slice(0,3).map(entry=>entry.id)}async function buildItemNotFoundError(pmRoot,badId,idPrefix,typeToFolder){const deleted=await buildDeletedItemError(pmRoot,badId,idPrefix);if(deleted)return deleted;const suggestions=await buildDidYouMeanSuggestions(pmRoot,badId,idPrefix,typeToFolder),nextSteps=["Confirm the active --path/PM_PATH scope, then retry with a valid id."];return suggestions.length>0&&nextSteps.unshift(`Did you mean one of: ${suggestions.join(", ")}?`),new PmCliError(`Item ${badId} not found`,EXIT_CODE.NOT_FOUND,{nextSteps})}function bypassesAssigneeConflict(op,bypassAssigneeConflict){return op==="claim"||bypassAssigneeConflict===!0&&(op==="comment_add"||op==="comment_edit"||op==="comment_delete"||op==="note_add"||op==="learning_add"||op==="release"||op==="update"||op==="update_ownership_bypass"||op==="update_audit")}async function prepareLockedItem(params){const typeToFolder=params.typeToFolder??resolveItemTypeRegistry(params.settings,getActiveExtensionRegistrations()).type_to_folder,located=await locateItem(params.pmRoot,params.id,params.settings.id_prefix,params.settings.item_format,typeToFolder);if(!located)throw await buildItemNotFoundError(params.pmRoot,params.id,params.settings.id_prefix,typeToFolder);const lease=await acquireLockLease(params.pmRoot,located.id,params.settings.locks.ttl_seconds,params.author,!!params.force,params.settings.governance.force_required_for_stale_lock,params.settings.locks.wait_ms,{waitPolicy:"progress"}),releaseLock=lease.release;try{const warnings=[],{raw:originalRaw,document}=await readLocatedItem(located,{schema:params.settings.schema,extensionFieldNames:params.extensionFieldNames,warnings}),assigned=document.metadata.assignee?.trim(),assignedPrincipal=document.metadata.claim_principal?.trim()||assigned,callerPrincipal=resolveClaimPrincipal(params.author),governance=resolveGovernanceKnobs(params.settings);if(assigned&&assignedPrincipal!==callerPrincipal&&!params.force&&!bypassesAssigneeConflict(params.op,params.bypassAssigneeConflict)){if(governance.ownership_enforcement==="strict")throw new PmCliError(`Item ${located.id} is assigned to ${assigned}. Use --force to override.`,EXIT_CODE.CONFLICT);governance.ownership_enforcement==="warn"&&warnings.push(`ownership_warning:assignee_conflict:${located.id}:${assigned}`)}return{typeToFolder,located,originalRaw,document,warnings,releaseLock,lease}}catch(error){throw await releaseLock(),error}}async function commitLockedItemWrite(lease,located,originalRaw,commit){return lease.fence(async()=>{let currentRaw;try{currentRaw=await fs.readFile(located.itemPath,"utf8")}catch(error){if(!isFileMissingError(error))throw error;currentRaw=null}if(currentRaw!==originalRaw)throw new PmCliError(`Item ${located.id} changed after this mutation read it; refusing to overwrite the newer revision. Nothing was written.`,EXIT_CODE.CONFLICT,{code:"stale_revision",item_id:located.id,reason:currentRaw===null?"item_removed":"item_revision_changed",required:"Re-read the item and apply the mutation again; the newer stored revision was kept."});return commit()})}function resolveItemStoreWriteOverride(serviceWriteOverride,targetItemPath,serializedAfter){if(!serviceWriteOverride.handled||typeof serviceWriteOverride.result!="object"||serviceWriteOverride.result===null)return{effectiveTargetItemPath:targetItemPath,effectiveSerializedAfter:serializedAfter,skipItemWrite:!1};const overrideRecord=serviceWriteOverride.result;return{effectiveTargetItemPath:typeof overrideRecord.target_item_path=="string"&&overrideRecord.target_item_path.trim().length>0?overrideRecord.target_item_path:targetItemPath,effectiveSerializedAfter:typeof overrideRecord.contents=="string"?overrideRecord.contents:serializedAfter,skipItemWrite:overrideRecord.skip_write===!0}}async function resolveItemStoreDeleteOverride(payload){const override=await runActiveServiceOverride("item_store_delete",payload),record=override.handled&&typeof override.result=="object"&&override.result!==null?override.result:{};return{effectiveItemPath:typeof record.item_path=="string"&&record.item_path.trim().length>0?record.item_path:payload.item_path,skipDelete:record.skip_delete===!0,warnings:override.warnings}}async function rollbackMutatedItemWrite(params){if(!params.skipItemWrite){if(params.effectiveTargetItemPath!==params.originalItemPath){await writeFileAtomic(params.originalItemPath,params.originalRaw),await fs.rm(params.effectiveTargetItemPath,{force:!0});return}await writeFileAtomic(params.originalItemPath,params.originalRaw)}}async function mutateItemWithDeferredHistoryContext(params){normalizeHistoryOperationForWrite(params.op);const prepared=await prepareLockedItem({pmRoot:params.pmRoot,settings:params.settings,id:params.id,op:params.op,author:params.author,force:params.force,bypassAssigneeConflict:params.bypassAssigneeConflict,extensionFieldNames:params.extensionFieldNames,typeToFolder:params.typeToFolder}),{typeToFolder,located,originalRaw,document,warnings:parseWarnings,releaseLock,lease}=prepared;try{assertItemMutationPreconditions(params.pmRoot,document);const beforeDocument=canonicalDocument(document,{schema:params.settings.schema,extensionFieldNames:params.extensionFieldNames}),mutableDocument=canonicalDocument(structuredClone(document),{schema:params.settings.schema,extensionFieldNames:params.extensionFieldNames}),mutation=await params.mutate(mutableDocument);if(params.skipNoop===!0&&mutation.changedFields.length===0)return{item:beforeDocument.metadata,body:beforeDocument.body,changedFields:[],warnings:[...parseWarnings,...mutation.warnings??[]]};const historyContext={...params.resolveHistoryContext?.(),...checkpointMutationHistoryContext(params.pmRoot,located.id)},requiredFormat=Math.max(requiredItemFormatVersion(beforeDocument.metadata),requiredItemFormatVersion(mutableDocument.metadata));assertProjectRuntimeFormatGuard(resolveWorkspaceRoot(params.pmRoot),requiredFormat),requiredFormat>effectiveItemFormatVersion(mutableDocument.metadata)&&(mutableDocument.metadata.pm_format_version=requiredFormat,mutation.changedFields.push("pm_format_version")),mutableDocument.metadata.updated_at=nowIso();const afterDocument=canonicalDocument(mutableDocument,{schema:params.settings.schema,extensionFieldNames:params.extensionFieldNames}),workflowPolicy=await enforceWorkflowMutation({pmRoot:params.pmRoot,settings:params.settings,operation:params.op,author:params.author,before:beforeDocument,after:afterDocument});await runActiveBeforeMutationHooks({pm_root:params.pmRoot,operation:params.op,before:beforeDocument,after:afterDocument,changed_fields:mutation.changedFields,sdk:createMutationGuardSdk({pmRoot:params.pmRoot,settings:params.settings,typeToFolder})});const historyPolicy=await enforceHistoryStreamPolicyForItem({pmRoot:params.pmRoot,settings:params.settings,itemId:located.id,commandLabel:params.op}),targetItemFormat="toon",serializedAfter=serializeItemDocument(afterDocument,{format:targetItemFormat,schema:params.settings.schema,extensionFieldNames:params.extensionFieldNames}),targetItemPath=getItemPath(params.pmRoot,afterDocument.metadata.type,located.id,targetItemFormat,typeToFolder),historyPath=getHistoryPath(params.pmRoot,located.id),committed=await commitLockedItemWrite(lease,located,originalRaw,async()=>{const serviceWriteOverride=await runActiveServiceOverride("item_store_write",{op:params.op,pm_root:params.pmRoot,item_id:located.id,source_item_path:located.itemPath,target_item_path:targetItemPath,history_path:historyPath,item_format:targetItemFormat,before:beforeDocument,after:afterDocument,contents:serializedAfter}),{effectiveTargetItemPath:effectiveTargetItemPath2,effectiveSerializedAfter,skipItemWrite}=resolveItemStoreWriteOverride(serviceWriteOverride,targetItemPath,serializedAfter),releaseDerivedIndexLock=await acquireItemMetadataDerivedIndexLock(params.pmRoot,params.author);let derivedIndexWarnings2=[];try{skipItemWrite||await writeFileAtomic(effectiveTargetItemPath2,effectiveSerializedAfter),!skipItemWrite&&effectiveTargetItemPath2!==located.itemPath&&await fs.rm(located.itemPath);const entry=createHistoryEntry({nowIso:afterDocument.metadata.updated_at,author:params.author,op:params.op,before:beforeDocument,after:afterDocument,message:params.message,context:workflowPolicy.decisions.length===0?Object.keys(historyContext).length===0?void 0:historyContext:{...historyContext,workflow_policies:workflowPolicy.decisions}});try{await appendHistoryEntry(historyPath,entry)}catch(error){throw await rollbackMutatedItemWrite({skipItemWrite,effectiveTargetItemPath:effectiveTargetItemPath2,originalItemPath:located.itemPath,originalRaw}),error}skipItemWrite||(derivedIndexWarnings2=await refreshItemMetadataDerivedIndex({pmRoot:params.pmRoot,preferredFormat:params.settings.item_format,typeToFolder,schema:params.settings.schema,itemPath:effectiveTargetItemPath2,previousItemPath:located.itemPath,document:afterDocument}))}finally{await releaseDerivedIndexLock()}return{derivedIndexWarnings:derivedIndexWarnings2,effectiveTargetItemPath:effectiveTargetItemPath2,overrideWarnings:serviceWriteOverride.warnings}}),{derivedIndexWarnings,effectiveTargetItemPath}=committed,hookWarnings=[...await runActiveOnWriteHooks({path:effectiveTargetItemPath,scope:"project",op:params.op,item_id:afterDocument.metadata.id,item_type:afterDocument.metadata.type,before:beforeDocument,after:afterDocument,changed_fields:mutation.changedFields}),...await runActiveOnWriteHooks({path:historyPath,scope:"project",op:`${params.op}:history`,item_id:afterDocument.metadata.id,item_type:afterDocument.metadata.type,before:beforeDocument,after:afterDocument,changed_fields:mutation.changedFields})];return recordAfterCommandAffectedItem({id:afterDocument.metadata.id,op:params.op,item_type:afterDocument.metadata.type,previous_status:beforeDocument.metadata.status,status:afterDocument.metadata.status,previous:projectAfterCommandItemSnapshot(beforeDocument.metadata,mutation.changedFields),current:projectAfterCommandItemSnapshot(afterDocument.metadata,mutation.changedFields),changed_fields:mutation.changedFields}),{item:afterDocument.metadata,body:afterDocument.body,changedFields:mutation.changedFields,warnings:[...parseWarnings,...mutation.warnings??[],...workflowPolicy.warnings,...historyPolicy.warnings,...committed.overrideWarnings,...derivedIndexWarnings,...hookWarnings]}}finally{await releaseLock()}}async function mutateItemWithHistoryContext(params){const{historyContext,...mutationParams}=params;return mutateItemWithDeferredHistoryContext({...mutationParams,resolveHistoryContext:()=>historyContext})}async function mutateItemWithHistoryContextResolver(params){return mutateItemWithDeferredHistoryContext(params)}async function mutateItem(params){return mutateItemWithDeferredHistoryContext(params)}const itemStoreTestOnly={appendWarning,bypassesAssigneeConflict,buildDidYouMeanSuggestions,isErrno};async function deleteItem(params){const prepared=await prepareLockedItem({pmRoot:params.pmRoot,settings:params.settings,id:params.id,op:"delete",author:params.author,force:params.force}),{located,originalRaw,document,warnings:parseWarnings,releaseLock,lease}=prepared;try{const extensionFieldNames=resolveActiveExtensionFieldNames(void 0),beforeDocument=canonicalDocument(document,{schema:params.settings.schema,extensionFieldNames}),workflowPolicy=await enforceWorkflowMutation({pmRoot:params.pmRoot,settings:params.settings,operation:"delete",author:params.author,before:beforeDocument,after:null,dryRun:params.dryRun});await runActiveBeforeMutationHooks({pm_root:params.pmRoot,operation:"delete",before:beforeDocument,after:null,changed_fields:["deleted"],sdk:createMutationGuardSdk({pmRoot:params.pmRoot,settings:params.settings,typeToFolder:prepared.typeToFolder})});const historyPolicy=await enforceHistoryStreamPolicyForItem({pmRoot:params.pmRoot,settings:params.settings,itemId:located.id,commandLabel:"delete"}),deletionTimestamp=nowIso(),tombstoneDocument=EMPTY_CANONICAL_DOCUMENT,historyEntry=createHistoryEntry({nowIso:deletionTimestamp,author:params.author,op:"delete",before:beforeDocument,after:tombstoneDocument,message:params.message,context:workflowPolicy.decisions.length===0?void 0:{workflow_policies:workflowPolicy.decisions}}),historyPath=getHistoryPath(params.pmRoot,located.id),runDeleteOverride=()=>resolveItemStoreDeleteOverride({op:"delete",pm_root:params.pmRoot,item_id:located.id,item_path:located.itemPath,history_path:historyPath,before:beforeDocument});if(params.dryRun===!0){const preview=await runDeleteOverride();return{item:beforeDocument.metadata,changedFields:["deleted"],targetPath:preview.effectiveItemPath,warnings:[...parseWarnings,...workflowPolicy.warnings,...historyPolicy.warnings,...preview.warnings]}}const committed=await commitLockedItemWrite(lease,located,originalRaw,async()=>{const{effectiveItemPath:effectiveItemPath2,skipDelete,warnings}=await runDeleteOverride(),releaseDerivedIndexLock=await acquireItemMetadataDerivedIndexLock(params.pmRoot,params.author);let derivedIndexWarnings2=[];try{skipDelete||await fs.rm(effectiveItemPath2);try{await appendHistoryEntry(historyPath,historyEntry)}catch(error){throw skipDelete||await writeFileAtomic(effectiveItemPath2,originalRaw),error}skipDelete||(derivedIndexWarnings2=await refreshItemMetadataDerivedIndex({pmRoot:params.pmRoot,preferredFormat:params.settings.item_format,typeToFolder:prepared.typeToFolder,schema:params.settings.schema,itemPath:effectiveItemPath2,document:null}))}finally{await releaseDerivedIndexLock()}return{derivedIndexWarnings:derivedIndexWarnings2,effectiveItemPath:effectiveItemPath2,overrideWarnings:warnings}}),{derivedIndexWarnings,effectiveItemPath}=committed,hookWarnings=[...await runActiveOnWriteHooks({path:effectiveItemPath,scope:"project",op:"delete",item_id:beforeDocument.metadata.id,item_type:beforeDocument.metadata.type,before:beforeDocument,after:tombstoneDocument,changed_fields:["deleted"]}),...await runActiveOnWriteHooks({path:historyPath,scope:"project",op:"delete:history",item_id:beforeDocument.metadata.id,item_type:beforeDocument.metadata.type,before:beforeDocument,after:tombstoneDocument,changed_fields:["deleted"]})];return recordAfterCommandAffectedItem({id:beforeDocument.metadata.id,op:"delete",item_type:beforeDocument.metadata.type,previous_status:beforeDocument.metadata.status,previous:projectAfterCommandItemSnapshot(beforeDocument.metadata,Object.keys(beforeDocument.metadata)),changed_fields:["deleted"]}),{item:beforeDocument.metadata,changedFields:["deleted"],warnings:[...parseWarnings,...workflowPolicy.warnings,...historyPolicy.warnings,...committed.overrideWarnings,...derivedIndexWarnings,...hookWarnings]}}finally{await releaseLock()}}export{buildItemNotFoundError,createMutationGuardSdk,deleteItem,itemStoreTestOnly,listAllItemMetadata,listAllItemMetadataLight,listAllItemMetadataWithBody,locateItem,mutateItem,mutateItemWithHistoryContext,mutateItemWithHistoryContextResolver,readLocatedItem,readLocatedItemSnapshot};
|
|
1
|
+
(function(){try{var e=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},n=new e.Error().stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="1b26ef78-61ae-53d1-a0c8-f6cf9d3794a0")}catch{}})();import fs from"node:fs/promises";import{buildDeletedItemError}from"../history/tombstone.js";import{normalizeHistoryOperationForWrite}from"../history/operation-contract.js";import{enforceWorkflowMutation}from"../policy/workflow-policy-store.js";import{recordItemMetadataEnumeration}from"./item-metadata-read-work.js";import{advanceItemMutationPreconditions,assertItemMutationPreconditions,itemMutationPreconditionHistoryContext}from"./item-mutation-precondition.js";import{checkpointMutationHistoryContext}from"../checkpoint/mutation-context.js";import path from"node:path";import{getActiveExtensionRegistrations,projectAfterCommandItemSnapshot,recordAfterCommandAffectedItem,runActiveOnReadHooks,runActiveBeforeMutationHooks,runActiveOnWriteHooks,runActiveServiceOverride}from"../extensions/index.js";import{collectRegisteredItemFieldNames}from"../extensions/item-fields.js";import{EMPTY_CANONICAL_DOCUMENT,EXIT_CODE,TYPE_TO_FOLDER}from"../shared/constants.js";import{PmCliError}from"../shared/errors.js";import{levenshteinDistanceWithinLimit}from"../shared/levenshtein.js";import{appendHistoryEntry,createHistoryEntry}from"../history/history.js";import{enforceHistoryStreamPolicyForItem}from"../history/history-stream-policy.js";import{canonicalDocument,parseItemDocument,serializeItemDocument}from"../item/item-format.js";import{resolveItemTypeRegistry}from"../item/type-registry.js";import{effectiveItemFormatVersion,requiredItemFormatVersion}from"../item/item-format-version.js";import{assertProjectRuntimeFormatGuard}from"../../sdk/environment/project-runtime-compatibility.js";import{acquireLockLease}from"../lock/lock.js";import{isFileMissingError,writeFileAtomic}from"../fs/fs-utils.js";import{isUtf8}from"node:buffer";import{normalizeItemId,normalizeRawItemId}from"../item/id.js";import{acquireItemMetadataDerivedIndexLock,listAllDocumentCandidatesCached,listAllDocumentsCached,listAllDocumentsCachedLight,refreshItemMetadataDerivedIndex}from"./item-metadata-cache.js";import{getHistoryPath,getItemPath,ITEM_FILE_EXTENSIONS,resolveWorkspaceRoot}from"./paths.js";import{resolveGovernanceKnobs}from"./settings.js";import{assertReadableTrackerRoot}from"./tracker-preflight.js";import{resolveClaimPrincipal}from"../shared/author.js";import{nowIso}from"../shared/time.js";async function fileExists(targetPath){try{return await fs.access(targetPath),!0}catch{return!1}}function isErrno(error,code){return typeof error=="object"&&error!==null&&"code"in error&&error.code===code}function appendWarning(warnings,warning){warnings&&(warnings.includes(warning)||warnings.push(warning))}function resolveActiveExtensionFieldNames(explicit){return explicit??collectRegisteredItemFieldNames(getActiveExtensionRegistrations())}function resolveItemFormatSearchOrder(preferredFormat){return preferredFormat==="toon"?["toon","json_markdown"]:preferredFormat==="json_markdown"?["json_markdown","toon"]:["toon","json_markdown"]}async function locateItem(pmRoot,rawId,idPrefix="pm-",preferredFormat,typeToFolder=TYPE_TO_FOLDER){const trimmedRawId=rawId.trim(),exactRawId=trimmedRawId.startsWith("#")?trimmedRawId.slice(1):trimmedRawId;if(exactRawId.length===0)return null;const normalizedId=normalizeItemId(rawId,idPrefix),rawNormalizedId=normalizeRawItemId(rawId),candidateIds=[...new Set([exactRawId,normalizedId,rawNormalizedId])].filter(candidateId=>candidateId.length>0),entries=Object.entries(typeToFolder),searchOrder=resolveItemFormatSearchOrder(preferredFormat);for(const candidateId of candidateIds){const matches=new Map;for(const[type]of entries)for(const itemFormat of searchOrder){const itemPath=getItemPath(pmRoot,type,candidateId,itemFormat,typeToFolder);await fileExists(itemPath)&&matches.set(itemPath,matches.get(itemPath)??{id:candidateId,type,itemPath,item_format:itemFormat})}if(matches.size>1){const paths=[...matches.keys()].map(itemPath=>path.relative(pmRoot,itemPath).split(path.sep).join("/")).sort();throw new PmCliError(`Item ${candidateId} has ambiguous identity across ${paths.join(", ")}. Preserve both documents and inspect pm ops validate --check-storage-integrity before resolving the collision.`,EXIT_CODE.CONFLICT,{code:"item_identity_ambiguous",item_id:candidateId,paths})}for(const match of matches.values())return match}return null}async function readLocatedItem(item,options={}){const snapshot=await readLocatedItemSnapshot(item,options);if(snapshot.error!==null)throw snapshot.error;return{raw:snapshot.raw,document:snapshot.document}}async function readLocatedItemSnapshot(item,options={}){const bytes=await fs.readFile(item.itemPath);if(!isUtf8(bytes))throw new PmCliError("Item contains invalid UTF-8; preserve a binary backup before recovering it.",EXIT_CODE.CONFLICT,{code:"item_document_encoding_invalid",item_id:item.id,item_path:item.itemPath,byte_length:bytes.length,empty:!1});const raw=bytes.toString("utf8");await runActiveOnReadHooks({path:item.itemPath,scope:"project"});let document;try{document=parseItemDocument(raw,{format:item.item_format,requireSupportedFormat:!0,schema:options.schema,extensionFieldNames:resolveActiveExtensionFieldNames(options.extensionFieldNames),onWarning:warning=>appendWarning(options.warnings,warning)})}catch(error){if(error instanceof PmCliError&&error.code==="item_format_version_unsupported")throw new PmCliError(`Item ${item.id}: ${error.message}`,error.exitCode,{...error.context,item_id:item.id,item_path:item.itemPath});if(error instanceof PmCliError&&error.code==="item_document_invalid")return{raw,document:null,error:new PmCliError(`Cannot read ${item.id} at ${item.itemPath}${bytes.length===0?" (zero-length file)":""}: ${error.message}`,error.exitCode,{...error.context,item_id:item.id,item_path:item.itemPath,byte_length:bytes.length,empty:bytes.length===0,nextSteps:[`Run pm history ${item.id} --full, then pm restore ${item.id} <version> to recover a recorded state.`]})};throw error}return{raw,document,error:null}}async function listAllItemMetadata(pmRoot,preferredFormat,typeToFolder=TYPE_TO_FOLDER,warnings,schema){await assertReadableTrackerRoot(pmRoot);const documents=await listAllDocumentsCached(pmRoot,preferredFormat,typeToFolder,warnings,schema);return recordItemMetadataEnumeration(documents.length),documents.map(document=>document.metadata)}async function listAllItemMetadataLight(pmRoot,preferredFormat,typeToFolder=TYPE_TO_FOLDER,warnings,schema){await assertReadableTrackerRoot(pmRoot);const documents=await listAllDocumentsCachedLight(pmRoot,preferredFormat,typeToFolder,warnings,schema);return recordItemMetadataEnumeration(documents.length),documents.map(document=>document.metadata)}async function listAllItemMetadataWithBody(pmRoot,preferredFormat,typeToFolder=TYPE_TO_FOLDER,warnings,schema,options={}){await assertReadableTrackerRoot(pmRoot);const candidates=await listAllDocumentCandidatesCached(pmRoot,preferredFormat,typeToFolder,warnings,schema,{includeBody:!0,forceSourceScan:options.forceSourceScan});return recordItemMetadataEnumeration(candidates.length),candidates.map(candidate=>({...candidate.metadata,body:candidate.body}))}function createMutationGuardSdk(params){return{async get(id){const located=await locateItem(params.pmRoot,id,params.settings.id_prefix,params.settings.item_format,params.typeToFolder);if(!located)return null;const{document}=await readLocatedItem(located,{schema:params.settings.schema});return{item:document.metadata,body:document.body}},list:()=>listAllItemMetadata(params.pmRoot,params.settings.item_format,params.typeToFolder,void 0,params.settings.schema)}}async function listKnownItemIds(pmRoot,typeToFolder){const folders=new Set(Object.values(typeToFolder)),allIds=[];return await Promise.all([...folders].map(async folder=>{try{const entries=await fs.readdir(path.join(pmRoot,folder));for(const entry of entries)for(const ext of ITEM_FILE_EXTENSIONS)if(entry.toLowerCase().endsWith(ext)){allIds.push(entry.slice(0,-ext.length));break}}catch{}})),allIds}async function buildDidYouMeanSuggestions(pmRoot,badId,idPrefix,typeToFolder){const normalized=normalizeItemId(badId,idPrefix),ids=await listKnownItemIds(pmRoot,typeToFolder);if(ids.length===0)return[];const limit=Math.max(3,Math.floor(normalized.length/2));return ids.map(id=>({id,distance:levenshteinDistanceWithinLimit(id,normalized,limit)})).filter(entry=>entry.distance!==null).sort((left,right)=>left.distance-right.distance).slice(0,3).map(entry=>entry.id)}async function buildItemNotFoundError(pmRoot,badId,idPrefix,typeToFolder){const deleted=await buildDeletedItemError(pmRoot,badId,idPrefix);if(deleted)return deleted;const suggestions=await buildDidYouMeanSuggestions(pmRoot,badId,idPrefix,typeToFolder),nextSteps=["Confirm the active --path/PM_PATH scope, then retry with a valid id."];return suggestions.length>0&&nextSteps.unshift(`Did you mean one of: ${suggestions.join(", ")}?`),new PmCliError(`Item ${badId} not found`,EXIT_CODE.NOT_FOUND,{nextSteps})}function bypassesAssigneeConflict(op,bypassAssigneeConflict){return op==="claim"||bypassAssigneeConflict===!0&&(op==="comment_add"||op==="comment_edit"||op==="comment_delete"||op==="note_add"||op==="learning_add"||op==="release"||op==="update"||op==="update_ownership_bypass"||op==="update_audit")}async function prepareLockedItem(params){const typeToFolder=params.typeToFolder??resolveItemTypeRegistry(params.settings,getActiveExtensionRegistrations()).type_to_folder,located=await locateItem(params.pmRoot,params.id,params.settings.id_prefix,params.settings.item_format,typeToFolder);if(!located)throw await buildItemNotFoundError(params.pmRoot,params.id,params.settings.id_prefix,typeToFolder);const lease=await acquireLockLease(params.pmRoot,located.id,params.settings.locks.ttl_seconds,params.author,!!params.force,params.settings.governance.force_required_for_stale_lock,params.settings.locks.wait_ms,{waitPolicy:"progress"}),releaseLock=lease.release;try{const warnings=[],{raw:originalRaw,document}=await readLocatedItem(located,{schema:params.settings.schema,extensionFieldNames:params.extensionFieldNames,warnings}),assigned=document.metadata.assignee?.trim(),assignedPrincipal=document.metadata.claim_principal?.trim()||assigned,callerPrincipal=resolveClaimPrincipal(params.author),governance=resolveGovernanceKnobs(params.settings);if(assigned&&assignedPrincipal!==callerPrincipal&&!params.force&&!bypassesAssigneeConflict(params.op,params.bypassAssigneeConflict)){if(governance.ownership_enforcement==="strict")throw new PmCliError(`Item ${located.id} is assigned to ${assigned}. Use --force to override.`,EXIT_CODE.CONFLICT);governance.ownership_enforcement==="warn"&&warnings.push(`ownership_warning:assignee_conflict:${located.id}:${assigned}`)}return{typeToFolder,located,originalRaw,document,warnings,releaseLock,lease}}catch(error){throw await releaseLock(),error}}async function commitLockedItemWrite(lease,located,originalRaw,commit){return lease.fence(async()=>{let currentRaw;try{currentRaw=await fs.readFile(located.itemPath,"utf8")}catch(error){if(!isFileMissingError(error))throw error;currentRaw=null}if(currentRaw!==originalRaw)throw new PmCliError(`Item ${located.id} changed after this mutation read it; refusing to overwrite the newer revision. Nothing was written.`,EXIT_CODE.CONFLICT,{code:"stale_revision",item_id:located.id,reason:currentRaw===null?"item_removed":"item_revision_changed",required:"Re-read the item and apply the mutation again; the newer stored revision was kept."});return commit()})}function resolveItemStoreWriteOverride(serviceWriteOverride,targetItemPath,serializedAfter){if(!serviceWriteOverride.handled||typeof serviceWriteOverride.result!="object"||serviceWriteOverride.result===null)return{effectiveTargetItemPath:targetItemPath,effectiveSerializedAfter:serializedAfter,skipItemWrite:!1};const overrideRecord=serviceWriteOverride.result;return{effectiveTargetItemPath:typeof overrideRecord.target_item_path=="string"&&overrideRecord.target_item_path.trim().length>0?overrideRecord.target_item_path:targetItemPath,effectiveSerializedAfter:typeof overrideRecord.contents=="string"?overrideRecord.contents:serializedAfter,skipItemWrite:overrideRecord.skip_write===!0}}async function resolveItemStoreDeleteOverride(payload){const override=await runActiveServiceOverride("item_store_delete",payload),record=override.handled&&typeof override.result=="object"&&override.result!==null?override.result:{};return{effectiveItemPath:typeof record.item_path=="string"&&record.item_path.trim().length>0?record.item_path:payload.item_path,skipDelete:record.skip_delete===!0,warnings:override.warnings}}async function rollbackMutatedItemWrite(params){if(!params.skipItemWrite){if(params.effectiveTargetItemPath!==params.originalItemPath){await writeFileAtomic(params.originalItemPath,params.originalRaw),await fs.rm(params.effectiveTargetItemPath,{force:!0});return}await writeFileAtomic(params.originalItemPath,params.originalRaw)}}async function mutateItemWithDeferredHistoryContext(params){normalizeHistoryOperationForWrite(params.op);const prepared=await prepareLockedItem({pmRoot:params.pmRoot,settings:params.settings,id:params.id,op:params.op,author:params.author,force:params.force,bypassAssigneeConflict:params.bypassAssigneeConflict,extensionFieldNames:params.extensionFieldNames,typeToFolder:params.typeToFolder}),{typeToFolder,located,originalRaw,document,warnings:parseWarnings,releaseLock,lease}=prepared;try{assertItemMutationPreconditions(params.pmRoot,document);const beforeDocument=canonicalDocument(document,{schema:params.settings.schema,extensionFieldNames:params.extensionFieldNames}),mutableDocument=canonicalDocument(structuredClone(document),{schema:params.settings.schema,extensionFieldNames:params.extensionFieldNames}),mutation=await params.mutate(mutableDocument);if(params.skipNoop===!0&&mutation.changedFields.length===0)return{item:beforeDocument.metadata,body:beforeDocument.body,changedFields:[],warnings:[...parseWarnings,...mutation.warnings??[]]};const historyContext={...params.resolveHistoryContext?.(),...checkpointMutationHistoryContext(params.pmRoot,located.id),...itemMutationPreconditionHistoryContext(params.pmRoot,document)},requiredFormat=Math.max(requiredItemFormatVersion(beforeDocument.metadata),requiredItemFormatVersion(mutableDocument.metadata));assertProjectRuntimeFormatGuard(resolveWorkspaceRoot(params.pmRoot),requiredFormat),requiredFormat>effectiveItemFormatVersion(mutableDocument.metadata)&&(mutableDocument.metadata.pm_format_version=requiredFormat,mutation.changedFields.push("pm_format_version")),mutableDocument.metadata.updated_at=nowIso();const afterDocument=canonicalDocument(mutableDocument,{schema:params.settings.schema,extensionFieldNames:params.extensionFieldNames}),workflowPolicy=await enforceWorkflowMutation({pmRoot:params.pmRoot,settings:params.settings,operation:params.op,author:params.author,before:beforeDocument,after:afterDocument});await runActiveBeforeMutationHooks({pm_root:params.pmRoot,operation:params.op,before:beforeDocument,after:afterDocument,changed_fields:mutation.changedFields,sdk:createMutationGuardSdk({pmRoot:params.pmRoot,settings:params.settings,typeToFolder})});const historyPolicy=await enforceHistoryStreamPolicyForItem({pmRoot:params.pmRoot,settings:params.settings,itemId:located.id,commandLabel:params.op}),targetItemFormat="toon",serializedAfter=serializeItemDocument(afterDocument,{format:targetItemFormat,schema:params.settings.schema,extensionFieldNames:params.extensionFieldNames}),targetItemPath=getItemPath(params.pmRoot,afterDocument.metadata.type,located.id,targetItemFormat,typeToFolder),historyPath=getHistoryPath(params.pmRoot,located.id),committed=await commitLockedItemWrite(lease,located,originalRaw,async()=>{const serviceWriteOverride=await runActiveServiceOverride("item_store_write",{op:params.op,pm_root:params.pmRoot,item_id:located.id,source_item_path:located.itemPath,target_item_path:targetItemPath,history_path:historyPath,item_format:targetItemFormat,before:beforeDocument,after:afterDocument,contents:serializedAfter}),{effectiveTargetItemPath:effectiveTargetItemPath2,effectiveSerializedAfter,skipItemWrite}=resolveItemStoreWriteOverride(serviceWriteOverride,targetItemPath,serializedAfter),releaseDerivedIndexLock=await acquireItemMetadataDerivedIndexLock(params.pmRoot,params.author);let derivedIndexWarnings2=[];try{skipItemWrite||await writeFileAtomic(effectiveTargetItemPath2,effectiveSerializedAfter),!skipItemWrite&&effectiveTargetItemPath2!==located.itemPath&&await fs.rm(located.itemPath);const entry=createHistoryEntry({nowIso:afterDocument.metadata.updated_at,author:params.author,op:params.op,before:beforeDocument,after:afterDocument,message:params.message,context:workflowPolicy.decisions.length===0?Object.keys(historyContext).length===0?void 0:historyContext:{...historyContext,workflow_policies:workflowPolicy.decisions}});try{await appendHistoryEntry(historyPath,entry)}catch(error){throw await rollbackMutatedItemWrite({skipItemWrite,effectiveTargetItemPath:effectiveTargetItemPath2,originalItemPath:located.itemPath,originalRaw}),error}skipItemWrite||(derivedIndexWarnings2=await refreshItemMetadataDerivedIndex({pmRoot:params.pmRoot,preferredFormat:params.settings.item_format,typeToFolder,schema:params.settings.schema,itemPath:effectiveTargetItemPath2,previousItemPath:located.itemPath,document:afterDocument}))}finally{await releaseDerivedIndexLock()}return{derivedIndexWarnings:derivedIndexWarnings2,effectiveTargetItemPath:effectiveTargetItemPath2,overrideWarnings:serviceWriteOverride.warnings}}),{derivedIndexWarnings,effectiveTargetItemPath}=committed;advanceItemMutationPreconditions(params.pmRoot,afterDocument);const hookWarnings=[...await runActiveOnWriteHooks({path:effectiveTargetItemPath,scope:"project",op:params.op,item_id:afterDocument.metadata.id,item_type:afterDocument.metadata.type,before:beforeDocument,after:afterDocument,changed_fields:mutation.changedFields}),...await runActiveOnWriteHooks({path:historyPath,scope:"project",op:`${params.op}:history`,item_id:afterDocument.metadata.id,item_type:afterDocument.metadata.type,before:beforeDocument,after:afterDocument,changed_fields:mutation.changedFields})];return recordAfterCommandAffectedItem({id:afterDocument.metadata.id,op:params.op,item_type:afterDocument.metadata.type,previous_status:beforeDocument.metadata.status,status:afterDocument.metadata.status,previous:projectAfterCommandItemSnapshot(beforeDocument.metadata,mutation.changedFields),current:projectAfterCommandItemSnapshot(afterDocument.metadata,mutation.changedFields),changed_fields:mutation.changedFields}),{item:afterDocument.metadata,body:afterDocument.body,changedFields:mutation.changedFields,warnings:[...parseWarnings,...mutation.warnings??[],...workflowPolicy.warnings,...historyPolicy.warnings,...committed.overrideWarnings,...derivedIndexWarnings,...hookWarnings]}}finally{await releaseLock()}}async function mutateItemWithHistoryContext(params){const{historyContext,...mutationParams}=params;return mutateItemWithDeferredHistoryContext({...mutationParams,resolveHistoryContext:()=>historyContext})}async function mutateItemWithHistoryContextResolver(params){return mutateItemWithDeferredHistoryContext(params)}async function mutateItem(params){return mutateItemWithDeferredHistoryContext(params)}const itemStoreTestOnly={appendWarning,bypassesAssigneeConflict,buildDidYouMeanSuggestions,isErrno};async function deleteItem(params){const prepared=await prepareLockedItem({pmRoot:params.pmRoot,settings:params.settings,id:params.id,op:"delete",author:params.author,force:params.force}),{located,originalRaw,document,warnings:parseWarnings,releaseLock,lease}=prepared;try{const extensionFieldNames=resolveActiveExtensionFieldNames(void 0),beforeDocument=canonicalDocument(document,{schema:params.settings.schema,extensionFieldNames}),workflowPolicy=await enforceWorkflowMutation({pmRoot:params.pmRoot,settings:params.settings,operation:"delete",author:params.author,before:beforeDocument,after:null,dryRun:params.dryRun});await runActiveBeforeMutationHooks({pm_root:params.pmRoot,operation:"delete",before:beforeDocument,after:null,changed_fields:["deleted"],sdk:createMutationGuardSdk({pmRoot:params.pmRoot,settings:params.settings,typeToFolder:prepared.typeToFolder})});const historyPolicy=await enforceHistoryStreamPolicyForItem({pmRoot:params.pmRoot,settings:params.settings,itemId:located.id,commandLabel:"delete"}),deletionTimestamp=nowIso(),tombstoneDocument=EMPTY_CANONICAL_DOCUMENT,historyEntry=createHistoryEntry({nowIso:deletionTimestamp,author:params.author,op:"delete",before:beforeDocument,after:tombstoneDocument,message:params.message,context:workflowPolicy.decisions.length===0?void 0:{workflow_policies:workflowPolicy.decisions}}),historyPath=getHistoryPath(params.pmRoot,located.id),runDeleteOverride=()=>resolveItemStoreDeleteOverride({op:"delete",pm_root:params.pmRoot,item_id:located.id,item_path:located.itemPath,history_path:historyPath,before:beforeDocument});if(params.dryRun===!0){const preview=await runDeleteOverride();return{item:beforeDocument.metadata,changedFields:["deleted"],targetPath:preview.effectiveItemPath,warnings:[...parseWarnings,...workflowPolicy.warnings,...historyPolicy.warnings,...preview.warnings]}}const committed=await commitLockedItemWrite(lease,located,originalRaw,async()=>{const{effectiveItemPath:effectiveItemPath2,skipDelete,warnings}=await runDeleteOverride(),releaseDerivedIndexLock=await acquireItemMetadataDerivedIndexLock(params.pmRoot,params.author);let derivedIndexWarnings2=[];try{skipDelete||await fs.rm(effectiveItemPath2);try{await appendHistoryEntry(historyPath,historyEntry)}catch(error){throw skipDelete||await writeFileAtomic(effectiveItemPath2,originalRaw),error}skipDelete||(derivedIndexWarnings2=await refreshItemMetadataDerivedIndex({pmRoot:params.pmRoot,preferredFormat:params.settings.item_format,typeToFolder:prepared.typeToFolder,schema:params.settings.schema,itemPath:effectiveItemPath2,document:null}))}finally{await releaseDerivedIndexLock()}return{derivedIndexWarnings:derivedIndexWarnings2,effectiveItemPath:effectiveItemPath2,overrideWarnings:warnings}}),{derivedIndexWarnings,effectiveItemPath}=committed,hookWarnings=[...await runActiveOnWriteHooks({path:effectiveItemPath,scope:"project",op:"delete",item_id:beforeDocument.metadata.id,item_type:beforeDocument.metadata.type,before:beforeDocument,after:tombstoneDocument,changed_fields:["deleted"]}),...await runActiveOnWriteHooks({path:historyPath,scope:"project",op:"delete:history",item_id:beforeDocument.metadata.id,item_type:beforeDocument.metadata.type,before:beforeDocument,after:tombstoneDocument,changed_fields:["deleted"]})];return recordAfterCommandAffectedItem({id:beforeDocument.metadata.id,op:"delete",item_type:beforeDocument.metadata.type,previous_status:beforeDocument.metadata.status,previous:projectAfterCommandItemSnapshot(beforeDocument.metadata,Object.keys(beforeDocument.metadata)),changed_fields:["deleted"]}),{item:beforeDocument.metadata,changedFields:["deleted"],warnings:[...parseWarnings,...workflowPolicy.warnings,...historyPolicy.warnings,...committed.overrideWarnings,...derivedIndexWarnings,...hookWarnings]}}finally{await releaseLock()}}export{buildItemNotFoundError,createMutationGuardSdk,deleteItem,itemStoreTestOnly,listAllItemMetadata,listAllItemMetadataLight,listAllItemMetadataWithBody,locateItem,mutateItem,mutateItemWithHistoryContext,mutateItemWithHistoryContextResolver,readLocatedItem,readLocatedItemSnapshot};
|
|
2
2
|
//# sourceMappingURL=item-store.js.map
|