@zaip-ai/cli 1.0.7 → 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 (444) hide show
  1. package/README.md +6 -4
  2. package/dist/services/adapters.d.ts +1 -1
  3. package/dist/services/adapters.d.ts.map +1 -1
  4. package/dist/services/adapters.js +15 -5
  5. package/dist/services/adapters.js.map +1 -1
  6. package/dist/services/adapters.test.js +1 -1
  7. package/dist/services/agent-adapters.d.ts +9 -0
  8. package/dist/services/agent-adapters.d.ts.map +1 -1
  9. package/dist/services/agent-adapters.js +39 -4
  10. package/dist/services/agent-adapters.js.map +1 -1
  11. package/dist/services/agent-adapters.test.js +13 -1
  12. package/dist/services/agent-adapters.test.js.map +1 -1
  13. package/dist/services/codex-config-template.d.ts +2 -1
  14. package/dist/services/codex-config-template.d.ts.map +1 -1
  15. package/dist/services/codex-config-template.js +13 -2
  16. package/dist/services/codex-config-template.js.map +1 -1
  17. package/dist/services/codex-rules.d.ts +1 -1
  18. package/dist/services/codex-rules.d.ts.map +1 -1
  19. package/dist/services/codex-rules.js +31 -7
  20. package/dist/services/codex-rules.js.map +1 -1
  21. package/dist/services/codex-subagent-hook-template.d.ts +1 -1
  22. package/dist/services/codex-subagent-hook-template.d.ts.map +1 -1
  23. package/dist/services/codex-subagent-hook-template.js +4 -1
  24. package/dist/services/codex-subagent-hook-template.js.map +1 -1
  25. package/dist/services/doctor.d.ts.map +1 -1
  26. package/dist/services/doctor.js +3 -1
  27. package/dist/services/doctor.js.map +1 -1
  28. package/dist/services/global-codex-config.d.ts +6 -3
  29. package/dist/services/global-codex-config.d.ts.map +1 -1
  30. package/dist/services/global-codex-config.js +82 -13
  31. package/dist/services/global-codex-config.js.map +1 -1
  32. package/dist/services/global-codex-config.test.js +57 -10
  33. package/dist/services/global-codex-config.test.js.map +1 -1
  34. package/dist/services/install-agents-template.d.ts.map +1 -1
  35. package/dist/services/install-agents-template.js +15 -2
  36. package/dist/services/install-agents-template.js.map +1 -1
  37. package/dist/services/logger.js +1 -1
  38. package/dist/services/logger.js.map +1 -1
  39. package/dist/services/logger.test.js +3 -3
  40. package/dist/services/logger.test.js.map +1 -1
  41. package/dist/services/runtime-template.test.js +21 -5
  42. package/dist/services/runtime-template.test.js.map +1 -1
  43. package/dist/services/template-manifest.d.ts.map +1 -1
  44. package/dist/services/template-manifest.js +7 -0
  45. package/dist/services/template-manifest.js.map +1 -1
  46. package/package.json +1 -1
  47. package/templates/.agents/architecture/software-architecture.md +131 -0
  48. package/templates/.agents/branding/brandbook.docx +0 -0
  49. package/templates/.agents/branding/brandbook.md +60 -0
  50. package/templates/.agents/config/agents/catalog.yaml +310 -10
  51. package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +30 -0
  52. package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +5 -5
  53. package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +2 -2
  54. package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +30 -0
  55. package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +30 -0
  56. package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +30 -0
  57. package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +8 -8
  58. package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +5 -3
  59. package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +30 -0
  60. package/templates/.agents/config/project.yaml +1 -1
  61. package/templates/.agents/dashboard/.tmp-run-list.json +254 -0
  62. package/templates/.agents/dashboard/.zaip-managed.json +775 -163
  63. package/templates/.agents/dashboard/DATA-DICTIONARY.md +2227 -0
  64. package/templates/.agents/dashboard/README.md +48 -97
  65. package/templates/.agents/dashboard/apps/frontend/dist/assets/CostCalculationGuidePage-DxR_pgSV.js +1 -0
  66. package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-4jZLTWE_.js +2156 -0
  67. package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-CKcsqb-r.js +1 -0
  68. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-BomxqCBU.css +1 -0
  69. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-DmmJD2Th.js +3 -0
  70. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BwtitWa_.js +1 -0
  71. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-CaKwQP43.css +1 -0
  72. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-BZ5WdaMN.css +1 -0
  73. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-N5Csr0RP.js +68 -0
  74. package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-C98LGC-Q.js → dashboard-localization-CLJ0lT9v.js} +1 -1
  75. package/templates/.agents/dashboard/apps/frontend/dist/assets/icons-vendor-C0fj2uU9.js +1 -0
  76. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-C4aIB1PV.js +2 -0
  77. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DK_enB5Z.css +1 -0
  78. package/templates/.agents/dashboard/apps/frontend/dist/assets/react-vendor-BEsJ-FYH.js +9 -0
  79. package/templates/.agents/dashboard/apps/frontend/dist/assets/rolldown-runtime-D9-fqq9M.js +1 -0
  80. package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-B6y6Gw12.js +1 -0
  81. package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-DFY7ZDlu.css +1 -0
  82. package/templates/.agents/dashboard/apps/frontend/dist/index.html +9 -2
  83. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  84. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +1129 -600
  85. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingBoundary.tsx +48 -0
  86. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingPanels.tsx +2 -0
  87. package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +247 -0
  88. package/templates/.agents/dashboard/apps/frontend/src/AppRequestGuards.tsx +95 -0
  89. package/templates/.agents/dashboard/apps/frontend/src/AppSetupRoutes.tsx +87 -0
  90. package/templates/.agents/dashboard/apps/frontend/src/CostCalculationGuidePage.tsx +641 -0
  91. package/templates/.agents/dashboard/apps/frontend/src/DashboardAlternateContent.tsx +159 -0
  92. package/templates/.agents/dashboard/apps/frontend/src/DashboardCapabilityNav.tsx +13 -13
  93. package/templates/.agents/dashboard/apps/frontend/src/DashboardGlobalSettings.tsx +56 -0
  94. package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.tsx +266 -315
  95. package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +151 -0
  96. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +37 -0
  97. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +114 -12
  98. package/templates/.agents/dashboard/apps/frontend/src/app-preferences.ts +15 -0
  99. package/templates/.agents/dashboard/apps/frontend/src/app-session-token.ts +20 -0
  100. package/templates/.agents/dashboard/apps/frontend/src/cost-calculation-guide.css +267 -0
  101. package/templates/.agents/dashboard/apps/frontend/src/cost-calculation-guide.test.ts +69 -0
  102. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +85 -0
  103. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +133 -0
  104. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +453 -13
  105. package/templates/.agents/dashboard/apps/frontend/src/dashboard-localization.ts +1 -1
  106. package/templates/.agents/dashboard/apps/frontend/src/main.tsx +1 -0
  107. package/templates/.agents/dashboard/apps/frontend/src/operations-detail-hydration.test.ts +55 -0
  108. package/templates/.agents/dashboard/apps/frontend/src/operations-detail-hydration.ts +36 -0
  109. package/templates/.agents/dashboard/apps/frontend/src/operations-entity-page-projection.test.ts +126 -0
  110. package/templates/.agents/dashboard/apps/frontend/src/operations-entity-page-projection.ts +181 -0
  111. package/templates/.agents/dashboard/apps/frontend/src/operations-period-contract.test.ts +138 -0
  112. package/templates/.agents/dashboard/apps/frontend/src/operations-period-render.test.ts +105 -0
  113. package/templates/.agents/dashboard/apps/frontend/src/operations-period.test.ts +6 -3
  114. package/templates/.agents/dashboard/apps/frontend/src/operations-period.ts +9 -5
  115. package/templates/.agents/dashboard/apps/frontend/src/overview-status-page.test.ts +26 -0
  116. package/templates/.agents/dashboard/apps/frontend/src/period-transition-overlay.css +44 -0
  117. package/templates/.agents/dashboard/apps/frontend/src/project-settings-model.ts +121 -62
  118. package/templates/.agents/dashboard/apps/frontend/src/project-settings-page.css +482 -3
  119. package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +84 -17
  120. package/templates/.agents/dashboard/apps/frontend/src/snapshot-banner-semantics.test.ts +78 -0
  121. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +234 -39
  122. package/templates/.agents/dashboard/apps/frontend/src/snapshot-notice.ts +15 -0
  123. package/templates/.agents/dashboard/apps/frontend/src/sqlite-cost-adapters.ts +26 -0
  124. package/templates/.agents/dashboard/apps/frontend/src/styles.css +14 -2
  125. package/templates/.agents/dashboard/apps/frontend/src/traceability-workspace.css +38 -2
  126. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +253 -0
  127. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +380 -0
  128. package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +175 -0
  129. package/templates/.agents/dashboard/apps/frontend/src/use-app-operations-pages.ts +395 -0
  130. package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-documents.ts +141 -0
  131. package/templates/.agents/dashboard/apps/frontend/src/use-idle-cost-prefetch.ts +45 -0
  132. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.test.ts +46 -0
  133. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.ts +36 -0
  134. package/templates/.agents/dashboard/apps/frontend/vite.config.ts +58 -5
  135. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  136. package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +47 -6
  137. package/templates/.agents/dashboard/modules/cost-monitor/src/duration-estimate.ts +13 -0
  138. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.test.ts +134 -0
  139. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.tsx +290 -0
  140. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.test.ts +133 -19
  141. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.tsx +134 -43
  142. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.tsx +137 -19
  143. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.test.ts +2 -0
  144. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.tsx +13 -10
  145. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +328 -0
  146. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +302 -6
  147. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +627 -400
  148. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostSummary.tsx +5 -13
  149. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +370 -72
  150. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +467 -326
  151. package/templates/.agents/dashboard/modules/cost-monitor/src/react/MacroCostTable.test.ts +17 -0
  152. package/templates/.agents/dashboard/modules/cost-monitor/src/react/MacroCostTable.tsx +25 -6
  153. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-copy.ts +0 -2
  154. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-page-helpers.ts +17 -0
  155. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-responsive.css +6 -3
  156. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-row-filters.ts +123 -0
  157. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-summary.ts +8 -3
  158. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-views.ts +32 -0
  159. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor.css +55 -14
  160. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-table-presentation.ts +121 -0
  161. package/templates/.agents/dashboard/modules/cost-monitor/src/react/index.ts +3 -0
  162. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.test.ts +68 -0
  163. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +33 -5
  164. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +119 -0
  165. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.test.ts +12 -0
  166. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +146 -0
  167. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +118 -0
  168. package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.test.ts +2 -2
  169. package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.ts +5 -3
  170. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-pricing-fallback.test.ts +53 -0
  171. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.basics.test.ts +34 -0
  172. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.test.ts +168 -161
  173. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.ts +188 -29
  174. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.test.ts +757 -0
  175. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +784 -0
  176. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-terminal-day.test.ts +100 -0
  177. package/templates/.agents/dashboard/modules/cost-monitor/src/server/index.ts +1 -0
  178. package/templates/.agents/dashboard/modules/cost-monitor/src/server/refactor-map-xr9l.json +61 -0
  179. package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-monitor-projection-agents.test.ts +31 -0
  180. package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-summary-projection-duration.test.ts +414 -0
  181. package/templates/.agents/dashboard/modules/cost-monitor/src/task-cost-groups.test.ts +41 -0
  182. package/templates/.agents/dashboard/modules/cost-monitor/src/task-cost-groups.ts +55 -11
  183. package/templates/.agents/dashboard/modules/operations/package.json +2 -1
  184. package/templates/.agents/dashboard/modules/operations/src/contracts.ts +185 -0
  185. package/templates/.agents/dashboard/modules/operations/src/duration-population.test.ts +63 -1
  186. package/templates/.agents/dashboard/modules/operations/src/duration-population.ts +60 -5
  187. package/templates/.agents/dashboard/modules/operations/src/react/EntityDetailsDialog.tsx +14 -3
  188. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.test.ts +44 -1
  189. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +345 -18
  190. package/templates/.agents/dashboard/modules/operations/src/react/MacroPlanningDialog.tsx +1 -0
  191. package/templates/.agents/dashboard/modules/operations/src/react/OperationDateFilters.test.ts +9 -5
  192. package/templates/.agents/dashboard/modules/operations/src/react/OperationDateFilters.tsx +2 -2
  193. package/templates/.agents/dashboard/modules/operations/src/react/OperationsMainContent.tsx +168 -0
  194. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +95 -6
  195. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +367 -14
  196. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +562 -131
  197. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +353 -355
  198. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageNavigation.tsx +65 -0
  199. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageOverview.tsx +169 -0
  200. package/templates/.agents/dashboard/modules/operations/src/react/PeriodSummaryTable.tsx +73 -8
  201. package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.test.tsx +32 -0
  202. package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.tsx +62 -6
  203. package/templates/.agents/dashboard/modules/operations/src/react/TaskBoard.test.ts +55 -0
  204. package/templates/.agents/dashboard/modules/operations/src/react/TaskBoard.tsx +27 -19
  205. package/templates/.agents/dashboard/modules/operations/src/react/TaskDetailsDialog.tsx +105 -42
  206. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.priority.test.ts +19 -0
  207. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +265 -36
  208. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.test.ts +171 -8
  209. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +98 -68
  210. package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.test.ts +72 -1
  211. package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.ts +78 -17
  212. package/templates/.agents/dashboard/modules/operations/src/react/execution-metrics.ts +6 -4
  213. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +431 -1
  214. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +574 -20
  215. package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +42 -4
  216. package/templates/.agents/dashboard/modules/operations/src/react/export/xlsx-runtime-e2e.test.ts +248 -0
  217. package/templates/.agents/dashboard/modules/operations/src/react/macro-planning-dialog-content.css +49 -0
  218. package/templates/.agents/dashboard/modules/operations/src/react/macro-planning-dialog.css +93 -50
  219. package/templates/.agents/dashboard/modules/operations/src/react/manual-work-priority.test.ts +82 -0
  220. package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.test.ts +29 -2
  221. package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.ts +22 -1
  222. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.test.ts +77 -4
  223. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.ts +67 -5
  224. package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts +54 -0
  225. package/templates/.agents/dashboard/modules/operations/src/react/operations-global-filters.test.ts +22 -0
  226. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +149 -12
  227. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +27 -0
  228. package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +176 -0
  229. package/templates/.agents/dashboard/modules/operations/src/react/operations-period-scope.test.ts +160 -0
  230. package/templates/.agents/dashboard/modules/operations/src/react/operations-period-scope.ts +47 -0
  231. package/templates/.agents/dashboard/modules/operations/src/react/operations-query.ts +10 -0
  232. package/templates/.agents/dashboard/modules/operations/src/react/operations-responsive.css +3 -0
  233. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-labels.ts +28 -0
  234. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-order.test.ts +21 -7
  235. package/templates/.agents/dashboard/modules/operations/src/react/operations.css +42 -0
  236. package/templates/.agents/dashboard/modules/operations/src/react/period-summary-population.test.ts +54 -0
  237. package/templates/.agents/dashboard/modules/operations/src/react/period-summary-population.ts +32 -0
  238. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.test.ts +241 -6
  239. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.ts +148 -6
  240. package/templates/.agents/dashboard/modules/operations/src/react/popup-localization.test.ts +1 -1
  241. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.css +132 -0
  242. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.css.test.ts +13 -0
  243. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.header.test.ts +16 -0
  244. package/templates/.agents/dashboard/modules/operations/src/react/use-infinite-column.ts +11 -5
  245. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.test.ts +7 -4
  246. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.ts +5 -84
  247. package/templates/.agents/dashboard/modules/operations/src/server/build-operations-projection.test.ts +291 -15
  248. package/templates/.agents/dashboard/modules/operations/src/server/build-operations-projection.ts +182 -472
  249. package/templates/.agents/dashboard/modules/operations/src/server/canonical-compat.test.ts +40 -0
  250. package/templates/.agents/dashboard/modules/operations/src/server/canonical-compat.ts +19 -6
  251. package/templates/.agents/dashboard/modules/operations/src/server/canonical-run-projection.ts +31 -6
  252. package/templates/.agents/dashboard/modules/operations/src/server/daily-duration-projection.ts +220 -0
  253. package/templates/.agents/dashboard/modules/operations/src/server/execution-duration-projection.ts +28 -0
  254. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.test.ts +39 -2
  255. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.ts +25 -13
  256. package/templates/.agents/dashboard/modules/operations/src/server/filter.test.ts +3 -3
  257. package/templates/.agents/dashboard/modules/operations/src/server/filter.ts +7 -2
  258. package/templates/.agents/dashboard/modules/operations/src/server/index.ts +3 -0
  259. package/templates/.agents/dashboard/modules/operations/src/server/macro.ts +3 -12
  260. package/templates/.agents/dashboard/modules/operations/src/server/manifest-usage.ts +65 -0
  261. package/templates/.agents/dashboard/modules/operations/src/server/operations-projection-aggregates.ts +297 -0
  262. package/templates/.agents/dashboard/modules/operations/src/server/quick-daily-operations-projection.ts +330 -0
  263. package/templates/.agents/dashboard/modules/operations/src/server/quick-operations-projection.test.ts +505 -0
  264. package/templates/.agents/dashboard/modules/operations/src/server/quick-operations-projection.ts +609 -0
  265. package/templates/.agents/dashboard/modules/operations/src/server/runs.ts +18 -19
  266. package/templates/.agents/dashboard/modules/operations/src/work-board-model.ts +76 -0
  267. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  268. package/templates/.agents/dashboard/modules/traceability/src/server/build-task-trace.ts +3 -1
  269. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  270. package/templates/.agents/dashboard/packages/branding/src/index.ts +1 -1
  271. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  272. package/templates/.agents/dashboard/packages/contracts/src/index.ts +2 -0
  273. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  274. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +1759 -0
  275. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs.metafile.json +7333 -0
  276. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +1611 -0
  277. package/templates/.agents/dashboard/packages/local-runtime/package.json +8 -3
  278. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-dashboard.mjs +20 -0
  279. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +114 -0
  280. package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +31 -0
  281. package/templates/.agents/dashboard/packages/local-runtime/scripts/source-size-runtime-scripts/sqlite-read-model-fixture.mjs +397 -0
  282. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-read-model.mjs +589 -0
  283. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-unix-launchers.mjs +53 -0
  284. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +203 -0
  285. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +1646 -107
  286. package/templates/.agents/dashboard/packages/local-runtime/src/agent-adapter-sync.ts +30 -0
  287. package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.test.ts +3 -2
  288. package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.ts +5 -0
  289. package/templates/.agents/dashboard/packages/local-runtime/src/manifest-runs.ts +72 -33
  290. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +339 -4
  291. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +392 -1
  292. package/templates/.agents/dashboard/packages/local-runtime/src/runtime.mjs +5 -5
  293. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.test.ts +110 -0
  294. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +189 -0
  295. package/templates/.agents/dashboard/packages/local-runtime/src/server-cross-layer-parity.test.ts +245 -0
  296. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models-route.ts +64 -0
  297. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models.test.ts +204 -0
  298. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models.ts +194 -0
  299. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-pages.test.ts +163 -0
  300. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-pages.ts +354 -0
  301. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-runs-route.test.ts +74 -0
  302. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-runs-route.ts +113 -0
  303. package/templates/.agents/dashboard/packages/local-runtime/src/server-overview.test.ts +68 -0
  304. package/templates/.agents/dashboard/packages/local-runtime/src/server-period.ts +47 -0
  305. package/templates/.agents/dashboard/packages/local-runtime/src/server-projections.ts +483 -0
  306. package/templates/.agents/dashboard/packages/local-runtime/src/server-settings.test.ts +5 -2
  307. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +178 -84
  308. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +149 -0
  309. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +675 -267
  310. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +1212 -309
  311. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.test.ts +53 -0
  312. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.ts +722 -0
  313. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +3743 -0
  314. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +7017 -0
  315. package/templates/.agents/dashboard/packages/local-runtime/src/work-priority-contract.test.ts +33 -0
  316. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-history.mjs +41 -0
  317. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-processes.mjs +210 -0
  318. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +514 -190
  319. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +324 -24
  320. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  321. package/templates/.agents/dashboard/packages/windows-launcher/LauncherVerificationBehaviorTests.cs +206 -0
  322. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +95 -8
  323. package/templates/.agents/dashboard/packages/windows-launcher/build-dashboard-launcher.ps1 +2 -2
  324. package/templates/.agents/dashboard/packages/windows-launcher/run-launcher-verification-tests.ps1 +22 -0
  325. package/templates/.agents/dashboard/read-model.dbml +2201 -0
  326. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  327. package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +1 -1
  328. package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +216 -41
  329. package/templates/.agents/dashboard/work-priority.json +34 -0
  330. package/templates/.agents/dashboard/zaip-dashboard-linux.sh +45 -0
  331. package/templates/.agents/dashboard/zaip-dashboard-mac.command +3 -0
  332. package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
  333. package/templates/.agents/design/.zaip-managed.json +3 -3
  334. package/templates/.agents/design/apps/catalog/package.json +1 -1
  335. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  336. package/templates/.agents/growth/estrategia-bootstrap-90-dias.md +169 -0
  337. package/templates/.agents/growth/strategy.md +27 -0
  338. package/templates/.agents/marketing/estrategia-marketing-zaip-90-dias.md +197 -0
  339. package/templates/.agents/marketing/strategy.md +29 -0
  340. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  341. package/templates/.agents/requirements/comparative-audit.md +1 -1
  342. package/templates/.agents/requirements/context-engine.md +1 -1
  343. package/templates/.agents/requirements/cost-observability.md +1 -1
  344. package/templates/.agents/requirements/dashboard-launch.md +24 -0
  345. package/templates/.agents/requirements/data-export.md +1 -1
  346. package/templates/.agents/requirements/design-system.md +1 -1
  347. package/templates/.agents/requirements/execution-engine.md +1 -1
  348. package/templates/.agents/requirements/kanban-dashboard.md +4 -3
  349. package/templates/.agents/requirements/model-orchestration.md +1 -1
  350. package/templates/.agents/requirements/public-distribution.md +9 -6
  351. package/templates/.agents/requirements/software-architecture.md +1 -1
  352. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  353. package/templates/.agents/requirements/work-lifecycle.md +9 -3
  354. package/templates/.agents/requirements/zaip-strategic-95.md +4 -4
  355. package/templates/.agents/rules/autonomy-decision.md +15 -0
  356. package/templates/.agents/rules/core.md +1 -1
  357. package/templates/.agents/rules/run-lifecycle-closure.md +1 -1
  358. package/templates/.agents/rules/specialist-routing.md +19 -0
  359. package/templates/.agents/rules/workflow.md +1 -1
  360. package/templates/.agents/runtime/VERSION +1 -1
  361. package/templates/.agents/runtime/bin/zaip.mjs +118 -67
  362. package/templates/.agents/runtime/checksums.sha256 +6 -4
  363. package/templates/.agents/runtime/manifest.json +3 -1
  364. package/templates/.agents/runtime/pnpm-lock.sha256 +1 -0
  365. package/templates/.agents/runtime/provenance.json +11 -0
  366. package/templates/.agents/runtime/sbom.cdx.json +1929 -39
  367. package/templates/.agents/sales/playbook.md +32 -0
  368. package/templates/.agents/sales/roteiro-comercial-zaip.md +252 -0
  369. package/templates/.agents/skills/zaip-aios/SKILL.md +1 -1
  370. package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +22 -0
  371. package/templates/.agents/skills/zaip-skill-brand-governance/agents/openai.yaml +4 -0
  372. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +39 -0
  373. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +30 -0
  374. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/SKILL.md +25 -0
  375. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/agents/openai.yaml +4 -0
  376. package/templates/.agents/skills/zaip-skill-enforce-architecture/SKILL.md +2 -0
  377. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/SKILL.md +18 -0
  378. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/agents/openai.yaml +6 -0
  379. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/ready-queue-fence.mjs +71 -0
  380. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/ready-queue-fence.test.mjs +60 -0
  381. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/scheduled-ready-queue-contract.test.mjs +111 -0
  382. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +2 -2
  383. package/templates/.agents/skills/zaip-skill-frontend-launcher/references/launcher-contract.md +3 -1
  384. package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +12 -0
  385. package/templates/.agents/skills/zaip-skill-general-operations/agents/openai.yaml +4 -0
  386. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +9 -7
  387. package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
  388. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +11 -38
  389. package/templates/.agents/skills/zaip-skill-governance-compliance/SKILL.md +12 -0
  390. package/templates/.agents/skills/zaip-skill-governance-compliance/agents/openai.yaml +4 -0
  391. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +21 -0
  392. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/agents/openai.yaml +4 -0
  393. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/references/growth-revops-framework.md +25 -0
  394. package/templates/.agents/skills/zaip-skill-instalar-agendamento/SKILL.md +17 -0
  395. package/templates/.agents/skills/zaip-skill-instalar-agendamento/agents/openai.yaml +6 -0
  396. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +9 -3
  397. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +99 -93
  398. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +132 -23
  399. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +149 -1
  400. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +76 -19
  401. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +217 -7
  402. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/source-size-telemetry/model-identity-reconciliation.mjs +77 -0
  403. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +20 -0
  404. package/templates/.agents/skills/zaip-skill-sales-methodology/agents/openai.yaml +4 -0
  405. package/templates/.agents/skills/zaip-skill-sales-methodology/references/sales-process-framework.md +77 -0
  406. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -0
  407. package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +96 -0
  408. package/templates/.agents/skills/zaip-skill-sqlite-read-model/agents/openai.yaml +4 -0
  409. package/templates/.agents/skills/zaip-skill-sqlite-read-model/scripts/ensure-node.mjs +41 -0
  410. package/templates/.agents/zaip-aios/INDEX.md +11 -0
  411. package/templates/.agents/zaip-aios/README.md +38 -8
  412. package/templates/.agents/zaip-aios/VERSION +1 -1
  413. package/templates/.agents/zaip-aios/guides/09-agentes-e-squads.md +43 -6
  414. package/templates/.agents/zaip-aios/guides/10-seguranca.md +21 -7
  415. package/templates/.agents/zaip-aios/guides/17-como-o-custo-e-calculado.md +32 -0
  416. package/templates/.agents/zaip-aios/guides/18-catalogo-de-agentes.md +76 -0
  417. package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +72 -0
  418. package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +330 -0
  419. package/templates/.agents/zaip-aios/guides/21-telemetria-monitor-custos.md +52 -0
  420. package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +69 -0
  421. package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +483 -0
  422. package/templates/.agents/zaip-aios/guides/24-calculo-de-custos-openai.md +85 -0
  423. package/templates/.agents/zaip-aios/guides/25-fontes-de-precos-openai.md +18 -0
  424. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +44 -0
  425. package/templates/.agents/zaip-aios/guides/27-telemetria-git-native.md +167 -0
  426. package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +144 -0
  427. package/templates/.agents/zaip-aios/guides/assets/diagrams/lifecycles-task-spec-prd.png +0 -0
  428. package/templates/.agents/zaip-aios/guides/assets/diagrams/prd-lifecycle.png +0 -0
  429. package/templates/.agents/zaip-aios/guides/assets/diagrams/spec-lifecycle.png +0 -0
  430. package/templates/.agents/zaip-aios/guides/assets/diagrams/task-lifecycle.png +0 -0
  431. package/templates/template-manifest.json +1082 -215
  432. package/templates/.agents/dashboard/apps/frontend/dist/assets/AgentCatalogSettingsPage-CLYIi7Vo.js +0 -1
  433. package/templates/.agents/dashboard/apps/frontend/dist/assets/AgentCatalogSettingsPage-DTDNVQGX.css +0 -1
  434. package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-Cv-JcoxP.js +0 -1320
  435. package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-CzEggcRb.js +0 -1
  436. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-05YKn6Z4.js +0 -3
  437. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-YO3yqtgV.css +0 -1
  438. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8AzCnl3a.js +0 -77
  439. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DdBYQ_8D.css +0 -1
  440. package/templates/.agents/dashboard/apps/frontend/src/AgentCatalogSettingsPage.tsx +0 -617
  441. package/templates/.agents/dashboard/apps/frontend/src/agent-catalog-settings-page.css +0 -660
  442. package/templates/.agents/dashboard/apps/frontend/src/agent-catalog-settings.test.ts +0 -249
  443. package/templates/.agents/dashboard/dashboard.config.json +0 -4
  444. package/templates/.agents/dashboard/zaip-dashboard.exe +0 -0
@@ -0,0 +1,3743 @@
1
+ import { mkdir, mkdtemp, readdir, readFile, rename, utimes, writeFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import { createDashboardApp, projectAllTimeTopTasks } from "./server.js";
6
+ import {
7
+ allocateAttributedTokenActiveMs,
8
+ rebuildSqliteReadModel,
9
+ SqliteReadModelFileSystem,
10
+ sourceFingerprint,
11
+ } from "./sqlite-read-model.js";
12
+
13
+ const roots: string[] = [];
14
+ // Windows can deny SQLite's sidecar files below the system TEMP directory.
15
+ // Keep disposable fixtures in the workspace-local test area instead; each
16
+ // test still receives its own root and never touches the live read model.
17
+ const sqliteTestTempRoot = join(
18
+ resolve(import.meta.dirname, "../../../../../.."),
19
+ ".tmp",
20
+ "sqlite-read-model-tests",
21
+ );
22
+ async function createSqliteTestRoot(): Promise<string> {
23
+ await mkdir(sqliteTestTempRoot, { recursive: true });
24
+ return mkdtemp(join(sqliteTestTempRoot, "fixture-"));
25
+ }
26
+ afterEach(async () => {
27
+ await Promise.all(
28
+ roots
29
+ .splice(0)
30
+ .map((root) =>
31
+ import("node:fs/promises").then(({ rm }) => rm(root, { recursive: true, force: true })),
32
+ ),
33
+ );
34
+ });
35
+
36
+ describe("SQLite dashboard read model", () => {
37
+ it("snapshots sorted eligible canonical sources once for fingerprinting and ingestion", async () => {
38
+ const root = await createSqliteTestRoot();
39
+ roots.push(root);
40
+ const included = join(root, ".agents", "records", "tasks", "nested", "task.md");
41
+ const excluded = join(root, ".agents", "records", "telemetry", "summary.json");
42
+ await mkdir(join(root, ".agents", "records", "tasks", "nested"), { recursive: true });
43
+ await mkdir(join(root, ".agents", "records", "telemetry"), { recursive: true });
44
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
45
+ await writeFile(
46
+ join(root, ".agents", "config", "project.yaml"),
47
+ "zaip:\n enabled: true\n",
48
+ "utf8",
49
+ );
50
+ await writeFile(included, "# included\n", "utf8");
51
+ await writeFile(excluded, "ignored\n", "utf8");
52
+
53
+ const before = await sourceFingerprint(root);
54
+ expect(before.candidates.map((candidate) => candidate.source)).toEqual([
55
+ ".agents/config/project.yaml",
56
+ ".agents/records/tasks/nested/task.md",
57
+ ]);
58
+ expect(before.fingerprint).toContain(".agents/records/tasks/nested/task.md:");
59
+ const changedAt = new Date(Date.now() + 2_000);
60
+ await utimes(included, changedAt, changedAt);
61
+ const after = await sourceFingerprint(root);
62
+ expect(after.fingerprint).not.toBe(before.fingerprint);
63
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ files: 2 });
64
+ });
65
+
66
+ it("uses a current-schema seed after a canonical change", async () => {
67
+ const root = await createSqliteTestRoot();
68
+ roots.push(root);
69
+ const task = join(root, ".agents", "records", "tasks", "task.md");
70
+ await mkdir(join(root, ".agents", "records", "tasks"), { recursive: true });
71
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
72
+ await writeFile(
73
+ join(root, ".agents", "config", "project.yaml"),
74
+ "zaip:\n enabled: true\n",
75
+ "utf8",
76
+ );
77
+ await writeFile(task, "# original\n", "utf8");
78
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "rebuilt" });
79
+ await writeFile(task, "# changed canonically\n", "utf8");
80
+ const previousPath = process.env.ZAIP_SQLITE_DB_PATH;
81
+ const previousSeed = process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED;
82
+ try {
83
+ process.env.ZAIP_SQLITE_DB_PATH = join(root, ".local", "dashboard-sqlite", "next.sqlite");
84
+ process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED = "1";
85
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({
86
+ cache: "stale_seed",
87
+ files: 2,
88
+ });
89
+ await expect(rebuildSqliteReadModel(root, { forceCanonical: true })).resolves.toMatchObject({
90
+ cache: "rebuilt",
91
+ files: 2,
92
+ });
93
+ } finally {
94
+ if (previousPath === undefined) delete process.env.ZAIP_SQLITE_DB_PATH;
95
+ else process.env.ZAIP_SQLITE_DB_PATH = previousPath;
96
+ if (previousSeed === undefined) delete process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED;
97
+ else process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED = previousSeed;
98
+ }
99
+ });
100
+
101
+ it("keeps an exact cached range valid with future facts and fast-paths an empty later month", async () => {
102
+ const root = await createSqliteTestRoot();
103
+ roots.push(root);
104
+ await mkdir(join(root, ".agents", "records", "tasks"), { recursive: true });
105
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
106
+ await writeFile(
107
+ join(root, ".agents", "config", "project.yaml"),
108
+ "zaip:\n enabled: true\n",
109
+ "utf8",
110
+ );
111
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "rebuilt" });
112
+ const path = join(root, ".local", "dashboard-sqlite", "read-model.sqlite");
113
+ const database = new DatabaseSync(path);
114
+ database
115
+ .prepare(
116
+ "UPDATE zaip_aios_activities SET payload_json=? WHERE payload_type='first_fold_monthly_quick_cache'",
117
+ )
118
+ .run(
119
+ JSON.stringify({
120
+ from_day: "2026-08-01",
121
+ to_day: "2026-08-18",
122
+ source_latest_day: "2026-08-30",
123
+ aggregate: {
124
+ days: 0,
125
+ total_run_count: 0,
126
+ agent_invocation_count: 0,
127
+ llm_call_count: null,
128
+ duration_ms: null,
129
+ total_tokens: null,
130
+ cost_brl: null,
131
+ execution_day_count: 0,
132
+ },
133
+ pages: { task: [], spec: [], prd: [] },
134
+ top_tasks: [],
135
+ }),
136
+ );
137
+ database.close();
138
+ const filesystem = new SqliteReadModelFileSystem(root);
139
+ try {
140
+ expect(
141
+ filesystem.firstFoldMonthlyQuick({ from: "2026-08-01", to: "2026-08-18" }),
142
+ ).not.toBeNull();
143
+ expect(filesystem.firstFoldMonthlyQuick({ from: "2026-08-01", to: "2026-08-19" })).toBeNull();
144
+ expect(
145
+ filesystem.firstFoldMonthlyQuick({ from: "2026-09-01", to: "2026-09-01" }),
146
+ ).toMatchObject({
147
+ aggregate: { total_run_count: 0 },
148
+ pages: { task: [], spec: [], prd: [] },
149
+ });
150
+ } finally {
151
+ filesystem.close();
152
+ }
153
+ });
154
+
155
+ it("treats an empty snapshot as an exact empty later-month projection", async () => {
156
+ const root = await createSqliteTestRoot();
157
+ roots.push(root);
158
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
159
+ await writeFile(
160
+ join(root, ".agents", "config", "project.yaml"),
161
+ "zaip:\n enabled: true\n",
162
+ "utf8",
163
+ );
164
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "rebuilt" });
165
+ const filesystem = new SqliteReadModelFileSystem(root);
166
+ try {
167
+ expect(
168
+ filesystem.firstFoldMonthlyQuick({ from: "2099-01-01", to: "2099-01-01" }),
169
+ ).toMatchObject({
170
+ aggregate: { total_run_count: 0 },
171
+ pages: { task: [], spec: [], prd: [] },
172
+ });
173
+ } finally {
174
+ filesystem.close();
175
+ }
176
+ });
177
+
178
+ it("does not serve a schema 59 seed during the one-time SQLite cache migration", async () => {
179
+ const root = await createSqliteTestRoot();
180
+ roots.push(root);
181
+ await mkdir(join(root, ".agents", "records", "tasks"), { recursive: true });
182
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
183
+ await writeFile(
184
+ join(root, ".agents", "config", "project.yaml"),
185
+ "zaip:\n enabled: true\n",
186
+ "utf8",
187
+ );
188
+ await writeFile(join(root, ".agents", "records", "tasks", "task.md"), "# task\n", "utf8");
189
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "rebuilt" });
190
+ const cacheDirectory = join(root, ".local", "dashboard-sqlite", "cache");
191
+ const current = (await readdir(cacheDirectory)).find(
192
+ (entry) => entry.startsWith("v60-") && entry.endsWith(".sqlite"),
193
+ );
194
+ expect(current).toBeDefined();
195
+ const legacy = join(cacheDirectory, current!.replace("v60-", ""));
196
+ await rename(join(cacheDirectory, current!), legacy);
197
+ const oldSeed = new DatabaseSync(legacy);
198
+ oldSeed
199
+ .prepare(
200
+ "UPDATE zaip_aios_activities SET payload_json=json_set(payload_json, '$.value', '59') WHERE payload_type='metadata' AND json_extract(payload_json, '$.key')='schema_version'",
201
+ )
202
+ .run();
203
+ oldSeed.close();
204
+ const priorPath = process.env.ZAIP_SQLITE_DB_PATH;
205
+ const priorSeed = process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED;
206
+ try {
207
+ process.env.ZAIP_SQLITE_DB_PATH = join(root, ".local", "dashboard-sqlite", "migrated.sqlite");
208
+ process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED = "1";
209
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "rebuilt" });
210
+ } finally {
211
+ if (priorPath === undefined) delete process.env.ZAIP_SQLITE_DB_PATH;
212
+ else process.env.ZAIP_SQLITE_DB_PATH = priorPath;
213
+ if (priorSeed === undefined) delete process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED;
214
+ else process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED = priorSeed;
215
+ }
216
+ });
217
+
218
+ it("rateia sobreposição e virada de dia na fato temporal aditiva", () => {
219
+ const overlap = allocateAttributedTokenActiveMs([
220
+ {
221
+ activityRowId: "A",
222
+ startMs: Date.parse("2026-08-10T12:00:00.000Z"),
223
+ endMs: Date.parse("2026-08-10T13:00:00.000Z"),
224
+ },
225
+ {
226
+ activityRowId: "B",
227
+ startMs: Date.parse("2026-08-10T12:30:00.000Z"),
228
+ endMs: Date.parse("2026-08-10T13:30:00.000Z"),
229
+ },
230
+ ]);
231
+ expect(overlap.reduce((sum, fact) => sum + fact.value, 0)).toBe(5_400_000);
232
+ expect(
233
+ overlap
234
+ .filter((fact) => fact.activityRowId === "A")
235
+ .reduce((sum, fact) => sum + fact.value, 0),
236
+ ).toBe(2_700_000);
237
+ expect(
238
+ overlap
239
+ .filter((fact) => fact.activityRowId === "B")
240
+ .reduce((sum, fact) => sum + fact.value, 0),
241
+ ).toBe(2_700_000);
242
+ const midnight = allocateAttributedTokenActiveMs([
243
+ {
244
+ activityRowId: "MID",
245
+ startMs: Date.parse("2026-08-11T02:30:00.000Z"),
246
+ endMs: Date.parse("2026-08-11T03:30:00.000Z"),
247
+ },
248
+ ]);
249
+ expect(midnight).toEqual(
250
+ expect.arrayContaining([
251
+ expect.objectContaining({ day: "2026-08-10", value: 1_800_000 }),
252
+ expect.objectContaining({ day: "2026-08-11", value: 1_800_000 }),
253
+ ]),
254
+ );
255
+ });
256
+ it("projects canonical executions, conversations, changes and decisions without lifecycle aggregates", async () => {
257
+ const root = await createSqliteTestRoot();
258
+ roots.push(root);
259
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
260
+ await mkdir(join(root, ".agents", "records", "executions", "2026", "08", "EXEC-1"), {
261
+ recursive: true,
262
+ });
263
+ await mkdir(join(root, ".agents", "records", "changes", "2026", "08", "CHG-1", "decisions"), {
264
+ recursive: true,
265
+ });
266
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
267
+ await writeFile(
268
+ join(root, ".agents", "records", "executions", "2026", "08", "EXEC-1", "execution.md"),
269
+ `---
270
+ schema_version: 2
271
+ id: EXEC-1
272
+ status: completed
273
+ kind: interaction
274
+ origin:
275
+ type: interaction
276
+ conversation_id: CONV-1
277
+ created_at: 2026-08-10T10:00:00.000Z
278
+ finished_at: 2026-08-10T10:05:00.000Z
279
+ ---
280
+
281
+ # Execution
282
+ `,
283
+ );
284
+ await writeFile(
285
+ join(root, ".agents", "records", "changes", "2026", "08", "CHG-1", "change.md"),
286
+ `---
287
+ schema_version: 2
288
+ id: CHG-1
289
+ title: Canonical change
290
+ status: completed
291
+ risk: standard
292
+ source:
293
+ type: task
294
+ task_id: TASK-1
295
+ requirement_ids: [REQ-1]
296
+ acceptance_ids: [AC-1]
297
+ task_ids: [TASK-1]
298
+ result: Done
299
+ created_at: 2026-08-10T11:00:00.000Z
300
+ ---
301
+
302
+ # Change
303
+ `,
304
+ );
305
+ await writeFile(
306
+ join(root, ".agents", "records", "changes", "2026", "08", "CHG-1", "decisions", "DEC-1.md"),
307
+ `---
308
+ schema_version: 2
309
+ id: DEC-1
310
+ title: Canonical decision
311
+ origin: agent
312
+ scope_type: change
313
+ scope_id: CHG-1
314
+ created_at: 2026-08-10T11:01:00.000Z
315
+ ---
316
+
317
+ # Decision
318
+ `,
319
+ );
320
+
321
+ await rebuildSqliteReadModel(root);
322
+ const filesystem = new SqliteReadModelFileSystem(root);
323
+ expect(filesystem.viewPage("operations_executions", { limit: 100 })).toMatchObject({
324
+ total_count: 1,
325
+ rows: [
326
+ {
327
+ execution_id: "EXEC-1",
328
+ conversation_id: "CONV-1",
329
+ invocation_count: 0,
330
+ duration_ms: null,
331
+ duration_provenance: "unavailable",
332
+ lifecycle_elapsed_ms: 300_000,
333
+ },
334
+ ],
335
+ });
336
+ expect(filesystem.viewPage("operations_conversations", { limit: 100 })).toMatchObject({
337
+ total_count: 1,
338
+ rows: [{ conversation_id: "CONV-1", invocation_count: 0 }],
339
+ });
340
+ expect(filesystem.viewPage("operations_changes", { limit: 100 })).toMatchObject({
341
+ total_count: 1,
342
+ rows: [{ id: "CHG-1", title: "Canonical change", source_type: "task" }],
343
+ });
344
+ expect(filesystem.viewPage("operations_decisions", { limit: 100 })).toMatchObject({
345
+ total_count: 1,
346
+ rows: [{ id: "DEC-1", title: "Canonical decision", scope_id: "CHG-1" }],
347
+ });
348
+ // Unfiltered Operations pagination is served from one immutable
349
+ // generation-local query result, while retaining the public page contract.
350
+ const workListFirst = filesystem.viewPage("operations_work_list", { limit: 1, offset: 0 });
351
+ const workListNext = filesystem.viewPage("operations_work_list", { limit: 1, offset: 1 });
352
+ expect(workListFirst.total_count).toBe(workListNext.total_count);
353
+ expect(workListFirst.has_more).toBe(workListFirst.total_count > 1);
354
+ filesystem.close();
355
+ });
356
+
357
+ it("projects catalog personas for canonical, legacy, and host Zaip Agent identities", async () => {
358
+ const root = await createSqliteTestRoot();
359
+ roots.push(root);
360
+ await mkdir(join(root, ".agents", "config", "agents"), { recursive: true });
361
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
362
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
363
+ await writeFile(join(root, ".agents", "config", "clients.yaml"), "profiles:\n - id: codex\n");
364
+ await writeFile(
365
+ join(root, ".agents", "config", "agents", "catalog.yaml"),
366
+ `agents:
367
+ - id: agent-orchestrator
368
+ codex_name: zaip-agent-orchestrator
369
+ persona: { nome: dave, cargo: product_owner }
370
+ role: orchestrator
371
+ - id: agent-builder
372
+ codex_name: zaip-agent-builder
373
+ persona: { nome: rafa, cargo: tech_lead }
374
+ role: builder
375
+ - id: agent-auditor
376
+ codex_name: zaip-agent-auditor
377
+ persona: { nome: ana, cargo: quality_auditor }
378
+ role: auditor
379
+ - id: agent-reviewer
380
+ codex_name: zaip-agent-reviewer
381
+ persona: { nome: nanda, cargo: principal_engineer }
382
+ role: reviewer
383
+ - id: agent-finalizer
384
+ codex_name: zaip-agent-finalizer
385
+ compatibility:
386
+ aliases:
387
+ - kind: id
388
+ value: agent-task-finalizer
389
+ - kind: id
390
+ value: agent-execution-finalizer
391
+ persona: { nome: gui, cargo: quality_assurance_lead }
392
+ role: execution-finalizer
393
+ `,
394
+ );
395
+ const expected = new Map([
396
+ ["agent-builder", ["rafa", "tech_lead"]],
397
+ ["agent-auditor", ["ana", "quality_auditor"]],
398
+ ["agent-reviewer", ["nanda", "principal_engineer"]],
399
+ ["agent-orchestrator", ["dave", "product_owner"]],
400
+ ["zaip-agent-execution-finalizer", ["gui", "quality_assurance_lead"]],
401
+ ["zaip-agent-task-finalizer", ["gui", "quality_assurance_lead"]],
402
+ ["codex", ["codex", "client_host"]],
403
+ ]);
404
+ const events = [...expected.keys()].flatMap((agent, index) => {
405
+ const runId = `RUN-AGENT-${index}`;
406
+ const started = {
407
+ schema_version: 2,
408
+ event_id: `${runId}:1`,
409
+ run_id: runId,
410
+ sequence: 1,
411
+ occurred_at: "2026-08-18T12:00:00.000Z",
412
+ type: "run.started",
413
+ attribution_status: "attributed",
414
+ phase: "execution",
415
+ run_role: "subagent",
416
+ run_purpose: "execution",
417
+ requires_finalizer: false,
418
+ execution_kind: "delivery",
419
+ execution_eligibility: ["delivery"],
420
+ entrypoint: "task",
421
+ work_type: "new",
422
+ storage_mode: "tracked_sanitized",
423
+ client: "codex",
424
+ agent,
425
+ agent_name: agent,
426
+ provider: "openai",
427
+ model: "gpt-5",
428
+ reasoning_effort: null,
429
+ client_thread_id: runId,
430
+ client_turn_id: runId,
431
+ conversation_id: `CONV-${index}`,
432
+ execution_id: `EXEC-${index}`,
433
+ task_id: `TASK-${index}`,
434
+ spec_id: null,
435
+ prd_id: null,
436
+ change_id: null,
437
+ parent_run_id: "RUN-ROOT",
438
+ continuation_of_run_id: null,
439
+ retry_of_run_id: null,
440
+ agent_invocation_id: `AINV-${index}`,
441
+ root_agent_invocation_id: "AINV-ROOT",
442
+ parent_agent_invocation_id: "AINV-ROOT",
443
+ invocation_kind: "delegated",
444
+ delegation_depth: 1,
445
+ };
446
+ const usage = {
447
+ schema_version: 2,
448
+ event_id: `${runId}:2`,
449
+ run_id: runId,
450
+ sequence: 2,
451
+ occurred_at: "2026-08-18T12:00:01.000Z",
452
+ type: "usage.recorded",
453
+ phase: "execution",
454
+ activity_kind: "model_usage",
455
+ classification_source: "explicit",
456
+ tokens: {
457
+ input: 1,
458
+ cached_input: 0,
459
+ output: 1,
460
+ reasoning: 0,
461
+ total: 2,
462
+ source: "provider_reported",
463
+ },
464
+ cost: { usd: 0.01, brl: 0.05, fx_rate: 5 },
465
+ provider: "openai",
466
+ model: "gpt-5",
467
+ source_segment_key: runId,
468
+ model_call_id: `MCALL-${index}`,
469
+ runtime: { reasoning_effort: null, service_tier: null },
470
+ supersedes_event_id: null,
471
+ duration_ms: null,
472
+ duration_provenance: null,
473
+ };
474
+ const finished = {
475
+ schema_version: 2,
476
+ event_id: `${runId}:3`,
477
+ run_id: runId,
478
+ sequence: 3,
479
+ occurred_at: "2026-08-18T12:01:00.000Z",
480
+ type: "run.finished",
481
+ phase: "execution",
482
+ status: "completed",
483
+ duration_ms: 60_000,
484
+ duration_provenance: "observed",
485
+ };
486
+ return [started, usage, finished];
487
+ });
488
+ await Promise.all(
489
+ [...expected.keys()].map((_, index) =>
490
+ writeFile(
491
+ join(root, ".agents", "records", "runs", "2026", "08", `RUN-AGENT-${index}.jsonl`),
492
+ `${events
493
+ .slice(index * 3, index * 3 + 3)
494
+ .map((event) => JSON.stringify(event))
495
+ .join("\n")}\n`,
496
+ ),
497
+ ),
498
+ );
499
+ await rebuildSqliteReadModel(root);
500
+ const filesystem = new SqliteReadModelFileSystem(root);
501
+ try {
502
+ const rows = filesystem.viewPage("cost_monitor_zaip_agents", { limit: 20 }).rows;
503
+ expect(rows).toHaveLength(expected.size);
504
+ for (const [agent, [persona, job]] of expected) {
505
+ expect(rows.find((row) => row.key === agent)).toMatchObject({ persona, job });
506
+ }
507
+ expect(rows.some((row) => row.persona === null || row.job === null)).toBe(false);
508
+ } finally {
509
+ filesystem.close();
510
+ }
511
+ });
512
+
513
+ it("marks token and cost telemetry unavailable when a canonical day has only Runs without usage", async () => {
514
+ const root = await createSqliteTestRoot();
515
+ roots.push(root);
516
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
517
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
518
+ await mkdir(join(root, ".agents", "config", "agents"), { recursive: true });
519
+ await mkdir(join(root, ".agents", "config", "pricing"), { recursive: true });
520
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
521
+ await writeFile(
522
+ join(root, ".agents", "config", "agents", "catalog.yaml"),
523
+ "agents:\n - id: agent-catalog\n codex_name: agent\n persona: { nome: ana, cargo: quality_engineer }\n role: reviewer\n",
524
+ );
525
+ await writeFile(
526
+ join(root, ".agents", "config", "pricing", "models.json"),
527
+ JSON.stringify({
528
+ schema_version: 1,
529
+ updated_at: "2026-08-04T00:00:00.000Z",
530
+ source: "fixture",
531
+ disclaimer: "fixture",
532
+ models: [
533
+ {
534
+ id: "gpt-a",
535
+ provider: "openai",
536
+ aliases: [],
537
+ input_per_million_usd: 2,
538
+ cached_input_per_million_usd: 0.2,
539
+ output_per_million_usd: 8,
540
+ reasoning_per_million_usd: 8,
541
+ reasoning_billed_separately: false,
542
+ long_context_threshold_tokens: null,
543
+ long_context_input_multiplier: 1,
544
+ long_context_output_multiplier: 1,
545
+ source_label: "fixture-openai",
546
+ source_url: "https://example.test/openai",
547
+ notes: [],
548
+ },
549
+ {
550
+ id: "claude-b",
551
+ provider: "anthropic",
552
+ aliases: [],
553
+ input_per_million_usd: 3,
554
+ cached_input_per_million_usd: 0.3,
555
+ output_per_million_usd: 12,
556
+ reasoning_per_million_usd: null,
557
+ reasoning_billed_separately: false,
558
+ long_context_threshold_tokens: null,
559
+ long_context_input_multiplier: 1,
560
+ long_context_output_multiplier: 1,
561
+ source_label: "fixture-anthropic",
562
+ source_url: "https://example.test/anthropic",
563
+ notes: [],
564
+ },
565
+ ],
566
+ }),
567
+ );
568
+ const started = {
569
+ schema_version: 2,
570
+ event_id: "RUN-MISSING:1",
571
+ run_id: "RUN-MISSING",
572
+ sequence: 1,
573
+ occurred_at: "2026-08-05T12:00:00.000Z",
574
+ type: "run.started",
575
+ attribution_status: "attributed",
576
+ phase: "conversation",
577
+ run_role: "orchestrator",
578
+ run_purpose: "orchestration",
579
+ requires_finalizer: false,
580
+ execution_kind: "interaction",
581
+ execution_eligibility: ["interaction"],
582
+ entrypoint: "conversation",
583
+ work_type: "new",
584
+ storage_mode: "tracked_sanitized",
585
+ client: "codex",
586
+ agent: "root",
587
+ agent_name: "root",
588
+ provider: null,
589
+ model: "unknown",
590
+ reasoning_effort: null,
591
+ client_thread_id: "missing",
592
+ client_turn_id: "missing",
593
+ conversation_id: "CONV-MISSING",
594
+ execution_id: null,
595
+ task_id: null,
596
+ spec_id: null,
597
+ prd_id: null,
598
+ change_id: null,
599
+ parent_run_id: null,
600
+ continuation_of_run_id: null,
601
+ retry_of_run_id: null,
602
+ };
603
+ const finished = {
604
+ schema_version: 2,
605
+ event_id: "RUN-MISSING:2",
606
+ run_id: "RUN-MISSING",
607
+ sequence: 2,
608
+ occurred_at: "2026-08-05T12:01:00.000Z",
609
+ type: "run.finished",
610
+ phase: "conversation",
611
+ status: "completed",
612
+ duration_ms: 60_000,
613
+ duration_provenance: "observed",
614
+ };
615
+ await writeFile(
616
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-MISSING.jsonl"),
617
+ `${JSON.stringify(started)}\n${JSON.stringify(finished)}\n`,
618
+ );
619
+ await rebuildSqliteReadModel(root);
620
+ const filesystem = new SqliteReadModelFileSystem(root);
621
+ expect(filesystem.dailyPeriods({ from: null, to: null })["2026-08-05"]).toMatchObject({
622
+ runs: 1,
623
+ usage: {
624
+ total_tokens: { known_sum: 0, unknown: 1 },
625
+ cost: { known_sum: 0, unknown: 1 },
626
+ },
627
+ });
628
+ filesystem.close();
629
+ });
630
+
631
+ it("releases the real SQLite handle before replacing a generation and reopens it after failure", async () => {
632
+ const root = await createSqliteTestRoot();
633
+ roots.push(root);
634
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
635
+ const configPath = join(root, ".agents", "config", "project.yaml");
636
+ await writeFile(configPath, "zaip:\n enabled: true\n");
637
+ await rebuildSqliteReadModel(root);
638
+ const filesystem = new SqliteReadModelFileSystem(root);
639
+ try {
640
+ expect(await filesystem.readText(".agents/config/project.yaml")).toBe(
641
+ "zaip:\n enabled: true\n",
642
+ );
643
+ expect(filesystem.viewPage("operations_work_list", { limit: 10 }).total_count).toBe(0);
644
+ const pageCache = (
645
+ filesystem as unknown as {
646
+ viewPageResultCache: Map<string, Record<string, unknown>>;
647
+ }
648
+ ).viewPageResultCache;
649
+ expect(pageCache.has('operations_work_list:{"limit":10}')).toBe(true);
650
+ // Change canonical input only after the read-only database has opened:
651
+ // rebuild must replace the generation rather than taking the cache path.
652
+ await writeFile(configPath, "zaip:\n enabled: true\nproject:\n id: refreshed-fixture\n");
653
+ await expect(
654
+ filesystem.rebuildAndRefresh(() => rebuildSqliteReadModel(root)),
655
+ ).resolves.toBeUndefined();
656
+ expect(await filesystem.readText(".agents/config/project.yaml")).toContain(
657
+ "refreshed-fixture",
658
+ );
659
+ expect(pageCache.has('operations_work_list:{"limit":10}')).toBe(false);
660
+ // Exercise the actual public view on the same reopened instance, not
661
+ // merely raw filesystem reads after a Windows-safe replacement.
662
+ expect(() =>
663
+ filesystem.viewPage("cost_monitor_daily", { limit: 1, offset: 0 }),
664
+ ).not.toThrow();
665
+ // A failed rebuild still reopens the existing, complete generation.
666
+ await expect(
667
+ filesystem.rebuildAndRefresh(async () => {
668
+ throw new Error("simulated rebuild failure");
669
+ }),
670
+ ).rejects.toThrow("simulated rebuild failure");
671
+ expect(await filesystem.readText(".agents/config/project.yaml")).toContain(
672
+ "refreshed-fixture",
673
+ );
674
+ } finally {
675
+ filesystem.close();
676
+ }
677
+ });
678
+
679
+ it("keeps Tempo unavailable when token telemetry has no source timing", async () => {
680
+ const root = await createSqliteTestRoot();
681
+ roots.push(root);
682
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
683
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
684
+ await writeFile(
685
+ join(root, ".agents", "config", "project.yaml"),
686
+ "schema_version: 1\nzaip:\n enabled: true\nproject:\n id: sqlite-test\n name: sqlite-test\n timezone: UTC\nstack:\n id: zaip-aios-stack\n overrides: {}\nclients:\n codex: true\n claude: true\ntracking:\n enabled: true\n currency: BRL\n storage_mode: tracked_sanitized\n",
687
+ "utf8",
688
+ );
689
+ const started = {
690
+ schema_version: 2,
691
+ event_id: "RUN-1:1",
692
+ run_id: "RUN-1",
693
+ sequence: 1,
694
+ occurred_at: "2026-08-01T12:00:00.000Z",
695
+ type: "run.started",
696
+ attribution_status: "attributed",
697
+ phase: "execution",
698
+ run_role: "orchestrator",
699
+ run_purpose: "execution",
700
+ requires_finalizer: false,
701
+ execution_kind: "delivery",
702
+ execution_eligibility: ["delivery"],
703
+ entrypoint: "task",
704
+ work_type: "new",
705
+ storage_mode: "tracked_sanitized",
706
+ client: "codex",
707
+ agent: "agent",
708
+ agent_name: "agent",
709
+ provider: "openai",
710
+ model: "gpt-5",
711
+ reasoning_effort: null,
712
+ client_thread_id: "thread",
713
+ client_turn_id: "turn",
714
+ conversation_id: "CONV-1",
715
+ execution_id: "EXEC-1",
716
+ task_id: "TASK-1",
717
+ spec_id: null,
718
+ prd_id: null,
719
+ change_id: null,
720
+ parent_run_id: null,
721
+ continuation_of_run_id: null,
722
+ retry_of_run_id: null,
723
+ agent_invocation_id: "AINV-1",
724
+ root_agent_invocation_id: "AINV-1",
725
+ parent_agent_invocation_id: null,
726
+ invocation_kind: "orchestrator",
727
+ delegation_depth: 0,
728
+ };
729
+ const usage = {
730
+ schema_version: 2,
731
+ event_id: "RUN-1:2",
732
+ run_id: "RUN-1",
733
+ sequence: 2,
734
+ occurred_at: "2026-08-01T12:00:00.000Z",
735
+ type: "usage.recorded",
736
+ phase: "execution",
737
+ activity_kind: "model_usage",
738
+ classification_source: "explicit",
739
+ tokens: {
740
+ input: 0,
741
+ cached_input: 0,
742
+ output: 4_000_000,
743
+ reasoning: 0,
744
+ total: 4_000_000,
745
+ source: "provider_reported",
746
+ },
747
+ cost: { usd: null, brl: null, fx_rate: null },
748
+ provider: "openai",
749
+ model: "gpt-5",
750
+ source_segment_key: "segment",
751
+ model_call_id: "MCALL-1",
752
+ runtime: { reasoning_effort: null, service_tier: null },
753
+ supersedes_event_id: null,
754
+ duration_ms: null,
755
+ duration_provenance: null,
756
+ };
757
+ const finished = {
758
+ schema_version: 2,
759
+ event_id: "RUN-1:3",
760
+ run_id: "RUN-1",
761
+ sequence: 3,
762
+ occurred_at: "2026-08-01T12:00:00.000Z",
763
+ type: "run.finished",
764
+ phase: "execution",
765
+ status: "completed",
766
+ duration_ms: null,
767
+ duration_provenance: "unavailable",
768
+ };
769
+ await writeFile(
770
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-1.jsonl"),
771
+ `${[started, usage, finished].map((record) => JSON.stringify(record)).join("\n")}\n`,
772
+ "utf8",
773
+ );
774
+ await rebuildSqliteReadModel(root);
775
+ const database = new DatabaseSync(resolve(root, ".local/dashboard-sqlite/read-model.sqlite"), {
776
+ readOnly: true,
777
+ });
778
+ expect(
779
+ database.prepare("SELECT duration_ms, duration_provenance FROM zaip_aios_activities").get(),
780
+ ).toEqual({ duration_ms: null, duration_provenance: null });
781
+ database.close();
782
+ const filesystem = new SqliteReadModelFileSystem(root);
783
+ const day = filesystem.viewPage("cost_monitor_daily", {
784
+ from: "2026-08-01",
785
+ to: "2026-08-01",
786
+ limit: 20,
787
+ offset: 0,
788
+ }).rows[0];
789
+ if (day === undefined) throw new Error("Expected the daily Cost Monitor row");
790
+ // Token telemetry without a complete source interval remains unavailable;
791
+ // a token estimate must never populate the factual Tempo column.
792
+ expect(day).toMatchObject({
793
+ total_tokens: 4_000_000,
794
+ total_duration: null,
795
+ total_run_count: 1,
796
+ duration_known: 0,
797
+ duration_unknown: 1,
798
+ });
799
+ expect(Object.keys(day).sort()).toEqual([
800
+ "duration_known",
801
+ "duration_unknown",
802
+ "evidence_count",
803
+ "key",
804
+ "label",
805
+ "prd_count",
806
+ "spec_count",
807
+ "task_count",
808
+ "total_cost_brl",
809
+ "total_cost_usd",
810
+ "total_duration",
811
+ "total_run_count",
812
+ "total_tokens",
813
+ "validation_count",
814
+ ]);
815
+ expect(
816
+ filesystem.costMonitorFactualDurationByDay({ from: null, to: null }).get("2026-08-01"),
817
+ ).toEqual({
818
+ known_sum: 0,
819
+ eligible_runs: 1,
820
+ known_runs: 0,
821
+ });
822
+ filesystem.close();
823
+ });
824
+
825
+ it("serves canonical model pricing with recorded-period and current FX", async () => {
826
+ const root = await createSqliteTestRoot();
827
+ roots.push(root);
828
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
829
+ await mkdir(join(root, ".agents", "config", "agents"), { recursive: true });
830
+ await mkdir(join(root, ".agents", "config", "pricing", "fx", "2026", "08", "04"), {
831
+ recursive: true,
832
+ });
833
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
834
+ await writeFile(
835
+ join(root, ".agents", "config", "agents", "catalog.yaml"),
836
+ "agents:\n - id: agent-catalog\n codex_name: agent\n persona: { nome: ana, cargo: quality_engineer }\n role: reviewer\n",
837
+ );
838
+ await writeFile(
839
+ join(root, ".agents", "config", "pricing", "models.json"),
840
+ JSON.stringify({
841
+ schema_version: 1,
842
+ updated_at: "2026-08-04T00:00:00.000Z",
843
+ source: "fixture",
844
+ disclaimer: "fixture",
845
+ models: [
846
+ {
847
+ id: "gpt-a",
848
+ provider: "openai",
849
+ aliases: [],
850
+ input_per_million_usd: 2,
851
+ cached_input_per_million_usd: 0.2,
852
+ output_per_million_usd: 8,
853
+ reasoning_per_million_usd: 8,
854
+ reasoning_billed_separately: false,
855
+ long_context_threshold_tokens: null,
856
+ long_context_input_multiplier: 1,
857
+ long_context_output_multiplier: 1,
858
+ source_label: "fixture-openai",
859
+ source_url: "https://example.test/openai",
860
+ notes: [],
861
+ },
862
+ ],
863
+ }),
864
+ );
865
+ await writeFile(
866
+ join(root, ".agents", "config", "pricing", "fx", "2026", "08", "04", "usd-brl.json"),
867
+ JSON.stringify({
868
+ schema_version: 1,
869
+ base: "USD",
870
+ quote: "BRL",
871
+ rate: 5.5,
872
+ as_of: "2026-08-04T00:00:00.000Z",
873
+ source_label: "fixture",
874
+ source_url: "https://example.test/fx",
875
+ }),
876
+ );
877
+ const started = {
878
+ schema_version: 2,
879
+ event_id: "RUN-PRICED:1",
880
+ run_id: "RUN-PRICED",
881
+ sequence: 1,
882
+ occurred_at: "2026-08-08T12:00:00.000Z",
883
+ type: "run.started",
884
+ attribution_status: "attributed",
885
+ phase: "execution",
886
+ run_role: "orchestrator",
887
+ run_purpose: "execution",
888
+ requires_finalizer: false,
889
+ execution_kind: "delivery",
890
+ execution_eligibility: ["delivery"],
891
+ entrypoint: "task",
892
+ work_type: "new",
893
+ storage_mode: "tracked_sanitized",
894
+ client: "codex",
895
+ agent: "agent",
896
+ agent_name: "agent",
897
+ provider: "openai",
898
+ model: "gpt-a",
899
+ reasoning_effort: null,
900
+ client_thread_id: "priced",
901
+ client_turn_id: "priced",
902
+ conversation_id: "CONV-PRICED",
903
+ execution_id: "EXEC-PRICED",
904
+ task_id: "TASK-PRICED",
905
+ spec_id: null,
906
+ prd_id: null,
907
+ change_id: null,
908
+ parent_run_id: null,
909
+ continuation_of_run_id: null,
910
+ retry_of_run_id: null,
911
+ agent_invocation_id: "AINV-PRICED",
912
+ root_agent_invocation_id: "AINV-PRICED",
913
+ parent_agent_invocation_id: null,
914
+ invocation_kind: "orchestrator",
915
+ delegation_depth: 0,
916
+ };
917
+ const usage = {
918
+ schema_version: 2,
919
+ event_id: "RUN-PRICED:2",
920
+ run_id: "RUN-PRICED",
921
+ sequence: 2,
922
+ occurred_at: "2026-08-08T12:00:01.000Z",
923
+ type: "usage.recorded",
924
+ phase: "execution",
925
+ activity_kind: "model_usage",
926
+ classification_source: "explicit",
927
+ tokens: {
928
+ input: 5,
929
+ cached_input: 0,
930
+ output: 0,
931
+ reasoning: 0,
932
+ total: 5,
933
+ source: "provider_reported",
934
+ },
935
+ cost: { usd: 0.5, brl: 2.5, fx_rate: 5 },
936
+ provider: "openai",
937
+ model: "gpt-a",
938
+ source_segment_key: "openai-gpt-a",
939
+ model_call_id: "MCALL-PRICED",
940
+ runtime: { reasoning_effort: null, service_tier: null },
941
+ supersedes_event_id: null,
942
+ duration_ms: null,
943
+ duration_provenance: null,
944
+ };
945
+ const finished = {
946
+ schema_version: 2,
947
+ event_id: "RUN-PRICED:3",
948
+ run_id: "RUN-PRICED",
949
+ sequence: 3,
950
+ occurred_at: "2026-08-08T12:01:00.000Z",
951
+ type: "run.finished",
952
+ phase: "execution",
953
+ status: "completed",
954
+ duration_ms: 60_000,
955
+ duration_provenance: "observed",
956
+ };
957
+ await writeFile(
958
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-PRICED.jsonl"),
959
+ `${[started, usage, finished].map((row) => JSON.stringify(row)).join("\n")}\n`,
960
+ );
961
+ await rebuildSqliteReadModel(root);
962
+ const filesystem = new SqliteReadModelFileSystem(root);
963
+ try {
964
+ expect(filesystem.viewPage("cost_monitor_agent_invocations", {}).rows[0]).toMatchObject({
965
+ input_rate_usd_per_million: 2,
966
+ cached_input_rate_usd_per_million: 0.2,
967
+ output_rate_usd_per_million: 8,
968
+ calculated_cost_usd: 0.00001,
969
+ recorded_cost_usd: 0.5,
970
+ recorded_fx_rate: 5,
971
+ recorded_brl: 2.5,
972
+ current_fx_rate: 5.5,
973
+ });
974
+ } finally {
975
+ filesystem.close();
976
+ }
977
+ });
978
+
979
+ it("keeps orphan telemetry out of the Kanban without dropping its Cost Monitor fact", async () => {
980
+ const root = await createSqliteTestRoot();
981
+ roots.push(root);
982
+ await mkdir(join(root, ".agents", "records", "tasks", "2026", "08"), { recursive: true });
983
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
984
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
985
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
986
+ await writeFile(
987
+ join(root, ".agents", "records", "tasks", "2026", "08", "TASK-CANONICAL.md"),
988
+ "---\nid: TASK-CANONICAL\ntitle: Titulo semantico canonico\nstatus: ready\nrisk: standard\ncreated_at: 2026-08-08T12:00:00.000Z\n---\n# Titulo semantico canonico\n",
989
+ );
990
+ const run = (id: string, taskId: string, tokens: number) => [
991
+ {
992
+ schema_version: 2,
993
+ event_id: `${id}:1`,
994
+ run_id: id,
995
+ sequence: 1,
996
+ occurred_at: "2026-08-08T12:00:00.000Z",
997
+ type: "run.started",
998
+ attribution_status: "attributed",
999
+ phase: "execution",
1000
+ run_role: "orchestrator",
1001
+ run_purpose: "execution",
1002
+ requires_finalizer: false,
1003
+ execution_kind: "delivery",
1004
+ execution_eligibility: ["delivery"],
1005
+ entrypoint: "task",
1006
+ work_type: "new",
1007
+ storage_mode: "tracked_sanitized",
1008
+ client: "codex",
1009
+ agent: "agent",
1010
+ agent_name: "agent",
1011
+ provider: "openai",
1012
+ model: "gpt-a",
1013
+ reasoning_effort: null,
1014
+ client_thread_id: id,
1015
+ client_turn_id: id,
1016
+ conversation_id: `CONV-${id}`,
1017
+ execution_id: `EXEC-${id}`,
1018
+ task_id: taskId,
1019
+ spec_id: null,
1020
+ prd_id: null,
1021
+ change_id: null,
1022
+ parent_run_id: null,
1023
+ continuation_of_run_id: null,
1024
+ retry_of_run_id: null,
1025
+ agent_invocation_id: `AINV-${id}`,
1026
+ root_agent_invocation_id: `AINV-${id}`,
1027
+ parent_agent_invocation_id: null,
1028
+ invocation_kind: "orchestrator",
1029
+ delegation_depth: 0,
1030
+ },
1031
+ {
1032
+ schema_version: 2,
1033
+ event_id: `${id}:2`,
1034
+ run_id: id,
1035
+ sequence: 2,
1036
+ occurred_at: "2026-08-08T12:00:30.000Z",
1037
+ type: "usage.recorded",
1038
+ phase: "execution",
1039
+ activity_kind: "model_usage",
1040
+ classification_source: "explicit",
1041
+ tokens: {
1042
+ input: tokens,
1043
+ cached_input: 0,
1044
+ output: 0,
1045
+ reasoning: 0,
1046
+ total: tokens,
1047
+ source: "provider_reported",
1048
+ },
1049
+ cost: { usd: tokens / 100, brl: tokens / 20, fx_rate: 5 },
1050
+ provider: "openai",
1051
+ model: "gpt-a",
1052
+ source_segment_key: id,
1053
+ model_call_id: `MCALL-${id}`,
1054
+ runtime: { reasoning_effort: null, service_tier: null },
1055
+ supersedes_event_id: null,
1056
+ source_started_at: "2026-08-08T12:00:00.000Z",
1057
+ source_finished_at: "2026-08-08T12:01:00.000Z",
1058
+ duration_ms: null,
1059
+ duration_provenance: null,
1060
+ },
1061
+ {
1062
+ schema_version: 2,
1063
+ event_id: `${id}:3`,
1064
+ run_id: id,
1065
+ sequence: 3,
1066
+ occurred_at: "2026-08-08T12:01:00.000Z",
1067
+ type: "run.finished",
1068
+ phase: "execution",
1069
+ status: "completed",
1070
+ duration_ms: 60_000,
1071
+ duration_provenance: "observed",
1072
+ },
1073
+ ];
1074
+ await writeFile(
1075
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-CANONICAL.jsonl"),
1076
+ `${run("RUN-CANONICAL", "TASK-CANONICAL", 7)
1077
+ .map((row) => JSON.stringify(row))
1078
+ .join("\n")}\n`,
1079
+ );
1080
+ await writeFile(
1081
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-ORPHAN.jsonl"),
1082
+ `${run("RUN-ORPHAN", "TASK-ORPHAN-REFERENCE", 11)
1083
+ .map((row) => JSON.stringify(row))
1084
+ .join("\n")}\n`,
1085
+ );
1086
+
1087
+ await rebuildSqliteReadModel(root);
1088
+ const snapshot = new DatabaseSync(resolve(root, ".local/dashboard-sqlite/read-model.sqlite"), {
1089
+ readOnly: true,
1090
+ });
1091
+ expect(
1092
+ snapshot
1093
+ .prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key='schema_version'")
1094
+ .get(),
1095
+ ).toEqual({ value: "60" });
1096
+ snapshot.close();
1097
+ const filesystem = new SqliteReadModelFileSystem(root);
1098
+ try {
1099
+ expect(filesystem.viewPage("operations_work_kanban", { limit: 20 }).rows).toEqual([
1100
+ expect.objectContaining({ id: "TASK-CANONICAL", title: "Titulo semantico canonico" }),
1101
+ ]);
1102
+ // The List export is sourced from this SQLite view, so its known facts
1103
+ // must survive the public entity union rather than be left as blank XLSX
1104
+ // columns by a mapper that only receives identity fields.
1105
+ expect(filesystem.viewPage("operations_work_list", { limit: 20 }).rows).toContainEqual(
1106
+ expect.objectContaining({
1107
+ id: "TASK-CANONICAL",
1108
+ invocation_count: 1,
1109
+ total_duration: 30_000,
1110
+ total_tokens: 7,
1111
+ total_cost_brl: 0.35,
1112
+ }),
1113
+ );
1114
+ const costTasks = filesystem.viewPage("cost_monitor_tasks", { limit: 20 }).rows;
1115
+ expect(costTasks).toContainEqual(
1116
+ expect.objectContaining({
1117
+ id: "TASK-ORPHAN-REFERENCE",
1118
+ total_tokens: 11,
1119
+ total_cost_brl: 0.55,
1120
+ }),
1121
+ );
1122
+ } finally {
1123
+ filesystem.close();
1124
+ }
1125
+ });
1126
+
1127
+ it("keeps a multi-provider and multi-model Run visible to both filtered financial and duration facts", async () => {
1128
+ const root = await createSqliteTestRoot();
1129
+ roots.push(root);
1130
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
1131
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
1132
+ await mkdir(join(root, ".agents", "config", "agents"), { recursive: true });
1133
+ await mkdir(join(root, ".agents", "config", "pricing"), { recursive: true });
1134
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
1135
+ await writeFile(
1136
+ join(root, ".agents", "config", "agents", "catalog.yaml"),
1137
+ "agents:\n - id: agent-catalog\n codex_name: agent\n persona: { nome: ana, cargo: quality_engineer }\n role: reviewer\n",
1138
+ );
1139
+ await writeFile(
1140
+ join(root, ".agents", "config", "pricing", "models.json"),
1141
+ JSON.stringify({
1142
+ schema_version: 1,
1143
+ updated_at: "2026-08-04T00:00:00.000Z",
1144
+ source: "fixture",
1145
+ disclaimer: "fixture",
1146
+ models: [
1147
+ {
1148
+ id: "gpt-a",
1149
+ provider: "openai",
1150
+ aliases: [],
1151
+ input_per_million_usd: 2,
1152
+ cached_input_per_million_usd: 0.2,
1153
+ output_per_million_usd: 8,
1154
+ reasoning_per_million_usd: 8,
1155
+ reasoning_billed_separately: false,
1156
+ long_context_threshold_tokens: null,
1157
+ long_context_input_multiplier: 1,
1158
+ long_context_output_multiplier: 1,
1159
+ source_label: "fixture-openai",
1160
+ source_url: "https://example.test/openai",
1161
+ notes: [],
1162
+ },
1163
+ {
1164
+ id: "claude-b",
1165
+ provider: "anthropic",
1166
+ aliases: [],
1167
+ input_per_million_usd: 3,
1168
+ cached_input_per_million_usd: 0.3,
1169
+ output_per_million_usd: 12,
1170
+ reasoning_per_million_usd: null,
1171
+ reasoning_billed_separately: false,
1172
+ long_context_threshold_tokens: null,
1173
+ long_context_input_multiplier: 1,
1174
+ long_context_output_multiplier: 1,
1175
+ source_label: "fixture-anthropic",
1176
+ source_url: "https://example.test/anthropic",
1177
+ notes: [],
1178
+ },
1179
+ ],
1180
+ }),
1181
+ );
1182
+ const started = {
1183
+ schema_version: 2,
1184
+ event_id: "RUN-MULTI:1",
1185
+ run_id: "RUN-MULTI",
1186
+ sequence: 1,
1187
+ occurred_at: "2026-08-04T12:00:00.000Z",
1188
+ type: "run.started",
1189
+ attribution_status: "attributed",
1190
+ phase: "execution",
1191
+ run_role: "orchestrator",
1192
+ run_purpose: "execution",
1193
+ requires_finalizer: false,
1194
+ execution_kind: "delivery",
1195
+ execution_eligibility: ["delivery"],
1196
+ entrypoint: "task",
1197
+ work_type: "new",
1198
+ storage_mode: "tracked_sanitized",
1199
+ client: "codex",
1200
+ agent: "agent",
1201
+ agent_name: "agent",
1202
+ provider: "mixed",
1203
+ model: "mixed",
1204
+ reasoning_effort: null,
1205
+ client_thread_id: "multi",
1206
+ client_turn_id: "multi",
1207
+ conversation_id: "CONV-MULTI",
1208
+ execution_id: "EXEC-MULTI",
1209
+ task_id: "TASK-MULTI",
1210
+ spec_id: null,
1211
+ prd_id: null,
1212
+ change_id: null,
1213
+ parent_run_id: null,
1214
+ continuation_of_run_id: null,
1215
+ retry_of_run_id: null,
1216
+ agent_invocation_id: "AINV-MULTI",
1217
+ root_agent_invocation_id: "AINV-MULTI",
1218
+ parent_agent_invocation_id: null,
1219
+ invocation_kind: "orchestrator",
1220
+ delegation_depth: 0,
1221
+ };
1222
+ const usage = (
1223
+ sequence: number,
1224
+ provider: string,
1225
+ model: string,
1226
+ tokens: number,
1227
+ brl: number,
1228
+ ) => ({
1229
+ schema_version: 2,
1230
+ event_id: `RUN-MULTI:${sequence}`,
1231
+ run_id: "RUN-MULTI",
1232
+ sequence,
1233
+ occurred_at: "2026-08-04T12:00:01.000Z",
1234
+ type: "usage.recorded",
1235
+ phase: "execution",
1236
+ activity_kind: "model_usage",
1237
+ classification_source: "explicit",
1238
+ tokens: {
1239
+ input: tokens,
1240
+ cached_input: 0,
1241
+ output: 0,
1242
+ reasoning: 0,
1243
+ total: tokens,
1244
+ source: "provider_reported",
1245
+ },
1246
+ cost: { usd: brl / 5, brl, fx_rate: 5 },
1247
+ provider,
1248
+ model,
1249
+ source_segment_key: `${provider}-${model}`,
1250
+ model_call_id: `MCALL-${sequence}`,
1251
+ runtime: { reasoning_effort: null, service_tier: null },
1252
+ supersedes_event_id: null,
1253
+ source_started_at: "2026-08-04T12:00:00.000Z",
1254
+ source_finished_at: "2026-08-04T12:01:00.000Z",
1255
+ duration_ms: null,
1256
+ duration_provenance: null,
1257
+ });
1258
+ const finished = {
1259
+ schema_version: 2,
1260
+ event_id: "RUN-MULTI:5",
1261
+ run_id: "RUN-MULTI",
1262
+ sequence: 5,
1263
+ occurred_at: "2026-08-04T12:01:00.000Z",
1264
+ type: "run.finished",
1265
+ phase: "execution",
1266
+ status: "completed",
1267
+ duration_ms: 60_000,
1268
+ duration_provenance: "observed",
1269
+ };
1270
+ const mixedNullSegment = {
1271
+ ...usage(4, "openai", "gpt-a", 0, 0),
1272
+ event_id: "RUN-MULTI:4",
1273
+ provider: null,
1274
+ model: null,
1275
+ source_segment_key: "mixed-null",
1276
+ model_call_id: "MCALL-MIXED-NULL",
1277
+ };
1278
+ await writeFile(
1279
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-MULTI.jsonl"),
1280
+ `${[started, usage(2, "openai", "gpt-a", 10, 5), usage(3, "anthropic", "claude-b", 20, 10), mixedNullSegment, finished].map((row) => JSON.stringify(row)).join("\n")}\n`,
1281
+ );
1282
+ const unavailableStarted = {
1283
+ ...started,
1284
+ event_id: "RUN-UNAVAILABLE:1",
1285
+ run_id: "RUN-UNAVAILABLE",
1286
+ occurred_at: "2026-08-04T14:00:00.000Z",
1287
+ client_turn_id: "unavailable",
1288
+ conversation_id: "CONV-UNAVAILABLE",
1289
+ execution_id: "EXEC-UNAVAILABLE",
1290
+ task_id: "TASK-UNAVAILABLE",
1291
+ agent_invocation_id: "AINV-UNAVAILABLE",
1292
+ root_agent_invocation_id: "AINV-UNAVAILABLE",
1293
+ provider: "unavailable",
1294
+ model: "unavailable",
1295
+ };
1296
+ const unavailableFinished = {
1297
+ ...finished,
1298
+ event_id: "RUN-UNAVAILABLE:2",
1299
+ run_id: "RUN-UNAVAILABLE",
1300
+ sequence: 3,
1301
+ occurred_at: "2026-08-04T14:01:00.000Z",
1302
+ };
1303
+ const unavailableUsage = {
1304
+ ...usage(2, "openai", "gpt-a", 0, 0),
1305
+ event_id: "RUN-UNAVAILABLE:usage",
1306
+ run_id: "RUN-UNAVAILABLE",
1307
+ sequence: 2,
1308
+ occurred_at: "2026-08-04T14:00:01.000Z",
1309
+ provider: null,
1310
+ model: null,
1311
+ source_started_at: "2026-08-04T14:00:00.000Z",
1312
+ source_finished_at: "2026-08-04T14:01:00.000Z",
1313
+ source_segment_key: "unknown-segment",
1314
+ model_call_id: "MCALL-UNAVAILABLE",
1315
+ };
1316
+ await writeFile(
1317
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-UNAVAILABLE.jsonl"),
1318
+ `${[unavailableStarted, unavailableUsage, unavailableFinished].map((row) => JSON.stringify(row)).join("\n")}\n`,
1319
+ );
1320
+ // It is a canonical activity but not an Agent Invocation. Cost Monitor
1321
+ // must not blend this population into its token/cost/session rate.
1322
+ const {
1323
+ agent_invocation_id: _invocation,
1324
+ root_agent_invocation_id: _rootInvocation,
1325
+ parent_agent_invocation_id: _parentInvocation,
1326
+ ...unassistedBase
1327
+ } = started;
1328
+ const unassistedStarted = {
1329
+ ...unassistedBase,
1330
+ event_id: "RUN-UNASSISTED:1",
1331
+ run_id: "RUN-UNASSISTED",
1332
+ occurred_at: "2026-08-04T16:00:00.000Z",
1333
+ client_turn_id: "unassisted",
1334
+ conversation_id: "CONV-UNASSISTED",
1335
+ execution_id: "EXEC-MULTI",
1336
+ task_id: "TASK-MULTI",
1337
+ };
1338
+ const unassistedUsage = {
1339
+ ...usage(2, "openai", "gpt-a", 1_000, 500),
1340
+ event_id: "RUN-UNASSISTED:2",
1341
+ run_id: "RUN-UNASSISTED",
1342
+ occurred_at: "2026-08-04T16:00:01.000Z",
1343
+ source_started_at: "2026-08-04T16:00:00.000Z",
1344
+ source_finished_at: "2026-08-04T16:01:00.000Z",
1345
+ source_segment_key: "unassisted-segment",
1346
+ };
1347
+ await writeFile(
1348
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-UNASSISTED.jsonl"),
1349
+ `${[unassistedStarted, unassistedUsage, { ...finished, event_id: "RUN-UNASSISTED:3", run_id: "RUN-UNASSISTED", sequence: 3 }].map((row) => JSON.stringify(row)).join("\n")}\n`,
1350
+ );
1351
+ await rebuildSqliteReadModel(root);
1352
+ const aggregateDatabase = new DatabaseSync(
1353
+ resolve(root, ".local/dashboard-sqlite/read-model.sqlite"),
1354
+ {
1355
+ readOnly: true,
1356
+ },
1357
+ );
1358
+ try {
1359
+ const aggregates = aggregateDatabase
1360
+ .prepare(`SELECT
1361
+ json_extract(payload_json, '$.dimension') AS dimension,
1362
+ json_extract(payload_json, '$.key') AS key,
1363
+ json_extract(payload_json, '$.schema_version') AS schema_version,
1364
+ json_extract(payload_json, '$.total_cost_brl') AS total_cost_brl
1365
+ FROM zaip_aios_activities
1366
+ WHERE payload_type = 'cost_dimension_aggregate_v1'`)
1367
+ .all();
1368
+ expect(aggregates).toEqual(
1369
+ expect.arrayContaining([
1370
+ expect.objectContaining({
1371
+ dimension: "provider",
1372
+ key: "openai",
1373
+ schema_version: 1,
1374
+ total_cost_brl: 505,
1375
+ }),
1376
+ expect.objectContaining({
1377
+ dimension: "model",
1378
+ key: "gpt-a",
1379
+ schema_version: 1,
1380
+ total_cost_brl: 505,
1381
+ }),
1382
+ ]),
1383
+ );
1384
+ expect(aggregates.map((row) => row.key)).toEqual(expect.arrayContaining(["unavailable"]));
1385
+ } finally {
1386
+ aggregateDatabase.close();
1387
+ }
1388
+ const filesystem = new SqliteReadModelFileSystem(root);
1389
+ expect(
1390
+ filesystem.viewPage("cost_monitor_daily", { from: "2026-08-04", to: "2026-08-04" }).rows[0],
1391
+ ).toMatchObject({ total_tokens: 1_030, total_cost_brl: 515 });
1392
+ // The public overview view backs /api/v2/sqlite-views. It must not leak
1393
+ // Cost Monitor retains all measurable financial activities, including
1394
+ // historical/unassisted telemetry without an Agent Invocation id.
1395
+ const publicOverview = filesystem.viewPage("cost_monitor_overview_view", {});
1396
+ expect(publicOverview.rows).toHaveLength(3);
1397
+ expect(publicOverview.rows).toEqual(
1398
+ expect.arrayContaining([
1399
+ expect.objectContaining({
1400
+ period_kind: "day",
1401
+ total_run_count: 3,
1402
+ invocation_count: 3,
1403
+ total_tokens: 1_030,
1404
+ total_cost_brl: 515,
1405
+ }),
1406
+ expect.objectContaining({
1407
+ period_kind: "month",
1408
+ total_run_count: 3,
1409
+ invocation_count: 3,
1410
+ total_tokens: 1_030,
1411
+ total_cost_brl: 515,
1412
+ }),
1413
+ expect.objectContaining({
1414
+ period_kind: "year",
1415
+ total_run_count: 3,
1416
+ invocation_count: 3,
1417
+ total_tokens: 1_030,
1418
+ total_cost_brl: 515,
1419
+ }),
1420
+ ]),
1421
+ );
1422
+ const allAgentGroups = filesystem.viewPage("cost_monitor_zaip_agents", {
1423
+ limit: 20,
1424
+ offset: 0,
1425
+ });
1426
+ const allCostTotals = publicOverview.rows.find((row) => row.period_kind === "day");
1427
+ if (allCostTotals === undefined) throw new Error("Expected the daily Cost Monitor overview");
1428
+ expect(
1429
+ allAgentGroups.rows.reduce((total, row) => total + Number(row.invocation_count ?? 0), 0),
1430
+ ).toBe(allCostTotals.total_run_count);
1431
+ expect(
1432
+ allAgentGroups.rows.reduce((total, row) => total + Number(row.total_tokens ?? 0), 0),
1433
+ ).toBe(allCostTotals?.total_tokens);
1434
+ expect(
1435
+ allAgentGroups.rows.reduce((total, row) => total + Number(row.total_cost_brl ?? 0), 0),
1436
+ ).toBe(allCostTotals?.total_cost_brl);
1437
+ expect(
1438
+ allAgentGroups.rows.reduce((total, row) => total + Number(row.total_cost_usd ?? 0), 0),
1439
+ ).toBe(allCostTotals.total_cost_usd);
1440
+ // Shared Cost Tempo is factual at the activity grain. It remains available
1441
+ // in the agent-table total even where a multi-dimension invocation cannot
1442
+ // be assigned to an individual Agent row.
1443
+ expect(allCostTotals.cost_monitor_total_duration).toBe(120_000);
1444
+ expect(allAgentGroups.totals).toMatchObject({
1445
+ invocation_count: allCostTotals.total_run_count,
1446
+ total_duration: 120_000,
1447
+ total_tokens: allCostTotals.total_tokens,
1448
+ total_cost_usd: allCostTotals.total_cost_usd,
1449
+ total_cost_brl: allCostTotals.total_cost_brl,
1450
+ });
1451
+ for (const [providers, models] of [
1452
+ [["openai"], ["gpt-a"]],
1453
+ [["anthropic"], ["claude-b"]],
1454
+ ] as const) {
1455
+ const rows = filesystem.invocationCostRows({ from: null, to: null, providers, models });
1456
+ expect(rows).toHaveLength(1);
1457
+ // Membership is segment-based; once the invocation matches, its
1458
+ // canonical invocation totals remain intact rather than being split.
1459
+ expect(rows[0]).toMatchObject({ total_tokens: 30, cost_brl: 15 });
1460
+ expect([
1461
+ ...filesystem.executionDurationByDay({ from: null, to: null, providers, models }).values(),
1462
+ ]).toEqual([60_000]);
1463
+ expect([
1464
+ ...filesystem.canonicalRunCountByDay({ from: null, to: null, providers, models }).values(),
1465
+ ]).toEqual([1]);
1466
+ for (const view of [
1467
+ "cost_monitor_daily",
1468
+ "cost_monitor_monthly",
1469
+ "cost_monitor_annual",
1470
+ ] as const) {
1471
+ const page = filesystem.viewPage(view, {
1472
+ provider: providers[0],
1473
+ model: models[0],
1474
+ limit: 20,
1475
+ offset: 0,
1476
+ });
1477
+ expect(page.rows[0]).toMatchObject({
1478
+ total_run_count: 1,
1479
+ total_tokens: 30,
1480
+ total_cost_brl: 15,
1481
+ });
1482
+ }
1483
+ const agents = filesystem.viewPage("cost_monitor_zaip_agents", {
1484
+ provider: providers[0],
1485
+ model: models[0],
1486
+ query: "agent",
1487
+ });
1488
+ const financialRows = filesystem.costMonitorFinancialActivityRows({
1489
+ from: null,
1490
+ to: null,
1491
+ providers,
1492
+ models,
1493
+ });
1494
+ const financialTotals = financialRows.reduce<{
1495
+ invocation_count: number;
1496
+ total_tokens: number;
1497
+ total_cost_brl: number;
1498
+ }>(
1499
+ (total, row) => ({
1500
+ invocation_count: total.invocation_count + 1,
1501
+ total_tokens: total.total_tokens + Number(row.total_tokens ?? 0),
1502
+ total_cost_brl: total.total_cost_brl + Number(row.cost_brl ?? 0),
1503
+ }),
1504
+ { invocation_count: 0, total_tokens: 0, total_cost_brl: 0 },
1505
+ );
1506
+ expect(agents.rows[0]).toMatchObject({
1507
+ key: "agent",
1508
+ persona: "ana",
1509
+ job: "quality_engineer",
1510
+ task_count: 1,
1511
+ ...financialTotals,
1512
+ });
1513
+ expect(agents.totals).toMatchObject(financialTotals);
1514
+ }
1515
+ const crossed = { from: null, to: null, providers: ["openai"], models: ["claude-b"] };
1516
+ expect(filesystem.invocationCostRows(crossed)).toHaveLength(0);
1517
+ expect([...filesystem.executionDurationByDay(crossed).values()]).toEqual([]);
1518
+ expect([...filesystem.canonicalRunCountByDay(crossed).values()]).toEqual([]);
1519
+ expect(
1520
+ filesystem.viewPage("cost_monitor_agent_invocations", {
1521
+ provider: "openai",
1522
+ model: "claude-b",
1523
+ }).total_count,
1524
+ ).toBe(0);
1525
+ expect(
1526
+ filesystem.viewPage("cost_monitor_zaip_agents", { provider: "openai", model: "claude-b" })
1527
+ .total_count,
1528
+ ).toBe(0);
1529
+ for (const view of [
1530
+ "cost_monitor_daily",
1531
+ "cost_monitor_monthly",
1532
+ "cost_monitor_annual",
1533
+ ] as const) {
1534
+ // Lifecycle buckets can remain present independently, but the crossed
1535
+ // pair must contribute no invocation financial or Run facts.
1536
+ const row = filesystem.viewPage(view, { provider: "openai", model: "claude-b" }).rows[0];
1537
+ expect(row).toMatchObject({
1538
+ total_run_count: 0,
1539
+ total_duration: null,
1540
+ total_tokens: null,
1541
+ total_cost_brl: null,
1542
+ });
1543
+ }
1544
+ for (const view of [
1545
+ "cost_monitor_daily",
1546
+ "cost_monitor_monthly",
1547
+ "cost_monitor_annual",
1548
+ ] as const) {
1549
+ const originFiltered = filesystem.viewPage(view, { origin: "task" });
1550
+ expect(originFiltered.rows[0]).toMatchObject({
1551
+ prd_count: null,
1552
+ spec_count: null,
1553
+ task_count: null,
1554
+ validation_count: null,
1555
+ evidence_count: null,
1556
+ });
1557
+ expect(filesystem.viewPage(view, { workType: "new" }).total_count).toBe(0);
1558
+ }
1559
+ expect(filesystem.viewPage("cost_monitor_zaip_agents", { priority: "high" })).toMatchObject({
1560
+ total_count: 0,
1561
+ rows: [],
1562
+ });
1563
+ const providerGroups = filesystem.viewPage("cost_monitor_providers", { limit: 20, offset: 0 });
1564
+ expect(providerGroups.rows).toEqual(
1565
+ expect.arrayContaining([
1566
+ expect.objectContaining({ key: "openai", total_tokens: 1_010, total_cost_brl: 505 }),
1567
+ expect.objectContaining({ key: "anthropic", total_tokens: 20, total_cost_brl: 10 }),
1568
+ ]),
1569
+ );
1570
+ expect(providerGroups.rows.map((row) => row.key)).toEqual(
1571
+ expect.arrayContaining(["unavailable"]),
1572
+ );
1573
+ expect(
1574
+ filesystem
1575
+ .viewPage("cost_monitor_providers", { provider: "openai", model: "gpt-a" })
1576
+ .rows.find((row) => row.key === "openai"),
1577
+ ).toMatchObject({ total_tokens: 10, total_cost_brl: 5 });
1578
+ expect(
1579
+ filesystem.viewPage("cost_monitor_providers", { provider: "openai", model: "claude-b" }),
1580
+ ).toMatchObject({ total_count: 0, rows: [] });
1581
+ // A source segment can prove its financial facts, but an invocation with
1582
+ // multiple observed providers cannot prove a provider-specific elapsed
1583
+ // time. It must not manufacture a "mixed" bucket for that remainder.
1584
+ const queriedProviderRows = filesystem.viewPage("cost_monitor_providers", {
1585
+ query: "openai",
1586
+ }).rows;
1587
+ expect(queriedProviderRows).toEqual([
1588
+ expect.objectContaining({ key: "openai", total_tokens: 10, total_cost_brl: 5 }),
1589
+ ]);
1590
+ const modelGroups = filesystem.viewPage("cost_monitor_models", { limit: 20, offset: 0 });
1591
+ expect(modelGroups.rows).toEqual(
1592
+ expect.arrayContaining([
1593
+ expect.objectContaining({ key: "gpt-a", total_tokens: 1_010, total_cost_brl: 505 }),
1594
+ expect.objectContaining({ key: "claude-b", total_tokens: 20, total_cost_brl: 10 }),
1595
+ ]),
1596
+ );
1597
+ // First-fold transport is aggregate-only, but its overview chart must
1598
+ // retain the segment-backed model rows instead of falling back to the
1599
+ // empty availability metadata.
1600
+ expect(filesystem.costMonitorSummaryAggregate({ from: null, to: null }).model_groups).toEqual(
1601
+ expect.arrayContaining([
1602
+ expect.objectContaining({ key: "gpt-a", total_tokens: 1_010, estimated_brl: 505 }),
1603
+ expect.objectContaining({ key: "claude-b", total_tokens: 20, estimated_brl: 10 }),
1604
+ ]),
1605
+ );
1606
+ expect(modelGroups.rows.map((row) => row.key)).toEqual(expect.arrayContaining(["unavailable"]));
1607
+ const modelTotals = modelGroups.rows.reduce<{
1608
+ invocations: number;
1609
+ tasks: number;
1610
+ duration: number;
1611
+ tokens: number;
1612
+ brl: number;
1613
+ }>(
1614
+ (total, row) => ({
1615
+ invocations: total.invocations + Number(row.invocation_count ?? 0),
1616
+ tasks: total.tasks + Number(row.task_count ?? 0),
1617
+ duration: total.duration + Number(row.total_duration ?? 0),
1618
+ tokens: total.tokens + Number(row.total_tokens ?? 0),
1619
+ brl: total.brl + Number(row.total_cost_brl ?? 0),
1620
+ }),
1621
+ { invocations: 0, tasks: 0, duration: 0, tokens: 0, brl: 0 },
1622
+ );
1623
+ expect(modelTotals).toEqual({
1624
+ invocations: 2,
1625
+ tasks: 3,
1626
+ duration: 60_000,
1627
+ tokens: 1030,
1628
+ brl: 515,
1629
+ });
1630
+ expect(filesystem.viewPage("cost_monitor_models", { query: "gpt-a" }).rows).toHaveLength(1);
1631
+ expect(
1632
+ filesystem.viewPage("cost_monitor_models", { provider: "openai", model: "gpt-a" }).rows[0],
1633
+ ).toMatchObject({ key: "gpt-a", total_tokens: 1010, total_cost_brl: 505 });
1634
+ expect(
1635
+ filesystem.viewPage("cost_monitor_models", { provider: "openai", model: "claude-b" }),
1636
+ ).toMatchObject({ total_count: 0, rows: [] });
1637
+ expect(filesystem.viewPage("cost_monitor_models", { priority: "high" })).toMatchObject({
1638
+ total_count: 0,
1639
+ rows: [],
1640
+ });
1641
+ const unavailable = {
1642
+ from: null,
1643
+ to: null,
1644
+ providers: ["unavailable"],
1645
+ models: ["unavailable"],
1646
+ };
1647
+ const unavailableRows = filesystem.invocationCostRows(unavailable);
1648
+ expect(unavailableRows).toHaveLength(1);
1649
+ // Observed zero telemetry stays zero, while the missing provider/model
1650
+ // attribute selects this segment rather than a literal sentinel value.
1651
+ expect(unavailableRows[0]).toMatchObject({ total_tokens: 0, cost_brl: 0 });
1652
+ // Execution-duration is an Operations compatibility aggregate and still
1653
+ // includes the unassisted canonical Run. Cost Monitor retains its
1654
+ // measurable financial activity as an unavailable historical fact.
1655
+ expect([...filesystem.executionDurationByDay(unavailable).values()]).toEqual([60_000]);
1656
+ expect([...filesystem.canonicalRunCountByDay(unavailable).values()]).toEqual([2]);
1657
+ const unavailableInvocation = filesystem.viewPage("cost_monitor_agent_invocations", {
1658
+ provider: "unavailable",
1659
+ model: "unavailable",
1660
+ });
1661
+ expect(unavailableInvocation).toMatchObject({ total_count: 1 });
1662
+ expect(unavailableInvocation.rows[0]).toMatchObject({ input_tokens: 0 });
1663
+ for (const view of [
1664
+ "cost_monitor_daily",
1665
+ "cost_monitor_monthly",
1666
+ "cost_monitor_annual",
1667
+ ] as const) {
1668
+ const row = filesystem.viewPage(view, { provider: "unavailable", model: "unavailable" })
1669
+ .rows[0];
1670
+ expect(row).toMatchObject({ total_run_count: 2, total_tokens: 1_000, total_cost_brl: 500 });
1671
+ }
1672
+
1673
+ filesystem.close();
1674
+ const sameModelStarted = {
1675
+ ...started,
1676
+ event_id: "RUN-SAME-MODEL:1",
1677
+ run_id: "RUN-SAME-MODEL",
1678
+ occurred_at: "2026-08-05T12:00:00.000Z",
1679
+ client_turn_id: "same-model",
1680
+ conversation_id: "CONV-SAME-MODEL",
1681
+ execution_id: "EXEC-SAME-MODEL",
1682
+ task_id: "TASK-SAME-MODEL",
1683
+ agent_invocation_id: "AINV-SAME-MODEL",
1684
+ root_agent_invocation_id: "AINV-SAME-MODEL",
1685
+ provider: "mixed",
1686
+ model: "gpt-a",
1687
+ };
1688
+ const sameModelUsage = (sequence: number, provider: string, tokens: number, brl: number) => ({
1689
+ ...usage(sequence, provider, "gpt-a", tokens, brl),
1690
+ event_id: `RUN-SAME-MODEL:${sequence}`,
1691
+ run_id: "RUN-SAME-MODEL",
1692
+ occurred_at: "2026-08-05T12:00:01.000Z",
1693
+ source_started_at: "2026-08-05T12:00:00.000Z",
1694
+ source_finished_at: "2026-08-05T12:01:00.000Z",
1695
+ });
1696
+ const sameModelFinished = {
1697
+ ...finished,
1698
+ event_id: "RUN-SAME-MODEL:4",
1699
+ run_id: "RUN-SAME-MODEL",
1700
+ sequence: 4,
1701
+ occurred_at: "2026-08-05T12:01:00.000Z",
1702
+ };
1703
+ await writeFile(
1704
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-SAME-MODEL.jsonl"),
1705
+ `${[sameModelStarted, sameModelUsage(2, "openai", 3, 1.5), sameModelUsage(3, "anthropic", 4, 2), sameModelFinished].map((row) => JSON.stringify(row)).join("\n")}\n`,
1706
+ );
1707
+ const durationRun = (
1708
+ id: string,
1709
+ day: string,
1710
+ duration: number | null,
1711
+ provenance: "reconciled" | "estimated",
1712
+ ) => {
1713
+ const start = {
1714
+ ...sameModelStarted,
1715
+ event_id: `${id}:1`,
1716
+ run_id: id,
1717
+ occurred_at: `${day}T12:00:00.000Z`,
1718
+ client_turn_id: id,
1719
+ conversation_id: `CONV-${id}`,
1720
+ execution_id: `EXEC-${id}`,
1721
+ task_id: `TASK-${id}`,
1722
+ agent_invocation_id: `AINV-${id}`,
1723
+ root_agent_invocation_id: `AINV-${id}`,
1724
+ };
1725
+ const measured = {
1726
+ ...usage(
1727
+ 2,
1728
+ "openai",
1729
+ "gpt-a",
1730
+ provenance === "estimated" ? 4_000_000 : 9,
1731
+ provenance === "estimated" ? 0 : 4.5,
1732
+ ),
1733
+ event_id: `${id}:2`,
1734
+ run_id: id,
1735
+ occurred_at: `${day}T12:00:01.000Z`,
1736
+ source_started_at: `${day}T12:00:00.000Z`,
1737
+ source_finished_at: `${day}T12:01:00.000Z`,
1738
+ ...(duration === null ? {} : { duration_ms: duration, duration_provenance: provenance }),
1739
+ };
1740
+ const end = {
1741
+ ...finished,
1742
+ event_id: `${id}:3`,
1743
+ run_id: id,
1744
+ sequence: 3,
1745
+ occurred_at: `${day}T12:01:00.000Z`,
1746
+ ...(provenance === "estimated"
1747
+ ? { duration_ms: null, duration_provenance: "unavailable" }
1748
+ : {}),
1749
+ };
1750
+ return [start, measured, end];
1751
+ };
1752
+ await writeFile(
1753
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-RECONCILED-MODEL.jsonl"),
1754
+ `${durationRun("RUN-RECONCILED-MODEL", "2026-08-06", 45_000, "reconciled")
1755
+ .map((row) => JSON.stringify(row))
1756
+ .join("\n")}\n`,
1757
+ );
1758
+ await writeFile(
1759
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-ESTIMATED-MODEL.jsonl"),
1760
+ `${durationRun("RUN-ESTIMATED-MODEL", "2026-08-07", null, "estimated")
1761
+ .map((row) => JSON.stringify(row))
1762
+ .join("\n")}\n`,
1763
+ );
1764
+ const sharedTaskRun = (id: string, model: string, tokens: number, brl: number) => [
1765
+ {
1766
+ ...sameModelStarted,
1767
+ event_id: `${id}:1`,
1768
+ run_id: id,
1769
+ occurred_at: "2026-08-08T12:00:00.000Z",
1770
+ client_turn_id: id,
1771
+ conversation_id: `CONV-${id}`,
1772
+ execution_id: `EXEC-${id}`,
1773
+ task_id: "TASK-CROSS-MODEL",
1774
+ agent_invocation_id: `AINV-${id}`,
1775
+ root_agent_invocation_id: `AINV-${id}`,
1776
+ provider: "openai",
1777
+ model,
1778
+ },
1779
+ {
1780
+ ...usage(2, "openai", model, tokens, brl),
1781
+ event_id: `${id}:2`,
1782
+ run_id: id,
1783
+ occurred_at: "2026-08-08T12:00:01.000Z",
1784
+ source_started_at: "2026-08-08T12:00:00.000Z",
1785
+ source_finished_at: "2026-08-08T12:01:00.000Z",
1786
+ },
1787
+ {
1788
+ ...finished,
1789
+ event_id: `${id}:3`,
1790
+ run_id: id,
1791
+ sequence: 3,
1792
+ occurred_at: "2026-08-08T12:01:00.000Z",
1793
+ },
1794
+ ];
1795
+ await writeFile(
1796
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-TASK-GPT.jsonl"),
1797
+ `${sharedTaskRun("RUN-TASK-GPT", "gpt-a", 5, 2.5)
1798
+ .map((row) => JSON.stringify(row))
1799
+ .join("\n")}\n`,
1800
+ );
1801
+ await writeFile(
1802
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-TASK-CLAUDE.jsonl"),
1803
+ `${sharedTaskRun("RUN-TASK-CLAUDE", "claude-b", 6, 3)
1804
+ .map((row) => JSON.stringify(row))
1805
+ .join("\n")}\n`,
1806
+ );
1807
+ const subrunStarted = {
1808
+ ...sameModelStarted,
1809
+ event_id: "RUN-PROVIDER-SUBRUN:1",
1810
+ run_id: "RUN-PROVIDER-SUBRUN",
1811
+ occurred_at: "2026-08-09T12:00:00.000Z",
1812
+ client_turn_id: "provider-subrun",
1813
+ conversation_id: "CONV-PROVIDER-SUBRUN",
1814
+ execution_id: "EXEC-PROVIDER-SUBRUN",
1815
+ task_id: "TASK-PROVIDER-SUBRUN",
1816
+ agent_invocation_id: "AINV-PROVIDER-SUBRUN",
1817
+ root_agent_invocation_id: "AINV-SAME-MODEL",
1818
+ parent_agent_invocation_id: "AINV-SAME-MODEL",
1819
+ parent_run_id: "RUN-SAME-MODEL",
1820
+ run_role: "subagent",
1821
+ invocation_kind: "delegated",
1822
+ delegation_depth: 1,
1823
+ provider: "openai",
1824
+ model: "gpt-a",
1825
+ };
1826
+ const subrunUsage = {
1827
+ ...usage(2, "openai", "gpt-a", 8, 4),
1828
+ event_id: "RUN-PROVIDER-SUBRUN:2",
1829
+ run_id: "RUN-PROVIDER-SUBRUN",
1830
+ occurred_at: "2026-08-09T12:00:01.000Z",
1831
+ source_started_at: "2026-08-09T12:00:00.000Z",
1832
+ source_finished_at: "2026-08-09T12:00:30.000Z",
1833
+ };
1834
+ const subrunFinished = {
1835
+ ...finished,
1836
+ event_id: "RUN-PROVIDER-SUBRUN:3",
1837
+ run_id: "RUN-PROVIDER-SUBRUN",
1838
+ sequence: 3,
1839
+ occurred_at: "2026-08-09T12:00:30.000Z",
1840
+ duration_ms: 30_000,
1841
+ duration_provenance: "observed",
1842
+ };
1843
+ await writeFile(
1844
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-PROVIDER-SUBRUN.jsonl"),
1845
+ `${[subrunStarted, subrunUsage, subrunFinished].map((row) => JSON.stringify(row)).join("\n")}\n`,
1846
+ );
1847
+ await rebuildSqliteReadModel(root);
1848
+ const sameModelFs = new SqliteReadModelFileSystem(root);
1849
+ const modelRow = (filter: Record<string, string>) =>
1850
+ sameModelFs
1851
+ .viewPage("cost_monitor_models", { from: "2026-08-05", to: "2026-08-05", ...filter })
1852
+ .rows.find((row) => row.key === "gpt-a");
1853
+ expect(modelRow({ provider: "openai", model: "gpt-a" })).toMatchObject({
1854
+ invocation_count: 1,
1855
+ task_count: 1,
1856
+ total_duration: 60_000,
1857
+ total_tokens: 3,
1858
+ total_cost_brl: 1.5,
1859
+ });
1860
+ expect(modelRow({ provider: "anthropic", model: "gpt-a" })).toMatchObject({
1861
+ invocation_count: 1,
1862
+ task_count: 1,
1863
+ total_duration: 60_000,
1864
+ total_tokens: 4,
1865
+ total_cost_brl: 2,
1866
+ });
1867
+ expect(modelRow({})).toMatchObject({
1868
+ invocation_count: 1,
1869
+ task_count: 1,
1870
+ total_duration: 60_000,
1871
+ total_tokens: 7,
1872
+ total_cost_brl: 3.5,
1873
+ });
1874
+ expect(
1875
+ sameModelFs.viewPage("cost_monitor_models", {
1876
+ from: "2026-08-05",
1877
+ to: "2026-08-05",
1878
+ provider: "openai",
1879
+ model: "claude-b",
1880
+ }),
1881
+ ).toMatchObject({ total_count: 0, rows: [] });
1882
+ for (const day of ["2026-08-06", "2026-08-07"]) {
1883
+ const effective = sameModelFs
1884
+ .invocationCostRows({ from: day, to: day })
1885
+ .reduce((sum, row) => sum + Number(row.duration_ms ?? 0), 0);
1886
+ const modelDuration = sameModelFs
1887
+ .viewPage("cost_monitor_models", { from: day, to: day })
1888
+ .rows.reduce((sum, row) => sum + Number(row.total_duration ?? 0), 0);
1889
+ expect(modelDuration).toBe(effective);
1890
+ }
1891
+ for (const day of ["2026-08-06", "2026-08-07"]) {
1892
+ const population = sameModelFs.invocationCostRows({ from: day, to: day });
1893
+ const providerRows = sameModelFs.viewPage("cost_monitor_providers", {
1894
+ from: day,
1895
+ to: day,
1896
+ }).rows;
1897
+ const providerTotals = providerRows.reduce<{
1898
+ invocations: number;
1899
+ tasks: number;
1900
+ duration: number;
1901
+ tokens: number;
1902
+ brl: number;
1903
+ }>(
1904
+ (total, row) => ({
1905
+ invocations: total.invocations + Number(row.invocation_count ?? 0),
1906
+ tasks: total.tasks + Number(row.task_count ?? 0),
1907
+ duration: total.duration + Number(row.total_duration ?? 0),
1908
+ tokens: total.tokens + Number(row.total_tokens ?? 0),
1909
+ brl: total.brl + Number(row.total_cost_brl ?? 0),
1910
+ }),
1911
+ { invocations: 0, tasks: 0, duration: 0, tokens: 0, brl: 0 },
1912
+ );
1913
+ expect(providerTotals).toEqual({
1914
+ invocations: population.length,
1915
+ tasks: new Set(
1916
+ population.map((row) => row.task_id).filter((id): id is string => typeof id === "string"),
1917
+ ).size,
1918
+ duration: population.reduce((sum, row) => sum + Number(row.duration_ms ?? 0), 0),
1919
+ tokens: population.reduce((sum, row) => sum + Number(row.total_tokens ?? 0), 0),
1920
+ brl: population.reduce((sum, row) => sum + Number(row.cost_brl ?? 0), 0),
1921
+ });
1922
+ }
1923
+ const sharedTaskRows = sameModelFs.viewPage("cost_monitor_models", {
1924
+ from: "2026-08-08",
1925
+ to: "2026-08-08",
1926
+ }).rows;
1927
+ expect(sharedTaskRows).toEqual(
1928
+ expect.arrayContaining([
1929
+ expect.objectContaining({
1930
+ key: "gpt-a",
1931
+ invocation_count: 1,
1932
+ task_count: 1,
1933
+ total_duration: 30_000,
1934
+ total_tokens: 5,
1935
+ total_cost_brl: 2.5,
1936
+ }),
1937
+ expect.objectContaining({
1938
+ key: "claude-b",
1939
+ invocation_count: 1,
1940
+ task_count: 1,
1941
+ total_duration: 30_000,
1942
+ total_tokens: 6,
1943
+ total_cost_brl: 3,
1944
+ }),
1945
+ ]),
1946
+ );
1947
+ expect(sharedTaskRows.map((row) => row.key)).not.toContain("mixed");
1948
+ expect(sharedTaskRows.reduce((sum, row) => sum + Number(row.task_count ?? 0), 0)).toBe(2);
1949
+ const subrunPopulation = sameModelFs.invocationCostRows({
1950
+ from: "2026-08-09",
1951
+ to: "2026-08-09",
1952
+ providers: ["openai"],
1953
+ models: ["gpt-a"],
1954
+ });
1955
+ const subrunProviderRows = sameModelFs.viewPage("cost_monitor_providers", {
1956
+ from: "2026-08-09",
1957
+ to: "2026-08-09",
1958
+ provider: "openai",
1959
+ model: "gpt-a",
1960
+ }).rows;
1961
+ const subrunProviderTotals = subrunProviderRows.reduce<{
1962
+ invocations: number;
1963
+ tasks: number;
1964
+ duration: number;
1965
+ tokens: number;
1966
+ brl: number;
1967
+ }>(
1968
+ (total, row) => ({
1969
+ invocations: total.invocations + Number(row.invocation_count ?? 0),
1970
+ tasks: total.tasks + Number(row.task_count ?? 0),
1971
+ duration: total.duration + Number(row.total_duration ?? 0),
1972
+ tokens: total.tokens + Number(row.total_tokens ?? 0),
1973
+ brl: total.brl + Number(row.total_cost_brl ?? 0),
1974
+ }),
1975
+ { invocations: 0, tasks: 0, duration: 0, tokens: 0, brl: 0 },
1976
+ );
1977
+ expect(subrunProviderTotals).toEqual({
1978
+ invocations: subrunPopulation.length,
1979
+ tasks: new Set(
1980
+ subrunPopulation
1981
+ .map((row) => row.task_id)
1982
+ .filter((id): id is string => typeof id === "string"),
1983
+ ).size,
1984
+ duration: subrunPopulation.reduce((sum, row) => sum + Number(row.duration_ms ?? 0), 0),
1985
+ tokens: subrunPopulation.reduce((sum, row) => sum + Number(row.total_tokens ?? 0), 0),
1986
+ brl: subrunPopulation.reduce((sum, row) => sum + Number(row.cost_brl ?? 0), 0),
1987
+ });
1988
+ expect(
1989
+ sameModelFs.viewPage("cost_monitor_providers", {
1990
+ from: "2026-08-09",
1991
+ to: "2026-08-09",
1992
+ provider: "openai",
1993
+ model: "claude-b",
1994
+ }),
1995
+ ).toMatchObject({ total_count: 0, rows: [] });
1996
+ sameModelFs.close();
1997
+ });
1998
+
1999
+ it("prefers the canonical started-to-finished interval over a token estimate", async () => {
2000
+ const root = await createSqliteTestRoot();
2001
+ roots.push(root);
2002
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
2003
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
2004
+ await writeFile(
2005
+ join(root, ".agents", "config", "project.yaml"),
2006
+ "schema_version: 1\nzaip:\n enabled: true\nproject:\n id: sqlite-test\n name: sqlite-test\n timezone: UTC\nstack:\n id: zaip-aios-stack\n overrides: {}\nclients:\n codex: true\n claude: true\ntracking:\n enabled: true\n currency: BRL\n storage_mode: tracked_sanitized\n",
2007
+ "utf8",
2008
+ );
2009
+ const started = {
2010
+ schema_version: 2,
2011
+ event_id: "RUN-2:1",
2012
+ run_id: "RUN-2",
2013
+ sequence: 1,
2014
+ occurred_at: "2026-08-01T12:00:00.000Z",
2015
+ type: "run.started",
2016
+ attribution_status: "attributed",
2017
+ phase: "execution",
2018
+ run_role: "orchestrator",
2019
+ run_purpose: "execution",
2020
+ requires_finalizer: false,
2021
+ execution_kind: "delivery",
2022
+ execution_eligibility: ["delivery"],
2023
+ entrypoint: "task",
2024
+ work_type: "new",
2025
+ storage_mode: "tracked_sanitized",
2026
+ client: "codex",
2027
+ agent: "agent",
2028
+ agent_name: "agent",
2029
+ provider: "openai",
2030
+ model: "gpt-5",
2031
+ reasoning_effort: null,
2032
+ client_thread_id: "thread",
2033
+ client_turn_id: "turn",
2034
+ conversation_id: "CONV-2",
2035
+ execution_id: "EXEC-2",
2036
+ task_id: "TASK-2",
2037
+ spec_id: null,
2038
+ prd_id: null,
2039
+ change_id: null,
2040
+ parent_run_id: null,
2041
+ continuation_of_run_id: null,
2042
+ retry_of_run_id: null,
2043
+ agent_invocation_id: "AINV-2",
2044
+ root_agent_invocation_id: "AINV-2",
2045
+ parent_agent_invocation_id: null,
2046
+ invocation_kind: "orchestrator",
2047
+ delegation_depth: 0,
2048
+ };
2049
+ const usage = {
2050
+ schema_version: 2,
2051
+ event_id: "RUN-2:2",
2052
+ run_id: "RUN-2",
2053
+ sequence: 2,
2054
+ occurred_at: "2026-08-01T12:10:00.000Z",
2055
+ type: "usage.recorded",
2056
+ phase: "execution",
2057
+ activity_kind: "model_usage",
2058
+ classification_source: "explicit",
2059
+ tokens: {
2060
+ input: 4_000_000,
2061
+ cached_input: 0,
2062
+ output: 0,
2063
+ reasoning: 0,
2064
+ total: 4_000_000,
2065
+ source: "provider_reported",
2066
+ },
2067
+ cost: { usd: null, brl: null, fx_rate: null },
2068
+ provider: "openai",
2069
+ model: "gpt-5",
2070
+ source_segment_key: "segment",
2071
+ model_call_id: "MCALL-2",
2072
+ runtime: { reasoning_effort: null, service_tier: null },
2073
+ supersedes_event_id: null,
2074
+ duration_ms: null,
2075
+ duration_provenance: null,
2076
+ };
2077
+ const finished = {
2078
+ schema_version: 2,
2079
+ event_id: "RUN-2:3",
2080
+ run_id: "RUN-2",
2081
+ sequence: 3,
2082
+ occurred_at: "2026-08-01T12:30:00.000Z",
2083
+ type: "run.finished",
2084
+ phase: "execution",
2085
+ status: "completed",
2086
+ duration_ms: null,
2087
+ duration_provenance: "unavailable",
2088
+ };
2089
+ await writeFile(
2090
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-2.jsonl"),
2091
+ `${[started, usage, finished].map((record) => JSON.stringify(record)).join("\n")}\n`,
2092
+ "utf8",
2093
+ );
2094
+ await rebuildSqliteReadModel(root);
2095
+ const database = new DatabaseSync(resolve(root, ".local/dashboard-sqlite/read-model.sqlite"), {
2096
+ readOnly: true,
2097
+ });
2098
+ expect(
2099
+ database.prepare("SELECT duration_ms, duration_provenance FROM zaip_aios_activities").get(),
2100
+ ).toEqual({ duration_ms: 1_800_000, duration_provenance: "attributed_token_active" });
2101
+ database.close();
2102
+ });
2103
+
2104
+ it("does not convert a delayed historical finalizer into execution time", async () => {
2105
+ const root = await createSqliteTestRoot();
2106
+ roots.push(root);
2107
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
2108
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
2109
+ await writeFile(
2110
+ join(root, ".agents", "config", "project.yaml"),
2111
+ "zaip:\n enabled: true\n",
2112
+ "utf8",
2113
+ );
2114
+ const started = {
2115
+ schema_version: 2,
2116
+ event_id: "RUN-3:1",
2117
+ run_id: "RUN-3",
2118
+ sequence: 1,
2119
+ occurred_at: "2026-08-01T12:00:00.000Z",
2120
+ type: "run.started",
2121
+ attribution_status: "attributed",
2122
+ phase: "execution",
2123
+ run_role: "orchestrator",
2124
+ run_purpose: "execution",
2125
+ requires_finalizer: false,
2126
+ execution_kind: "delivery",
2127
+ execution_eligibility: ["delivery"],
2128
+ entrypoint: "task",
2129
+ work_type: "new",
2130
+ storage_mode: "tracked_sanitized",
2131
+ client: "codex",
2132
+ agent: "agent",
2133
+ agent_name: "agent",
2134
+ provider: "openai",
2135
+ model: "gpt-5",
2136
+ reasoning_effort: null,
2137
+ client_thread_id: "thread",
2138
+ client_turn_id: "turn",
2139
+ conversation_id: "CONV-3",
2140
+ execution_id: "EXEC-3",
2141
+ task_id: "TASK-3",
2142
+ spec_id: null,
2143
+ prd_id: null,
2144
+ change_id: null,
2145
+ parent_run_id: null,
2146
+ continuation_of_run_id: null,
2147
+ retry_of_run_id: null,
2148
+ agent_invocation_id: "AINV-3",
2149
+ root_agent_invocation_id: "AINV-3",
2150
+ parent_agent_invocation_id: null,
2151
+ invocation_kind: "orchestrator",
2152
+ delegation_depth: 0,
2153
+ };
2154
+ const usage = {
2155
+ schema_version: 2,
2156
+ event_id: "RUN-3:2",
2157
+ run_id: "RUN-3",
2158
+ sequence: 2,
2159
+ occurred_at: "2026-08-01T12:10:00.000Z",
2160
+ type: "usage.recorded",
2161
+ phase: "execution",
2162
+ activity_kind: "model_usage",
2163
+ classification_source: "explicit",
2164
+ tokens: {
2165
+ input: 20_000_000,
2166
+ cached_input: 19_000_000,
2167
+ output: 400_000,
2168
+ reasoning: 0,
2169
+ total: 20_400_000,
2170
+ source: "provider_reported",
2171
+ },
2172
+ cost: { usd: null, brl: null, fx_rate: null },
2173
+ provider: "openai",
2174
+ model: "gpt-5",
2175
+ source_segment_key: "segment",
2176
+ model_call_id: "MCALL-3",
2177
+ runtime: { reasoning_effort: null, service_tier: null },
2178
+ supersedes_event_id: null,
2179
+ duration_ms: null,
2180
+ duration_provenance: null,
2181
+ };
2182
+ const finished = {
2183
+ schema_version: 2,
2184
+ event_id: "RUN-3:3",
2185
+ run_id: "RUN-3",
2186
+ sequence: 3,
2187
+ occurred_at: "2026-08-03T12:00:00.000Z",
2188
+ type: "run.finished",
2189
+ phase: "execution",
2190
+ status: "completed",
2191
+ duration_ms: null,
2192
+ duration_provenance: "unavailable",
2193
+ };
2194
+ await writeFile(
2195
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-3.jsonl"),
2196
+ `${[started, usage, finished].map((record) => JSON.stringify(record)).join("\n")}\n`,
2197
+ "utf8",
2198
+ );
2199
+ await rebuildSqliteReadModel(root);
2200
+ const database = new DatabaseSync(resolve(root, ".local/dashboard-sqlite/read-model.sqlite"), {
2201
+ readOnly: true,
2202
+ });
2203
+ expect(
2204
+ database.prepare("SELECT duration_ms, duration_provenance FROM zaip_aios_activities").get(),
2205
+ ).toEqual({ duration_ms: null, duration_provenance: null });
2206
+ database.close();
2207
+ });
2208
+
2209
+ it("unions overlapping source intervals, deduplicates source-segment aliases, and keeps cross-midnight financial facts in their source day", async () => {
2210
+ const root = await createSqliteTestRoot();
2211
+ roots.push(root);
2212
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "07"), { recursive: true });
2213
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
2214
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
2215
+ const run = ({
2216
+ id,
2217
+ phase,
2218
+ sourceStartedAt,
2219
+ sourceFinishedAt,
2220
+ parentRunId = null,
2221
+ }: {
2222
+ id: string;
2223
+ phase: "conversation" | "planning" | "execution";
2224
+ sourceStartedAt: string;
2225
+ sourceFinishedAt: string;
2226
+ parentRunId?: string | null;
2227
+ }) => {
2228
+ const occurredAt = sourceStartedAt;
2229
+ const agentInvocationId = `AINV-${id}`;
2230
+ return [
2231
+ {
2232
+ schema_version: 2,
2233
+ event_id: `${id}:1`,
2234
+ run_id: id,
2235
+ sequence: 1,
2236
+ occurred_at: occurredAt,
2237
+ type: "run.started",
2238
+ attribution_status: "attributed",
2239
+ phase,
2240
+ run_role: parentRunId === null ? "orchestrator" : "subagent",
2241
+ run_purpose: "orchestration",
2242
+ requires_finalizer: false,
2243
+ execution_kind: "delivery",
2244
+ execution_eligibility: ["delivery"],
2245
+ entrypoint: "task",
2246
+ work_type: "new",
2247
+ storage_mode: "tracked_sanitized",
2248
+ client: "codex",
2249
+ agent: "agent",
2250
+ agent_name: "agent",
2251
+ provider: "openai",
2252
+ model: "gpt-5",
2253
+ reasoning_effort: null,
2254
+ client_thread_id: `thread-${id}`,
2255
+ client_turn_id: `turn-${id}`,
2256
+ conversation_id: `CONV-${id}`,
2257
+ execution_id: `EXEC-${id}`,
2258
+ task_id: `TASK-${id}`,
2259
+ spec_id: null,
2260
+ prd_id: null,
2261
+ change_id: null,
2262
+ parent_run_id: parentRunId,
2263
+ continuation_of_run_id: null,
2264
+ retry_of_run_id: null,
2265
+ agent_invocation_id: agentInvocationId,
2266
+ root_agent_invocation_id: parentRunId === null ? agentInvocationId : "AINV-root",
2267
+ parent_agent_invocation_id: parentRunId === null ? null : "AINV-root",
2268
+ invocation_kind: parentRunId === null ? "orchestrator" : "delegated",
2269
+ delegation_depth: parentRunId === null ? 0 : 1,
2270
+ },
2271
+ {
2272
+ schema_version: 2,
2273
+ event_id: `${id}:2`,
2274
+ run_id: id,
2275
+ sequence: 2,
2276
+ occurred_at: sourceFinishedAt,
2277
+ type: "usage.recorded",
2278
+ phase,
2279
+ activity_kind: "retrospective_local_remeasurement",
2280
+ classification_source: "current_context",
2281
+ tokens: {
2282
+ input: 1,
2283
+ cached_input: 0,
2284
+ output: 1,
2285
+ reasoning: 0,
2286
+ total: 2,
2287
+ source: "provider_reported",
2288
+ },
2289
+ cost: { usd: 1, brl: 5, fx_rate: 5 },
2290
+ provider: "openai",
2291
+ model: "gpt-5",
2292
+ source_segment_key: id,
2293
+ model_call_id: `MCALL-${id}`,
2294
+ runtime: { reasoning_effort: null, service_tier: null },
2295
+ supersedes_event_id: null,
2296
+ retrospective_correction: true,
2297
+ source_occurred_at: sourceFinishedAt,
2298
+ source_started_at: sourceStartedAt,
2299
+ source_finished_at: sourceFinishedAt,
2300
+ duration_ms: null,
2301
+ duration_provenance: null,
2302
+ },
2303
+ {
2304
+ schema_version: 2,
2305
+ event_id: `${id}:3`,
2306
+ run_id: id,
2307
+ sequence: 3,
2308
+ occurred_at: new Date(Date.parse(occurredAt) + 60_000).toISOString(),
2309
+ type: "run.finished",
2310
+ phase,
2311
+ status: "completed",
2312
+ duration_ms: 60_000,
2313
+ duration_provenance: "observed",
2314
+ },
2315
+ ];
2316
+ };
2317
+ for (const value of [
2318
+ // 23:17–01:01 São Paulo: factual duration is split at local midnight.
2319
+ {
2320
+ id: "RUN-planning",
2321
+ phase: "planning" as const,
2322
+ sourceStartedAt: "2026-07-23T02:17:00.000Z",
2323
+ sourceFinishedAt: "2026-07-23T04:01:00.000Z",
2324
+ },
2325
+ {
2326
+ id: "RUN-conversation",
2327
+ phase: "conversation" as const,
2328
+ sourceStartedAt: "2026-07-23T03:00:00.000Z",
2329
+ sourceFinishedAt: "2026-07-23T04:01:00.000Z",
2330
+ },
2331
+ {
2332
+ id: "RUN-execution",
2333
+ phase: "execution" as const,
2334
+ sourceStartedAt: "2026-07-23T03:30:00.000Z",
2335
+ sourceFinishedAt: "2026-07-23T04:30:00.000Z",
2336
+ },
2337
+ {
2338
+ id: "RUN-subrun",
2339
+ phase: "execution" as const,
2340
+ parentRunId: "RUN-execution",
2341
+ sourceStartedAt: "2026-07-23T03:45:00.000Z",
2342
+ sourceFinishedAt: "2026-07-23T04:15:00.000Z",
2343
+ },
2344
+ ]) {
2345
+ await writeFile(
2346
+ join(root, ".agents", "records", "runs", "2026", "07", `${value.id}.jsonl`),
2347
+ `${run(value)
2348
+ .map((record) => JSON.stringify(record))
2349
+ .join("\n")}\n`,
2350
+ );
2351
+ }
2352
+ const aliasRecords = run({
2353
+ id: "RUN-source-alias",
2354
+ phase: "execution",
2355
+ sourceStartedAt: "2026-07-23T04:10:00.000Z",
2356
+ sourceFinishedAt: "2026-07-23T04:20:00.000Z",
2357
+ });
2358
+ const aliasUsage = {
2359
+ ...aliasRecords[1],
2360
+ event_id: "RUN-source-alias:3",
2361
+ sequence: 3,
2362
+ };
2363
+ const aliasStarted = {
2364
+ ...aliasRecords[0],
2365
+ // Administrative capture predates the factual source session. Every
2366
+ // token/time/invocation surface must classify this record by the source.
2367
+ occurred_at: "2026-07-22T12:00:00.000Z",
2368
+ };
2369
+ const aliasFinished = { ...aliasRecords[2], event_id: "RUN-source-alias:4", sequence: 4 };
2370
+ await writeFile(
2371
+ join(root, ".agents", "records", "runs", "2026", "07", "RUN-source-alias.jsonl"),
2372
+ `${[aliasStarted, aliasRecords[1], aliasUsage, aliasFinished].map((record) => JSON.stringify(record)).join("\n")}\n`,
2373
+ );
2374
+ // Historical financial telemetry can have no agent_invocation_id. It is
2375
+ // still a Cost Monitor financial fact and must not disappear merely
2376
+ // because agent-specific views cannot address it.
2377
+ const historicalRecords = run({
2378
+ id: "RUN-historical-financial",
2379
+ phase: "conversation",
2380
+ sourceStartedAt: "2026-07-23T06:00:00.000Z",
2381
+ sourceFinishedAt: "2026-07-23T06:00:20.000Z",
2382
+ });
2383
+ const historicalStarted = { ...historicalRecords[0] } as Record<string, unknown>;
2384
+ // The record was captured the following day, while its source session is
2385
+ // 23 July. Cost Monitor filters must follow the latter.
2386
+ historicalStarted.occurred_at = "2026-07-24T12:00:00.000Z";
2387
+ delete historicalStarted.agent_invocation_id;
2388
+ delete historicalStarted.root_agent_invocation_id;
2389
+ delete historicalStarted.parent_agent_invocation_id;
2390
+ await writeFile(
2391
+ join(root, ".agents", "records", "runs", "2026", "07", "RUN-historical-financial.jsonl"),
2392
+ `${[historicalStarted, ...historicalRecords.slice(1)].map((record) => JSON.stringify(record)).join("\n")}\n`,
2393
+ );
2394
+ await rebuildSqliteReadModel(root);
2395
+ const database = new DatabaseSync(resolve(root, ".local/dashboard-sqlite/read-model.sqlite"), {
2396
+ readOnly: true,
2397
+ });
2398
+ expect(
2399
+ database
2400
+ .prepare(
2401
+ "SELECT duration_ms, source_duration_ms, duration_provenance FROM zaip_aios_activities WHERE run_id='RUN-planning'",
2402
+ )
2403
+ .get(),
2404
+ ).toEqual({
2405
+ duration_ms: 4_020_000,
2406
+ source_duration_ms: 6_240_000,
2407
+ duration_provenance: "attributed_token_active",
2408
+ });
2409
+ const daily = database
2410
+ .prepare(
2411
+ "SELECT day, total_duration, invocation_count, cost_monitor_invocation_count, cost_monitor_total_duration FROM zaip_vw_daily_aggregates WHERE day IN ('2026-07-22', '2026-07-23') ORDER BY day",
2412
+ )
2413
+ .all();
2414
+ expect(daily).toEqual([
2415
+ {
2416
+ day: "2026-07-22",
2417
+ total_duration: 2_580_000,
2418
+ invocation_count: 1,
2419
+ cost_monitor_invocation_count: 1,
2420
+ cost_monitor_total_duration: 2_580_000,
2421
+ },
2422
+ {
2423
+ day: "2026-07-23",
2424
+ total_duration: 5_420_000,
2425
+ invocation_count: 4,
2426
+ cost_monitor_invocation_count: 5,
2427
+ cost_monitor_total_duration: 5_420_000,
2428
+ },
2429
+ ]);
2430
+ expect(
2431
+ database
2432
+ .prepare(
2433
+ "SELECT COUNT(*) AS count FROM zaip_vw_daily_aggregates WHERE total_duration > 86400000",
2434
+ )
2435
+ .get(),
2436
+ ).toEqual({ count: 0 });
2437
+ database.close();
2438
+ const factualFilesystem = new SqliteReadModelFileSystem(root);
2439
+ expect(
2440
+ factualFilesystem
2441
+ .costMonitorFinancialActivityRows({ from: "2026-07-23", to: "2026-07-23" })
2442
+ .some((row) => row.run_id === "RUN-historical-financial"),
2443
+ ).toBe(true);
2444
+ const compactFinancialRow = factualFilesystem.costMonitorFinancialActivityRows({
2445
+ from: "2026-07-23",
2446
+ to: "2026-07-23",
2447
+ })[0];
2448
+ expect(compactFinancialRow).not.toHaveProperty("payload_json");
2449
+ expect(compactFinancialRow).toHaveProperty("total_tokens");
2450
+ expect(
2451
+ factualFilesystem
2452
+ .costMonitorFinancialActivityRows({ from: "2026-07-24", to: "2026-07-24" })
2453
+ .some((row) => row.run_id === "RUN-historical-financial"),
2454
+ ).toBe(false);
2455
+ expect(
2456
+ factualFilesystem
2457
+ .viewPage("operations_agent_invocations", {
2458
+ from: "2026-07-23",
2459
+ to: "2026-07-23",
2460
+ limit: 20,
2461
+ })
2462
+ .rows.some((row) => row.run_id === "RUN-source-alias"),
2463
+ ).toBe(true);
2464
+ expect(
2465
+ factualFilesystem
2466
+ .viewPage("operations_agent_invocations", {
2467
+ from: "2026-07-22",
2468
+ to: "2026-07-22",
2469
+ limit: 20,
2470
+ })
2471
+ .rows.some((row) => row.run_id === "RUN-source-alias"),
2472
+ ).toBe(false);
2473
+ // Filter dimensions stay in the SQL population: they must retain the
2474
+ // source-session day and never require an activity-by-activity lookup.
2475
+ expect(
2476
+ factualFilesystem
2477
+ .costMonitorFinancialActivityRows({
2478
+ from: "2026-07-23",
2479
+ to: "2026-07-23",
2480
+ phases: ["conversation"],
2481
+ providers: ["openai"],
2482
+ models: ["gpt-5"],
2483
+ })
2484
+ .map((row) => row.run_id),
2485
+ ).toContain("RUN-historical-financial");
2486
+ expect(
2487
+ factualFilesystem
2488
+ .costMonitorFinancialActivityRows({
2489
+ from: "2026-07-22",
2490
+ to: "2026-07-22",
2491
+ phases: ["planning"],
2492
+ })
2493
+ .map((row) => row.run_id),
2494
+ ).toEqual(["RUN-planning"]);
2495
+ for (const view of [
2496
+ "cost_monitor_daily",
2497
+ "cost_monitor_monthly",
2498
+ "cost_monitor_annual",
2499
+ ] as const) {
2500
+ expect(
2501
+ factualFilesystem
2502
+ .viewPage(view, { from: "2026-07-23", to: "2026-07-23", limit: 20, offset: 0 })
2503
+ .rows.some((row) => Number(row.total_tokens) === 10),
2504
+ ).toBe(true);
2505
+ // The API boundary supplies inclusive Sao Paulo days as ISO instants.
2506
+ // Do not truncate the UTC end instant into the following calendar day.
2507
+ const bounded = factualFilesystem.viewPage(view, {
2508
+ from: "2026-07-23T03:00:00.000Z",
2509
+ to: "2026-07-24T02:59:59.999Z",
2510
+ limit: 20,
2511
+ offset: 0,
2512
+ }).rows;
2513
+ expect(bounded.some((row) => Number(row.total_tokens) === 10)).toBe(true);
2514
+ expect(
2515
+ bounded.some(
2516
+ (row) =>
2517
+ String(row.period_key).startsWith("2026-07-24") && Number(row.total_tokens) === 10,
2518
+ ),
2519
+ ).toBe(false);
2520
+ // A structural day or other source financial fact may still exist on
2521
+ // the 24th, but this fixture's source-day-23 aggregate must not leak.
2522
+ expect(
2523
+ factualFilesystem
2524
+ .viewPage(view, { from: "2026-07-24", to: "2026-07-24", limit: 20, offset: 0 })
2525
+ .rows.every((row) => row.total_tokens !== 10),
2526
+ ).toBe(true);
2527
+ }
2528
+ const allFinancialRows = factualFilesystem.costMonitorFinancialActivityRows({
2529
+ from: null,
2530
+ to: null,
2531
+ });
2532
+ const factual = factualFilesystem.costMonitorFactualDurationByDay(
2533
+ { from: null, to: null },
2534
+ allFinancialRows,
2535
+ );
2536
+ // The cross-midnight session is split at São Paulo midnight and each day
2537
+ // is the union of complete source intervals, never above one local day.
2538
+ expect(factual.get("2026-07-22")).toEqual({
2539
+ known_sum: 2_580_000,
2540
+ eligible_runs: 1,
2541
+ known_runs: 1,
2542
+ });
2543
+ expect(factual.get("2026-07-23")).toEqual({
2544
+ known_sum: 5_420_000,
2545
+ eligible_runs: 6,
2546
+ known_runs: 6,
2547
+ });
2548
+ factualFilesystem.close();
2549
+ });
2550
+
2551
+ it("inherits unambiguous SPEC/PRD creation telemetry for planned Tasks without copying it to siblings", async () => {
2552
+ const root = await createSqliteTestRoot();
2553
+ roots.push(root);
2554
+ for (const kind of ["tasks", "specs", "prds"])
2555
+ await mkdir(join(root, ".agents", "records", kind, "2026", "08"), { recursive: true });
2556
+ await mkdir(join(root, ".agents", "records", "changes", "2026", "08", "CHG-UNIQUE"), {
2557
+ recursive: true,
2558
+ });
2559
+ await mkdir(join(root, ".agents", "records", "changes", "2026", "08", "CHG-SHARED-A"), {
2560
+ recursive: true,
2561
+ });
2562
+ await mkdir(join(root, ".agents", "records", "changes", "2026", "08", "CHG-SHARED-B"), {
2563
+ recursive: true,
2564
+ });
2565
+ await mkdir(join(root, ".agents", "records", "changes", "2026", "08", "CHG-PRD-ONLY"), {
2566
+ recursive: true,
2567
+ });
2568
+ await mkdir(join(root, ".agents", "records", "changes", "2026", "08", "CHG-NO-MACRO"), {
2569
+ recursive: true,
2570
+ });
2571
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
2572
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
2573
+ await writeFile(
2574
+ join(root, ".agents", "config", "project.yaml"),
2575
+ "zaip:\n enabled: true\n",
2576
+ "utf8",
2577
+ );
2578
+ const item = (id: string, status = "planned") =>
2579
+ `---\nid: ${id}\ntitle: ${id}\nstatus: ${status}\nrisk: standard\ncreated_at: 2026-08-12T12:00:00.000Z\n---\n# ${id}\n`;
2580
+ for (const id of [
2581
+ "TASK-UNIQUE",
2582
+ "TASK-SHARED-A",
2583
+ "TASK-SHARED-B",
2584
+ "TASK-PRD-ONLY",
2585
+ "TASK-NO-MACRO",
2586
+ ])
2587
+ await writeFile(
2588
+ join(root, ".agents", "records", "tasks", "2026", "08", `${id}.md`),
2589
+ item(id),
2590
+ "utf8",
2591
+ );
2592
+ for (const id of ["SPEC-UNIQUE", "SPEC-SHARED"])
2593
+ await writeFile(
2594
+ join(root, ".agents", "records", "specs", "2026", "08", `${id}.md`),
2595
+ item(id, "active"),
2596
+ "utf8",
2597
+ );
2598
+ for (const id of ["PRD-UNIQUE", "PRD-SHARED", "PRD-ONLY"])
2599
+ await writeFile(
2600
+ join(root, ".agents", "records", "prds", "2026", "08", `${id}.md`),
2601
+ item(id, "active"),
2602
+ "utf8",
2603
+ );
2604
+ const change = (id: string, taskId: string, specId: string | null, prdId: string | null) =>
2605
+ `---\nid: ${id}\ntitle: ${id}\nstatus: planned\nrisk: standard\nsource:\n type: ${prdId === null ? "task" : "prd"}\n ${prdId === null ? `task_id: ${taskId}` : `prd_id: ${prdId}`}\n${specId === null ? "" : ` spec_id: ${specId}\n`}task_ids:\n - ${taskId}\ncreated_at: 2026-08-12T12:00:00.000Z\n---\n# ${id}\n`;
2606
+ await writeFile(
2607
+ join(root, ".agents", "records", "changes", "2026", "08", "CHG-UNIQUE", "change.md"),
2608
+ change("CHG-UNIQUE", "TASK-UNIQUE", "SPEC-UNIQUE", "PRD-UNIQUE"),
2609
+ "utf8",
2610
+ );
2611
+ await writeFile(
2612
+ join(root, ".agents", "records", "changes", "2026", "08", "CHG-SHARED-A", "change.md"),
2613
+ change("CHG-SHARED-A", "TASK-SHARED-A", "SPEC-SHARED", "PRD-SHARED"),
2614
+ "utf8",
2615
+ );
2616
+ await writeFile(
2617
+ join(root, ".agents", "records", "changes", "2026", "08", "CHG-SHARED-B", "change.md"),
2618
+ change("CHG-SHARED-B", "TASK-SHARED-B", "SPEC-SHARED", "PRD-SHARED"),
2619
+ "utf8",
2620
+ );
2621
+ await writeFile(
2622
+ join(root, ".agents", "records", "changes", "2026", "08", "CHG-PRD-ONLY", "change.md"),
2623
+ change("CHG-PRD-ONLY", "TASK-PRD-ONLY", null, "PRD-ONLY"),
2624
+ "utf8",
2625
+ );
2626
+ await writeFile(
2627
+ join(root, ".agents", "records", "changes", "2026", "08", "CHG-NO-MACRO", "change.md"),
2628
+ change("CHG-NO-MACRO", "TASK-NO-MACRO", null, null),
2629
+ "utf8",
2630
+ );
2631
+ const run = (
2632
+ id: string,
2633
+ specId: string | null,
2634
+ prdId: string | null,
2635
+ tokens: number,
2636
+ cost: number,
2637
+ duration: number,
2638
+ ) => [
2639
+ {
2640
+ schema_version: 2,
2641
+ event_id: `${id}:1`,
2642
+ run_id: id,
2643
+ sequence: 1,
2644
+ occurred_at: "2026-08-12T12:00:00.000Z",
2645
+ type: "run.started",
2646
+ attribution_status: "attributed",
2647
+ phase: "planning",
2648
+ run_role: "orchestrator",
2649
+ run_purpose: "orchestration",
2650
+ requires_finalizer: false,
2651
+ execution_kind: "interaction",
2652
+ execution_eligibility: [],
2653
+ entrypoint: specId === null ? (prdId === null ? "conversation" : "prd") : "spec",
2654
+ work_type: "new",
2655
+ storage_mode: "tracked_sanitized",
2656
+ client: "codex",
2657
+ agent: "agent",
2658
+ agent_name: "agent",
2659
+ provider: "openai",
2660
+ model: "gpt-5",
2661
+ reasoning_effort: null,
2662
+ client_thread_id: id,
2663
+ client_turn_id: id,
2664
+ conversation_id: `CONV-${id}`,
2665
+ execution_id: null,
2666
+ task_id: null,
2667
+ spec_id: specId,
2668
+ prd_id: prdId,
2669
+ change_id: null,
2670
+ parent_run_id: null,
2671
+ continuation_of_run_id: null,
2672
+ retry_of_run_id: null,
2673
+ agent_invocation_id: `AINV-${id}`,
2674
+ root_agent_invocation_id: `AINV-${id}`,
2675
+ parent_agent_invocation_id: null,
2676
+ invocation_kind: "orchestrator",
2677
+ delegation_depth: 0,
2678
+ },
2679
+ {
2680
+ schema_version: 2,
2681
+ event_id: `${id}:2`,
2682
+ run_id: id,
2683
+ sequence: 2,
2684
+ occurred_at: "2026-08-12T12:01:00.000Z",
2685
+ type: "usage.recorded",
2686
+ phase: "planning",
2687
+ activity_kind: "model_usage",
2688
+ classification_source: "explicit",
2689
+ tokens: {
2690
+ input: tokens,
2691
+ cached_input: 0,
2692
+ output: 0,
2693
+ reasoning: 0,
2694
+ total: tokens,
2695
+ source: "provider_reported",
2696
+ },
2697
+ cost: { usd: cost / 5, brl: cost, fx_rate: 5 },
2698
+ provider: "openai",
2699
+ model: "gpt-5",
2700
+ source_segment_key: id,
2701
+ model_call_id: `MCALL-${id}`,
2702
+ runtime: { reasoning_effort: null, service_tier: null },
2703
+ supersedes_event_id: null,
2704
+ duration_ms: null,
2705
+ duration_provenance: null,
2706
+ },
2707
+ {
2708
+ schema_version: 2,
2709
+ event_id: `${id}:3`,
2710
+ run_id: id,
2711
+ sequence: 3,
2712
+ occurred_at: "2026-08-12T12:02:00.000Z",
2713
+ type: "run.finished",
2714
+ phase: "planning",
2715
+ status: "completed",
2716
+ duration_ms: duration,
2717
+ duration_provenance: "observed",
2718
+ },
2719
+ ];
2720
+ for (const [id, specId, prdId, tokens, cost, duration] of [
2721
+ ["RUN-UNIQUE", "SPEC-UNIQUE", "PRD-UNIQUE", 7, 3, 120_000],
2722
+ ["RUN-SHARED", "SPEC-SHARED", "PRD-SHARED", 11, 5, 60_000],
2723
+ ["RUN-PRD-ONLY", null, "PRD-ONLY", 13, 6, 90_000],
2724
+ ["RUN-NO-MACRO", null, null, 17, 8, 30_000],
2725
+ ] as const)
2726
+ await writeFile(
2727
+ join(root, ".agents", "records", "runs", "2026", "08", `${id}.jsonl`),
2728
+ `${run(id, specId, prdId, tokens, cost, duration)
2729
+ .map((record) => JSON.stringify(record))
2730
+ .join("\n")}\n`,
2731
+ "utf8",
2732
+ );
2733
+ await rebuildSqliteReadModel(root);
2734
+ const filesystem = new SqliteReadModelFileSystem(root);
2735
+ const tasks = filesystem.summaryPage("task", { from: null, to: null }, 0, 10).rows;
2736
+ expect(tasks).toContainEqual(
2737
+ expect.objectContaining({
2738
+ id: "TASK-UNIQUE",
2739
+ invocation_count: 1,
2740
+ total_duration: 62_500,
2741
+ total_tokens: 7,
2742
+ total_cost_brl: 3,
2743
+ }),
2744
+ );
2745
+ expect(tasks).toContainEqual(
2746
+ expect.objectContaining({
2747
+ id: "TASK-SHARED-A",
2748
+ invocation_count: null,
2749
+ total_duration: null,
2750
+ total_tokens: null,
2751
+ total_cost_brl: null,
2752
+ }),
2753
+ );
2754
+ expect(tasks).toContainEqual(
2755
+ expect.objectContaining({
2756
+ id: "TASK-SHARED-B",
2757
+ invocation_count: null,
2758
+ total_duration: null,
2759
+ total_tokens: null,
2760
+ total_cost_brl: null,
2761
+ }),
2762
+ );
2763
+ expect(tasks).toContainEqual(
2764
+ expect.objectContaining({
2765
+ id: "TASK-PRD-ONLY",
2766
+ invocation_count: 1,
2767
+ total_duration: 32_500,
2768
+ total_tokens: 13,
2769
+ total_cost_brl: 6,
2770
+ }),
2771
+ );
2772
+ expect(tasks).toContainEqual(
2773
+ expect.objectContaining({
2774
+ id: "TASK-NO-MACRO",
2775
+ invocation_count: null,
2776
+ total_duration: null,
2777
+ total_tokens: null,
2778
+ total_cost_brl: null,
2779
+ }),
2780
+ );
2781
+ filesystem.close();
2782
+ });
2783
+
2784
+ it("counts every root execution day and returns task duration leaders", async () => {
2785
+ const root = await createSqliteTestRoot();
2786
+ roots.push(root);
2787
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
2788
+ for (const kind of ["tasks", "specs", "prds"])
2789
+ await mkdir(join(root, ".agents", "records", kind, "2026", "08"), { recursive: true });
2790
+ await mkdir(
2791
+ join(root, ".agents", "records", "changes", "2026", "08", "CHG-ART", "artifacts", "evidence"),
2792
+ { recursive: true },
2793
+ );
2794
+ await mkdir(
2795
+ join(
2796
+ root,
2797
+ ".agents",
2798
+ "records",
2799
+ "changes",
2800
+ "2026",
2801
+ "08",
2802
+ "CHG-ART",
2803
+ "artifacts",
2804
+ "validations",
2805
+ ),
2806
+ { recursive: true },
2807
+ );
2808
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
2809
+ await writeFile(
2810
+ join(root, ".agents", "config", "project.yaml"),
2811
+ "schema_version: 1\nzaip:\n enabled: true\nproject:\n id: sqlite-test\n name: sqlite-test\n timezone: UTC\nstack:\n id: zaip-aios-stack\n overrides: {}\nclients:\n codex: true\n claude: true\ntracking:\n enabled: true\n currency: BRL\n storage_mode: tracked_sanitized\n",
2812
+ "utf8",
2813
+ );
2814
+ await writeFile(
2815
+ join(root, ".agents", "config", "framework.lock.yaml"),
2816
+ "lock_version: 1\nframework:\n version: 1.3.0\n distribution_sha256: null\nlayout:\n version: 3\nresolved_profile:\n id: null\n sha256: null\nmanaged_files: {}\n",
2817
+ "utf8",
2818
+ );
2819
+ const run = (
2820
+ id: string,
2821
+ day: string,
2822
+ task: string,
2823
+ duration: number,
2824
+ time = "12:00:00.000Z",
2825
+ spec: string | null = null,
2826
+ prd: string | null = null,
2827
+ ) => [
2828
+ {
2829
+ schema_version: 2,
2830
+ event_id: `${id}:1`,
2831
+ run_id: id,
2832
+ sequence: 1,
2833
+ occurred_at: `${day}T${time}`,
2834
+ type: "run.started",
2835
+ attribution_status: "attributed",
2836
+ phase: "execution",
2837
+ run_role: "orchestrator",
2838
+ run_purpose: "execution",
2839
+ requires_finalizer: false,
2840
+ execution_kind: "delivery",
2841
+ execution_eligibility: ["delivery"],
2842
+ entrypoint: "task",
2843
+ work_type: "new",
2844
+ storage_mode: "tracked_sanitized",
2845
+ client: "codex",
2846
+ agent: "agent",
2847
+ agent_name: "agent",
2848
+ provider: "openai",
2849
+ model: "gpt-5",
2850
+ reasoning_effort: null,
2851
+ client_thread_id: id,
2852
+ client_turn_id: id,
2853
+ conversation_id: `CONV-${id}`,
2854
+ execution_id: `EXEC-${id}`,
2855
+ task_id: task,
2856
+ spec_id: spec,
2857
+ prd_id: prd,
2858
+ change_id: null,
2859
+ parent_run_id: null,
2860
+ continuation_of_run_id: null,
2861
+ retry_of_run_id: null,
2862
+ agent_invocation_id: `AINV-${id}`,
2863
+ root_agent_invocation_id: `AINV-${id}`,
2864
+ parent_agent_invocation_id: null,
2865
+ invocation_kind: "orchestrator",
2866
+ delegation_depth: 0,
2867
+ },
2868
+ {
2869
+ schema_version: 2,
2870
+ event_id: `${id}:2`,
2871
+ run_id: id,
2872
+ sequence: 2,
2873
+ occurred_at: `${day}T12:00:01.000Z`,
2874
+ type: "usage.recorded",
2875
+ phase: "execution",
2876
+ activity_kind: "model_usage",
2877
+ classification_source: "explicit",
2878
+ tokens: {
2879
+ input: 1,
2880
+ cached_input: 0,
2881
+ output: 1,
2882
+ reasoning: 0,
2883
+ total: 2,
2884
+ source: "provider_reported",
2885
+ },
2886
+ cost: { usd: 1, brl: 5, fx_rate: 5 },
2887
+ provider: "openai",
2888
+ model: "gpt-5",
2889
+ source_segment_key: id,
2890
+ model_call_id: `MCALL-${id}`,
2891
+ runtime: { reasoning_effort: null, service_tier: null },
2892
+ supersedes_event_id: null,
2893
+ duration_ms: null,
2894
+ duration_provenance: null,
2895
+ },
2896
+ {
2897
+ schema_version: 2,
2898
+ event_id: `${id}:3`,
2899
+ run_id: id,
2900
+ sequence: 3,
2901
+ occurred_at: `${day}T12:00:02.000Z`,
2902
+ type: "run.finished",
2903
+ phase: "execution",
2904
+ status: "completed",
2905
+ duration_ms: duration,
2906
+ duration_provenance: "observed",
2907
+ },
2908
+ ];
2909
+ for (const [id, records] of [
2910
+ [
2911
+ "RUN-R1",
2912
+ run("RUN-R1", "2026-08-01", "TASK-A", 600_000, "12:00:00.000Z", "SPEC-A", "PRD-A"),
2913
+ ],
2914
+ [
2915
+ "RUN-R2",
2916
+ run("RUN-R2", "2026-08-02", "TASK-B", 1_200_000, "12:00:00.000Z", "SPEC-B", "PRD-A"),
2917
+ ],
2918
+ [
2919
+ "RUN-R3",
2920
+ run("RUN-R3", "2026-08-02", "TASK-B", 1_200_000, "12:10:00.000Z", "SPEC-B", "PRD-A"),
2921
+ ],
2922
+ ] as const) {
2923
+ await writeFile(
2924
+ join(root, ".agents", "records", "runs", "2026", "08", `${id}.jsonl`),
2925
+ `${records.map((record) => JSON.stringify(record)).join("\n")}\n`,
2926
+ "utf8",
2927
+ );
2928
+ }
2929
+ await writeFile(
2930
+ join(
2931
+ root,
2932
+ ".agents",
2933
+ "records",
2934
+ "changes",
2935
+ "2026",
2936
+ "08",
2937
+ "CHG-ART",
2938
+ "artifacts",
2939
+ "evidence",
2940
+ "EVD-1.md",
2941
+ ),
2942
+ "---\nid: EVD-1\nobserved_at: 2026-08-01T12:00:00.000Z\n---\n# Evidence\n",
2943
+ "utf8",
2944
+ );
2945
+ await writeFile(
2946
+ join(
2947
+ root,
2948
+ ".agents",
2949
+ "records",
2950
+ "changes",
2951
+ "2026",
2952
+ "08",
2953
+ "CHG-ART",
2954
+ "artifacts",
2955
+ "validations",
2956
+ "VAL-1.md",
2957
+ ),
2958
+ "---\nid: VAL-1\nexecuted_at: 2026-08-02T12:00:00.000Z\n---\n# Validation\n",
2959
+ "utf8",
2960
+ );
2961
+ for (const [kind, id] of [
2962
+ ["tasks", "TASK-NO-RUN"],
2963
+ ["specs", "SPEC-NO-RUN"],
2964
+ ["prds", "PRD-NO-RUN"],
2965
+ ] as const)
2966
+ await writeFile(
2967
+ join(root, ".agents", "records", kind, "2026", "08", `${id}.md`),
2968
+ `---\nid: ${id}\ntitle: ${id}\nstatus: draft\ncreated_at: 2026-08-03T12:00:00.000Z\n---\n# ${id}\n`,
2969
+ "utf8",
2970
+ );
2971
+ await rebuildSqliteReadModel(root);
2972
+ const filesystem = new SqliteReadModelFileSystem(root);
2973
+ expect(filesystem.firstFoldAggregate({ from: null, to: null }).execution_day_count).toBe(2);
2974
+ expect(filesystem.firstFoldAggregate({ from: null, to: null }).duration_ms).toBe(2_400_000);
2975
+ expect(filesystem.topTasks({ from: null, to: null })).toMatchObject([
2976
+ { task_id: "TASK-B", duration_ms: 1_800_000 },
2977
+ { task_id: "TASK-A", duration_ms: 600_000 },
2978
+ ]);
2979
+ expect(filesystem.summaryPage("task", { from: null, to: null }, 0, 50).rows).toContainEqual(
2980
+ expect.objectContaining({
2981
+ id: "TASK-B",
2982
+ invocation_count: 2,
2983
+ total_duration: 1_800_000,
2984
+ total_tokens: 4,
2985
+ total_cost_brl: 10,
2986
+ }),
2987
+ );
2988
+ expect(filesystem.summaryPage("spec", { from: null, to: null }, 0, 50).rows).toContainEqual(
2989
+ expect.objectContaining({
2990
+ id: "SPEC-B",
2991
+ invocation_count: 2,
2992
+ total_duration: 1_800_000,
2993
+ total_tokens: 4,
2994
+ total_cost_brl: 10,
2995
+ }),
2996
+ );
2997
+ expect(filesystem.summaryPage("prd", { from: null, to: null }, 0, 50).rows).toContainEqual(
2998
+ expect.objectContaining({
2999
+ id: "PRD-A",
3000
+ invocation_count: 3,
3001
+ total_duration: 2_400_000,
3002
+ total_tokens: 6,
3003
+ total_cost_brl: 15,
3004
+ }),
3005
+ );
3006
+ expect(filesystem.summaryPage("task", { from: null, to: null }, 0, 50).rows).toContainEqual(
3007
+ expect.objectContaining({
3008
+ id: "TASK-NO-RUN",
3009
+ invocation_count: null,
3010
+ total_duration: null,
3011
+ total_tokens: null,
3012
+ total_cost_brl: null,
3013
+ }),
3014
+ );
3015
+ const allTime = filesystem.firstFoldAllTime();
3016
+ expect(allTime.aggregate).toMatchObject({
3017
+ start_day: "2026-08-01",
3018
+ end_day: "2026-08-03",
3019
+ task_count: 3,
3020
+ spec_count: 3,
3021
+ prd_count: 2,
3022
+ total_run_count: 3,
3023
+ duration_ms: 2_400_000,
3024
+ total_tokens: 6,
3025
+ cost_brl: 15,
3026
+ execution_day_count: 2,
3027
+ });
3028
+ expect(allTime.task_status_counts).toEqual({ draft: 3 });
3029
+ // The served quick projection uses this deliberately lean subset. It must
3030
+ // remain factually identical to the rich projection for the six cards and
3031
+ // two first-fold charts, while leaving rankings and macro metrics lazy.
3032
+ const viewport = filesystem.firstFoldViewportAllTime();
3033
+ expect(viewport.aggregate).toEqual(allTime.aggregate);
3034
+ expect(viewport.task_status_counts).toEqual(allTime.task_status_counts);
3035
+ expect(viewport.task_risk_counts).toEqual(allTime.task_risk_counts);
3036
+ expect(allTime.top_tasks).toMatchObject([
3037
+ { rank: 1, task_id: "TASK-B", total_duration_ms: 1_800_000 },
3038
+ { rank: 2, task_id: "TASK-A", total_duration_ms: 600_000 },
3039
+ ]);
3040
+ expect(filesystem.firstFoldTopTasksAllTime()).toEqual(allTime.top_tasks);
3041
+ expect(
3042
+ projectAllTimeTopTasks([
3043
+ {
3044
+ task_id: "TASK-NO-USAGE",
3045
+ total_duration_ms: null,
3046
+ total_tokens: null,
3047
+ total_cost_usd: null,
3048
+ total_cost_brl: null,
3049
+ total_run_count: 1,
3050
+ },
3051
+ ]),
3052
+ ).toEqual([
3053
+ {
3054
+ task_id: "TASK-NO-USAGE",
3055
+ duration_ms: null,
3056
+ total_tokens: null,
3057
+ total_cost_usd: null,
3058
+ cost_brl: null,
3059
+ run_count: 1,
3060
+ },
3061
+ ]);
3062
+ const app = createDashboardApp(root, "test-session", undefined, { filesystem });
3063
+ const allTimeQuick = await app.request("/api/v2/operations?scope=quick", {
3064
+ headers: { "x-zaip-session": "test-session" },
3065
+ });
3066
+ const boundedQuick = await app.request(
3067
+ "/api/v2/operations?scope=quick&from=2026-08-02&to=2026-08-02",
3068
+ { headers: { "x-zaip-session": "test-session" } },
3069
+ );
3070
+ const topTasks = (await allTimeQuick.json()) as {
3071
+ summary: { overview_metrics?: { top_tasks?: unknown[] } };
3072
+ };
3073
+ const boundedTopTasks = (await boundedQuick.json()) as {
3074
+ summary: { overview_metrics?: { top_tasks?: unknown[] } };
3075
+ };
3076
+ expect(topTasks.summary.overview_metrics?.top_tasks).toEqual(
3077
+ expect.arrayContaining([
3078
+ expect.objectContaining({
3079
+ task_id: "TASK-B",
3080
+ duration_ms: 1_800_000,
3081
+ total_tokens: 4,
3082
+ cost_brl: 10,
3083
+ }),
3084
+ ]),
3085
+ );
3086
+ expect(boundedTopTasks.summary.overview_metrics?.top_tasks).toEqual([
3087
+ expect.objectContaining({
3088
+ task_id: "TASK-B",
3089
+ duration_ms: 1_800_000,
3090
+ total_tokens: 4,
3091
+ cost_brl: 10,
3092
+ }),
3093
+ ]);
3094
+ // A new calendar month after this immutable snapshot has no factual rows.
3095
+ // It must be a fast exact empty projection, not a slow scan caused by the
3096
+ // default date range changing at midnight on the first day of the month.
3097
+ const nextMonthQuick = await app.request(
3098
+ "/api/v2/operations?scope=quick&from=2026-09-01&to=2026-09-01",
3099
+ { headers: { "x-zaip-session": "test-session" } },
3100
+ );
3101
+ const nextMonth = (await nextMonthQuick.json()) as {
3102
+ summary: {
3103
+ run_count: number;
3104
+ task_count: number;
3105
+ overview_metrics: { tokens: number | null; duration_ms: number | null };
3106
+ };
3107
+ };
3108
+ expect(nextMonth.summary).toMatchObject({
3109
+ run_count: 0,
3110
+ task_count: 0,
3111
+ overview_metrics: { tokens: null, duration_ms: null },
3112
+ });
3113
+ expect(allTime.macro_metrics.specs).toMatchObject({
3114
+ entity_count: 3,
3115
+ status_counts: { planning: 3 },
3116
+ classification_counts: { structured: 2 },
3117
+ total_run_count: 3,
3118
+ total_duration_ms: 2_400_000,
3119
+ total_tokens: 6,
3120
+ total_cost_brl: 15,
3121
+ average_duration_ms: 800_000,
3122
+ });
3123
+ expect(allTime.macro_metrics.specs.top_entities.slice(0, 2)).toMatchObject([
3124
+ { rank: 1, entity_id: "SPEC-B", total_duration_ms: 1_800_000 },
3125
+ { rank: 2, entity_id: "SPEC-A", total_duration_ms: 600_000 },
3126
+ ]);
3127
+ expect(allTime.macro_metrics.prds).toMatchObject({
3128
+ entity_count: 2,
3129
+ status_counts: { definition: 2 },
3130
+ classification_counts: { structured: 1 },
3131
+ total_run_count: 3,
3132
+ total_duration_ms: 2_400_000,
3133
+ total_tokens: 6,
3134
+ total_cost_brl: 15,
3135
+ average_duration_ms: 1_200_000,
3136
+ });
3137
+ const operationDays = filesystem.dailyPeriods({ from: null, to: null });
3138
+ expect(Object.keys(operationDays)).toEqual(["2026-08-01", "2026-08-02", "2026-08-03"]);
3139
+ expect(operationDays["2026-08-02"]).toMatchObject({
3140
+ runs: 2,
3141
+ specs: 1,
3142
+ tasks: 1,
3143
+ validations: 1,
3144
+ evidences: 0,
3145
+ usage: {
3146
+ total_tokens: { known_sum: 4, unknown: 0 },
3147
+ cost: { known_sum: 10, unknown: 0 },
3148
+ },
3149
+ duration: { known_sum: 1_800_000, unknown: 0 },
3150
+ });
3151
+ expect(filesystem.viewPage("operations_daily", { limit: 50 }).rows).toContainEqual(
3152
+ expect.objectContaining({
3153
+ day: "2026-08-02",
3154
+ task_count: 1,
3155
+ spec_count: 1,
3156
+ validation_count: 1,
3157
+ evidence_count: 0,
3158
+ total_run_count: 2,
3159
+ operations_total_duration: 1_800_000,
3160
+ total_tokens: 4,
3161
+ total_cost_brl: 10,
3162
+ }),
3163
+ );
3164
+ for (const [view, key] of [
3165
+ ["operations_monthly", "2026-08"],
3166
+ ["operations_annual", "2026"],
3167
+ ] as const) {
3168
+ expect(filesystem.viewPage(view, { limit: 50 }).rows).toContainEqual(
3169
+ expect.objectContaining({
3170
+ period_key: key,
3171
+ prd_count: 2,
3172
+ spec_count: 3,
3173
+ task_count: 3,
3174
+ validation_count: 1,
3175
+ evidence_count: 1,
3176
+ total_run_count: 3,
3177
+ operations_total_duration: 2_400_000,
3178
+ total_tokens: 6,
3179
+ total_cost_brl: 15,
3180
+ }),
3181
+ );
3182
+ }
3183
+ for (const view of ["operations_monthly", "operations_annual"] as const) {
3184
+ expect(
3185
+ filesystem.viewPage(view, {
3186
+ from: "2026-08-02T03:00:00.000Z",
3187
+ to: "2026-08-02T23:59:59.999Z",
3188
+ limit: 50,
3189
+ }).rows,
3190
+ ).toEqual([
3191
+ expect.objectContaining({
3192
+ start_day: "2026-08-02",
3193
+ end_day: "2026-08-02",
3194
+ total_run_count: 2,
3195
+ invocation_count: 2,
3196
+ operations_total_duration: 1_800_000,
3197
+ total_tokens: 4,
3198
+ total_cost_usd: 2,
3199
+ total_cost_brl: 10,
3200
+ }),
3201
+ ]);
3202
+ }
3203
+ for (const [view, key, counts] of [
3204
+ [
3205
+ "cost_monitor_daily",
3206
+ "2026-08-02",
3207
+ { prd_count: 0, spec_count: 1, task_count: 1, validation_count: 1, evidence_count: 0 },
3208
+ ],
3209
+ [
3210
+ "cost_monitor_monthly",
3211
+ "2026-08",
3212
+ { prd_count: 2, spec_count: 3, task_count: 3, validation_count: 1, evidence_count: 1 },
3213
+ ],
3214
+ [
3215
+ "cost_monitor_annual",
3216
+ "2026",
3217
+ { prd_count: 2, spec_count: 3, task_count: 3, validation_count: 1, evidence_count: 1 },
3218
+ ],
3219
+ ] as const) {
3220
+ expect(filesystem.viewPage(view, { limit: 50 }).rows).toContainEqual(
3221
+ expect.objectContaining({ key, ...counts }),
3222
+ );
3223
+ }
3224
+ expect(filesystem.viewPage("cost_monitor_daily", { limit: 50 }).rows).toContainEqual(
3225
+ expect.objectContaining({ key: "2026-08-03", prd_count: 1, spec_count: 1, task_count: 1 }),
3226
+ );
3227
+ expect(filesystem.viewPage("cost_monitor_monthly", { limit: 50 }).rows).toContainEqual(
3228
+ expect.objectContaining({ key: "2026-08", prd_count: 2, spec_count: 3, task_count: 3 }),
3229
+ );
3230
+ expect(filesystem.viewPage("cost_monitor_annual", { limit: 50 }).rows).toContainEqual(
3231
+ expect.objectContaining({ key: "2026", prd_count: 2, spec_count: 3, task_count: 3 }),
3232
+ );
3233
+ const annualCostPage = filesystem.viewPage("cost_monitor_annual", { limit: 50 });
3234
+ const dailyCostRows = filesystem.viewPage("cost_monitor_daily", { limit: 50 }).rows;
3235
+ const pagedDailyCostPage = filesystem.viewPage("cost_monitor_daily", { limit: 1 });
3236
+ expect(pagedDailyCostPage).toMatchObject({
3237
+ total_count: dailyCostRows.length,
3238
+ rows: [expect.any(Object)],
3239
+ });
3240
+ expect(pagedDailyCostPage.totals).toMatchObject({
3241
+ total_tokens: dailyCostRows.reduce((sum, row) => sum + Number(row.total_tokens ?? 0), 0),
3242
+ total_cost_brl: dailyCostRows.reduce((sum, row) => sum + Number(row.total_cost_brl ?? 0), 0),
3243
+ total_duration: dailyCostRows.reduce((sum, row) => sum + Number(row.total_duration ?? 0), 0),
3244
+ });
3245
+ expect(annualCostPage).toMatchObject({
3246
+ total_count: 1,
3247
+ rows: [expect.objectContaining({ key: "2026" })],
3248
+ });
3249
+ const annualCostRow = annualCostPage.rows[0];
3250
+ if (annualCostRow === undefined) throw new Error("Expected the consolidated 2026 Cost row");
3251
+ for (const metric of [
3252
+ "prd_count",
3253
+ "spec_count",
3254
+ "task_count",
3255
+ "validation_count",
3256
+ "evidence_count",
3257
+ "total_run_count",
3258
+ "total_duration",
3259
+ "duration_known",
3260
+ "total_tokens",
3261
+ "total_cost_usd",
3262
+ "total_cost_brl",
3263
+ ] as const) {
3264
+ const observed = dailyCostRows
3265
+ .map((row) => row[metric])
3266
+ .filter((value): value is number => typeof value === "number");
3267
+ expect(annualCostRow[metric]).toBe(
3268
+ observed.length === 0 ? null : observed.reduce((sum, value) => sum + value, 0),
3269
+ );
3270
+ }
3271
+ expect(
3272
+ filesystem.viewPage("cost_monitor_daily", { limit: 50, provider: "openai" }).rows,
3273
+ ).toContainEqual(
3274
+ expect.objectContaining({
3275
+ key: "2026-08-02",
3276
+ prd_count: 0,
3277
+ spec_count: 1,
3278
+ task_count: 1,
3279
+ validation_count: 1,
3280
+ evidence_count: 0,
3281
+ }),
3282
+ );
3283
+ for (const [view, entityCount] of [
3284
+ ["cost_monitor_tasks", 2],
3285
+ ["cost_monitor_specs", 3],
3286
+ ["cost_monitor_prds", 2],
3287
+ ["cost_monitor_conversations", 3],
3288
+ ] as const) {
3289
+ const page = filesystem.viewPage(view, { limit: 1 });
3290
+ // The subtotal is computed before pagination and reconciles the full
3291
+ // filtered population, rather than the single visible row.
3292
+ expect(page).toMatchObject({ total_count: entityCount, rows: [expect.any(Object)] });
3293
+ expect(page.totals).toEqual({
3294
+ entity_count: entityCount,
3295
+ invocation_count: 3,
3296
+ total_duration: 2_400_000,
3297
+ total_tokens: 6,
3298
+ total_cost_usd: 3,
3299
+ total_cost_brl: 15,
3300
+ });
3301
+ }
3302
+ expect(
3303
+ filesystem.viewPage("cost_monitor_tasks", {
3304
+ from: "2026-08-02",
3305
+ to: "2026-08-02",
3306
+ query: "TASK-B",
3307
+ limit: 1,
3308
+ }).totals,
3309
+ ).toEqual({
3310
+ entity_count: 1,
3311
+ invocation_count: 2,
3312
+ total_duration: 1_800_000,
3313
+ total_tokens: 4,
3314
+ total_cost_usd: 2,
3315
+ total_cost_brl: 10,
3316
+ });
3317
+ // The compact Cost Monitor tables share the canonical macro population
3318
+ // with their big numbers. A macro with no direct invocation must remain
3319
+ // paginable and retain unavailable financial facts as null.
3320
+ expect(
3321
+ filesystem.viewPage("cost_monitor_specs", { query: "SPEC-NO-RUN", limit: 1 }),
3322
+ ).toMatchObject({
3323
+ total_count: 1,
3324
+ totals: {
3325
+ entity_count: 1,
3326
+ invocation_count: 0,
3327
+ total_duration: null,
3328
+ total_tokens: null,
3329
+ total_cost_brl: null,
3330
+ },
3331
+ rows: [
3332
+ expect.objectContaining({
3333
+ id: "SPEC-NO-RUN",
3334
+ title: "SPEC-NO-RUN",
3335
+ invocation_count: 0,
3336
+ total_duration: null,
3337
+ total_tokens: null,
3338
+ total_cost_brl: null,
3339
+ }),
3340
+ ],
3341
+ });
3342
+ expect(
3343
+ filesystem.viewPage("cost_monitor_prds", { query: "PRD-NO-RUN", limit: 1 }),
3344
+ ).toMatchObject({
3345
+ total_count: 1,
3346
+ totals: {
3347
+ entity_count: 1,
3348
+ invocation_count: 0,
3349
+ total_duration: null,
3350
+ total_tokens: null,
3351
+ total_cost_brl: null,
3352
+ },
3353
+ rows: [
3354
+ expect.objectContaining({
3355
+ id: "PRD-NO-RUN",
3356
+ title: "PRD-NO-RUN",
3357
+ invocation_count: 0,
3358
+ total_duration: null,
3359
+ total_tokens: null,
3360
+ total_cost_brl: null,
3361
+ }),
3362
+ ],
3363
+ });
3364
+ for (const view of [
3365
+ "cost_monitor_tasks",
3366
+ "cost_monitor_specs",
3367
+ "cost_monitor_prds",
3368
+ "cost_monitor_conversations",
3369
+ ] as const) {
3370
+ const filtered = filesystem.viewPage(view, {
3371
+ phase: "execution",
3372
+ provider: "openai",
3373
+ model: "gpt-5",
3374
+ limit: 1,
3375
+ });
3376
+ // Dimensional membership and the subtotal share one pre-pagination
3377
+ // invocation population for every compact lifecycle view.
3378
+ expect(filtered.rows).toHaveLength(1);
3379
+ expect(filtered.totals).toMatchObject({
3380
+ invocation_count: 3,
3381
+ total_tokens: 6,
3382
+ total_cost_brl: 15,
3383
+ });
3384
+ }
3385
+ expect(filesystem.viewPage("cost_monitor_tasks", { workType: "new", limit: 1 })).toMatchObject({
3386
+ total_count: 0,
3387
+ rows: [],
3388
+ });
3389
+ expect(filesystem.firstFoldAllTime()).toBe(allTime);
3390
+ filesystem.close();
3391
+
3392
+ // Period membership follows the canonical macro day, not an invocation
3393
+ // that happened on another day. Search accepts either the macro ID or
3394
+ // title, and totals are still calculated before its one-row page.
3395
+ await writeFile(
3396
+ join(root, ".agents", "records", "specs", "2026", "08", "SPEC-CANONICAL-DAY.md"),
3397
+ "---\nid: SPEC-CANONICAL-DAY\ntitle: Macro by canonical title\nstatus: draft\ncreated_at: 2026-08-03T12:00:00.000Z\n---\n# Macro\n",
3398
+ "utf8",
3399
+ );
3400
+ await writeFile(
3401
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-CANONICAL-DAY.jsonl"),
3402
+ `${run(
3403
+ "RUN-CANONICAL-DAY",
3404
+ "2026-08-02",
3405
+ "TASK-CANONICAL-DAY",
3406
+ 1_000,
3407
+ "12:30:00.000Z",
3408
+ "SPEC-CANONICAL-DAY",
3409
+ )
3410
+ .map((record) => JSON.stringify(record))
3411
+ .join("\n")}\n`,
3412
+ "utf8",
3413
+ );
3414
+ await writeFile(
3415
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-CANONICAL-DAY-LATE.jsonl"),
3416
+ `${run(
3417
+ "RUN-CANONICAL-DAY-LATE",
3418
+ "2026-08-04",
3419
+ "TASK-CANONICAL-DAY-LATE",
3420
+ 1_000,
3421
+ "12:30:00.000Z",
3422
+ "SPEC-CANONICAL-DAY",
3423
+ )
3424
+ .map((record) => JSON.stringify(record))
3425
+ .join("\n")}\n`,
3426
+ "utf8",
3427
+ );
3428
+ await rebuildSqliteReadModel(root);
3429
+ const canonicalDayFilesystem = new SqliteReadModelFileSystem(root);
3430
+ expect(
3431
+ canonicalDayFilesystem.viewPage("cost_monitor_specs", {
3432
+ from: "2026-08-03",
3433
+ to: "2026-08-03",
3434
+ query: "canonical title",
3435
+ limit: 1,
3436
+ }),
3437
+ ).toMatchObject({
3438
+ total_count: 1,
3439
+ totals: { entity_count: 1, invocation_count: 0, total_tokens: null, total_cost_brl: null },
3440
+ rows: [
3441
+ expect.objectContaining({
3442
+ id: "SPEC-CANONICAL-DAY",
3443
+ invocation_count: 0,
3444
+ total_tokens: null,
3445
+ total_cost_brl: null,
3446
+ }),
3447
+ ],
3448
+ });
3449
+ expect(
3450
+ canonicalDayFilesystem.viewPage("cost_monitor_specs", {
3451
+ from: "2026-08-02",
3452
+ to: "2026-08-02",
3453
+ query: "SPEC-CANONICAL-DAY",
3454
+ limit: 1,
3455
+ }),
3456
+ ).toMatchObject({ total_count: 0, rows: [] });
3457
+ canonicalDayFilesystem.close();
3458
+
3459
+ // A large result set keeps its subtotal for all 270 matching Tasks while
3460
+ // the page transports only 50 records.
3461
+ for (let index = 0; index < 270; index += 1) {
3462
+ const id = `RUN-PAGE-${index}`;
3463
+ await writeFile(
3464
+ join(root, ".agents", "records", "runs", "2026", "08", `${id}.jsonl`),
3465
+ `${run(id, "2026-08-04", `TASK-PAGE-${index}`, 1_000)
3466
+ .map((record) => JSON.stringify(record))
3467
+ .join("\n")}\n`,
3468
+ "utf8",
3469
+ );
3470
+ }
3471
+ await rebuildSqliteReadModel(root);
3472
+ const pageFilesystem = new SqliteReadModelFileSystem(root);
3473
+ const paged = pageFilesystem.viewPage("cost_monitor_tasks", {
3474
+ from: "2026-08-04",
3475
+ to: "2026-08-04",
3476
+ query: "TASK-PAGE-",
3477
+ offset: 50,
3478
+ limit: 50,
3479
+ });
3480
+ expect(paged).toMatchObject({ total_count: 270, has_more: true });
3481
+ expect(paged.rows).toHaveLength(50);
3482
+ expect(paged.totals).toEqual({
3483
+ entity_count: 270,
3484
+ invocation_count: 270,
3485
+ total_duration: 1_000,
3486
+ total_tokens: 540,
3487
+ total_cost_usd: 270,
3488
+ total_cost_brl: 1_350,
3489
+ });
3490
+ pageFilesystem.close();
3491
+ });
3492
+
3493
+ it("rebuilds a disposable snapshot and serves ZAIP records from SQLite", async () => {
3494
+ const root = await createSqliteTestRoot();
3495
+ roots.push(root);
3496
+ await mkdir(join(root, ".agents", "records", "tasks"), { recursive: true });
3497
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
3498
+ await writeFile(join(root, ".agents", "records", "tasks", "task.md"), "# task\n", "utf8");
3499
+ await writeFile(
3500
+ join(root, ".agents", "config", "project.yaml"),
3501
+ "zaip:\n enabled: true\n",
3502
+ "utf8",
3503
+ );
3504
+ const result = await rebuildSqliteReadModel(root);
3505
+ const filesystem = new SqliteReadModelFileSystem(root);
3506
+ expect(result.files).toBe(2);
3507
+ await expect(filesystem.readText(".agents/records/tasks/task.md")).resolves.toContain("task");
3508
+ await expect(filesystem.listFiles(".agents/records", ".md")).resolves.toEqual([
3509
+ ".agents/records/tasks/task.md",
3510
+ ]);
3511
+ filesystem.close();
3512
+ });
3513
+
3514
+ it("serves canonical Task trace inputs without lifecycle manifests", async () => {
3515
+ const root = await createSqliteTestRoot();
3516
+ roots.push(root);
3517
+ await mkdir(join(root, ".agents", "records", "tasks", "2026", "08"), {
3518
+ recursive: true,
3519
+ });
3520
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
3521
+ await writeFile(
3522
+ join(root, ".agents", "config", "project.yaml"),
3523
+ "zaip:\n enabled: true\n",
3524
+ "utf8",
3525
+ );
3526
+ await writeFile(
3527
+ join(root, ".agents", "records", "tasks", "2026", "08", "TASK-TRACE.md"),
3528
+ "---\nid: TASK-TRACE\ntitle: Canonical trace\nstatus: completed\nrisk: standard\ncreated_at: 2026-08-01T12:00:00.000Z\n---\n# Canonical trace\n",
3529
+ "utf8",
3530
+ );
3531
+ await rebuildSqliteReadModel(root);
3532
+ const filesystem = new SqliteReadModelFileSystem(root);
3533
+ expect(filesystem.canonicalTraceFiles(["TASK-TRACE"])).toMatchObject([
3534
+ { path: ".agents/records/tasks/2026/08/TASK-TRACE.md" },
3535
+ ]);
3536
+ expect(filesystem.taskTraceUsage("TASK-TRACE")).toEqual({
3537
+ run_count: 0,
3538
+ duration_ms: { value: null, known_runs: 0, unknown_runs: 0, coverage_ratio: 1 },
3539
+ total_tokens: { value: null, known_runs: 0, unknown_runs: 0, coverage_ratio: 1 },
3540
+ cost: {
3541
+ value: null,
3542
+ known_runs: 0,
3543
+ unknown_runs: 0,
3544
+ coverage_ratio: 1,
3545
+ currency: null,
3546
+ },
3547
+ });
3548
+ filesystem.close();
3549
+ });
3550
+
3551
+ it("invalidates a nested canonical source change and ignores legacy aggregate telemetry", async () => {
3552
+ const root = await createSqliteTestRoot();
3553
+ roots.push(root);
3554
+ const nested = join(root, ".agents", "records", "tasks", "nested", "task.md");
3555
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
3556
+ await mkdir(join(root, ".agents", "records", "tasks", "nested"), { recursive: true });
3557
+ await writeFile(
3558
+ join(root, ".agents", "config", "project.yaml"),
3559
+ "zaip:\n enabled: true\n",
3560
+ "utf8",
3561
+ );
3562
+ await writeFile(nested, "# before\n", "utf8");
3563
+ await rebuildSqliteReadModel(root);
3564
+ await writeFile(nested, "# after\n", "utf8");
3565
+ const changedAt = new Date(Date.now() + 2_000);
3566
+ await utimes(nested, changedAt, changedAt);
3567
+ await rebuildSqliteReadModel(root);
3568
+ const updated = new SqliteReadModelFileSystem(root);
3569
+ await expect(updated.readText(".agents/records/tasks/nested/task.md")).resolves.toContain(
3570
+ "after",
3571
+ );
3572
+ updated.close();
3573
+
3574
+ const duplicate = join(
3575
+ root,
3576
+ ".agents",
3577
+ "records",
3578
+ "telemetry",
3579
+ "2026",
3580
+ "08",
3581
+ "writers",
3582
+ "w",
3583
+ "chunks",
3584
+ "c.jsonl",
3585
+ );
3586
+ await mkdir(
3587
+ join(root, ".agents", "records", "telemetry", "2026", "08", "writers", "w", "chunks"),
3588
+ { recursive: true },
3589
+ );
3590
+ await writeFile(
3591
+ join(root, ".agents", "records", "telemetry", "2026", "08", "writers", "w", "manifest.json"),
3592
+ JSON.stringify({
3593
+ chunks: [{ path: ".agents/records/telemetry/2026/08/writers/w/chunks/c.jsonl" }],
3594
+ }),
3595
+ "utf8",
3596
+ );
3597
+ await writeFile(
3598
+ duplicate,
3599
+ `${JSON.stringify({ agent_invocation_id: "same", summary_hash: "one" })}\n${JSON.stringify({ agent_invocation_id: "same", summary_hash: "two" })}\n`,
3600
+ "utf8",
3601
+ );
3602
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ files: 2 });
3603
+ expect(await readFile(nested, "utf8")).toContain("after");
3604
+ const retained = new SqliteReadModelFileSystem(root);
3605
+ await expect(retained.readText(".agents/records/tasks/nested/task.md")).resolves.toContain(
3606
+ "after",
3607
+ );
3608
+ retained.close();
3609
+ });
3610
+
3611
+ it("does not promote legacy aggregate telemetry into canonical activity facts", async () => {
3612
+ const root = await createSqliteTestRoot();
3613
+ roots.push(root);
3614
+ await mkdir(
3615
+ join(root, ".agents", "records", "telemetry", "2026", "08", "writers", "w", "chunks"),
3616
+ { recursive: true },
3617
+ );
3618
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
3619
+ await writeFile(
3620
+ join(root, ".agents", "config", "project.yaml"),
3621
+ "zaip:\n enabled: true\n",
3622
+ "utf8",
3623
+ );
3624
+ const summary = {
3625
+ agent_invocation_id: "root",
3626
+ summary_hash: "hash",
3627
+ root_agent_invocation_id: "root",
3628
+ parent_agent_invocation_id: null,
3629
+ execution_id: null,
3630
+ task_id: null,
3631
+ spec_id: null,
3632
+ prd_id: null,
3633
+ change_id: null,
3634
+ run_id: "RUN-1",
3635
+ occurred_at: "2026-08-01T12:00:00.000Z",
3636
+ invocation_kind: "orchestrator",
3637
+ delegation_depth: 0,
3638
+ source_event_count: 1,
3639
+ historical_cost: {
3640
+ estimated_usd: 2,
3641
+ estimated_brl: 10,
3642
+ fx_rate: 5,
3643
+ pricing_model: "model",
3644
+ pricing_source: "canonical",
3645
+ },
3646
+ quantitative_metrics: { duration_ms: { known_sum: 5 }, tokens_total: { known_sum: 0 } },
3647
+ usage_segments: [
3648
+ {
3649
+ provider: "openai",
3650
+ model: "model",
3651
+ service_tier: "default",
3652
+ reasoning_effort: "medium",
3653
+ tokens: { input: 0, cached_input: 0, output: 0, reasoning: 0, total: 0 },
3654
+ historical_cost: {
3655
+ estimated_usd: 2,
3656
+ estimated_brl: 10,
3657
+ fx_rate: 5,
3658
+ pricing_model: "model",
3659
+ pricing_source: "canonical",
3660
+ },
3661
+ },
3662
+ ],
3663
+ };
3664
+ await writeFile(
3665
+ join(
3666
+ root,
3667
+ ".agents",
3668
+ "records",
3669
+ "telemetry",
3670
+ "2026",
3671
+ "08",
3672
+ "writers",
3673
+ "w",
3674
+ "chunks",
3675
+ "c.jsonl",
3676
+ ),
3677
+ `${JSON.stringify(summary)}\n`,
3678
+ "utf8",
3679
+ );
3680
+ await writeFile(
3681
+ join(root, ".agents", "records", "telemetry", "2026", "08", "writers", "w", "manifest.json"),
3682
+ JSON.stringify({
3683
+ chunks: [{ path: ".agents/records/telemetry/2026/08/writers/w/chunks/c.jsonl" }],
3684
+ }),
3685
+ "utf8",
3686
+ );
3687
+ await rebuildSqliteReadModel(root);
3688
+ const filesystem = new SqliteReadModelFileSystem(root);
3689
+ expect(filesystem.firstFoldAggregate({ from: "2026-08-01", to: "2026-08-01" })).toEqual({
3690
+ days: 0,
3691
+ total_run_count: 0,
3692
+ agent_invocation_count: 0,
3693
+ llm_call_count: null,
3694
+ duration_ms: null,
3695
+ total_tokens: null,
3696
+ cost_brl: null,
3697
+ execution_day_count: 0,
3698
+ });
3699
+ const database = new DatabaseSync(resolve(root, ".local/dashboard-sqlite/read-model.sqlite"), {
3700
+ readOnly: true,
3701
+ });
3702
+ const columns = (table: string) =>
3703
+ (database.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>).map(
3704
+ (row) => row.name,
3705
+ );
3706
+ expect(columns("zaip_vw_daily_aggregates")).toEqual(
3707
+ expect.arrayContaining([
3708
+ "day",
3709
+ "invocation_count",
3710
+ "total_duration",
3711
+ "total_tokens",
3712
+ "total_cost_usd",
3713
+ "total_cost_brl",
3714
+ ]),
3715
+ );
3716
+ expect(columns("zaip_vw_daily_aggregates")).not.toContain(["sub", "run_count"].join(""));
3717
+ expect(columns("zaip_vw_invocations")).toEqual(
3718
+ expect.arrayContaining([
3719
+ "cost_usd",
3720
+ "cost_brl",
3721
+ "cost_method",
3722
+ "pricing_model",
3723
+ "pricing_source",
3724
+ "fx_rate",
3725
+ ]),
3726
+ );
3727
+ expect(columns("zaip_vw_invocations")).not.toEqual(
3728
+ expect.arrayContaining([
3729
+ "coverage",
3730
+ "_coverage",
3731
+ "usage_segments_json",
3732
+ "estimated_usd",
3733
+ "estimated_brl",
3734
+ ]),
3735
+ );
3736
+ expect(columns("zaip_vw_invocation_usage")).not.toContain("coverage");
3737
+ expect(database.prepare("SELECT COUNT(*) AS count FROM zaip_vw_invocations").get()).toEqual({
3738
+ count: 0,
3739
+ });
3740
+ database.close();
3741
+ filesystem.close();
3742
+ });
3743
+ });