@aiwg/cli 2026.10.3 → 2026.10.5

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