@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
@@ -7,7 +7,7 @@
7
7
  * value for the coordinator that performed the merge.
8
8
  */
9
9
 
10
- !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]="df7d5bd9-8381-5961-bda1-9e8ed335afc9")}catch(e){}}();
10
+ !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]="dfdd6f10-e11f-59e8-a16f-507dc40d5b31")}catch(e){}}();
11
11
  import { execFile } from "node:child_process";
12
12
  import { randomUUID } from "node:crypto";
13
13
  import { readdir, stat } from "node:fs/promises";
@@ -16,6 +16,7 @@ import { promisify } from "node:util";
16
16
  import { ensureDir, isFileAbsentError, isFileMissingError, pathExists, writeFileAtomic, } from "../../core/fs/fs-utils.js";
17
17
  import { sha256Hex, stableStringify } from "../../core/shared/serialization.js";
18
18
  import { isRfc3339DateTime, nowIso } from "../../core/shared/time.js";
19
+ import { hashItemScalarDecisionValue, isItemScalarMissingValue, } from "./three-way.js";
19
20
  import { readBoundedRegularFile } from "./receipt-file-boundary.js";
20
21
  const execFileAsync = promisify(execFile);
21
22
  const RECEIPT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
@@ -31,6 +32,7 @@ const RECEIPT_KEYS = new Set([
31
32
  "item_path",
32
33
  "item_id",
33
34
  "requested_preference",
35
+ "requested_preference_applied",
34
36
  "preferred",
35
37
  "conflict_resolution",
36
38
  "fields_from_theirs",
@@ -41,6 +43,7 @@ const RECEIPT_KEYS = new Set([
41
43
  "created_at",
42
44
  "reconciled_at",
43
45
  "value_availability",
46
+ "value_policy",
44
47
  "evidence_source",
45
48
  ]);
46
49
  /** Return whether a receipt id is safe to expose in cleartext diagnostics. */
@@ -54,7 +57,33 @@ const RECEIPT_DECISION_KEYS = new Set([
54
57
  "theirs",
55
58
  "retained",
56
59
  "discarded",
60
+ "retained_side",
61
+ "resolution_basis",
57
62
  ]);
63
+ const DURABLE_VALUE_POLICY = "bounded_non_sensitive_scalars_v1";
64
+ const REVIEWABLE_BUILTIN_STATUSES = new Set([
65
+ "open",
66
+ "in_progress",
67
+ "blocked",
68
+ "closed",
69
+ "canceled",
70
+ ]);
71
+ const REVIEWABLE_ORDINAL_FIELDS = new Set(["risk", "confidence", "severity"]);
72
+ const REVIEWABLE_ORDINAL_VALUES = new Set([
73
+ "low",
74
+ "medium",
75
+ "high",
76
+ "critical",
77
+ ]);
78
+ const REVIEWABLE_DURABLE_VALUES_BY_FIELD = new Map([
79
+ ["priority", new Set([0, 1, 2, 3, 4].map(stableStringify))],
80
+ ["status", new Set([...REVIEWABLE_BUILTIN_STATUSES].map(stableStringify))],
81
+ ...[...REVIEWABLE_ORDINAL_FIELDS].map((field) => [
82
+ field,
83
+ new Set([...REVIEWABLE_ORDINAL_VALUES].map(stableStringify)),
84
+ ]),
85
+ ]);
86
+ /** Resolve the clone-local receipt store without crossing a Git worktree boundary. */
58
87
  async function resolveReceiptDirectory(cwd) {
59
88
  try {
60
89
  const { stdout } = await execFileAsync("git", [
@@ -69,6 +98,7 @@ async function resolveReceiptDirectory(cwd) {
69
98
  return null;
70
99
  }
71
100
  }
101
+ /** Derive the tracker root only when the merged item belongs to this workspace. */
72
102
  async function resolveTrackerRootFromItemPath(cwd, itemPath) {
73
103
  let directory = path.dirname(path.resolve(cwd, itemPath));
74
104
  for (let depth = 0; depth < 12; depth += 1) {
@@ -81,23 +111,29 @@ async function resolveTrackerRootFromItemPath(cwd, itemPath) {
81
111
  }
82
112
  return null;
83
113
  }
114
+ /** Locate the tracked durable receipt sidecar directory. */
84
115
  function durableReceiptDirectory(pmRoot) {
85
116
  return path.join(pmRoot, "merge-receipts");
86
117
  }
118
+ /** Map a validated opaque receipt identifier to its canonical filename. */
87
119
  function receiptFileName(id) {
88
120
  return `${id}.json`;
89
121
  }
122
+ /** Narrow untrusted JSON to a non-array object before schema inspection. */
90
123
  function isRecord(value) {
91
124
  return typeof value === "object" && value !== null && !Array.isArray(value);
92
125
  }
126
+ /** Refuse receipt extensions outside the versioned schema key set. */
93
127
  function hasOnlyKeys(value, allowed) {
94
128
  return Object.keys(value).every((key) => allowed.has(key));
95
129
  }
130
+ /** Validate bounded dense string collections used in receipt coordinates. */
96
131
  function isStringArray(value) {
97
132
  return (Array.isArray(value) &&
98
133
  value.length <= RECEIPT_FIELD_MAX &&
99
134
  value.every((entry) => typeof entry === "string" && entry.length > 0 && entry.length <= 256));
100
135
  }
136
+ /** Bound JSON depth and node count before retaining untrusted decision values. */
101
137
  function hasBoundedJsonStructure(value) {
102
138
  const pending = [
103
139
  { value, depth: 0 },
@@ -123,23 +159,37 @@ function hasBoundedJsonStructure(value) {
123
159
  }
124
160
  return true;
125
161
  }
162
+ /** Validate one clone-local scalar decision and its optional resolution evidence. */
126
163
  function isMergeDecision(value) {
127
- return (isRecord(value) &&
128
- hasOnlyKeys(value, RECEIPT_DECISION_KEYS) &&
129
- typeof value.field === "string" &&
130
- value.field.length > 0 &&
131
- value.field.length <= 256 &&
132
- Object.hasOwn(value, "base") &&
133
- Object.hasOwn(value, "ours") &&
134
- Object.hasOwn(value, "theirs") &&
135
- Object.hasOwn(value, "retained") &&
136
- Object.hasOwn(value, "discarded") &&
137
- hasBoundedJsonStructure(value.base) &&
138
- hasBoundedJsonStructure(value.ours) &&
139
- hasBoundedJsonStructure(value.theirs) &&
140
- hasBoundedJsonStructure(value.retained) &&
141
- hasBoundedJsonStructure(value.discarded));
142
- }
164
+ if (!isRecord(value))
165
+ return false;
166
+ const retainedSides = [undefined, "ours", "theirs"];
167
+ const resolutionBases = [
168
+ undefined,
169
+ "requested_preference",
170
+ "document_updated_at",
171
+ "stable_value_tiebreak",
172
+ ];
173
+ return [
174
+ hasOnlyKeys(value, RECEIPT_DECISION_KEYS),
175
+ typeof value.field === "string",
176
+ typeof value.field === "string" && value.field.length > 0,
177
+ typeof value.field === "string" && value.field.length <= 256,
178
+ Object.hasOwn(value, "base"),
179
+ Object.hasOwn(value, "ours"),
180
+ Object.hasOwn(value, "theirs"),
181
+ Object.hasOwn(value, "retained"),
182
+ Object.hasOwn(value, "discarded"),
183
+ hasBoundedJsonStructure(value.base),
184
+ hasBoundedJsonStructure(value.ours),
185
+ hasBoundedJsonStructure(value.theirs),
186
+ hasBoundedJsonStructure(value.retained),
187
+ hasBoundedJsonStructure(value.discarded),
188
+ retainedSides.includes(value.retained_side),
189
+ resolutionBases.includes(value.resolution_basis),
190
+ ].every(Boolean);
191
+ }
192
+ /** Validate the complete field-to-SHA-256 proof map used during repair. */
143
193
  function isMergedFieldHashes(value) {
144
194
  if (!isRecord(value))
145
195
  return false;
@@ -150,6 +200,7 @@ function isMergedFieldHashes(value) {
150
200
  typeof hash === "string" &&
151
201
  /^[a-f0-9]{64}$/u.test(hash)));
152
202
  }
203
+ /** Require a normalized tracker item path whose basename matches the receipt item. */
153
204
  function isSafeReceiptItemPath(value, itemId) {
154
205
  if (typeof value !== "string" ||
155
206
  value.length === 0 ||
@@ -164,15 +215,7 @@ function isSafeReceiptItemPath(value, itemId) {
164
215
  !normalized.startsWith("../") &&
165
216
  path.posix.basename(normalized, path.posix.extname(normalized)) === itemId);
166
217
  }
167
- function hasValidReceiptIdentity(value) {
168
- return !hasOnlyKeys(value, RECEIPT_KEYS) || value.version !== 1
169
- ? false
170
- : typeof value.id === "string" &&
171
- isSafeReceiptId(value.id) &&
172
- typeof value.item_id === "string" &&
173
- RECEIPT_ITEM_ID_PATTERN.test(value.item_id) &&
174
- isSafeReceiptItemPath(value.item_path, value.item_id);
175
- }
218
+ /** Validate receipt arrays and optional merged-field proof coverage. */
176
219
  function hasValidReceiptCollections(value) {
177
220
  return !isStringArray(value.fields_from_theirs) ||
178
221
  !isStringArray(value.union_fields) ||
@@ -183,18 +226,35 @@ function hasValidReceiptCollections(value) {
183
226
  : value.merged_field_hashes === undefined ||
184
227
  isMergedFieldHashes(value.merged_field_hashes);
185
228
  }
229
+ /** Validate every closed-domain lifecycle and merge-policy field. */
186
230
  function hasValidReceiptEnums(value) {
187
231
  const preferences = [undefined, "ours", "theirs"];
188
- const resolutions = [undefined, "preferred_side", "stable_value_order"];
189
- const availabilities = [undefined, "clone_local", "hash_only"];
232
+ const resolutions = [
233
+ undefined,
234
+ "preferred_side",
235
+ "stable_value_order",
236
+ "latest_document_update",
237
+ ];
238
+ const availabilities = [
239
+ undefined,
240
+ "clone_local",
241
+ "hash_only",
242
+ "bounded_inline",
243
+ "mixed",
244
+ ];
190
245
  const sources = [undefined, "clone_local", "durable"];
191
246
  return (preferences.includes(value.requested_preference) &&
192
247
  preferences.includes(value.preferred) &&
248
+ (value.requested_preference_applied === undefined ||
249
+ typeof value.requested_preference_applied === "boolean") &&
193
250
  resolutions.includes(value.conflict_resolution) &&
194
251
  (value.state === "pending" || value.state === "reconciled") &&
195
252
  availabilities.includes(value.value_availability) &&
253
+ (value.value_policy === undefined ||
254
+ value.value_policy === DURABLE_VALUE_POLICY) &&
196
255
  sources.includes(value.evidence_source));
197
256
  }
257
+ /** Validate creation and optional settlement timestamps as RFC 3339 values. */
198
258
  function hasValidReceiptTimestamps(value) {
199
259
  return (typeof value.created_at === "string" &&
200
260
  isRfc3339DateTime(value.created_at) &&
@@ -202,28 +262,99 @@ function hasValidReceiptTimestamps(value) {
202
262
  (typeof value.reconciled_at === "string" &&
203
263
  isRfc3339DateTime(value.reconciled_at))));
204
264
  }
205
- function hasHashOnlyDurableDecisions(receipt) {
206
- return (receipt.value_availability === "hash_only" &&
207
- receipt.decisions.every((decision) => decision.base === null &&
208
- decision.ours === null &&
209
- decision.theirs === null &&
210
- isExactPrehashedValue(decision.retained) &&
211
- isExactPrehashedValue(decision.discarded)));
212
- }
213
- function isMergeDecisionReceipt(value, evidenceSource) {
214
- if (!isRecord(value) ||
215
- !hasValidReceiptIdentity(value) ||
216
- !hasValidReceiptCollections(value) ||
217
- !hasValidReceiptEnums(value) ||
218
- !hasValidReceiptTimestamps(value)) {
265
+ /** Validate one hash-only or policy-bounded durable decision value. */
266
+ function hasDurableDecisionValue(field, value) {
267
+ if (!isRecord(value) || isPrehashedValue(value) === undefined)
268
+ return false;
269
+ const keys = Object.keys(value);
270
+ if (!keys.every((key) => key === "pm_value_hash" || key === "pm_value")) {
271
+ return false;
272
+ }
273
+ if (!Object.hasOwn(value, "pm_value"))
274
+ return keys.length === 1;
275
+ const preview = value.pm_value;
276
+ const allowed = preview === null ||
277
+ REVIEWABLE_DURABLE_VALUES_BY_FIELD.get(field)?.has(stableStringify(preview)) === true;
278
+ return (allowed && hashItemScalarDecisionValue(preview) === value.pm_value_hash);
279
+ }
280
+ /** Validate durable decision redaction and the declared availability summary. */
281
+ function hasValidDurableDecisions(receipt) {
282
+ if (!receipt.decisions.every((decision) => decision.base === null &&
283
+ decision.ours === null &&
284
+ decision.theirs === null &&
285
+ hasDurableDecisionValue(decision.field, decision.retained) &&
286
+ hasDurableDecisionValue(decision.field, decision.discarded))) {
219
287
  return false;
220
288
  }
221
- const receipt = value;
222
- return evidenceSource !== "durable" || hasHashOnlyDurableDecisions(receipt);
289
+ const inlineValueCount = receipt.decisions.reduce((count, decision) => count +
290
+ (isRecord(decision.retained) &&
291
+ Object.hasOwn(decision.retained, "pm_value")
292
+ ? 1
293
+ : 0) +
294
+ (isRecord(decision.discarded) &&
295
+ Object.hasOwn(decision.discarded, "pm_value")
296
+ ? 1
297
+ : 0), 0);
298
+ const expectedAvailability = inlineValueCount === 0
299
+ ? "hash_only"
300
+ : inlineValueCount === receipt.decisions.length * 2
301
+ ? "bounded_inline"
302
+ : "mixed";
303
+ return (receipt.value_availability === expectedAvailability &&
304
+ (inlineValueCount === 0 || receipt.value_policy === DURABLE_VALUE_POLICY));
305
+ }
306
+ /** Return the narrowest validation class for untrusted receipt evidence. */
307
+ function receiptValidationError(value, evidenceSource) {
308
+ if (!isRecord(value))
309
+ return "required_fields";
310
+ for (const key of [
311
+ "version",
312
+ "id",
313
+ "item_path",
314
+ "item_id",
315
+ "fields_from_theirs",
316
+ "union_fields",
317
+ "decisions",
318
+ "state",
319
+ "created_at",
320
+ ]) {
321
+ if (!Object.hasOwn(value, key))
322
+ return "required_fields";
323
+ }
324
+ if (value.version !== 1 || !hasOnlyKeys(value, RECEIPT_KEYS)) {
325
+ return "required_fields";
326
+ }
327
+ if (typeof value.id !== "string" || !isSafeReceiptId(value.id)) {
328
+ return "receipt_id";
329
+ }
330
+ if (typeof value.item_id !== "string" ||
331
+ !RECEIPT_ITEM_ID_PATTERN.test(value.item_id)) {
332
+ return "item_id";
333
+ }
334
+ if (!isSafeReceiptItemPath(value.item_path, value.item_id)) {
335
+ return "item_path";
336
+ }
337
+ if (!hasValidReceiptCollections(value))
338
+ return "collections";
339
+ if (!hasValidReceiptEnums(value))
340
+ return "enums";
341
+ if (!hasValidReceiptTimestamps(value))
342
+ return "timestamps";
343
+ if (evidenceSource === "durable" &&
344
+ !hasValidDurableDecisions(value)) {
345
+ return "durable_decisions";
346
+ }
347
+ return null;
223
348
  }
349
+ /** Validate a complete receipt against its clone-local or durable evidence policy. */
350
+ function isMergeDecisionReceipt(value, evidenceSource) {
351
+ return receiptValidationError(value, evidenceSource) === null;
352
+ }
353
+ /** Read one receipt through the no-follow bounded regular-file guard. */
224
354
  async function readBoundedRegularReceiptFile(receiptPath) {
225
355
  return readBoundedRegularFile(receiptPath, RECEIPT_FILE_MAX_BYTES);
226
356
  }
357
+ /** Revalidate one receipt and stage its atomic reconciled-state replacement. */
227
358
  async function prepareReceiptSettlement(params) {
228
359
  let raw;
229
360
  try {
@@ -260,6 +391,7 @@ async function prepareReceiptSettlement(params) {
260
391
  }, null, 2)}\n`,
261
392
  };
262
393
  }
394
+ /** Fingerprint immutable receipt provenance while excluding lifecycle state. */
263
395
  function receiptProvenanceFingerprint(receipt) {
264
396
  return sha256Hex(stableStringify({
265
397
  id: receipt.id,
@@ -274,6 +406,7 @@ function receiptProvenanceFingerprint(receipt) {
274
406
  decisions: summarizeMergeReceipt(receipt).decisions,
275
407
  }));
276
408
  }
409
+ /** Merge clone-local and durable lifecycle state without losing pending work. */
277
410
  function mergeReceiptCopyLifecycle(local, durable) {
278
411
  if (local.state === "reconciled" && durable.state === "reconciled") {
279
412
  return local;
@@ -291,16 +424,23 @@ export function summarizeMergeReceipt(receipt) {
291
424
  fields_from_theirs: receipt.fields_from_theirs,
292
425
  union_fields: receipt.union_fields,
293
426
  requested_preference: receipt.requested_preference ?? receipt.preferred ?? "ours",
427
+ requested_preference_applied: receipt.requested_preference_applied ??
428
+ (receipt.conflict_resolution ?? "preferred_side") === "preferred_side",
294
429
  conflict_resolution: receipt.conflict_resolution,
295
430
  decisions: receipt.decisions.map((decision) => ({
296
431
  field: decision.field,
297
432
  retained_hash: isPrehashedValue(decision.retained) ??
298
- sha256Hex(stableStringify(decision.retained)),
433
+ hashItemScalarDecisionValue(isItemScalarMissingValue(decision.retained)
434
+ ? undefined
435
+ : decision.retained),
299
436
  discarded_hash: isPrehashedValue(decision.discarded) ??
300
- sha256Hex(stableStringify(decision.discarded)),
437
+ hashItemScalarDecisionValue(isItemScalarMissingValue(decision.discarded)
438
+ ? undefined
439
+ : decision.discarded),
301
440
  })),
302
441
  };
303
442
  }
443
+ /** Extract a structurally valid precomputed SHA-256 decision hash. */
304
444
  function isPrehashedValue(value) {
305
445
  if (typeof value !== "object" ||
306
446
  value === null ||
@@ -313,10 +453,16 @@ function isPrehashedValue(value) {
313
453
  ? hash
314
454
  : undefined;
315
455
  }
316
- function isExactPrehashedValue(value) {
317
- return (isRecord(value) &&
318
- Object.keys(value).length === 1 &&
319
- isPrehashedValue(value) !== undefined);
456
+ /** Convert a raw scalar into hashed evidence with an allowlisted preview. */
457
+ function durableValueEvidence(field, value) {
458
+ const missing = isItemScalarMissingValue(value);
459
+ const pmValueHash = hashItemScalarDecisionValue(missing ? undefined : value);
460
+ const reviewable = !missing &&
461
+ (value === null ||
462
+ REVIEWABLE_DURABLE_VALUES_BY_FIELD.get(field)?.has(stableStringify(value)) === true);
463
+ return reviewable
464
+ ? { pm_value_hash: pmValueHash, pm_value: value }
465
+ : { pm_value_hash: pmValueHash };
320
466
  }
321
467
  /** Persist one item-driver outcome in the clone-local Git directory. */
322
468
  export async function writeMergeReceipt(params) {
@@ -325,11 +471,7 @@ export async function writeMergeReceipt(params) {
325
471
  return null;
326
472
  }
327
473
  const trimmedItemPath = params.itemPath.trim();
328
- const itemPath = trimmedItemPath.length >= 2 &&
329
- ((trimmedItemPath.startsWith("'") && trimmedItemPath.endsWith("'")) ||
330
- (trimmedItemPath.startsWith('"') && trimmedItemPath.endsWith('"')))
331
- ? trimmedItemPath.slice(1, -1)
332
- : trimmedItemPath;
474
+ const itemPath = /^(['"])(.*)\1$/su.exec(trimmedItemPath)?.[2] ?? trimmedItemPath;
333
475
  const itemId = path.basename(itemPath, path.extname(itemPath));
334
476
  const receipt = {
335
477
  version: 1,
@@ -337,6 +479,7 @@ export async function writeMergeReceipt(params) {
337
479
  item_path: itemPath.replaceAll("\\", "/"),
338
480
  item_id: itemId,
339
481
  requested_preference: params.preferred,
482
+ requested_preference_applied: (params.conflictResolution ?? "preferred_side") === "preferred_side",
340
483
  conflict_resolution: params.conflictResolution ?? "preferred_side",
341
484
  fields_from_theirs: [...params.fieldsFromTheirs],
342
485
  union_fields: [...params.unionFields],
@@ -353,24 +496,35 @@ export async function writeMergeReceipt(params) {
353
496
  const trackerRoot = await resolveTrackerRootFromItemPath(params.cwd, receipt.item_path);
354
497
  if (trackerRoot !== null) {
355
498
  const durableDirectory = durableReceiptDirectory(trackerRoot);
356
- const summary = summarizeMergeReceipt(receipt);
499
+ const durableDecisions = receipt.decisions.map((decision) => ({
500
+ field: decision.field,
501
+ base: null,
502
+ ours: null,
503
+ theirs: null,
504
+ retained: durableValueEvidence(decision.field, decision.retained),
505
+ discarded: durableValueEvidence(decision.field, decision.discarded),
506
+ retained_side: decision.retained_side,
507
+ resolution_basis: decision.resolution_basis,
508
+ }));
509
+ const inlineValueCount = durableDecisions.reduce((count, decision) => count +
510
+ (Object.hasOwn(decision.retained, "pm_value") ? 1 : 0) +
511
+ (Object.hasOwn(decision.discarded, "pm_value") ? 1 : 0), 0);
357
512
  const durableReceipt = {
358
513
  ...receipt,
359
- decisions: summary.decisions.map((decision) => ({
360
- field: decision.field,
361
- base: null,
362
- ours: null,
363
- theirs: null,
364
- retained: { pm_value_hash: decision.retained_hash },
365
- discarded: { pm_value_hash: decision.discarded_hash },
366
- })),
367
- value_availability: "hash_only",
514
+ decisions: durableDecisions,
515
+ value_availability: inlineValueCount === 0
516
+ ? "hash_only"
517
+ : inlineValueCount === durableDecisions.length * 2
518
+ ? "bounded_inline"
519
+ : "mixed",
520
+ value_policy: DURABLE_VALUE_POLICY,
368
521
  };
369
522
  await ensureDir(durableDirectory);
370
523
  await writeFileAtomic(path.join(durableDirectory, receiptFileName(receipt.id)), `${JSON.stringify(durableReceipt, null, 2)}\n`);
371
524
  }
372
525
  return receipt;
373
526
  }
527
+ /** Scan one receipt directory with bounded per-candidate failure evidence. */
374
528
  async function readReceiptsFromDirectory(directory, evidenceSource) {
375
529
  let names;
376
530
  try {
@@ -390,14 +544,17 @@ async function readReceiptsFromDirectory(directory, evidenceSource) {
390
544
  const receipts = [];
391
545
  let invalidEvidenceCount = 0;
392
546
  const invalidEvidence = [];
393
- const recordInvalidEvidence = (name, reason) => {
547
+ const recordInvalidEvidence = (name, candidate) => {
394
548
  invalidEvidenceCount += 1;
395
549
  if (invalidEvidence.length >= RECEIPT_INVALID_EVIDENCE_DETAIL_LIMIT)
396
550
  return;
397
551
  const receiptId = name.slice(0, -".json".length);
398
552
  invalidEvidence.push({
399
553
  evidence_source: evidenceSource,
400
- reason,
554
+ reason: candidate.reason,
555
+ ...(candidate.validation_error === undefined
556
+ ? {}
557
+ : { validation_error: candidate.validation_error }),
401
558
  ...(isSafeReceiptId(receiptId)
402
559
  ? { receipt_id: receiptId }
403
560
  : { candidate_name_hash: sha256Hex(name) }),
@@ -411,7 +568,7 @@ async function readReceiptsFromDirectory(directory, evidenceSource) {
411
568
  receipts.push(candidate.receipt);
412
569
  }
413
570
  else {
414
- recordInvalidEvidence(name, candidate.reason);
571
+ recordInvalidEvidence(name, candidate);
415
572
  }
416
573
  }
417
574
  return {
@@ -421,6 +578,7 @@ async function readReceiptsFromDirectory(directory, evidenceSource) {
421
578
  invalid_evidence_truncated: invalidEvidenceCount > invalidEvidence.length,
422
579
  };
423
580
  }
581
+ /** Distinguish a missing optional receipt store from an unreadable one. */
424
582
  async function receiptDirectoryFailureMeansAbsent(directory, error, inspectAncestor = stat) {
425
583
  if (!isFileMissingError(error))
426
584
  return false;
@@ -439,6 +597,7 @@ async function receiptDirectoryFailureMeansAbsent(directory, error, inspectAnces
439
597
  }
440
598
  }
441
599
  }
600
+ /** Inspect one candidate without exposing rejected file contents. */
442
601
  async function inspectReceiptCandidate(directory, name, evidenceSource) {
443
602
  let raw;
444
603
  try {
@@ -456,16 +615,28 @@ async function inspectReceiptCandidate(directory, name, evidenceSource) {
456
615
  catch {
457
616
  return { reason: "candidate_invalid_json" };
458
617
  }
459
- if (!isMergeDecisionReceipt(parsed, evidenceSource) ||
460
- name !== receiptFileName(parsed.id)) {
461
- return { reason: "schema_or_identity_invalid" };
618
+ const validationError = receiptValidationError(parsed, evidenceSource);
619
+ if (validationError !== null) {
620
+ const identityErrors = new Set(["receipt_id", "item_id", "item_path"]);
621
+ return {
622
+ reason: identityErrors.has(validationError)
623
+ ? "identity_invalid"
624
+ : "schema_invalid",
625
+ validation_error: validationError,
626
+ };
627
+ }
628
+ const receipt = parsed;
629
+ if (name !== receiptFileName(receipt.id)) {
630
+ return { reason: "identity_invalid", validation_error: "filename" };
462
631
  }
463
- const { preferred: legacyPreference, evidence_source: _serializedEvidenceSource, ...receiptWithoutRuntimeKeys } = parsed;
632
+ const { preferred: legacyPreference, evidence_source: _serializedEvidenceSource, ...receiptWithoutRuntimeKeys } = receipt;
464
633
  return {
465
634
  receipt: {
466
635
  ...receiptWithoutRuntimeKeys,
467
- requested_preference: parsed.requested_preference ?? legacyPreference ?? "ours",
468
- conflict_resolution: parsed.conflict_resolution ?? "preferred_side",
636
+ requested_preference: receipt.requested_preference ?? legacyPreference ?? "ours",
637
+ requested_preference_applied: receipt.requested_preference_applied ??
638
+ (receipt.conflict_resolution ?? "preferred_side") === "preferred_side",
639
+ conflict_resolution: receipt.conflict_resolution ?? "preferred_side",
469
640
  evidence_source: evidenceSource,
470
641
  },
471
642
  };
@@ -638,4 +809,4 @@ export const _testOnlyMergeReceipts = {
638
809
  receiptDirectoryFailureMeansAbsent,
639
810
  };
640
811
  //# sourceMappingURL=receipts.js.map
641
- //# debugId=df7d5bd9-8381-5961-bda1-9e8ed335afc9
812
+ //# debugId=dfdd6f10-e11f-59e8-a16f-507dc40d5b31
@@ -1,12 +1,6 @@
1
- /**
2
- * @module sdk/merge/reconcile
3
- *
4
- * Provides the post-merge SDK workflow that previews or repairs every drifted
5
- * item-history stream, then verifies history and storage integrity in one call.
6
- */
7
1
  import type { GlobalOptions } from "../../core/shared/command-types.js";
8
- import { type HistoryRepairAllResult } from "../history-repair.js";
9
2
  import { type ValidateResult } from "../governance/validate.js";
3
+ import { type HistoryRepairAllResult } from "../history-repair.js";
10
4
  import { type MergeDecisionReceiptSummary } from "./receipts.js";
11
5
  /** Options for the audited post-merge reconciliation workflow. */
12
6
  export interface MergeReconcileOptions {
@@ -36,6 +30,14 @@ export interface MergeReconcileResult {
36
30
  pending_before: number;
37
31
  reconciled: number;
38
32
  summaries: MergeDecisionReceiptSummary[];
33
+ /** Missing durable references discovered before this reconciliation. */
34
+ missing_history_references_before: number;
35
+ /** Pre-durable references eligible for explicit audited disposition. */
36
+ legacy_disposition_eligible: number;
37
+ /** Eligible references dispositioned by this apply pass. */
38
+ legacy_disposition_recorded: number;
39
+ /** Missing durable references still blocking health after this pass. */
40
+ missing_history_references_after: number;
39
41
  };
40
42
  /** Post-operation history-drift and storage-integrity validation. */
41
43
  validation: ValidateResult;