@unbrained/pm-cli 2026.9.3 → 2026.9.5

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