@aiwg/cli 2026.10.3 → 2026.10.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 (279) hide show
  1. package/README.md +2 -2
  2. package/agentic/code/providers/model-capabilities.v1.json +96 -4
  3. package/bin/aiwg.mjs +50 -27
  4. package/dist/src/agents/grounding/knowledge-base.js +54 -6
  5. package/dist/src/artifacts/browser-export.js +6 -0
  6. package/dist/src/artifacts/checksum-manifest.js +1 -0
  7. package/dist/src/artifacts/citation-parser.js +173 -92
  8. package/dist/src/artifacts/citation-table-schema.js +59 -0
  9. package/dist/src/artifacts/cli.js +57 -18
  10. package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
  11. package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
  12. package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
  13. package/dist/src/artifacts/corpus-views/renderers.js +3 -3
  14. package/dist/src/artifacts/discovery-eval.js +6 -4
  15. package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
  16. package/dist/src/artifacts/fortemi-core-sync.js +166 -40
  17. package/dist/src/artifacts/framework-capabilities.js +171 -0
  18. package/dist/src/artifacts/framework-index-paths.js +73 -0
  19. package/dist/src/artifacts/framework-source-digest.js +75 -0
  20. package/dist/src/artifacts/index-builder.js +285 -131
  21. package/dist/src/artifacts/index-files.js +132 -18
  22. package/dist/src/artifacts/index-preparation.js +65 -0
  23. package/dist/src/artifacts/index-refresh.js +576 -0
  24. package/dist/src/artifacts/index-worker.mjs +9 -0
  25. package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
  26. package/dist/src/artifacts/prebuilt-deployment.js +46 -0
  27. package/dist/src/artifacts/prebuilt-relations.js +80 -0
  28. package/dist/src/artifacts/query-engine.js +410 -88
  29. package/dist/src/artifacts/relations-builder.js +168 -0
  30. package/dist/src/artifacts/relations.js +148 -0
  31. package/dist/src/artifacts/stats.js +5 -2
  32. package/dist/src/artifacts/types.js +30 -10
  33. package/dist/src/artifacts/watcher.js +1 -1
  34. package/dist/src/auth/credential-store.js +2 -1
  35. package/dist/src/catalog/cli.d.mts +5 -0
  36. package/dist/src/channel/manager.d.mts +20 -0
  37. package/dist/src/channel/manager.mjs +19 -2
  38. package/dist/src/cli/command-log.js +2 -3
  39. package/dist/src/cli/handlers/artifacts.js +3 -0
  40. package/dist/src/cli/handlers/auth.js +3 -0
  41. package/dist/src/cli/handlers/decision.js +19 -12
  42. package/dist/src/cli/handlers/diagnose.js +4 -3
  43. package/dist/src/cli/handlers/effect.js +4 -2
  44. package/dist/src/cli/handlers/evidence.js +3 -0
  45. package/dist/src/cli/handlers/help.js +1 -0
  46. package/dist/src/cli/handlers/index.js +3 -1
  47. package/dist/src/cli/handlers/init.js +4 -1
  48. package/dist/src/cli/handlers/lint.js +5 -0
  49. package/dist/src/cli/handlers/models.js +104 -8
  50. package/dist/src/cli/handlers/ralph-launcher.js +2 -0
  51. package/dist/src/cli/handlers/refresh.js +12 -0
  52. package/dist/src/cli/handlers/resource-versions.js +3 -0
  53. package/dist/src/cli/handlers/rules.js +195 -0
  54. package/dist/src/cli/handlers/run.js +4 -0
  55. package/dist/src/cli/handlers/runtime-info.js +1 -2
  56. package/dist/src/cli/handlers/script-runner.js +3 -2
  57. package/dist/src/cli/handlers/session.js +19 -9
  58. package/dist/src/cli/handlers/sessions.js +98 -37
  59. package/dist/src/cli/handlers/setup-manifest.js +39 -44
  60. package/dist/src/cli/handlers/setup.js +41 -0
  61. package/dist/src/cli/handlers/steward.js +11 -0
  62. package/dist/src/cli/handlers/subcommands.js +57 -2
  63. package/dist/src/cli/handlers/uhp.js +3 -0
  64. package/dist/src/cli/handlers/use.js +294 -55
  65. package/dist/src/cli/handlers/utilities.js +26 -10
  66. package/dist/src/cli/handlers/version.js +8 -0
  67. package/dist/src/cli/router.js +15 -1
  68. package/dist/src/cli/services/deployment-verification.js +19 -9
  69. package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
  70. package/dist/src/cli/skill-usage.js +2 -3
  71. package/dist/src/config/aiwg-config.js +87 -37
  72. package/dist/src/config/cli.js +111 -5
  73. package/dist/src/config/decision-config.js +106 -0
  74. package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
  75. package/dist/src/config/gitignore.js +50 -0
  76. package/dist/src/config/index-graph-overrides.js +36 -0
  77. package/dist/src/config/project-artifacts-health.d.mts +37 -0
  78. package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
  79. package/dist/src/config/user-config-dir.d.mts +9 -0
  80. package/dist/src/config/user-config.js +2 -1
  81. package/dist/src/config/user-registry.js +23 -15
  82. package/dist/src/config/user-state.js +7 -0
  83. package/dist/src/decision/ask.js +203 -43
  84. package/dist/src/decision/context-live-qualification.js +2 -2
  85. package/dist/src/decision/driver.js +13 -8
  86. package/dist/src/decision/ensemble/aggregate.js +23 -10
  87. package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
  88. package/dist/src/decision/entry.js +2 -15
  89. package/dist/src/decision/graph-live-qualification.js +1 -1
  90. package/dist/src/decision/graph-run.js +9 -0
  91. package/dist/src/decision/heldout/collector.js +5 -13
  92. package/dist/src/decision/heldout/contract.js +30 -7
  93. package/dist/src/decision/jev-credentials.js +15 -1
  94. package/dist/src/decision/ledger.js +131 -28
  95. package/dist/src/decision/offload-activation.js +137 -0
  96. package/dist/src/decision/offload.js +550 -0
  97. package/dist/src/decision/qualification/load.js +4 -6
  98. package/dist/src/decision/savings.js +28 -0
  99. package/dist/src/effects/types.js +5 -1
  100. package/dist/src/effects/verifiers/git.js +2 -1
  101. package/dist/src/effects/verifiers/tracker.js +26 -14
  102. package/dist/src/extensions/commands/definitions.js +19 -0
  103. package/dist/src/extensions/project-local-remove.js +36 -1
  104. package/dist/src/gates/providers/loader.js +61 -17
  105. package/dist/src/installation/manager-command.d.mts +18 -0
  106. package/dist/src/installation/manager-command.mjs +14 -2
  107. package/dist/src/installation/manager.d.mts +49 -0
  108. package/dist/src/installation/manager.mjs +78 -3
  109. package/dist/src/issues/cli.js +4 -4
  110. package/dist/src/issues/local.js +24 -5
  111. package/dist/src/jobs/runner.js +1 -1
  112. package/dist/src/lint/cli.js +27 -1
  113. package/dist/src/lint/loader.js +2 -1
  114. package/dist/src/lint/reporters.js +25 -1
  115. package/dist/src/lint/rule-activation.js +43 -0
  116. package/dist/src/lint/runner.js +137 -62
  117. package/dist/src/mcp/adapters/codex-runtime.js +7 -1
  118. package/dist/src/mcp/claude-install.d.mts +4 -0
  119. package/dist/src/mcp/claude-install.mjs +41 -0
  120. package/dist/src/mcp/cli.d.mts +1 -0
  121. package/dist/src/mcp/cli.mjs +36 -11
  122. package/dist/src/mcp/config-file.d.mts +28 -0
  123. package/dist/src/mcp/config-layers.d.mts +36 -0
  124. package/dist/src/mcp/credentials.d.mts +51 -0
  125. package/dist/src/mcp/grok-build-config.d.mts +6 -0
  126. package/dist/src/mcp/grok-build-config.mjs +1 -1
  127. package/dist/src/mcp/helpers.mjs +3 -2
  128. package/dist/src/mcp/muse-native-config.d.mts +13 -0
  129. package/dist/src/mcp/muse-native-config.mjs +1 -0
  130. package/dist/src/mcp/omp-config.d.mts +4 -0
  131. package/dist/src/mcp/registry.js +3 -93
  132. package/dist/src/mcp/registry.mjs +3 -87
  133. package/dist/src/mcp/server-config.d.mts +3 -0
  134. package/dist/src/mcp/server-config.mjs +89 -0
  135. package/dist/src/mcp/server.mjs +37 -375
  136. package/dist/src/mcp/test-simple.mjs +2 -1
  137. package/dist/src/mcp/toml-editor.d.mts +4 -0
  138. package/dist/src/mcp/toml-string.d.mts +1 -0
  139. package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
  140. package/dist/src/mcp/tool-filters.d.mts +46 -0
  141. package/dist/src/mcp/tools/decision.mjs +12 -11
  142. package/dist/src/mcp/tools/discovery.mjs +62 -55
  143. package/dist/src/mcp/tools/orchestration.mjs +12 -2
  144. package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
  145. package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
  146. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
  147. package/dist/src/mission-protocol/codecs.js +8 -0
  148. package/dist/src/models/config-loader.js +7 -2
  149. package/dist/src/models/model-capabilities.v1.json +96 -4
  150. package/dist/src/models/model-discovery.js +76 -11
  151. package/dist/src/models/provider-policy.js +101 -7
  152. package/dist/src/models/qualification-digest.d.mts +2 -0
  153. package/dist/src/models/resolver.js +12 -10
  154. package/dist/src/models/worker-direct-readiness.js +65 -44
  155. package/dist/src/models/worker-dispatch-offer.js +3 -3
  156. package/dist/src/models/worker-health-readiness.js +16 -3
  157. package/dist/src/models/worker-installation.d.mts +39 -0
  158. package/dist/src/models/worker-installation.mjs +135 -28
  159. package/dist/src/process/bounded-child-process.d.mts +84 -0
  160. package/dist/src/process/bounded-child-process.mjs +43 -0
  161. package/dist/src/process/cli-command.d.mts +16 -0
  162. package/dist/src/process/signal-child.d.mts +6 -0
  163. package/dist/src/process/signal-child.mjs +7 -0
  164. package/dist/src/providers/deployed-skill-roots.js +20 -0
  165. package/dist/src/providers/grok-build-acp.js +3 -2
  166. package/dist/src/providers/grok-build-headless.js +12 -4
  167. package/dist/src/providers/grok-build-inspect.js +1 -0
  168. package/dist/src/providers/omp-agent.d.mts +8 -0
  169. package/dist/src/providers/omp-diagnostics.d.mts +1 -0
  170. package/dist/src/providers/omp-paths.d.mts +13 -0
  171. package/dist/src/providers/skill-ownership.d.mts +9 -0
  172. package/dist/src/providers/skill-ownership.mjs +88 -0
  173. package/dist/src/rules/activation.d.mts +21 -0
  174. package/dist/src/rules/activation.mjs +62 -0
  175. package/dist/src/rules/inline-budget.d.mts +26 -0
  176. package/dist/src/rules/inline-budget.mjs +66 -0
  177. package/dist/src/rules/inventory.js +129 -0
  178. package/dist/src/rules/pin-state.js +73 -0
  179. package/dist/src/rules/pins.d.mts +14 -0
  180. package/dist/src/rules/pins.mjs +112 -0
  181. package/dist/src/rules/rule-frontmatter.schema.json +197 -0
  182. package/dist/src/serve/mission-conductor.js +38 -12
  183. package/dist/src/serve/worker-invocation.js +30 -10
  184. package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
  185. package/dist/src/sessions/adapter-registry.js +23 -19
  186. package/dist/src/sessions/adapters/claude.js +39 -54
  187. package/dist/src/sessions/adapters/codex.js +82 -67
  188. package/dist/src/sessions/adapters/copilot.js +4 -4
  189. package/dist/src/sessions/adapters/cursor.js +40 -57
  190. package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
  191. package/dist/src/sessions/adapters/factory.js +31 -27
  192. package/dist/src/sessions/adapters/generic.js +10 -10
  193. package/dist/src/sessions/adapters/grok-build.js +11 -11
  194. package/dist/src/sessions/adapters/grokbot.js +24 -13
  195. package/dist/src/sessions/adapters/hermes.js +27 -29
  196. package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
  197. package/dist/src/sessions/adapters/muse.js +150 -93
  198. package/dist/src/sessions/adapters/omp.js +28 -14
  199. package/dist/src/sessions/adapters/openclaw.js +89 -79
  200. package/dist/src/sessions/adapters/opencode.js +27 -24
  201. package/dist/src/sessions/adapters/openhuman.js +18 -60
  202. package/dist/src/sessions/adapters/pi.js +29 -14
  203. package/dist/src/sessions/adapters/warp.js +3 -3
  204. package/dist/src/sessions/adapters/windsurf.js +19 -48
  205. package/dist/src/sessions/batch-contracts.js +16 -8
  206. package/dist/src/sessions/batch-import.js +51 -11
  207. package/dist/src/sessions/contracts.js +40 -2
  208. package/dist/src/sessions/coverage-preflight.js +1 -1
  209. package/dist/src/sessions/export-plan.js +4 -3
  210. package/dist/src/sessions/import-exclusions.js +72 -0
  211. package/dist/src/sessions/import-lease.js +34 -18
  212. package/dist/src/sessions/import-limits.js +37 -0
  213. package/dist/src/sessions/importer.js +60 -38
  214. package/dist/src/sessions/lifecycle.js +45 -0
  215. package/dist/src/sessions/policy.js +133 -25
  216. package/dist/src/sessions/readers.js +106 -53
  217. package/dist/src/sessions/refresh.js +106 -0
  218. package/dist/src/sessions/repository.js +147 -52
  219. package/dist/src/sessions/workspace-discovery.js +221 -56
  220. package/dist/src/sessions/workspace-matching.js +107 -0
  221. package/dist/src/skills/importer.js +1 -0
  222. package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
  223. package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
  224. package/dist/src/smiths/context-pipeline/finalization.js +6 -5
  225. package/dist/src/smiths/context-pipeline/generator.js +21 -4
  226. package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
  227. package/dist/src/smiths/mcpsmith/generator.js +1 -1
  228. package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
  229. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
  230. package/dist/src/storage/backends/fs.js +18 -76
  231. package/dist/src/storage/backends/obsidian.js +1 -0
  232. package/dist/src/tracker/forge-http.js +2 -1
  233. package/dist/src/update/checker.d.mts +2 -0
  234. package/dist/src/update/service.d.mts +22 -0
  235. package/dist/src/utils/contained-filesystem.d.mts +37 -0
  236. package/dist/src/utils/contained-filesystem.mjs +683 -0
  237. package/dist/src/utils/temporary-directory.js +36 -0
  238. package/dist/src/writing/example-generator.js +1 -1
  239. package/package.json +15 -2
  240. package/schemas/models/model-catalog.v1.schema.json +1 -1
  241. package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
  242. package/tools/_resolve-impl.mjs +13 -15
  243. package/tools/agents/deploy-agents.mjs +49 -21
  244. package/tools/agents/providers/base.mjs +104 -134
  245. package/tools/agents/providers/claude.mjs +8 -24
  246. package/tools/agents/providers/codex.mjs +18 -11
  247. package/tools/agents/providers/cursor.mjs +19 -18
  248. package/tools/agents/providers/grok-build.mjs +3 -1
  249. package/tools/agents/providers/hermes.mjs +57 -47
  250. package/tools/agents/providers/openclaw.mjs +9 -4
  251. package/tools/agents/providers/pi.mjs +5 -0
  252. package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
  253. package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
  254. package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
  255. package/tools/agents/providers/rule-delivery.mjs +1270 -0
  256. package/tools/agents/providers/warp.mjs +20 -26
  257. package/tools/cli/decision-offload-report.mjs +40 -0
  258. package/tools/cli/workspace-status.mjs +862 -0
  259. package/tools/plugin/package-plugins.mjs +186 -8
  260. package/tools/plugin/plugin-links.mjs +114 -0
  261. package/tools/providers/antigravity-transport.mjs +3 -1
  262. package/tools/providers/deepseek-harness-transport.mjs +6 -4
  263. package/tools/providers/omp-transport.mjs +4 -2
  264. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
  265. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
  266. package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
  267. package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
  268. package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
  269. package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
  270. package/tools/ralph-external/lib/session-environment.mjs +28 -0
  271. package/tools/ralph-external/lib/systemd-client.mjs +49 -0
  272. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  273. package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
  274. package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
  275. package/tools/ralph-external/process-monitor.mjs +1017 -0
  276. package/tools/ralph-external/session-launcher.mjs +5057 -0
  277. package/tools/scripts/resource-preflight.mjs +541 -0
  278. package/tools/skills/deploy-skills-codex.mjs +2 -1
  279. package/vscode-extension/schemas/aiwg.config.v1.json +1194 -231
@@ -1,8 +1,8 @@
1
1
  import { opendir } from 'node:fs/promises';
2
2
  import { basename, extname, resolve } from 'node:path';
3
3
  import { z } from 'zod';
4
- import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
5
- import { readBoundedJsonLines, streamBoundedJsonLines, } from '../readers.js';
4
+ import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
5
+ import { streamBoundedJsonLines, } from '../readers.js';
6
6
  export const FACTORY_ADAPTER_VERSION = '1.0.0';
7
7
  export const FACTORY_SOURCE_SCHEMA_VERSION = '1.0.0';
8
8
  const FactoryRecordSchema = z.object({
@@ -51,13 +51,21 @@ export class FactorySessionAdapter {
51
51
  for (const root of [...scope.allowedRoots].sort()) {
52
52
  for await (const locator of discoverJsonl(resolve(root), this.discoveryLimits.maxDepth)) {
53
53
  if (++emitted > this.discoveryLimits.maxFiles) {
54
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Factory source discovery exceeded the authorized file limit');
54
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Factory source discovery exceeded the authorized file limit', { limit: 'maxFiles', configured: this.discoveryLimits.maxFiles, observed: emitted });
55
55
  }
56
56
  yield { provider: 'factory', locator, locatorClass: 'factory-droid-jsonl' };
57
57
  }
58
58
  }
59
59
  }
60
60
  async inspect(source) {
61
+ if (source.locatorClass === 'factory-droid-jsonl') {
62
+ const records = this.stream(source);
63
+ let result = await records.next();
64
+ while (!result.done)
65
+ result = await records.next();
66
+ if (result.value)
67
+ return result.value;
68
+ }
61
69
  const parsed = await this.readSource(source);
62
70
  return {
63
71
  sourceSchemaVersion: parsed.schemaVersion,
@@ -76,6 +84,7 @@ export class FactorySessionAdapter {
76
84
  let establishedId;
77
85
  let schemaVersion = null;
78
86
  let sawRecord = false;
87
+ let complete = false;
79
88
  for await (const line of input) {
80
89
  sawRecord = true;
81
90
  const parsed = FactoryRecordSchema.safeParse(line.value);
@@ -85,7 +94,7 @@ export class FactorySessionAdapter {
85
94
  const value = parsed.data;
86
95
  const currentSchema = value.schemaVersion ?? FACTORY_SOURCE_SCHEMA_VERSION;
87
96
  if (schemaVersion && schemaVersion !== currentSchema) {
88
- throw new SessionContractError('SCHEMA_DRIFT', 'mixed Factory source schemas');
97
+ throw new SessionContractError('SCHEMA_DRIFT', 'mixed Factory source schemas', schemaIssueDetail('schemaVersion'));
89
98
  }
90
99
  schemaVersion = currentSchema;
91
100
  assertSupportedSchemaMajor(schemaVersion);
@@ -95,44 +104,39 @@ export class FactorySessionAdapter {
95
104
  throw new SessionContractError('DUPLICATE_NATIVE_ID', 'Factory source changes session identity');
96
105
  }
97
106
  establishedId = currentId;
98
- for (const record of normalize([line], source, establishedId).records) {
107
+ const normalized = normalize([line], source, establishedId);
108
+ complete ||= normalized.complete;
109
+ for (const record of normalized.records) {
99
110
  if (outputIndex++ >= start)
100
111
  yield record;
101
112
  }
102
113
  }
103
114
  if (!sawRecord && !input.incompleteTail) {
104
- throw new SessionContractError('MALFORMED_SOURCE', 'Factory session source is empty');
115
+ throw new SessionContractError('MALFORMED_SOURCE', 'Factory session source is empty', schemaIssueDetail('<root>'));
105
116
  }
106
- return;
117
+ return {
118
+ sourceSchemaVersion: schemaVersion ?? FACTORY_SOURCE_SCHEMA_VERSION,
119
+ consistency: complete && !input.incompleteTail ? 'complete' : 'provisional',
120
+ operationalState: 'available',
121
+ };
107
122
  }
108
123
  const parsed = await this.readSource(source);
109
124
  const start = parseCursor(cursor?.value);
110
125
  for (const record of parsed.records.slice(start))
111
126
  yield record;
127
+ return undefined;
112
128
  }
113
129
  async readSource(source) {
114
- let input;
115
- let incompleteTail = false;
116
- if (source.locatorClass === 'factory-droid-jsonl') {
117
- const result = await readBoundedJsonLines({
118
- selectedPath: source.locator,
119
- allowedRoots: source.authorizedScope.allowedRoots,
120
- }, { consistency: 'provisional', limits: this.limits });
121
- input = result.records;
122
- incompleteTail = result.incompleteTail;
123
- }
124
- else {
125
- input = await this.remoteRecords(source);
126
- }
127
- if (input.length === 0 && !incompleteTail) {
128
- throw new SessionContractError('MALFORMED_SOURCE', 'Factory session source is empty');
130
+ const input = await this.remoteRecords(source);
131
+ if (input.length === 0) {
132
+ throw new SessionContractError('MALFORMED_SOURCE', 'Factory session source is empty', schemaIssueDetail('<root>'));
129
133
  }
130
134
  const schemaVersion = declaredVersion(input);
131
135
  assertSupportedSchemaMajor(schemaVersion);
132
136
  const normalized = normalize(input, source);
133
137
  return {
134
138
  schemaVersion,
135
- consistency: normalized.complete && !incompleteTail ? 'complete' : 'provisional',
139
+ consistency: normalized.complete ? 'complete' : 'provisional',
136
140
  records: normalized.records,
137
141
  };
138
142
  }
@@ -155,7 +159,7 @@ export class FactorySessionAdapter {
155
159
  }
156
160
  const values = await transport.snapshot(source);
157
161
  if (values.length > (this.limits?.maxRecords ?? 1_000_000)) {
158
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Factory remote record limit exceeded');
162
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Factory remote record limit exceeded', { limit: 'maxRecords', configured: this.limits?.maxRecords ?? 1_000_000, observed: values.length });
159
163
  }
160
164
  return values.map((value, sequence) => ({
161
165
  value, sequence, byteOffset: sequence, byteLength: Buffer.byteLength(JSON.stringify(value)),
@@ -330,7 +334,7 @@ function factoryMalformedRecord(source, record, error) {
330
334
  ? issue.path.map(String).join('.')
331
335
  : 'record';
332
336
  return new SessionContractError('MALFORMED_SOURCE', `Factory source ${basename(source.locator)} record ${record.sequence + 1} `
333
- + `type ${type} failed requirement ${requirement} (${issue?.code ?? 'invalid_type'})`);
337
+ + `type ${type} failed requirement ${requirement} (${issue?.code ?? 'invalid_type'})`, schemaRejectionDetail(error));
334
338
  }
335
339
  function declaredVersion(records) {
336
340
  const versions = new Set(records.flatMap((record) => {
@@ -338,7 +342,7 @@ function declaredVersion(records) {
338
342
  return typeof value.schemaVersion === 'string' ? [value.schemaVersion] : [];
339
343
  }));
340
344
  if (versions.size > 1)
341
- throw new SessionContractError('SCHEMA_DRIFT', 'mixed Factory schema versions');
345
+ throw new SessionContractError('SCHEMA_DRIFT', 'mixed Factory schema versions', schemaIssueDetail('schemaVersion'));
342
346
  return [...versions][0] ?? FACTORY_SOURCE_SCHEMA_VERSION;
343
347
  }
344
348
  function timestamp(value) {
@@ -375,7 +379,7 @@ function parseCursor(value) {
375
379
  if (!value)
376
380
  return 0;
377
381
  if (!/^\d+$/.test(value))
378
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid Factory cursor');
382
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid Factory cursor', schemaIssueDetail('cursor'));
379
383
  return Number(value);
380
384
  }
381
385
  const RECORD_KEYS = new Set([
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
2
+ import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
3
3
  import { streamBoundedJsonLines } from '../readers.js';
4
4
  export const GENERIC_INTERCHANGE_KIND = 'aiwg.session-interchange';
5
5
  export const GENERIC_ADAPTER_VERSION = '1.0.0';
@@ -80,7 +80,7 @@ export class GenericSessionInterchangeAdapter {
80
80
  sessionSequences.add(sequenceKey);
81
81
  }
82
82
  if (!header)
83
- throw new SessionContractError('MALFORMED_SOURCE', 'generic interchange is empty');
83
+ throw new SessionContractError('MALFORMED_SOURCE', 'generic interchange is empty', schemaIssueDetail('<root>'));
84
84
  if (input.incompleteTail) {
85
85
  throw new SessionContractError('TRUNCATED_SOURCE', 'generic interchange contains a truncated record');
86
86
  }
@@ -100,7 +100,7 @@ export class GenericSessionInterchangeAdapter {
100
100
  const first = await iterator.next();
101
101
  await iterator.return?.();
102
102
  if (first.done)
103
- throw new SessionContractError('MALFORMED_SOURCE', 'generic interchange is empty');
103
+ throw new SessionContractError('MALFORMED_SOURCE', 'generic interchange is empty', schemaIssueDetail('<root>'));
104
104
  header = this.validateHeader(first.value.value, source);
105
105
  }
106
106
  const input = await this.openStream(source, byteCursor ?? undefined);
@@ -160,7 +160,7 @@ export class GenericSessionInterchangeAdapter {
160
160
  };
161
161
  }
162
162
  if (!header) {
163
- throw new SessionContractError('MALFORMED_SOURCE', 'generic interchange is empty');
163
+ throw new SessionContractError('MALFORMED_SOURCE', 'generic interchange is empty', schemaIssueDetail('<root>'));
164
164
  }
165
165
  if (input.incompleteTail) {
166
166
  throw new SessionContractError('TRUNCATED_SOURCE', 'generic interchange contains a truncated record');
@@ -183,7 +183,7 @@ export class GenericSessionInterchangeAdapter {
183
183
  // SCHEMA_DRIFT: the selected identity no longer describes this file's
184
184
  // declared source, so importing it would attach events to the wrong source.
185
185
  throw new SessionContractError('SCHEMA_DRIFT', `selected source identity does not match interchange header: --source-id ${quoteValue(source.sourceId)} `
186
- + `but header sourceId is ${quoteValue(header.sourceId)}; pass --source-id ${quoteValue(header.sourceId)}`);
186
+ + `but header sourceId is ${quoteValue(header.sourceId)}; pass --source-id ${quoteValue(header.sourceId)}`, schemaIssueDetail('sourceId'));
187
187
  }
188
188
  return header;
189
189
  }
@@ -193,7 +193,7 @@ function parseByteCursor(value) {
193
193
  return null;
194
194
  const position = value.slice(5);
195
195
  if (!/^\d+$/.test(position)) {
196
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid generic byte cursor');
196
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid generic byte cursor', schemaIssueDetail('cursor'));
197
197
  }
198
198
  return Number(position);
199
199
  }
@@ -205,9 +205,9 @@ function parseHeader(value) {
205
205
  throw new SessionContractError('AMBIGUOUS_TIMESTAMP', 'generic interchange header has an ambiguous timestamp');
206
206
  }
207
207
  if (!isDeclaredInterchange(value)) {
208
- throw new SessionContractError('MALFORMED_SOURCE', 'input is not a declared AIWG generic session interchange');
208
+ throw new SessionContractError('MALFORMED_SOURCE', 'input is not a declared AIWG generic session interchange', schemaRejectionDetail(result.error));
209
209
  }
210
- throw new SessionContractError('MALFORMED_SOURCE', `generic interchange header ${describeIssue(result.error, true)}`);
210
+ throw new SessionContractError('MALFORMED_SOURCE', `generic interchange header ${describeIssue(result.error, true)}`, schemaRejectionDetail(result.error));
211
211
  }
212
212
  function parseEvent(value) {
213
213
  const result = GenericInterchangeEventSchema.safeParse(value);
@@ -218,7 +218,7 @@ function parseEvent(value) {
218
218
  }
219
219
  // Event values can carry transcript content, so name the field and the
220
220
  // expected shape without echoing the received value.
221
- throw new SessionContractError('MALFORMED_SOURCE', `generic interchange event is malformed: ${describeIssue(result.error, false)}`);
221
+ throw new SessionContractError('MALFORMED_SOURCE', `generic interchange event is malformed: ${describeIssue(result.error, false)}`, schemaRejectionDetail(result.error));
222
222
  }
223
223
  function isDeclaredInterchange(value) {
224
224
  return typeof value === 'object' && value !== null
@@ -253,7 +253,7 @@ function parseEventCursor(value) {
253
253
  if (value === undefined || value === '')
254
254
  return 0;
255
255
  if (!/^\d+$/.test(value))
256
- throw new SessionContractError('SCHEMA_DRIFT', 'generic event cursor is invalid');
256
+ throw new SessionContractError('SCHEMA_DRIFT', 'generic event cursor is invalid', schemaIssueDetail('cursor'));
257
257
  return Number(value);
258
258
  }
259
259
  //# sourceMappingURL=generic.js.map
@@ -1,5 +1,5 @@
1
1
  import { basename, extname } from 'node:path';
2
- import { SessionContractError, } from '../contracts.js';
2
+ import { SessionContractError, schemaIssueDetail, } from '../contracts.js';
3
3
  import { readBoundedText } from '../readers.js';
4
4
  export const GROK_BUILD_ADAPTER_VERSION = '1.0.0';
5
5
  export const GROK_BUILD_CLI_EXPORT_SCHEMA_VERSION = '1.0.0';
@@ -75,7 +75,7 @@ export class GrokBuildSessionAdapter {
75
75
  }
76
76
  const filename = basename(source.locator, extname(source.locator));
77
77
  if (extname(source.locator).toLowerCase() !== '.md' || !UUID.test(filename)) {
78
- throw new SessionContractError('MALFORMED_SOURCE', 'Grok Build export filename must be <session-id>.md so the documented CLI session ID is preserved.');
78
+ throw new SessionContractError('MALFORMED_SOURCE', 'Grok Build export filename must be <session-id>.md so the documented CLI session ID is preserved.', schemaIssueDetail('filename'));
79
79
  }
80
80
  const { value } = await readBoundedText({
81
81
  selectedPath: source.locator,
@@ -93,7 +93,7 @@ export function validateGrokBuildMarkdown(value) {
93
93
  parseTools(block.text, sequence);
94
94
  }
95
95
  if (blocks.length === 0 || !blocks.some((block) => block.heading === 'User')) {
96
- throw new SessionContractError('MALFORMED_SOURCE', 'Unrecognized Grok Build CLI export; expected exact `## User`, `## Assistant`, or `## Tools` headings.');
96
+ throw new SessionContractError('MALFORMED_SOURCE', 'Unrecognized Grok Build CLI export; expected exact `## User`, `## Assistant`, or `## Tools` headings.', schemaIssueDetail('headings'));
97
97
  }
98
98
  return blocks;
99
99
  }
@@ -105,7 +105,7 @@ function parseBlocks(value) {
105
105
  return;
106
106
  const text = current.lines.join('\n').trim();
107
107
  if (!text && current.heading === 'Tools') {
108
- throw new SessionContractError('SCHEMA_DRIFT', 'Grok Build tools block is empty');
108
+ throw new SessionContractError('SCHEMA_DRIFT', 'Grok Build tools block is empty', schemaIssueDetail('tools'));
109
109
  }
110
110
  if (text)
111
111
  blocks.push({ heading: current.heading, text });
@@ -117,13 +117,13 @@ function parseBlocks(value) {
117
117
  current = { heading: match[1], lines: [] };
118
118
  }
119
119
  else if (/^##\s+/.test(line)) {
120
- throw new SessionContractError('SCHEMA_DRIFT', `unknown Grok Build CLI export heading: ${line}`);
120
+ throw new SessionContractError('SCHEMA_DRIFT', `unknown Grok Build CLI export heading: ${line}`, schemaIssueDetail('headings'));
121
121
  }
122
122
  else if (current) {
123
123
  current.lines.push(line);
124
124
  }
125
125
  else if (line.trim()) {
126
- throw new SessionContractError('SCHEMA_DRIFT', 'unexpected content before the first Grok Build CLI export heading');
126
+ throw new SessionContractError('SCHEMA_DRIFT', 'unexpected content before the first Grok Build CLI export heading', schemaIssueDetail('headings'));
127
127
  }
128
128
  }
129
129
  flush();
@@ -132,17 +132,17 @@ function parseBlocks(value) {
132
132
  function parseTools(text, blockSequence) {
133
133
  const lines = text.split('\n').filter((line) => line.trim());
134
134
  if (lines.length === 0 || lines.length > 1_000) {
135
- throw new SessionContractError('SCHEMA_DRIFT', 'Grok Build tools block has no summaries or exceeds 1,000 summaries');
135
+ throw new SessionContractError('SCHEMA_DRIFT', 'Grok Build tools block has no summaries or exceeds 1,000 summaries', schemaIssueDetail('tools'));
136
136
  }
137
137
  return lines.map((line, index) => {
138
138
  const match = /^- (.+)$/.exec(line);
139
139
  if (!match) {
140
- throw new SessionContractError('SCHEMA_DRIFT', `unrecognized Grok Build tool summary: ${line}`);
140
+ throw new SessionContractError('SCHEMA_DRIFT', `unrecognized Grok Build tool summary: ${line}`, schemaIssueDetail('tools'));
141
141
  }
142
142
  const summary = match[1];
143
143
  const name = summary.split(/:| \(/, 1)[0]?.trim();
144
144
  if (!name) {
145
- throw new SessionContractError('SCHEMA_DRIFT', 'empty Grok Build tool summary');
145
+ throw new SessionContractError('SCHEMA_DRIFT', 'empty Grok Build tool summary', schemaIssueDetail('tools'));
146
146
  }
147
147
  return { sequence: blockSequence * 1_000 + index, name, text: summary };
148
148
  });
@@ -176,11 +176,11 @@ function parseCursor(value) {
176
176
  if (!value)
177
177
  return 0;
178
178
  if (!/^\d+$/.test(value)) {
179
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid Grok Build export cursor');
179
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid Grok Build export cursor', schemaIssueDetail('cursor'));
180
180
  }
181
181
  const cursor = Number(value);
182
182
  if (!Number.isSafeInteger(cursor)) {
183
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid Grok Build export cursor');
183
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid Grok Build export cursor', schemaIssueDetail('cursor'));
184
184
  }
185
185
  return cursor;
186
186
  }
@@ -55,7 +55,7 @@
55
55
  import { opendir } from 'node:fs/promises';
56
56
  import { resolve } from 'node:path';
57
57
  import { z } from 'zod';
58
- import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
58
+ import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
59
59
  import { readBoundedJson } from '../readers.js';
60
60
  import { GenericSessionInterchangeAdapter, } from './generic.js';
61
61
  import { classifyGrokbotBlobName, grokbotConversationIdFromLocator, } from './grokbot-locator.js';
@@ -130,10 +130,10 @@ const NoticeEntrySchema = GrokbotEntryBaseSchema.extend({
130
130
  });
131
131
  const GrokbotReplicaValueSchema = z.object({
132
132
  entries: z.array(GrokbotEntryBaseSchema),
133
- epochHint: z.string().optional(),
134
- acceptedSequenceHint: z.number().int().nonnegative().optional(),
135
- persistedAt: z.number().int().nonnegative().optional(),
136
- precedingTimestampMs: z.number().int().nonnegative().optional(),
133
+ epochHint: z.string().nullish(),
134
+ acceptedSequenceHint: z.number().int().nonnegative().nullish(),
135
+ persistedAt: z.number().int().nonnegative().nullish(),
136
+ precedingTimestampMs: z.number().int().nonnegative().nullish(),
137
137
  }).passthrough();
138
138
  const GrokbotReplicaDocumentSchema = z.object({
139
139
  schemaVersion: z.number().int().nonnegative(),
@@ -181,7 +181,7 @@ export class GrokbotSessionAdapter {
181
181
  if (classification?.kind !== 'transcript-replica')
182
182
  continue;
183
183
  if (++count > this.maxFiles) {
184
- throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Grok Bot native discovery exceeded the authorized file limit');
184
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Grok Bot native discovery exceeded the authorized file limit', { limit: 'maxFiles', configured: this.maxFiles, observed: count });
185
185
  }
186
186
  yield {
187
187
  provider: 'grokbot',
@@ -268,7 +268,7 @@ export class GrokbotSessionAdapter {
268
268
  }, this.limits);
269
269
  const parsed = GrokbotReplicaDocumentSchema.safeParse(value);
270
270
  if (!parsed.success) {
271
- throw new SessionContractError('MALFORMED_SOURCE', 'Grok Bot transcript replica is malformed');
271
+ throw malformedGrokbotSource('transcript replica', parsed.error);
272
272
  }
273
273
  assertSupportedSchemaMajor(`${parsed.data.schemaVersion}.0.0`, GROKBOT_NATIVE_SCHEMA_MAJOR);
274
274
  const { entries, precedingTimestampMs } = parsed.data.value;
@@ -287,7 +287,7 @@ function parseNativeCursor(value) {
287
287
  return 0;
288
288
  const raw = value.startsWith('native:') ? value.slice('native:'.length) : value;
289
289
  if (!/^\d+$/.test(raw))
290
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid Grok Bot native cursor');
290
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid Grok Bot native cursor', schemaIssueDetail('cursor'));
291
291
  return Number(raw);
292
292
  }
293
293
  function mapGrokbotEntry(entry) {
@@ -308,10 +308,21 @@ function mapGrokbotEntry(entry) {
308
308
  return null;
309
309
  }
310
310
  }
311
+ function malformedGrokbotSource(subject, error) {
312
+ // Include only schema paths and codes, never Zod messages or input values.
313
+ const issues = error.issues.map((issue) => `${issue.path.join('.') || '<root>'} (${issue.code})`);
314
+ return new SessionContractError('MALFORMED_SOURCE', `Grok Bot ${subject} is malformed at ${issues.join(', ')}`, schemaRejectionDetail(error));
315
+ }
311
316
  function mapSendMessage(raw, occurredAt) {
317
+ const messageType = z.object({ type: z.string().min(1) }).safeParse(raw.message);
318
+ if (messageType.success && !SendMessageEntrySchema.shape.message.options.some((option) => option.shape.type.value === messageType.data.type)) {
319
+ // Like unknown entry kinds, unknown message types are skipped without inventing content.
320
+ // Known types still go through their full schema validation below.
321
+ return null;
322
+ }
312
323
  const parsed = SendMessageEntrySchema.safeParse(raw);
313
324
  if (!parsed.success) {
314
- throw new SessionContractError('MALFORMED_SOURCE', 'Grok Bot send-message entry is malformed');
325
+ throw malformedGrokbotSource('send-message entry', parsed.error);
315
326
  }
316
327
  const entry = parsed.data;
317
328
  const { message } = entry;
@@ -359,7 +370,7 @@ function mapSendMessage(raw, occurredAt) {
359
370
  function mapMessage(raw, occurredAt) {
360
371
  const parsed = MessageEntrySchema.safeParse(raw);
361
372
  if (!parsed.success) {
362
- throw new SessionContractError('MALFORMED_SOURCE', 'Grok Bot message entry is malformed');
373
+ throw malformedGrokbotSource('message entry', parsed.error);
363
374
  }
364
375
  const entry = parsed.data;
365
376
  return {
@@ -380,7 +391,7 @@ function mapMessage(raw, occurredAt) {
380
391
  function mapUserAttachment(raw, occurredAt) {
381
392
  const parsed = UserAttachmentEntrySchema.safeParse(raw);
382
393
  if (!parsed.success) {
383
- throw new SessionContractError('MALFORMED_SOURCE', 'Grok Bot user-attachment entry is malformed');
394
+ throw malformedGrokbotSource('user-attachment entry', parsed.error);
384
395
  }
385
396
  const entry = parsed.data;
386
397
  return {
@@ -405,7 +416,7 @@ function mapUserAttachment(raw, occurredAt) {
405
416
  function mapEvent(raw, occurredAt) {
406
417
  const parsed = EventEntrySchema.safeParse(raw);
407
418
  if (!parsed.success) {
408
- throw new SessionContractError('MALFORMED_SOURCE', 'Grok Bot event entry is malformed');
419
+ throw malformedGrokbotSource('event entry', parsed.error);
409
420
  }
410
421
  const entry = parsed.data;
411
422
  const label = stringOrEmpty(entry.event.kind) || stringOrEmpty(entry.event.type) || 'event';
@@ -426,7 +437,7 @@ function mapEvent(raw, occurredAt) {
426
437
  function mapNotice(raw, occurredAt) {
427
438
  const parsed = NoticeEntrySchema.safeParse(raw);
428
439
  if (!parsed.success) {
429
- throw new SessionContractError('MALFORMED_SOURCE', 'Grok Bot notice entry is malformed');
440
+ throw malformedGrokbotSource('notice entry', parsed.error);
430
441
  }
431
442
  const entry = parsed.data;
432
443
  return {
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
- import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
3
- import { readBoundedJsonLines, streamBoundedJsonLines, } from '../readers.js';
2
+ import { schemaRejectionDetail, schemaIssueDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
3
+ import { streamBoundedJsonLines, } from '../readers.js';
4
4
  export const HERMES_ADAPTER_VERSION = '1.0.0';
5
5
  export const HERMES_EXPORT_SCHEMA_VERSION = '1.0.0';
6
6
  export const HERMES_NATIVE_SCHEMA_VERSION = '23.0.0';
@@ -73,6 +73,15 @@ export class HermesSessionAdapter {
73
73
  // Native exports and consistent snapshots require explicit selection.
74
74
  }
75
75
  async inspect(source) {
76
+ if (source.locatorClass === 'hermes-export-jsonl'
77
+ || source.locatorClass === 'hermes-consistent-snapshot-jsonl') {
78
+ const records = this.stream(source);
79
+ let result = await records.next();
80
+ while (!result.done)
81
+ result = await records.next();
82
+ if (result.value)
83
+ return result.value;
84
+ }
76
85
  const parsed = await this.readSource(source);
77
86
  return {
78
87
  sourceSchemaVersion: parsed.schemaVersion,
@@ -94,21 +103,23 @@ export class HermesSessionAdapter {
94
103
  let outputIndex = 0;
95
104
  let schema = null;
96
105
  let sawRecord = false;
106
+ let complete = true;
97
107
  for await (const line of input) {
98
108
  sawRecord = true;
99
109
  const parsed = ExportSchema.safeParse(line.value);
100
110
  if (!parsed.success) {
101
- throw new SessionContractError('MALFORMED_SOURCE', 'Hermes export is malformed');
111
+ throw new SessionContractError('MALFORMED_SOURCE', 'Hermes export is malformed', schemaRejectionDetail(parsed.error));
102
112
  }
113
+ complete &&= ended(parsed.data);
103
114
  const currentSchema = declaredSchema(parsed.data);
104
115
  if (schema && schema !== currentSchema) {
105
- throw new SessionContractError('SCHEMA_DRIFT', 'mixed Hermes export schemas');
116
+ throw new SessionContractError('SCHEMA_DRIFT', 'mixed Hermes export schemas', schemaIssueDetail('schemaVersion'));
106
117
  }
107
118
  schema = currentSchema;
108
119
  assertSupportedSchemaMajor(schema, 23);
109
120
  if (source.locatorClass === 'hermes-consistent-snapshot-jsonl'
110
121
  && parsed.data.snapshotConsistency !== 'sqlite-backup') {
111
- throw new SessionContractError('SCHEMA_DRIFT', 'Hermes snapshot lacks sqlite3.backup() consistency evidence');
122
+ throw new SessionContractError('SCHEMA_DRIFT', 'Hermes snapshot lacks sqlite3.backup() consistency evidence', schemaIssueDetail('snapshotConsistency'));
112
123
  }
113
124
  for (const record of normalizeSession(parsed.data, line, source.locatorClass)) {
114
125
  if (outputIndex++ >= start)
@@ -116,16 +127,21 @@ export class HermesSessionAdapter {
116
127
  }
117
128
  }
118
129
  if (!sawRecord)
119
- throw new SessionContractError('MALFORMED_SOURCE', 'Hermes source is empty');
130
+ throw new SessionContractError('MALFORMED_SOURCE', 'Hermes source is empty', schemaIssueDetail('<root>'));
120
131
  if (input.incompleteTail && source.locatorClass !== 'hermes-export-jsonl') {
121
132
  throw new SessionContractError('TRUNCATED_SOURCE', 'Hermes consistent snapshot is truncated');
122
133
  }
123
- return;
134
+ return {
135
+ sourceSchemaVersion: HERMES_EXPORT_SCHEMA_VERSION,
136
+ consistency: complete ? 'complete' : 'provisional',
137
+ operationalState: 'available',
138
+ };
124
139
  }
125
140
  const parsed = await this.readSource(source);
126
141
  const start = parseCursor(cursor?.value);
127
142
  for (const record of parsed.records.slice(start))
128
143
  yield record;
144
+ return undefined;
129
145
  }
130
146
  async readSource(source) {
131
147
  if (source.locatorClass === 'hermes-state-db') {
@@ -143,37 +159,19 @@ export class HermesSessionAdapter {
143
159
  value, sequence, byteOffset: sequence, byteLength: Buffer.byteLength(JSON.stringify(value)),
144
160
  }));
145
161
  }
146
- else if (source.locatorClass === 'hermes-export-jsonl'
147
- || source.locatorClass === 'hermes-consistent-snapshot-jsonl') {
148
- const result = await readBoundedJsonLines({
149
- selectedPath: source.locator,
150
- allowedRoots: source.authorizedScope.allowedRoots,
151
- }, {
152
- consistency: source.locatorClass === 'hermes-export-jsonl' ? 'provisional' : 'complete',
153
- limits: this.limits,
154
- });
155
- input = result.records;
156
- if (result.incompleteTail && source.locatorClass !== 'hermes-export-jsonl') {
157
- throw new SessionContractError('TRUNCATED_SOURCE', 'Hermes consistent snapshot is truncated');
158
- }
159
- }
160
162
  else {
161
163
  throw new SessionContractError('UNSUPPORTED_OPERATION', 'unsupported Hermes source class');
162
164
  }
163
165
  if (input.length === 0)
164
- throw new SessionContractError('MALFORMED_SOURCE', 'Hermes source is empty');
166
+ throw new SessionContractError('MALFORMED_SOURCE', 'Hermes source is empty', schemaIssueDetail('<root>'));
165
167
  const parsed = input.map((line) => {
166
168
  const result = ExportSchema.safeParse(line.value);
167
169
  if (!result.success)
168
- throw new SessionContractError('MALFORMED_SOURCE', 'Hermes export is malformed');
170
+ throw new SessionContractError('MALFORMED_SOURCE', 'Hermes export is malformed', schemaRejectionDetail(result.error));
169
171
  return { line, value: result.data };
170
172
  });
171
173
  const schemaVersion = declaredVersion(parsed.map(({ value }) => value));
172
174
  assertSupportedSchemaMajor(schemaVersion, 23);
173
- if (source.locatorClass === 'hermes-consistent-snapshot-jsonl'
174
- && parsed.some(({ value }) => value.snapshotConsistency !== 'sqlite-backup')) {
175
- throw new SessionContractError('SCHEMA_DRIFT', 'Hermes snapshot lacks sqlite3.backup() consistency evidence');
176
- }
177
175
  return {
178
176
  schemaVersion: HERMES_EXPORT_SCHEMA_VERSION,
179
177
  consistency: parsed.every(({ value }) => ended(value)) ? 'complete' : 'provisional',
@@ -291,7 +289,7 @@ function ended(value) {
291
289
  function declaredVersion(values) {
292
290
  const versions = new Set(values.map(declaredSchema));
293
291
  if (versions.size !== 1)
294
- throw new SessionContractError('SCHEMA_DRIFT', 'mixed Hermes export schemas');
292
+ throw new SessionContractError('SCHEMA_DRIFT', 'mixed Hermes export schemas', schemaIssueDetail('schemaVersion'));
295
293
  return [...versions][0];
296
294
  }
297
295
  function declaredSchema(value) {
@@ -330,7 +328,7 @@ function parseCursor(value) {
330
328
  if (!value)
331
329
  return 0;
332
330
  if (!/^\d+$/.test(value))
333
- throw new SessionContractError('SCHEMA_DRIFT', 'invalid Hermes cursor');
331
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid Hermes cursor', schemaIssueDetail('cursor'));
334
332
  return Number(value);
335
333
  }
336
334
  const SESSION_KEYS = new Set([