@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,7 +1,8 @@
1
1
  import { requireFeaturePackage } from '../features/runtime.js';
2
2
  import { CandidateReviewReceiptSchema, DeletionReceiptSchema, IntelligenceCandidateSchema, PromotionDependencyDecisionSchema, PromotionReceiptSchema, SessionContractError, sha256, } from './contracts.js';
3
3
  import { coverageFromBatchRun, } from './batch-contracts.js';
4
- import { redactSessionText, sanitizeNativeExtensions } from './policy.js';
4
+ import { redactSessionText, redactSessionValue, sanitizeSessionEvent, sanitizeNativeExtensions } from './policy.js';
5
+ import { effectiveSessionLifecycle } from './lifecycle.js';
5
6
  import { deriveSessionAnalytics, } from './analytics.js';
6
7
  const POLICY_PROVIDER_MIGRATION = 'policy-provider-identity:v2';
7
8
  const EVENT_ORIGIN_INTENT_MIGRATION = 'event-origin-intent:v1';
@@ -124,6 +125,12 @@ export class SessionRepository {
124
125
  ON sessions(workspace_id, source_id, lifecycle);
125
126
  CREATE INDEX IF NOT EXISTS idx_event_session_sequence
126
127
  ON session_events(session_id, sequence_no);
128
+ CREATE INDEX IF NOT EXISTS idx_event_source_run
129
+ ON session_events(source_id, import_run_id);
130
+ CREATE INDEX IF NOT EXISTS idx_event_import_run_session
131
+ ON session_events(import_run_id, session_id);
132
+ CREATE INDEX IF NOT EXISTS idx_session_analytics_session
133
+ ON session_analytics_facts(session_id);
127
134
  CREATE INDEX IF NOT EXISTS idx_session_analytics_scope
128
135
  ON session_analytics_facts(
129
136
  workspace_id, category, provider, status, occurred_at, fact_id
@@ -131,6 +138,9 @@ export class SessionRepository {
131
138
  CREATE VIRTUAL TABLE IF NOT EXISTS session_event_fts USING fts5(
132
139
  event_id UNINDEXED, searchable_text, tokenize='unicode61'
133
140
  );
141
+ -- FTS5 stores event_id in c0. A covering index checks identity/rowid
142
+ -- alignment without reading searchable text or event JSON.
143
+ CREATE INDEX IF NOT EXISTS idx_fts_event_id ON session_event_fts_content(c0);
134
144
  `);
135
145
  const deletionReceiptColumns = this.db.prepare('PRAGMA table_info(deletion_receipts)').all().map((row) => String(row.name));
136
146
  if (!deletionReceiptColumns.includes('workspace_id')) {
@@ -154,48 +164,79 @@ export class SessionRepository {
154
164
  CREATE INDEX IF NOT EXISTS idx_mutation_audit_workspace
155
165
  ON mutation_audit(workspace_id, occurred_at, operation_id)
156
166
  ;
167
+ CREATE INDEX IF NOT EXISTS idx_import_runs_batch
168
+ ON import_runs(batch_run_id);
157
169
  CREATE INDEX IF NOT EXISTS idx_batch_import_workspace
158
170
  ON batch_import_runs(workspace_id, updated_at, batch_run_id)
159
171
  `);
172
+ this.initializeAnalyticsVerification();
160
173
  this.alignSessionFtsRowids();
161
174
  this.migrateSessionPolicyAndProviderIdentity();
162
175
  this.migrateEventOriginAndIntent();
163
176
  this.alignSessionAnalytics();
164
177
  }
178
+ initializeAnalyticsVerification() {
179
+ // Invalidate verification even for writes from another connection. Ordinary
180
+ // repository mutations maintain analytics in the same transaction below.
181
+ this.db.transaction(() => {
182
+ this.db.prepare('INSERT OR IGNORE INTO session_catalog_meta(key, value) VALUES (?, ?)').run('consistency:analytics:v1', 'dirty');
183
+ for (const table of [
184
+ 'session_events', 'session_analytics_facts', 'sessions', 'session_sources', 'import_runs',
185
+ ]) {
186
+ for (const operation of ['INSERT', 'UPDATE', 'DELETE']) {
187
+ this.db.exec(`
188
+ CREATE TRIGGER IF NOT EXISTS consistency_${table}_${operation.toLowerCase()}
189
+ AFTER ${operation} ON ${table}
190
+ BEGIN
191
+ UPDATE session_catalog_meta SET value='dirty'
192
+ WHERE key='consistency:analytics:v1' AND value!='dirty';
193
+ END;
194
+ `);
195
+ }
196
+ }
197
+ })();
198
+ }
199
+ analyticsVerified() {
200
+ return this.db.prepare('SELECT value FROM session_catalog_meta WHERE key=?').get('consistency:analytics:v1')?.value === 'verified';
201
+ }
202
+ markAnalyticsVerified() {
203
+ this.db.prepare('UPDATE session_catalog_meta SET value=? WHERE key=?').run('verified', 'consistency:analytics:v1');
204
+ }
205
+ maintainedAnalyticsTransaction(mutate) {
206
+ return this.db.transaction(() => {
207
+ const verified = this.analyticsVerified();
208
+ const result = mutate();
209
+ // A scoped mutation must not hide pre-existing damage elsewhere.
210
+ if (verified)
211
+ this.markAnalyticsVerified();
212
+ return result;
213
+ });
214
+ }
165
215
  alignSessionAnalytics() {
166
- const eventCount = Number(this.db.prepare(`SELECT COUNT(*) AS count
167
- FROM session_events e
168
- JOIN import_runs r ON r.import_run_id=e.import_run_id
169
- JOIN sessions s ON s.session_id=e.session_id
170
- WHERE r.status='committed' AND s.lifecycle!='tombstoned'`).get()?.count ?? 0);
171
- const factSessions = Number(this.db.prepare('SELECT COUNT(DISTINCT session_id) AS count FROM session_analytics_facts').get()?.count ?? 0);
172
- const sourceSessions = Number(this.db.prepare(`SELECT COUNT(DISTINCT e.session_id) AS count
173
- FROM session_events e
174
- JOIN import_runs r ON r.import_run_id=e.import_run_id
175
- JOIN sessions s ON s.session_id=e.session_id
176
- WHERE r.status='committed' AND s.lifecycle!='tombstoned'`).get()?.count ?? 0);
177
- if (eventCount === 0 || factSessions === sourceSessions)
178
- return;
179
- this.rebuildAnalytics();
216
+ this.db.transaction(() => {
217
+ if (this.analyticsVerified())
218
+ return;
219
+ this.rebuildAnalytics();
220
+ })();
180
221
  }
181
222
  alignSessionFtsRowids() {
182
- const eventCount = Number(this.db.prepare('SELECT COUNT(*) AS count FROM session_events').get()?.count ?? 0);
183
- const ftsCount = Number(this.db.prepare('SELECT COUNT(*) AS count FROM session_event_fts').get()?.count ?? 0);
184
- const mismatches = Number(this.db.prepare(`SELECT COUNT(*) AS count
185
- FROM session_event_fts f
186
- LEFT JOIN session_events e ON e.rowid=f.rowid
187
- WHERE e.rowid IS NULL OR e.event_id!=f.event_id`).get()?.count ?? 0);
188
- if (eventCount === ftsCount && mismatches === 0)
189
- return;
190
- const rebuild = this.db.transaction(() => {
223
+ this.db.transaction(() => {
224
+ const eventCount = Number(this.db.prepare('SELECT COUNT(*) AS count FROM session_events').get()?.count ?? 0);
225
+ const ftsCount = Number(this.db.prepare('SELECT COUNT(*) AS count FROM session_event_fts_content').get()?.count ?? 0);
226
+ const mismatch = this.db.prepare(`SELECT 1 AS mismatch
227
+ FROM session_event_fts_content f
228
+ LEFT JOIN session_events e ON e.event_id=f.c0
229
+ WHERE e.rowid IS NULL OR e.rowid!=f.id
230
+ LIMIT 1`).get();
231
+ if (eventCount === ftsCount && !mismatch)
232
+ return;
191
233
  this.db.exec('DELETE FROM session_event_fts');
192
234
  this.db.exec(`
193
235
  INSERT INTO session_event_fts(rowid, event_id, searchable_text)
194
236
  SELECT rowid, event_id, json_extract(data, '$.searchableText')
195
237
  FROM session_events
196
238
  `);
197
- });
198
- rebuild();
239
+ })();
199
240
  }
200
241
  migrateSessionPolicyAndProviderIdentity() {
201
242
  this.runCatalogMigration(POLICY_PROVIDER_MIGRATION, () => {
@@ -331,6 +372,7 @@ export class SessionRepository {
331
372
  transaction();
332
373
  }
333
374
  emitMutation(input) {
375
+ input = redactSessionValue(input).value;
334
376
  const unsigned = {
335
377
  contractVersion: '1.0.0',
336
378
  ...input,
@@ -435,7 +477,17 @@ export class SessionRepository {
435
477
  };
436
478
  }
437
479
  applyImport(batch, checkpoint, publish = true) {
438
- const apply = this.db.transaction(() => {
480
+ // Guard callers that submit normalized batches directly as well as adapters.
481
+ batch = redactSessionValue({
482
+ ...batch,
483
+ source: { ...batch.source, extensions: sanitizeNativeExtensions(batch.source.extensions).value },
484
+ sessions: batch.sessions.map((session) => ({
485
+ ...session, extensions: sanitizeSessionExtensions(session.extensions, session.provider),
486
+ })),
487
+ events: batch.events.map(sanitizeSessionEvent),
488
+ }).value;
489
+ checkpoint = redactSessionValue(checkpoint).value;
490
+ const apply = this.maintainedAnalyticsTransaction(() => {
439
491
  const existing = this.db.prepare('SELECT outcome, counts, checkpoint FROM import_receipts WHERE import_run_id = ?').get(batch.run.importRunId);
440
492
  if (existing) {
441
493
  const counts = JSON.parse(String(existing.counts));
@@ -541,7 +593,7 @@ export class SessionRepository {
541
593
  return apply();
542
594
  }
543
595
  commitStagedImports(sourceId, parserVersion) {
544
- const commit = this.db.transaction(() => {
596
+ const commit = this.maintainedAnalyticsTransaction(() => {
545
597
  const staged = this.db.prepare(`SELECT import_run_id FROM import_runs
546
598
  WHERE source_id=? AND parser_version=? AND status='staged'`).all(sourceId, parserVersion).map((row) => String(row.import_run_id));
547
599
  const result = this.db.prepare(`UPDATE import_runs SET status='committed'
@@ -569,7 +621,7 @@ export class SessionRepository {
569
621
  WHERE operation_id=?`).run(updated.observedAt, JSON.stringify(updated), operationId);
570
622
  }
571
623
  if (result.changes > 0)
572
- this.rebuildAnalytics();
624
+ this.rebuildImportAnalytics(staged);
573
625
  return result.changes;
574
626
  });
575
627
  return commit();
@@ -578,7 +630,7 @@ export class SessionRepository {
578
630
  if (sourceIds.length === 0)
579
631
  return 0;
580
632
  const placeholders = sourceIds.map(() => '?').join(', ');
581
- const commit = this.db.transaction(() => {
633
+ const commit = this.maintainedAnalyticsTransaction(() => {
582
634
  const params = [batchRunId, ...sourceIds];
583
635
  const staged = this.db.prepare(`SELECT import_run_id FROM import_runs
584
636
  WHERE batch_run_id=? AND source_id IN (${placeholders}) AND status='staged'`).all(...params).map((row) => String(row.import_run_id));
@@ -607,28 +659,39 @@ export class SessionRepository {
607
659
  WHERE operation_id=?`).run(updated.observedAt, JSON.stringify(updated), operationId);
608
660
  }
609
661
  if (result.changes > 0)
610
- this.rebuildAnalytics();
662
+ this.rebuildImportAnalytics(staged);
611
663
  return result.changes;
612
664
  });
613
665
  return commit();
614
666
  }
615
- sourceImportTotals(sourceId, batchRunId) {
616
- const events = Number(this.db.prepare(`SELECT COUNT(*) AS count
667
+ rebuildImportAnalytics(importRunIds) {
668
+ const affected = this.db.prepare(`SELECT DISTINCT s.workspace_id, s.session_id
617
669
  FROM session_events e
618
- JOIN import_runs r ON r.import_run_id=e.import_run_id
619
- WHERE e.source_id=? AND r.batch_run_id=?`).get(sourceId, batchRunId)?.count ?? 0);
620
- const sessions = Number(this.db.prepare(`SELECT COUNT(DISTINCT e.session_id) AS count
670
+ JOIN sessions s ON s.session_id=e.session_id
671
+ WHERE e.import_run_id=?`);
672
+ const sessions = new Map();
673
+ for (const importRunId of importRunIds) {
674
+ for (const row of affected.all(importRunId)) {
675
+ sessions.set(String(row.session_id), String(row.workspace_id));
676
+ }
677
+ }
678
+ for (const [sessionId, workspaceId] of sessions) {
679
+ this.rebuildAnalytics(workspaceId, sessionId);
680
+ }
681
+ }
682
+ sourceImportTotals(sourceId, batchRunId) {
683
+ const totals = this.db.prepare(`SELECT COUNT(*) AS events, COUNT(DISTINCT e.session_id) AS sessions
621
684
  FROM session_events e
622
685
  JOIN import_runs r ON r.import_run_id=e.import_run_id
623
- WHERE e.source_id=? AND r.batch_run_id=?`).get(sourceId, batchRunId)?.count ?? 0);
624
- return { sessions, events };
686
+ WHERE e.source_id=? AND r.batch_run_id=?`).get(sourceId, batchRunId);
687
+ return { sessions: Number(totals?.sessions ?? 0), events: Number(totals?.events ?? 0) };
625
688
  }
626
689
  getSession(id, workspaceId) {
627
690
  const row = this.db.prepare(`SELECT s.data FROM sessions s
628
691
  WHERE s.session_id=? AND ${VISIBLE_SESSION_PREDICATE}
629
692
  ${workspaceId ? 'AND s.workspace_id=?' : ''}`)
630
693
  .get(...(workspaceId ? [id, workspaceId] : [id]));
631
- return row ? JSON.parse(String(row.data)) : null;
694
+ return row ? effectiveSessionLifecycle(JSON.parse(String(row.data))) : null;
632
695
  }
633
696
  findSessionsByReference(reference, workspaceId) {
634
697
  const internal = this.getSession(reference, workspaceId);
@@ -637,7 +700,7 @@ export class SessionRepository {
637
700
  const query = (predicate) => this.db.prepare(`SELECT s.data FROM sessions s
638
701
  WHERE ${workspaceId ? 's.workspace_id=? AND' : ''} ${VISIBLE_SESSION_PREDICATE} AND (${predicate})
639
702
  ORDER BY s.session_id`).all(...(workspaceId ? [workspaceId, reference] : [reference]))
640
- .map((row) => JSON.parse(String(row.data)));
703
+ .map((row) => effectiveSessionLifecycle(JSON.parse(String(row.data))));
641
704
  const exact = query('s.native_session_id=?');
642
705
  if (exact.length === 1)
643
706
  return exact;
@@ -669,7 +732,7 @@ export class SessionRepository {
669
732
  OR substr(s.session_id, 1, length(?))=?)
670
733
  ORDER BY s.session_id`).all(...(workspaceId ? [workspaceId, reference, reference, reference, reference]
671
734
  : [reference, reference, reference, reference]))
672
- .map((row) => JSON.parse(String(row.data)));
735
+ .map((row) => effectiveSessionLifecycle(JSON.parse(String(row.data))));
673
736
  }
674
737
  listWorkspaceSessionCounts() {
675
738
  return this.db.prepare(`SELECT s.workspace_id, COUNT(*) AS count FROM sessions s
@@ -682,6 +745,7 @@ export class SessionRepository {
682
745
  return this.db.prepare('SELECT DISTINCT workspace_id FROM sessions ORDER BY workspace_id').all().map((row) => String(row.workspace_id));
683
746
  }
684
747
  saveBatchImportRun(run) {
748
+ run = redactSessionValue(run).value;
685
749
  const save = this.db.transaction(() => {
686
750
  const prior = this.db.prepare('SELECT manifest_id, workspace_id FROM batch_import_runs WHERE batch_run_id=?').get(run.runId);
687
751
  if (prior && (String(prior.manifest_id) !== run.manifestId
@@ -700,6 +764,22 @@ export class SessionRepository {
700
764
  const row = this.db.prepare('SELECT data FROM batch_import_runs WHERE batch_run_id=?').get(runId);
701
765
  return row ? JSON.parse(String(row.data)) : null;
702
766
  }
767
+ /** Call only while holding the catalog import lease, before starting a new import. */
768
+ interruptRunningBatchImports(now = new Date()) {
769
+ const interruptedAt = now.toISOString();
770
+ return this.db.transaction(() => {
771
+ const rows = this.db.prepare("SELECT data FROM batch_import_runs WHERE status='running'").all();
772
+ return rows.map((row) => {
773
+ const run = JSON.parse(String(row.data));
774
+ run.status = 'interrupted';
775
+ run.interruptedAt = interruptedAt;
776
+ run.updatedAt = interruptedAt;
777
+ run.completedAt = null;
778
+ this.saveBatchImportRun(run);
779
+ return { runId: run.runId, interruptedAt };
780
+ });
781
+ })();
782
+ }
703
783
  getBatchImportRunForManifest(manifestId, workspaceId) {
704
784
  const row = this.db.prepare(`SELECT data FROM batch_import_runs
705
785
  WHERE manifest_id=? AND workspace_id=?`).get(manifestId, workspaceId);
@@ -743,18 +823,18 @@ export class SessionRepository {
743
823
  const pageParams = [...params];
744
824
  if (cursor) {
745
825
  pageWhere.push(`(
746
- COALESCE(json_extract(s.data, '$.updatedAt'), '') > ?
826
+ COALESCE(json_extract(s.data, '$.updatedAt'), '') < ?
747
827
  OR (
748
828
  COALESCE(json_extract(s.data, '$.updatedAt'), '') = ?
749
- AND s.session_id > ?
829
+ AND s.session_id < ?
750
830
  )
751
831
  )`);
752
832
  pageParams.push(cursor.updatedAt, cursor.updatedAt, cursor.sessionId);
753
833
  }
754
834
  const items = this.db.prepare(`SELECT s.data FROM sessions s WHERE ${pageWhere.join(' AND ')}
755
- ORDER BY COALESCE(json_extract(s.data, '$.updatedAt'), ''), s.session_id
835
+ ORDER BY COALESCE(json_extract(s.data, '$.updatedAt'), '') DESC, s.session_id DESC
756
836
  LIMIT ?${options.offset ? ' OFFSET ?' : ''}`).all(...pageParams, options.limit + 1, ...(options.offset ? [options.offset] : []))
757
- .map((row) => JSON.parse(String(row.data)));
837
+ .map((row) => effectiveSessionLifecycle(JSON.parse(String(row.data))));
758
838
  const page = items.slice(0, options.limit);
759
839
  const last = page.at(-1);
760
840
  return {
@@ -779,6 +859,7 @@ export class SessionRepository {
779
859
  .map((row) => String(row.tag));
780
860
  }
781
861
  tagSession(sessionId, tag, workspaceId) {
862
+ tag = redactSessionText(tag).text;
782
863
  const mutate = this.db.transaction(() => {
783
864
  if (!this.getSession(sessionId, workspaceId))
784
865
  return false;
@@ -819,7 +900,7 @@ export class SessionRepository {
819
900
  e.session_id,
820
901
  e.sequence_no,
821
902
  e.event_id`).all(workspaceId).map((row) => ({
822
- session: JSON.parse(String(row.session_data)),
903
+ session: effectiveSessionLifecycle(JSON.parse(String(row.session_data))),
823
904
  event: JSON.parse(String(row.event_data)),
824
905
  }));
825
906
  }
@@ -960,6 +1041,14 @@ export class SessionRepository {
960
1041
  };
961
1042
  }
962
1043
  rebuildAnalytics(workspaceId, sessionId) {
1044
+ return this.maintainedAnalyticsTransaction(() => {
1045
+ const inserted = this.rebuildAnalyticsFacts(workspaceId, sessionId);
1046
+ if (!workspaceId && !sessionId)
1047
+ this.markAnalyticsVerified();
1048
+ return inserted;
1049
+ })();
1050
+ }
1051
+ rebuildAnalyticsFacts(workspaceId, sessionId) {
963
1052
  const where = [
964
1053
  `r.status='committed'`,
965
1054
  `s.lifecycle!='tombstoned'`,
@@ -1128,7 +1217,10 @@ export class SessionRepository {
1128
1217
  }
1129
1218
  }
1130
1219
  const saved = candidates.map((input) => {
1131
- const parsed = IntelligenceCandidateSchema.parse(input);
1220
+ const redacted = redactSessionValue(input);
1221
+ const parsed = IntelligenceCandidateSchema.parse(redacted.value);
1222
+ if (redacted.classes.length)
1223
+ parsed.sensitivity = 'sensitive';
1132
1224
  this.assertCandidateEvidence(parsed);
1133
1225
  const priorRow = this.db.prepare(`SELECT data FROM intelligence_candidates
1134
1226
  WHERE candidate_id=? ORDER BY version DESC LIMIT 1`).get(parsed.candidateId);
@@ -1196,6 +1288,7 @@ export class SessionRepository {
1196
1288
  return rows.map((row) => IntelligenceCandidateSchema.parse(JSON.parse(String(row.data))));
1197
1289
  }
1198
1290
  reviewCandidate(input) {
1291
+ input = redactSessionValue(input).value;
1199
1292
  const review = this.db.transaction(() => {
1200
1293
  const candidate = this.getCandidate(input.candidateId, input.version, input.workspaceId);
1201
1294
  if (!candidate) {
@@ -1257,7 +1350,7 @@ export class SessionRepository {
1257
1350
  }
1258
1351
  recordPromotion(receiptInput) {
1259
1352
  const record = this.db.transaction(() => {
1260
- const receipt = PromotionReceiptSchema.parse(receiptInput);
1353
+ const receipt = PromotionReceiptSchema.parse(redactSessionValue(receiptInput).value);
1261
1354
  const existing = this.getPromotionReceipt(receipt.candidateId, receipt.candidateVersion, receipt.consumer);
1262
1355
  if (existing)
1263
1356
  return { ...existing, duplicate: true };
@@ -1324,6 +1417,7 @@ export class SessionRepository {
1324
1417
  return row ? JSON.parse(String(row.checkpoint)) : null;
1325
1418
  }
1326
1419
  relocateSource(sourceId, redactedLocator, workspaceId) {
1420
+ redactedLocator = redactSessionText(redactedLocator).text;
1327
1421
  const relocate = this.db.transaction(() => {
1328
1422
  const row = this.db.prepare(`SELECT src.data FROM session_sources src
1329
1423
  WHERE src.source_id=? ${workspaceId ? `AND EXISTS (
@@ -1381,7 +1475,7 @@ export class SessionRepository {
1381
1475
  };
1382
1476
  }
1383
1477
  tombstoneSession(sessionId, workspaceId) {
1384
- const tombstone = this.db.transaction(() => {
1478
+ const tombstone = this.maintainedAnalyticsTransaction(() => {
1385
1479
  const row = this.db.prepare(`SELECT data, workspace_id FROM sessions
1386
1480
  WHERE session_id=? ${workspaceId ? 'AND workspace_id=?' : ''}`).get(...(workspaceId ? [sessionId, workspaceId] : [sessionId]));
1387
1481
  if (!row)
@@ -1410,7 +1504,7 @@ export class SessionRepository {
1410
1504
  return tombstone();
1411
1505
  }
1412
1506
  restoreSession(sessionId, workspaceId) {
1413
- const restore = this.db.transaction(() => {
1507
+ const restore = this.maintainedAnalyticsTransaction(() => {
1414
1508
  const row = this.db.prepare(`SELECT data, workspace_id FROM sessions
1415
1509
  WHERE session_id=? ${workspaceId ? 'AND workspace_id=?' : ''}`).get(...(workspaceId ? [sessionId, workspaceId] : [sessionId]));
1416
1510
  if (!row)
@@ -1524,6 +1618,7 @@ export class SessionRepository {
1524
1618
  return rows.map((row) => JSON.parse(String(row.data)));
1525
1619
  }
1526
1620
  purgeSession(input) {
1621
+ input = redactSessionValue(input).value;
1527
1622
  const existing = this.db.prepare('SELECT data FROM deletion_receipts WHERE operation_id=?').get(input.preview.operationId);
1528
1623
  if (existing)
1529
1624
  return JSON.parse(String(existing.data));
@@ -1543,7 +1638,7 @@ export class SessionRepository {
1543
1638
  if (!mutationWorkspaceId) {
1544
1639
  throw new SessionContractError('OPERATION_NOT_AUTHORIZED', 'purge mutation lacks an authorized workspace');
1545
1640
  }
1546
- const apply = this.db.transaction(() => {
1641
+ const apply = this.maintainedAnalyticsTransaction(() => {
1547
1642
  for (const decision of validatedDecisions) {
1548
1643
  this.db.prepare(`INSERT INTO promotion_dependency_decisions
1549
1644
  (operation_id, dependent_id, action, basis) VALUES (?, ?, ?, ?)`).run(input.preview.operationId, decision.dependentId, decision.action, decision.basis);
@@ -1637,7 +1732,7 @@ export class SessionRepository {
1637
1732
  return apply();
1638
1733
  }
1639
1734
  reindex(workspaceId) {
1640
- const rebuild = this.db.transaction(() => {
1735
+ const rebuild = this.maintainedAnalyticsTransaction(() => {
1641
1736
  let indexed = 0;
1642
1737
  if (!workspaceId) {
1643
1738
  this.db.exec('REINDEX');