@unbrained/pm-cli 2026.9.2 → 2026.9.4

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 (165) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +44 -0
  3. package/dist/cli/after-command-context-usage.js +16 -3
  4. package/dist/cli-bundle/bundle-manifest.json +151 -151
  5. package/dist/cli-bundle/chunks/chunk-6XSKPNCF.js +230 -0
  6. package/dist/cli-bundle/chunks/chunk-A24WAVE6.js +8 -0
  7. package/dist/cli-bundle/chunks/{chunk-TAG4VC22.js → chunk-HI6BHPCU.js} +27 -27
  8. package/dist/cli-bundle/chunks/{chunk-Y7VOMSZT.js → chunk-I2AS367G.js} +2 -2
  9. package/dist/cli-bundle/chunks/{chunk-X4RCGLTX.js → chunk-OGBVU67G.js} +5 -5
  10. package/dist/cli-bundle/chunks/{chunk-DTGWEL23.js → chunk-RI4XNOYS.js} +2 -2
  11. package/dist/cli-bundle/chunks/chunk-TXW6VL33.js +38 -0
  12. package/dist/cli-bundle/chunks/{chunk-FZ4Z2J4F.js → chunk-V72UV7QS.js} +76 -79
  13. package/dist/cli-bundle/chunks/chunk-VGLHECAP.js +9 -0
  14. package/dist/cli-bundle/chunks/{chunk-T6H5BZCP.js → chunk-X6CMGV6L.js} +2 -2
  15. package/dist/cli-bundle/chunks/{chunk-3WICXLVT.js → chunk-YW4QK2G7.js} +2 -2
  16. package/dist/cli-bundle/chunks/{register-list-query-XYNX4ASQ.js → register-list-query-Z5YYSJJG.js} +2 -2
  17. package/dist/cli-bundle/chunks/{register-mutation-WFQ7MJTX.js → register-mutation-ELSP6I4I.js} +2 -2
  18. package/dist/cli-bundle/chunks/{register-operations-JVTI7JPP.js → register-operations-OH7R3ZT4.js} +2 -2
  19. package/dist/cli-bundle/chunks/{register-setup-KVNC5KPL.js → register-setup-E2AE72QG.js} +2 -2
  20. package/dist/cli-bundle/focused-chunks/{chunk-MEASX544.js → chunk-25UJBH4M.js} +2 -2
  21. package/dist/cli-bundle/focused-chunks/{chunk-SVXJYD6G.js → chunk-3646BZ4W.js} +2 -2
  22. package/dist/cli-bundle/focused-chunks/chunk-4YRAVI43.js +12 -0
  23. package/dist/cli-bundle/focused-chunks/chunk-53IHPICX.js +3 -0
  24. package/dist/cli-bundle/focused-chunks/chunk-5P4HVOWG.js +176 -0
  25. package/dist/cli-bundle/focused-chunks/chunk-636VVQ7F.js +54 -0
  26. package/dist/cli-bundle/focused-chunks/{chunk-4O4CJAR6.js → chunk-6PWPQKDN.js} +2 -2
  27. package/dist/cli-bundle/focused-chunks/chunk-7N2TV7WZ.js +8 -0
  28. package/dist/cli-bundle/focused-chunks/chunk-AJR3OP2E.js +3 -0
  29. package/dist/cli-bundle/focused-chunks/chunk-B3A6MJYE.js +2 -0
  30. package/dist/cli-bundle/focused-chunks/chunk-BFDDJ2T2.js +2 -0
  31. package/dist/cli-bundle/focused-chunks/chunk-BHUIRZWQ.js +9 -0
  32. package/dist/cli-bundle/focused-chunks/chunk-JADHHPY6.js +6 -0
  33. package/dist/cli-bundle/focused-chunks/{chunk-KB5OFSFD.js → chunk-MHKUIPJW.js} +2 -2
  34. package/dist/cli-bundle/focused-chunks/chunk-NBMJD6MK.js +2 -0
  35. package/dist/cli-bundle/focused-chunks/chunk-NSKBLHVI.js +20 -0
  36. package/dist/cli-bundle/focused-chunks/{chunk-BEVHCEWG.js → chunk-P2GOAOQN.js} +2 -2
  37. package/dist/cli-bundle/focused-chunks/chunk-YARWJM2B.js +2 -0
  38. package/dist/cli-bundle/main.js +15 -15
  39. package/dist/cli-bundle/sdk-authoring.js +1 -1
  40. package/dist/cli-bundle/sdk-contracts.js +1 -1
  41. package/dist/cli-bundle/sdk-core.js +28 -31
  42. package/dist/cli-bundle/sdk-governance.js +1 -1
  43. package/dist/cli-bundle/sdk-graph.js +1 -1
  44. package/dist/cli-bundle/sdk-merge.js +43 -36
  45. package/dist/cli-bundle/sdk-query.js +1 -1
  46. package/dist/cli-bundle/sdk-runtime.js +1 -1
  47. package/dist/cli-bundle/sdk-testing.js +1 -1
  48. package/dist/cli-bundle/sdk.js +10 -10
  49. package/dist/core/diagnostics/remediation.js +4 -4
  50. package/dist/core/history/event-classification.d.ts +21 -0
  51. package/dist/core/history/event-classification.js +109 -0
  52. package/dist/core/history/event-index.d.ts +14 -3
  53. package/dist/core/history/event-index.js +543 -88
  54. package/dist/core/history/history.d.ts +37 -1
  55. package/dist/core/history/history.js +320 -71
  56. package/dist/core/history/replay.js +68 -34
  57. package/dist/core/history/workspace-history.js +7 -6
  58. package/dist/core/lock/lock.d.ts +32 -2
  59. package/dist/core/lock/lock.js +161 -20
  60. package/dist/core/output/output.js +31 -7
  61. package/dist/core/shared/author.d.ts +3 -1
  62. package/dist/core/shared/author.js +111 -23
  63. package/dist/core/shared/time.d.ts +2 -0
  64. package/dist/core/shared/time.js +8 -2
  65. package/dist/core/store/item-metadata-query-index.js +5 -5
  66. package/dist/sdk/context/recency.d.ts +9 -0
  67. package/dist/sdk/context/recency.js +34 -0
  68. package/dist/sdk/context/usage-egress.d.ts +9 -0
  69. package/dist/sdk/context/usage-egress.js +30 -0
  70. package/dist/sdk/context-intent-contracts.js +7 -3
  71. package/dist/sdk/context-relevance.d.ts +23 -0
  72. package/dist/sdk/context-relevance.js +94 -23
  73. package/dist/sdk/context-signal-store.d.ts +7 -3
  74. package/dist/sdk/context-signal-store.js +155 -42
  75. package/dist/sdk/context-usage.d.ts +54 -0
  76. package/dist/sdk/context-usage.js +245 -16
  77. package/dist/sdk/environment/agent-environment.d.ts +296 -0
  78. package/dist/sdk/environment/agent-environment.js +467 -0
  79. package/dist/sdk/environment/project-runtime-compatibility.d.ts +19 -1
  80. package/dist/sdk/environment/project-runtime-compatibility.js +118 -21
  81. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +14 -2
  82. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +14 -2
  83. package/dist/sdk/governance/health.js +13 -44
  84. package/dist/sdk/governance/merge-receipt-history.d.ts +62 -0
  85. package/dist/sdk/governance/merge-receipt-history.js +207 -0
  86. package/dist/sdk/governance/provenance-health.js +9 -3
  87. package/dist/sdk/history-compact.js +14 -5
  88. package/dist/sdk/history-redact.js +58 -23
  89. package/dist/sdk/history-repair.js +16 -6
  90. package/dist/sdk/index.d.ts +4 -0
  91. package/dist/sdk/index.js +6 -2
  92. package/dist/sdk/init.d.ts +2 -0
  93. package/dist/sdk/init.js +18 -9
  94. package/dist/sdk/merge/driver.d.ts +3 -1
  95. package/dist/sdk/merge/driver.js +9 -9
  96. package/dist/sdk/merge/index.d.ts +1 -1
  97. package/dist/sdk/merge/index.js +3 -3
  98. package/dist/sdk/merge/receipts.d.ts +17 -6
  99. package/dist/sdk/merge/receipts.js +246 -75
  100. package/dist/sdk/merge/reconcile.d.ts +9 -7
  101. package/dist/sdk/merge/reconcile.js +175 -12
  102. package/dist/sdk/merge/three-way.d.ts +22 -2
  103. package/dist/sdk/merge/three-way.js +92 -25
  104. package/dist/sdk/query/context.d.ts +3 -1
  105. package/dist/sdk/query/context.js +21 -9
  106. package/dist/sdk/query/next.js +18 -9
  107. package/dist/sdk/runtime-input.d.ts +2 -0
  108. package/dist/sdk/runtime-input.js +25 -2
  109. package/dist/sdk/runtime-primitives.d.ts +2 -2
  110. package/dist/sdk/runtime-primitives.js +4 -4
  111. package/dist/sdk/runtime.d.ts +1 -0
  112. package/dist/sdk/runtime.js +17 -26
  113. package/dist/types.d.ts +27 -0
  114. package/dist/types.js +2 -2
  115. package/docs/AGENT_ENVIRONMENTS.md +47 -0
  116. package/docs/AGENT_PROVENANCE_ADR.md +17 -2
  117. package/docs/CONTEXT_INTEGRITY_CONTRACTS.md +38 -1
  118. package/docs/CONTEXT_RELEVANCE.md +44 -1
  119. package/docs/MERGE_SAFETY.md +119 -23
  120. package/docs/PERFORMANCE.md +6 -3
  121. package/docs/README.md +1 -0
  122. package/docs/READ_OUTPUT_CONTRACTS.md +9 -1
  123. package/docs/SDK.md +11 -8
  124. package/docs/SDK_CONTEXT.md +7 -0
  125. package/docs/SDK_CONTEXT_EVIDENCE_CONTRACTS.md +24 -2
  126. package/docs/SDK_RUNTIME_BOUNDARIES.md +17 -1
  127. package/docs/examples/sdk-agent-environment/README.md +14 -0
  128. package/docs/examples/sdk-agent-environment/package.json +8 -0
  129. package/docs/examples/sdk-agent-environment/run-agent-environment.mjs +154 -0
  130. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +5 -3
  131. package/marketplace.json +2 -2
  132. package/package.json +5 -5
  133. package/packages/pm-beads/package.json +1 -1
  134. package/packages/pm-calendar/package.json +1 -1
  135. package/packages/pm-command-kit/package.json +1 -1
  136. package/packages/pm-digital-twin/package.json +1 -1
  137. package/packages/pm-governance-audit/package.json +1 -1
  138. package/packages/pm-guide-shell/package.json +1 -1
  139. package/packages/pm-kanban/package.json +1 -1
  140. package/packages/pm-lifecycle-hooks/package.json +1 -1
  141. package/packages/pm-linked-test-adapters/package.json +1 -1
  142. package/packages/pm-search-advanced/package.json +1 -1
  143. package/packages/pm-templates/package.json +1 -1
  144. package/packages/pm-todos/package.json +1 -1
  145. package/packages/pm-vcs/package.json +1 -1
  146. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  147. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  148. package/sdk/public-surface.json +773 -46
  149. package/dist/cli-bundle/chunks/chunk-GOACULJW.js +0 -13
  150. package/dist/cli-bundle/chunks/chunk-NOPL37X4.js +0 -205
  151. package/dist/cli-bundle/chunks/chunk-UDW64J2W.js +0 -8
  152. package/dist/cli-bundle/chunks/chunk-ZLJIFSOE.js +0 -35
  153. package/dist/cli-bundle/focused-chunks/chunk-242AXYB2.js +0 -2
  154. package/dist/cli-bundle/focused-chunks/chunk-2FEZ5SKM.js +0 -3
  155. package/dist/cli-bundle/focused-chunks/chunk-6GCRSLPG.js +0 -2
  156. package/dist/cli-bundle/focused-chunks/chunk-7VATS5QY.js +0 -16
  157. package/dist/cli-bundle/focused-chunks/chunk-JEFKLWMM.js +0 -2
  158. package/dist/cli-bundle/focused-chunks/chunk-MKHG3T76.js +0 -20
  159. package/dist/cli-bundle/focused-chunks/chunk-O67VDLOT.js +0 -51
  160. package/dist/cli-bundle/focused-chunks/chunk-PF3E6Q36.js +0 -8
  161. package/dist/cli-bundle/focused-chunks/chunk-QDFJIRKN.js +0 -4
  162. package/dist/cli-bundle/focused-chunks/chunk-RMRCQPWU.js +0 -2
  163. package/dist/cli-bundle/focused-chunks/chunk-S4U5CN5Q.js +0 -2
  164. package/dist/cli-bundle/focused-chunks/chunk-T2LXXSOD.js +0 -158
  165. package/dist/cli-bundle/focused-chunks/chunk-Z2USIBR2.js +0 -5
@@ -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,18 +4,20 @@
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]="60d6ec0e-a8ed-5a5d-b5bd-d29da8532a79")}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]="bf6a9831-134c-5dc2-a3e2-74dca00cf22c")}catch(e){}}();
8
8
  import jsonPatch from "fast-json-patch";
9
- import { ITEM_METADATA_KEY_ORDER } from "../shared/constants.js";
9
+ import { EXIT_CODE, ITEM_METADATA_KEY_ORDER } from "../shared/constants.js";
10
10
  import { runActiveServiceOverride } from "../extensions/index.js";
11
11
  import { appendLineAtomic } from "../fs/fs-utils.js";
12
12
  import { canonicalDocument } from "../item/item-format.js";
13
13
  import { toItemRecord } from "../item/item-record.js";
14
14
  import { orderObject, sha256Hex, stableStringify, } from "../shared/serialization.js";
15
- import { nowIso } from "../shared/time.js";
15
+ import { isMillisecondPrecisionRfc3339DateTime, nowIso, } from "../shared/time.js";
16
16
  import { diagnoseAgentIdentity, resolveHistoryAgentIdentity, resolveHistoryAuthorSource, } from "../shared/author.js";
17
- import { removeHistoryEventIndexForHistoryPath, updateHistoryEventIndexAfterAppend, } from "./event-index.js";
17
+ import { appendHistoryEntryWithEventIndex, removeHistoryEventIndexForHistoryPath, } from "./event-index.js";
18
+ import { classifyHistoryEvent } from "./event-classification.js";
18
19
  import { invalidateHistoryDriftCacheForPath } from "./drift-cache.js";
20
+ import { PmCliError } from "../shared/errors.js";
19
21
  const EMPTY_LEGACY_HASH_DOCUMENT = {
20
22
  front_matter: {},
21
23
  body: "",
@@ -24,6 +26,242 @@ const EMPTY_LEGACY_HASH_DOCUMENT = {
24
26
  export const CURRENT_HISTORY_ITEM_HASH_VERSION = 3;
25
27
  /** Hash epochs understood by this runtime. */
26
28
  export const SUPPORTED_HISTORY_ITEM_HASH_VERSIONS = [1, 2, 3];
29
+ /** Record-integrity epoch written beside new immutable history events. */
30
+ export const CURRENT_HISTORY_RECORD_HASH_VERSION = 1;
31
+ /** Record-integrity epochs understood by this runtime. */
32
+ export const SUPPORTED_HISTORY_RECORD_HASH_VERSIONS = [1];
33
+ /** Canonicalize and hash every immutable record field except the hash value itself. */
34
+ export function hashHistoryRecord(entry) {
35
+ return sha256Hex(stableStringify(entry));
36
+ }
37
+ /** Hash one patch representation without retaining its potentially sensitive values. */
38
+ export function hashHistoryPatch(patch) {
39
+ return sha256Hex(stableStringify(patch));
40
+ }
41
+ /** Hash an ordered history prefix for compact checkpoint provenance. */
42
+ export function hashHistoryStream(entries) {
43
+ return sha256Hex(stableStringify(entries));
44
+ }
45
+ /** Attach the current immutable-record integrity envelope to one history event. */
46
+ export function sealHistoryRecord(entry) {
47
+ const sealed = {
48
+ ...entry,
49
+ record_hash_version: CURRENT_HISTORY_RECORD_HASH_VERSION,
50
+ };
51
+ const { record_hash: _recordHash, ...canonical } = sealed;
52
+ return { ...sealed, record_hash: hashHistoryRecord(canonical) };
53
+ }
54
+ /** Verify record-level attribution coverage while accepting explicit legacy item-state-only entries. */
55
+ export function verifyHistoryRecordHash(entry) {
56
+ if (entry.record_hash_version === undefined &&
57
+ entry.record_hash === undefined) {
58
+ return { ok: true, coverage: "item_state_only" };
59
+ }
60
+ if (entry.record_hash_version === undefined ||
61
+ entry.record_hash === undefined) {
62
+ return { ok: false, error: "incomplete_record_hash_envelope" };
63
+ }
64
+ if (!SUPPORTED_HISTORY_RECORD_HASH_VERSIONS.includes(entry.record_hash_version)) {
65
+ return { ok: false, error: "unsupported_record_hash_version" };
66
+ }
67
+ const { record_hash: _recordHash, ...canonical } = entry;
68
+ return hashHistoryRecord(canonical) === entry.record_hash
69
+ ? { ok: true, coverage: "record_and_item_state" }
70
+ : { ok: false, error: "record_hash_mismatch" };
71
+ }
72
+ /** Build the retained coordinate and optional exact-record rewrite evidence. */
73
+ function createHistoryRewriteEvidence(original, options) {
74
+ const evidence = {
75
+ before_hash: original.before_hash,
76
+ after_hash: original.after_hash,
77
+ patch_hash: hashHistoryPatch(original.patch),
78
+ };
79
+ if (original.item_hash_version !== undefined) {
80
+ evidence.item_hash_version = original.item_hash_version;
81
+ }
82
+ if (options.retainOriginalPatch) {
83
+ evidence.patch = structuredClone(original.patch);
84
+ }
85
+ if (options.retainPriorRecordHash !== false &&
86
+ original.record_hash_version !== undefined &&
87
+ original.record_hash !== undefined) {
88
+ evidence.record_hash_version = original.record_hash_version;
89
+ evidence.record_hash = original.record_hash;
90
+ evidence.record = structuredClone(original);
91
+ }
92
+ return evidence;
93
+ }
94
+ /** Reseal a maintenance rewrite and retain the anchors and patch digest it replaced. */
95
+ export function resealHistoryRewrite(original, rewritten, options = {}) {
96
+ const originalRecordVerification = verifyHistoryRecordHash(original);
97
+ if (!originalRecordVerification.ok) {
98
+ throw new TypeError(originalRecordVerification.error);
99
+ }
100
+ const originalPatchHash = hashHistoryPatch(original.patch);
101
+ const retainsRewriteEvidence = options.retainPriorRecord === true ||
102
+ original.before_hash !== rewritten.before_hash ||
103
+ original.after_hash !== rewritten.after_hash ||
104
+ original.item_hash_version !== rewritten.item_hash_version ||
105
+ originalPatchHash !== hashHistoryPatch(rewritten.patch);
106
+ const sealed = sealHistoryRecord({
107
+ ...rewritten,
108
+ ...(retainsRewriteEvidence
109
+ ? {
110
+ reanchor_evidence: [
111
+ ...(rewritten.reanchor_evidence ?? []),
112
+ createHistoryRewriteEvidence(original, options),
113
+ ],
114
+ }
115
+ : {}),
116
+ });
117
+ const evidenceVerification = verifyHistoryRewriteEvidence(sealed);
118
+ if (!evidenceVerification.ok) {
119
+ throw new TypeError(evidenceVerification.error);
120
+ }
121
+ return sealed;
122
+ }
123
+ /** Return whether runtime data is a dense list of structurally valid patch operations. */
124
+ function isHistoryPatch(value) {
125
+ if (!Array.isArray(value) || Object.keys(value).length !== value.length) {
126
+ return false;
127
+ }
128
+ return value.every((operation) => {
129
+ if (!isRecord(operation) || typeof operation.path !== "string") {
130
+ return false;
131
+ }
132
+ if (operation.op === "move" || operation.op === "copy") {
133
+ return typeof operation.from === "string";
134
+ }
135
+ if (["add", "replace", "test"].includes(operation.op)) {
136
+ return Object.hasOwn(operation, "value");
137
+ }
138
+ return operation.op === "remove";
139
+ });
140
+ }
141
+ /** Return whether a retained record has the minimum immutable event shape. */
142
+ function isRetainedHistoryRecordShape(value) {
143
+ return (isRecord(value) &&
144
+ typeof value.ts === "string" &&
145
+ typeof value.author === "string" &&
146
+ typeof value.op === "string" &&
147
+ typeof value.before_hash === "string" &&
148
+ typeof value.after_hash === "string" &&
149
+ isHistoryPatch(value.patch));
150
+ }
151
+ /** Return whether optional retained record-hash fields form a complete pair. */
152
+ function hasCompleteRetainedRecordHashEnvelope(evidence) {
153
+ const hasVersion = evidence.record_hash_version !== undefined;
154
+ const hasHash = evidence.record_hash !== undefined;
155
+ return hasVersion === hasHash;
156
+ }
157
+ /** Return whether runtime evidence has the bounded shape needed for verification. */
158
+ function isHistoryReanchorEvidence(evidence) {
159
+ if (!isRecord(evidence) ||
160
+ !hasCompleteRetainedRecordHashEnvelope(evidence) ||
161
+ typeof evidence.before_hash !== "string" ||
162
+ typeof evidence.after_hash !== "string" ||
163
+ typeof evidence.patch_hash !== "string" ||
164
+ !/^[a-f0-9]{64}$/u.test(evidence.patch_hash) ||
165
+ (evidence.patch !== undefined && !isHistoryPatch(evidence.patch)) ||
166
+ (evidence.record_hash_version !== undefined &&
167
+ typeof evidence.record_hash_version !== "number") ||
168
+ (evidence.record_hash !== undefined &&
169
+ (typeof evidence.record_hash !== "string" ||
170
+ !/^[a-f0-9]{64}$/u.test(evidence.record_hash)))) {
171
+ return false;
172
+ }
173
+ return (evidence.record === undefined ||
174
+ isRetainedHistoryRecordShape(evidence.record));
175
+ }
176
+ /** Return whether a retained exact record agrees with its summary coordinates. */
177
+ function matchesRetainedHistoryRecord(evidence) {
178
+ const record = evidence.record;
179
+ return (record !== undefined &&
180
+ record.before_hash === evidence.before_hash &&
181
+ record.after_hash === evidence.after_hash &&
182
+ record.item_hash_version === evidence.item_hash_version &&
183
+ record.record_hash_version === evidence.record_hash_version &&
184
+ record.record_hash === evidence.record_hash &&
185
+ hashHistoryPatch(record.patch) === evidence.patch_hash);
186
+ }
187
+ /** Reconstruct one prior record represented by retained rewrite evidence. */
188
+ function reconstructPriorHistoryRecord(current, evidence, priorEvidence) {
189
+ if (evidence.patch &&
190
+ hashHistoryPatch(evidence.patch) !== evidence.patch_hash) {
191
+ return { kind: "patch_hash_mismatch" };
192
+ }
193
+ if (evidence.record) {
194
+ if (!matchesRetainedHistoryRecord(evidence)) {
195
+ return { kind: "record_hash_mismatch" };
196
+ }
197
+ return { kind: "record", entry: evidence.record };
198
+ }
199
+ const priorPatch = evidence.patch ?? current.patch;
200
+ if (hashHistoryPatch(priorPatch) !== evidence.patch_hash) {
201
+ return { kind: "digest_only" };
202
+ }
203
+ const priorRecord = {
204
+ ...current,
205
+ before_hash: evidence.before_hash,
206
+ after_hash: evidence.after_hash,
207
+ patch: priorPatch,
208
+ ...(evidence.item_hash_version === undefined
209
+ ? {}
210
+ : { item_hash_version: evidence.item_hash_version }),
211
+ ...(evidence.record_hash_version === undefined
212
+ ? {}
213
+ : { record_hash_version: evidence.record_hash_version }),
214
+ ...(evidence.record_hash === undefined
215
+ ? {}
216
+ : { record_hash: evidence.record_hash }),
217
+ ...(priorEvidence.length === 0 ? {} : { reanchor_evidence: priorEvidence }),
218
+ };
219
+ if (priorEvidence.length === 0)
220
+ delete priorRecord.reanchor_evidence;
221
+ if (evidence.item_hash_version === undefined) {
222
+ delete priorRecord.item_hash_version;
223
+ }
224
+ return evidence.record_hash_version === undefined ||
225
+ evidence.record_hash === undefined
226
+ ? { kind: "legacy", entry: priorRecord }
227
+ : { kind: "record", entry: priorRecord };
228
+ }
229
+ /** Verify that retained rewrite evidence reconstructs every available prior record envelope. */
230
+ export function verifyHistoryRewriteEvidence(entry) {
231
+ const evidenceEntries = entry.reanchor_evidence;
232
+ if (evidenceEntries === undefined)
233
+ return { ok: true, coverage: "none" };
234
+ if (!Array.isArray(evidenceEntries)) {
235
+ return { ok: false, error: "rewrite_evidence_invalid" };
236
+ }
237
+ if (evidenceEntries.length === 0)
238
+ return { ok: true, coverage: "none" };
239
+ let reconstructed = entry;
240
+ for (let index = evidenceEntries.length - 1; index >= 0; index -= 1) {
241
+ const evidence = evidenceEntries[index];
242
+ if (!(index in evidenceEntries) || !isHistoryReanchorEvidence(evidence)) {
243
+ return { ok: false, error: "rewrite_evidence_invalid" };
244
+ }
245
+ const prior = reconstructPriorHistoryRecord(reconstructed, evidence, evidenceEntries.slice(0, index));
246
+ if (prior.kind === "patch_hash_mismatch") {
247
+ return { ok: false, error: "rewrite_evidence_patch_hash_mismatch" };
248
+ }
249
+ if (prior.kind === "record_hash_mismatch") {
250
+ return { ok: false, error: "rewrite_evidence_record_hash_mismatch" };
251
+ }
252
+ if (prior.kind === "digest_only") {
253
+ return { ok: true, coverage: "digest_only" };
254
+ }
255
+ if (prior.kind === "legacy") {
256
+ return { ok: true, coverage: "legacy_anchor_only" };
257
+ }
258
+ if (!verifyHistoryRecordHash(prior.entry).ok) {
259
+ return { ok: false, error: "rewrite_evidence_record_hash_mismatch" };
260
+ }
261
+ reconstructed = prior.entry;
262
+ }
263
+ return { ok: true, coverage: "complete" };
264
+ }
27
265
  function compareOptionalStrings(left, right) {
28
266
  return (left ?? "").localeCompare(right ?? "");
29
267
  }
@@ -42,7 +280,8 @@ function compareLegacyLinkedTests(left, right) {
42
280
  compareOptionalStrings(left.command, right.command),
43
281
  compareOptionalNumbers(left.timeout_seconds, right.timeout_seconds),
44
282
  compareOptionalStrings(left.pm_context_mode, right.pm_context_mode),
45
- Number(Boolean(left.shared_host_safe)) - Number(Boolean(right.shared_host_safe)),
283
+ Number(Boolean(left.shared_host_safe)) -
284
+ Number(Boolean(right.shared_host_safe)),
46
285
  compareJsonValues(left.env_clear, right.env_clear, []),
47
286
  compareJsonValues(left.env_set, right.env_set, {}),
48
287
  compareJsonValues(left.assert_stdout_contains, right.assert_stdout_contains, []),
@@ -75,31 +314,17 @@ function isDefinedPointerPath(document, path) {
75
314
  }
76
315
  let cursor = document;
77
316
  for (const segment of segments) {
78
- if (Array.isArray(cursor)) {
79
- if (segment === "-" || !/^(0|[1-9]\d*)$/.test(segment)) {
80
- return false;
81
- }
82
- const index = Number(segment);
83
- if (!Number.isInteger(index) || index < 0 || index >= cursor.length) {
84
- return false;
85
- }
86
- const next = cursor[index];
87
- if (next === undefined) {
88
- return false;
89
- }
90
- cursor = next;
91
- continue;
92
- }
93
- if (typeof cursor !== "object" || cursor === null) {
94
- return false;
95
- }
96
- if (!Object.prototype.hasOwnProperty.call(cursor, segment)) {
97
- return false;
98
- }
99
- const next = cursor[segment];
100
- if (next === undefined) {
317
+ const arrayIndex = /^(0|[1-9]\d*)$/.test(segment) ? Number(segment) : -1;
318
+ const next = Array.isArray(cursor)
319
+ ? arrayIndex >= 0 && arrayIndex < cursor.length
320
+ ? cursor[arrayIndex]
321
+ : undefined
322
+ : isRecord(cursor) &&
323
+ Object.prototype.hasOwnProperty.call(cursor, segment)
324
+ ? cursor[segment]
325
+ : undefined;
326
+ if (next === undefined)
101
327
  return false;
102
- }
103
328
  cursor = next;
104
329
  }
105
330
  return true;
@@ -222,13 +447,14 @@ export function createHistoryEntry(params) {
222
447
  const provenanceOutcomes = agentIdentity.harness
223
448
  ? Object.fromEntries(Object.entries(diagnoseAgentIdentity().provenance_outcomes).filter(([, outcome]) => outcome.resolver !== undefined && outcome.status !== "resolved"))
224
449
  : undefined;
225
- const context = provenanceOutcomes === undefined || Object.keys(provenanceOutcomes).length === 0
450
+ const context = provenanceOutcomes === undefined ||
451
+ Object.keys(provenanceOutcomes).length === 0
226
452
  ? params.context
227
453
  : {
228
454
  ...params.context,
229
455
  agent_provenance_outcomes: provenanceOutcomes,
230
456
  };
231
- return {
457
+ const entry = {
232
458
  ts: params.nowIso,
233
459
  author: params.author,
234
460
  author_source: params.authorSource ?? resolveHistoryAuthorSource(params.author),
@@ -243,9 +469,7 @@ export function createHistoryEntry(params) {
243
469
  ...(agentIdentity.provenance
244
470
  ? { agent_provenance: agentIdentity.provenance }
245
471
  : {}),
246
- ...(agentIdentity.episode
247
- ? { agent_episode: agentIdentity.episode }
248
- : {}),
472
+ ...(agentIdentity.episode ? { agent_episode: agentIdentity.episode } : {}),
249
473
  op: params.op,
250
474
  patch,
251
475
  before_hash: sha256Hex(stableStringify(beforeHashCanonical)),
@@ -254,17 +478,37 @@ export function createHistoryEntry(params) {
254
478
  message: params.message === undefined ? undefined : params.message,
255
479
  ...(context === undefined ? {} : { context }),
256
480
  };
481
+ return sealHistoryRecord({
482
+ ...entry,
483
+ event_class: classifyHistoryEvent(entry),
484
+ });
257
485
  }
258
486
  function isRecord(value) {
259
487
  return typeof value === "object" && value !== null && !Array.isArray(value);
260
488
  }
489
+ function invalidHistoryTimestamp(message) {
490
+ return new PmCliError(message, EXIT_CODE.GENERIC_FAILURE, {
491
+ code: "history_timestamp_invalid",
492
+ });
493
+ }
261
494
  function fallbackHistoryTimestamp(entry) {
262
495
  const ts = entry.ts.trim();
263
- return ts.length > 0 ? ts : nowIso();
496
+ if (ts.length === 0)
497
+ return nowIso();
498
+ if (!isMillisecondPrecisionRfc3339DateTime(ts)) {
499
+ throw invalidHistoryTimestamp("History timestamp must be a valid RFC 3339 date-time with millisecond precision");
500
+ }
501
+ if (ts !== entry.ts) {
502
+ throw invalidHistoryTimestamp("History timestamp must not contain surrounding whitespace");
503
+ }
504
+ return entry.ts;
264
505
  }
265
506
  function withHistoryTimestamp(value, fallbackTs) {
266
507
  const ts = value.ts;
267
508
  if (typeof ts === "string" && ts.trim().length > 0) {
509
+ if (!isMillisecondPrecisionRfc3339DateTime(ts)) {
510
+ throw invalidHistoryTimestamp("History timestamp must be a valid RFC 3339 date-time with millisecond precision");
511
+ }
268
512
  return value;
269
513
  }
270
514
  return { ...value, ts: fallbackTs };
@@ -272,14 +516,16 @@ function withHistoryTimestamp(value, fallbackTs) {
272
516
  function serializeHistoryLine(value, fallbackEntry) {
273
517
  const fallbackTs = fallbackHistoryTimestamp(fallbackEntry);
274
518
  if (typeof value === "string") {
519
+ let parsed;
275
520
  try {
276
- const parsed = JSON.parse(value);
277
- if (isRecord(parsed)) {
278
- return JSON.stringify(withHistoryTimestamp(parsed, fallbackTs));
279
- }
521
+ parsed = JSON.parse(value);
280
522
  }
281
523
  catch {
282
524
  // Non-JSON extension lines are preserved for compatibility.
525
+ return value;
526
+ }
527
+ if (isRecord(parsed)) {
528
+ return JSON.stringify(withHistoryTimestamp(parsed, fallbackTs));
283
529
  }
284
530
  return value;
285
531
  }
@@ -288,45 +534,48 @@ function serializeHistoryLine(value, fallbackEntry) {
288
534
  }
289
535
  return JSON.stringify(value);
290
536
  }
537
+ /** Apply one extension-provided append decision and report whether it consumed the write. */
538
+ async function applyHistoryAppendOverride(historyPath, entry, result) {
539
+ if (result === false)
540
+ return true;
541
+ if (typeof result === "string") {
542
+ await appendLineAtomic(historyPath, serializeHistoryLine(result, entry));
543
+ await invalidateHistoryDriftCacheForPath(historyPath);
544
+ await removeHistoryEventIndexForHistoryPath(historyPath);
545
+ return true;
546
+ }
547
+ if (typeof result !== "object" || result === null)
548
+ return false;
549
+ const record = result;
550
+ if (record.skip === true)
551
+ return true;
552
+ const nextHistoryPath = typeof record.history_path === "string" ? record.history_path : historyPath;
553
+ await appendLineAtomic(nextHistoryPath, serializeHistoryLine(typeof record.line === "string" ? record.line : (record.entry ?? entry), entry));
554
+ await invalidateHistoryDriftCacheForPath(nextHistoryPath);
555
+ await removeHistoryEventIndexForHistoryPath(nextHistoryPath);
556
+ return true;
557
+ }
291
558
  /** Implements append history entry for the public runtime surface of this module. */
292
559
  export async function appendHistoryEntry(historyPath, entry) {
293
560
  const override = await runActiveServiceOverride("history_append", {
294
561
  history_path: historyPath,
295
562
  entry,
296
563
  });
297
- if (override.handled) {
298
- if (override.result === false) {
299
- return;
300
- }
301
- if (typeof override.result === "string") {
302
- await appendLineAtomic(historyPath, serializeHistoryLine(override.result, entry));
303
- await invalidateHistoryDriftCacheForPath(historyPath);
304
- await removeHistoryEventIndexForHistoryPath(historyPath);
305
- return;
306
- }
307
- if (typeof override.result === "object" && override.result !== null) {
308
- const record = override.result;
309
- if (record.skip === true) {
310
- return;
311
- }
312
- const nextHistoryPath = typeof record.history_path === "string"
313
- ? record.history_path
314
- : historyPath;
315
- if (typeof record.line === "string") {
316
- await appendLineAtomic(nextHistoryPath, serializeHistoryLine(record.line, entry));
317
- await invalidateHistoryDriftCacheForPath(nextHistoryPath);
318
- await removeHistoryEventIndexForHistoryPath(nextHistoryPath);
319
- return;
320
- }
321
- await appendLineAtomic(nextHistoryPath, serializeHistoryLine(record.entry ?? entry, entry));
322
- await invalidateHistoryDriftCacheForPath(nextHistoryPath);
323
- await removeHistoryEventIndexForHistoryPath(nextHistoryPath);
324
- return;
325
- }
564
+ if (override.handled &&
565
+ (await applyHistoryAppendOverride(historyPath, entry, override.result))) {
566
+ return;
326
567
  }
327
- await appendLineAtomic(historyPath, serializeHistoryLine(entry, entry));
568
+ const timestampedEntry = entry.ts.trim().length > 0
569
+ ? entry
570
+ : { ...entry, ts: fallbackHistoryTimestamp(entry) };
571
+ const normalizedEntry = sealHistoryRecord({
572
+ ...timestampedEntry,
573
+ event_class: classifyHistoryEvent(timestampedEntry),
574
+ });
575
+ await appendHistoryEntryWithEventIndex(historyPath, normalizedEntry, async () => {
576
+ await appendLineAtomic(historyPath, serializeHistoryLine(normalizedEntry, normalizedEntry));
577
+ });
328
578
  await invalidateHistoryDriftCacheForPath(historyPath);
329
- await updateHistoryEventIndexAfterAppend(historyPath, entry);
330
579
  }
331
580
  /** Public contract for test only, shared by SDK and presentation-layer consumers. */
332
581
  export const _testOnly = {
@@ -334,4 +583,4 @@ export const _testOnly = {
334
583
  isDefinedPointerPath,
335
584
  };
336
585
  //# sourceMappingURL=history.js.map
337
- //# debugId=60d6ec0e-a8ed-5a5d-b5bd-d29da8532a79
586
+ //# debugId=bf6a9831-134c-5dc2-a3e2-74dca00cf22c