@unbrained/pm-cli 2026.8.16 → 2026.8.18

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 (225) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/AGENTS.md +4 -3
  3. package/CHANGELOG.md +65 -6
  4. package/README.md +16 -16
  5. package/dist/cli/error-guidance.js +7 -4
  6. package/dist/cli/public.d.ts +2 -0
  7. package/dist/cli/public.js +21 -0
  8. package/dist/cli/register-list-query.js +132 -51
  9. package/dist/cli/register-mutation.js +4 -4
  10. package/dist/cli-bundle/bundle-manifest.json +148 -148
  11. package/dist/cli-bundle/chunks/chunk-5UZZAJKR.js +3 -0
  12. package/dist/cli-bundle/chunks/chunk-ALDJGKAK.js +2 -0
  13. package/dist/cli-bundle/chunks/chunk-BSK2IN3C.js +8 -0
  14. package/dist/cli-bundle/chunks/chunk-DY4DMUMC.js +197 -0
  15. package/dist/cli-bundle/chunks/{chunk-73UGEBRS.js → chunk-GNFAFIJI.js} +10 -10
  16. package/dist/cli-bundle/chunks/chunk-L5Q2CLPE.js +35 -0
  17. package/dist/cli-bundle/chunks/{chunk-RIS565OA.js → chunk-PVRUN5ZS.js} +5 -5
  18. package/dist/cli-bundle/chunks/chunk-RZQTVMRQ.js +2 -0
  19. package/dist/cli-bundle/chunks/chunk-SARFF5H5.js +3 -0
  20. package/dist/cli-bundle/chunks/chunk-UYCLQVL2.js +13 -0
  21. package/dist/cli-bundle/chunks/{chunk-URW4QRMI.js → chunk-YRGOLZA7.js} +59 -55
  22. package/dist/cli-bundle/chunks/register-list-query-I23LALEE.js +11 -0
  23. package/dist/cli-bundle/chunks/register-mutation-6YGU3GPH.js +20 -0
  24. package/dist/cli-bundle/chunks/{register-operations-KGDNRMCL.js → register-operations-4HTFC6J7.js} +2 -2
  25. package/dist/cli-bundle/chunks/{register-setup-NVXBOD5I.js → register-setup-PPPEF3SN.js} +2 -2
  26. package/dist/cli-bundle/focused-chunks/chunk-2PN4TJXH.js +2 -0
  27. package/dist/cli-bundle/focused-chunks/{chunk-C7JUBRFP.js → chunk-3TKWMNKF.js} +3 -3
  28. package/dist/cli-bundle/focused-chunks/chunk-5AF3ZBNB.js +155 -0
  29. package/dist/cli-bundle/focused-chunks/chunk-C5IW2NDA.js +2 -0
  30. package/dist/cli-bundle/focused-chunks/{chunk-CODV5LUT.js → chunk-EUEGXZ2A.js} +3 -3
  31. package/dist/cli-bundle/focused-chunks/{chunk-4JPEBFFC.js → chunk-EX6MKP2X.js} +2 -2
  32. package/dist/cli-bundle/focused-chunks/{chunk-SSNDYZTM.js → chunk-K43KKAFS.js} +2 -2
  33. package/dist/cli-bundle/focused-chunks/{chunk-6ROKKT4X.js → chunk-M62NO7EI.js} +2 -2
  34. package/dist/cli-bundle/focused-chunks/{chunk-T46KLOIB.js → chunk-ODOVCP45.js} +47 -47
  35. package/dist/cli-bundle/focused-chunks/chunk-RNDNMARJ.js +16 -0
  36. package/dist/cli-bundle/focused-chunks/chunk-TN4AE665.js +26 -0
  37. package/dist/cli-bundle/focused-chunks/chunk-TVV2DONO.js +2 -0
  38. package/dist/cli-bundle/focused-chunks/chunk-UI6AJ5TD.js +4 -0
  39. package/dist/cli-bundle/focused-chunks/chunk-V5XTGLK7.js +2 -0
  40. package/dist/cli-bundle/focused-chunks/chunk-WGHQVU6P.js +2 -0
  41. package/dist/cli-bundle/focused-chunks/chunk-XYNBXTDQ.js +2 -0
  42. package/dist/cli-bundle/focused-chunks/chunk-Z56ECT7I.js +2 -0
  43. package/dist/cli-bundle/main.js +7 -7
  44. package/dist/cli-bundle/sdk-authoring.js +1 -1
  45. package/dist/cli-bundle/sdk-contracts.js +1 -1
  46. package/dist/cli-bundle/sdk-core.js +31 -31
  47. package/dist/cli-bundle/sdk-governance.js +1 -1
  48. package/dist/cli-bundle/sdk-graph.js +1 -1
  49. package/dist/cli-bundle/sdk-merge.js +32 -32
  50. package/dist/cli-bundle/sdk-query.js +1 -1
  51. package/dist/cli-bundle/sdk-runtime.js +1 -1
  52. package/dist/cli-bundle/sdk-testing.js +1 -1
  53. package/dist/cli-bundle/sdk.js +4 -2
  54. package/dist/core/config/nested-settings.js +8 -2
  55. package/dist/core/extensions/exporter-output-contract.d.ts +12 -0
  56. package/dist/core/extensions/exporter-output-contract.js +67 -0
  57. package/dist/core/extensions/extension-types.d.ts +36 -1
  58. package/dist/core/extensions/extension-types.js +2 -2
  59. package/dist/core/extensions/loader.js +43 -48
  60. package/dist/core/extensions/manifest-schema.d.ts +20 -0
  61. package/dist/core/extensions/manifest-schema.js +52 -0
  62. package/dist/core/history/event-index.d.ts +5 -0
  63. package/dist/core/history/event-index.js +133 -27
  64. package/dist/core/history/replay.js +32 -14
  65. package/dist/core/schema/fields-file.d.ts +1 -1
  66. package/dist/core/schema/fields-file.js +2 -2
  67. package/dist/core/schema/runtime-field-values.js +115 -9
  68. package/dist/core/schema/runtime-schema.d.ts +3 -1
  69. package/dist/core/schema/runtime-schema.js +124 -3
  70. package/dist/core/sentry/helpers.js +3 -3
  71. package/dist/core/shared/constants.js +5 -2
  72. package/dist/core/shared/errors.d.ts +10 -0
  73. package/dist/core/shared/errors.js +2 -2
  74. package/dist/core/shared/time.d.ts +2 -0
  75. package/dist/core/shared/time.js +27 -2
  76. package/dist/core/store/settings-validator.d.ts +4 -0
  77. package/dist/core/store/settings-validator.js +3 -2
  78. package/dist/core/store/settings.js +8 -2
  79. package/dist/mcp/tool-definitions.js +12 -7
  80. package/dist/sdk/agent/refusal-reachability.d.ts +46 -6
  81. package/dist/sdk/agent/refusal-reachability.js +350 -8
  82. package/dist/sdk/authoring.d.ts +1 -1
  83. package/dist/sdk/authoring.js +2 -2
  84. package/dist/sdk/cli-bootstrap.d.ts +2 -2
  85. package/dist/sdk/cli-bootstrap.js +4 -4
  86. package/dist/sdk/cli-contracts/command-aliases.d.ts +34 -2
  87. package/dist/sdk/cli-contracts/command-aliases.js +64 -13
  88. package/dist/sdk/cli-contracts/enum-contracts.d.ts +4 -0
  89. package/dist/sdk/cli-contracts/enum-contracts.js +9 -2
  90. package/dist/sdk/cli-contracts/flag-contracts.js +11 -7
  91. package/dist/sdk/cli-contracts/grammar-contracts.d.ts +70 -0
  92. package/dist/sdk/cli-contracts/grammar-contracts.js +345 -0
  93. package/dist/sdk/cli-contracts/runtime-contracts.d.ts +19 -2
  94. package/dist/sdk/cli-contracts/runtime-contracts.js +61 -14
  95. package/dist/sdk/cli-contracts/tool-parameter-tables.js +10 -4
  96. package/dist/sdk/cli-contracts/tool-schema.d.ts +2 -2
  97. package/dist/sdk/cli-contracts/tool-schema.js +20 -11
  98. package/dist/sdk/cli-contracts.d.ts +2 -2
  99. package/dist/sdk/cli-contracts.js +4 -4
  100. package/dist/sdk/completion.js +13 -15
  101. package/dist/sdk/compose.d.ts +9 -5
  102. package/dist/sdk/compose.js +33 -3
  103. package/dist/sdk/context-intent-contracts.d.ts +8 -4
  104. package/dist/sdk/context-intent-contracts.js +76 -31
  105. package/dist/sdk/contracts.d.ts +1 -0
  106. package/dist/sdk/contracts.js +3 -2
  107. package/dist/sdk/core.d.ts +1 -1
  108. package/dist/sdk/core.js +3 -3
  109. package/dist/sdk/dependency-flag-validation.d.ts +9 -2
  110. package/dist/sdk/dependency-flag-validation.js +101 -35
  111. package/dist/sdk/environment/host-environment-errors.js +25 -7
  112. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +78 -14
  113. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +62 -14
  114. package/dist/sdk/governance/assurance-action.d.ts +3 -7
  115. package/dist/sdk/governance/assurance-action.js +97 -2
  116. package/dist/sdk/governance/boundary-fixtures.d.ts +86 -0
  117. package/dist/sdk/governance/boundary-fixtures.js +201 -0
  118. package/dist/sdk/governance/defect-recurrence-signals.d.ts +10 -0
  119. package/dist/sdk/governance/defect-recurrence-signals.js +25 -0
  120. package/dist/sdk/governance/defect-recurrence.d.ts +242 -0
  121. package/dist/sdk/governance/defect-recurrence.js +523 -0
  122. package/dist/sdk/governance.d.ts +2 -0
  123. package/dist/sdk/governance.js +4 -2
  124. package/dist/sdk/graph/governance.js +3 -2
  125. package/dist/sdk/guide-topics.js +193 -4
  126. package/dist/sdk/index.d.ts +7 -3
  127. package/dist/sdk/index.js +8 -5
  128. package/dist/sdk/init-agent-guidance.js +3 -3
  129. package/dist/sdk/lifecycle/create.js +4 -2
  130. package/dist/sdk/lifecycle/update-many.js +15 -2
  131. package/dist/sdk/lifecycle/update.js +5 -2
  132. package/dist/sdk/linked-artifacts.js +63 -14
  133. package/dist/sdk/mutation-events.d.ts +18 -2
  134. package/dist/sdk/mutation-events.js +59 -16
  135. package/dist/sdk/output.d.ts +44 -0
  136. package/dist/sdk/output.js +28 -2
  137. package/dist/sdk/query/complete-list.d.ts +94 -0
  138. package/dist/sdk/query/complete-list.js +173 -0
  139. package/dist/sdk/query/get.d.ts +2 -0
  140. package/dist/sdk/query/get.js +32 -7
  141. package/dist/sdk/query/list.js +5 -2
  142. package/dist/sdk/query.d.ts +1 -0
  143. package/dist/sdk/query.js +3 -2
  144. package/dist/sdk/read-output-contracts.d.ts +2 -2
  145. package/dist/sdk/read-output-contracts.js +36 -9
  146. package/dist/sdk/relationship-analytics.js +90 -14
  147. package/dist/sdk/relationship-kinds/contract.d.ts +4 -0
  148. package/dist/sdk/relationship-kinds/contract.js +3 -2
  149. package/dist/sdk/relationships.d.ts +1 -1
  150. package/dist/sdk/relationships.js +11 -2
  151. package/dist/sdk/runtime-extended-actions.js +10 -2
  152. package/dist/sdk/runtime.d.ts +8 -2
  153. package/dist/sdk/runtime.js +11 -8
  154. package/dist/sdk/schema.d.ts +1 -0
  155. package/dist/sdk/schema.js +2 -2
  156. package/dist/sdk/test/execution.d.ts +6 -2
  157. package/dist/sdk/test/execution.js +39 -13
  158. package/dist/types.d.ts +34 -2
  159. package/dist/types.js +4 -2
  160. package/docs/AGENT_GUIDE.md +25 -9
  161. package/docs/CLI_GRAMMAR.md +106 -0
  162. package/docs/COMMANDS.md +23 -23
  163. package/docs/CONFIGURATION.md +58 -1
  164. package/docs/CONTEXT_INTEGRITY_CONTRACTS.md +58 -0
  165. package/docs/DEFECT_RECURRENCE.md +134 -0
  166. package/docs/DEPENDENCY_KIND_CONTRACT.md +12 -2
  167. package/docs/ONBOARDING.md +4 -4
  168. package/docs/QUICKSTART.md +1 -1
  169. package/docs/README.md +4 -0
  170. package/docs/RELATIONSHIP_GRAPH.md +16 -2
  171. package/docs/RELEASING.md +30 -6
  172. package/docs/SDK.md +74 -7
  173. package/docs/SDK_ARTIFACT_OUTPUT.md +72 -0
  174. package/docs/SDK_CONTEXT_COORDINATION.md +46 -12
  175. package/docs/SDK_RUNTIME_BOUNDARIES.md +9 -1
  176. package/docs/TESTING.md +18 -1
  177. package/docs/TRUSTWORTHY_CONTEXT_EVIDENCE.md +1 -1
  178. package/docs/agent-task-token-baseline.json +5 -5
  179. package/marketplace.json +2 -2
  180. package/package.json +8 -5
  181. package/packages/pm-beads/package.json +1 -1
  182. package/packages/pm-calendar/package.json +1 -1
  183. package/packages/pm-command-kit/package.json +1 -1
  184. package/packages/pm-digital-twin/package.json +1 -1
  185. package/packages/pm-governance-audit/package.json +1 -1
  186. package/packages/pm-guide-shell/package.json +1 -1
  187. package/packages/pm-kanban/package.json +1 -1
  188. package/packages/pm-lifecycle-hooks/package.json +1 -1
  189. package/packages/pm-linked-test-adapters/package.json +1 -1
  190. package/packages/pm-search-advanced/package.json +1 -1
  191. package/packages/pm-templates/package.json +1 -1
  192. package/packages/pm-todos/package.json +1 -1
  193. package/packages/pm-vcs/package.json +1 -1
  194. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  195. package/plugins/pm-claude/skills/pm-audit/SKILL.md +26 -0
  196. package/plugins/pm-claude/skills/pm-developer/SKILL.md +26 -0
  197. package/plugins/pm-claude/skills/pm-planner/SKILL.md +26 -0
  198. package/plugins/pm-claude/skills/pm-release/SKILL.md +26 -0
  199. package/plugins/pm-claude/skills/pm-workflow/SKILL.md +26 -0
  200. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  201. package/plugins/pm-codex/skills/pm-auditor/SKILL.md +26 -0
  202. package/plugins/pm-codex/skills/pm-native/SKILL.md +26 -0
  203. package/plugins/pm-codex/skills/pm-release/SKILL.md +26 -0
  204. package/sdk/public-surface.json +1243 -54
  205. package/dist/cli-bundle/chunks/chunk-47OQEOQB.js +0 -2
  206. package/dist/cli-bundle/chunks/chunk-HCIYWD6M.js +0 -35
  207. package/dist/cli-bundle/chunks/chunk-I2LUWWR5.js +0 -3
  208. package/dist/cli-bundle/chunks/chunk-IC5W2T34.js +0 -2
  209. package/dist/cli-bundle/chunks/chunk-OAC5NIV4.js +0 -3
  210. package/dist/cli-bundle/chunks/chunk-PANOOBIS.js +0 -13
  211. package/dist/cli-bundle/chunks/chunk-TK6N7HGM.js +0 -8
  212. package/dist/cli-bundle/chunks/chunk-TOFG2URD.js +0 -197
  213. package/dist/cli-bundle/chunks/register-list-query-EYCXHZAG.js +0 -10
  214. package/dist/cli-bundle/chunks/register-mutation-7YVIG54J.js +0 -20
  215. package/dist/cli-bundle/focused-chunks/chunk-5NMFSX26.js +0 -155
  216. package/dist/cli-bundle/focused-chunks/chunk-725JSCMP.js +0 -2
  217. package/dist/cli-bundle/focused-chunks/chunk-DQ6SMTBF.js +0 -2
  218. package/dist/cli-bundle/focused-chunks/chunk-HAACPSUW.js +0 -2
  219. package/dist/cli-bundle/focused-chunks/chunk-J4UFYHOD.js +0 -2
  220. package/dist/cli-bundle/focused-chunks/chunk-JX5Z344Q.js +0 -2
  221. package/dist/cli-bundle/focused-chunks/chunk-L4RDYBIQ.js +0 -2
  222. package/dist/cli-bundle/focused-chunks/chunk-NJ5RLEFD.js +0 -26
  223. package/dist/cli-bundle/focused-chunks/chunk-QYZERYR5.js +0 -4
  224. package/dist/cli-bundle/focused-chunks/chunk-TZATVC4G.js +0 -16
  225. package/dist/cli-bundle/focused-chunks/chunk-WY3WRPVN.js +0 -2
@@ -4,7 +4,7 @@
4
4
  * Implements the pm linked artifacts command surface and its agent-facing runtime behavior.
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]="256b39b7-882e-553c-9dea-813bfd363dd7")}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]="72f57c4d-65c5-57f1-a16a-f6c970aca680")}catch(e){}}();
8
8
  import fs from "node:fs/promises";
9
9
  import path from "node:path";
10
10
  import fg from "fast-glob";
@@ -83,24 +83,73 @@ function expandBareCommaSeparatedAddEntries(raw) {
83
83
  return splitCommaList(trimmed);
84
84
  });
85
85
  }
86
+ /** Whether a Markdown destination contains only balanced parentheses. */
87
+ function hasBalancedParentheses(value) {
88
+ let depth = 0;
89
+ for (const character of value) {
90
+ if (character === "(")
91
+ depth += 1;
92
+ if (character === ")")
93
+ depth -= 1;
94
+ if (depth < 0)
95
+ return false;
96
+ }
97
+ return depth === 0;
98
+ }
99
+ /** Parse one lossless bare, Markdown, or CSV-label remote documentation reference. */
100
+ function parseDocumentationReference(trimmed) {
101
+ if (/^[A-Za-z][A-Za-z0-9+.-]*:\/\//u.test(trimmed) &&
102
+ !looksLikeStructuredPathEntry(trimmed)) {
103
+ return { path: trimmed, scope: "project", note: undefined };
104
+ }
105
+ const labelEnd = trimmed.indexOf("](");
106
+ if (trimmed.startsWith("[") && labelEnd > 0 && trimmed.endsWith(")")) {
107
+ const destination = trimmed.slice(labelEnd + 2, -1).trim();
108
+ if (destination.length > 0 && hasBalancedParentheses(destination)) {
109
+ return {
110
+ path: destination,
111
+ scope: "project",
112
+ note: trimmed.slice(1, labelEnd).trim() || undefined,
113
+ };
114
+ }
115
+ }
116
+ const delimiterIndex = trimmed.indexOf(",");
117
+ if (delimiterIndex < 0)
118
+ return undefined;
119
+ const label = trimmed.slice(0, delimiterIndex).trim();
120
+ const destination = trimmed.slice(delimiterIndex + 1).trim();
121
+ if (label.length === 0 ||
122
+ !/^[A-Za-z][A-Za-z0-9+.-]*:\/\//u.test(destination)) {
123
+ return undefined;
124
+ }
125
+ return { path: destination, scope: "project", note: label };
126
+ }
86
127
  /** Implements parse add entries for the public runtime surface of this module. */
87
128
  export function parseAddEntries(raw, bareNoun) {
88
129
  if (!raw)
89
130
  return [];
90
- return expandBareCommaSeparatedAddEntries(raw).map((entry) => {
131
+ return raw.flatMap((entry) => {
91
132
  const trimmed = entry.trim();
92
- const kv = looksLikeStructuredPathEntry(trimmed)
93
- ? parseCsvKv(entry, "--add")
94
- : { path: trimmed };
95
- assertNoUnknownCsvKeys(kv, "--add", LINKED_ARTIFACT_ADD_KEYS);
96
- if (!kv.path) {
97
- throw new PmCliError(`--add requires path=<value> or a bare ${bareNoun} path`, EXIT_CODE.USAGE);
133
+ if (bareNoun === "doc") {
134
+ const reference = parseDocumentationReference(trimmed);
135
+ if (reference !== undefined)
136
+ return [reference];
98
137
  }
99
- return {
100
- path: kv.path,
101
- scope: ensureScope(kv.scope),
102
- note: kv.note?.trim() || undefined,
103
- };
138
+ return expandBareCommaSeparatedAddEntries([entry]).map((expanded) => {
139
+ const expandedTrimmed = expanded.trim();
140
+ const kv = looksLikeStructuredPathEntry(expandedTrimmed)
141
+ ? parseCsvKv(expanded, "--add")
142
+ : { path: expandedTrimmed };
143
+ assertNoUnknownCsvKeys(kv, "--add", LINKED_ARTIFACT_ADD_KEYS);
144
+ if (!kv.path) {
145
+ throw new PmCliError(`--add requires path=<value> or a bare ${bareNoun} path`, EXIT_CODE.USAGE);
146
+ }
147
+ return {
148
+ path: kv.path,
149
+ scope: ensureScope(kv.scope),
150
+ note: kv.note?.trim() || undefined,
151
+ };
152
+ });
104
153
  });
105
154
  }
106
155
  /** Implements parse add glob entries for the public runtime surface of this module. */
@@ -490,4 +539,4 @@ export function renameArtifactsResultKey(result, key) {
490
539
  return out;
491
540
  }
492
541
  //# sourceMappingURL=linked-artifacts.js.map
493
- //# debugId=256b39b7-882e-553c-9dea-813bfd363dd7
542
+ //# debugId=72f57c4d-65c5-57f1-a16a-f6c970aca680
@@ -1,6 +1,13 @@
1
1
  import { type IndexedHistoryEvent } from "../core/history/event-index.js";
2
2
  import type { HistoryEntry } from "../types/index.js";
3
3
  import { type HistoryProvenanceRow, type HistoryProvenanceSummary } from "./history-provenance.js";
4
+ /** Regression budgets for the canonical 200-event wire-cost fixture. */
5
+ export declare const MUTATION_EVENT_WIRE_BUDGET: Readonly<{
6
+ /** Maximum default batch bytes divided by legacy row-cursor bytes. */
7
+ batch_to_row_ratio: 0.6;
8
+ /** Fixture size used to keep the ratio representative of fleet catch-up. */
9
+ fixture_events: 200;
10
+ }>;
4
11
  interface MutationEventCursorEnvelope {
5
12
  version: number;
6
13
  fingerprint: string;
@@ -36,11 +43,13 @@ export interface ListMutationEventsOptions {
36
43
  agentInstance?: string | readonly string[];
37
44
  /** Exact provenance dimension predicates (`dimension=value`). */
38
45
  provenanceFilter?: string | readonly string[];
46
+ /** Cursor framing mode; batch is the token-efficient default. */
47
+ cursorMode?: "batch" | "row";
39
48
  }
40
49
  /** One ordered cross-item mutation event. */
41
50
  export interface MutationEvent {
42
51
  /** Cursor that resumes strictly after this event. */
43
- cursor: string;
52
+ cursor?: string;
44
53
  /** Item id or `_workspace` stream subject. */
45
54
  item_id: string;
46
55
  /** One-based version within the subject's stream. */
@@ -68,10 +77,12 @@ export interface MutationEventPage {
68
77
  count: number;
69
78
  /** Whether another matching event is immediately available. */
70
79
  has_more: boolean;
80
+ /** Cursor framing used by this page. */
81
+ cursor_mode: "batch" | "row";
71
82
  /** Cursor that resumes after the final returned event. */
72
83
  next_cursor?: string;
73
84
  /** Persistent derived projection used for the read. */
74
- source: "derived_index";
85
+ source: "derived_index" | "authoritative_history";
75
86
  /** Constant-size provenance completeness metrics for the returned page. */
76
87
  provenance_summary?: HistoryProvenanceSummary;
77
88
  }
@@ -94,6 +105,11 @@ declare function resolveMutationEventStart(sinceValue: string | undefined, finge
94
105
  };
95
106
  /** Read one bounded page of mutation events from the persistent projection. */
96
107
  export declare function listMutationEvents(options?: ListMutationEventsOptions): Promise<MutationEventPage>;
108
+ /**
109
+ * Subscribe to complete event batches. Every yield is a recoverable boundary;
110
+ * an empty batch is an idle heartbeat carrying the last known cursor.
111
+ */
112
+ export declare function subscribeMutationEventBatches(options?: SubscribeMutationEventsOptions): AsyncGenerator<MutationEventPage, void, void>;
97
113
  /**
98
114
  * Subscribe to committed mutation facts as an async iterable. The iterator
99
115
  * performs cursor catch-up reads and waits only when no new event is available.
@@ -5,10 +5,10 @@
5
5
  * history store without requiring a daemon or shell subprocess.
6
6
  */
7
7
 
8
- !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]="3f9853cc-0719-544f-8726-3cfd6063b59e")}catch(e){}}();
8
+ !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]="e8cd5790-eacf-5ef7-bc0a-11ea3fe17d2d")}catch(e){}}();
9
9
  import { createHash } from "node:crypto";
10
10
  import { setTimeout as delay } from "node:timers/promises";
11
- import { queryHistoryEventIndex, rebuildHistoryEventIndex, } from "../core/history/event-index.js";
11
+ import { queryHistoryEventIndex, queryHistoryEventStreams, rebuildHistoryEventIndex, } from "../core/history/event-index.js";
12
12
  import { pathExists } from "../core/fs/fs-utils.js";
13
13
  import { EXIT_CODE } from "../core/shared/constants.js";
14
14
  import { PmCliError } from "../core/shared/errors.js";
@@ -21,6 +21,13 @@ const MUTATION_EVENT_CURSOR_PATTERN = /^[A-Za-z0-9_-]+$/;
21
21
  const DEFAULT_EVENT_LIMIT = 100;
22
22
  const MAX_EVENT_LIMIT = 1_000;
23
23
  const DEFAULT_FOLLOW_INTERVAL_MS = 250;
24
+ /** Regression budgets for the canonical 200-event wire-cost fixture. */
25
+ export const MUTATION_EVENT_WIRE_BUDGET = Object.freeze({
26
+ /** Maximum default batch bytes divided by legacy row-cursor bytes. */
27
+ batch_to_row_ratio: 0.6,
28
+ /** Fixture size used to keep the ratio representative of fleet catch-up. */
29
+ fixture_events: 200,
30
+ });
24
31
  function normalizeFilter(value) {
25
32
  if (value === undefined)
26
33
  return undefined;
@@ -120,14 +127,25 @@ async function resolveIndexedMutationEvents(pmRoot, query) {
120
127
  let indexed = await queryHistoryEventIndex(pmRoot, query);
121
128
  if (indexed === null) {
122
129
  if (!(await rebuildHistoryEventIndex(pmRoot))) {
123
- throw new PmCliError("Mutation events require a runtime with node:sqlite DatabaseSync support.", EXIT_CODE.GENERIC_FAILURE, { code: "event_index_unavailable" });
130
+ return {
131
+ ...(await queryHistoryEventStreams(pmRoot, query)),
132
+ source: "authoritative_history",
133
+ };
124
134
  }
125
135
  indexed = await queryHistoryEventIndex(pmRoot, query);
126
136
  }
127
137
  if (indexed === null) {
128
138
  throw new PmCliError("Mutation event index could not be opened after rebuild.", EXIT_CODE.GENERIC_FAILURE, { code: "event_index_unavailable" });
129
139
  }
130
- return indexed;
140
+ return { ...indexed, source: "derived_index" };
141
+ }
142
+ /** Resolves the event cursor emission mode and rejects unsupported transport spellings. */
143
+ function resolveMutationEventCursorMode(value) {
144
+ const cursorMode = value ?? "batch";
145
+ if (cursorMode !== "batch" && cursorMode !== "row") {
146
+ throw new PmCliError("Mutation event cursor mode must be batch or row.", EXIT_CODE.USAGE, { code: "invalid_event_cursor_mode" });
147
+ }
148
+ return cursorMode;
131
149
  }
132
150
  /** Read one bounded page of mutation events from the persistent projection. */
133
151
  export async function listMutationEvents(options = {}) {
@@ -136,6 +154,7 @@ export async function listMutationEvents(options = {}) {
136
154
  throw new PmCliError(`Tracker is not initialized at ${pmRoot}. Run pm init first.`, EXIT_CODE.NOT_FOUND);
137
155
  }
138
156
  const requestedLimit = parseMutationEventLimit(options.limit);
157
+ const cursorMode = resolveMutationEventCursorMode(options.cursorMode);
139
158
  if (options.full === true && options.provenance === true) {
140
159
  throw new PmCliError("Mutation event projections are mutually exclusive. Use --provenance or --full.", EXIT_CODE.USAGE);
141
160
  }
@@ -174,7 +193,9 @@ export async function listMutationEvents(options = {}) {
174
193
  };
175
194
  const indexed = await resolveIndexedMutationEvents(pmRoot, query);
176
195
  const events = indexed.events.map((event) => ({
177
- cursor: encodeMutationEventCursor(event, fingerprint),
196
+ ...(cursorMode === "row"
197
+ ? { cursor: encodeMutationEventCursor(event, fingerprint) }
198
+ : {}),
178
199
  item_id: event.stream_id,
179
200
  version: event.stream_offset + 1,
180
201
  ts: event.entry.ts,
@@ -198,10 +219,13 @@ export async function listMutationEvents(options = {}) {
198
219
  events,
199
220
  count: events.length,
200
221
  has_more: indexed.has_more,
201
- ...(events.length === 0
222
+ cursor_mode: cursorMode,
223
+ ...(indexed.events.length === 0
202
224
  ? {}
203
- : { next_cursor: events[events.length - 1].cursor }),
204
- source: "derived_index",
225
+ : {
226
+ next_cursor: encodeMutationEventCursor(indexed.events[indexed.events.length - 1], fingerprint),
227
+ }),
228
+ source: indexed.source,
205
229
  ...(options.provenanceSummary === true
206
230
  ? {
207
231
  provenance_summary: summarizeHistoryProvenance(indexed.events.map((event) => event.entry), vocabulary, dimensions),
@@ -210,21 +234,26 @@ export async function listMutationEvents(options = {}) {
210
234
  };
211
235
  }
212
236
  /**
213
- * Subscribe to committed mutation facts as an async iterable. The iterator
214
- * performs cursor catch-up reads and waits only when no new event is available.
237
+ * Subscribe to complete event batches. Every yield is a recoverable boundary;
238
+ * an empty batch is an idle heartbeat carrying the last known cursor.
215
239
  */
216
- export async function* subscribeMutationEvents(options = {}) {
240
+ export async function* subscribeMutationEventBatches(options = {}) {
217
241
  const intervalMs = options.intervalMs ?? DEFAULT_FOLLOW_INTERVAL_MS;
218
242
  if (!Number.isSafeInteger(intervalMs) || intervalMs < 10) {
219
243
  throw new PmCliError("Mutation event follow interval must be an integer of at least 10ms.", EXIT_CODE.USAGE, { code: "invalid_event_follow_interval" });
220
244
  }
221
245
  let cursor = options.since;
222
246
  while (options.signal?.aborted !== true) {
223
- const page = await listMutationEvents({ ...options, since: cursor });
247
+ const page = await listMutationEvents({
248
+ ...options,
249
+ cursorMode: options.cursorMode ?? "batch",
250
+ since: cursor,
251
+ });
252
+ const boundary = page.next_cursor === undefined && cursor !== undefined
253
+ ? { ...page, next_cursor: cursor }
254
+ : page;
255
+ yield boundary;
224
256
  if (page.events.length > 0) {
225
- for (const event of page.events) {
226
- yield event;
227
- }
228
257
  cursor = page.next_cursor;
229
258
  continue;
230
259
  }
@@ -238,6 +267,20 @@ export async function* subscribeMutationEvents(options = {}) {
238
267
  }
239
268
  }
240
269
  }
270
+ /**
271
+ * Subscribe to committed mutation facts as an async iterable. The iterator
272
+ * performs cursor catch-up reads and waits only when no new event is available.
273
+ */
274
+ export async function* subscribeMutationEvents(options = {}) {
275
+ for await (const page of subscribeMutationEventBatches({
276
+ ...options,
277
+ cursorMode: "row",
278
+ })) {
279
+ for (const event of page.events) {
280
+ yield event;
281
+ }
282
+ }
283
+ }
241
284
  /** Public test seams for cursor contract verification. */
242
285
  export const _testOnlyMutationEvents = {
243
286
  decodeMutationEventCursor,
@@ -249,4 +292,4 @@ export const _testOnlyMutationEvents = {
249
292
  resolveMutationEventStart,
250
293
  };
251
294
  //# sourceMappingURL=mutation-events.js.map
252
- //# debugId=3f9853cc-0719-544f-8726-3cfd6063b59e
295
+ //# debugId=e8cd5790-eacf-5ef7-bc0a-11ea3fe17d2d
@@ -14,3 +14,47 @@ export { SUPPRESS_HOST_OUTPUT_MARKER, isHostOutputSuppressed, suppressHostOutput
14
14
  * non-array object.
15
15
  */
16
16
  export declare function serializeNdjsonRows(rows: readonly unknown[]): string;
17
+ /** Terminal metadata that makes an NDJSON batch independently resumable. */
18
+ export interface PmNdjsonStreamTrailer {
19
+ /** Producer-owned constant-size fields may extend the shared trailer. */
20
+ readonly [key: string]: unknown;
21
+ /** Stable discriminator that cannot be confused with a domain row. */
22
+ record_type: "pm.stream.trailer";
23
+ /** Number of domain rows preceding this trailer in the batch. */
24
+ count: number;
25
+ /** Whether another row was available when the batch was read. */
26
+ has_more: boolean;
27
+ /** Opaque cursor that resumes strictly after this batch. */
28
+ next_cursor: string | null;
29
+ /** Name of the authoritative or derived source that produced the batch. */
30
+ source: string;
31
+ /** Optional constant-size metadata owned by the stream producer. */
32
+ metadata?: Readonly<Record<string, unknown>>;
33
+ }
34
+ /** Metadata accepted by {@link serializeNdjsonStream}. */
35
+ export interface PmNdjsonStreamTrailerInput {
36
+ /** Producer-owned constant-size fields may extend the shared trailer. */
37
+ readonly [key: string]: unknown;
38
+ /** The serializer owns the discriminator and callers cannot override it. */
39
+ readonly record_type?: never;
40
+ /** Number of domain rows preceding the terminal trailer. */
41
+ count: number;
42
+ /** Whether another row was available when the batch was read. */
43
+ has_more: boolean;
44
+ /** Opaque cursor that resumes strictly after this batch. */
45
+ next_cursor: string | null;
46
+ /** Name of the authoritative or derived source that produced the batch. */
47
+ source: string;
48
+ /** Optional constant-size metadata owned by the stream producer. */
49
+ metadata?: Readonly<Record<string, unknown>>;
50
+ }
51
+ /**
52
+ * Serialize a bounded NDJSON batch followed by exactly one typed terminal
53
+ * trailer. The result has no trailing newline so callers retain framing
54
+ * control while consumers always receive count and recovery metadata, even
55
+ * for an empty batch.
56
+ *
57
+ * @throws {TypeError} When the trailer count differs from the number of domain
58
+ * rows or a domain row uses the reserved terminal discriminator.
59
+ */
60
+ export declare function serializeNdjsonStream(rows: readonly unknown[], trailer: PmNdjsonStreamTrailerInput): string;
@@ -5,7 +5,7 @@
5
5
  * renderers, and custom SDK-built hosts.
6
6
  */
7
7
 
8
- !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]="f3cc2b57-d6e1-5df0-bdde-756af08a3552")}catch(e){}}();
8
+ !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]="38facf38-c1e1-5574-9e70-98977494d939")}catch(e){}}();
9
9
  export { SUPPRESS_HOST_OUTPUT_MARKER, isHostOutputSuppressed, suppressHostOutput, } from "../core/output/output-control.js";
10
10
  /**
11
11
  * Serialize object rows as newline-delimited JSON without adding a trailing
@@ -35,5 +35,31 @@ export function serializeNdjsonRows(rows) {
35
35
  })
36
36
  .join("\n");
37
37
  }
38
+ /**
39
+ * Serialize a bounded NDJSON batch followed by exactly one typed terminal
40
+ * trailer. The result has no trailing newline so callers retain framing
41
+ * control while consumers always receive count and recovery metadata, even
42
+ * for an empty batch.
43
+ *
44
+ * @throws {TypeError} When the trailer count differs from the number of domain
45
+ * rows or a domain row uses the reserved terminal discriminator.
46
+ */
47
+ export function serializeNdjsonStream(rows, trailer) {
48
+ if (trailer.count !== rows.length) {
49
+ throw new TypeError(`NDJSON trailer count ${trailer.count} does not match ${rows.length} ${rows.length === 1 ? "row" : "rows"}.`);
50
+ }
51
+ for (const [index, row] of rows.entries()) {
52
+ if (typeof row === "object" &&
53
+ row !== null &&
54
+ !Array.isArray(row) &&
55
+ Reflect.get(row, "record_type") === "pm.stream.trailer") {
56
+ throw new TypeError(`NDJSON row ${index} uses reserved record_type pm.stream.trailer`);
57
+ }
58
+ }
59
+ return serializeNdjsonRows([
60
+ ...rows,
61
+ { ...trailer, record_type: "pm.stream.trailer" },
62
+ ]);
63
+ }
38
64
  //# sourceMappingURL=output.js.map
39
- //# debugId=f3cc2b57-d6e1-5df0-bdde-756af08a3552
65
+ //# debugId=38facf38-c1e1-5574-9e70-98977494d939
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @module sdk/query/complete-list
3
+ *
4
+ * Certifies that a list result represents the complete, unfiltered workspace.
5
+ */
6
+ import type { PmReadOutputReceipt } from "../read-output-contracts.js";
7
+ import type { ListFullResult, ListOptions } from "./list.js";
8
+ /** Stable reasons a purported whole-workspace list cannot be trusted. */
9
+ export declare const PM_COMPLETE_LIST_FINDING_CODES: readonly ["invalid_envelope", "source_incomplete", "source_unchecked", "filtered_corpus", "terminal_items_excluded", "strict_read_unproven", "page_incomplete", "count_mismatch", "projection_incomplete", "field_omission", "budget_compaction", "budget_omission", "session_projection", "invalid_item_id", "duplicate_item_id"];
10
+ /** Machine-readable reason a complete-list candidate failed certification. */
11
+ export type PmCompleteListFindingCode = (typeof PM_COMPLETE_LIST_FINDING_CODES)[number];
12
+ /** One independently actionable complete-list contract violation. */
13
+ export interface PmCompleteListFinding {
14
+ /** Stable finding identifier suitable for automation. */
15
+ code: PmCompleteListFindingCode;
16
+ /** Concise explanation of the evidence that failed. */
17
+ message: string;
18
+ }
19
+ /** Proof attached only after every whole-corpus invariant passes. */
20
+ export interface PmCompleteListCertificate {
21
+ /** Certificate schema version. */
22
+ contract_version: 1;
23
+ /** Number of full item rows certified. */
24
+ item_count: number;
25
+ /** Number of distinct non-empty item identifiers certified. */
26
+ unique_item_id_count: number;
27
+ /** Confirms the request did not exclude closed or canceled work. */
28
+ terminal_items_included: true;
29
+ /** Confirms the underlying tracker scan reported complete. */
30
+ source_complete: true;
31
+ /** Confirms every row used the full metadata projection. */
32
+ full_projection: true;
33
+ /** Confirms neither field nor whole-result omission occurred. */
34
+ no_omissions: true;
35
+ /** Confirms no page, row, or token ceiling reduced the corpus. */
36
+ unbounded: true;
37
+ }
38
+ /** Full list result augmented with a fail-closed whole-corpus certificate. */
39
+ export interface PmCompleteListResult extends ListFullResult {
40
+ /** Exact proof that the returned rows represent the full tracker corpus. */
41
+ complete_list: PmCompleteListCertificate;
42
+ /** Universal read receipt when the runtime attached one. */
43
+ read_output?: PmReadOutputReceipt;
44
+ }
45
+ /** Inspection report returned without throwing for policy and UI consumers. */
46
+ export interface PmCompleteListInspection {
47
+ /** Whether the candidate is safe to certify as the whole tracker corpus. */
48
+ ok: boolean;
49
+ /** Every independently detected contract violation. */
50
+ findings: PmCompleteListFinding[];
51
+ }
52
+ /** Failure receipt with both CLI and SDK recovery paths. */
53
+ export interface PmCompleteListFailureReceipt {
54
+ /** Stable receipt schema version. */
55
+ contract_version: 1;
56
+ /** Failed contract name. */
57
+ contract: "complete_list";
58
+ /** Every independently detected contract violation. */
59
+ findings: PmCompleteListFinding[];
60
+ /** Executable recovery instructions for both public transports. */
61
+ recovery: {
62
+ /** Exact CLI command that requests the canonical whole corpus. */
63
+ suggested_retry: string;
64
+ /** Equivalent SDK operation. */
65
+ sdk: string;
66
+ };
67
+ }
68
+ /** Typed fail-closed error raised when whole-corpus proof is absent. */
69
+ export declare class PmCompleteListValidationError extends Error {
70
+ /** Structured failure and executable recovery evidence. */
71
+ readonly receipt: PmCompleteListFailureReceipt;
72
+ /** Construct an immutable complete-list validation failure. */
73
+ constructor(findings: PmCompleteListFinding[]);
74
+ }
75
+ /** Options accepted by the whole-corpus list helper. */
76
+ export interface PmCompleteListOptions {
77
+ /** Include item bodies in addition to complete metadata. */
78
+ includeBody?: boolean;
79
+ }
80
+ /** Build the canonical all-status, full, strict, and unbounded list request. */
81
+ export declare function createCompleteListOptions(options?: PmCompleteListOptions): ListOptions & {
82
+ excludeTerminal: false;
83
+ full: true;
84
+ noTruncate: true;
85
+ outputBudget: "unbounded";
86
+ outputLimit: "unbounded";
87
+ strictRead: true;
88
+ };
89
+ /** Inspect an unknown result and report every missing whole-corpus invariant. */
90
+ export declare function inspectCompleteListResult(candidate: unknown): PmCompleteListInspection;
91
+ /** Certify a result as the whole tracker corpus or throw a typed recovery error. */
92
+ export declare function certifyCompleteListResult(candidate: unknown): PmCompleteListResult;
93
+ /** Assert whole-corpus completeness while narrowing the candidate type in place. */
94
+ export declare function assertCompleteListResult(candidate: unknown): asserts candidate is ListFullResult;
@@ -0,0 +1,173 @@
1
+ /** Stable reasons a purported whole-workspace list cannot be trusted. */
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]="cc342d27-aa2d-5e84-8441-46a35c42dc00")}catch(e){}}();
4
+ export const PM_COMPLETE_LIST_FINDING_CODES = [
5
+ "invalid_envelope",
6
+ "source_incomplete",
7
+ "source_unchecked",
8
+ "filtered_corpus",
9
+ "terminal_items_excluded",
10
+ "strict_read_unproven",
11
+ "page_incomplete",
12
+ "count_mismatch",
13
+ "projection_incomplete",
14
+ "field_omission",
15
+ "budget_compaction",
16
+ "budget_omission",
17
+ "session_projection",
18
+ "invalid_item_id",
19
+ "duplicate_item_id",
20
+ ];
21
+ /** Typed fail-closed error raised when whole-corpus proof is absent. */
22
+ export class PmCompleteListValidationError extends Error {
23
+ /** Structured failure and executable recovery evidence. */
24
+ receipt;
25
+ /** Construct an immutable complete-list validation failure. */
26
+ constructor(findings) {
27
+ super(`Complete-list certification failed: ${findings.map((finding) => finding.code).join(", ")}.`);
28
+ this.name = "PmCompleteListValidationError";
29
+ this.receipt = {
30
+ contract_version: 1,
31
+ contract: "complete_list",
32
+ findings,
33
+ recovery: {
34
+ suggested_retry: "pm list-all --full --strict-read --no-truncate --output-budget unbounded --json",
35
+ sdk: "await client.listAllComplete()",
36
+ },
37
+ };
38
+ }
39
+ }
40
+ /** Build the canonical all-status, full, strict, and unbounded list request. */
41
+ export function createCompleteListOptions(options = {}) {
42
+ return {
43
+ excludeTerminal: false,
44
+ full: true,
45
+ ...(options.includeBody === undefined
46
+ ? {}
47
+ : { includeBody: options.includeBody }),
48
+ noTruncate: true,
49
+ outputBudget: "unbounded",
50
+ outputLimit: "unbounded",
51
+ strictRead: true,
52
+ };
53
+ }
54
+ /** Narrow an unknown envelope fragment to a non-array object. */
55
+ function isRecord(value) {
56
+ return typeof value === "object" && value !== null && !Array.isArray(value);
57
+ }
58
+ /** Append one finding only when its invariant failed. */
59
+ function addFinding(findings, condition, code, message) {
60
+ if (condition)
61
+ findings.push({ code, message });
62
+ }
63
+ /** Append source-scan and filter-scope findings for a list envelope. */
64
+ function inspectSourceScope(candidate, findings) {
65
+ const completeness = isRecord(candidate.completeness)
66
+ ? candidate.completeness
67
+ : {};
68
+ addFinding(findings, completeness.status === "partial", "source_incomplete", "The source scan reported unreadable tracker artifacts.");
69
+ addFinding(findings, completeness.status !== "complete" && completeness.status !== "partial", "source_unchecked", "The source scan did not prove completeness.");
70
+ const filters = isRecord(candidate.filters) ? candidate.filters : {};
71
+ const runtimeFilters = isRecord(filters.runtime_filters)
72
+ ? filters.runtime_filters
73
+ : {};
74
+ const filterEchoKeys = new Set([
75
+ "exclude_terminal",
76
+ "include_body",
77
+ "no_truncate",
78
+ "runtime_filters",
79
+ "status",
80
+ "strict_read",
81
+ ]);
82
+ addFinding(findings, filters.status !== "all" ||
83
+ Object.keys(runtimeFilters).length > 0 ||
84
+ Object.keys(filters).some((key) => !filterEchoKeys.has(key)), "filtered_corpus", "The list did not explicitly select every lifecycle status.");
85
+ addFinding(findings, filters.exclude_terminal === true, "terminal_items_excluded", "Terminal lifecycle states were explicitly excluded.");
86
+ addFinding(findings, filters.strict_read !== true, "strict_read_unproven", "The result does not echo strict source-read enforcement.");
87
+ return filters;
88
+ }
89
+ /** Append pagination, count, projection, and field-omission findings. */
90
+ function inspectEnvelopeShape(candidate, filters, findings) {
91
+ addFinding(findings, candidate.has_more !== false ||
92
+ candidate.next_cursor !== null ||
93
+ candidate.truncated !== false ||
94
+ candidate.applied_limit !== undefined ||
95
+ filters.no_truncate !== true, "page_incomplete", "Pagination or a row ceiling may have withheld items.");
96
+ addFinding(findings, candidate.count !== candidate.items.length ||
97
+ candidate.total !== candidate.items.length, "count_mismatch", "Envelope counts do not equal the returned item count.");
98
+ const projection = isRecord(candidate.projection) ? candidate.projection : {};
99
+ addFinding(findings, projection.mode !== "full" || projection.fields !== null, "projection_incomplete", "The result is not the full metadata projection.");
100
+ const omissionReceipt = isRecord(candidate.omission_receipt)
101
+ ? candidate.omission_receipt
102
+ : undefined;
103
+ addFinding(findings, omissionReceipt?.has_omissions === true, "field_omission", "The omission receipt reports withheld field groups.");
104
+ }
105
+ /** Append universal-output and cross-call-session findings. */
106
+ function inspectOutputScope(candidate, findings) {
107
+ const readOutput = isRecord(candidate.read_output)
108
+ ? candidate.read_output
109
+ : undefined;
110
+ addFinding(findings, readOutput?.strings_compacted === true ||
111
+ readOutput?.rows_compacted === true ||
112
+ candidate.output_budget_truncation !== undefined, "budget_compaction", "Universal output shaping compacted the result.");
113
+ const budgetExceeded = isRecord(candidate.output_budget_exceeded)
114
+ ? candidate.output_budget_exceeded
115
+ : undefined;
116
+ addFinding(findings, readOutput?.result_omitted === true ||
117
+ budgetExceeded?.omitted_result === true, "budget_omission", "Universal output shaping omitted the useful result.");
118
+ addFinding(findings, candidate.read_session !== undefined, "session_projection", "A cross-call read session can suppress previously served facts.");
119
+ }
120
+ /** Append row-identity findings that prevent exact set reasoning. */
121
+ function inspectItemIds(items, findings) {
122
+ const ids = items.map((item) => isRecord(item) && typeof item.id === "string" ? item.id.trim() : "");
123
+ addFinding(findings, ids.some((id) => id.length === 0), "invalid_item_id", "Every full item row must carry a non-empty string id.");
124
+ addFinding(findings, new Set(ids).size !== ids.length, "duplicate_item_id", "Every item id must occur exactly once.");
125
+ }
126
+ /** Inspect an unknown result and report every missing whole-corpus invariant. */
127
+ export function inspectCompleteListResult(candidate) {
128
+ if (!isRecord(candidate) || !Array.isArray(candidate.items)) {
129
+ return {
130
+ ok: false,
131
+ findings: [
132
+ {
133
+ code: PM_COMPLETE_LIST_FINDING_CODES[0],
134
+ message: "The result must be an object with an items array.",
135
+ },
136
+ ],
137
+ };
138
+ }
139
+ const envelope = candidate;
140
+ const findings = [];
141
+ const filters = inspectSourceScope(envelope, findings);
142
+ inspectEnvelopeShape(envelope, filters, findings);
143
+ inspectOutputScope(envelope, findings);
144
+ inspectItemIds(envelope.items, findings);
145
+ return { ok: findings.length === 0, findings };
146
+ }
147
+ /** Certify a result as the whole tracker corpus or throw a typed recovery error. */
148
+ export function certifyCompleteListResult(candidate) {
149
+ const inspection = inspectCompleteListResult(candidate);
150
+ if (!inspection.ok) {
151
+ throw new PmCompleteListValidationError(inspection.findings);
152
+ }
153
+ const result = candidate;
154
+ return {
155
+ ...result,
156
+ complete_list: {
157
+ contract_version: 1,
158
+ item_count: result.items.length,
159
+ unique_item_id_count: new Set(result.items.map((item) => item.id)).size,
160
+ terminal_items_included: true,
161
+ source_complete: true,
162
+ full_projection: true,
163
+ no_omissions: true,
164
+ unbounded: true,
165
+ },
166
+ };
167
+ }
168
+ /** Assert whole-corpus completeness while narrowing the candidate type in place. */
169
+ export function assertCompleteListResult(candidate) {
170
+ certifyCompleteListResult(candidate);
171
+ }
172
+ //# sourceMappingURL=complete-list.js.map
173
+ //# debugId=cc342d27-aa2d-5e84-8441-46a35c42dc00