@zaip-ai/cli 1.0.8 → 1.3.0

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 (212) hide show
  1. package/README.md +1 -1
  2. package/dist/services/adapters.d.ts +1 -1
  3. package/dist/services/adapters.js +1 -1
  4. package/dist/services/agent-adapters.js +2 -2
  5. package/dist/services/agent-adapters.js.map +1 -1
  6. package/dist/services/agent-adapters.test.js +5 -0
  7. package/dist/services/agent-adapters.test.js.map +1 -1
  8. package/dist/services/logger.test.js +2 -2
  9. package/dist/services/runtime-template.test.js +6 -6
  10. package/dist/services/template-manifest.d.ts.map +1 -1
  11. package/dist/services/template-manifest.js +5 -0
  12. package/dist/services/template-manifest.js.map +1 -1
  13. package/package.json +1 -1
  14. package/templates/.agents/architecture/software-architecture.md +131 -0
  15. package/templates/.agents/branding/brandbook.docx +0 -0
  16. package/templates/.agents/branding/brandbook.md +60 -0
  17. package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +1 -1
  18. package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +1 -1
  19. package/templates/.agents/dashboard/.tmp-run-list.json +254 -0
  20. package/templates/.agents/dashboard/.zaip-managed.json +252 -136
  21. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-Z60df2r-.js → CostCalculationGuidePage-DxR_pgSV.js} +1 -1
  22. package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-4jZLTWE_.js +2156 -0
  23. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-Coc6pA-1.js → PricingSettingsDialog-CKcsqb-r.js} +1 -1
  24. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-BomxqCBU.css +1 -0
  25. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-DmmJD2Th.js +3 -0
  26. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BwtitWa_.js +1 -0
  27. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-N5Csr0RP.js +68 -0
  28. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DFBuc_qp.js → icons-vendor-C0fj2uU9.js} +1 -1
  29. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-C4aIB1PV.js +2 -0
  30. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-CCQOHdVI.js → react-vendor-BEsJ-FYH.js} +1 -1
  31. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-pC2_0J8x.js → traceability-B6y6Gw12.js} +1 -1
  32. package/templates/.agents/dashboard/apps/frontend/dist/index.html +5 -5
  33. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  34. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +204 -32
  35. package/templates/.agents/dashboard/apps/frontend/src/DashboardAlternateContent.tsx +11 -11
  36. package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.tsx +14 -7
  37. package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +4 -2
  38. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +3 -1
  39. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +14 -11
  40. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +19 -4
  41. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +24 -2
  42. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +22 -1
  43. package/templates/.agents/dashboard/apps/frontend/src/overview-status-page.test.ts +3 -1
  44. package/templates/.agents/dashboard/apps/frontend/src/project-settings-page.css +36 -26
  45. package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +2 -2
  46. package/templates/.agents/dashboard/apps/frontend/src/snapshot-banner-semantics.test.ts +40 -6
  47. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +39 -1
  48. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +89 -23
  49. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +219 -29
  50. package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +61 -7
  51. package/templates/.agents/dashboard/apps/frontend/src/use-app-operations-pages.ts +3 -6
  52. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.test.ts +2 -2
  53. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.ts +3 -4
  54. package/templates/.agents/dashboard/apps/frontend/vite.config.ts +4 -1
  55. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  56. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.test.ts +89 -19
  57. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.tsx +31 -16
  58. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.test.ts +43 -14
  59. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.tsx +5 -5
  60. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.tsx +1 -1
  61. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +67 -42
  62. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +115 -39
  63. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +188 -38
  64. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +75 -17
  65. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +147 -58
  66. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor.css +5 -2
  67. package/templates/.agents/dashboard/modules/cost-monitor/src/react/index.ts +2 -2
  68. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +2 -1
  69. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +18 -7
  70. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +95 -9
  71. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +29 -17
  72. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.test.ts +0 -24
  73. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.test.ts +225 -424
  74. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +24 -15
  75. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-terminal-day.test.ts +6 -1
  76. package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-monitor-projection-agents.test.ts +31 -0
  77. package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-summary-projection-duration.test.ts +414 -0
  78. package/templates/.agents/dashboard/modules/operations/package.json +1 -1
  79. package/templates/.agents/dashboard/modules/operations/src/contracts.ts +26 -23
  80. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +31 -11
  81. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +3 -1
  82. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +51 -21
  83. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +47 -5
  84. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageNavigation.tsx +1 -4
  85. package/templates/.agents/dashboard/modules/operations/src/react/PeriodSummaryTable.tsx +32 -10
  86. package/templates/.agents/dashboard/modules/operations/src/react/TaskDetailsDialog.tsx +9 -3
  87. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +13 -5
  88. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.test.ts +12 -4
  89. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +1 -4
  90. package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.ts +1 -2
  91. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +385 -4
  92. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +481 -61
  93. package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +14 -1
  94. package/templates/.agents/dashboard/modules/operations/src/react/export/xlsx-runtime-e2e.test.ts +248 -0
  95. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.test.ts +4 -4
  96. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.ts +6 -4
  97. package/templates/.agents/dashboard/modules/operations/src/react/operations-global-filters.test.ts +4 -1
  98. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +1 -1
  99. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +1 -1
  100. package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +46 -6
  101. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-order.test.ts +1 -4
  102. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.test.ts +82 -6
  103. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.test.ts +2 -1
  104. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.ts +1 -1
  105. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.test.ts +19 -7
  106. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.ts +4 -1
  107. package/templates/.agents/dashboard/modules/operations/src/server/quick-daily-operations-projection.ts +11 -5
  108. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  109. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  110. package/templates/.agents/dashboard/packages/branding/src/index.ts +1 -1
  111. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  112. package/templates/.agents/dashboard/packages/contracts/src/index.ts +1 -0
  113. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  114. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +152 -149
  115. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs.metafile.json +7333 -0
  116. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +149 -146
  117. package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
  118. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-dashboard.mjs +15 -7
  119. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +44 -13
  120. package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +5 -2
  121. package/templates/.agents/dashboard/packages/local-runtime/scripts/source-size-runtime-scripts/sqlite-read-model-fixture.mjs +397 -0
  122. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-read-model.mjs +472 -145
  123. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-unix-launchers.mjs +15 -3
  124. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +129 -31
  125. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +153 -150
  126. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +6 -2
  127. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +3 -3
  128. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.test.ts +55 -16
  129. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +23 -13
  130. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +19 -4
  131. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +7 -3
  132. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +132 -9
  133. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +126 -60
  134. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.ts +16 -2
  135. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +1861 -350
  136. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +1218 -352
  137. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +119 -42
  138. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +45 -20
  139. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  140. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  141. package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +7 -5
  142. package/templates/.agents/design/.zaip-managed.json +3 -3
  143. package/templates/.agents/design/apps/catalog/package.json +1 -1
  144. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  145. package/templates/.agents/growth/estrategia-bootstrap-90-dias.md +169 -0
  146. package/templates/.agents/growth/strategy.md +27 -0
  147. package/templates/.agents/marketing/estrategia-marketing-zaip-90-dias.md +197 -0
  148. package/templates/.agents/marketing/strategy.md +29 -0
  149. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  150. package/templates/.agents/requirements/comparative-audit.md +1 -1
  151. package/templates/.agents/requirements/context-engine.md +1 -1
  152. package/templates/.agents/requirements/cost-observability.md +1 -1
  153. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  154. package/templates/.agents/requirements/data-export.md +1 -1
  155. package/templates/.agents/requirements/design-system.md +1 -1
  156. package/templates/.agents/requirements/execution-engine.md +1 -1
  157. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  158. package/templates/.agents/requirements/model-orchestration.md +1 -1
  159. package/templates/.agents/requirements/public-distribution.md +1 -1
  160. package/templates/.agents/requirements/software-architecture.md +1 -1
  161. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  162. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  163. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  164. package/templates/.agents/runtime/VERSION +1 -1
  165. package/templates/.agents/runtime/bin/zaip.mjs +30 -29
  166. package/templates/.agents/runtime/checksums.sha256 +6 -6
  167. package/templates/.agents/runtime/manifest.json +1 -1
  168. package/templates/.agents/runtime/pnpm-lock.sha256 +1 -1
  169. package/templates/.agents/runtime/provenance.json +5 -5
  170. package/templates/.agents/runtime/sbom.cdx.json +3 -3
  171. package/templates/.agents/sales/playbook.md +32 -0
  172. package/templates/.agents/sales/roteiro-comercial-zaip.md +252 -0
  173. package/templates/.agents/skills/zaip-aios/SKILL.md +1 -1
  174. package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +9 -2
  175. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +39 -0
  176. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +1 -1
  177. package/templates/.agents/skills/zaip-skill-enforce-architecture/SKILL.md +2 -0
  178. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +6 -0
  179. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +6 -0
  180. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +39 -82
  181. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +13 -8
  182. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +44 -14
  183. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +19 -9
  184. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +29 -5
  185. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/source-size-telemetry/model-identity-reconciliation.mjs +77 -0
  186. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +6 -0
  187. package/templates/.agents/skills/zaip-skill-sqlite-read-model/scripts/ensure-node.mjs +28 -5
  188. package/templates/.agents/zaip-aios/INDEX.md +10 -0
  189. package/templates/.agents/zaip-aios/README.md +2 -1
  190. package/templates/.agents/zaip-aios/VERSION +1 -1
  191. package/templates/.agents/zaip-aios/guides/18-catalogo-de-agentes.md +76 -0
  192. package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +72 -0
  193. package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +330 -0
  194. package/templates/.agents/zaip-aios/guides/21-telemetria-monitor-custos.md +52 -0
  195. package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +69 -0
  196. package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +483 -0
  197. package/templates/.agents/zaip-aios/guides/24-calculo-de-custos-openai.md +85 -0
  198. package/templates/.agents/zaip-aios/guides/25-fontes-de-precos-openai.md +18 -0
  199. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +44 -0
  200. package/templates/.agents/zaip-aios/guides/27-telemetria-git-native.md +167 -0
  201. package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +144 -0
  202. package/templates/.agents/zaip-aios/guides/assets/diagrams/lifecycles-task-spec-prd.png +0 -0
  203. package/templates/.agents/zaip-aios/guides/assets/diagrams/prd-lifecycle.png +0 -0
  204. package/templates/.agents/zaip-aios/guides/assets/diagrams/spec-lifecycle.png +0 -0
  205. package/templates/.agents/zaip-aios/guides/assets/diagrams/task-lifecycle.png +0 -0
  206. package/templates/template-manifest.json +313 -177
  207. package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-Cs6dzDmW.js +0 -1505
  208. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-QE1k9yZB.css +0 -1
  209. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-_Pf5DA_A.js +0 -3
  210. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BVlz3DLZ.js +0 -1
  211. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Cf9ctPVt.js +0 -68
  212. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8A4DcNXM.js +0 -2
@@ -74,7 +74,8 @@ type CostDimensionAggregateRow = {
74
74
  function observedDimensionValue(value: unknown): string | null {
75
75
  if (typeof value !== "string") return null;
76
76
  const normalized = value.trim();
77
- return normalized.length > 0 && !["unavailable", "unknown", "mixed"].includes(normalized.toLowerCase())
77
+ return normalized.length > 0 &&
78
+ !["unavailable", "unknown", "mixed"].includes(normalized.toLowerCase())
78
79
  ? normalized
79
80
  : null;
80
81
  }
@@ -97,14 +98,21 @@ function buildCostDimensionAggregates(
97
98
  ): Record<"model" | "provider", CostDimensionAggregateRow[]> {
98
99
  const dimensions = ["model", "provider"] as const;
99
100
  const allowedProviders = new Set(
100
- (database.prepare("SELECT provider FROM zaip_dim_provider").all() as Array<{ provider: string }>)
101
- .map((row) => row.provider),
101
+ (
102
+ database.prepare("SELECT provider FROM zaip_dim_provider").all() as Array<{
103
+ provider: string;
104
+ }>
105
+ ).map((row) => row.provider),
102
106
  );
103
- const activities = database.prepare(`SELECT activity_row_id, agent_invocation_id, task_id, duration_ms,
107
+ const activities = database
108
+ .prepare(`SELECT activity_row_id, agent_invocation_id, task_id, duration_ms,
104
109
  source_event_count, provider, model, total_tokens, cost_usd, cost_brl
105
- FROM zaip_aios_activities WHERE payload_type IS NULL`).all() as Array<Record<string, unknown>>;
106
- const segments = database.prepare(`SELECT agent_invocation_id, provider, model, total_tokens, cost_usd, cost_brl
107
- FROM invocation_usage_segments`).all() as Array<Record<string, unknown>>;
110
+ FROM zaip_aios_activities WHERE payload_type IS NULL`)
111
+ .all() as Array<Record<string, unknown>>;
112
+ const segments = database
113
+ .prepare(`SELECT agent_invocation_id, provider, model, total_tokens, cost_usd, cost_brl
114
+ FROM invocation_usage_segments`)
115
+ .all() as Array<Record<string, unknown>>;
108
116
  const activityByInvocation = new Map(
109
117
  activities
110
118
  .filter((row) => typeof row.agent_invocation_id === "string")
@@ -118,13 +126,24 @@ function buildCostDimensionAggregates(
118
126
  const current = rows.get(key);
119
127
  if (current !== undefined) return current;
120
128
  const created: CostDimensionAggregateRow = {
121
- key, label: key, invocation_count: 0, task_count: 0, total_duration: null,
122
- source_event_count: null, total_tokens: null, total_cost_usd: null, total_cost_brl: null,
129
+ key,
130
+ label: key,
131
+ invocation_count: 0,
132
+ task_count: 0,
133
+ total_duration: null,
134
+ source_event_count: null,
135
+ total_tokens: null,
136
+ total_cost_usd: null,
137
+ total_cost_brl: null,
123
138
  };
124
139
  rows.set(key, created);
125
140
  return created;
126
141
  };
127
- const add = (target: CostDimensionAggregateRow, field: "total_tokens" | "total_cost_usd" | "total_cost_brl", value: unknown) => {
142
+ const add = (
143
+ target: CostDimensionAggregateRow,
144
+ field: "total_tokens" | "total_cost_usd" | "total_cost_brl",
145
+ value: unknown,
146
+ ) => {
128
147
  if (typeof value === "number") target[field] = (target[field] ?? 0) + value;
129
148
  };
130
149
  const segmentsByInvocation = new Map<string, Array<Record<string, unknown>>>();
@@ -148,15 +167,20 @@ function buildCostDimensionAggregates(
148
167
  typeof segment.cost_usd === "number" ||
149
168
  typeof segment.cost_brl === "number"),
150
169
  );
151
- const keys = [...new Set(financialSegments.map((row) => observedDimensionValue(row[dimension])!))];
170
+ const keys = [
171
+ ...new Set(financialSegments.map((row) => observedDimensionValue(row[dimension])!)),
172
+ ];
152
173
  if (keys.length === 0) {
153
174
  // Keep auditable, dimensionless invocation facts visible so Models
154
175
  // and Providers reconcile to the Cost KPI instead of silently
155
176
  // dropping them.
156
177
  const target = ensure(dimension, "unavailable");
157
178
  target.invocation_count += 1;
158
- if (typeof activity.duration_ms === "number") target.total_duration = (target.total_duration ?? 0) + activity.duration_ms;
159
- if (typeof activity.source_event_count === "number") target.source_event_count = (target.source_event_count ?? 0) + activity.source_event_count;
179
+ if (typeof activity.duration_ms === "number")
180
+ target.total_duration = (target.total_duration ?? 0) + activity.duration_ms;
181
+ if (typeof activity.source_event_count === "number")
182
+ target.source_event_count =
183
+ (target.source_event_count ?? 0) + activity.source_event_count;
160
184
  add(target, "total_tokens", activity.total_tokens);
161
185
  add(target, "total_cost_usd", activity.cost_usd);
162
186
  add(target, "total_cost_brl", activity.cost_brl);
@@ -173,8 +197,11 @@ function buildCostDimensionAggregates(
173
197
  if (keys.length === 1) {
174
198
  const target = ensure(dimension, keys[0]!);
175
199
  target.invocation_count += 1;
176
- if (typeof activity.duration_ms === "number") target.total_duration = (target.total_duration ?? 0) + activity.duration_ms;
177
- if (typeof activity.source_event_count === "number") target.source_event_count = (target.source_event_count ?? 0) + activity.source_event_count;
200
+ if (typeof activity.duration_ms === "number")
201
+ target.total_duration = (target.total_duration ?? 0) + activity.duration_ms;
202
+ if (typeof activity.source_event_count === "number")
203
+ target.source_event_count =
204
+ (target.source_event_count ?? 0) + activity.source_event_count;
178
205
  }
179
206
  }
180
207
  }
@@ -217,16 +244,25 @@ function buildCostDimensionAggregates(
217
244
  const target = ensure(dimension, key);
218
245
  target.invocation_count += 1;
219
246
  if (typeof activity.task_id === "string") target.task_count += 1;
220
- if (typeof activity.duration_ms === "number") target.total_duration = (target.total_duration ?? 0) + activity.duration_ms;
221
- if (typeof activity.source_event_count === "number") target.source_event_count = (target.source_event_count ?? 0) + activity.source_event_count;
247
+ if (typeof activity.duration_ms === "number")
248
+ target.total_duration = (target.total_duration ?? 0) + activity.duration_ms;
249
+ if (typeof activity.source_event_count === "number")
250
+ target.source_event_count = (target.source_event_count ?? 0) + activity.source_event_count;
222
251
  add(target, "total_tokens", activity.total_tokens);
223
252
  add(target, "total_cost_usd", activity.cost_usd);
224
253
  add(target, "total_cost_brl", activity.cost_brl);
225
254
  }
226
255
  }
227
- return Object.fromEntries(dimensions.map((dimension) => [dimension, [...rowsByDimension.get(dimension)!.values()].sort(
228
- (left, right) => (right.total_cost_brl ?? -1) - (left.total_cost_brl ?? -1) || left.key.localeCompare(right.key),
229
- )])) as Record<"model" | "provider", CostDimensionAggregateRow[]>;
256
+ return Object.fromEntries(
257
+ dimensions.map((dimension) => [
258
+ dimension,
259
+ [...rowsByDimension.get(dimension)!.values()].sort(
260
+ (left, right) =>
261
+ (right.total_cost_brl ?? -1) - (left.total_cost_brl ?? -1) ||
262
+ left.key.localeCompare(right.key),
263
+ ),
264
+ ]),
265
+ ) as Record<"model" | "provider", CostDimensionAggregateRow[]>;
230
266
  }
231
267
 
232
268
  /** Only canonical inputs consumed by the fact or its direct dimensions. */
@@ -336,9 +372,7 @@ function stringValues(value: unknown): string[] {
336
372
  : [];
337
373
  }
338
374
 
339
- function readCanonicalLifecycleRecords(
340
- rows: Array<{ path: string; content: string }>,
341
- ): {
375
+ function readCanonicalLifecycleRecords(rows: Array<{ path: string; content: string }>): {
342
376
  executions: CanonicalExecution[];
343
377
  changes: CanonicalChange[];
344
378
  decisions: CanonicalDecision[];
@@ -490,7 +524,9 @@ function readAgentProfiles(
490
524
  // host records used `zaip-` forms of deprecated IDs. All of these are
491
525
  // explicit identities from the catalog; no UI fallback is involved.
492
526
  const identities = new Set([agent, catalogId]);
493
- for (const alias of block.matchAll(/^ {8}- kind:\s*(?:id|codex_name|codex_task_name)\s*\r?\n {10}value:\s*([^\s#]+)/gm)) {
527
+ for (const alias of block.matchAll(
528
+ /^ {8}- kind:\s*(?:id|codex_name|codex_task_name)\s*\r?\n {10}value:\s*([^\s#]+)/gm,
529
+ )) {
494
530
  const value = alias[1];
495
531
  if (value !== undefined) identities.add(value);
496
532
  }
@@ -586,7 +622,9 @@ type ResolvedSourceInterval = { start: string; end: string; duration: number };
586
622
  * happened. It wins over Run finalization elapsed time, which may include
587
623
  * orchestration/capture lag or be recorded much later.
588
624
  */
589
- function resolvedSourceInterval(records: readonly UsageRecordedV2[]): ResolvedSourceInterval | null {
625
+ function resolvedSourceInterval(
626
+ records: readonly UsageRecordedV2[],
627
+ ): ResolvedSourceInterval | null {
590
628
  const intervals = records.flatMap((record) => {
591
629
  const start = record.source_started_at;
592
630
  const end = record.source_finished_at;
@@ -677,7 +715,13 @@ function resolveDurations(
677
715
  }
678
716
  // Tokens and lifecycle timestamps are not a time source. Absence stays
679
717
  // absent: consumers must display unavailable rather than manufacture time.
680
- return { source: null, effective: null, provenance: null, sourceStartedAt: null, sourceFinishedAt: null };
718
+ return {
719
+ source: null,
720
+ effective: null,
721
+ provenance: null,
722
+ sourceStartedAt: null,
723
+ sourceFinishedAt: null,
724
+ };
681
725
  }
682
726
 
683
727
  function latestFxRate(rows: Array<{ path: string; content: string }>): FxRate | null {
@@ -857,7 +901,9 @@ function usagePricingAudit(
857
901
  const effectiveBrl =
858
902
  record.cost.brl ??
859
903
  actual?.estimated_brl ??
860
- (effectiveUsd !== null && fxRate !== null ? Math.round(effectiveUsd * fxRate.rate * 1_000_000) / 1_000_000 : null);
904
+ (effectiveUsd !== null && fxRate !== null
905
+ ? Math.round(effectiveUsd * fxRate.rate * 1_000_000) / 1_000_000
906
+ : null);
861
907
  return {
862
908
  provider,
863
909
  model,
@@ -946,8 +992,8 @@ export function allocateAttributedTokenActiveMs(intervals: readonly TemporalInte
946
992
  boundaries.add(interval.startMs);
947
993
  boundaries.add(interval.endMs);
948
994
  for (let cursor = interval.startMs; cursor < interval.endMs; ) {
949
- const nextDay = Date.parse(`${saoPauloDay(new Date(cursor).toISOString())}T03:00:00.000Z`) +
950
- 86_400_000;
995
+ const nextDay =
996
+ Date.parse(`${saoPauloDay(new Date(cursor).toISOString())}T03:00:00.000Z`) + 86_400_000;
951
997
  if (nextDay >= interval.endMs) break;
952
998
  boundaries.add(nextDay);
953
999
  cursor = nextDay;
@@ -958,7 +1004,9 @@ export function allocateAttributedTokenActiveMs(intervals: readonly TemporalInte
958
1004
  for (let index = 0; index < points.length - 1; index += 1) {
959
1005
  const startMs = points[index]!;
960
1006
  const endMs = points[index + 1]!;
961
- const active = intervals.filter((interval) => interval.startMs < endMs && interval.endMs > startMs);
1007
+ const active = intervals.filter(
1008
+ (interval) => interval.startMs < endMs && interval.endMs > startMs,
1009
+ );
962
1010
  if (active.length === 0 || endMs <= startMs) continue;
963
1011
  const share = (endMs - startMs) / active.length;
964
1012
  const day = saoPauloDay(new Date(startMs).toISOString());
@@ -1048,36 +1096,54 @@ export function validateSqliteReadModelSchema(path: string): {
1048
1096
  } {
1049
1097
  const database = new DatabaseSync(path, { readOnly: true });
1050
1098
  try {
1051
- const integrity = database.prepare("PRAGMA integrity_check").get() as { integrity_check?: string };
1099
+ const integrity = database.prepare("PRAGMA integrity_check").get() as {
1100
+ integrity_check?: string;
1101
+ };
1052
1102
  if (integrity.integrity_check !== "ok")
1053
- throw new Error(`SQLite integrity check failed for ${path}: ${integrity.integrity_check ?? "unknown"}`);
1103
+ throw new Error(
1104
+ `SQLite integrity check failed for ${path}: ${integrity.integrity_check ?? "unknown"}`,
1105
+ );
1054
1106
  const objects = database
1055
1107
  .prepare("SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view', 'index')")
1056
1108
  .all() as Array<{ name: string; type: "table" | "view" | "index" }>;
1057
- const tables = objects.filter((object) => object.type === "table").map((object) => object.name).sort();
1109
+ const tables = objects
1110
+ .filter((object) => object.type === "table")
1111
+ .map((object) => object.name)
1112
+ .sort();
1058
1113
  const expectedTables = [...requiredPersistedTables].sort();
1059
- if (tables.length !== expectedTables.length || tables.some((table, index) => table !== expectedTables[index]))
1060
- throw new Error(`SQLite schema ${readModelSchemaVersion} requires exactly five persisted tables; found ${tables.join(", ") || "none"}`);
1114
+ if (
1115
+ tables.length !== expectedTables.length ||
1116
+ tables.some((table, index) => table !== expectedTables[index])
1117
+ )
1118
+ throw new Error(
1119
+ `SQLite schema ${readModelSchemaVersion} requires exactly five persisted tables; found ${tables.join(", ") || "none"}`,
1120
+ );
1061
1121
  const names = new Set(objects.map((object) => object.name));
1062
1122
  const missingViews = requiredViews.filter((view) => !names.has(view));
1063
- if (missingViews.length > 0) throw new Error(`SQLite schema is missing required views: ${missingViews.join(", ")}`);
1123
+ if (missingViews.length > 0)
1124
+ throw new Error(`SQLite schema is missing required views: ${missingViews.join(", ")}`);
1064
1125
  const views = objects.filter((object) => object.type === "view").map((object) => object.name);
1065
1126
  if (views.some((view) => !view.startsWith("zaip_vw_")))
1066
1127
  throw new Error("SQLite persisted views must use the zaip_vw_ namespace");
1067
1128
  const missingIndexes = requiredIndexes.filter((index) => !names.has(index));
1068
- if (missingIndexes.length > 0) throw new Error(`SQLite schema is missing required indexes: ${missingIndexes.join(", ")}`);
1129
+ if (missingIndexes.length > 0)
1130
+ throw new Error(`SQLite schema is missing required indexes: ${missingIndexes.join(", ")}`);
1069
1131
  const schemaVersion = (
1070
1132
  database
1071
1133
  .prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key = ?")
1072
1134
  .get("schema_version") as { value?: string } | undefined
1073
1135
  )?.value;
1074
1136
  if (schemaVersion !== readModelSchemaVersion)
1075
- throw new Error(`SQLite schema version must be ${readModelSchemaVersion}; found ${schemaVersion ?? "missing"}`);
1137
+ throw new Error(
1138
+ `SQLite schema version must be ${readModelSchemaVersion}; found ${schemaVersion ?? "missing"}`,
1139
+ );
1076
1140
  return {
1077
1141
  schemaVersion,
1078
1142
  tableCount: tables.length,
1079
1143
  viewCount: views.length,
1080
- indexCount: objects.filter((object) => object.type === "index" && !object.name.startsWith("sqlite_")).length,
1144
+ indexCount: objects.filter(
1145
+ (object) => object.type === "index" && !object.name.startsWith("sqlite_"),
1146
+ ).length,
1081
1147
  };
1082
1148
  } finally {
1083
1149
  database.close();
@@ -1087,6 +1153,7 @@ export function validateSqliteReadModelSchema(path: string): {
1087
1153
  /** Recreates the disposable comparison read model from ZAIP records and configuration. */
1088
1154
  export async function rebuildSqliteReadModel(
1089
1155
  workspaceRoot: string,
1156
+ options: { forceCanonical?: boolean } = {},
1090
1157
  ): Promise<{
1091
1158
  files: number;
1092
1159
  path: string;
@@ -1099,14 +1166,22 @@ export async function rebuildSqliteReadModel(
1099
1166
  const sourceSnapshot = await sourceFingerprint(workspaceRoot);
1100
1167
  const reportLaunchProgress = (phase: string, percent: number, detail: string) => {
1101
1168
  if (process.env.ZAIP_DASHBOARD_LAUNCH_PROGRESS !== "1") return;
1102
- process.stderr.write(`${JSON.stringify({ event: "dashboard_sqlite_rebuild_progress", phase, percent, detail })}\n`);
1169
+ process.stderr.write(
1170
+ `${JSON.stringify({ event: "dashboard_sqlite_rebuild_progress", phase, percent, detail })}\n`,
1171
+ );
1103
1172
  };
1104
1173
  const { fingerprint } = sourceSnapshot;
1105
1174
  const fingerprintMs = Math.round(performance.now() - fingerprintStartedAt);
1106
1175
  const existingFiles = readSnapshotFileCount(path, fingerprint);
1107
1176
  if (existingFiles !== null) {
1108
1177
  validateSqliteReadModelSchema(path);
1109
- return { files: existingFiles, path, cache: "generation", fingerprint_ms: fingerprintMs, materialize_ms: 0 };
1178
+ return {
1179
+ files: existingFiles,
1180
+ path,
1181
+ cache: "generation",
1182
+ fingerprint_ms: fingerprintMs,
1183
+ materialize_ms: 0,
1184
+ };
1110
1185
  }
1111
1186
 
1112
1187
  // The launcher intentionally gives every live server a private database path
@@ -1135,7 +1210,7 @@ export async function rebuildSqliteReadModel(
1135
1210
  // reconstruction merely because a record changed since the last snapshot.
1136
1211
  // The launcher labels this state as updating and starts an isolated refresh;
1137
1212
  // this process still serves only a schema-validated immutable seed.
1138
- if (process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED === "1") {
1213
+ if (!options.forceCanonical && process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED === "1") {
1139
1214
  const fallback = await mostRecentCompatibleSnapshot(dirname(cachePath));
1140
1215
  if (fallback !== null) {
1141
1216
  const materializeStartedAt = performance.now();
@@ -1150,7 +1225,11 @@ export async function rebuildSqliteReadModel(
1150
1225
  }
1151
1226
  }
1152
1227
 
1153
- reportLaunchProgress("source_inventory", 52, `Encontradas ${sourceSnapshot.candidates.length} fontes canônicas.`);
1228
+ reportLaunchProgress(
1229
+ "source_inventory",
1230
+ 52,
1231
+ `Encontradas ${sourceSnapshot.candidates.length} fontes canônicas.`,
1232
+ );
1154
1233
  await mkdir(dirname(cachePath), { recursive: true });
1155
1234
  const temporaryPath = `${cachePath}.${process.pid}.${randomUUID()}.tmp`;
1156
1235
  const database = new DatabaseSync(temporaryPath);
@@ -1225,11 +1304,17 @@ export async function rebuildSqliteReadModel(
1225
1304
  let files = 0;
1226
1305
  const skipped = 0;
1227
1306
  const sourceReadBatchSize = 32;
1228
- for (let offset = 0; offset < sourceSnapshot.candidates.length; offset += sourceReadBatchSize) {
1307
+ for (
1308
+ let offset = 0;
1309
+ offset < sourceSnapshot.candidates.length;
1310
+ offset += sourceReadBatchSize
1311
+ ) {
1229
1312
  const batch = sourceSnapshot.candidates.slice(offset, offset + sourceReadBatchSize);
1230
1313
  // Parallel reads reduce cold I/O latency, while the following ordered
1231
1314
  // loop retains deterministic SQLite inserts and canonical projections.
1232
- const contents = await Promise.all(batch.map((candidate) => readFile(candidate.path, "utf8")));
1315
+ const contents = await Promise.all(
1316
+ batch.map((candidate) => readFile(candidate.path, "utf8")),
1317
+ );
1233
1318
  for (let index = 0; index < batch.length; index += 1) {
1234
1319
  const candidate = batch[index];
1235
1320
  const content = contents[index];
@@ -1267,8 +1352,13 @@ export async function rebuildSqliteReadModel(
1267
1352
  );
1268
1353
  files += 1;
1269
1354
  }
1270
- const completedPercent = 52 + Math.round(((offset + batch.length) / sourceSnapshot.candidates.length) * 20);
1271
- reportLaunchProgress("source_import", completedPercent, `Lidas ${Math.min(offset + batch.length, sourceSnapshot.candidates.length)} de ${sourceSnapshot.candidates.length} fontes.`);
1355
+ const completedPercent =
1356
+ 52 + Math.round(((offset + batch.length) / sourceSnapshot.candidates.length) * 20);
1357
+ reportLaunchProgress(
1358
+ "source_import",
1359
+ completedPercent,
1360
+ `Lidas ${Math.min(offset + batch.length, sourceSnapshot.candidates.length)} de ${sourceSnapshot.candidates.length} fontes.`,
1361
+ );
1272
1362
  }
1273
1363
  reportLaunchProgress("projection_build", 74, "Organizando fatos e projeções do banco local.");
1274
1364
  const canonicalEntities = readCanonicalEntities(
@@ -1673,7 +1763,8 @@ export async function rebuildSqliteReadModel(
1673
1763
  canonicalInsertActivity.run(...columns.map((column) => activity[column] ?? null));
1674
1764
  if (totalTokens > 0 && durations.effective !== null && durations.effective >= 0) {
1675
1765
  const startMs = Date.parse(durations.sourceStartedAt ?? started.occurred_at);
1676
- const endMs = Date.parse(durations.sourceFinishedAt ?? "") || startMs + durations.effective;
1766
+ const endMs =
1767
+ Date.parse(durations.sourceFinishedAt ?? "") || startMs + durations.effective;
1677
1768
  if (Number.isFinite(startMs) && Number.isFinite(endMs) && endMs > startMs)
1678
1769
  temporalIntervals.push({ activityRowId, startMs, endMs });
1679
1770
  else if (durations.effective === 0)
@@ -1828,16 +1919,46 @@ export async function rebuildSqliteReadModel(
1828
1919
  'TECH-' || lower(hex(randomblob(16))), 'technical_payload', '${payloadType}:' || row_key,
1829
1920
  'sqlite://${payloadType}', '', datetime('now'), 'technical', 'technical', '${payloadType}', payload_json
1830
1921
  FROM (${selectSql});`);
1831
- persistPayload("metadata", "SELECT key AS row_key, json_object('key', key, 'value', value) AS payload_json FROM read_model_metadata");
1832
- persistPayload("source_state", "SELECT source_path AS row_key, json_object('source_path', source_path, 'source_hash', source_hash, 'mtime_ms', mtime_ms, 'size_bytes', size_bytes, 'source_kind', source_kind, 'last_processed_at', last_processed_at) AS payload_json FROM read_model_source_state");
1833
- persistPayload("file", "SELECT path AS row_key, json_object('path', path, 'content', content, 'mtime_ms', mtime_ms, 'size_bytes', size_bytes, 'content_sha256', content_sha256, 'artifact_kind', artifact_kind, 'artifact_id', artifact_id, 'artifact_occurred_at', artifact_occurred_at, 'artifact_occurred_day_sp', artifact_occurred_day_sp) AS payload_json FROM files");
1834
- persistPayload("work_item", "SELECT entity_kind || ':' || id AS row_key, json_object('entity_kind', entity_kind, 'id', id, 'title', title, 'status', status, 'stage', stage, 'risk', risk, 'occurred_at', occurred_at, 'occurred_day_sp', occurred_day_sp) AS payload_json FROM canonical_work_items");
1835
- persistPayload("execution", "SELECT id AS row_key, json_object('id', id, 'status', status, 'kind', kind, 'origin_type', origin_type, 'origin_id', origin_id, 'created_at', created_at, 'occurred_day_sp', occurred_day_sp, 'finished_at', finished_at, 'source_path', source_path) AS payload_json FROM canonical_executions");
1836
- persistPayload("change", "SELECT id AS row_key, json_object('id', id, 'title', title, 'status', status, 'risk', risk, 'source_type', source_type, 'source_id', source_id, 'source_spec_id', source_spec_id, 'source_prd_id', source_prd_id, 'requirement_ids_json', requirement_ids_json, 'acceptance_ids_json', acceptance_ids_json, 'task_ids_json', task_ids_json, 'result', result, 'created_at', created_at, 'occurred_day_sp', occurred_day_sp, 'source_path', source_path) AS payload_json FROM canonical_changes");
1837
- persistPayload("decision", "SELECT id AS row_key, json_object('id', id, 'title', title, 'origin', origin, 'scope_type', scope_type, 'scope_id', scope_id, 'created_at', created_at, 'occurred_day_sp', occurred_day_sp, 'source_path', source_path) AS payload_json FROM canonical_decisions");
1838
- persistPayload("run_index", "SELECT run_id AS row_key, json_object('run_id', run_id, 'occurred_at', occurred_at, 'occurred_day_sp', occurred_day_sp, 'task_id', task_id, 'phase', phase, 'run_role', run_role, 'duration_ms', duration_ms, 'source_event_count', source_event_count, 'zaip_agent', zaip_agent, 'provider', provider, 'model', model, 'status', status) AS payload_json FROM canonical_run_index");
1839
- persistPayload("usage_segment", "SELECT agent_invocation_id || ':' || rowid AS row_key, json_object('agent_invocation_id', agent_invocation_id, 'provider', provider, 'model', model, 'service_tier', service_tier, 'reasoning_effort', reasoning_effort, 'input_tokens', input_tokens, 'cached_input_tokens', cached_input_tokens, 'uncached_input_tokens', uncached_input_tokens, 'output_tokens', output_tokens, 'reasoning_tokens', reasoning_tokens, 'visible_output_tokens', visible_output_tokens, 'total_tokens', total_tokens, 'input_rate_usd_per_million', input_rate_usd_per_million, 'cached_input_rate_usd_per_million', cached_input_rate_usd_per_million, 'output_rate_usd_per_million', output_rate_usd_per_million, 'reasoning_rate_usd_per_million', reasoning_rate_usd_per_million, 'context_input_multiplier', context_input_multiplier, 'context_output_multiplier', context_output_multiplier, 'speed_cost_multiplier', speed_cost_multiplier, 'pricing_scenario', pricing_scenario, 'standard_cost_usd', standard_cost_usd, 'speed_uplift_usd', speed_uplift_usd, 'input_cost_usd', input_cost_usd, 'cached_input_cost_usd', cached_input_cost_usd, 'output_cost_usd', output_cost_usd, 'reasoning_cost_usd', reasoning_cost_usd, 'calculated_cost_usd', calculated_cost_usd, 'recorded_cost_usd', recorded_cost_usd, 'cost_variance_usd', cost_variance_usd, 'cost_usd', cost_usd, 'cost_brl', cost_brl, 'cost_method', cost_method, 'pricing_model', pricing_model, 'pricing_source', pricing_source, 'fx_rate', fx_rate) AS payload_json FROM invocation_usage_segments");
1840
- persistPayload("duration_allocation", "SELECT activity_row_id || ':' || occurred_day_sp AS row_key, json_object('activity_row_id', activity_row_id, 'occurred_day_sp', occurred_day_sp, 'attributed_token_active_ms', attributed_token_active_ms) AS payload_json FROM attributed_token_active_facts");
1922
+ persistPayload(
1923
+ "metadata",
1924
+ "SELECT key AS row_key, json_object('key', key, 'value', value) AS payload_json FROM read_model_metadata",
1925
+ );
1926
+ persistPayload(
1927
+ "source_state",
1928
+ "SELECT source_path AS row_key, json_object('source_path', source_path, 'source_hash', source_hash, 'mtime_ms', mtime_ms, 'size_bytes', size_bytes, 'source_kind', source_kind, 'last_processed_at', last_processed_at) AS payload_json FROM read_model_source_state",
1929
+ );
1930
+ persistPayload(
1931
+ "file",
1932
+ "SELECT path AS row_key, json_object('path', path, 'content', content, 'mtime_ms', mtime_ms, 'size_bytes', size_bytes, 'content_sha256', content_sha256, 'artifact_kind', artifact_kind, 'artifact_id', artifact_id, 'artifact_occurred_at', artifact_occurred_at, 'artifact_occurred_day_sp', artifact_occurred_day_sp) AS payload_json FROM files",
1933
+ );
1934
+ persistPayload(
1935
+ "work_item",
1936
+ "SELECT entity_kind || ':' || id AS row_key, json_object('entity_kind', entity_kind, 'id', id, 'title', title, 'status', status, 'stage', stage, 'risk', risk, 'occurred_at', occurred_at, 'occurred_day_sp', occurred_day_sp) AS payload_json FROM canonical_work_items",
1937
+ );
1938
+ persistPayload(
1939
+ "execution",
1940
+ "SELECT id AS row_key, json_object('id', id, 'status', status, 'kind', kind, 'origin_type', origin_type, 'origin_id', origin_id, 'created_at', created_at, 'occurred_day_sp', occurred_day_sp, 'finished_at', finished_at, 'source_path', source_path) AS payload_json FROM canonical_executions",
1941
+ );
1942
+ persistPayload(
1943
+ "change",
1944
+ "SELECT id AS row_key, json_object('id', id, 'title', title, 'status', status, 'risk', risk, 'source_type', source_type, 'source_id', source_id, 'source_spec_id', source_spec_id, 'source_prd_id', source_prd_id, 'requirement_ids_json', requirement_ids_json, 'acceptance_ids_json', acceptance_ids_json, 'task_ids_json', task_ids_json, 'result', result, 'created_at', created_at, 'occurred_day_sp', occurred_day_sp, 'source_path', source_path) AS payload_json FROM canonical_changes",
1945
+ );
1946
+ persistPayload(
1947
+ "decision",
1948
+ "SELECT id AS row_key, json_object('id', id, 'title', title, 'origin', origin, 'scope_type', scope_type, 'scope_id', scope_id, 'created_at', created_at, 'occurred_day_sp', occurred_day_sp, 'source_path', source_path) AS payload_json FROM canonical_decisions",
1949
+ );
1950
+ persistPayload(
1951
+ "run_index",
1952
+ "SELECT run_id AS row_key, json_object('run_id', run_id, 'occurred_at', occurred_at, 'occurred_day_sp', occurred_day_sp, 'task_id', task_id, 'phase', phase, 'run_role', run_role, 'duration_ms', duration_ms, 'source_event_count', source_event_count, 'zaip_agent', zaip_agent, 'provider', provider, 'model', model, 'status', status) AS payload_json FROM canonical_run_index",
1953
+ );
1954
+ persistPayload(
1955
+ "usage_segment",
1956
+ "SELECT agent_invocation_id || ':' || rowid AS row_key, json_object('agent_invocation_id', agent_invocation_id, 'provider', provider, 'model', model, 'service_tier', service_tier, 'reasoning_effort', reasoning_effort, 'input_tokens', input_tokens, 'cached_input_tokens', cached_input_tokens, 'uncached_input_tokens', uncached_input_tokens, 'output_tokens', output_tokens, 'reasoning_tokens', reasoning_tokens, 'visible_output_tokens', visible_output_tokens, 'total_tokens', total_tokens, 'input_rate_usd_per_million', input_rate_usd_per_million, 'cached_input_rate_usd_per_million', cached_input_rate_usd_per_million, 'output_rate_usd_per_million', output_rate_usd_per_million, 'reasoning_rate_usd_per_million', reasoning_rate_usd_per_million, 'context_input_multiplier', context_input_multiplier, 'context_output_multiplier', context_output_multiplier, 'speed_cost_multiplier', speed_cost_multiplier, 'pricing_scenario', pricing_scenario, 'standard_cost_usd', standard_cost_usd, 'speed_uplift_usd', speed_uplift_usd, 'input_cost_usd', input_cost_usd, 'cached_input_cost_usd', cached_input_cost_usd, 'output_cost_usd', output_cost_usd, 'reasoning_cost_usd', reasoning_cost_usd, 'calculated_cost_usd', calculated_cost_usd, 'recorded_cost_usd', recorded_cost_usd, 'cost_variance_usd', cost_variance_usd, 'cost_usd', cost_usd, 'cost_brl', cost_brl, 'cost_method', cost_method, 'pricing_model', pricing_model, 'pricing_source', pricing_source, 'fx_rate', fx_rate) AS payload_json FROM invocation_usage_segments",
1957
+ );
1958
+ persistPayload(
1959
+ "duration_allocation",
1960
+ "SELECT activity_row_id || ':' || occurred_day_sp AS row_key, json_object('activity_row_id', activity_row_id, 'occurred_day_sp', occurred_day_sp, 'attributed_token_active_ms', attributed_token_active_ms) AS payload_json FROM attributed_token_active_facts",
1961
+ );
1841
1962
  // Configuration-backed dimensions contain the current selectable
1842
1963
  // catalog even when no activity has used an option yet. Historical
1843
1964
  // values remain faithfully recorded in the fact without becoming
@@ -2196,7 +2317,10 @@ export async function rebuildSqliteReadModel(
2196
2317
  CREATE VIEW vw_prd_summary_daily AS SELECT occurred_day_sp AS day, COUNT(*) AS prd_count, SUM(llm_call_count) AS llm_call_count FROM prd_summary GROUP BY occurred_day_sp;
2197
2318
  CREATE VIEW vw_task_status_daily AS SELECT occurred_day_sp AS day, status, COUNT(*) AS task_count FROM task_summary GROUP BY occurred_day_sp, status;
2198
2319
  CREATE VIEW vw_task_risk_daily AS SELECT occurred_day_sp AS day, risk, COUNT(*) AS task_count FROM task_summary GROUP BY occurred_day_sp, risk;
2199
- CREATE VIEW vw_entities AS SELECT 'task' AS entity_kind, id, title, status, risk, occurred_at, occurred_day_sp FROM task_summary UNION ALL SELECT 'spec', id, title, status, risk, occurred_at, occurred_day_sp FROM spec_summary UNION ALL SELECT 'prd', id, title, status, risk, occurred_at, occurred_day_sp FROM prd_summary;
2320
+ -- The Operations work-list export reads this public union directly.
2321
+ -- Keep the known aggregate facts from each entity summary so its
2322
+ -- snapshot is equivalent to the metrics shown by the work views.
2323
+ CREATE VIEW vw_entities AS SELECT 'task' AS entity_kind, id, title, status, risk, occurred_at, occurred_day_sp, invocation_count, total_duration, total_tokens, total_cost_usd, total_cost_brl FROM task_summary UNION ALL SELECT 'spec', id, title, status, risk, occurred_at, occurred_day_sp, invocation_count, total_duration, total_tokens, total_cost_usd, total_cost_brl FROM spec_summary UNION ALL SELECT 'prd', id, title, status, risk, occurred_at, occurred_day_sp, invocation_count, total_duration, total_tokens, total_cost_usd, total_cost_brl FROM prd_summary;
2200
2324
  CREATE VIEW vw_cost_group_daily AS SELECT * FROM cost_monitor_group_daily_cache;
2201
2325
  -- Narrow temporal Cost surfaces. Do not route the visible Cost
2202
2326
  -- tables through the three-grain overview union: it expands every
@@ -2740,7 +2864,11 @@ export async function rebuildSqliteReadModel(
2740
2864
  },
2741
2865
  );
2742
2866
  database.exec(deduplicatedViewSql);
2743
- reportLaunchProgress("projection_finalize", 80, "Finalizando índices e a cópia local do dashboard.");
2867
+ reportLaunchProgress(
2868
+ "projection_finalize",
2869
+ 80,
2870
+ "Finalizando índices e a cópia local do dashboard.",
2871
+ );
2744
2872
  completedFiles = files;
2745
2873
  } catch (error) {
2746
2874
  database.exec("ROLLBACK;");
@@ -2786,21 +2914,25 @@ export async function rebuildSqliteReadModel(
2786
2914
  for (const target of [cachePath, path]) {
2787
2915
  const targetDatabase = new DatabaseSync(target);
2788
2916
  try {
2789
- targetDatabase.prepare(`INSERT INTO zaip_aios_activities(
2917
+ targetDatabase
2918
+ .prepare(`INSERT INTO zaip_aios_activities(
2790
2919
  activity_row_id, activity_source_kind, activity_key, source_path, source_hash,
2791
2920
  recorded_at, run_role, root_run_id, payload_type, payload_json
2792
2921
  ) VALUES (?, 'technical_payload', 'first_fold_viewport_cache:all_time',
2793
2922
  'sqlite://first_fold_viewport_cache/all_time', '', datetime('now'), 'technical', 'technical',
2794
2923
  'first_fold_viewport_cache', ?)`)
2795
2924
  .run(`TECH-${randomUUID().toLowerCase()}`, firstFoldPayload);
2796
- targetDatabase.prepare(`INSERT INTO zaip_aios_activities(
2925
+ targetDatabase
2926
+ .prepare(`INSERT INTO zaip_aios_activities(
2797
2927
  activity_row_id, activity_source_kind, activity_key, source_path, source_hash,
2798
2928
  recorded_at, run_role, root_run_id, payload_type, payload_json
2799
2929
  ) VALUES (?, 'technical_payload', 'first_fold_monthly_quick_cache:default',
2800
2930
  'sqlite://first_fold_monthly_quick_cache/default', '', datetime('now'), 'technical', 'technical',
2801
2931
  'first_fold_monthly_quick_cache', ?)`)
2802
2932
  .run(`TECH-${randomUUID().toLowerCase()}`, firstFoldMonthlyQuickPayload);
2803
- } finally { targetDatabase.close(); }
2933
+ } finally {
2934
+ targetDatabase.close();
2935
+ }
2804
2936
  }
2805
2937
  reportLaunchProgress("snapshot_ready", 86, "A cópia privada do banco local está pronta.");
2806
2938
  validateSqliteReadModelSchema(path);
@@ -2836,7 +2968,8 @@ async function pruneSnapshotCache(directory: string, activePath: string): Promis
2836
2968
  // Interrupted SQLite builds leave only cache-local temporary files. They
2837
2969
  // cannot be valid immutable seeds and otherwise inflate the launch folder.
2838
2970
  for (const entry of entries) {
2839
- if (!entry.isFile() || !entry.name.includes(".sqlite.") || !entry.name.includes(".tmp")) continue;
2971
+ if (!entry.isFile() || !entry.name.includes(".sqlite.") || !entry.name.includes(".tmp"))
2972
+ continue;
2840
2973
  const candidate = resolve(directory, entry.name);
2841
2974
  const age = Date.now() - (await stat(candidate)).mtimeMs;
2842
2975
  if (age > 60_000) await rm(candidate, { force: true }).catch(() => {});
@@ -2870,10 +3003,18 @@ function readSnapshotFileCount(path: string, fingerprint: string): number | null
2870
3003
  }
2871
3004
  }
2872
3005
 
2873
- async function mostRecentCompatibleSnapshot(directory: string): Promise<{ path: string; files: number } | null> {
3006
+ async function mostRecentCompatibleSnapshot(
3007
+ directory: string,
3008
+ ): Promise<{ path: string; files: number } | null> {
2874
3009
  const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
2875
- const candidates = await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".sqlite"))
2876
- .map(async (entry) => ({ path: join(directory, entry.name), mtime: (await stat(join(directory, entry.name))).mtimeMs })));
3010
+ const candidates = await Promise.all(
3011
+ entries
3012
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".sqlite"))
3013
+ .map(async (entry) => ({
3014
+ path: join(directory, entry.name),
3015
+ mtime: (await stat(join(directory, entry.name))).mtimeMs,
3016
+ })),
3017
+ );
2877
3018
  for (const candidate of candidates.sort((left, right) => right.mtime - left.mtime)) {
2878
3019
  const files = readCompatibleSnapshotFileCount(candidate.path);
2879
3020
  if (files !== null) return { path: candidate.path, files };
@@ -2884,11 +3025,23 @@ async function mostRecentCompatibleSnapshot(directory: string): Promise<{ path:
2884
3025
  function readCompatibleSnapshotFileCount(path: string): number | null {
2885
3026
  try {
2886
3027
  const database = new DatabaseSync(path, { readOnly: true });
2887
- const schema = (database.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key = ?").get("schema_version") as { value?: string } | undefined)?.value;
2888
- const files = Number((database.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key = ?").get("source_file_count") as { value?: string } | undefined)?.value ?? 0);
3028
+ const schema = (
3029
+ database
3030
+ .prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key = ?")
3031
+ .get("schema_version") as { value?: string } | undefined
3032
+ )?.value;
3033
+ const files = Number(
3034
+ (
3035
+ database
3036
+ .prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key = ?")
3037
+ .get("source_file_count") as { value?: string } | undefined
3038
+ )?.value ?? 0,
3039
+ );
2889
3040
  database.close();
2890
3041
  return schema === readModelSchemaVersion ? files : null;
2891
- } catch { return null; }
3042
+ } catch {
3043
+ return null;
3044
+ }
2892
3045
  }
2893
3046
 
2894
3047
  /**
@@ -3008,34 +3161,37 @@ export type SqliteFirstFoldAllTime = {
3008
3161
  total_cost_brl: number | null;
3009
3162
  duration_share: number;
3010
3163
  }>;
3011
- macro_metrics: Record<"specs" | "prds", {
3012
- status_counts: Record<string, number>;
3013
- classification_counts: Record<string, number>;
3014
- entity_count: number;
3015
- total_run_count: number;
3016
- total_duration_ms: number | null;
3017
- total_tokens: number | null;
3018
- total_cost_usd: number | null;
3019
- total_cost_brl: number | null;
3020
- average_duration_ms: number | null;
3021
- average_tokens: number | null;
3022
- average_cost_usd: number | null;
3023
- average_cost_brl: number | null;
3024
- latest_created_at: string | null;
3025
- top_entities: Array<{
3026
- rank: number;
3027
- entity_id: string;
3028
- title: string;
3029
- status: string | null;
3164
+ macro_metrics: Record<
3165
+ "specs" | "prds",
3166
+ {
3167
+ status_counts: Record<string, number>;
3168
+ classification_counts: Record<string, number>;
3169
+ entity_count: number;
3030
3170
  total_run_count: number;
3031
- llm_call_count: number | null;
3032
3171
  total_duration_ms: number | null;
3033
3172
  total_tokens: number | null;
3034
3173
  total_cost_usd: number | null;
3035
3174
  total_cost_brl: number | null;
3036
- duration_share: number;
3037
- }>;
3038
- }>;
3175
+ average_duration_ms: number | null;
3176
+ average_tokens: number | null;
3177
+ average_cost_usd: number | null;
3178
+ average_cost_brl: number | null;
3179
+ latest_created_at: string | null;
3180
+ top_entities: Array<{
3181
+ rank: number;
3182
+ entity_id: string;
3183
+ title: string;
3184
+ status: string | null;
3185
+ total_run_count: number;
3186
+ llm_call_count: number | null;
3187
+ total_duration_ms: number | null;
3188
+ total_tokens: number | null;
3189
+ total_cost_usd: number | null;
3190
+ total_cost_brl: number | null;
3191
+ duration_share: number;
3192
+ }>;
3193
+ }
3194
+ >;
3039
3195
  };
3040
3196
 
3041
3197
  /** The only all-time facts needed before the Operations first fold is painted. */
@@ -3112,11 +3268,19 @@ export const sqliteViewMetadata = {
3112
3268
  first_fold_task_metrics_all_time: { date: null, id: null, order: "task_count DESC" },
3113
3269
  first_fold_top_tasks_all_time: { date: null, id: "task_id", order: "rank" },
3114
3270
  first_fold_spec_status_all_time: { date: null, id: "stage", order: "stage" },
3115
- first_fold_spec_classification_all_time: { date: null, id: "classification", order: "classification" },
3271
+ first_fold_spec_classification_all_time: {
3272
+ date: null,
3273
+ id: "classification",
3274
+ order: "classification",
3275
+ },
3116
3276
  first_fold_spec_metrics_all_time: { date: null, id: null, order: "entity_count DESC" },
3117
3277
  first_fold_top_specs_all_time: { date: null, id: "entity_id", order: "rank" },
3118
3278
  first_fold_prd_status_all_time: { date: null, id: "stage", order: "stage" },
3119
- first_fold_prd_classification_all_time: { date: null, id: "classification", order: "classification" },
3279
+ first_fold_prd_classification_all_time: {
3280
+ date: null,
3281
+ id: "classification",
3282
+ order: "classification",
3283
+ },
3120
3284
  first_fold_prd_metrics_all_time: { date: null, id: null, order: "entity_count DESC" },
3121
3285
  first_fold_top_prds_all_time: { date: null, id: "entity_id", order: "rank" },
3122
3286
  first_fold_task: { date: "occurred_day_sp", id: "id", order: "occurred_at DESC, id DESC" },
@@ -3207,9 +3371,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3207
3371
  const cached = this.unfilteredViewPageCache.get(view);
3208
3372
  if (cached !== undefined) return cached;
3209
3373
  const order = sqliteViewMetadata[view].order;
3210
- const rows = this.database
3211
- .prepare(`SELECT * FROM ${view} ORDER BY ${order}`)
3212
- .all() as Array<Record<string, unknown>>;
3374
+ const rows = this.database.prepare(`SELECT * FROM ${view} ORDER BY ${order}`).all() as Array<
3375
+ Record<string, unknown>
3376
+ >;
3213
3377
  this.unfilteredViewPageCache.set(view, rows);
3214
3378
  return rows;
3215
3379
  }
@@ -3353,12 +3517,16 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3353
3517
  const providerValues = (filter.providers ?? []).filter((value) => value !== "unavailable");
3354
3518
  const modelValues = (filter.models ?? []).filter((value) => value !== "unavailable");
3355
3519
  const requestsUnavailable =
3356
- filter.providers?.includes("unavailable") === true || filter.models?.includes("unavailable") === true;
3520
+ filter.providers?.includes("unavailable") === true ||
3521
+ filter.models?.includes("unavailable") === true;
3357
3522
  if (requestsUnavailable) {
3358
3523
  const unavailableDimension = (column: "provider" | "model", selected?: readonly string[]) => {
3359
3524
  if (selected === undefined || selected.length === 0) return;
3360
3525
  const available = selected.filter((value) => value !== "unavailable");
3361
- const matches = available.length === 0 ? "0" : `EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=a.agent_run_id AND u.${column} IN (${available.map(() => "?").join(",")}))`;
3526
+ const matches =
3527
+ available.length === 0
3528
+ ? "0"
3529
+ : `EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=a.agent_run_id AND u.${column} IN (${available.map(() => "?").join(",")}))`;
3362
3530
  if (available.length > 0) values.push(...available);
3363
3531
  const missing = `NOT EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=a.agent_run_id AND u.${column} IS NOT NULL AND u.${column} <> 'unavailable')`;
3364
3532
  clauses.push(selected.includes("unavailable") ? `(${matches} OR ${missing})` : matches);
@@ -3377,7 +3545,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3377
3545
  }
3378
3546
  // Provider and model describe one observed usage segment. Independent
3379
3547
  // EXISTS clauses would let a crossed provider/model pair match a mixed Run.
3380
- clauses.push(`EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=a.agent_run_id AND ${segmentClauses.join(" AND ")})`);
3548
+ clauses.push(
3549
+ `EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=a.agent_run_id AND ${segmentClauses.join(" AND ")})`,
3550
+ );
3381
3551
  }
3382
3552
  const dimensions = [
3383
3553
  ["zaip_agent", filter.agents],
@@ -3415,12 +3585,16 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3415
3585
  const providerValues = (filter.providers ?? []).filter((value) => value !== "unavailable");
3416
3586
  const modelValues = (filter.models ?? []).filter((value) => value !== "unavailable");
3417
3587
  const requestsUnavailable =
3418
- filter.providers?.includes("unavailable") === true || filter.models?.includes("unavailable") === true;
3588
+ filter.providers?.includes("unavailable") === true ||
3589
+ filter.models?.includes("unavailable") === true;
3419
3590
  if (requestsUnavailable) {
3420
3591
  const unavailableDimension = (column: "provider" | "model", selected?: readonly string[]) => {
3421
3592
  if (selected === undefined || selected.length === 0) return;
3422
3593
  const available = selected.filter((value) => value !== "unavailable");
3423
- const matches = available.length === 0 ? "0" : `EXISTS (SELECT 1 FROM invocations i JOIN invocation_usage u USING(agent_invocation_id) WHERE i.run_id=canonical_run_index.run_id AND u.${column} IN (${available.map(() => "?").join(",")}))`;
3594
+ const matches =
3595
+ available.length === 0
3596
+ ? "0"
3597
+ : `EXISTS (SELECT 1 FROM invocations i JOIN invocation_usage u USING(agent_invocation_id) WHERE i.run_id=canonical_run_index.run_id AND u.${column} IN (${available.map(() => "?").join(",")}))`;
3424
3598
  if (available.length > 0) values.push(...available);
3425
3599
  const missing = `NOT EXISTS (SELECT 1 FROM invocations i JOIN invocation_usage u USING(agent_invocation_id) WHERE i.run_id=canonical_run_index.run_id AND u.${column} IS NOT NULL AND u.${column} <> 'unavailable')`;
3426
3600
  clauses.push(selected.includes("unavailable") ? `(${matches} OR ${missing})` : matches);
@@ -3437,7 +3611,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3437
3611
  segmentClauses.push(`u.model IN (${modelValues.map(() => "?").join(",")})`);
3438
3612
  values.push(...modelValues);
3439
3613
  }
3440
- clauses.push(`EXISTS (SELECT 1 FROM invocations i JOIN invocation_usage u USING(agent_invocation_id) WHERE i.run_id=canonical_run_index.run_id AND ${segmentClauses.join(" AND ")})`);
3614
+ clauses.push(
3615
+ `EXISTS (SELECT 1 FROM invocations i JOIN invocation_usage u USING(agent_invocation_id) WHERE i.run_id=canonical_run_index.run_id AND ${segmentClauses.join(" AND ")})`,
3616
+ );
3441
3617
  }
3442
3618
  const dimensions = [
3443
3619
  ["zaip_agent", filter.agents],
@@ -3450,7 +3626,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3450
3626
  values.push(...selected);
3451
3627
  }
3452
3628
  const rows = this.database
3453
- .prepare(`SELECT occurred_day_sp AS day, COUNT(*) AS count FROM canonical_run_index WHERE ${clauses.join(" AND ")} GROUP BY occurred_day_sp`)
3629
+ .prepare(
3630
+ `SELECT occurred_day_sp AS day, COUNT(*) AS count FROM canonical_run_index WHERE ${clauses.join(" AND ")} GROUP BY occurred_day_sp`,
3631
+ )
3454
3632
  .all(...values) as Array<{ day: string; count: number }>;
3455
3633
  return new Map(rows.map((row) => [row.day, Number(row.count)]));
3456
3634
  }
@@ -3472,30 +3650,30 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3472
3650
  const knownUsageRuns = Number(row.activity_count ?? 0);
3473
3651
  const unknownUsageRuns = Math.max(0, totalRuns - knownUsageRuns);
3474
3652
  return [
3475
- day,
3476
- {
3477
- prds: Number(row.prd_count ?? 0),
3478
- specs: Number(row.spec_count ?? 0),
3479
- tasks: Number(row.task_count ?? 0),
3480
- validations: Number(row.validation_count ?? 0),
3481
- evidences: Number(row.evidence_count ?? 0),
3482
- runs: Number(row.total_run_count ?? 0),
3483
- usage: {
3484
- total_tokens: {
3485
- known_sum: Number(row.total_tokens ?? 0),
3486
- unknown: unknownUsageRuns,
3653
+ day,
3654
+ {
3655
+ prds: Number(row.prd_count ?? 0),
3656
+ specs: Number(row.spec_count ?? 0),
3657
+ tasks: Number(row.task_count ?? 0),
3658
+ validations: Number(row.validation_count ?? 0),
3659
+ evidences: Number(row.evidence_count ?? 0),
3660
+ runs: Number(row.total_run_count ?? 0),
3661
+ usage: {
3662
+ total_tokens: {
3663
+ known_sum: Number(row.total_tokens ?? 0),
3664
+ unknown: unknownUsageRuns,
3665
+ },
3666
+ cost: {
3667
+ known_sum: Number(row.total_cost_brl ?? 0),
3668
+ unknown: unknownUsageRuns,
3669
+ },
3487
3670
  },
3488
- cost: {
3489
- known_sum: Number(row.total_cost_brl ?? 0),
3490
- unknown: unknownUsageRuns,
3671
+ duration: {
3672
+ known_sum: durations.get(day) ?? 0,
3673
+ unknown: durations.has(day) ? 0 : Number(row.total_run_count ?? 0),
3491
3674
  },
3492
3675
  },
3493
- duration: {
3494
- known_sum: durations.get(day) ?? 0,
3495
- unknown: durations.has(day) ? 0 : Number(row.total_run_count ?? 0),
3496
- },
3497
- },
3498
- ];
3676
+ ];
3499
3677
  }),
3500
3678
  );
3501
3679
  }
@@ -3533,15 +3711,21 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3533
3711
  * A later month with no facts in this immutable generation is also an exact
3534
3712
  * empty result; every month that may contain a fact continues through SQL.
3535
3713
  */
3536
- firstFoldMonthlyQuick(filter: { from: string | null; to: string | null }): SqliteFirstFoldMonthlyQuick | null {
3714
+ firstFoldMonthlyQuick(filter: {
3715
+ from: string | null;
3716
+ to: string | null;
3717
+ }): SqliteFirstFoldMonthlyQuick | null {
3537
3718
  if (this.firstFoldMonthlyQuickCache === undefined) {
3538
3719
  const persisted = this.database
3539
- .prepare("SELECT payload_json FROM zaip_aios_activities WHERE payload_type='first_fold_monthly_quick_cache' ORDER BY recorded_at DESC LIMIT 1")
3720
+ .prepare(
3721
+ "SELECT payload_json FROM zaip_aios_activities WHERE payload_type='first_fold_monthly_quick_cache' ORDER BY recorded_at DESC LIMIT 1",
3722
+ )
3540
3723
  .get() as { payload_json?: string } | undefined;
3541
3724
  try {
3542
- const candidate = typeof persisted?.payload_json === "string"
3543
- ? JSON.parse(persisted.payload_json) as Partial<SqliteFirstFoldMonthlyQuick>
3544
- : null;
3725
+ const candidate =
3726
+ typeof persisted?.payload_json === "string"
3727
+ ? (JSON.parse(persisted.payload_json) as Partial<SqliteFirstFoldMonthlyQuick>)
3728
+ : null;
3545
3729
  this.firstFoldMonthlyQuickCache =
3546
3730
  candidate !== null &&
3547
3731
  typeof candidate.from_day === "string" &&
@@ -3549,7 +3733,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3549
3733
  candidate.aggregate !== undefined &&
3550
3734
  candidate.pages !== undefined &&
3551
3735
  Array.isArray(candidate.top_tasks)
3552
- ? candidate as SqliteFirstFoldMonthlyQuick
3736
+ ? (candidate as SqliteFirstFoldMonthlyQuick)
3553
3737
  : null;
3554
3738
  } catch {
3555
3739
  this.firstFoldMonthlyQuickCache = null;
@@ -3600,13 +3784,15 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3600
3784
  }
3601
3785
 
3602
3786
  firstFoldRelevantLatestDay(): string | null {
3603
- const row = this.database.prepare(`SELECT MAX(day) AS day FROM (
3787
+ const row = this.database
3788
+ .prepare(`SELECT MAX(day) AS day FROM (
3604
3789
  SELECT occurred_day_sp AS day FROM activities
3605
3790
  UNION ALL SELECT occurred_day_sp AS day FROM canonical_work_items
3606
3791
  UNION ALL SELECT occurred_day_sp AS day FROM canonical_artifacts
3607
3792
  UNION ALL SELECT occurred_day_sp AS day FROM canonical_run_index
3608
3793
  UNION ALL SELECT occurred_day_sp AS day FROM attributed_token_active_facts
3609
- )`).get() as { day?: string | null };
3794
+ )`)
3795
+ .get() as { day?: string | null };
3610
3796
  return typeof row.day === "string" ? row.day : null;
3611
3797
  }
3612
3798
 
@@ -3632,8 +3818,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3632
3818
  const topTasks = this.database
3633
3819
  .prepare("SELECT * FROM zaip_vw_first_fold_top_tasks_all_time ORDER BY rank")
3634
3820
  .all() as SqliteFirstFoldAllTime["top_tasks"];
3635
- const number = (value: unknown, fallback = 0) =>
3636
- typeof value === "number" ? value : fallback;
3821
+ const number = (value: unknown, fallback = 0) => (typeof value === "number" ? value : fallback);
3637
3822
  const nullableNumber = (value: unknown) => (typeof value === "number" ? value : null);
3638
3823
  const readMacroMetrics = (kind: "spec" | "prd") => {
3639
3824
  const statusRows = this.database
@@ -3741,7 +3926,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3741
3926
  firstFoldViewportAllTime(): SqliteFirstFoldViewport {
3742
3927
  if (this.firstFoldViewportCache !== null) return this.firstFoldViewportCache;
3743
3928
  const persisted = this.database
3744
- .prepare("SELECT payload_json FROM zaip_aios_activities WHERE payload_type='first_fold_viewport_cache' ORDER BY recorded_at DESC LIMIT 1")
3929
+ .prepare(
3930
+ "SELECT payload_json FROM zaip_aios_activities WHERE payload_type='first_fold_viewport_cache' ORDER BY recorded_at DESC LIMIT 1",
3931
+ )
3745
3932
  .get() as { payload_json?: string } | undefined;
3746
3933
  if (typeof persisted?.payload_json === "string") {
3747
3934
  this.firstFoldViewportCache = JSON.parse(persisted.payload_json) as SqliteFirstFoldViewport;
@@ -3756,7 +3943,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3756
3943
  const startedAt = performance.now();
3757
3944
  const value = read();
3758
3945
  if (profile)
3759
- this.firstFoldViewportTimings[select] = Math.round((performance.now() - startedAt) * 10) / 10;
3946
+ this.firstFoldViewportTimings[select] =
3947
+ Math.round((performance.now() - startedAt) * 10) / 10;
3760
3948
  if (profile)
3761
3949
  console.info(
3762
3950
  JSON.stringify({
@@ -3767,8 +3955,11 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3767
3955
  );
3768
3956
  return value;
3769
3957
  };
3770
- const row = timed("viewport_overview", () => this.database
3771
- .prepare(`WITH totals AS (
3958
+ const row = timed(
3959
+ "viewport_overview",
3960
+ () =>
3961
+ this.database
3962
+ .prepare(`WITH totals AS (
3772
3963
  SELECT SUM(CASE WHEN activity_source_kind='agent_run' THEN 1 ELSE 0 END) AS agent_invocation_count,
3773
3964
  SUM(llm_call_count) AS llm_call_count,
3774
3965
  SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS total_cost_usd,
@@ -3829,9 +4020,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3829
4020
  ) SELECT totals.*, run_totals.*, duration_totals.*, bounds.*, item_totals.*, task_status.*, task_risk.*
3830
4021
  FROM totals CROSS JOIN run_totals CROSS JOIN duration_totals CROSS JOIN bounds
3831
4022
  CROSS JOIN item_totals CROSS JOIN task_status CROSS JOIN task_risk`)
3832
- .get() as Record<string, unknown>);
3833
- const number = (value: unknown, fallback = 0) =>
3834
- typeof value === "number" ? value : fallback;
4023
+ .get() as Record<string, unknown>,
4024
+ );
4025
+ const number = (value: unknown, fallback = 0) => (typeof value === "number" ? value : fallback);
3835
4026
  const nullableNumber = (value: unknown) => (typeof value === "number" ? value : null);
3836
4027
  this.firstFoldViewportCache = {
3837
4028
  aggregate: {
@@ -3889,7 +4080,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3889
4080
  AND (? IS NULL OR f.occurred_day_sp >= ?) AND (? IS NULL OR f.occurred_day_sp <= ?)
3890
4081
  GROUP BY a.task_id`)
3891
4082
  .all(from, from, to, to) as Array<{ task_id: string; duration_ms: number }>;
3892
- const durationByTask = new Map(durationRows.map((row) => [row.task_id, Number(row.duration_ms)]));
4083
+ const durationByTask = new Map(
4084
+ durationRows.map((row) => [row.task_id, Number(row.duration_ms)]),
4085
+ );
3893
4086
  return facts
3894
4087
  .map((row) => ({
3895
4088
  ...row,
@@ -3935,14 +4128,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3935
4128
  }>;
3936
4129
  const knownDurationRuns = runs.filter(
3937
4130
  (run) =>
3938
- run.run_role === "root" &&
3939
- run.phase === "execution" &&
3940
- typeof run.duration_ms === "number",
4131
+ run.run_role === "root" && run.phase === "execution" && typeof run.duration_ms === "number",
3941
4132
  ).length;
3942
- const durationByDay = this.executionDurationByDay(
3943
- { from: null, to: null },
3944
- taskId,
3945
- );
4133
+ const durationByDay = this.executionDurationByDay({ from: null, to: null }, taskId);
3946
4134
  const duration =
3947
4135
  durationByDay.size === 0
3948
4136
  ? null
@@ -4018,11 +4206,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4018
4206
  const to = filter.to?.slice(0, 10) ?? null;
4019
4207
  // Financial periods are source-session periods when available. This keeps
4020
4208
  // a retrospective capture on its observed day rather than its write day.
4021
- const periodDay = "date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours')";
4022
- const clauses = [
4023
- `(? IS NULL OR ${periodDay} >= ?)`,
4024
- `(? IS NULL OR ${periodDay} <= ?)`,
4025
- ];
4209
+ const periodDay =
4210
+ "date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours')";
4211
+ const clauses = [`(? IS NULL OR ${periodDay} >= ?)`, `(? IS NULL OR ${periodDay} <= ?)`];
4026
4212
  const values: Array<string | number | null> = [from, from, to, to];
4027
4213
  const add = (column: string, items?: readonly string[]) => {
4028
4214
  if (items !== undefined && items.length > 0) {
@@ -4041,7 +4227,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4041
4227
  const providerValues = (filter.providers ?? []).filter((item) => item !== "unavailable");
4042
4228
  const modelValues = (filter.models ?? []).filter((item) => item !== "unavailable");
4043
4229
  const requestsUnavailable =
4044
- filter.providers?.includes("unavailable") === true || filter.models?.includes("unavailable") === true;
4230
+ filter.providers?.includes("unavailable") === true ||
4231
+ filter.models?.includes("unavailable") === true;
4045
4232
  if (requestsUnavailable) {
4046
4233
  // Keep the pre-existing explicit-unavailable behaviour; it describes a
4047
4234
  // missing invocation attribute rather than a provider/model pair.
@@ -4069,7 +4256,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4069
4256
  segmentClauses.push(`f.model IN (${modelValues.map(() => "?").join(",")})`);
4070
4257
  values.push(...modelValues);
4071
4258
  }
4072
- clauses.push(`EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id = i.agent_invocation_id AND ${segmentClauses.join(" AND ")})`);
4259
+ clauses.push(
4260
+ `EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id = i.agent_invocation_id AND ${segmentClauses.join(" AND ")})`,
4261
+ );
4073
4262
  }
4074
4263
  return this.database
4075
4264
  .prepare(`SELECT i.*, CASE COUNT(DISTINCT s.provider) WHEN 0 THEN NULL WHEN 1 THEN MIN(s.provider) ELSE 'mixed' END AS provider, CASE COUNT(DISTINCT s.model) WHEN 0 THEN NULL WHEN 1 THEN MIN(s.model) ELSE 'mixed' END AS model,
@@ -4090,7 +4279,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4090
4279
  */
4091
4280
  costMonitorFactualDurationByDay(
4092
4281
  filter: Parameters<SqliteReadModelFileSystem["invocationCostRows"]>[0],
4093
- financialRows: readonly Record<string, unknown>[] = this.costMonitorFinancialActivityRows(filter),
4282
+ financialRows: readonly Record<string, unknown>[] = this.costMonitorFinancialActivityRows(
4283
+ filter,
4284
+ ),
4094
4285
  ): Map<string, { known_sum: number; eligible_runs: number; known_runs: number }> {
4095
4286
  const rows = financialRows;
4096
4287
  const activityIds = new Set<string>();
@@ -4105,7 +4296,10 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4105
4296
  ? row.occurred_at
4106
4297
  : null;
4107
4298
  if (timestamp === null) continue;
4108
- const hasFinancialFact = typeof row.total_tokens === "number" || typeof row.cost_brl === "number" || typeof row.cost_usd === "number";
4299
+ const hasFinancialFact =
4300
+ typeof row.total_tokens === "number" ||
4301
+ typeof row.cost_brl === "number" ||
4302
+ typeof row.cost_usd === "number";
4109
4303
  if (!hasFinancialFact) continue;
4110
4304
  // SQLite exposes the durable activity key as an integer; normalize it
4111
4305
  // before joining it to the JSON-backed temporal fact key.
@@ -4122,7 +4316,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4122
4316
  // local session can be refreshed instead of failing its Cost Monitor.
4123
4317
  const durationFactTable =
4124
4318
  this.database
4125
- .prepare("SELECT 1 AS present FROM sqlite_master WHERE name = 'attributed_token_active_facts'")
4319
+ .prepare(
4320
+ "SELECT 1 AS present FROM sqlite_master WHERE name = 'attributed_token_active_facts'",
4321
+ )
4126
4322
  .get() !== undefined
4127
4323
  ? "attributed_token_active_facts"
4128
4324
  : "zaip_vw_attributed_token_active_facts";
@@ -4168,7 +4364,10 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4168
4364
  ids.add(activityId);
4169
4365
  unavailableByDay.set(day, ids);
4170
4366
  }
4171
- const byDay = new Map<string, { known_sum: number; eligible_runs: number; known_runs: number }>();
4367
+ const byDay = new Map<
4368
+ string,
4369
+ { known_sum: number; eligible_runs: number; known_runs: number }
4370
+ >();
4172
4371
  for (const day of new Set([...factsByDay.keys(), ...unavailableByDay.keys()])) {
4173
4372
  const facts = factsByDay.get(day) ?? { known_sum: 0, activityIds: new Set<string>() };
4174
4373
  const unavailableIds = unavailableByDay.get(day) ?? new Set<string>();
@@ -4227,7 +4426,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4227
4426
  }
4228
4427
 
4229
4428
  /** Cost Monitor's historical financial population, independent of Agent-only views. */
4230
- costMonitorFinancialActivityRows(filter: Parameters<SqliteReadModelFileSystem["activityCostRows"]>[0]) {
4429
+ costMonitorFinancialActivityRows(
4430
+ filter: Parameters<SqliteReadModelFileSystem["activityCostRows"]>[0],
4431
+ ) {
4231
4432
  // Activity capture day can differ from the observed source session day.
4232
4433
  // Keep that source-session population in SQL: the previous implementation
4233
4434
  // read every activity into JS and issued a usage lookup for each filtered
@@ -4235,7 +4436,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4235
4436
  const { from, to, providers = [], models = [] } = filter;
4236
4437
  const sourceFrom = costMonitorFilterDay(from);
4237
4438
  const sourceTo = costMonitorFilterDay(to);
4238
- const sourceDay = "date(COALESCE(a.source_started_at, a.source_occurred_at, a.occurred_at), '-3 hours')";
4439
+ const sourceDay =
4440
+ "date(COALESCE(a.source_started_at, a.source_occurred_at, a.occurred_at), '-3 hours')";
4239
4441
  const clauses = [
4240
4442
  "(a.total_tokens IS NOT NULL OR a.cost_brl IS NOT NULL OR a.cost_usd IS NOT NULL)",
4241
4443
  `(? IS NULL OR ${sourceDay} >= ?)`,
@@ -4259,15 +4461,25 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4259
4461
  if (providers.length > 0 || models.length > 0) {
4260
4462
  const usageClauses: string[] = [];
4261
4463
  if (providers.length > 0) {
4262
- usageClauses.push(`COALESCE(u.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`);
4464
+ usageClauses.push(
4465
+ `COALESCE(u.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`,
4466
+ );
4263
4467
  }
4264
4468
  if (models.length > 0) {
4265
- usageClauses.push(`COALESCE(u.model, 'unavailable') IN (${models.map(() => "?").join(",")})`);
4469
+ usageClauses.push(
4470
+ `COALESCE(u.model, 'unavailable') IN (${models.map(() => "?").join(",")})`,
4471
+ );
4266
4472
  }
4267
4473
  const usageValues = [...providers, ...models];
4268
4474
  const directClauses: string[] = [];
4269
- if (providers.length > 0) directClauses.push(`COALESCE(a.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`);
4270
- if (models.length > 0) directClauses.push(`COALESCE(a.model, 'unavailable') IN (${models.map(() => "?").join(",")})`);
4475
+ if (providers.length > 0)
4476
+ directClauses.push(
4477
+ `COALESCE(a.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`,
4478
+ );
4479
+ if (models.length > 0)
4480
+ directClauses.push(
4481
+ `COALESCE(a.model, 'unavailable') IN (${models.map(() => "?").join(",")})`,
4482
+ );
4271
4483
  // Rows without an Agent Invocation keep their activity dimensions;
4272
4484
  // Agent rows retain the segment-level provider/model semantics.
4273
4485
  clauses.push(
@@ -4308,7 +4520,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4308
4520
  const { from, to, providers = [], models = [] } = filter;
4309
4521
  const sourceFrom = costMonitorFilterDay(from);
4310
4522
  const sourceTo = costMonitorFilterDay(to);
4311
- const sourceDay = "date(COALESCE(a.source_started_at, a.source_occurred_at, a.occurred_at), '-3 hours')";
4523
+ const sourceDay =
4524
+ "date(COALESCE(a.source_started_at, a.source_occurred_at, a.occurred_at), '-3 hours')";
4312
4525
  const clauses = [
4313
4526
  "(a.total_tokens IS NOT NULL OR a.cost_brl IS NOT NULL OR a.cost_usd IS NOT NULL)",
4314
4527
  `(? IS NULL OR ${sourceDay} >= ?)`,
@@ -4334,14 +4547,22 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4334
4547
  const usageValues: string[] = [];
4335
4548
  const directValues: string[] = [];
4336
4549
  if (providers.length > 0) {
4337
- usageClauses.push(`COALESCE(u.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`);
4338
- directClauses.push(`COALESCE(a.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`);
4550
+ usageClauses.push(
4551
+ `COALESCE(u.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`,
4552
+ );
4553
+ directClauses.push(
4554
+ `COALESCE(a.provider, 'unavailable') IN (${providers.map(() => "?").join(",")})`,
4555
+ );
4339
4556
  usageValues.push(...providers);
4340
4557
  directValues.push(...providers);
4341
4558
  }
4342
4559
  if (models.length > 0) {
4343
- usageClauses.push(`COALESCE(u.model, 'unavailable') IN (${models.map(() => "?").join(",")})`);
4344
- directClauses.push(`COALESCE(a.model, 'unavailable') IN (${models.map(() => "?").join(",")})`);
4560
+ usageClauses.push(
4561
+ `COALESCE(u.model, 'unavailable') IN (${models.map(() => "?").join(",")})`,
4562
+ );
4563
+ directClauses.push(
4564
+ `COALESCE(a.model, 'unavailable') IN (${models.map(() => "?").join(",")})`,
4565
+ );
4345
4566
  usageValues.push(...models);
4346
4567
  directValues.push(...models);
4347
4568
  }
@@ -4387,21 +4608,34 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4387
4608
  FROM selected GROUP BY day, phase_key ORDER BY day, phase_key`)
4388
4609
  .all(...values) as Array<Record<string, unknown>>;
4389
4610
  const nullableColumns = [
4390
- "duration_ms", "input_tokens", "cached_input_tokens", "output_tokens",
4391
- "reasoning_tokens", "total_tokens", "cost_usd", "cost_brl",
4611
+ "duration_ms",
4612
+ "input_tokens",
4613
+ "cached_input_tokens",
4614
+ "output_tokens",
4615
+ "reasoning_tokens",
4616
+ "total_tokens",
4617
+ "cost_usd",
4618
+ "cost_brl",
4392
4619
  ] as const;
4393
4620
  const additiveColumns = [
4394
- "eligible_runs", "events", "duration_known", "tokens_known", "usd_known", "brl_known",
4621
+ "eligible_runs",
4622
+ "events",
4623
+ "duration_known",
4624
+ "tokens_known",
4625
+ "usd_known",
4626
+ "brl_known",
4395
4627
  ] as const;
4396
4628
  const rollup = (key: string, rows: readonly Record<string, unknown>[]) => {
4397
4629
  const result: Record<string, unknown> = { key };
4398
4630
  for (const column of additiveColumns)
4399
- result[column] = rows.reduce((sum, row) => sum + (typeof row[column] === "number" ? Number(row[column]) : 0), 0);
4631
+ result[column] = rows.reduce(
4632
+ (sum, row) => sum + (typeof row[column] === "number" ? Number(row[column]) : 0),
4633
+ 0,
4634
+ );
4400
4635
  for (const column of nullableColumns) {
4401
4636
  const present = rows.filter((row) => typeof row[column] === "number");
4402
- result[column] = present.length === 0
4403
- ? null
4404
- : present.reduce((sum, row) => sum + Number(row[column]), 0);
4637
+ result[column] =
4638
+ present.length === 0 ? null : present.reduce((sum, row) => sum + Number(row[column]), 0);
4405
4639
  }
4406
4640
  const taskIds = new Set<string>();
4407
4641
  for (const row of rows) {
@@ -4413,10 +4647,18 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4413
4647
  return result;
4414
4648
  };
4415
4649
  const phaseKeys = ["conversation", "planning", "execution"] as const;
4416
- const phaseRows = phaseKeys.map((key) => rollup(key, factRows.filter((row) => row.phase_key === key)));
4650
+ const phaseRows = phaseKeys.map((key) =>
4651
+ rollup(
4652
+ key,
4653
+ factRows.filter((row) => row.phase_key === key),
4654
+ ),
4655
+ );
4417
4656
  phaseRows.push(rollup("total", factRows));
4418
4657
  const dailyRows = [...new Set(factRows.map((row) => String(row.day)))].map((day) =>
4419
- rollup(day, factRows.filter((row) => String(row.day) === day)),
4658
+ rollup(
4659
+ day,
4660
+ factRows.filter((row) => String(row.day) === day),
4661
+ ),
4420
4662
  );
4421
4663
  const structuralRows = this.database
4422
4664
  .prepare(`WITH canonical_ids AS MATERIALIZED (
@@ -4467,7 +4709,16 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4467
4709
  SUM(validation_count) AS validation_count,
4468
4710
  SUM(evidence_count) AS evidence_count
4469
4711
  FROM structural GROUP BY day ORDER BY day`)
4470
- .all(sourceFrom, sourceFrom, sourceTo, sourceTo, sourceFrom, sourceFrom, sourceTo, sourceTo) as Array<Record<string, unknown>>;
4712
+ .all(
4713
+ sourceFrom,
4714
+ sourceFrom,
4715
+ sourceTo,
4716
+ sourceTo,
4717
+ sourceFrom,
4718
+ sourceFrom,
4719
+ sourceTo,
4720
+ sourceTo,
4721
+ ) as Array<Record<string, unknown>>;
4471
4722
  const number = (value: unknown) => (typeof value === "number" ? value : 0);
4472
4723
  const nullable = (value: unknown) => (typeof value === "number" ? value : null);
4473
4724
  const metric = (row?: Record<string, unknown>) => {
@@ -4588,7 +4839,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4588
4839
  // phases and daily rows. Calling the paginated view here was tempting but
4589
4840
  // silently loses multi-value and lineage filters (Task/SPEC/PRD/etc.).
4590
4841
  // This remains one bounded SQL aggregate, without transporting Runs.
4591
- const summaryModelRows = this.database.prepare(`${selected},
4842
+ const summaryModelRows = this.database
4843
+ .prepare(`${selected},
4592
4844
  segment_totals AS (
4593
4845
  SELECT agent_invocation_id, SUM(total_tokens) AS total_tokens,
4594
4846
  SUM(cost_usd) AS cost_usd, SUM(cost_brl) AS cost_brl
@@ -4630,7 +4882,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4630
4882
  NULL AS total_duration, 0 AS duration_unknown, 0 AS duration_known,
4631
4883
  0 AS source_event_count, SUM(total_tokens) AS total_tokens,
4632
4884
  SUM(total_cost_usd) AS total_cost_usd, SUM(total_cost_brl) AS total_cost_brl
4633
- FROM components GROUP BY key ORDER BY total_cost_brl DESC, key`).all(...values) as Array<Record<string, unknown>>;
4885
+ FROM components GROUP BY key ORDER BY total_cost_brl DESC, key`)
4886
+ .all(...values) as Array<Record<string, unknown>>;
4634
4887
  const model_groups = summaryModelRows.map((row) => {
4635
4888
  const duration = nullable(row.total_duration);
4636
4889
  const durationUnknown = number(row.duration_unknown);
@@ -4696,7 +4949,11 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4696
4949
  const from = filter.from?.slice(0, 10) ?? null;
4697
4950
  const to = filter.to?.slice(0, 10) ?? null;
4698
4951
  const cursorValue = parseSummaryCursor(cursor);
4699
- const clauses = ["entity_kind = ?", "(? IS NULL OR occurred_day_sp >= ?)", "(? IS NULL OR occurred_day_sp <= ?)"];
4952
+ const clauses = [
4953
+ "entity_kind = ?",
4954
+ "(? IS NULL OR occurred_day_sp >= ?)",
4955
+ "(? IS NULL OR occurred_day_sp <= ?)",
4956
+ ];
4700
4957
  const values: Array<string | number | null> = [kind, from, from, to, to];
4701
4958
  if (filter.status) {
4702
4959
  clauses.push("status = ?");
@@ -4788,9 +5045,20 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4788
5045
  viewPage(
4789
5046
  view: SqliteViewName,
4790
5047
  input: {
4791
- from?: string | null; to?: string | null; query?: string; status?: string; risk?: string;
4792
- provider?: string; model?: string; agent?: string; phase?: string; origin?: string;
4793
- workType?: string; priority?: string; offset?: number; limit?: number;
5048
+ from?: string | null;
5049
+ to?: string | null;
5050
+ query?: string;
5051
+ status?: string;
5052
+ risk?: string;
5053
+ provider?: string;
5054
+ model?: string;
5055
+ agent?: string;
5056
+ phase?: string;
5057
+ origin?: string;
5058
+ workType?: string;
5059
+ priority?: string;
5060
+ offset?: number;
5061
+ limit?: number;
4794
5062
  },
4795
5063
  ): ReturnType<SqliteReadModelFileSystem["uncachedViewPage"]> {
4796
5064
  // The key includes every filter and pagination input. The cache is scoped
@@ -4798,7 +5066,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4798
5066
  // launcher refresh cannot expose a page from the previous snapshot.
4799
5067
  const key = `${view}:${JSON.stringify(input)}`;
4800
5068
  const cached = this.viewPageResultCache.get(key);
4801
- if (cached !== undefined) return structuredClone(cached) as ReturnType<SqliteReadModelFileSystem["uncachedViewPage"]>;
5069
+ if (cached !== undefined)
5070
+ return structuredClone(cached) as ReturnType<SqliteReadModelFileSystem["uncachedViewPage"]>;
4802
5071
  const result = this.uncachedViewPage(view, input);
4803
5072
  if (this.viewPageResultCache.size >= 256) {
4804
5073
  const oldest = this.viewPageResultCache.keys().next().value as string | undefined;
@@ -4836,9 +5105,18 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4836
5105
  ];
4837
5106
  if (
4838
5107
  cacheableUnfilteredViews.includes(view) &&
4839
- !input.from && !input.to && !input.query && !input.status && !input.risk &&
4840
- !input.provider && !input.model && !input.agent && !input.phase &&
4841
- !input.origin && !input.workType && !input.priority
5108
+ !input.from &&
5109
+ !input.to &&
5110
+ !input.query &&
5111
+ !input.status &&
5112
+ !input.risk &&
5113
+ !input.provider &&
5114
+ !input.model &&
5115
+ !input.agent &&
5116
+ !input.phase &&
5117
+ !input.origin &&
5118
+ !input.workType &&
5119
+ !input.priority
4842
5120
  ) {
4843
5121
  const rows = this.unfilteredViewRows(view);
4844
5122
  const limit = Math.min(200, Math.max(1, input.limit ?? 100));
@@ -4882,11 +5160,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4882
5160
  };
4883
5161
  }
4884
5162
  if (view === "cost_monitor_agent_invocations") {
4885
- const sourceDay = "date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours')";
4886
- const clauses = [
4887
- `(? IS NULL OR ${sourceDay} >= ?)`,
4888
- `(? IS NULL OR ${sourceDay} <= ?)`,
4889
- ];
5163
+ const sourceDay =
5164
+ "date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours')";
5165
+ const clauses = [`(? IS NULL OR ${sourceDay} >= ?)`, `(? IS NULL OR ${sourceDay} <= ?)`];
4890
5166
  const from = costMonitorFilterDay(input.from);
4891
5167
  const to = costMonitorFilterDay(input.to);
4892
5168
  const values: Array<string | number | null> = [from, from, to, to];
@@ -4902,7 +5178,9 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4902
5178
  const usageSegmentClauses: string[] = [];
4903
5179
  if (input.provider) {
4904
5180
  if (input.provider === "unavailable") {
4905
- clauses.push("((i.agent_invocation_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')) OR (i.agent_invocation_id IS NULL AND (i.provider IS NULL OR i.provider = 'unavailable')))");
5181
+ clauses.push(
5182
+ "((i.agent_invocation_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')) OR (i.agent_invocation_id IS NULL AND (i.provider IS NULL OR i.provider = 'unavailable')))",
5183
+ );
4906
5184
  } else {
4907
5185
  usageSegmentClauses.push("f.provider=?");
4908
5186
  values.push(input.provider);
@@ -4910,14 +5188,18 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4910
5188
  }
4911
5189
  if (input.model) {
4912
5190
  if (input.model === "unavailable") {
4913
- clauses.push("((i.agent_invocation_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM invocation_usage known_model WHERE known_model.agent_invocation_id=i.agent_invocation_id AND known_model.model IS NOT NULL AND known_model.model <> 'unavailable')) OR (i.agent_invocation_id IS NULL AND (i.model IS NULL OR i.model = 'unavailable')))");
5191
+ clauses.push(
5192
+ "((i.agent_invocation_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM invocation_usage known_model WHERE known_model.agent_invocation_id=i.agent_invocation_id AND known_model.model IS NOT NULL AND known_model.model <> 'unavailable')) OR (i.agent_invocation_id IS NULL AND (i.model IS NULL OR i.model = 'unavailable')))",
5193
+ );
4914
5194
  } else {
4915
5195
  usageSegmentClauses.push("f.model=?");
4916
5196
  values.push(input.model);
4917
5197
  }
4918
5198
  }
4919
5199
  if (usageSegmentClauses.length > 0)
4920
- clauses.push(`EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id=i.agent_invocation_id AND ${usageSegmentClauses.join(" AND ")})`);
5200
+ clauses.push(
5201
+ `EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id=i.agent_invocation_id AND ${usageSegmentClauses.join(" AND ")})`,
5202
+ );
4921
5203
  if (input.query) {
4922
5204
  clauses.push("(i.agent_invocation_id LIKE ? OR i.task_id LIKE ? OR i.execution_id LIKE ?)");
4923
5205
  values.push(`%${input.query}%`, `%${input.query}%`, `%${input.query}%`);
@@ -4925,14 +5207,14 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4925
5207
  const where = clauses.join(" AND ");
4926
5208
  const limit = Math.min(200, Math.max(1, input.limit ?? 100));
4927
5209
  const offset = Math.max(0, input.offset ?? 0);
4928
- let total_count = Number(
5210
+ const total_count = Number(
4929
5211
  (
4930
5212
  this.database
4931
5213
  .prepare(`SELECT COUNT(*) AS count FROM cost_agent_invocations i WHERE ${where}`)
4932
5214
  .get(...values) as { count: number }
4933
5215
  ).count,
4934
5216
  );
4935
- let rows = this.database
5217
+ const rows = this.database
4936
5218
  .prepare(
4937
5219
  `SELECT i.* FROM cost_agent_invocations i WHERE ${where} ORDER BY i.occurred_at DESC, i.agent_invocation_id DESC LIMIT ? OFFSET ?`,
4938
5220
  )
@@ -4960,12 +5242,22 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4960
5242
  const filteredCostGroup = filteredCostGroups[view];
4961
5243
  const isUnfilteredCostDimension =
4962
5244
  (filteredCostGroup === "model" || filteredCostGroup === "provider") &&
4963
- !input.from && !input.to && !input.query && !input.status && !input.risk &&
4964
- !input.provider && !input.model && !input.agent && !input.phase &&
4965
- !input.origin && !input.workType && !input.priority;
5245
+ !input.from &&
5246
+ !input.to &&
5247
+ !input.query &&
5248
+ !input.status &&
5249
+ !input.risk &&
5250
+ !input.provider &&
5251
+ !input.model &&
5252
+ !input.agent &&
5253
+ !input.phase &&
5254
+ !input.origin &&
5255
+ !input.workType &&
5256
+ !input.priority;
4966
5257
  if (isUnfilteredCostDimension) {
4967
5258
  const dimension = filteredCostGroup;
4968
- const payloadRows = this.database.prepare(`SELECT
5259
+ const payloadRows = this.database
5260
+ .prepare(`SELECT
4969
5261
  json_extract(payload_json, '$.key') AS key,
4970
5262
  json_extract(payload_json, '$.label') AS label,
4971
5263
  json_extract(payload_json, '$.invocation_count') AS invocation_count,
@@ -4978,7 +5270,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4978
5270
  FROM zaip_aios_activities
4979
5271
  WHERE payload_type='cost_dimension_aggregate_v1'
4980
5272
  AND json_extract(payload_json, '$.dimension')=?
4981
- ORDER BY total_cost_brl DESC, key`).all(dimension) as Array<Record<string, unknown>>;
5273
+ ORDER BY total_cost_brl DESC, key`)
5274
+ .all(dimension) as Array<Record<string, unknown>>;
4982
5275
  const limit = Math.min(200, Math.max(1, input.limit ?? 50));
4983
5276
  const offset = Math.max(0, input.offset ?? 0);
4984
5277
  const rows = payloadRows.slice(offset, offset + limit);
@@ -4995,17 +5288,46 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
4995
5288
  if (filteredCostGroup === "agent") {
4996
5289
  const clauses = ["i.zaip_agent IS NOT NULL"];
4997
5290
  const values: Array<string | number> = [];
4998
- if (input.from) { clauses.push("date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?"); values.push(input.from.slice(0, 10)); }
4999
- if (input.to) { clauses.push("date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?"); values.push(input.to.slice(0, 10)); }
5000
- if (input.agent) { clauses.push("i.zaip_agent = ?"); values.push(input.agent); }
5001
- if (input.phase) { clauses.push("i.phase = ?"); values.push(input.phase); }
5002
- if (input.status) { clauses.push("i.status = ?"); values.push(input.status); }
5291
+ if (input.from) {
5292
+ clauses.push(
5293
+ "date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?",
5294
+ );
5295
+ values.push(input.from.slice(0, 10));
5296
+ }
5297
+ if (input.to) {
5298
+ clauses.push(
5299
+ "date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?",
5300
+ );
5301
+ values.push(input.to.slice(0, 10));
5302
+ }
5303
+ if (input.agent) {
5304
+ clauses.push("i.zaip_agent = ?");
5305
+ values.push(input.agent);
5306
+ }
5307
+ if (input.phase) {
5308
+ clauses.push("i.phase = ?");
5309
+ values.push(input.phase);
5310
+ }
5311
+ if (input.status) {
5312
+ clauses.push("i.status = ?");
5313
+ values.push(input.status);
5314
+ }
5003
5315
  // This read model does not materialize canonical Task priority. Keep
5004
5316
  // the filtered view safe and avoid substituting a different field.
5005
5317
  if (input.priority) clauses.push("0");
5006
- if (input.risk) { clauses.push("t.risk = ?"); values.push(input.risk); }
5318
+ if (input.risk) {
5319
+ clauses.push("t.risk = ?");
5320
+ values.push(input.risk);
5321
+ }
5007
5322
  if (input.origin) {
5008
- const origin = input.origin === "prd" ? "i.prd_id IS NOT NULL" : input.origin === "spec" ? "i.prd_id IS NULL AND i.spec_id IS NOT NULL" : input.origin === "task" ? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL" : "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL";
5323
+ const origin =
5324
+ input.origin === "prd"
5325
+ ? "i.prd_id IS NOT NULL"
5326
+ : input.origin === "spec"
5327
+ ? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
5328
+ : input.origin === "task"
5329
+ ? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
5330
+ : "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL";
5009
5331
  clauses.push(origin);
5010
5332
  }
5011
5333
  // SQLite invocation facts do not contain canonical work_type. A work-type
@@ -5013,33 +5335,82 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5013
5335
  if (input.workType) clauses.push("0");
5014
5336
  const segment: string[] = [];
5015
5337
  if (input.provider) {
5016
- if (input.provider === "unavailable") clauses.push("NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')");
5017
- else { segment.push("f.provider = ?"); values.push(input.provider); }
5338
+ if (input.provider === "unavailable")
5339
+ clauses.push(
5340
+ "NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')",
5341
+ );
5342
+ else {
5343
+ segment.push("f.provider = ?");
5344
+ values.push(input.provider);
5345
+ }
5018
5346
  }
5019
5347
  if (input.model) {
5020
- if (input.model === "unavailable") clauses.push("NOT EXISTS (SELECT 1 FROM invocation_usage known_model WHERE known_model.agent_invocation_id=i.agent_invocation_id AND known_model.model IS NOT NULL AND known_model.model <> 'unavailable')");
5021
- else { segment.push("f.model = ?"); values.push(input.model); }
5348
+ if (input.model === "unavailable")
5349
+ clauses.push(
5350
+ "NOT EXISTS (SELECT 1 FROM invocation_usage known_model WHERE known_model.agent_invocation_id=i.agent_invocation_id AND known_model.model IS NOT NULL AND known_model.model <> 'unavailable')",
5351
+ );
5352
+ else {
5353
+ segment.push("f.model = ?");
5354
+ values.push(input.model);
5355
+ }
5022
5356
  }
5023
- if (segment.length > 0) clauses.push(`EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id=i.agent_invocation_id AND ${segment.join(" AND ")})`);
5357
+ if (segment.length > 0)
5358
+ clauses.push(
5359
+ `EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id=i.agent_invocation_id AND ${segment.join(" AND ")})`,
5360
+ );
5024
5361
  if (input.query) {
5025
5362
  const query = `%${input.query}%`;
5026
- clauses.push("(i.zaip_agent LIKE ? OR i.zaip_agent_persona LIKE ? OR i.zaip_agent_job LIKE ?)");
5363
+ clauses.push(
5364
+ "(i.zaip_agent LIKE ? OR i.zaip_agent_persona LIKE ? OR i.zaip_agent_job LIKE ?)",
5365
+ );
5027
5366
  values.push(query, query, query);
5028
5367
  }
5029
5368
  // This is deliberately the same financial fact population as the Cost
5030
5369
  // Monitor summary, grouped by the factual activity.zaip_agent. It is
5031
5370
  // not an invocation-only approximation.
5032
- const sourceDay = "date(COALESCE(a.source_started_at, a.source_occurred_at, a.occurred_at), '-3 hours')";
5033
- const activityClauses = ["a.activity_source_kind <> 'agent_run'", "a.zaip_agent IS NOT NULL", "(a.total_tokens IS NOT NULL OR a.cost_brl IS NOT NULL OR a.cost_usd IS NOT NULL)"];
5371
+ const sourceDay =
5372
+ "date(COALESCE(a.source_started_at, a.source_occurred_at, a.occurred_at), '-3 hours')";
5373
+ const activityClauses = [
5374
+ "a.activity_source_kind <> 'agent_run'",
5375
+ "a.zaip_agent IS NOT NULL",
5376
+ "(a.total_tokens IS NOT NULL OR a.cost_brl IS NOT NULL OR a.cost_usd IS NOT NULL)",
5377
+ ];
5034
5378
  const activityValues: Array<string | number> = [];
5035
- if (input.from) { activityClauses.push(`${sourceDay} >= ?`); activityValues.push(input.from.slice(0, 10)); }
5036
- if (input.to) { activityClauses.push(`${sourceDay} <= ?`); activityValues.push(input.to.slice(0, 10)); }
5037
- if (input.agent) { activityClauses.push("a.zaip_agent = ?"); activityValues.push(input.agent); }
5038
- if (input.phase) { activityClauses.push("a.phase = ?"); activityValues.push(input.phase); }
5039
- if (input.status) { activityClauses.push("a.status = ?"); activityValues.push(input.status); }
5379
+ if (input.from) {
5380
+ activityClauses.push(`${sourceDay} >= ?`);
5381
+ activityValues.push(input.from.slice(0, 10));
5382
+ }
5383
+ if (input.to) {
5384
+ activityClauses.push(`${sourceDay} <= ?`);
5385
+ activityValues.push(input.to.slice(0, 10));
5386
+ }
5387
+ if (input.agent) {
5388
+ activityClauses.push("a.zaip_agent = ?");
5389
+ activityValues.push(input.agent);
5390
+ }
5391
+ if (input.phase) {
5392
+ activityClauses.push("a.phase = ?");
5393
+ activityValues.push(input.phase);
5394
+ }
5395
+ if (input.status) {
5396
+ activityClauses.push("a.status = ?");
5397
+ activityValues.push(input.status);
5398
+ }
5040
5399
  if (input.priority || input.workType) activityClauses.push("0");
5041
- if (input.risk) { activityClauses.push("activity_task.risk = ?"); activityValues.push(input.risk); }
5042
- if (input.origin) activityClauses.push(input.origin === "prd" ? "a.prd_id IS NOT NULL" : input.origin === "spec" ? "a.prd_id IS NULL AND a.spec_id IS NOT NULL" : input.origin === "task" ? "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NOT NULL" : "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NULL");
5400
+ if (input.risk) {
5401
+ activityClauses.push("activity_task.risk = ?");
5402
+ activityValues.push(input.risk);
5403
+ }
5404
+ if (input.origin)
5405
+ activityClauses.push(
5406
+ input.origin === "prd"
5407
+ ? "a.prd_id IS NOT NULL"
5408
+ : input.origin === "spec"
5409
+ ? "a.prd_id IS NULL AND a.spec_id IS NOT NULL"
5410
+ : input.origin === "task"
5411
+ ? "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NOT NULL"
5412
+ : "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NULL",
5413
+ );
5043
5414
  if (input.provider || input.model) {
5044
5415
  const direct: string[] = [];
5045
5416
  const usage: string[] = [];
@@ -5053,30 +5424,83 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5053
5424
  usage.push("COALESCE(u.model, 'unavailable') = ?");
5054
5425
  activityValues.push(input.model);
5055
5426
  }
5056
- activityClauses.push(`((a.agent_run_id IS NULL AND ${direct.join(" AND ")}) OR (a.agent_run_id IS NOT NULL AND EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id = a.agent_run_id AND ${usage.join(" AND ")})))`);
5427
+ activityClauses.push(
5428
+ `((a.agent_run_id IS NULL AND ${direct.join(" AND ")}) OR (a.agent_run_id IS NOT NULL AND EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id = a.agent_run_id AND ${usage.join(" AND ")})))`,
5429
+ );
5057
5430
  if (input.provider) activityValues.push(input.provider);
5058
5431
  if (input.model) activityValues.push(input.model);
5059
5432
  }
5060
- if (input.query) { const query = `%${input.query}%`; activityClauses.push("(a.zaip_agent LIKE ? OR a.zaip_agent_persona LIKE ? OR a.zaip_agent_job LIKE ?)"); activityValues.push(query, query, query); }
5433
+ if (input.query) {
5434
+ const query = `%${input.query}%`;
5435
+ activityClauses.push(
5436
+ "(a.zaip_agent LIKE ? OR a.zaip_agent_persona LIKE ? OR a.zaip_agent_job LIKE ?)",
5437
+ );
5438
+ activityValues.push(query, query, query);
5439
+ }
5061
5440
  const where = clauses.join(" AND ");
5062
- const invocationClauses = ["i.zaip_agent IS NOT NULL", "(i.total_tokens IS NOT NULL OR i.cost_brl IS NOT NULL OR i.cost_usd IS NOT NULL)"];
5441
+ const invocationClauses = [
5442
+ "i.zaip_agent IS NOT NULL",
5443
+ "(i.total_tokens IS NOT NULL OR i.cost_brl IS NOT NULL OR i.cost_usd IS NOT NULL)",
5444
+ ];
5063
5445
  const invocationValues: Array<string | number> = [];
5064
- const invocationSourceDay = "date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours')";
5065
- if (input.from) { invocationClauses.push(`${invocationSourceDay} >= ?`); invocationValues.push(input.from.slice(0, 10)); }
5066
- if (input.to) { invocationClauses.push(`${invocationSourceDay} <= ?`); invocationValues.push(input.to.slice(0, 10)); }
5067
- if (input.agent) { invocationClauses.push("i.zaip_agent = ?"); invocationValues.push(input.agent); }
5068
- if (input.phase) { invocationClauses.push("i.phase = ?"); invocationValues.push(input.phase); }
5069
- if (input.status) { invocationClauses.push("i.status = ?"); invocationValues.push(input.status); }
5446
+ const invocationSourceDay =
5447
+ "date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours')";
5448
+ if (input.from) {
5449
+ invocationClauses.push(`${invocationSourceDay} >= ?`);
5450
+ invocationValues.push(input.from.slice(0, 10));
5451
+ }
5452
+ if (input.to) {
5453
+ invocationClauses.push(`${invocationSourceDay} <= ?`);
5454
+ invocationValues.push(input.to.slice(0, 10));
5455
+ }
5456
+ if (input.agent) {
5457
+ invocationClauses.push("i.zaip_agent = ?");
5458
+ invocationValues.push(input.agent);
5459
+ }
5460
+ if (input.phase) {
5461
+ invocationClauses.push("i.phase = ?");
5462
+ invocationValues.push(input.phase);
5463
+ }
5464
+ if (input.status) {
5465
+ invocationClauses.push("i.status = ?");
5466
+ invocationValues.push(input.status);
5467
+ }
5070
5468
  if (input.priority || input.workType) invocationClauses.push("0");
5071
- if (input.risk) { invocationClauses.push("invocation_task.risk = ?"); invocationValues.push(input.risk); }
5072
- if (input.origin) invocationClauses.push(input.origin === "prd" ? "i.prd_id IS NOT NULL" : input.origin === "spec" ? "i.prd_id IS NULL AND i.spec_id IS NOT NULL" : input.origin === "task" ? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL" : "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL");
5469
+ if (input.risk) {
5470
+ invocationClauses.push("invocation_task.risk = ?");
5471
+ invocationValues.push(input.risk);
5472
+ }
5473
+ if (input.origin)
5474
+ invocationClauses.push(
5475
+ input.origin === "prd"
5476
+ ? "i.prd_id IS NOT NULL"
5477
+ : input.origin === "spec"
5478
+ ? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
5479
+ : input.origin === "task"
5480
+ ? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
5481
+ : "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL",
5482
+ );
5073
5483
  if (input.provider || input.model) {
5074
5484
  const segment: string[] = [];
5075
- if (input.provider) { segment.push("COALESCE(u.provider, 'unavailable') = ?"); invocationValues.push(input.provider); }
5076
- if (input.model) { segment.push("COALESCE(u.model, 'unavailable') = ?"); invocationValues.push(input.model); }
5077
- invocationClauses.push(`EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=i.agent_invocation_id AND ${segment.join(" AND ")})`);
5485
+ if (input.provider) {
5486
+ segment.push("COALESCE(u.provider, 'unavailable') = ?");
5487
+ invocationValues.push(input.provider);
5488
+ }
5489
+ if (input.model) {
5490
+ segment.push("COALESCE(u.model, 'unavailable') = ?");
5491
+ invocationValues.push(input.model);
5492
+ }
5493
+ invocationClauses.push(
5494
+ `EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=i.agent_invocation_id AND ${segment.join(" AND ")})`,
5495
+ );
5496
+ }
5497
+ if (input.query) {
5498
+ const query = `%${input.query}%`;
5499
+ invocationClauses.push(
5500
+ "(i.zaip_agent LIKE ? OR i.zaip_agent_persona LIKE ? OR i.zaip_agent_job LIKE ?)",
5501
+ );
5502
+ invocationValues.push(query, query, query);
5078
5503
  }
5079
- if (input.query) { const query = `%${input.query}%`; invocationClauses.push("(i.zaip_agent LIKE ? OR i.zaip_agent_persona LIKE ? OR i.zaip_agent_job LIKE ?)"); invocationValues.push(query, query, query); }
5080
5504
  const groupedFacts = `WITH agent_facts AS (
5081
5505
  SELECT i.zaip_agent AS key, i.zaip_agent_persona AS persona, i.zaip_agent_job AS job, d.persona AS catalog_persona, d.job AS catalog_job,
5082
5506
  i.occurred_at, i.task_id, i.duration_ms, i.source_event_count, i.total_tokens, i.cost_usd, i.cost_brl
@@ -5090,57 +5514,164 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5090
5514
  )`;
5091
5515
  const limit = Math.min(200, Math.max(1, input.limit ?? 50));
5092
5516
  const offset = Math.max(0, input.offset ?? 0);
5093
- const total_count = Number((this.database.prepare(`${groupedFacts} SELECT COUNT(DISTINCT key) AS count FROM agent_facts`).get(...invocationValues, ...activityValues) as { count: number }).count);
5517
+ const total_count = Number(
5518
+ (
5519
+ this.database
5520
+ .prepare(`${groupedFacts} SELECT COUNT(DISTINCT key) AS count FROM agent_facts`)
5521
+ .get(...invocationValues, ...activityValues) as { count: number }
5522
+ ).count,
5523
+ );
5094
5524
  // Activity facts retain the catalog snapshot used when they were
5095
5525
  // materialized. The current dimension is a safe fallback for older
5096
5526
  // snapshots that predate those fields: it supplies only catalog display
5097
5527
  // metadata, never tokens, cost, duration, or attribution.
5098
- const totals = this.database.prepare(`${groupedFacts} SELECT COUNT(*) AS invocation_count, COUNT(DISTINCT task_id) AS task_count, SUM(duration_ms) AS total_duration, SUM(CASE WHEN duration_ms IS NULL THEN 1 ELSE 0 END) AS duration_unknown, SUM(CASE WHEN duration_ms IS NULL THEN 0 ELSE duration_ms END) AS duration_known, SUM(source_event_count) AS source_event_count, SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS total_cost_usd, SUM(cost_brl) AS total_cost_brl FROM agent_facts`).get(...invocationValues, ...activityValues) as Record<string, unknown>;
5099
- const rows = this.database.prepare(`${groupedFacts} SELECT key, key AS label, COALESCE(MAX(persona), MAX(catalog_persona)) AS persona, COALESCE(MAX(job), MAX(catalog_job)) AS job, MAX(occurred_at) AS latest_started_at, COUNT(*) AS invocation_count, COUNT(DISTINCT task_id) AS task_count, SUM(duration_ms) AS total_duration, SUM(CASE WHEN duration_ms IS NULL THEN 1 ELSE 0 END) AS duration_unknown, SUM(CASE WHEN duration_ms IS NULL THEN 0 ELSE duration_ms END) AS duration_known, SUM(source_event_count) AS source_event_count, SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS total_cost_usd, SUM(cost_brl) AS total_cost_brl FROM agent_facts GROUP BY key ORDER BY total_cost_brl DESC, key LIMIT ? OFFSET ?`).all(...invocationValues, ...activityValues, limit, offset) as Array<Record<string, unknown>>;
5100
- return { data_source: "sqlite_read_model" as const, view, offset, limit, total_count, has_more: offset + rows.length < total_count, totals, rows };
5528
+ const totals = this.database
5529
+ .prepare(
5530
+ `${groupedFacts} SELECT COUNT(*) AS invocation_count, COUNT(DISTINCT task_id) AS task_count, SUM(duration_ms) AS total_duration, SUM(CASE WHEN duration_ms IS NULL THEN 1 ELSE 0 END) AS duration_unknown, SUM(CASE WHEN duration_ms IS NULL THEN 0 ELSE duration_ms END) AS duration_known, SUM(source_event_count) AS source_event_count, SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS total_cost_usd, SUM(cost_brl) AS total_cost_brl FROM agent_facts`,
5531
+ )
5532
+ .get(...invocationValues, ...activityValues) as Record<string, unknown>;
5533
+ const rows = this.database
5534
+ .prepare(
5535
+ `${groupedFacts} SELECT key, key AS label, COALESCE(MAX(persona), MAX(catalog_persona)) AS persona, COALESCE(MAX(job), MAX(catalog_job)) AS job, MAX(occurred_at) AS latest_started_at, COUNT(*) AS invocation_count, COUNT(DISTINCT task_id) AS task_count, SUM(duration_ms) AS total_duration, SUM(CASE WHEN duration_ms IS NULL THEN 1 ELSE 0 END) AS duration_unknown, SUM(CASE WHEN duration_ms IS NULL THEN 0 ELSE duration_ms END) AS duration_known, SUM(source_event_count) AS source_event_count, SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS total_cost_usd, SUM(cost_brl) AS total_cost_brl FROM agent_facts GROUP BY key ORDER BY total_cost_brl DESC, key LIMIT ? OFFSET ?`,
5536
+ )
5537
+ .all(...invocationValues, ...activityValues, limit, offset) as Array<
5538
+ Record<string, unknown>
5539
+ >;
5540
+ return {
5541
+ data_source: "sqlite_read_model" as const,
5542
+ view,
5543
+ offset,
5544
+ limit,
5545
+ total_count,
5546
+ has_more: offset + rows.length < total_count,
5547
+ totals,
5548
+ rows,
5549
+ };
5101
5550
  }
5102
5551
  if (filteredCostGroup === "model") {
5103
5552
  const clauses = ["1=1"];
5104
5553
  const values: Array<string | number> = [];
5105
- if (input.from) { clauses.push("date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?"); values.push(input.from.slice(0, 10)); }
5106
- if (input.to) { clauses.push("date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?"); values.push(input.to.slice(0, 10)); }
5107
- if (input.agent) { clauses.push("i.zaip_agent = ?"); values.push(input.agent); }
5108
- if (input.phase) { clauses.push("i.phase = ?"); values.push(input.phase); }
5109
- if (input.status) { clauses.push("i.status = ?"); values.push(input.status); }
5554
+ if (input.from) {
5555
+ clauses.push(
5556
+ "date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?",
5557
+ );
5558
+ values.push(input.from.slice(0, 10));
5559
+ }
5560
+ if (input.to) {
5561
+ clauses.push(
5562
+ "date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?",
5563
+ );
5564
+ values.push(input.to.slice(0, 10));
5565
+ }
5566
+ if (input.agent) {
5567
+ clauses.push("i.zaip_agent = ?");
5568
+ values.push(input.agent);
5569
+ }
5570
+ if (input.phase) {
5571
+ clauses.push("i.phase = ?");
5572
+ values.push(input.phase);
5573
+ }
5574
+ if (input.status) {
5575
+ clauses.push("i.status = ?");
5576
+ values.push(input.status);
5577
+ }
5110
5578
  if (input.priority || input.workType) clauses.push("0");
5111
- if (input.risk) { clauses.push("t.risk = ?"); values.push(input.risk); }
5579
+ if (input.risk) {
5580
+ clauses.push("t.risk = ?");
5581
+ values.push(input.risk);
5582
+ }
5112
5583
  if (input.origin) {
5113
- clauses.push(input.origin === "prd" ? "i.prd_id IS NOT NULL" : input.origin === "spec" ? "i.prd_id IS NULL AND i.spec_id IS NOT NULL" : input.origin === "task" ? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL" : "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL");
5584
+ clauses.push(
5585
+ input.origin === "prd"
5586
+ ? "i.prd_id IS NOT NULL"
5587
+ : input.origin === "spec"
5588
+ ? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
5589
+ : input.origin === "task"
5590
+ ? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
5591
+ : "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL",
5592
+ );
5114
5593
  }
5115
5594
  if (input.provider) {
5116
- if (input.provider === "unavailable") clauses.push("s.provider IS NULL AND NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')");
5117
- else { clauses.push("s.provider = ?"); values.push(input.provider); }
5595
+ if (input.provider === "unavailable")
5596
+ clauses.push(
5597
+ "s.provider IS NULL AND NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')",
5598
+ );
5599
+ else {
5600
+ clauses.push("s.provider = ?");
5601
+ values.push(input.provider);
5602
+ }
5118
5603
  }
5119
5604
  if (input.model) {
5120
5605
  if (input.model === "unavailable") clauses.push("s.model_key = 'unavailable'");
5121
- else { clauses.push("s.model_key = ?"); values.push(input.model); }
5606
+ else {
5607
+ clauses.push("s.model_key = ?");
5608
+ values.push(input.model);
5609
+ }
5610
+ }
5611
+ if (input.query) {
5612
+ clauses.push("s.model_key LIKE ?");
5613
+ values.push(`%${input.query}%`);
5122
5614
  }
5123
- if (input.query) { clauses.push("s.model_key LIKE ?"); values.push(`%${input.query}%`); }
5124
5615
  const where = clauses.join(" AND ");
5125
5616
  const activityClauses = ["a.activity_source_kind <> 'agent_run'"];
5126
5617
  const activityValues: Array<string | number> = [];
5127
- if (input.from) { activityClauses.push("a.occurred_day_sp >= ?"); activityValues.push(input.from.slice(0, 10)); }
5128
- if (input.to) { activityClauses.push("a.occurred_day_sp <= ?"); activityValues.push(input.to.slice(0, 10)); }
5129
- if (input.agent) { activityClauses.push("a.zaip_agent = ?"); activityValues.push(input.agent); }
5130
- if (input.phase) { activityClauses.push("a.phase = ?"); activityValues.push(input.phase); }
5131
- if (input.status) { activityClauses.push("a.status = ?"); activityValues.push(input.status); }
5618
+ if (input.from) {
5619
+ activityClauses.push("a.occurred_day_sp >= ?");
5620
+ activityValues.push(input.from.slice(0, 10));
5621
+ }
5622
+ if (input.to) {
5623
+ activityClauses.push("a.occurred_day_sp <= ?");
5624
+ activityValues.push(input.to.slice(0, 10));
5625
+ }
5626
+ if (input.agent) {
5627
+ activityClauses.push("a.zaip_agent = ?");
5628
+ activityValues.push(input.agent);
5629
+ }
5630
+ if (input.phase) {
5631
+ activityClauses.push("a.phase = ?");
5632
+ activityValues.push(input.phase);
5633
+ }
5634
+ if (input.status) {
5635
+ activityClauses.push("a.status = ?");
5636
+ activityValues.push(input.status);
5637
+ }
5132
5638
  if (input.priority || input.workType) activityClauses.push("0");
5133
- if (input.risk) { activityClauses.push("activity_task.risk = ?"); activityValues.push(input.risk); }
5134
- if (input.origin) activityClauses.push(input.origin === "prd" ? "a.prd_id IS NOT NULL" : input.origin === "spec" ? "a.prd_id IS NULL AND a.spec_id IS NOT NULL" : input.origin === "task" ? "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NOT NULL" : "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NULL");
5639
+ if (input.risk) {
5640
+ activityClauses.push("activity_task.risk = ?");
5641
+ activityValues.push(input.risk);
5642
+ }
5643
+ if (input.origin)
5644
+ activityClauses.push(
5645
+ input.origin === "prd"
5646
+ ? "a.prd_id IS NOT NULL"
5647
+ : input.origin === "spec"
5648
+ ? "a.prd_id IS NULL AND a.spec_id IS NOT NULL"
5649
+ : input.origin === "task"
5650
+ ? "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NOT NULL"
5651
+ : "a.prd_id IS NULL AND a.spec_id IS NULL AND a.task_id IS NULL",
5652
+ );
5135
5653
  if (input.provider) {
5136
- if (input.provider === "unavailable") activityClauses.push("a.provider IS NULL OR trim(a.provider) = '' OR a.provider = 'unavailable'");
5137
- else { activityClauses.push("a.provider = ?"); activityValues.push(input.provider); }
5654
+ if (input.provider === "unavailable")
5655
+ activityClauses.push(
5656
+ "a.provider IS NULL OR trim(a.provider) = '' OR a.provider = 'unavailable'",
5657
+ );
5658
+ else {
5659
+ activityClauses.push("a.provider = ?");
5660
+ activityValues.push(input.provider);
5661
+ }
5138
5662
  }
5139
5663
  if (input.model) {
5140
- if (input.model === "unavailable") activityClauses.push("a.model IS NULL OR trim(a.model) = '' OR a.model = 'unavailable'");
5141
- else { activityClauses.push("a.model = ?"); activityValues.push(input.model); }
5664
+ if (input.model === "unavailable")
5665
+ activityClauses.push("a.model IS NULL OR trim(a.model) = '' OR a.model = 'unavailable'");
5666
+ else {
5667
+ activityClauses.push("a.model = ?");
5668
+ activityValues.push(input.model);
5669
+ }
5670
+ }
5671
+ if (input.query) {
5672
+ activityClauses.push("COALESCE(a.model, 'unavailable') LIKE ?");
5673
+ activityValues.push(`%${input.query}%`);
5142
5674
  }
5143
- if (input.query) { activityClauses.push("COALESCE(a.model, 'unavailable') LIKE ?"); activityValues.push(`%${input.query}%`); }
5144
5675
  const ctes = `WITH segment_totals AS (
5145
5676
  SELECT agent_invocation_id, SUM(input_tokens) AS input_tokens, SUM(cached_input_tokens) AS cached_input_tokens,
5146
5677
  SUM(output_tokens) AS output_tokens, SUM(reasoning_tokens) AS reasoning_tokens,
@@ -5223,7 +5754,13 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5223
5754
  )`;
5224
5755
  const limit = Math.min(200, Math.max(1, input.limit ?? 50));
5225
5756
  const offset = Math.max(0, input.offset ?? 0);
5226
- const total_count = Number((this.database.prepare(`${ctes} ${facts} SELECT COUNT(*) AS count FROM keys`).get(...values, ...activityValues) as { count: number }).count);
5757
+ const total_count = Number(
5758
+ (
5759
+ this.database
5760
+ .prepare(`${ctes} ${facts} SELECT COUNT(*) AS count FROM keys`)
5761
+ .get(...values, ...activityValues) as { count: number }
5762
+ ).count,
5763
+ );
5227
5764
  // Dimension membership is intentionally non-additive: one Task can use
5228
5765
  // several concrete models. Expose the distinct population separately so
5229
5766
  // a footer never reports the inflated sum of row memberships as total.
@@ -5244,23 +5781,82 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5244
5781
  SUM(cost_brl) AS total_cost_brl
5245
5782
  FROM filtered_segments`)
5246
5783
  .get(...values, ...activityValues) as Record<string, unknown>;
5247
- const rows = this.database.prepare(`${ctes} ${facts} SELECT keys.key, keys.key AS label, COALESCE(population.invocation_count, 0) AS invocation_count, COALESCE(task_population.task_count, 0) AS task_count, population.total_duration, population.source_event_count, financial.total_tokens, financial.total_cost_usd, financial.total_cost_brl FROM keys LEFT JOIN population USING(key) LEFT JOIN task_population USING(key) LEFT JOIN financial USING(key) ORDER BY financial.total_cost_brl DESC, keys.key LIMIT ? OFFSET ?`).all(...values, ...activityValues, limit, offset) as Array<Record<string, unknown>>;
5248
- return { data_source: "sqlite_read_model" as const, view, offset, limit, total_count, has_more: offset + rows.length < total_count, totals, rows };
5784
+ const rows = this.database
5785
+ .prepare(
5786
+ `${ctes} ${facts} SELECT keys.key, keys.key AS label, COALESCE(population.invocation_count, 0) AS invocation_count, COALESCE(task_population.task_count, 0) AS task_count, population.total_duration, population.source_event_count, financial.total_tokens, financial.total_cost_usd, financial.total_cost_brl FROM keys LEFT JOIN population USING(key) LEFT JOIN task_population USING(key) LEFT JOIN financial USING(key) ORDER BY financial.total_cost_brl DESC, keys.key LIMIT ? OFFSET ?`,
5787
+ )
5788
+ .all(...values, ...activityValues, limit, offset) as Array<Record<string, unknown>>;
5789
+ return {
5790
+ data_source: "sqlite_read_model" as const,
5791
+ view,
5792
+ offset,
5793
+ limit,
5794
+ total_count,
5795
+ has_more: offset + rows.length < total_count,
5796
+ totals,
5797
+ rows,
5798
+ };
5249
5799
  }
5250
5800
  if (filteredCostGroup === "provider") {
5251
5801
  const clauses = ["1=1"];
5252
5802
  const values: Array<string | number> = [];
5253
- if (input.from) { clauses.push("date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?"); values.push(input.from.slice(0, 10)); }
5254
- if (input.to) { clauses.push("date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?"); values.push(input.to.slice(0, 10)); }
5255
- if (input.agent) { clauses.push("i.zaip_agent = ?"); values.push(input.agent); }
5256
- if (input.phase) { clauses.push("i.phase = ?"); values.push(input.phase); }
5257
- if (input.status) { clauses.push("i.status = ?"); values.push(input.status); }
5803
+ if (input.from) {
5804
+ clauses.push(
5805
+ "date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?",
5806
+ );
5807
+ values.push(input.from.slice(0, 10));
5808
+ }
5809
+ if (input.to) {
5810
+ clauses.push(
5811
+ "date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?",
5812
+ );
5813
+ values.push(input.to.slice(0, 10));
5814
+ }
5815
+ if (input.agent) {
5816
+ clauses.push("i.zaip_agent = ?");
5817
+ values.push(input.agent);
5818
+ }
5819
+ if (input.phase) {
5820
+ clauses.push("i.phase = ?");
5821
+ values.push(input.phase);
5822
+ }
5823
+ if (input.status) {
5824
+ clauses.push("i.status = ?");
5825
+ values.push(input.status);
5826
+ }
5258
5827
  if (input.priority || input.workType) clauses.push("0");
5259
- if (input.risk) { clauses.push("t.risk = ?"); values.push(input.risk); }
5260
- if (input.origin) clauses.push(input.origin === "prd" ? "i.prd_id IS NOT NULL" : input.origin === "spec" ? "i.prd_id IS NULL AND i.spec_id IS NOT NULL" : input.origin === "task" ? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL" : "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL");
5261
- if (input.provider) { if (input.provider === "unavailable") clauses.push("s.provider_key = 'unavailable'"); else { clauses.push("s.provider_key = ?"); values.push(input.provider); } }
5262
- if (input.model) { if (input.model === "unavailable") clauses.push("s.model_key = 'unavailable'"); else { clauses.push("s.model_key = ?"); values.push(input.model); } }
5263
- if (input.query) { clauses.push("s.provider_key LIKE ?"); values.push(`%${input.query}%`); }
5828
+ if (input.risk) {
5829
+ clauses.push("t.risk = ?");
5830
+ values.push(input.risk);
5831
+ }
5832
+ if (input.origin)
5833
+ clauses.push(
5834
+ input.origin === "prd"
5835
+ ? "i.prd_id IS NOT NULL"
5836
+ : input.origin === "spec"
5837
+ ? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
5838
+ : input.origin === "task"
5839
+ ? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
5840
+ : "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL",
5841
+ );
5842
+ if (input.provider) {
5843
+ if (input.provider === "unavailable") clauses.push("s.provider_key = 'unavailable'");
5844
+ else {
5845
+ clauses.push("s.provider_key = ?");
5846
+ values.push(input.provider);
5847
+ }
5848
+ }
5849
+ if (input.model) {
5850
+ if (input.model === "unavailable") clauses.push("s.model_key = 'unavailable'");
5851
+ else {
5852
+ clauses.push("s.model_key = ?");
5853
+ values.push(input.model);
5854
+ }
5855
+ }
5856
+ if (input.query) {
5857
+ clauses.push("s.provider_key LIKE ?");
5858
+ values.push(`%${input.query}%`);
5859
+ }
5264
5860
  const where = clauses.join(" AND ");
5265
5861
  const ctes = `WITH segment_totals AS (
5266
5862
  SELECT agent_invocation_id, SUM(input_tokens) AS input_tokens, SUM(cached_input_tokens) AS cached_input_tokens,
@@ -5319,8 +5915,15 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5319
5915
  SELECT provider_key AS key, COUNT(*) AS task_count
5320
5916
  FROM task_providers GROUP BY provider_key
5321
5917
  ), keys AS (SELECT key FROM financial UNION SELECT key FROM population UNION SELECT key FROM task_population)`;
5322
- const limit = Math.min(200, Math.max(1, input.limit ?? 50)); const offset = Math.max(0, input.offset ?? 0);
5323
- const total_count = Number((this.database.prepare(`${ctes} ${facts} SELECT COUNT(*) AS count FROM keys`).get(...values) as { count: number }).count);
5918
+ const limit = Math.min(200, Math.max(1, input.limit ?? 50));
5919
+ const offset = Math.max(0, input.offset ?? 0);
5920
+ const total_count = Number(
5921
+ (
5922
+ this.database
5923
+ .prepare(`${ctes} ${facts} SELECT COUNT(*) AS count FROM keys`)
5924
+ .get(...values) as { count: number }
5925
+ ).count,
5926
+ );
5324
5927
  const totals = this.database
5325
5928
  .prepare(`${ctes} ${facts} SELECT
5326
5929
  (SELECT COUNT(*) FROM population_ranked WHERE provider_rank = 1) AS invocation_count,
@@ -5338,13 +5941,34 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5338
5941
  SUM(cost_brl) AS total_cost_brl
5339
5942
  FROM filtered_segments`)
5340
5943
  .get(...values) as Record<string, unknown>;
5341
- const rows = this.database.prepare(`${ctes} ${facts} SELECT keys.key, keys.key AS label, COALESCE(population.invocation_count,0) AS invocation_count, COALESCE(task_population.task_count,0) AS task_count, population.total_duration, population.source_event_count, financial.total_tokens, financial.total_cost_usd, financial.total_cost_brl FROM keys LEFT JOIN population USING(key) LEFT JOIN task_population USING(key) LEFT JOIN financial USING(key) ORDER BY financial.total_cost_brl DESC, keys.key LIMIT ? OFFSET ?`).all(...values, limit, offset) as Array<Record<string, unknown>>;
5342
- return { data_source: "sqlite_read_model" as const, view, offset, limit, total_count, has_more: offset + rows.length < total_count, totals, rows };
5944
+ const rows = this.database
5945
+ .prepare(
5946
+ `${ctes} ${facts} SELECT keys.key, keys.key AS label, COALESCE(population.invocation_count,0) AS invocation_count, COALESCE(task_population.task_count,0) AS task_count, population.total_duration, population.source_event_count, financial.total_tokens, financial.total_cost_usd, financial.total_cost_brl FROM keys LEFT JOIN population USING(key) LEFT JOIN task_population USING(key) LEFT JOIN financial USING(key) ORDER BY financial.total_cost_brl DESC, keys.key LIMIT ? OFFSET ?`,
5947
+ )
5948
+ .all(...values, limit, offset) as Array<Record<string, unknown>>;
5949
+ return {
5950
+ data_source: "sqlite_read_model" as const,
5951
+ view,
5952
+ offset,
5953
+ limit,
5954
+ total_count,
5955
+ has_more: offset + rows.length < total_count,
5956
+ totals,
5957
+ rows,
5958
+ };
5343
5959
  }
5344
5960
  const temporalCostGroup =
5345
5961
  filteredCostGroup === "day" || filteredCostGroup === "month" || filteredCostGroup === "year";
5346
5962
  const hasDimensionalCostFilter = Boolean(
5347
- input.provider || input.model || input.agent || input.phase || input.status || input.origin || input.priority || input.risk || input.workType,
5963
+ input.provider ||
5964
+ input.model ||
5965
+ input.agent ||
5966
+ input.phase ||
5967
+ input.status ||
5968
+ input.origin ||
5969
+ input.priority ||
5970
+ input.risk ||
5971
+ input.workType,
5348
5972
  );
5349
5973
  if (filteredCostGroup !== undefined && temporalCostGroup && !hasDimensionalCostFilter) {
5350
5974
  const from = costMonitorFilterDay(input.from);
@@ -5400,12 +6024,14 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5400
6024
  for (const column of additive) {
5401
6025
  const value = financial?.[column];
5402
6026
  if (typeof value === "number")
5403
- target[column] = (typeof target[column] === "number" ? Number(target[column]) : 0) + value;
6027
+ target[column] =
6028
+ (typeof target[column] === "number" ? Number(target[column]) : 0) + value;
5404
6029
  }
5405
6030
  const duration = financial?.total_duration;
5406
6031
  if (typeof duration === "number") {
5407
6032
  target.total_duration =
5408
- (typeof target.total_duration === "number" ? Number(target.total_duration) : 0) + duration;
6033
+ (typeof target.total_duration === "number" ? Number(target.total_duration) : 0) +
6034
+ duration;
5409
6035
  }
5410
6036
  if (typeof financial?.duration_unknown === "number" && financial.duration_unknown > 0)
5411
6037
  target.duration_incomplete = true;
@@ -5440,7 +6066,20 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5440
6066
  .filter((row) => query === null || String(row.key).toLocaleLowerCase().includes(query))
5441
6067
  .sort((left, right) => String(right.key).localeCompare(String(left.key)));
5442
6068
  const totals = allRows.reduce<Record<string, number | null>>((result, row) => {
5443
- for (const key of ["prd_count", "spec_count", "task_count", "total_run_count", "validation_count", "evidence_count", "total_duration", "duration_known", "duration_unknown", "total_tokens", "total_cost_usd", "total_cost_brl"] as const) {
6069
+ for (const key of [
6070
+ "prd_count",
6071
+ "spec_count",
6072
+ "task_count",
6073
+ "total_run_count",
6074
+ "validation_count",
6075
+ "evidence_count",
6076
+ "total_duration",
6077
+ "duration_known",
6078
+ "duration_unknown",
6079
+ "total_tokens",
6080
+ "total_cost_usd",
6081
+ "total_cost_brl",
6082
+ ] as const) {
5444
6083
  const value = row[key];
5445
6084
  if (typeof value === "number") result[key] = (result[key] ?? 0) + value;
5446
6085
  else if (!(key in result)) result[key] = null;
@@ -5465,7 +6104,15 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5465
6104
  if (temporalCostGroup && input.workType) {
5466
6105
  // No canonical work_type exists at invocation grain, including for
5467
6106
  // duration/Run completion backfills. Do not emit unrelated buckets.
5468
- return { data_source: "sqlite_read_model" as const, view, offset: Math.max(0, input.offset ?? 0), limit: Math.min(200, Math.max(1, input.limit ?? 50)), total_count: 0, has_more: false, rows: [] };
6107
+ return {
6108
+ data_source: "sqlite_read_model" as const,
6109
+ view,
6110
+ offset: Math.max(0, input.offset ?? 0),
6111
+ limit: Math.min(200, Math.max(1, input.limit ?? 50)),
6112
+ total_count: 0,
6113
+ has_more: false,
6114
+ rows: [],
6115
+ };
5469
6116
  }
5470
6117
  const groupExpression =
5471
6118
  filteredCostGroup === "day"
@@ -5481,17 +6128,23 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5481
6128
  ];
5482
6129
  const values: Array<string | number> = [];
5483
6130
  if (input.from) {
5484
- clauses.push("date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?");
6131
+ clauses.push(
6132
+ "date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') >= ?",
6133
+ );
5485
6134
  values.push(costMonitorFilterDay(input.from)!);
5486
6135
  }
5487
6136
  if (input.to) {
5488
- clauses.push("date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?");
6137
+ clauses.push(
6138
+ "date(COALESCE(i.source_started_at, i.source_occurred_at, i.occurred_at), '-3 hours') <= ?",
6139
+ );
5489
6140
  values.push(costMonitorFilterDay(input.to)!);
5490
6141
  }
5491
6142
  const usageSegmentClauses: string[] = [];
5492
6143
  if (input.provider) {
5493
6144
  if (input.provider === "unavailable") {
5494
- clauses.push("NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')");
6145
+ clauses.push(
6146
+ "NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')",
6147
+ );
5495
6148
  } else {
5496
6149
  usageSegmentClauses.push("f.provider=?");
5497
6150
  values.push(input.provider);
@@ -5499,14 +6152,18 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5499
6152
  }
5500
6153
  if (input.model) {
5501
6154
  if (input.model === "unavailable") {
5502
- clauses.push("NOT EXISTS (SELECT 1 FROM invocation_usage known_model WHERE known_model.agent_invocation_id=i.agent_invocation_id AND known_model.model IS NOT NULL AND known_model.model <> 'unavailable')");
6155
+ clauses.push(
6156
+ "NOT EXISTS (SELECT 1 FROM invocation_usage known_model WHERE known_model.agent_invocation_id=i.agent_invocation_id AND known_model.model IS NOT NULL AND known_model.model <> 'unavailable')",
6157
+ );
5503
6158
  } else {
5504
6159
  usageSegmentClauses.push("f.model=?");
5505
6160
  values.push(input.model);
5506
6161
  }
5507
6162
  }
5508
6163
  if (usageSegmentClauses.length > 0)
5509
- clauses.push(`EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id=i.agent_invocation_id AND ${usageSegmentClauses.join(" AND ")})`);
6164
+ clauses.push(
6165
+ `EXISTS (SELECT 1 FROM invocation_usage f WHERE f.agent_invocation_id=i.agent_invocation_id AND ${usageSegmentClauses.join(" AND ")})`,
6166
+ );
5510
6167
  if (input.agent) {
5511
6168
  clauses.push("i.zaip_agent = ?");
5512
6169
  values.push(input.agent);
@@ -5602,19 +6259,43 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5602
6259
  const factualDuration = this.costMonitorFactualDurationByDay(canonicalFilter);
5603
6260
  // Lifecycle counts are canonical facts keyed by their own occurrence
5604
6261
  // date. They deliberately do not inherit financial dimensions.
5605
- const hasUnsupportedLifecycleAttribution = Boolean(input.origin || input.priority || input.risk || input.workType);
6262
+ const hasUnsupportedLifecycleAttribution = Boolean(
6263
+ input.origin || input.priority || input.risk || input.workType,
6264
+ );
5606
6265
  const lifecycleRows = hasUnsupportedLifecycleAttribution
5607
6266
  ? []
5608
- : this.database
6267
+ : (this.database
5609
6268
  .prepare(`SELECT day, prd_count, spec_count, task_count, validation_count, evidence_count
5610
6269
  FROM daily_aggregate_cache WHERE (? IS NULL OR day >= ?) AND (? IS NULL OR day <= ?)`)
5611
- .all(costMonitorFilterDay(input.from), costMonitorFilterDay(input.from), costMonitorFilterDay(input.to), costMonitorFilterDay(input.to)) as Array<Record<string, unknown>>;
6270
+ .all(
6271
+ costMonitorFilterDay(input.from),
6272
+ costMonitorFilterDay(input.from),
6273
+ costMonitorFilterDay(input.to),
6274
+ costMonitorFilterDay(input.to),
6275
+ ) as Array<Record<string, unknown>>);
5612
6276
  const lifecycleByKey = new Map<string, Record<string, number>>();
5613
6277
  for (const row of lifecycleRows) {
5614
6278
  const day = String(row.day);
5615
- const key = filteredCostGroup === "day" ? day : filteredCostGroup === "month" ? day.slice(0, 7) : day.slice(0, 4);
5616
- const target = lifecycleByKey.get(key) ?? { prd_count: 0, spec_count: 0, task_count: 0, validation_count: 0, evidence_count: 0 };
5617
- for (const column of ["prd_count", "spec_count", "task_count", "validation_count", "evidence_count"] as const)
6279
+ const key =
6280
+ filteredCostGroup === "day"
6281
+ ? day
6282
+ : filteredCostGroup === "month"
6283
+ ? day.slice(0, 7)
6284
+ : day.slice(0, 4);
6285
+ const target = lifecycleByKey.get(key) ?? {
6286
+ prd_count: 0,
6287
+ spec_count: 0,
6288
+ task_count: 0,
6289
+ validation_count: 0,
6290
+ evidence_count: 0,
6291
+ };
6292
+ for (const column of [
6293
+ "prd_count",
6294
+ "spec_count",
6295
+ "task_count",
6296
+ "validation_count",
6297
+ "evidence_count",
6298
+ ] as const)
5618
6299
  target[column] = (target[column] ?? 0) + Number(row[column] ?? 0);
5619
6300
  lifecycleByKey.set(key, target);
5620
6301
  }
@@ -5622,7 +6303,12 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5622
6303
  const canonicalKeys = new Set(
5623
6304
  (input.query
5624
6305
  ? []
5625
- : [...new Set([...factualDuration.keys(), ...lifecycleRows.map((row) => String(row.day))])]
6306
+ : [
6307
+ ...new Set([
6308
+ ...factualDuration.keys(),
6309
+ ...lifecycleRows.map((row) => String(row.day)),
6310
+ ]),
6311
+ ]
5626
6312
  ).map((day) =>
5627
6313
  filteredCostGroup === "day"
5628
6314
  ? day
@@ -5653,12 +6339,26 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5653
6339
  const durationMatches = [...factualDuration].filter(([day]) => dayMatches(day));
5654
6340
  return {
5655
6341
  ...financial,
5656
- ...(lifecycleByKey.get(key) ?? (hasUnsupportedLifecycleAttribution
5657
- ? { prd_count: null, spec_count: null, task_count: null, validation_count: null, evidence_count: null }
5658
- : { prd_count: 0, spec_count: 0, task_count: 0, validation_count: 0, evidence_count: 0 })),
6342
+ ...(lifecycleByKey.get(key) ??
6343
+ (hasUnsupportedLifecycleAttribution
6344
+ ? {
6345
+ prd_count: null,
6346
+ spec_count: null,
6347
+ task_count: null,
6348
+ validation_count: null,
6349
+ evidence_count: null,
6350
+ }
6351
+ : {
6352
+ prd_count: 0,
6353
+ spec_count: 0,
6354
+ task_count: 0,
6355
+ validation_count: 0,
6356
+ evidence_count: 0,
6357
+ })),
5659
6358
  total_run_count: Number(financial.invocation_count ?? 0),
5660
6359
  total_duration:
5661
- durationMatches.length === 0 || durationMatches.some(([, duration]) => duration.eligible_runs !== duration.known_runs)
6360
+ durationMatches.length === 0 ||
6361
+ durationMatches.some(([, duration]) => duration.eligible_runs !== duration.known_runs)
5662
6362
  ? null
5663
6363
  : durationMatches.reduce((sum, [, duration]) => sum + duration.known_sum, 0),
5664
6364
  };
@@ -5701,8 +6401,13 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5701
6401
  // or belongs to another period. Financial measures stay nullable.
5702
6402
  const isCanonicalMacro = groupKind === "spec" || groupKind === "prd";
5703
6403
  const hasInvocationOnlyFilter = Boolean(
5704
- input.provider || input.model || input.agent || input.phase ||
5705
- input.origin || input.workType || input.priority,
6404
+ input.provider ||
6405
+ input.model ||
6406
+ input.agent ||
6407
+ input.phase ||
6408
+ input.origin ||
6409
+ input.workType ||
6410
+ input.priority,
5706
6411
  );
5707
6412
  if (isCanonicalMacro && !hasInvocationOnlyFilter) {
5708
6413
  const entityTable = groupKind === "spec" ? "specs" : "prds";
@@ -5736,7 +6441,8 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5736
6441
  entityValues.push(`%${input.query}%`, `%${input.query}%`);
5737
6442
  }
5738
6443
  const entityWhere = entityClauses.length === 0 ? "1" : entityClauses.join(" AND ");
5739
- const invocationJoin = invocationClauses.length === 0 ? "" : ` AND ${invocationClauses.join(" AND ")}`;
6444
+ const invocationJoin =
6445
+ invocationClauses.length === 0 ? "" : ` AND ${invocationClauses.join(" AND ")}`;
5740
6446
  const ctes = `WITH grouped AS (
5741
6447
  SELECT entity.id AS group_value, entity.title, entity.status, entity.risk,
5742
6448
  COALESCE(MIN(i.occurred_day_sp), entity.occurred_day_sp) AS first_day,
@@ -5758,42 +6464,135 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5758
6464
  const limit = Math.min(200, Math.max(1, input.limit ?? 50));
5759
6465
  const offset = Math.max(0, input.offset ?? 0);
5760
6466
  const values = [...invocationValues, ...entityValues];
5761
- const total_count = Number((this.database.prepare(`${ctes} SELECT COUNT(*) AS count FROM grouped`).get(...values) as { count: number }).count);
5762
- const totals = this.database.prepare(`${ctes} SELECT COUNT(*) AS entity_count, COALESCE(SUM(invocation_count), 0) AS invocation_count, ROUND(SUM(total_duration)) AS total_duration, SUM(total_tokens) AS total_tokens, SUM(total_cost_usd) AS total_cost_usd, SUM(total_cost_brl) AS total_cost_brl FROM grouped`).get(...values) as Record<string, unknown>;
5763
- const rows = this.database.prepare(`${ctes} SELECT group_value AS id, group_value AS key, title, status, risk, first_day, last_day, occurred_at, invocation_count, llm_call_count, total_duration, total_input_tokens, total_cached_input_tokens, total_output_tokens, total_reasoning_tokens, total_tokens, total_cost_usd, total_cost_brl FROM grouped ORDER BY total_cost_brl DESC, group_value LIMIT ? OFFSET ?`).all(...values, limit, offset) as Array<Record<string, unknown>>;
5764
- return { data_source: "sqlite_read_model" as const, view, offset, limit, total_count, has_more: offset + rows.length < total_count, totals, rows };
6467
+ const total_count = Number(
6468
+ (
6469
+ this.database
6470
+ .prepare(`${ctes} SELECT COUNT(*) AS count FROM grouped`)
6471
+ .get(...values) as { count: number }
6472
+ ).count,
6473
+ );
6474
+ const totals = this.database
6475
+ .prepare(
6476
+ `${ctes} SELECT COUNT(*) AS entity_count, COALESCE(SUM(invocation_count), 0) AS invocation_count, ROUND(SUM(total_duration)) AS total_duration, SUM(total_tokens) AS total_tokens, SUM(total_cost_usd) AS total_cost_usd, SUM(total_cost_brl) AS total_cost_brl FROM grouped`,
6477
+ )
6478
+ .get(...values) as Record<string, unknown>;
6479
+ const rows = this.database
6480
+ .prepare(
6481
+ `${ctes} SELECT group_value AS id, group_value AS key, title, status, risk, first_day, last_day, occurred_at, invocation_count, llm_call_count, total_duration, total_input_tokens, total_cached_input_tokens, total_output_tokens, total_reasoning_tokens, total_tokens, total_cost_usd, total_cost_brl FROM grouped ORDER BY total_cost_brl DESC, group_value LIMIT ? OFFSET ?`,
6482
+ )
6483
+ .all(...values, limit, offset) as Array<Record<string, unknown>>;
6484
+ return {
6485
+ data_source: "sqlite_read_model" as const,
6486
+ view,
6487
+ offset,
6488
+ limit,
6489
+ total_count,
6490
+ has_more: offset + rows.length < total_count,
6491
+ totals,
6492
+ rows,
6493
+ };
5765
6494
  }
5766
6495
  const hasDimensionalFilter = Boolean(
5767
- input.provider || input.model || input.agent || input.phase || input.status ||
5768
- input.origin || input.workType || input.priority || input.risk,
6496
+ input.provider ||
6497
+ input.model ||
6498
+ input.agent ||
6499
+ input.phase ||
6500
+ input.status ||
6501
+ input.origin ||
6502
+ input.workType ||
6503
+ input.priority ||
6504
+ input.risk,
5769
6505
  );
5770
6506
  if (hasDimensionalFilter) {
5771
6507
  const column = {
5772
- task: "i.task_id", spec: "i.spec_id", prd: "i.prd_id",
5773
- execution: "i.execution_id", conversation: "i.conversation_id",
6508
+ task: "i.task_id",
6509
+ spec: "i.spec_id",
6510
+ prd: "i.prd_id",
6511
+ execution: "i.execution_id",
6512
+ conversation: "i.conversation_id",
5774
6513
  }[groupKind];
5775
- const entity = groupKind === "task" ? "tasks" : groupKind === "spec" ? "specs" : groupKind === "prd" ? "prds" : null;
6514
+ const entity =
6515
+ groupKind === "task"
6516
+ ? "tasks"
6517
+ : groupKind === "spec"
6518
+ ? "specs"
6519
+ : groupKind === "prd"
6520
+ ? "prds"
6521
+ : null;
5776
6522
  const clauses = [`${column} IS NOT NULL`];
5777
6523
  const values: Array<string | number> = [];
5778
- if (input.from) { clauses.push("i.occurred_day_sp >= ?"); values.push(input.from.slice(0, 10)); }
5779
- if (input.to) { clauses.push("i.occurred_day_sp <= ?"); values.push(input.to.slice(0, 10)); }
5780
- if (input.agent) { clauses.push("i.zaip_agent = ?"); values.push(input.agent); }
5781
- if (input.phase) { clauses.push("i.phase = ?"); values.push(input.phase); }
5782
- if (input.status) { clauses.push("i.status = ?"); values.push(input.status); }
5783
- if (input.risk) { clauses.push("task_entity.risk = ?"); values.push(input.risk); }
5784
- if (input.origin) clauses.push(input.origin === "prd" ? "i.prd_id IS NOT NULL" : input.origin === "spec" ? "i.prd_id IS NULL AND i.spec_id IS NOT NULL" : input.origin === "task" ? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL" : "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL");
6524
+ if (input.from) {
6525
+ clauses.push("i.occurred_day_sp >= ?");
6526
+ values.push(input.from.slice(0, 10));
6527
+ }
6528
+ if (input.to) {
6529
+ clauses.push("i.occurred_day_sp <= ?");
6530
+ values.push(input.to.slice(0, 10));
6531
+ }
6532
+ if (input.agent) {
6533
+ clauses.push("i.zaip_agent = ?");
6534
+ values.push(input.agent);
6535
+ }
6536
+ if (input.phase) {
6537
+ clauses.push("i.phase = ?");
6538
+ values.push(input.phase);
6539
+ }
6540
+ if (input.status) {
6541
+ clauses.push("i.status = ?");
6542
+ values.push(input.status);
6543
+ }
6544
+ if (input.risk) {
6545
+ clauses.push("task_entity.risk = ?");
6546
+ values.push(input.risk);
6547
+ }
6548
+ if (input.origin)
6549
+ clauses.push(
6550
+ input.origin === "prd"
6551
+ ? "i.prd_id IS NOT NULL"
6552
+ : input.origin === "spec"
6553
+ ? "i.prd_id IS NULL AND i.spec_id IS NOT NULL"
6554
+ : input.origin === "task"
6555
+ ? "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NOT NULL"
6556
+ : "i.prd_id IS NULL AND i.spec_id IS NULL AND i.task_id IS NULL",
6557
+ );
5785
6558
  // Compact lifecycle rows do not materialize canonical work type or Task priority.
5786
6559
  // Return a safe empty result instead of substituting another attribution.
5787
6560
  if (input.workType || input.priority) clauses.push("0");
5788
6561
  const segment: string[] = [];
5789
- if (input.provider) { if (input.provider === "unavailable") clauses.push("NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')"); else { segment.push("u.provider = ?"); values.push(input.provider); } }
5790
- if (input.model) { if (input.model === "unavailable") clauses.push("NOT EXISTS (SELECT 1 FROM invocation_usage known_model WHERE known_model.agent_invocation_id=i.agent_invocation_id AND known_model.model IS NOT NULL AND known_model.model <> 'unavailable')"); else { segment.push("u.model = ?"); values.push(input.model); } }
5791
- if (segment.length > 0) clauses.push(`EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=i.agent_invocation_id AND ${segment.join(" AND ")})`);
6562
+ if (input.provider) {
6563
+ if (input.provider === "unavailable")
6564
+ clauses.push(
6565
+ "NOT EXISTS (SELECT 1 FROM invocation_usage known_provider WHERE known_provider.agent_invocation_id=i.agent_invocation_id AND known_provider.provider IS NOT NULL AND known_provider.provider <> 'unavailable')",
6566
+ );
6567
+ else {
6568
+ segment.push("u.provider = ?");
6569
+ values.push(input.provider);
6570
+ }
6571
+ }
6572
+ if (input.model) {
6573
+ if (input.model === "unavailable")
6574
+ clauses.push(
6575
+ "NOT EXISTS (SELECT 1 FROM invocation_usage known_model WHERE known_model.agent_invocation_id=i.agent_invocation_id AND known_model.model IS NOT NULL AND known_model.model <> 'unavailable')",
6576
+ );
6577
+ else {
6578
+ segment.push("u.model = ?");
6579
+ values.push(input.model);
6580
+ }
6581
+ }
6582
+ if (segment.length > 0)
6583
+ clauses.push(
6584
+ `EXISTS (SELECT 1 FROM invocation_usage u WHERE u.agent_invocation_id=i.agent_invocation_id AND ${segment.join(" AND ")})`,
6585
+ );
5792
6586
  if (input.query) {
5793
- clauses.push(entity === null ? `${column} LIKE ?` : `(${column} LIKE ? OR COALESCE(entity.title, '') LIKE ?)`);
6587
+ clauses.push(
6588
+ entity === null
6589
+ ? `${column} LIKE ?`
6590
+ : `(${column} LIKE ? OR COALESCE(entity.title, '') LIKE ?)`,
6591
+ );
5794
6592
  values.push(`%${input.query}%`, ...(entity === null ? [] : [`%${input.query}%`]));
5795
6593
  }
5796
- const entityJoin = entity === null ? "" : `LEFT JOIN ${entity} entity ON entity.id=${column}`;
6594
+ const entityJoin =
6595
+ entity === null ? "" : `LEFT JOIN ${entity} entity ON entity.id=${column}`;
5797
6596
  const taskJoin = "LEFT JOIN tasks task_entity ON task_entity.id=i.task_id";
5798
6597
  const join = `${entityJoin} ${taskJoin}`;
5799
6598
  const where = clauses.join(" AND ");
@@ -5802,17 +6601,60 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5802
6601
  const ctes = `WITH grouped AS (SELECT ${column} AS group_value, ${title} AS title, ${status} AS status, MAX(task_entity.risk) AS risk, MAX(i.occurred_at) AS occurred_at, COUNT(*) AS invocation_count, SUM(i.duration_ms) AS total_duration, SUM(i.total_tokens) AS total_tokens, SUM(i.cost_usd) AS total_cost_usd, SUM(i.cost_brl) AS total_cost_brl FROM invocations i ${join} WHERE ${where} GROUP BY ${column})`;
5803
6602
  const limit = Math.min(200, Math.max(1, input.limit ?? 50));
5804
6603
  const offset = Math.max(0, input.offset ?? 0);
5805
- const total_count = Number((this.database.prepare(`${ctes} SELECT COUNT(*) AS count FROM grouped`).get(...values) as { count: number }).count);
5806
- const totals = this.database.prepare(`${ctes} SELECT COUNT(*) AS entity_count, COALESCE(SUM(invocation_count), 0) AS invocation_count, ROUND(SUM(total_duration)) AS total_duration, SUM(total_tokens) AS total_tokens, SUM(total_cost_usd) AS total_cost_usd, SUM(total_cost_brl) AS total_cost_brl FROM grouped`).get(...values) as Record<string, unknown>;
5807
- const rows = this.database.prepare(`${ctes} SELECT group_value AS id, group_value AS key, title, status, risk, occurred_at, invocation_count, total_duration, total_tokens, total_cost_usd, total_cost_brl FROM grouped ORDER BY total_cost_brl DESC, group_value LIMIT ? OFFSET ?`).all(...values, limit, offset) as Array<Record<string, unknown>>;
5808
- return { data_source: "sqlite_read_model" as const, view, offset, limit, total_count, has_more: offset + rows.length < total_count, totals, rows };
6604
+ const total_count = Number(
6605
+ (
6606
+ this.database
6607
+ .prepare(`${ctes} SELECT COUNT(*) AS count FROM grouped`)
6608
+ .get(...values) as { count: number }
6609
+ ).count,
6610
+ );
6611
+ const totals = this.database
6612
+ .prepare(
6613
+ `${ctes} SELECT COUNT(*) AS entity_count, COALESCE(SUM(invocation_count), 0) AS invocation_count, ROUND(SUM(total_duration)) AS total_duration, SUM(total_tokens) AS total_tokens, SUM(total_cost_usd) AS total_cost_usd, SUM(total_cost_brl) AS total_cost_brl FROM grouped`,
6614
+ )
6615
+ .get(...values) as Record<string, unknown>;
6616
+ const rows = this.database
6617
+ .prepare(
6618
+ `${ctes} SELECT group_value AS id, group_value AS key, title, status, risk, occurred_at, invocation_count, total_duration, total_tokens, total_cost_usd, total_cost_brl FROM grouped ORDER BY total_cost_brl DESC, group_value LIMIT ? OFFSET ?`,
6619
+ )
6620
+ .all(...values, limit, offset) as Array<Record<string, unknown>>;
6621
+ return {
6622
+ data_source: "sqlite_read_model" as const,
6623
+ view,
6624
+ offset,
6625
+ limit,
6626
+ total_count,
6627
+ has_more: offset + rows.length < total_count,
6628
+ totals,
6629
+ rows,
6630
+ };
5809
6631
  }
5810
6632
  const entityMetadata = {
5811
- task: { table: "tasks", title: "entity.title", status: "entity.status", risk: "entity.risk" },
5812
- spec: { table: "specs", title: "entity.title", status: "entity.status", risk: "entity.risk" },
6633
+ task: {
6634
+ table: "tasks",
6635
+ title: "entity.title",
6636
+ status: "entity.status",
6637
+ risk: "entity.risk",
6638
+ },
6639
+ spec: {
6640
+ table: "specs",
6641
+ title: "entity.title",
6642
+ status: "entity.status",
6643
+ risk: "entity.risk",
6644
+ },
5813
6645
  prd: { table: "prds", title: "entity.title", status: "entity.status", risk: "entity.risk" },
5814
- execution: { table: "executions", title: "grouped.group_value", status: "entity.status", risk: "NULL" },
5815
- conversation: { table: "conversations", title: "grouped.group_value", status: "entity.status", risk: "NULL" },
6646
+ execution: {
6647
+ table: "executions",
6648
+ title: "grouped.group_value",
6649
+ status: "entity.status",
6650
+ risk: "NULL",
6651
+ },
6652
+ conversation: {
6653
+ table: "conversations",
6654
+ title: "grouped.group_value",
6655
+ status: "entity.status",
6656
+ risk: "NULL",
6657
+ },
5816
6658
  } as const;
5817
6659
  const metadata = entityMetadata[groupKind as keyof typeof entityMetadata];
5818
6660
  const clauses = ["group_kind = ?", "group_value IS NOT NULL"];
@@ -5918,8 +6760,10 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
5918
6760
  // Period rows use a coarse key (YYYY-MM/YYYY), so a partial daily range
5919
6761
  // must intersect start/end days instead of comparing that key directly.
5920
6762
  const overlaps =
5921
- view === "cost_monitor_monthly" || view === "cost_monitor_annual" ||
5922
- view === "operations_monthly" || view === "operations_annual";
6763
+ view === "cost_monitor_monthly" ||
6764
+ view === "cost_monitor_annual" ||
6765
+ view === "operations_monthly" ||
6766
+ view === "operations_annual";
5923
6767
  if (overlaps && input.from) {
5924
6768
  clauses.push("end_day >= ?");
5925
6769
  values.push(input.from.slice(0, 10));
@@ -6068,8 +6912,13 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
6068
6912
  const partialFromDay = input.from?.slice(0, 10) ?? null;
6069
6913
  const partialToDay = input.to?.slice(0, 10) ?? null;
6070
6914
  const dailyRows = partialCoarse
6071
- ? this.database.prepare(`SELECT * FROM operations_daily WHERE (? IS NULL OR day >= ?) AND (? IS NULL OR day <= ?)`)
6072
- .all(partialFromDay, partialFromDay, partialToDay, partialToDay) as Array<Record<string, unknown>>
6915
+ ? (this.database
6916
+ .prepare(
6917
+ `SELECT * FROM operations_daily WHERE (? IS NULL OR day >= ?) AND (? IS NULL OR day <= ?)`,
6918
+ )
6919
+ .all(partialFromDay, partialFromDay, partialToDay, partialToDay) as Array<
6920
+ Record<string, unknown>
6921
+ >)
6073
6922
  : [];
6074
6923
  const daily = this.executionDurationByDay({ from: input.from ?? null, to: input.to ?? null });
6075
6924
  for (const row of rows) {
@@ -6079,8 +6928,23 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
6079
6928
  const matchingDays = matching.map((day) => String(day.day)).sort();
6080
6929
  row.start_day = matchingDays[0] ?? null;
6081
6930
  row.end_day = matchingDays.at(-1) ?? null;
6082
- for (const column of ["prd_count", "spec_count", "task_count", "validation_count", "evidence_count", "total_run_count", "invocation_count", "llm_call_count", "total_tokens", "total_cost_usd", "total_cost_brl"] as const)
6083
- row[column] = matching.reduce((sum, day) => sum + (typeof day[column] === "number" ? Number(day[column]) : 0), 0);
6931
+ for (const column of [
6932
+ "prd_count",
6933
+ "spec_count",
6934
+ "task_count",
6935
+ "validation_count",
6936
+ "evidence_count",
6937
+ "total_run_count",
6938
+ "invocation_count",
6939
+ "llm_call_count",
6940
+ "total_tokens",
6941
+ "total_cost_usd",
6942
+ "total_cost_brl",
6943
+ ] as const)
6944
+ row[column] = matching.reduce(
6945
+ (sum, day) => sum + (typeof day[column] === "number" ? Number(day[column]) : 0),
6946
+ 0,
6947
+ );
6084
6948
  }
6085
6949
  row.operations_total_duration = [...daily]
6086
6950
  .filter(([day]) => (view === "operations_daily" ? day === key : day.startsWith(key)))
@@ -6112,10 +6976,12 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
6112
6976
  } {
6113
6977
  if (this.metadataValues === null) {
6114
6978
  this.metadataValues = new Map(
6115
- (this.database.prepare("SELECT key, value FROM read_model_metadata").all() as Array<{
6116
- key: string;
6117
- value: string;
6118
- }>).map((row) => [row.key, row.value]),
6979
+ (
6980
+ this.database.prepare("SELECT key, value FROM read_model_metadata").all() as Array<{
6981
+ key: string;
6982
+ value: string;
6983
+ }>
6984
+ ).map((row) => [row.key, row.value]),
6119
6985
  );
6120
6986
  }
6121
6987
  const value = (key: string) => Number(this.metadataValues?.get(key) ?? 0);