@zaip-ai/cli 1.0.7 → 1.0.8

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 (410) 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 +38 -3
  10. package/dist/services/agent-adapters.js.map +1 -1
  11. package/dist/services/agent-adapters.test.js +8 -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 +2 -0
  45. package/dist/services/template-manifest.js.map +1 -1
  46. package/package.json +1 -1
  47. package/templates/.agents/config/agents/catalog.yaml +310 -10
  48. package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +30 -0
  49. package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +5 -5
  50. package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +1 -1
  51. package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +30 -0
  52. package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +30 -0
  53. package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +30 -0
  54. package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +8 -8
  55. package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +5 -3
  56. package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +30 -0
  57. package/templates/.agents/config/project.yaml +1 -1
  58. package/templates/.agents/dashboard/.zaip-managed.json +659 -163
  59. package/templates/.agents/dashboard/DATA-DICTIONARY.md +2227 -0
  60. package/templates/.agents/dashboard/README.md +48 -97
  61. package/templates/.agents/dashboard/apps/frontend/dist/assets/CostCalculationGuidePage-Z60df2r-.js +1 -0
  62. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-Cv-JcoxP.js → DashboardHelpDialog-Cs6dzDmW.js} +294 -109
  63. package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-Coc6pA-1.js +1 -0
  64. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-QE1k9yZB.css +1 -0
  65. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-_Pf5DA_A.js +3 -0
  66. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BVlz3DLZ.js +1 -0
  67. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-CaKwQP43.css +1 -0
  68. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-BZ5WdaMN.css +1 -0
  69. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Cf9ctPVt.js +68 -0
  70. package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-C98LGC-Q.js → dashboard-localization-CLJ0lT9v.js} +1 -1
  71. package/templates/.agents/dashboard/apps/frontend/dist/assets/icons-vendor-DFBuc_qp.js +1 -0
  72. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8A4DcNXM.js +2 -0
  73. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DK_enB5Z.css +1 -0
  74. package/templates/.agents/dashboard/apps/frontend/dist/assets/react-vendor-CCQOHdVI.js +9 -0
  75. package/templates/.agents/dashboard/apps/frontend/dist/assets/rolldown-runtime-D9-fqq9M.js +1 -0
  76. package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-DFY7ZDlu.css +1 -0
  77. package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-pC2_0J8x.js +1 -0
  78. package/templates/.agents/dashboard/apps/frontend/dist/index.html +9 -2
  79. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  80. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +952 -595
  81. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingBoundary.tsx +48 -0
  82. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingPanels.tsx +2 -0
  83. package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +247 -0
  84. package/templates/.agents/dashboard/apps/frontend/src/AppRequestGuards.tsx +95 -0
  85. package/templates/.agents/dashboard/apps/frontend/src/AppSetupRoutes.tsx +87 -0
  86. package/templates/.agents/dashboard/apps/frontend/src/CostCalculationGuidePage.tsx +641 -0
  87. package/templates/.agents/dashboard/apps/frontend/src/DashboardAlternateContent.tsx +159 -0
  88. package/templates/.agents/dashboard/apps/frontend/src/DashboardCapabilityNav.tsx +13 -13
  89. package/templates/.agents/dashboard/apps/frontend/src/DashboardGlobalSettings.tsx +56 -0
  90. package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.tsx +257 -313
  91. package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +149 -0
  92. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +35 -0
  93. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +111 -12
  94. package/templates/.agents/dashboard/apps/frontend/src/app-preferences.ts +15 -0
  95. package/templates/.agents/dashboard/apps/frontend/src/app-session-token.ts +20 -0
  96. package/templates/.agents/dashboard/apps/frontend/src/cost-calculation-guide.css +267 -0
  97. package/templates/.agents/dashboard/apps/frontend/src/cost-calculation-guide.test.ts +69 -0
  98. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +70 -0
  99. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +111 -0
  100. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +432 -13
  101. package/templates/.agents/dashboard/apps/frontend/src/dashboard-localization.ts +1 -1
  102. package/templates/.agents/dashboard/apps/frontend/src/main.tsx +1 -0
  103. package/templates/.agents/dashboard/apps/frontend/src/operations-detail-hydration.test.ts +55 -0
  104. package/templates/.agents/dashboard/apps/frontend/src/operations-detail-hydration.ts +36 -0
  105. package/templates/.agents/dashboard/apps/frontend/src/operations-entity-page-projection.test.ts +126 -0
  106. package/templates/.agents/dashboard/apps/frontend/src/operations-entity-page-projection.ts +181 -0
  107. package/templates/.agents/dashboard/apps/frontend/src/operations-period-contract.test.ts +138 -0
  108. package/templates/.agents/dashboard/apps/frontend/src/operations-period-render.test.ts +105 -0
  109. package/templates/.agents/dashboard/apps/frontend/src/operations-period.test.ts +6 -3
  110. package/templates/.agents/dashboard/apps/frontend/src/operations-period.ts +9 -5
  111. package/templates/.agents/dashboard/apps/frontend/src/overview-status-page.test.ts +24 -0
  112. package/templates/.agents/dashboard/apps/frontend/src/period-transition-overlay.css +44 -0
  113. package/templates/.agents/dashboard/apps/frontend/src/project-settings-model.ts +121 -62
  114. package/templates/.agents/dashboard/apps/frontend/src/project-settings-page.css +471 -2
  115. package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +84 -17
  116. package/templates/.agents/dashboard/apps/frontend/src/snapshot-banner-semantics.test.ts +44 -0
  117. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +196 -39
  118. package/templates/.agents/dashboard/apps/frontend/src/snapshot-notice.ts +15 -0
  119. package/templates/.agents/dashboard/apps/frontend/src/sqlite-cost-adapters.ts +26 -0
  120. package/templates/.agents/dashboard/apps/frontend/src/styles.css +14 -2
  121. package/templates/.agents/dashboard/apps/frontend/src/traceability-workspace.css +38 -2
  122. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +187 -0
  123. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +190 -0
  124. package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +121 -0
  125. package/templates/.agents/dashboard/apps/frontend/src/use-app-operations-pages.ts +398 -0
  126. package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-documents.ts +141 -0
  127. package/templates/.agents/dashboard/apps/frontend/src/use-idle-cost-prefetch.ts +45 -0
  128. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.test.ts +46 -0
  129. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.ts +37 -0
  130. package/templates/.agents/dashboard/apps/frontend/vite.config.ts +55 -5
  131. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  132. package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +47 -6
  133. package/templates/.agents/dashboard/modules/cost-monitor/src/duration-estimate.ts +13 -0
  134. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.test.ts +64 -0
  135. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.tsx +275 -0
  136. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.test.ts +100 -15
  137. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.tsx +131 -40
  138. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.tsx +137 -19
  139. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.test.ts +2 -0
  140. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.tsx +13 -10
  141. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +303 -0
  142. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +226 -6
  143. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +479 -402
  144. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostSummary.tsx +5 -13
  145. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +312 -72
  146. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +373 -321
  147. package/templates/.agents/dashboard/modules/cost-monitor/src/react/MacroCostTable.test.ts +17 -0
  148. package/templates/.agents/dashboard/modules/cost-monitor/src/react/MacroCostTable.tsx +25 -6
  149. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-copy.ts +0 -2
  150. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-page-helpers.ts +17 -0
  151. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-responsive.css +6 -3
  152. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-row-filters.ts +123 -0
  153. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-summary.ts +8 -3
  154. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-views.ts +32 -0
  155. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor.css +52 -14
  156. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-table-presentation.ts +121 -0
  157. package/templates/.agents/dashboard/modules/cost-monitor/src/react/index.ts +3 -0
  158. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.test.ts +68 -0
  159. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +32 -5
  160. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +108 -0
  161. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.test.ts +12 -0
  162. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +60 -0
  163. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +106 -0
  164. package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.test.ts +2 -2
  165. package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.ts +5 -3
  166. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-pricing-fallback.test.ts +53 -0
  167. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.basics.test.ts +34 -0
  168. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.test.ts +168 -137
  169. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.ts +188 -29
  170. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.test.ts +956 -0
  171. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +775 -0
  172. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-terminal-day.test.ts +95 -0
  173. package/templates/.agents/dashboard/modules/cost-monitor/src/server/index.ts +1 -0
  174. package/templates/.agents/dashboard/modules/cost-monitor/src/server/refactor-map-xr9l.json +61 -0
  175. package/templates/.agents/dashboard/modules/cost-monitor/src/task-cost-groups.test.ts +41 -0
  176. package/templates/.agents/dashboard/modules/cost-monitor/src/task-cost-groups.ts +55 -11
  177. package/templates/.agents/dashboard/modules/operations/package.json +2 -1
  178. package/templates/.agents/dashboard/modules/operations/src/contracts.ts +182 -0
  179. package/templates/.agents/dashboard/modules/operations/src/duration-population.test.ts +63 -1
  180. package/templates/.agents/dashboard/modules/operations/src/duration-population.ts +60 -5
  181. package/templates/.agents/dashboard/modules/operations/src/react/EntityDetailsDialog.tsx +14 -3
  182. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.test.ts +44 -1
  183. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +325 -18
  184. package/templates/.agents/dashboard/modules/operations/src/react/MacroPlanningDialog.tsx +1 -0
  185. package/templates/.agents/dashboard/modules/operations/src/react/OperationDateFilters.test.ts +9 -5
  186. package/templates/.agents/dashboard/modules/operations/src/react/OperationDateFilters.tsx +2 -2
  187. package/templates/.agents/dashboard/modules/operations/src/react/OperationsMainContent.tsx +168 -0
  188. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +95 -6
  189. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +365 -14
  190. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +532 -131
  191. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +314 -358
  192. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageNavigation.tsx +68 -0
  193. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageOverview.tsx +169 -0
  194. package/templates/.agents/dashboard/modules/operations/src/react/PeriodSummaryTable.tsx +51 -8
  195. package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.test.tsx +32 -0
  196. package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.tsx +62 -6
  197. package/templates/.agents/dashboard/modules/operations/src/react/TaskBoard.test.ts +55 -0
  198. package/templates/.agents/dashboard/modules/operations/src/react/TaskBoard.tsx +27 -19
  199. package/templates/.agents/dashboard/modules/operations/src/react/TaskDetailsDialog.tsx +99 -42
  200. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.priority.test.ts +19 -0
  201. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +255 -34
  202. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.test.ts +163 -8
  203. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +101 -68
  204. package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.test.ts +72 -1
  205. package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.ts +78 -16
  206. package/templates/.agents/dashboard/modules/operations/src/react/execution-metrics.ts +6 -4
  207. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +50 -1
  208. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +143 -9
  209. package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +29 -4
  210. package/templates/.agents/dashboard/modules/operations/src/react/macro-planning-dialog-content.css +49 -0
  211. package/templates/.agents/dashboard/modules/operations/src/react/macro-planning-dialog.css +93 -50
  212. package/templates/.agents/dashboard/modules/operations/src/react/manual-work-priority.test.ts +82 -0
  213. package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.test.ts +29 -2
  214. package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.ts +22 -1
  215. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.test.ts +77 -4
  216. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.ts +65 -5
  217. package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts +54 -0
  218. package/templates/.agents/dashboard/modules/operations/src/react/operations-global-filters.test.ts +19 -0
  219. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +149 -12
  220. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +27 -0
  221. package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +136 -0
  222. package/templates/.agents/dashboard/modules/operations/src/react/operations-period-scope.test.ts +160 -0
  223. package/templates/.agents/dashboard/modules/operations/src/react/operations-period-scope.ts +47 -0
  224. package/templates/.agents/dashboard/modules/operations/src/react/operations-query.ts +10 -0
  225. package/templates/.agents/dashboard/modules/operations/src/react/operations-responsive.css +3 -0
  226. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-labels.ts +28 -0
  227. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-order.test.ts +24 -7
  228. package/templates/.agents/dashboard/modules/operations/src/react/operations.css +42 -0
  229. package/templates/.agents/dashboard/modules/operations/src/react/period-summary-population.test.ts +54 -0
  230. package/templates/.agents/dashboard/modules/operations/src/react/period-summary-population.ts +32 -0
  231. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.test.ts +165 -6
  232. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.ts +148 -6
  233. package/templates/.agents/dashboard/modules/operations/src/react/popup-localization.test.ts +1 -1
  234. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.css +132 -0
  235. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.css.test.ts +13 -0
  236. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.header.test.ts +16 -0
  237. package/templates/.agents/dashboard/modules/operations/src/react/use-infinite-column.ts +11 -5
  238. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.test.ts +5 -3
  239. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.ts +4 -83
  240. package/templates/.agents/dashboard/modules/operations/src/server/build-operations-projection.test.ts +291 -15
  241. package/templates/.agents/dashboard/modules/operations/src/server/build-operations-projection.ts +182 -472
  242. package/templates/.agents/dashboard/modules/operations/src/server/canonical-compat.test.ts +40 -0
  243. package/templates/.agents/dashboard/modules/operations/src/server/canonical-compat.ts +19 -6
  244. package/templates/.agents/dashboard/modules/operations/src/server/canonical-run-projection.ts +31 -6
  245. package/templates/.agents/dashboard/modules/operations/src/server/daily-duration-projection.ts +220 -0
  246. package/templates/.agents/dashboard/modules/operations/src/server/execution-duration-projection.ts +28 -0
  247. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.test.ts +27 -2
  248. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.ts +22 -13
  249. package/templates/.agents/dashboard/modules/operations/src/server/filter.test.ts +3 -3
  250. package/templates/.agents/dashboard/modules/operations/src/server/filter.ts +7 -2
  251. package/templates/.agents/dashboard/modules/operations/src/server/index.ts +3 -0
  252. package/templates/.agents/dashboard/modules/operations/src/server/macro.ts +3 -12
  253. package/templates/.agents/dashboard/modules/operations/src/server/manifest-usage.ts +65 -0
  254. package/templates/.agents/dashboard/modules/operations/src/server/operations-projection-aggregates.ts +297 -0
  255. package/templates/.agents/dashboard/modules/operations/src/server/quick-daily-operations-projection.ts +324 -0
  256. package/templates/.agents/dashboard/modules/operations/src/server/quick-operations-projection.test.ts +505 -0
  257. package/templates/.agents/dashboard/modules/operations/src/server/quick-operations-projection.ts +609 -0
  258. package/templates/.agents/dashboard/modules/operations/src/server/runs.ts +18 -19
  259. package/templates/.agents/dashboard/modules/operations/src/work-board-model.ts +76 -0
  260. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  261. package/templates/.agents/dashboard/modules/traceability/src/server/build-task-trace.ts +3 -1
  262. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  263. package/templates/.agents/dashboard/packages/branding/src/index.ts +1 -1
  264. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  265. package/templates/.agents/dashboard/packages/contracts/src/index.ts +1 -0
  266. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  267. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +1756 -0
  268. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +1608 -0
  269. package/templates/.agents/dashboard/packages/local-runtime/package.json +8 -3
  270. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-dashboard.mjs +12 -0
  271. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +83 -0
  272. package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +28 -0
  273. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-read-model.mjs +262 -0
  274. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-unix-launchers.mjs +41 -0
  275. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +105 -0
  276. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +1643 -107
  277. package/templates/.agents/dashboard/packages/local-runtime/src/agent-adapter-sync.ts +30 -0
  278. package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.test.ts +3 -2
  279. package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.ts +5 -0
  280. package/templates/.agents/dashboard/packages/local-runtime/src/manifest-runs.ts +72 -33
  281. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +335 -4
  282. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +392 -1
  283. package/templates/.agents/dashboard/packages/local-runtime/src/runtime.mjs +5 -5
  284. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.test.ts +71 -0
  285. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +179 -0
  286. package/templates/.agents/dashboard/packages/local-runtime/src/server-cross-layer-parity.test.ts +245 -0
  287. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models-route.ts +64 -0
  288. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models.test.ts +204 -0
  289. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models.ts +194 -0
  290. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-pages.test.ts +163 -0
  291. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-pages.ts +354 -0
  292. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-runs-route.test.ts +74 -0
  293. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-runs-route.ts +113 -0
  294. package/templates/.agents/dashboard/packages/local-runtime/src/server-overview.test.ts +68 -0
  295. package/templates/.agents/dashboard/packages/local-runtime/src/server-period.ts +47 -0
  296. package/templates/.agents/dashboard/packages/local-runtime/src/server-projections.ts +483 -0
  297. package/templates/.agents/dashboard/packages/local-runtime/src/server-settings.test.ts +5 -2
  298. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +163 -84
  299. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +145 -0
  300. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +551 -266
  301. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +1145 -308
  302. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.test.ts +53 -0
  303. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.ts +708 -0
  304. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +2232 -0
  305. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +6151 -0
  306. package/templates/.agents/dashboard/packages/local-runtime/src/work-priority-contract.test.ts +33 -0
  307. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-history.mjs +41 -0
  308. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-processes.mjs +210 -0
  309. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +437 -190
  310. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +299 -24
  311. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  312. package/templates/.agents/dashboard/packages/windows-launcher/LauncherVerificationBehaviorTests.cs +206 -0
  313. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +95 -8
  314. package/templates/.agents/dashboard/packages/windows-launcher/build-dashboard-launcher.ps1 +2 -2
  315. package/templates/.agents/dashboard/packages/windows-launcher/run-launcher-verification-tests.ps1 +22 -0
  316. package/templates/.agents/dashboard/read-model.dbml +2201 -0
  317. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  318. package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +1 -1
  319. package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +214 -41
  320. package/templates/.agents/dashboard/work-priority.json +34 -0
  321. package/templates/.agents/dashboard/zaip-dashboard-linux.sh +45 -0
  322. package/templates/.agents/dashboard/zaip-dashboard-mac.command +3 -0
  323. package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
  324. package/templates/.agents/design/.zaip-managed.json +3 -3
  325. package/templates/.agents/design/apps/catalog/package.json +1 -1
  326. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  327. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  328. package/templates/.agents/requirements/comparative-audit.md +1 -1
  329. package/templates/.agents/requirements/context-engine.md +1 -1
  330. package/templates/.agents/requirements/cost-observability.md +1 -1
  331. package/templates/.agents/requirements/dashboard-launch.md +24 -0
  332. package/templates/.agents/requirements/data-export.md +1 -1
  333. package/templates/.agents/requirements/design-system.md +1 -1
  334. package/templates/.agents/requirements/execution-engine.md +1 -1
  335. package/templates/.agents/requirements/kanban-dashboard.md +4 -3
  336. package/templates/.agents/requirements/model-orchestration.md +1 -1
  337. package/templates/.agents/requirements/public-distribution.md +9 -6
  338. package/templates/.agents/requirements/software-architecture.md +1 -1
  339. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  340. package/templates/.agents/requirements/work-lifecycle.md +9 -3
  341. package/templates/.agents/requirements/zaip-strategic-95.md +4 -4
  342. package/templates/.agents/rules/autonomy-decision.md +15 -0
  343. package/templates/.agents/rules/core.md +1 -1
  344. package/templates/.agents/rules/run-lifecycle-closure.md +1 -1
  345. package/templates/.agents/rules/specialist-routing.md +19 -0
  346. package/templates/.agents/rules/workflow.md +1 -1
  347. package/templates/.agents/runtime/VERSION +1 -1
  348. package/templates/.agents/runtime/bin/zaip.mjs +114 -64
  349. package/templates/.agents/runtime/checksums.sha256 +6 -4
  350. package/templates/.agents/runtime/manifest.json +3 -1
  351. package/templates/.agents/runtime/pnpm-lock.sha256 +1 -0
  352. package/templates/.agents/runtime/provenance.json +11 -0
  353. package/templates/.agents/runtime/sbom.cdx.json +1929 -39
  354. package/templates/.agents/skills/zaip-aios/SKILL.md +1 -1
  355. package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +15 -0
  356. package/templates/.agents/skills/zaip-skill-brand-governance/agents/openai.yaml +4 -0
  357. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +30 -0
  358. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/SKILL.md +25 -0
  359. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/agents/openai.yaml +4 -0
  360. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/SKILL.md +18 -0
  361. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/agents/openai.yaml +6 -0
  362. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/ready-queue-fence.mjs +71 -0
  363. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/ready-queue-fence.test.mjs +60 -0
  364. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/scheduled-ready-queue-contract.test.mjs +111 -0
  365. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +2 -2
  366. package/templates/.agents/skills/zaip-skill-frontend-launcher/references/launcher-contract.md +3 -1
  367. package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +12 -0
  368. package/templates/.agents/skills/zaip-skill-general-operations/agents/openai.yaml +4 -0
  369. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +9 -7
  370. package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
  371. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +11 -38
  372. package/templates/.agents/skills/zaip-skill-governance-compliance/SKILL.md +12 -0
  373. package/templates/.agents/skills/zaip-skill-governance-compliance/agents/openai.yaml +4 -0
  374. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +15 -0
  375. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/agents/openai.yaml +4 -0
  376. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/references/growth-revops-framework.md +25 -0
  377. package/templates/.agents/skills/zaip-skill-instalar-agendamento/SKILL.md +17 -0
  378. package/templates/.agents/skills/zaip-skill-instalar-agendamento/agents/openai.yaml +6 -0
  379. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +3 -3
  380. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +76 -27
  381. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +128 -24
  382. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +122 -4
  383. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +66 -19
  384. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +193 -7
  385. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +14 -0
  386. package/templates/.agents/skills/zaip-skill-sales-methodology/agents/openai.yaml +4 -0
  387. package/templates/.agents/skills/zaip-skill-sales-methodology/references/sales-process-framework.md +77 -0
  388. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -0
  389. package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +96 -0
  390. package/templates/.agents/skills/zaip-skill-sqlite-read-model/agents/openai.yaml +4 -0
  391. package/templates/.agents/skills/zaip-skill-sqlite-read-model/scripts/ensure-node.mjs +18 -0
  392. package/templates/.agents/zaip-aios/INDEX.md +1 -0
  393. package/templates/.agents/zaip-aios/README.md +37 -8
  394. package/templates/.agents/zaip-aios/VERSION +1 -1
  395. package/templates/.agents/zaip-aios/guides/09-agentes-e-squads.md +43 -6
  396. package/templates/.agents/zaip-aios/guides/10-seguranca.md +21 -7
  397. package/templates/.agents/zaip-aios/guides/17-como-o-custo-e-calculado.md +32 -0
  398. package/templates/template-manifest.json +945 -214
  399. package/templates/.agents/dashboard/apps/frontend/dist/assets/AgentCatalogSettingsPage-CLYIi7Vo.js +0 -1
  400. package/templates/.agents/dashboard/apps/frontend/dist/assets/AgentCatalogSettingsPage-DTDNVQGX.css +0 -1
  401. package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-CzEggcRb.js +0 -1
  402. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-05YKn6Z4.js +0 -3
  403. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-YO3yqtgV.css +0 -1
  404. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8AzCnl3a.js +0 -77
  405. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DdBYQ_8D.css +0 -1
  406. package/templates/.agents/dashboard/apps/frontend/src/AgentCatalogSettingsPage.tsx +0 -617
  407. package/templates/.agents/dashboard/apps/frontend/src/agent-catalog-settings-page.css +0 -660
  408. package/templates/.agents/dashboard/apps/frontend/src/agent-catalog-settings.test.ts +0 -249
  409. package/templates/.agents/dashboard/dashboard.config.json +0 -4
  410. package/templates/.agents/dashboard/zaip-dashboard.exe +0 -0
@@ -0,0 +1,2232 @@
1
+ import { mkdir, mkdtemp, readFile, readdir, 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 { allocateAttributedTokenActiveMs, rebuildSqliteReadModel, sourceFingerprint, SqliteReadModelFileSystem } from "./sqlite-read-model.js";
7
+
8
+ const roots: string[] = [];
9
+ // Windows can deny SQLite's sidecar files below the system TEMP directory.
10
+ // Keep disposable fixtures in the workspace-local test area instead; each
11
+ // test still receives its own root and never touches the live read model.
12
+ const sqliteTestTempRoot = join(resolve(import.meta.dirname, "../../../../../.."), ".tmp", "sqlite-read-model-tests");
13
+ async function createSqliteTestRoot(): Promise<string> {
14
+ await mkdir(sqliteTestTempRoot, { recursive: true });
15
+ return mkdtemp(join(sqliteTestTempRoot, "fixture-"));
16
+ }
17
+ afterEach(async () => {
18
+ await Promise.all(
19
+ roots
20
+ .splice(0)
21
+ .map((root) =>
22
+ import("node:fs/promises").then(({ rm }) => rm(root, { recursive: true, force: true })),
23
+ ),
24
+ );
25
+ });
26
+
27
+ describe("SQLite dashboard read model", () => {
28
+ it("snapshots sorted eligible canonical sources once for fingerprinting and ingestion", async () => {
29
+ const root = await createSqliteTestRoot();
30
+ roots.push(root);
31
+ const included = join(root, ".agents", "records", "tasks", "nested", "task.md");
32
+ const excluded = join(root, ".agents", "records", "telemetry", "summary.json");
33
+ await mkdir(join(root, ".agents", "records", "tasks", "nested"), { recursive: true });
34
+ await mkdir(join(root, ".agents", "records", "telemetry"), { recursive: true });
35
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
36
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n", "utf8");
37
+ await writeFile(included, "# included\n", "utf8");
38
+ await writeFile(excluded, "ignored\n", "utf8");
39
+
40
+ const before = await sourceFingerprint(root);
41
+ expect(before.candidates.map((candidate) => candidate.source)).toEqual([
42
+ ".agents/config/project.yaml",
43
+ ".agents/records/tasks/nested/task.md",
44
+ ]);
45
+ expect(before.fingerprint).toContain(".agents/records/tasks/nested/task.md:");
46
+ const changedAt = new Date(Date.now() + 2_000);
47
+ await utimes(included, changedAt, changedAt);
48
+ const after = await sourceFingerprint(root);
49
+ expect(after.fingerprint).not.toBe(before.fingerprint);
50
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ files: 2 });
51
+ });
52
+
53
+ it("uses a current-schema seed after a canonical change", async () => {
54
+ const root = await createSqliteTestRoot();
55
+ roots.push(root);
56
+ const task = join(root, ".agents", "records", "tasks", "task.md");
57
+ await mkdir(join(root, ".agents", "records", "tasks"), { recursive: true });
58
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
59
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n", "utf8");
60
+ await writeFile(task, "# original\n", "utf8");
61
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "rebuilt" });
62
+ await writeFile(task, "# changed canonically\n", "utf8");
63
+ const previousPath = process.env.ZAIP_SQLITE_DB_PATH;
64
+ const previousSeed = process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED;
65
+ try {
66
+ process.env.ZAIP_SQLITE_DB_PATH = join(root, ".local", "dashboard-sqlite", "next.sqlite");
67
+ process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED = "1";
68
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "stale_seed", files: 2 });
69
+ } finally {
70
+ if (previousPath === undefined) delete process.env.ZAIP_SQLITE_DB_PATH;
71
+ else process.env.ZAIP_SQLITE_DB_PATH = previousPath;
72
+ if (previousSeed === undefined) delete process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED;
73
+ else process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED = previousSeed;
74
+ }
75
+ });
76
+
77
+ it("keeps an exact cached range valid with future facts and fast-paths an empty later month", async () => {
78
+ const root = await createSqliteTestRoot();
79
+ roots.push(root);
80
+ await mkdir(join(root, ".agents", "records", "tasks"), { recursive: true });
81
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
82
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n", "utf8");
83
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "rebuilt" });
84
+ const path = join(root, ".local", "dashboard-sqlite", "read-model.sqlite");
85
+ const database = new DatabaseSync(path);
86
+ database.prepare("UPDATE zaip_aios_activities SET payload_json=? WHERE payload_type='first_fold_monthly_quick_cache'").run(JSON.stringify({
87
+ from_day: "2026-08-01", to_day: "2026-08-18", source_latest_day: "2026-08-30",
88
+ aggregate: { days: 0, total_run_count: 0, agent_invocation_count: 0, llm_call_count: null, duration_ms: null, total_tokens: null, cost_brl: null, execution_day_count: 0 },
89
+ pages: { task: [], spec: [], prd: [] }, top_tasks: [],
90
+ }));
91
+ database.close();
92
+ const filesystem = new SqliteReadModelFileSystem(root);
93
+ try {
94
+ expect(filesystem.firstFoldMonthlyQuick({ from: "2026-08-01", to: "2026-08-18" })).not.toBeNull();
95
+ expect(filesystem.firstFoldMonthlyQuick({ from: "2026-08-01", to: "2026-08-19" })).toBeNull();
96
+ expect(filesystem.firstFoldMonthlyQuick({ from: "2026-09-01", to: "2026-09-01" })).toMatchObject({
97
+ aggregate: { total_run_count: 0 }, pages: { task: [], spec: [], prd: [] },
98
+ });
99
+ } finally { filesystem.close(); }
100
+ });
101
+
102
+ it("treats an empty snapshot as an exact empty later-month projection", async () => {
103
+ const root = await createSqliteTestRoot();
104
+ roots.push(root);
105
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
106
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n", "utf8");
107
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "rebuilt" });
108
+ const filesystem = new SqliteReadModelFileSystem(root);
109
+ try {
110
+ expect(filesystem.firstFoldMonthlyQuick({ from: "2099-01-01", to: "2099-01-01" })).toMatchObject({
111
+ aggregate: { total_run_count: 0 }, pages: { task: [], spec: [], prd: [] },
112
+ });
113
+ } finally { filesystem.close(); }
114
+ });
115
+
116
+ it("does not serve a schema 59 seed during the one-time SQLite cache migration", async () => {
117
+ const root = await createSqliteTestRoot();
118
+ roots.push(root);
119
+ await mkdir(join(root, ".agents", "records", "tasks"), { recursive: true });
120
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
121
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n", "utf8");
122
+ await writeFile(join(root, ".agents", "records", "tasks", "task.md"), "# task\n", "utf8");
123
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "rebuilt" });
124
+ const cacheDirectory = join(root, ".local", "dashboard-sqlite", "cache");
125
+ const current = (await readdir(cacheDirectory)).find((entry) => entry.startsWith("v60-") && entry.endsWith(".sqlite"));
126
+ expect(current).toBeDefined();
127
+ const legacy = join(cacheDirectory, current!.replace("v60-", ""));
128
+ await rename(join(cacheDirectory, current!), legacy);
129
+ const oldSeed = new DatabaseSync(legacy);
130
+ oldSeed.prepare("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'").run();
131
+ oldSeed.close();
132
+ const priorPath = process.env.ZAIP_SQLITE_DB_PATH;
133
+ const priorSeed = process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED;
134
+ try {
135
+ process.env.ZAIP_SQLITE_DB_PATH = join(root, ".local", "dashboard-sqlite", "migrated.sqlite");
136
+ process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED = "1";
137
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "rebuilt" });
138
+ } finally {
139
+ if (priorPath === undefined) delete process.env.ZAIP_SQLITE_DB_PATH;
140
+ else process.env.ZAIP_SQLITE_DB_PATH = priorPath;
141
+ if (priorSeed === undefined) delete process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED;
142
+ else process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED = priorSeed;
143
+ }
144
+ });
145
+
146
+ it("rateia sobreposição e virada de dia na fato temporal aditiva", () => {
147
+ const overlap = allocateAttributedTokenActiveMs([
148
+ { activityRowId: "A", startMs: Date.parse("2026-08-10T12:00:00.000Z"), endMs: Date.parse("2026-08-10T13:00:00.000Z") },
149
+ { activityRowId: "B", startMs: Date.parse("2026-08-10T12:30:00.000Z"), endMs: Date.parse("2026-08-10T13:30:00.000Z") },
150
+ ]);
151
+ expect(overlap.reduce((sum, fact) => sum + fact.value, 0)).toBe(5_400_000);
152
+ expect(overlap.filter((fact) => fact.activityRowId === "A").reduce((sum, fact) => sum + fact.value, 0)).toBe(2_700_000);
153
+ expect(overlap.filter((fact) => fact.activityRowId === "B").reduce((sum, fact) => sum + fact.value, 0)).toBe(2_700_000);
154
+ const midnight = allocateAttributedTokenActiveMs([
155
+ { activityRowId: "MID", startMs: Date.parse("2026-08-11T02:30:00.000Z"), endMs: Date.parse("2026-08-11T03:30:00.000Z") },
156
+ ]);
157
+ expect(midnight).toEqual(expect.arrayContaining([
158
+ expect.objectContaining({ day: "2026-08-10", value: 1_800_000 }),
159
+ expect.objectContaining({ day: "2026-08-11", value: 1_800_000 }),
160
+ ]));
161
+ });
162
+ it("projects canonical executions, conversations, changes and decisions without lifecycle aggregates", async () => {
163
+ const root = await createSqliteTestRoot();
164
+ roots.push(root);
165
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
166
+ await mkdir(join(root, ".agents", "records", "executions", "2026", "08", "EXEC-1"), {
167
+ recursive: true,
168
+ });
169
+ await mkdir(
170
+ join(root, ".agents", "records", "changes", "2026", "08", "CHG-1", "decisions"),
171
+ { recursive: true },
172
+ );
173
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
174
+ await writeFile(
175
+ join(root, ".agents", "records", "executions", "2026", "08", "EXEC-1", "execution.md"),
176
+ `---
177
+ schema_version: 2
178
+ id: EXEC-1
179
+ status: completed
180
+ kind: interaction
181
+ origin:
182
+ type: interaction
183
+ conversation_id: CONV-1
184
+ created_at: 2026-08-10T10:00:00.000Z
185
+ finished_at: 2026-08-10T10:05:00.000Z
186
+ ---
187
+
188
+ # Execution
189
+ `,
190
+ );
191
+ await writeFile(
192
+ join(root, ".agents", "records", "changes", "2026", "08", "CHG-1", "change.md"),
193
+ `---
194
+ schema_version: 2
195
+ id: CHG-1
196
+ title: Canonical change
197
+ status: completed
198
+ risk: standard
199
+ source:
200
+ type: task
201
+ task_id: TASK-1
202
+ requirement_ids: [REQ-1]
203
+ acceptance_ids: [AC-1]
204
+ task_ids: [TASK-1]
205
+ result: Done
206
+ created_at: 2026-08-10T11:00:00.000Z
207
+ ---
208
+
209
+ # Change
210
+ `,
211
+ );
212
+ await writeFile(
213
+ join(
214
+ root,
215
+ ".agents",
216
+ "records",
217
+ "changes",
218
+ "2026",
219
+ "08",
220
+ "CHG-1",
221
+ "decisions",
222
+ "DEC-1.md",
223
+ ),
224
+ `---
225
+ schema_version: 2
226
+ id: DEC-1
227
+ title: Canonical decision
228
+ origin: agent
229
+ scope_type: change
230
+ scope_id: CHG-1
231
+ created_at: 2026-08-10T11:01:00.000Z
232
+ ---
233
+
234
+ # Decision
235
+ `,
236
+ );
237
+
238
+ await rebuildSqliteReadModel(root);
239
+ const filesystem = new SqliteReadModelFileSystem(root);
240
+ expect(filesystem.viewPage("operations_executions", { limit: 100 })).toMatchObject({
241
+ total_count: 1,
242
+ rows: [{ execution_id: "EXEC-1", conversation_id: "CONV-1", invocation_count: 0, duration_ms: null, duration_provenance: "unavailable", lifecycle_elapsed_ms: 300_000 }],
243
+ });
244
+ expect(filesystem.viewPage("operations_conversations", { limit: 100 })).toMatchObject({
245
+ total_count: 1,
246
+ rows: [{ conversation_id: "CONV-1", invocation_count: 0 }],
247
+ });
248
+ expect(filesystem.viewPage("operations_changes", { limit: 100 })).toMatchObject({
249
+ total_count: 1,
250
+ rows: [{ id: "CHG-1", title: "Canonical change", source_type: "task" }],
251
+ });
252
+ expect(filesystem.viewPage("operations_decisions", { limit: 100 })).toMatchObject({
253
+ total_count: 1,
254
+ rows: [{ id: "DEC-1", title: "Canonical decision", scope_id: "CHG-1" }],
255
+ });
256
+ // Unfiltered Operations pagination is served from one immutable
257
+ // generation-local query result, while retaining the public page contract.
258
+ const workListFirst = filesystem.viewPage("operations_work_list", { limit: 1, offset: 0 });
259
+ const workListNext = filesystem.viewPage("operations_work_list", { limit: 1, offset: 1 });
260
+ expect(workListFirst.total_count).toBe(workListNext.total_count);
261
+ expect(workListFirst.has_more).toBe(workListFirst.total_count > 1);
262
+ filesystem.close();
263
+ });
264
+
265
+ it("projects catalog personas for canonical, legacy, and host Zaip Agent identities", async () => {
266
+ const root = await createSqliteTestRoot();
267
+ roots.push(root);
268
+ await mkdir(join(root, ".agents", "config", "agents"), { recursive: true });
269
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
270
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
271
+ await writeFile(join(root, ".agents", "config", "clients.yaml"), "profiles:\n - id: codex\n");
272
+ await writeFile(
273
+ join(root, ".agents", "config", "agents", "catalog.yaml"),
274
+ `agents:
275
+ - id: agent-orchestrator
276
+ codex_name: zaip-agent-orchestrator
277
+ persona: { nome: dave, cargo: product_owner }
278
+ role: orchestrator
279
+ - id: agent-builder
280
+ codex_name: zaip-agent-builder
281
+ persona: { nome: rafa, cargo: tech_lead }
282
+ role: builder
283
+ - id: agent-auditor
284
+ codex_name: zaip-agent-auditor
285
+ persona: { nome: ana, cargo: quality_auditor }
286
+ role: auditor
287
+ - id: agent-reviewer
288
+ codex_name: zaip-agent-reviewer
289
+ persona: { nome: nanda, cargo: principal_engineer }
290
+ role: reviewer
291
+ - id: agent-finalizer
292
+ codex_name: zaip-agent-finalizer
293
+ compatibility:
294
+ aliases:
295
+ - kind: id
296
+ value: agent-task-finalizer
297
+ - kind: id
298
+ value: agent-execution-finalizer
299
+ persona: { nome: gui, cargo: quality_assurance_lead }
300
+ role: execution-finalizer
301
+ `,
302
+ );
303
+ const expected = new Map([
304
+ ["agent-builder", ["rafa", "tech_lead"]],
305
+ ["agent-auditor", ["ana", "quality_auditor"]],
306
+ ["agent-reviewer", ["nanda", "principal_engineer"]],
307
+ ["agent-orchestrator", ["dave", "product_owner"]],
308
+ ["zaip-agent-execution-finalizer", ["gui", "quality_assurance_lead"]],
309
+ ["zaip-agent-task-finalizer", ["gui", "quality_assurance_lead"]],
310
+ ["codex", ["codex", "client_host"]],
311
+ ]);
312
+ const events = [...expected.keys()].flatMap((agent, index) => {
313
+ const runId = `RUN-AGENT-${index}`;
314
+ const started = {
315
+ schema_version: 2, event_id: `${runId}:1`, run_id: runId, sequence: 1,
316
+ occurred_at: "2026-08-18T12:00:00.000Z", type: "run.started", attribution_status: "attributed",
317
+ phase: "execution", run_role: "subagent", run_purpose: "execution", requires_finalizer: false,
318
+ execution_kind: "delivery", execution_eligibility: ["delivery"], entrypoint: "task", work_type: "new",
319
+ storage_mode: "tracked_sanitized", client: "codex", agent, agent_name: agent, provider: "openai",
320
+ model: "gpt-5", reasoning_effort: null, client_thread_id: runId, client_turn_id: runId,
321
+ conversation_id: `CONV-${index}`, execution_id: `EXEC-${index}`, task_id: `TASK-${index}`,
322
+ spec_id: null, prd_id: null, change_id: null, parent_run_id: "RUN-ROOT", continuation_of_run_id: null,
323
+ retry_of_run_id: null, agent_invocation_id: `AINV-${index}`, root_agent_invocation_id: "AINV-ROOT",
324
+ parent_agent_invocation_id: "AINV-ROOT", invocation_kind: "delegated", delegation_depth: 1,
325
+ };
326
+ const usage = {
327
+ schema_version: 2, event_id: `${runId}:2`, run_id: runId, sequence: 2,
328
+ occurred_at: "2026-08-18T12:00:01.000Z", type: "usage.recorded", phase: "execution",
329
+ activity_kind: "model_usage", classification_source: "explicit",
330
+ tokens: { input: 1, cached_input: 0, output: 1, reasoning: 0, total: 2, source: "provider_reported" },
331
+ cost: { usd: 0.01, brl: 0.05, fx_rate: 5 }, provider: "openai", model: "gpt-5",
332
+ source_segment_key: runId, model_call_id: `MCALL-${index}`, runtime: { reasoning_effort: null, service_tier: null },
333
+ supersedes_event_id: null, duration_ms: null, duration_provenance: null,
334
+ };
335
+ const finished = {
336
+ schema_version: 2, event_id: `${runId}:3`, run_id: runId, sequence: 3,
337
+ occurred_at: "2026-08-18T12:01:00.000Z", type: "run.finished", phase: "execution",
338
+ status: "completed", duration_ms: 60_000, duration_provenance: "observed",
339
+ };
340
+ return [started, usage, finished];
341
+ });
342
+ await Promise.all(
343
+ [...expected.keys()].map((_, index) =>
344
+ writeFile(
345
+ join(root, ".agents", "records", "runs", "2026", "08", `RUN-AGENT-${index}.jsonl`),
346
+ `${events.slice(index * 3, index * 3 + 3).map((event) => JSON.stringify(event)).join("\n")}\n`,
347
+ ),
348
+ ),
349
+ );
350
+ await rebuildSqliteReadModel(root);
351
+ const filesystem = new SqliteReadModelFileSystem(root);
352
+ try {
353
+ const rows = filesystem.viewPage("cost_monitor_zaip_agents", { limit: 20 }).rows;
354
+ expect(rows).toHaveLength(expected.size);
355
+ for (const [agent, [persona, job]] of expected) {
356
+ expect(rows.find((row) => row.key === agent)).toMatchObject({ persona, job });
357
+ }
358
+ expect(rows.some((row) => row.persona === null || row.job === null)).toBe(false);
359
+ } finally {
360
+ filesystem.close();
361
+ }
362
+ });
363
+
364
+ it("marks token and cost telemetry unavailable when a canonical day has only Runs without usage", async () => {
365
+ const root = await createSqliteTestRoot();
366
+ roots.push(root);
367
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
368
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
369
+ await mkdir(join(root, ".agents", "config", "agents"), { recursive: true });
370
+ await mkdir(join(root, ".agents", "config", "pricing"), { recursive: true });
371
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
372
+ await writeFile(
373
+ join(root, ".agents", "config", "agents", "catalog.yaml"),
374
+ "agents:\n - id: agent-catalog\n codex_name: agent\n persona: { nome: ana, cargo: quality_engineer }\n role: reviewer\n",
375
+ );
376
+ await writeFile(
377
+ join(root, ".agents", "config", "pricing", "models.json"),
378
+ JSON.stringify({
379
+ schema_version: 1,
380
+ updated_at: "2026-08-04T00:00:00.000Z",
381
+ source: "fixture",
382
+ disclaimer: "fixture",
383
+ models: [
384
+ { id: "gpt-a", provider: "openai", aliases: [], input_per_million_usd: 2, cached_input_per_million_usd: 0.2, output_per_million_usd: 8, reasoning_per_million_usd: 8, reasoning_billed_separately: false, long_context_threshold_tokens: null, long_context_input_multiplier: 1, long_context_output_multiplier: 1, source_label: "fixture-openai", source_url: "https://example.test/openai", notes: [] },
385
+ { id: "claude-b", provider: "anthropic", aliases: [], input_per_million_usd: 3, cached_input_per_million_usd: 0.3, output_per_million_usd: 12, reasoning_per_million_usd: null, reasoning_billed_separately: false, long_context_threshold_tokens: null, long_context_input_multiplier: 1, long_context_output_multiplier: 1, source_label: "fixture-anthropic", source_url: "https://example.test/anthropic", notes: [] },
386
+ ],
387
+ }),
388
+ );
389
+ const started = {
390
+ schema_version: 2, event_id: "RUN-MISSING:1", run_id: "RUN-MISSING", sequence: 1,
391
+ occurred_at: "2026-08-05T12:00:00.000Z", type: "run.started", attribution_status: "attributed",
392
+ phase: "conversation", run_role: "orchestrator", run_purpose: "orchestration", requires_finalizer: false,
393
+ execution_kind: "interaction", execution_eligibility: ["interaction"], entrypoint: "conversation",
394
+ work_type: "new", storage_mode: "tracked_sanitized", client: "codex", agent: "root", agent_name: "root",
395
+ provider: null, model: "unknown", reasoning_effort: null, client_thread_id: "missing", client_turn_id: "missing",
396
+ conversation_id: "CONV-MISSING", execution_id: null, task_id: null, spec_id: null, prd_id: null,
397
+ change_id: null, parent_run_id: null, continuation_of_run_id: null, retry_of_run_id: null,
398
+ };
399
+ const finished = {
400
+ schema_version: 2, event_id: "RUN-MISSING:2", run_id: "RUN-MISSING", sequence: 2,
401
+ occurred_at: "2026-08-05T12:01:00.000Z", type: "run.finished", phase: "conversation",
402
+ status: "completed", duration_ms: 60_000, duration_provenance: "observed",
403
+ };
404
+ await writeFile(
405
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-MISSING.jsonl"),
406
+ `${JSON.stringify(started)}\n${JSON.stringify(finished)}\n`,
407
+ );
408
+ await rebuildSqliteReadModel(root);
409
+ const filesystem = new SqliteReadModelFileSystem(root);
410
+ expect(filesystem.dailyPeriods({ from: null, to: null })["2026-08-05"]).toMatchObject({
411
+ runs: 1,
412
+ usage: {
413
+ total_tokens: { known_sum: 0, unknown: 1 },
414
+ cost: { known_sum: 0, unknown: 1 },
415
+ },
416
+ });
417
+ filesystem.close();
418
+ });
419
+
420
+ it("releases the real SQLite handle before replacing a generation and reopens it after failure", async () => {
421
+ const root = await createSqliteTestRoot();
422
+ roots.push(root);
423
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
424
+ const configPath = join(root, ".agents", "config", "project.yaml");
425
+ await writeFile(configPath, "zaip:\n enabled: true\n");
426
+ await rebuildSqliteReadModel(root);
427
+ const filesystem = new SqliteReadModelFileSystem(root);
428
+ try {
429
+ expect(await filesystem.readText(".agents/config/project.yaml")).toBe("zaip:\n enabled: true\n");
430
+ expect(filesystem.viewPage("operations_work_list", { limit: 10 }).total_count).toBe(0);
431
+ const pageCache = (filesystem as unknown as {
432
+ viewPageResultCache: Map<string, Record<string, unknown>>;
433
+ }).viewPageResultCache;
434
+ expect(pageCache.has('operations_work_list:{"limit":10}')).toBe(true);
435
+ // Change canonical input only after the read-only database has opened:
436
+ // rebuild must replace the generation rather than taking the cache path.
437
+ await writeFile(configPath, "zaip:\n enabled: true\nproject:\n id: refreshed-fixture\n");
438
+ await expect(filesystem.rebuildAndRefresh(() => rebuildSqliteReadModel(root))).resolves.toBeUndefined();
439
+ expect(await filesystem.readText(".agents/config/project.yaml")).toContain("refreshed-fixture");
440
+ expect(pageCache.has('operations_work_list:{"limit":10}')).toBe(false);
441
+ // Exercise the actual public view on the same reopened instance, not
442
+ // merely raw filesystem reads after a Windows-safe replacement.
443
+ expect(() => filesystem.viewPage("cost_monitor_daily", { limit: 1, offset: 0 })).not.toThrow();
444
+ // A failed rebuild still reopens the existing, complete generation.
445
+ await expect(filesystem.rebuildAndRefresh(async () => {
446
+ throw new Error("simulated rebuild failure");
447
+ })).rejects.toThrow("simulated rebuild failure");
448
+ expect(await filesystem.readText(".agents/config/project.yaml")).toContain("refreshed-fixture");
449
+ } finally {
450
+ filesystem.close();
451
+ }
452
+ });
453
+
454
+ it("keeps Tempo unavailable when token telemetry has no source timing", async () => {
455
+ const root = await createSqliteTestRoot();
456
+ roots.push(root);
457
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
458
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
459
+ await writeFile(
460
+ join(root, ".agents", "config", "project.yaml"),
461
+ "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",
462
+ "utf8",
463
+ );
464
+ const started = {
465
+ schema_version: 2,
466
+ event_id: "RUN-1:1",
467
+ run_id: "RUN-1",
468
+ sequence: 1,
469
+ occurred_at: "2026-08-01T12:00:00.000Z",
470
+ type: "run.started",
471
+ attribution_status: "attributed",
472
+ phase: "execution",
473
+ run_role: "orchestrator",
474
+ run_purpose: "execution",
475
+ requires_finalizer: false,
476
+ execution_kind: "delivery",
477
+ execution_eligibility: ["delivery"],
478
+ entrypoint: "task",
479
+ work_type: "new",
480
+ storage_mode: "tracked_sanitized",
481
+ client: "codex",
482
+ agent: "agent",
483
+ agent_name: "agent",
484
+ provider: "openai",
485
+ model: "gpt-5",
486
+ reasoning_effort: null,
487
+ client_thread_id: "thread",
488
+ client_turn_id: "turn",
489
+ conversation_id: "CONV-1",
490
+ execution_id: "EXEC-1",
491
+ task_id: "TASK-1",
492
+ spec_id: null,
493
+ prd_id: null,
494
+ change_id: null,
495
+ parent_run_id: null,
496
+ continuation_of_run_id: null,
497
+ retry_of_run_id: null,
498
+ agent_invocation_id: "AINV-1",
499
+ root_agent_invocation_id: "AINV-1",
500
+ parent_agent_invocation_id: null,
501
+ invocation_kind: "orchestrator",
502
+ delegation_depth: 0,
503
+ };
504
+ const usage = {
505
+ schema_version: 2,
506
+ event_id: "RUN-1:2",
507
+ run_id: "RUN-1",
508
+ sequence: 2,
509
+ occurred_at: "2026-08-01T12:00:00.000Z",
510
+ type: "usage.recorded",
511
+ phase: "execution",
512
+ activity_kind: "model_usage",
513
+ classification_source: "explicit",
514
+ tokens: {
515
+ input: 0,
516
+ cached_input: 0,
517
+ output: 4_000_000,
518
+ reasoning: 0,
519
+ total: 4_000_000,
520
+ source: "provider_reported",
521
+ },
522
+ cost: { usd: null, brl: null, fx_rate: null },
523
+ provider: "openai",
524
+ model: "gpt-5",
525
+ source_segment_key: "segment",
526
+ model_call_id: "MCALL-1",
527
+ runtime: { reasoning_effort: null, service_tier: null },
528
+ supersedes_event_id: null,
529
+ duration_ms: null,
530
+ duration_provenance: null,
531
+ };
532
+ const finished = {
533
+ schema_version: 2,
534
+ event_id: "RUN-1:3",
535
+ run_id: "RUN-1",
536
+ sequence: 3,
537
+ occurred_at: "2026-08-01T12:00:00.000Z",
538
+ type: "run.finished",
539
+ phase: "execution",
540
+ status: "completed",
541
+ duration_ms: null,
542
+ duration_provenance: "unavailable",
543
+ };
544
+ await writeFile(
545
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-1.jsonl"),
546
+ `${[started, usage, finished].map((record) => JSON.stringify(record)).join("\n")}\n`,
547
+ "utf8",
548
+ );
549
+ await rebuildSqliteReadModel(root);
550
+ const database = new DatabaseSync(resolve(root, ".local/dashboard-sqlite/read-model.sqlite"), {
551
+ readOnly: true,
552
+ });
553
+ expect(
554
+ database.prepare("SELECT duration_ms, duration_provenance FROM zaip_aios_activities").get(),
555
+ ).toEqual({ duration_ms: null, duration_provenance: null });
556
+ database.close();
557
+ const filesystem = new SqliteReadModelFileSystem(root);
558
+ const day = filesystem.viewPage("cost_monitor_daily", {
559
+ from: "2026-08-01",
560
+ to: "2026-08-01",
561
+ limit: 20,
562
+ offset: 0,
563
+ }).rows[0];
564
+ if (day === undefined) throw new Error("Expected the daily Cost Monitor row");
565
+ // Token telemetry without a complete source interval remains unavailable;
566
+ // a token estimate must never populate the factual Tempo column.
567
+ expect(day).toMatchObject({
568
+ total_tokens: 4_000_000,
569
+ total_duration: null,
570
+ total_run_count: 1,
571
+ duration_known: 0,
572
+ duration_unknown: 1,
573
+ });
574
+ expect(Object.keys(day).sort()).toEqual([
575
+ "duration_known", "duration_unknown", "evidence_count", "key", "label",
576
+ "prd_count", "spec_count", "task_count", "total_cost_brl", "total_cost_usd",
577
+ "total_duration", "total_run_count", "total_tokens", "validation_count",
578
+ ]);
579
+ expect(filesystem.costMonitorFactualDurationByDay({ from: null, to: null }).get("2026-08-01")).toEqual({
580
+ known_sum: 0,
581
+ eligible_runs: 1,
582
+ known_runs: 0,
583
+ });
584
+ filesystem.close();
585
+ });
586
+
587
+ it("serves canonical model pricing with recorded-period and current FX", async () => {
588
+ const root = await createSqliteTestRoot();
589
+ roots.push(root);
590
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
591
+ await mkdir(join(root, ".agents", "config", "agents"), { recursive: true });
592
+ await mkdir(join(root, ".agents", "config", "pricing", "fx", "2026", "08", "04"), { recursive: true });
593
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
594
+ await writeFile(
595
+ join(root, ".agents", "config", "agents", "catalog.yaml"),
596
+ "agents:\n - id: agent-catalog\n codex_name: agent\n persona: { nome: ana, cargo: quality_engineer }\n role: reviewer\n",
597
+ );
598
+ await writeFile(
599
+ join(root, ".agents", "config", "pricing", "models.json"),
600
+ JSON.stringify({
601
+ schema_version: 1,
602
+ updated_at: "2026-08-04T00:00:00.000Z",
603
+ source: "fixture",
604
+ disclaimer: "fixture",
605
+ models: [
606
+ { id: "gpt-a", provider: "openai", aliases: [], input_per_million_usd: 2, cached_input_per_million_usd: 0.2, output_per_million_usd: 8, reasoning_per_million_usd: 8, reasoning_billed_separately: false, long_context_threshold_tokens: null, long_context_input_multiplier: 1, long_context_output_multiplier: 1, source_label: "fixture-openai", source_url: "https://example.test/openai", notes: [] },
607
+ ],
608
+ }),
609
+ );
610
+ await writeFile(
611
+ join(root, ".agents", "config", "pricing", "fx", "2026", "08", "04", "usd-brl.json"),
612
+ JSON.stringify({ schema_version: 1, base: "USD", quote: "BRL", rate: 5.5, as_of: "2026-08-04T00:00:00.000Z", source_label: "fixture", source_url: "https://example.test/fx" }),
613
+ );
614
+ const started = { schema_version: 2, event_id: "RUN-PRICED:1", run_id: "RUN-PRICED", sequence: 1, occurred_at: "2026-08-08T12:00:00.000Z", type: "run.started", attribution_status: "attributed", phase: "execution", run_role: "orchestrator", run_purpose: "execution", requires_finalizer: false, execution_kind: "delivery", execution_eligibility: ["delivery"], entrypoint: "task", work_type: "new", storage_mode: "tracked_sanitized", client: "codex", agent: "agent", agent_name: "agent", provider: "openai", model: "gpt-a", reasoning_effort: null, client_thread_id: "priced", client_turn_id: "priced", conversation_id: "CONV-PRICED", execution_id: "EXEC-PRICED", task_id: "TASK-PRICED", spec_id: null, prd_id: null, change_id: null, parent_run_id: null, continuation_of_run_id: null, retry_of_run_id: null, agent_invocation_id: "AINV-PRICED", root_agent_invocation_id: "AINV-PRICED", parent_agent_invocation_id: null, invocation_kind: "orchestrator", delegation_depth: 0 };
615
+ const usage = { schema_version: 2, event_id: "RUN-PRICED:2", run_id: "RUN-PRICED", sequence: 2, occurred_at: "2026-08-08T12:00:01.000Z", type: "usage.recorded", phase: "execution", activity_kind: "model_usage", classification_source: "explicit", tokens: { input: 5, cached_input: 0, output: 0, reasoning: 0, total: 5, source: "provider_reported" }, cost: { usd: 0.5, brl: 2.5, fx_rate: 5 }, provider: "openai", model: "gpt-a", source_segment_key: "openai-gpt-a", model_call_id: "MCALL-PRICED", runtime: { reasoning_effort: null, service_tier: null }, supersedes_event_id: null, duration_ms: null, duration_provenance: null };
616
+ const finished = { schema_version: 2, event_id: "RUN-PRICED:3", run_id: "RUN-PRICED", sequence: 3, occurred_at: "2026-08-08T12:01:00.000Z", type: "run.finished", phase: "execution", status: "completed", duration_ms: 60_000, duration_provenance: "observed" };
617
+ await writeFile(
618
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-PRICED.jsonl"),
619
+ `${[started, usage, finished].map((row) => JSON.stringify(row)).join("\n")}\n`,
620
+ );
621
+ await rebuildSqliteReadModel(root);
622
+ const filesystem = new SqliteReadModelFileSystem(root);
623
+ try {
624
+ expect(filesystem.viewPage("cost_monitor_agent_invocations", {}).rows[0]).toMatchObject({
625
+ input_rate_usd_per_million: 2,
626
+ cached_input_rate_usd_per_million: 0.2,
627
+ output_rate_usd_per_million: 8,
628
+ calculated_cost_usd: 0.00001,
629
+ recorded_cost_usd: 0.5,
630
+ recorded_fx_rate: 5,
631
+ recorded_brl: 2.5,
632
+ current_fx_rate: 5.5,
633
+ });
634
+ } finally {
635
+ filesystem.close();
636
+ }
637
+ });
638
+
639
+ it("keeps orphan telemetry out of the Kanban without dropping its Cost Monitor fact", async () => {
640
+ const root = await createSqliteTestRoot();
641
+ roots.push(root);
642
+ await mkdir(join(root, ".agents", "records", "tasks", "2026", "08"), { recursive: true });
643
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
644
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
645
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
646
+ await writeFile(
647
+ join(root, ".agents", "records", "tasks", "2026", "08", "TASK-CANONICAL.md"),
648
+ "---\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",
649
+ );
650
+ const run = (id: string, taskId: string, tokens: number) => [
651
+ { schema_version: 2, event_id: `${id}:1`, run_id: id, sequence: 1, occurred_at: "2026-08-08T12:00:00.000Z", type: "run.started", attribution_status: "attributed", phase: "execution", run_role: "orchestrator", run_purpose: "execution", requires_finalizer: false, execution_kind: "delivery", execution_eligibility: ["delivery"], entrypoint: "task", work_type: "new", storage_mode: "tracked_sanitized", client: "codex", agent: "agent", agent_name: "agent", provider: "openai", model: "gpt-a", reasoning_effort: null, client_thread_id: id, client_turn_id: id, conversation_id: `CONV-${id}`, execution_id: `EXEC-${id}`, task_id: taskId, spec_id: null, prd_id: null, change_id: null, parent_run_id: null, continuation_of_run_id: null, retry_of_run_id: null, agent_invocation_id: `AINV-${id}`, root_agent_invocation_id: `AINV-${id}`, parent_agent_invocation_id: null, invocation_kind: "orchestrator", delegation_depth: 0 },
652
+ { schema_version: 2, event_id: `${id}:2`, run_id: id, sequence: 2, occurred_at: "2026-08-08T12:00:30.000Z", type: "usage.recorded", phase: "execution", activity_kind: "model_usage", classification_source: "explicit", tokens: { input: tokens, cached_input: 0, output: 0, reasoning: 0, total: tokens, source: "provider_reported" }, cost: { usd: tokens / 100, brl: tokens / 20, fx_rate: 5 }, provider: "openai", model: "gpt-a", source_segment_key: id, model_call_id: `MCALL-${id}`, runtime: { reasoning_effort: null, service_tier: null }, supersedes_event_id: null, source_started_at: "2026-08-08T12:00:00.000Z", source_finished_at: "2026-08-08T12:01:00.000Z", duration_ms: null, duration_provenance: null },
653
+ { schema_version: 2, event_id: `${id}:3`, run_id: id, sequence: 3, occurred_at: "2026-08-08T12:01:00.000Z", type: "run.finished", phase: "execution", status: "completed", duration_ms: 60_000, duration_provenance: "observed" },
654
+ ];
655
+ await writeFile(join(root, ".agents", "records", "runs", "2026", "08", "RUN-CANONICAL.jsonl"), `${run("RUN-CANONICAL", "TASK-CANONICAL", 7).map((row) => JSON.stringify(row)).join("\n")}\n`);
656
+ await writeFile(join(root, ".agents", "records", "runs", "2026", "08", "RUN-ORPHAN.jsonl"), `${run("RUN-ORPHAN", "TASK-ORPHAN-REFERENCE", 11).map((row) => JSON.stringify(row)).join("\n")}\n`);
657
+
658
+ await rebuildSqliteReadModel(root);
659
+ const snapshot = new DatabaseSync(resolve(root, ".local/dashboard-sqlite/read-model.sqlite"), { readOnly: true });
660
+ expect(snapshot.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key='schema_version'").get()).toEqual({ value: "60" });
661
+ snapshot.close();
662
+ const filesystem = new SqliteReadModelFileSystem(root);
663
+ try {
664
+ expect(filesystem.viewPage("operations_work_kanban", { limit: 20 }).rows).toEqual([
665
+ expect.objectContaining({ id: "TASK-CANONICAL", title: "Titulo semantico canonico" }),
666
+ ]);
667
+ const costTasks = filesystem.viewPage("cost_monitor_tasks", { limit: 20 }).rows;
668
+ expect(costTasks).toContainEqual(expect.objectContaining({ id: "TASK-ORPHAN-REFERENCE", total_tokens: 11, total_cost_brl: 0.55 }));
669
+ } finally {
670
+ filesystem.close();
671
+ }
672
+ });
673
+
674
+ it("keeps a multi-provider and multi-model Run visible to both filtered financial and duration facts", async () => {
675
+ const root = await createSqliteTestRoot();
676
+ roots.push(root);
677
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
678
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
679
+ await mkdir(join(root, ".agents", "config", "agents"), { recursive: true });
680
+ await mkdir(join(root, ".agents", "config", "pricing"), { recursive: true });
681
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
682
+ await writeFile(
683
+ join(root, ".agents", "config", "agents", "catalog.yaml"),
684
+ "agents:\n - id: agent-catalog\n codex_name: agent\n persona: { nome: ana, cargo: quality_engineer }\n role: reviewer\n",
685
+ );
686
+ await writeFile(
687
+ join(root, ".agents", "config", "pricing", "models.json"),
688
+ JSON.stringify({
689
+ schema_version: 1,
690
+ updated_at: "2026-08-04T00:00:00.000Z",
691
+ source: "fixture",
692
+ disclaimer: "fixture",
693
+ models: [
694
+ { id: "gpt-a", provider: "openai", aliases: [], input_per_million_usd: 2, cached_input_per_million_usd: 0.2, output_per_million_usd: 8, reasoning_per_million_usd: 8, reasoning_billed_separately: false, long_context_threshold_tokens: null, long_context_input_multiplier: 1, long_context_output_multiplier: 1, source_label: "fixture-openai", source_url: "https://example.test/openai", notes: [] },
695
+ { id: "claude-b", provider: "anthropic", aliases: [], input_per_million_usd: 3, cached_input_per_million_usd: 0.3, output_per_million_usd: 12, reasoning_per_million_usd: null, reasoning_billed_separately: false, long_context_threshold_tokens: null, long_context_input_multiplier: 1, long_context_output_multiplier: 1, source_label: "fixture-anthropic", source_url: "https://example.test/anthropic", notes: [] },
696
+ ],
697
+ }),
698
+ );
699
+ const started = { schema_version: 2, event_id: "RUN-MULTI:1", run_id: "RUN-MULTI", sequence: 1, occurred_at: "2026-08-04T12:00:00.000Z", type: "run.started", attribution_status: "attributed", phase: "execution", run_role: "orchestrator", run_purpose: "execution", requires_finalizer: false, execution_kind: "delivery", execution_eligibility: ["delivery"], entrypoint: "task", work_type: "new", storage_mode: "tracked_sanitized", client: "codex", agent: "agent", agent_name: "agent", provider: "mixed", model: "mixed", reasoning_effort: null, client_thread_id: "multi", client_turn_id: "multi", conversation_id: "CONV-MULTI", execution_id: "EXEC-MULTI", task_id: "TASK-MULTI", spec_id: null, prd_id: null, change_id: null, parent_run_id: null, continuation_of_run_id: null, retry_of_run_id: null, agent_invocation_id: "AINV-MULTI", root_agent_invocation_id: "AINV-MULTI", parent_agent_invocation_id: null, invocation_kind: "orchestrator", delegation_depth: 0 };
700
+ const usage = (sequence: number, provider: string, model: string, tokens: number, brl: number) => ({ schema_version: 2, event_id: `RUN-MULTI:${sequence}`, run_id: "RUN-MULTI", sequence, occurred_at: "2026-08-04T12:00:01.000Z", type: "usage.recorded", phase: "execution", activity_kind: "model_usage", classification_source: "explicit", tokens: { input: tokens, cached_input: 0, output: 0, reasoning: 0, total: tokens, source: "provider_reported" }, cost: { usd: brl / 5, brl, fx_rate: 5 }, provider, model, source_segment_key: `${provider}-${model}`, model_call_id: `MCALL-${sequence}`, runtime: { reasoning_effort: null, service_tier: null }, supersedes_event_id: null, source_started_at: "2026-08-04T12:00:00.000Z", source_finished_at: "2026-08-04T12:01:00.000Z", duration_ms: null, duration_provenance: null });
701
+ const finished = { schema_version: 2, event_id: "RUN-MULTI:5", run_id: "RUN-MULTI", sequence: 5, occurred_at: "2026-08-04T12:01:00.000Z", type: "run.finished", phase: "execution", status: "completed", duration_ms: 60_000, duration_provenance: "observed" };
702
+ const mixedNullSegment = {
703
+ ...usage(4, "openai", "gpt-a", 0, 0),
704
+ event_id: "RUN-MULTI:4",
705
+ provider: null,
706
+ model: null,
707
+ source_segment_key: "mixed-null",
708
+ model_call_id: "MCALL-MIXED-NULL",
709
+ };
710
+ await writeFile(join(root, ".agents", "records", "runs", "2026", "08", "RUN-MULTI.jsonl"), `${[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`);
711
+ const unavailableStarted = {
712
+ ...started,
713
+ event_id: "RUN-UNAVAILABLE:1",
714
+ run_id: "RUN-UNAVAILABLE",
715
+ occurred_at: "2026-08-04T14:00:00.000Z",
716
+ client_turn_id: "unavailable",
717
+ conversation_id: "CONV-UNAVAILABLE",
718
+ execution_id: "EXEC-UNAVAILABLE",
719
+ task_id: "TASK-UNAVAILABLE",
720
+ agent_invocation_id: "AINV-UNAVAILABLE",
721
+ root_agent_invocation_id: "AINV-UNAVAILABLE",
722
+ provider: "unavailable",
723
+ model: "unavailable",
724
+ };
725
+ const unavailableFinished = {
726
+ ...finished,
727
+ event_id: "RUN-UNAVAILABLE:2",
728
+ run_id: "RUN-UNAVAILABLE",
729
+ sequence: 3,
730
+ occurred_at: "2026-08-04T14:01:00.000Z",
731
+ };
732
+ const unavailableUsage = {
733
+ ...usage(2, "openai", "gpt-a", 0, 0),
734
+ event_id: "RUN-UNAVAILABLE:usage",
735
+ run_id: "RUN-UNAVAILABLE",
736
+ sequence: 2,
737
+ occurred_at: "2026-08-04T14:00:01.000Z",
738
+ provider: null,
739
+ model: null,
740
+ source_started_at: "2026-08-04T14:00:00.000Z",
741
+ source_finished_at: "2026-08-04T14:01:00.000Z",
742
+ source_segment_key: "unknown-segment",
743
+ model_call_id: "MCALL-UNAVAILABLE",
744
+ };
745
+ await writeFile(
746
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-UNAVAILABLE.jsonl"),
747
+ `${[unavailableStarted, unavailableUsage, unavailableFinished].map((row) => JSON.stringify(row)).join("\n")}\n`,
748
+ );
749
+ // It is a canonical activity but not an Agent Invocation. Cost Monitor
750
+ // must not blend this population into its token/cost/session rate.
751
+ const { agent_invocation_id: _invocation, root_agent_invocation_id: _rootInvocation, parent_agent_invocation_id: _parentInvocation, ...unassistedBase } = started;
752
+ const unassistedStarted = {
753
+ ...unassistedBase,
754
+ event_id: "RUN-UNASSISTED:1",
755
+ run_id: "RUN-UNASSISTED",
756
+ occurred_at: "2026-08-04T16:00:00.000Z",
757
+ client_turn_id: "unassisted",
758
+ conversation_id: "CONV-UNASSISTED",
759
+ execution_id: "EXEC-MULTI",
760
+ task_id: "TASK-MULTI",
761
+ };
762
+ const unassistedUsage = {
763
+ ...usage(2, "openai", "gpt-a", 1_000, 500),
764
+ event_id: "RUN-UNASSISTED:2",
765
+ run_id: "RUN-UNASSISTED",
766
+ occurred_at: "2026-08-04T16:00:01.000Z",
767
+ source_started_at: "2026-08-04T16:00:00.000Z",
768
+ source_finished_at: "2026-08-04T16:01:00.000Z",
769
+ source_segment_key: "unassisted-segment",
770
+ };
771
+ await writeFile(
772
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-UNASSISTED.jsonl"),
773
+ `${[unassistedStarted, unassistedUsage, { ...finished, event_id: "RUN-UNASSISTED:3", run_id: "RUN-UNASSISTED", sequence: 3 }].map((row) => JSON.stringify(row)).join("\n")}\n`,
774
+ );
775
+ await rebuildSqliteReadModel(root);
776
+ const aggregateDatabase = new DatabaseSync(resolve(root, ".local/dashboard-sqlite/read-model.sqlite"), {
777
+ readOnly: true,
778
+ });
779
+ try {
780
+ const aggregates = aggregateDatabase.prepare(`SELECT
781
+ json_extract(payload_json, '$.dimension') AS dimension,
782
+ json_extract(payload_json, '$.key') AS key,
783
+ json_extract(payload_json, '$.schema_version') AS schema_version,
784
+ json_extract(payload_json, '$.total_cost_brl') AS total_cost_brl
785
+ FROM zaip_aios_activities
786
+ WHERE payload_type = 'cost_dimension_aggregate_v1'`).all();
787
+ expect(aggregates).toEqual(expect.arrayContaining([
788
+ expect.objectContaining({ dimension: "provider", key: "openai", schema_version: 1, total_cost_brl: 505 }),
789
+ expect.objectContaining({ dimension: "model", key: "gpt-a", schema_version: 1, total_cost_brl: 505 }),
790
+ ]));
791
+ expect(aggregates.map((row) => row.key)).toEqual(expect.arrayContaining(["unavailable"]));
792
+ } finally {
793
+ aggregateDatabase.close();
794
+ }
795
+ const filesystem = new SqliteReadModelFileSystem(root);
796
+ expect(filesystem.viewPage("cost_monitor_daily", { from: "2026-08-04", to: "2026-08-04" }).rows[0]).toMatchObject({ total_tokens: 1_030, total_cost_brl: 515 });
797
+ // The public overview view backs /api/v2/sqlite-views. It must not leak
798
+ // Cost Monitor retains all measurable financial activities, including
799
+ // historical/unassisted telemetry without an Agent Invocation id.
800
+ const publicOverview = filesystem.viewPage("cost_monitor_overview_view", {});
801
+ expect(publicOverview.rows).toHaveLength(3);
802
+ expect(publicOverview.rows).toEqual(
803
+ expect.arrayContaining([
804
+ expect.objectContaining({ period_kind: "day", total_run_count: 3, invocation_count: 3, total_tokens: 1_030, total_cost_brl: 515 }),
805
+ expect.objectContaining({ period_kind: "month", total_run_count: 3, invocation_count: 3, total_tokens: 1_030, total_cost_brl: 515 }),
806
+ expect.objectContaining({ period_kind: "year", total_run_count: 3, invocation_count: 3, total_tokens: 1_030, total_cost_brl: 515 }),
807
+ ]),
808
+ );
809
+ const allAgentGroups = filesystem.viewPage("cost_monitor_zaip_agents", { limit: 20, offset: 0 });
810
+ const allCostTotals = publicOverview.rows.find((row) => row.period_kind === "day");
811
+ if (allCostTotals === undefined) throw new Error("Expected the daily Cost Monitor overview");
812
+ expect(allAgentGroups.rows.reduce((total, row) => total + Number(row.invocation_count ?? 0), 0)).toBe(allCostTotals.total_run_count);
813
+ expect(allAgentGroups.rows.reduce((total, row) => total + Number(row.total_tokens ?? 0), 0)).toBe(allCostTotals?.total_tokens);
814
+ expect(allAgentGroups.rows.reduce((total, row) => total + Number(row.total_cost_brl ?? 0), 0)).toBe(allCostTotals?.total_cost_brl);
815
+ expect(allAgentGroups.rows.reduce((total, row) => total + Number(row.total_cost_usd ?? 0), 0)).toBe(allCostTotals.total_cost_usd);
816
+ // Shared Cost Tempo is factual at the activity grain. It remains available
817
+ // in the agent-table total even where a multi-dimension invocation cannot
818
+ // be assigned to an individual Agent row.
819
+ expect(allCostTotals.cost_monitor_total_duration).toBe(120_000);
820
+ expect(allAgentGroups.totals).toMatchObject({ invocation_count: allCostTotals.total_run_count, total_duration: 120_000, total_tokens: allCostTotals.total_tokens, total_cost_usd: allCostTotals.total_cost_usd, total_cost_brl: allCostTotals.total_cost_brl });
821
+ for (const [providers, models] of [[ ["openai"], ["gpt-a"] ], [["anthropic"], ["claude-b"] ]] as const) {
822
+ const rows = filesystem.invocationCostRows({ from: null, to: null, providers, models });
823
+ expect(rows).toHaveLength(1);
824
+ // Membership is segment-based; once the invocation matches, its
825
+ // canonical invocation totals remain intact rather than being split.
826
+ expect(rows[0]).toMatchObject({ total_tokens: 30, cost_brl: 15 });
827
+ expect([...filesystem.executionDurationByDay({ from: null, to: null, providers, models }).values()]).toEqual([60_000]);
828
+ expect([...filesystem.canonicalRunCountByDay({ from: null, to: null, providers, models }).values()]).toEqual([1]);
829
+ for (const view of ["cost_monitor_daily", "cost_monitor_monthly", "cost_monitor_annual"] as const) {
830
+ const page = filesystem.viewPage(view, { provider: providers[0], model: models[0], limit: 20, offset: 0 });
831
+ expect(page.rows[0]).toMatchObject({ total_run_count: 1, total_tokens: 30, total_cost_brl: 15 });
832
+ }
833
+ const agents = filesystem.viewPage("cost_monitor_zaip_agents", { provider: providers[0], model: models[0], query: "agent" });
834
+ const financialRows = filesystem.costMonitorFinancialActivityRows({ from: null, to: null, providers, models });
835
+ const financialTotals = financialRows.reduce<{ invocation_count: number; total_tokens: number; total_cost_brl: number }>((total, row) => ({
836
+ invocation_count: total.invocation_count + 1,
837
+ total_tokens: total.total_tokens + Number(row.total_tokens ?? 0),
838
+ total_cost_brl: total.total_cost_brl + Number(row.cost_brl ?? 0),
839
+ }), { invocation_count: 0, total_tokens: 0, total_cost_brl: 0 });
840
+ expect(agents.rows[0]).toMatchObject({ key: "agent", persona: "ana", job: "quality_engineer", task_count: 1, ...financialTotals });
841
+ expect(agents.totals).toMatchObject(financialTotals);
842
+ }
843
+ const crossed = { from: null, to: null, providers: ["openai"], models: ["claude-b"] };
844
+ expect(filesystem.invocationCostRows(crossed)).toHaveLength(0);
845
+ expect([...filesystem.executionDurationByDay(crossed).values()]).toEqual([]);
846
+ expect([...filesystem.canonicalRunCountByDay(crossed).values()]).toEqual([]);
847
+ expect(filesystem.viewPage("cost_monitor_agent_invocations", { provider: "openai", model: "claude-b" }).total_count).toBe(0);
848
+ expect(filesystem.viewPage("cost_monitor_zaip_agents", { provider: "openai", model: "claude-b" }).total_count).toBe(0);
849
+ for (const view of ["cost_monitor_daily", "cost_monitor_monthly", "cost_monitor_annual"] as const) {
850
+ // Lifecycle buckets can remain present independently, but the crossed
851
+ // pair must contribute no invocation financial or Run facts.
852
+ const row = filesystem.viewPage(view, { provider: "openai", model: "claude-b" }).rows[0];
853
+ expect(row).toMatchObject({ total_run_count: 0, total_duration: null, total_tokens: null, total_cost_brl: null });
854
+ }
855
+ for (const view of ["cost_monitor_daily", "cost_monitor_monthly", "cost_monitor_annual"] as const) {
856
+ const originFiltered = filesystem.viewPage(view, { origin: "task" });
857
+ expect(originFiltered.rows[0]).toMatchObject({ prd_count: null, spec_count: null, task_count: null, validation_count: null, evidence_count: null });
858
+ expect(filesystem.viewPage(view, { workType: "new" }).total_count).toBe(0);
859
+ }
860
+ expect(filesystem.viewPage("cost_monitor_zaip_agents", { priority: "high" })).toMatchObject({ total_count: 0, rows: [] });
861
+ const providerGroups = filesystem.viewPage("cost_monitor_providers", { limit: 20, offset: 0 });
862
+ expect(providerGroups.rows).toEqual(expect.arrayContaining([
863
+ expect.objectContaining({ key: "openai", total_tokens: 1_010, total_cost_brl: 505 }),
864
+ expect.objectContaining({ key: "anthropic", total_tokens: 20, total_cost_brl: 10 }),
865
+ ]));
866
+ expect(providerGroups.rows.map((row) => row.key)).toEqual(expect.arrayContaining(["unavailable"]));
867
+ expect(filesystem.viewPage("cost_monitor_providers", { provider: "openai", model: "gpt-a" }).rows.find((row) => row.key === "openai")).toMatchObject({ total_tokens: 10, total_cost_brl: 5 });
868
+ expect(filesystem.viewPage("cost_monitor_providers", { provider: "openai", model: "claude-b" })).toMatchObject({ total_count: 0, rows: [] });
869
+ // A source segment can prove its financial facts, but an invocation with
870
+ // multiple observed providers cannot prove a provider-specific elapsed
871
+ // time. It must not manufacture a "mixed" bucket for that remainder.
872
+ const queriedProviderRows = filesystem.viewPage("cost_monitor_providers", { query: "openai" }).rows;
873
+ expect(queriedProviderRows).toEqual([
874
+ expect.objectContaining({ key: "openai", total_tokens: 10, total_cost_brl: 5 }),
875
+ ]);
876
+ const modelGroups = filesystem.viewPage("cost_monitor_models", { limit: 20, offset: 0 });
877
+ expect(modelGroups.rows).toEqual(expect.arrayContaining([
878
+ expect.objectContaining({ key: "gpt-a", total_tokens: 1_010, total_cost_brl: 505 }),
879
+ expect.objectContaining({ key: "claude-b", total_tokens: 20, total_cost_brl: 10 }),
880
+ ]));
881
+ // First-fold transport is aggregate-only, but its overview chart must
882
+ // retain the segment-backed model rows instead of falling back to the
883
+ // empty availability metadata.
884
+ expect(filesystem.costMonitorSummaryAggregate({ from: null, to: null }).model_groups).toEqual(
885
+ expect.arrayContaining([
886
+ expect.objectContaining({ key: "gpt-a", total_tokens: 1_010, estimated_brl: 505 }),
887
+ expect.objectContaining({ key: "claude-b", total_tokens: 20, estimated_brl: 10 }),
888
+ ]),
889
+ );
890
+ expect(modelGroups.rows.map((row) => row.key)).toEqual(expect.arrayContaining(["unavailable"]));
891
+ const modelTotals = modelGroups.rows.reduce<{ invocations: number; tasks: number; duration: number; tokens: number; brl: number }>(
892
+ (total, row) => ({
893
+ invocations: total.invocations + Number(row.invocation_count ?? 0),
894
+ tasks: total.tasks + Number(row.task_count ?? 0),
895
+ duration: total.duration + Number(row.total_duration ?? 0),
896
+ tokens: total.tokens + Number(row.total_tokens ?? 0),
897
+ brl: total.brl + Number(row.total_cost_brl ?? 0),
898
+ }),
899
+ { invocations: 0, tasks: 0, duration: 0, tokens: 0, brl: 0 },
900
+ );
901
+ expect(modelTotals).toEqual({ invocations: 2, tasks: 3, duration: 60_000, tokens: 1030, brl: 515 });
902
+ expect(filesystem.viewPage("cost_monitor_models", { query: "gpt-a" }).rows).toHaveLength(1);
903
+ expect(filesystem.viewPage("cost_monitor_models", { provider: "openai", model: "gpt-a" }).rows[0]).toMatchObject({ key: "gpt-a", total_tokens: 1010, total_cost_brl: 505 });
904
+ expect(filesystem.viewPage("cost_monitor_models", { provider: "openai", model: "claude-b" })).toMatchObject({ total_count: 0, rows: [] });
905
+ expect(filesystem.viewPage("cost_monitor_models", { priority: "high" })).toMatchObject({ total_count: 0, rows: [] });
906
+ const unavailable = { from: null, to: null, providers: ["unavailable"], models: ["unavailable"] };
907
+ const unavailableRows = filesystem.invocationCostRows(unavailable);
908
+ expect(unavailableRows).toHaveLength(1);
909
+ // Observed zero telemetry stays zero, while the missing provider/model
910
+ // attribute selects this segment rather than a literal sentinel value.
911
+ expect(unavailableRows[0]).toMatchObject({ total_tokens: 0, cost_brl: 0 });
912
+ // Execution-duration is an Operations compatibility aggregate and still
913
+ // includes the unassisted canonical Run. Cost Monitor retains its
914
+ // measurable financial activity as an unavailable historical fact.
915
+ expect([...filesystem.executionDurationByDay(unavailable).values()]).toEqual([60_000]);
916
+ expect([...filesystem.canonicalRunCountByDay(unavailable).values()]).toEqual([2]);
917
+ const unavailableInvocation = filesystem.viewPage("cost_monitor_agent_invocations", {
918
+ provider: "unavailable",
919
+ model: "unavailable",
920
+ });
921
+ expect(unavailableInvocation).toMatchObject({ total_count: 1 });
922
+ expect(unavailableInvocation.rows[0]).toMatchObject({ input_tokens: 0 });
923
+ for (const view of ["cost_monitor_daily", "cost_monitor_monthly", "cost_monitor_annual"] as const) {
924
+ const row = filesystem.viewPage(view, { provider: "unavailable", model: "unavailable" }).rows[0];
925
+ expect(row).toMatchObject({ total_run_count: 2, total_tokens: 1_000, total_cost_brl: 500 });
926
+ }
927
+
928
+ filesystem.close();
929
+ const sameModelStarted = { ...started, event_id: "RUN-SAME-MODEL:1", run_id: "RUN-SAME-MODEL", occurred_at: "2026-08-05T12:00:00.000Z", client_turn_id: "same-model", conversation_id: "CONV-SAME-MODEL", execution_id: "EXEC-SAME-MODEL", task_id: "TASK-SAME-MODEL", agent_invocation_id: "AINV-SAME-MODEL", root_agent_invocation_id: "AINV-SAME-MODEL", provider: "mixed", model: "gpt-a" };
930
+ const sameModelUsage = (sequence: number, provider: string, tokens: number, brl: number) => ({ ...usage(sequence, provider, "gpt-a", tokens, brl), event_id: `RUN-SAME-MODEL:${sequence}`, run_id: "RUN-SAME-MODEL", occurred_at: "2026-08-05T12:00:01.000Z", source_started_at: "2026-08-05T12:00:00.000Z", source_finished_at: "2026-08-05T12:01:00.000Z" });
931
+ const sameModelFinished = { ...finished, event_id: "RUN-SAME-MODEL:4", run_id: "RUN-SAME-MODEL", sequence: 4, occurred_at: "2026-08-05T12:01:00.000Z" };
932
+ await writeFile(join(root, ".agents", "records", "runs", "2026", "08", "RUN-SAME-MODEL.jsonl"), `${[sameModelStarted, sameModelUsage(2, "openai", 3, 1.5), sameModelUsage(3, "anthropic", 4, 2), sameModelFinished].map((row) => JSON.stringify(row)).join("\n")}\n`);
933
+ const durationRun = (id: string, day: string, duration: number | null, provenance: "reconciled" | "estimated") => {
934
+ const start = { ...sameModelStarted, event_id: `${id}:1`, run_id: id, occurred_at: `${day}T12:00:00.000Z`, client_turn_id: id, conversation_id: `CONV-${id}`, execution_id: `EXEC-${id}`, task_id: `TASK-${id}`, agent_invocation_id: `AINV-${id}`, root_agent_invocation_id: `AINV-${id}` };
935
+ const measured = { ...usage(2, "openai", "gpt-a", provenance === "estimated" ? 4_000_000 : 9, provenance === "estimated" ? 0 : 4.5), event_id: `${id}:2`, run_id: id, occurred_at: `${day}T12:00:01.000Z`, source_started_at: `${day}T12:00:00.000Z`, source_finished_at: `${day}T12:01:00.000Z`, ...(duration === null ? {} : { duration_ms: duration, duration_provenance: provenance }) };
936
+ const end = { ...finished, event_id: `${id}:3`, run_id: id, sequence: 3, occurred_at: `${day}T12:01:00.000Z`, ...(provenance === "estimated" ? { duration_ms: null, duration_provenance: "unavailable" } : {}) };
937
+ return [start, measured, end];
938
+ };
939
+ await writeFile(join(root, ".agents", "records", "runs", "2026", "08", "RUN-RECONCILED-MODEL.jsonl"), `${durationRun("RUN-RECONCILED-MODEL", "2026-08-06", 45_000, "reconciled").map((row) => JSON.stringify(row)).join("\n")}\n`);
940
+ await writeFile(join(root, ".agents", "records", "runs", "2026", "08", "RUN-ESTIMATED-MODEL.jsonl"), `${durationRun("RUN-ESTIMATED-MODEL", "2026-08-07", null, "estimated").map((row) => JSON.stringify(row)).join("\n")}\n`);
941
+ const sharedTaskRun = (id: string, model: string, tokens: number, brl: number) => [
942
+ { ...sameModelStarted, event_id: `${id}:1`, run_id: id, occurred_at: "2026-08-08T12:00:00.000Z", client_turn_id: id, conversation_id: `CONV-${id}`, execution_id: `EXEC-${id}`, task_id: "TASK-CROSS-MODEL", agent_invocation_id: `AINV-${id}`, root_agent_invocation_id: `AINV-${id}`, provider: "openai", model },
943
+ { ...usage(2, "openai", model, tokens, brl), event_id: `${id}:2`, run_id: id, occurred_at: "2026-08-08T12:00:01.000Z", source_started_at: "2026-08-08T12:00:00.000Z", source_finished_at: "2026-08-08T12:01:00.000Z" },
944
+ { ...finished, event_id: `${id}:3`, run_id: id, sequence: 3, occurred_at: "2026-08-08T12:01:00.000Z" },
945
+ ];
946
+ await writeFile(join(root, ".agents", "records", "runs", "2026", "08", "RUN-TASK-GPT.jsonl"), `${sharedTaskRun("RUN-TASK-GPT", "gpt-a", 5, 2.5).map((row) => JSON.stringify(row)).join("\n")}\n`);
947
+ await writeFile(join(root, ".agents", "records", "runs", "2026", "08", "RUN-TASK-CLAUDE.jsonl"), `${sharedTaskRun("RUN-TASK-CLAUDE", "claude-b", 6, 3).map((row) => JSON.stringify(row)).join("\n")}\n`);
948
+ const subrunStarted = { ...sameModelStarted, event_id: "RUN-PROVIDER-SUBRUN:1", run_id: "RUN-PROVIDER-SUBRUN", occurred_at: "2026-08-09T12:00:00.000Z", client_turn_id: "provider-subrun", conversation_id: "CONV-PROVIDER-SUBRUN", execution_id: "EXEC-PROVIDER-SUBRUN", task_id: "TASK-PROVIDER-SUBRUN", agent_invocation_id: "AINV-PROVIDER-SUBRUN", root_agent_invocation_id: "AINV-SAME-MODEL", parent_agent_invocation_id: "AINV-SAME-MODEL", parent_run_id: "RUN-SAME-MODEL", run_role: "subagent", invocation_kind: "delegated", delegation_depth: 1, provider: "openai", model: "gpt-a" };
949
+ const subrunUsage = { ...usage(2, "openai", "gpt-a", 8, 4), event_id: "RUN-PROVIDER-SUBRUN:2", run_id: "RUN-PROVIDER-SUBRUN", occurred_at: "2026-08-09T12:00:01.000Z", source_started_at: "2026-08-09T12:00:00.000Z", source_finished_at: "2026-08-09T12:00:30.000Z" };
950
+ const subrunFinished = { ...finished, event_id: "RUN-PROVIDER-SUBRUN:3", run_id: "RUN-PROVIDER-SUBRUN", sequence: 3, occurred_at: "2026-08-09T12:00:30.000Z", duration_ms: 30_000, duration_provenance: "observed" };
951
+ await writeFile(join(root, ".agents", "records", "runs", "2026", "08", "RUN-PROVIDER-SUBRUN.jsonl"), `${[subrunStarted, subrunUsage, subrunFinished].map((row) => JSON.stringify(row)).join("\n")}\n`);
952
+ await rebuildSqliteReadModel(root);
953
+ const sameModelFs = new SqliteReadModelFileSystem(root);
954
+ const modelRow = (filter: Record<string, string>) => sameModelFs.viewPage("cost_monitor_models", { from: "2026-08-05", to: "2026-08-05", ...filter }).rows.find((row) => row.key === "gpt-a");
955
+ expect(modelRow({ provider: "openai", model: "gpt-a" })).toMatchObject({ invocation_count: 1, task_count: 1, total_duration: 60_000, total_tokens: 3, total_cost_brl: 1.5 });
956
+ expect(modelRow({ provider: "anthropic", model: "gpt-a" })).toMatchObject({ invocation_count: 1, task_count: 1, total_duration: 60_000, total_tokens: 4, total_cost_brl: 2 });
957
+ expect(modelRow({})).toMatchObject({ invocation_count: 1, task_count: 1, total_duration: 60_000, total_tokens: 7, total_cost_brl: 3.5 });
958
+ expect(sameModelFs.viewPage("cost_monitor_models", { from: "2026-08-05", to: "2026-08-05", provider: "openai", model: "claude-b" })).toMatchObject({ total_count: 0, rows: [] });
959
+ for (const day of ["2026-08-06", "2026-08-07"]) {
960
+ const effective = sameModelFs.invocationCostRows({ from: day, to: day }).reduce((sum, row) => sum + Number(row.duration_ms ?? 0), 0);
961
+ const modelDuration = sameModelFs.viewPage("cost_monitor_models", { from: day, to: day }).rows.reduce((sum, row) => sum + Number(row.total_duration ?? 0), 0);
962
+ expect(modelDuration).toBe(effective);
963
+ }
964
+ for (const day of ["2026-08-06", "2026-08-07"]) {
965
+ const population = sameModelFs.invocationCostRows({ from: day, to: day });
966
+ const providerRows = sameModelFs.viewPage("cost_monitor_providers", { from: day, to: day }).rows;
967
+ const providerTotals = providerRows.reduce<{ invocations: number; tasks: number; duration: number; tokens: number; brl: number }>(
968
+ (total, row) => ({
969
+ invocations: total.invocations + Number(row.invocation_count ?? 0),
970
+ tasks: total.tasks + Number(row.task_count ?? 0),
971
+ duration: total.duration + Number(row.total_duration ?? 0),
972
+ tokens: total.tokens + Number(row.total_tokens ?? 0),
973
+ brl: total.brl + Number(row.total_cost_brl ?? 0),
974
+ }),
975
+ { invocations: 0, tasks: 0, duration: 0, tokens: 0, brl: 0 },
976
+ );
977
+ expect(providerTotals).toEqual({
978
+ invocations: population.length,
979
+ tasks: new Set(population.map((row) => row.task_id).filter((id): id is string => typeof id === "string")).size,
980
+ duration: population.reduce((sum, row) => sum + Number(row.duration_ms ?? 0), 0),
981
+ tokens: population.reduce((sum, row) => sum + Number(row.total_tokens ?? 0), 0),
982
+ brl: population.reduce((sum, row) => sum + Number(row.cost_brl ?? 0), 0),
983
+ });
984
+ }
985
+ const sharedTaskRows = sameModelFs.viewPage("cost_monitor_models", { from: "2026-08-08", to: "2026-08-08" }).rows;
986
+ expect(sharedTaskRows).toEqual(expect.arrayContaining([
987
+ expect.objectContaining({ key: "gpt-a", invocation_count: 1, task_count: 1, total_duration: 30_000, total_tokens: 5, total_cost_brl: 2.5 }),
988
+ expect.objectContaining({ key: "claude-b", invocation_count: 1, task_count: 1, total_duration: 30_000, total_tokens: 6, total_cost_brl: 3 }),
989
+ ]));
990
+ expect(sharedTaskRows.map((row) => row.key)).not.toContain("mixed");
991
+ expect(sharedTaskRows.reduce((sum, row) => sum + Number(row.task_count ?? 0), 0)).toBe(2);
992
+ const subrunPopulation = sameModelFs.invocationCostRows({ from: "2026-08-09", to: "2026-08-09", providers: ["openai"], models: ["gpt-a"] });
993
+ const subrunProviderRows = sameModelFs.viewPage("cost_monitor_providers", { from: "2026-08-09", to: "2026-08-09", provider: "openai", model: "gpt-a" }).rows;
994
+ const subrunProviderTotals = subrunProviderRows.reduce<{ invocations: number; tasks: number; duration: number; tokens: number; brl: number }>(
995
+ (total, row) => ({ invocations: total.invocations + Number(row.invocation_count ?? 0), tasks: total.tasks + Number(row.task_count ?? 0), duration: total.duration + Number(row.total_duration ?? 0), tokens: total.tokens + Number(row.total_tokens ?? 0), brl: total.brl + Number(row.total_cost_brl ?? 0) }),
996
+ { invocations: 0, tasks: 0, duration: 0, tokens: 0, brl: 0 },
997
+ );
998
+ expect(subrunProviderTotals).toEqual({ invocations: subrunPopulation.length, tasks: new Set(subrunPopulation.map((row) => row.task_id).filter((id): id is string => typeof id === "string")).size, duration: subrunPopulation.reduce((sum, row) => sum + Number(row.duration_ms ?? 0), 0), tokens: subrunPopulation.reduce((sum, row) => sum + Number(row.total_tokens ?? 0), 0), brl: subrunPopulation.reduce((sum, row) => sum + Number(row.cost_brl ?? 0), 0) });
999
+ expect(sameModelFs.viewPage("cost_monitor_providers", { from: "2026-08-09", to: "2026-08-09", provider: "openai", model: "claude-b" })).toMatchObject({ total_count: 0, rows: [] });
1000
+ sameModelFs.close();
1001
+ });
1002
+
1003
+ it("prefers the canonical started-to-finished interval over a token estimate", async () => {
1004
+ const root = await createSqliteTestRoot();
1005
+ roots.push(root);
1006
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
1007
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
1008
+ await writeFile(
1009
+ join(root, ".agents", "config", "project.yaml"),
1010
+ "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",
1011
+ "utf8",
1012
+ );
1013
+ const started = {
1014
+ schema_version: 2,
1015
+ event_id: "RUN-2:1",
1016
+ run_id: "RUN-2",
1017
+ sequence: 1,
1018
+ occurred_at: "2026-08-01T12:00:00.000Z",
1019
+ type: "run.started",
1020
+ attribution_status: "attributed",
1021
+ phase: "execution",
1022
+ run_role: "orchestrator",
1023
+ run_purpose: "execution",
1024
+ requires_finalizer: false,
1025
+ execution_kind: "delivery",
1026
+ execution_eligibility: ["delivery"],
1027
+ entrypoint: "task",
1028
+ work_type: "new",
1029
+ storage_mode: "tracked_sanitized",
1030
+ client: "codex",
1031
+ agent: "agent",
1032
+ agent_name: "agent",
1033
+ provider: "openai",
1034
+ model: "gpt-5",
1035
+ reasoning_effort: null,
1036
+ client_thread_id: "thread",
1037
+ client_turn_id: "turn",
1038
+ conversation_id: "CONV-2",
1039
+ execution_id: "EXEC-2",
1040
+ task_id: "TASK-2",
1041
+ spec_id: null,
1042
+ prd_id: null,
1043
+ change_id: null,
1044
+ parent_run_id: null,
1045
+ continuation_of_run_id: null,
1046
+ retry_of_run_id: null,
1047
+ agent_invocation_id: "AINV-2",
1048
+ root_agent_invocation_id: "AINV-2",
1049
+ parent_agent_invocation_id: null,
1050
+ invocation_kind: "orchestrator",
1051
+ delegation_depth: 0,
1052
+ };
1053
+ const usage = {
1054
+ schema_version: 2,
1055
+ event_id: "RUN-2:2",
1056
+ run_id: "RUN-2",
1057
+ sequence: 2,
1058
+ occurred_at: "2026-08-01T12:10:00.000Z",
1059
+ type: "usage.recorded",
1060
+ phase: "execution",
1061
+ activity_kind: "model_usage",
1062
+ classification_source: "explicit",
1063
+ tokens: {
1064
+ input: 4_000_000,
1065
+ cached_input: 0,
1066
+ output: 0,
1067
+ reasoning: 0,
1068
+ total: 4_000_000,
1069
+ source: "provider_reported",
1070
+ },
1071
+ cost: { usd: null, brl: null, fx_rate: null },
1072
+ provider: "openai",
1073
+ model: "gpt-5",
1074
+ source_segment_key: "segment",
1075
+ model_call_id: "MCALL-2",
1076
+ runtime: { reasoning_effort: null, service_tier: null },
1077
+ supersedes_event_id: null,
1078
+ duration_ms: null,
1079
+ duration_provenance: null,
1080
+ };
1081
+ const finished = {
1082
+ schema_version: 2,
1083
+ event_id: "RUN-2:3",
1084
+ run_id: "RUN-2",
1085
+ sequence: 3,
1086
+ occurred_at: "2026-08-01T12:30:00.000Z",
1087
+ type: "run.finished",
1088
+ phase: "execution",
1089
+ status: "completed",
1090
+ duration_ms: null,
1091
+ duration_provenance: "unavailable",
1092
+ };
1093
+ await writeFile(
1094
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-2.jsonl"),
1095
+ `${[started, usage, finished].map((record) => JSON.stringify(record)).join("\n")}\n`,
1096
+ "utf8",
1097
+ );
1098
+ await rebuildSqliteReadModel(root);
1099
+ const database = new DatabaseSync(resolve(root, ".local/dashboard-sqlite/read-model.sqlite"), {
1100
+ readOnly: true,
1101
+ });
1102
+ expect(
1103
+ database.prepare("SELECT duration_ms, duration_provenance FROM zaip_aios_activities").get(),
1104
+ ).toEqual({ duration_ms: 1_800_000, duration_provenance: "attributed_token_active" });
1105
+ database.close();
1106
+ });
1107
+
1108
+ it("does not convert a delayed historical finalizer into execution time", async () => {
1109
+ const root = await createSqliteTestRoot();
1110
+ roots.push(root);
1111
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
1112
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
1113
+ await writeFile(
1114
+ join(root, ".agents", "config", "project.yaml"),
1115
+ "zaip:\n enabled: true\n",
1116
+ "utf8",
1117
+ );
1118
+ const started = {
1119
+ schema_version: 2,
1120
+ event_id: "RUN-3:1",
1121
+ run_id: "RUN-3",
1122
+ sequence: 1,
1123
+ occurred_at: "2026-08-01T12:00:00.000Z",
1124
+ type: "run.started",
1125
+ attribution_status: "attributed",
1126
+ phase: "execution",
1127
+ run_role: "orchestrator",
1128
+ run_purpose: "execution",
1129
+ requires_finalizer: false,
1130
+ execution_kind: "delivery",
1131
+ execution_eligibility: ["delivery"],
1132
+ entrypoint: "task",
1133
+ work_type: "new",
1134
+ storage_mode: "tracked_sanitized",
1135
+ client: "codex",
1136
+ agent: "agent",
1137
+ agent_name: "agent",
1138
+ provider: "openai",
1139
+ model: "gpt-5",
1140
+ reasoning_effort: null,
1141
+ client_thread_id: "thread",
1142
+ client_turn_id: "turn",
1143
+ conversation_id: "CONV-3",
1144
+ execution_id: "EXEC-3",
1145
+ task_id: "TASK-3",
1146
+ spec_id: null,
1147
+ prd_id: null,
1148
+ change_id: null,
1149
+ parent_run_id: null,
1150
+ continuation_of_run_id: null,
1151
+ retry_of_run_id: null,
1152
+ agent_invocation_id: "AINV-3",
1153
+ root_agent_invocation_id: "AINV-3",
1154
+ parent_agent_invocation_id: null,
1155
+ invocation_kind: "orchestrator",
1156
+ delegation_depth: 0,
1157
+ };
1158
+ const usage = {
1159
+ schema_version: 2,
1160
+ event_id: "RUN-3:2",
1161
+ run_id: "RUN-3",
1162
+ sequence: 2,
1163
+ occurred_at: "2026-08-01T12:10:00.000Z",
1164
+ type: "usage.recorded",
1165
+ phase: "execution",
1166
+ activity_kind: "model_usage",
1167
+ classification_source: "explicit",
1168
+ tokens: {
1169
+ input: 20_000_000,
1170
+ cached_input: 19_000_000,
1171
+ output: 400_000,
1172
+ reasoning: 0,
1173
+ total: 20_400_000,
1174
+ source: "provider_reported",
1175
+ },
1176
+ cost: { usd: null, brl: null, fx_rate: null },
1177
+ provider: "openai",
1178
+ model: "gpt-5",
1179
+ source_segment_key: "segment",
1180
+ model_call_id: "MCALL-3",
1181
+ runtime: { reasoning_effort: null, service_tier: null },
1182
+ supersedes_event_id: null,
1183
+ duration_ms: null,
1184
+ duration_provenance: null,
1185
+ };
1186
+ const finished = {
1187
+ schema_version: 2,
1188
+ event_id: "RUN-3:3",
1189
+ run_id: "RUN-3",
1190
+ sequence: 3,
1191
+ occurred_at: "2026-08-03T12:00:00.000Z",
1192
+ type: "run.finished",
1193
+ phase: "execution",
1194
+ status: "completed",
1195
+ duration_ms: null,
1196
+ duration_provenance: "unavailable",
1197
+ };
1198
+ await writeFile(
1199
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-3.jsonl"),
1200
+ `${[started, usage, finished].map((record) => JSON.stringify(record)).join("\n")}\n`,
1201
+ "utf8",
1202
+ );
1203
+ await rebuildSqliteReadModel(root);
1204
+ const database = new DatabaseSync(resolve(root, ".local/dashboard-sqlite/read-model.sqlite"), {
1205
+ readOnly: true,
1206
+ });
1207
+ expect(
1208
+ database.prepare("SELECT duration_ms, duration_provenance FROM zaip_aios_activities").get(),
1209
+ ).toEqual({ duration_ms: null, duration_provenance: null });
1210
+ database.close();
1211
+ });
1212
+
1213
+ it("unions overlapping source intervals, deduplicates source-segment aliases, and keeps cross-midnight financial facts in their source day", async () => {
1214
+ const root = await createSqliteTestRoot();
1215
+ roots.push(root);
1216
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "07"), { recursive: true });
1217
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
1218
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
1219
+ const run = ({ id, phase, sourceStartedAt, sourceFinishedAt, parentRunId = null }: {
1220
+ id: string; phase: "conversation" | "planning" | "execution";
1221
+ sourceStartedAt: string; sourceFinishedAt: string; parentRunId?: string | null;
1222
+ }) => {
1223
+ const occurredAt = sourceStartedAt;
1224
+ const agentInvocationId = `AINV-${id}`;
1225
+ return [
1226
+ {
1227
+ schema_version: 2, event_id: `${id}:1`, run_id: id, sequence: 1, occurred_at: occurredAt,
1228
+ type: "run.started", attribution_status: "attributed", phase, run_role: parentRunId === null ? "orchestrator" : "subagent",
1229
+ run_purpose: "orchestration", requires_finalizer: false, execution_kind: "delivery",
1230
+ execution_eligibility: ["delivery"], entrypoint: "task", work_type: "new",
1231
+ storage_mode: "tracked_sanitized", client: "codex", agent: "agent", agent_name: "agent",
1232
+ provider: "openai", model: "gpt-5", reasoning_effort: null, client_thread_id: `thread-${id}`,
1233
+ client_turn_id: `turn-${id}`, conversation_id: `CONV-${id}`, execution_id: `EXEC-${id}`,
1234
+ task_id: `TASK-${id}`, spec_id: null, prd_id: null, change_id: null, parent_run_id: parentRunId,
1235
+ continuation_of_run_id: null, retry_of_run_id: null, agent_invocation_id: agentInvocationId,
1236
+ root_agent_invocation_id: parentRunId === null ? agentInvocationId : "AINV-root",
1237
+ parent_agent_invocation_id: parentRunId === null ? null : "AINV-root",
1238
+ invocation_kind: parentRunId === null ? "orchestrator" : "delegated", delegation_depth: parentRunId === null ? 0 : 1,
1239
+ },
1240
+ {
1241
+ schema_version: 2, event_id: `${id}:2`, run_id: id, sequence: 2, occurred_at: sourceFinishedAt,
1242
+ type: "usage.recorded", phase, activity_kind: "retrospective_local_remeasurement",
1243
+ classification_source: "current_context", tokens: { input: 1, cached_input: 0, output: 1, reasoning: 0, total: 2, source: "provider_reported" },
1244
+ cost: { usd: 1, brl: 5, fx_rate: 5 }, provider: "openai", model: "gpt-5", source_segment_key: id,
1245
+ model_call_id: `MCALL-${id}`, runtime: { reasoning_effort: null, service_tier: null }, supersedes_event_id: null,
1246
+ retrospective_correction: true, source_occurred_at: sourceFinishedAt, source_started_at: sourceStartedAt,
1247
+ source_finished_at: sourceFinishedAt, duration_ms: null, duration_provenance: null,
1248
+ },
1249
+ {
1250
+ schema_version: 2, event_id: `${id}:3`, run_id: id, sequence: 3,
1251
+ occurred_at: new Date(Date.parse(occurredAt) + 60_000).toISOString(), type: "run.finished", phase,
1252
+ status: "completed", duration_ms: 60_000, duration_provenance: "observed",
1253
+ },
1254
+ ];
1255
+ };
1256
+ for (const value of [
1257
+ // 23:17–01:01 São Paulo: factual duration is split at local midnight.
1258
+ { id: "RUN-planning", phase: "planning" as const, sourceStartedAt: "2026-07-23T02:17:00.000Z", sourceFinishedAt: "2026-07-23T04:01:00.000Z" },
1259
+ { id: "RUN-conversation", phase: "conversation" as const, sourceStartedAt: "2026-07-23T03:00:00.000Z", sourceFinishedAt: "2026-07-23T04:01:00.000Z" },
1260
+ { id: "RUN-execution", phase: "execution" as const, sourceStartedAt: "2026-07-23T03:30:00.000Z", sourceFinishedAt: "2026-07-23T04:30:00.000Z" },
1261
+ { id: "RUN-subrun", phase: "execution" as const, parentRunId: "RUN-execution", sourceStartedAt: "2026-07-23T03:45:00.000Z", sourceFinishedAt: "2026-07-23T04:15:00.000Z" },
1262
+ ]) {
1263
+ await writeFile(
1264
+ join(root, ".agents", "records", "runs", "2026", "07", `${value.id}.jsonl`),
1265
+ `${run(value)
1266
+ .map((record) => JSON.stringify(record))
1267
+ .join("\n")}\n`,
1268
+ );
1269
+ }
1270
+ const aliasRecords = run({
1271
+ id: "RUN-source-alias",
1272
+ phase: "execution",
1273
+ sourceStartedAt: "2026-07-23T04:10:00.000Z",
1274
+ sourceFinishedAt: "2026-07-23T04:20:00.000Z",
1275
+ });
1276
+ const aliasUsage = {
1277
+ ...aliasRecords[1],
1278
+ event_id: "RUN-source-alias:3",
1279
+ sequence: 3,
1280
+ };
1281
+ const aliasStarted = {
1282
+ ...aliasRecords[0],
1283
+ // Administrative capture predates the factual source session. Every
1284
+ // token/time/invocation surface must classify this record by the source.
1285
+ occurred_at: "2026-07-22T12:00:00.000Z",
1286
+ };
1287
+ const aliasFinished = { ...aliasRecords[2], event_id: "RUN-source-alias:4", sequence: 4 };
1288
+ await writeFile(
1289
+ join(root, ".agents", "records", "runs", "2026", "07", "RUN-source-alias.jsonl"),
1290
+ `${[aliasStarted, aliasRecords[1], aliasUsage, aliasFinished].map((record) => JSON.stringify(record)).join("\n")}\n`,
1291
+ );
1292
+ // Historical financial telemetry can have no agent_invocation_id. It is
1293
+ // still a Cost Monitor financial fact and must not disappear merely
1294
+ // because agent-specific views cannot address it.
1295
+ const historicalRecords = run({
1296
+ id: "RUN-historical-financial",
1297
+ phase: "conversation",
1298
+ sourceStartedAt: "2026-07-23T06:00:00.000Z",
1299
+ sourceFinishedAt: "2026-07-23T06:00:20.000Z",
1300
+ });
1301
+ const historicalStarted = { ...historicalRecords[0] } as Record<string, unknown>;
1302
+ // The record was captured the following day, while its source session is
1303
+ // 23 July. Cost Monitor filters must follow the latter.
1304
+ historicalStarted.occurred_at = "2026-07-24T12:00:00.000Z";
1305
+ delete historicalStarted.agent_invocation_id;
1306
+ delete historicalStarted.root_agent_invocation_id;
1307
+ delete historicalStarted.parent_agent_invocation_id;
1308
+ await writeFile(
1309
+ join(root, ".agents", "records", "runs", "2026", "07", "RUN-historical-financial.jsonl"),
1310
+ `${[historicalStarted, ...historicalRecords.slice(1)].map((record) => JSON.stringify(record)).join("\n")}\n`,
1311
+ );
1312
+ await rebuildSqliteReadModel(root);
1313
+ const database = new DatabaseSync(resolve(root, ".local/dashboard-sqlite/read-model.sqlite"), { readOnly: true });
1314
+ expect(database.prepare("SELECT duration_ms, source_duration_ms, duration_provenance FROM zaip_aios_activities WHERE run_id='RUN-planning'").get()).toEqual({ duration_ms: 4_020_000, source_duration_ms: 6_240_000, duration_provenance: "attributed_token_active" });
1315
+ const daily = database.prepare("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").all();
1316
+ expect(daily).toEqual([
1317
+ { day: "2026-07-22", total_duration: 2_580_000, invocation_count: 1, cost_monitor_invocation_count: 1, cost_monitor_total_duration: 2_580_000 },
1318
+ { day: "2026-07-23", total_duration: 5_420_000, invocation_count: 4, cost_monitor_invocation_count: 5, cost_monitor_total_duration: 5_420_000 },
1319
+ ]);
1320
+ expect(database.prepare("SELECT COUNT(*) AS count FROM zaip_vw_daily_aggregates WHERE total_duration > 86400000").get()).toEqual({ count: 0 });
1321
+ database.close();
1322
+ const factualFilesystem = new SqliteReadModelFileSystem(root);
1323
+ expect(
1324
+ factualFilesystem.costMonitorFinancialActivityRows({ from: "2026-07-23", to: "2026-07-23" })
1325
+ .some((row) => row.run_id === "RUN-historical-financial"),
1326
+ ).toBe(true);
1327
+ const compactFinancialRow = factualFilesystem.costMonitorFinancialActivityRows({
1328
+ from: "2026-07-23",
1329
+ to: "2026-07-23",
1330
+ })[0];
1331
+ expect(compactFinancialRow).not.toHaveProperty("payload_json");
1332
+ expect(compactFinancialRow).toHaveProperty("total_tokens");
1333
+ expect(
1334
+ factualFilesystem.costMonitorFinancialActivityRows({ from: "2026-07-24", to: "2026-07-24" })
1335
+ .some((row) => row.run_id === "RUN-historical-financial"),
1336
+ ).toBe(false);
1337
+ expect(
1338
+ factualFilesystem.viewPage("operations_agent_invocations", {
1339
+ from: "2026-07-23", to: "2026-07-23", limit: 20,
1340
+ }).rows.some((row) => row.run_id === "RUN-source-alias"),
1341
+ ).toBe(true);
1342
+ expect(
1343
+ factualFilesystem.viewPage("operations_agent_invocations", {
1344
+ from: "2026-07-22", to: "2026-07-22", limit: 20,
1345
+ }).rows.some((row) => row.run_id === "RUN-source-alias"),
1346
+ ).toBe(false);
1347
+ // Filter dimensions stay in the SQL population: they must retain the
1348
+ // source-session day and never require an activity-by-activity lookup.
1349
+ expect(
1350
+ factualFilesystem.costMonitorFinancialActivityRows({
1351
+ from: "2026-07-23",
1352
+ to: "2026-07-23",
1353
+ phases: ["conversation"],
1354
+ providers: ["openai"],
1355
+ models: ["gpt-5"],
1356
+ }).map((row) => row.run_id),
1357
+ ).toContain("RUN-historical-financial");
1358
+ expect(
1359
+ factualFilesystem.costMonitorFinancialActivityRows({
1360
+ from: "2026-07-22",
1361
+ to: "2026-07-22",
1362
+ phases: ["planning"],
1363
+ }).map((row) => row.run_id),
1364
+ ).toEqual(["RUN-planning"]);
1365
+ for (const view of ["cost_monitor_daily", "cost_monitor_monthly", "cost_monitor_annual"] as const) {
1366
+ expect(
1367
+ factualFilesystem.viewPage(view, { from: "2026-07-23", to: "2026-07-23", limit: 20, offset: 0 }).rows
1368
+ .some((row) => Number(row.total_tokens) === 10),
1369
+ ).toBe(true);
1370
+ // The API boundary supplies inclusive Sao Paulo days as ISO instants.
1371
+ // Do not truncate the UTC end instant into the following calendar day.
1372
+ const bounded = factualFilesystem.viewPage(view, {
1373
+ from: "2026-07-23T03:00:00.000Z",
1374
+ to: "2026-07-24T02:59:59.999Z",
1375
+ limit: 20,
1376
+ offset: 0,
1377
+ }).rows;
1378
+ expect(bounded.some((row) => Number(row.total_tokens) === 10)).toBe(true);
1379
+ expect(bounded.some((row) => String(row.period_key).startsWith("2026-07-24") && Number(row.total_tokens) === 10)).toBe(false);
1380
+ // A structural day or other source financial fact may still exist on
1381
+ // the 24th, but this fixture's source-day-23 aggregate must not leak.
1382
+ expect(
1383
+ factualFilesystem.viewPage(view, { from: "2026-07-24", to: "2026-07-24", limit: 20, offset: 0 }).rows
1384
+ .every((row) => row.total_tokens !== 10),
1385
+ ).toBe(true);
1386
+ }
1387
+ const allFinancialRows = factualFilesystem.costMonitorFinancialActivityRows({ from: null, to: null });
1388
+ const factual = factualFilesystem.costMonitorFactualDurationByDay({ from: null, to: null }, allFinancialRows);
1389
+ // The cross-midnight session is split at São Paulo midnight and each day
1390
+ // is the union of complete source intervals, never above one local day.
1391
+ expect(factual.get("2026-07-22")).toEqual({ known_sum: 2_580_000, eligible_runs: 1, known_runs: 1 });
1392
+ expect(factual.get("2026-07-23")).toEqual({ known_sum: 5_420_000, eligible_runs: 6, known_runs: 6 });
1393
+ factualFilesystem.close();
1394
+ });
1395
+
1396
+ it("inherits unambiguous SPEC/PRD creation telemetry for planned Tasks without copying it to siblings", async () => {
1397
+ const root = await createSqliteTestRoot();
1398
+ roots.push(root);
1399
+ for (const kind of ["tasks", "specs", "prds"])
1400
+ await mkdir(join(root, ".agents", "records", kind, "2026", "08"), { recursive: true });
1401
+ await mkdir(join(root, ".agents", "records", "changes", "2026", "08", "CHG-UNIQUE"), { recursive: true });
1402
+ await mkdir(join(root, ".agents", "records", "changes", "2026", "08", "CHG-SHARED-A"), { recursive: true });
1403
+ await mkdir(join(root, ".agents", "records", "changes", "2026", "08", "CHG-SHARED-B"), { recursive: true });
1404
+ await mkdir(join(root, ".agents", "records", "changes", "2026", "08", "CHG-PRD-ONLY"), { recursive: true });
1405
+ await mkdir(join(root, ".agents", "records", "changes", "2026", "08", "CHG-NO-MACRO"), { recursive: true });
1406
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
1407
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
1408
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n", "utf8");
1409
+ const item = (id: string, status = "planned") => `---\nid: ${id}\ntitle: ${id}\nstatus: ${status}\nrisk: standard\ncreated_at: 2026-08-12T12:00:00.000Z\n---\n# ${id}\n`;
1410
+ for (const id of ["TASK-UNIQUE", "TASK-SHARED-A", "TASK-SHARED-B", "TASK-PRD-ONLY", "TASK-NO-MACRO"])
1411
+ await writeFile(join(root, ".agents", "records", "tasks", "2026", "08", `${id}.md`), item(id), "utf8");
1412
+ for (const id of ["SPEC-UNIQUE", "SPEC-SHARED"])
1413
+ await writeFile(join(root, ".agents", "records", "specs", "2026", "08", `${id}.md`), item(id, "active"), "utf8");
1414
+ for (const id of ["PRD-UNIQUE", "PRD-SHARED", "PRD-ONLY"])
1415
+ await writeFile(join(root, ".agents", "records", "prds", "2026", "08", `${id}.md`), item(id, "active"), "utf8");
1416
+ const change = (id: string, taskId: string, specId: string | null, prdId: string | null) => `---\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`;
1417
+ await writeFile(join(root, ".agents", "records", "changes", "2026", "08", "CHG-UNIQUE", "change.md"), change("CHG-UNIQUE", "TASK-UNIQUE", "SPEC-UNIQUE", "PRD-UNIQUE"), "utf8");
1418
+ await writeFile(join(root, ".agents", "records", "changes", "2026", "08", "CHG-SHARED-A", "change.md"), change("CHG-SHARED-A", "TASK-SHARED-A", "SPEC-SHARED", "PRD-SHARED"), "utf8");
1419
+ await writeFile(join(root, ".agents", "records", "changes", "2026", "08", "CHG-SHARED-B", "change.md"), change("CHG-SHARED-B", "TASK-SHARED-B", "SPEC-SHARED", "PRD-SHARED"), "utf8");
1420
+ await writeFile(join(root, ".agents", "records", "changes", "2026", "08", "CHG-PRD-ONLY", "change.md"), change("CHG-PRD-ONLY", "TASK-PRD-ONLY", null, "PRD-ONLY"), "utf8");
1421
+ await writeFile(join(root, ".agents", "records", "changes", "2026", "08", "CHG-NO-MACRO", "change.md"), change("CHG-NO-MACRO", "TASK-NO-MACRO", null, null), "utf8");
1422
+ const run = (id: string, specId: string | null, prdId: string | null, tokens: number, cost: number, duration: number) => [
1423
+ { schema_version: 2, event_id: `${id}:1`, run_id: id, sequence: 1, occurred_at: "2026-08-12T12:00:00.000Z", type: "run.started", attribution_status: "attributed", phase: "planning", run_role: "orchestrator", run_purpose: "orchestration", requires_finalizer: false, execution_kind: "interaction", execution_eligibility: [], entrypoint: specId === null ? (prdId === null ? "conversation" : "prd") : "spec", work_type: "new", storage_mode: "tracked_sanitized", client: "codex", agent: "agent", agent_name: "agent", provider: "openai", model: "gpt-5", reasoning_effort: null, client_thread_id: id, client_turn_id: id, conversation_id: `CONV-${id}`, execution_id: null, task_id: null, spec_id: specId, prd_id: prdId, change_id: null, parent_run_id: null, continuation_of_run_id: null, retry_of_run_id: null, agent_invocation_id: `AINV-${id}`, root_agent_invocation_id: `AINV-${id}`, parent_agent_invocation_id: null, invocation_kind: "orchestrator", delegation_depth: 0 },
1424
+ { schema_version: 2, event_id: `${id}:2`, run_id: id, sequence: 2, occurred_at: "2026-08-12T12:01:00.000Z", type: "usage.recorded", phase: "planning", activity_kind: "model_usage", classification_source: "explicit", tokens: { input: tokens, cached_input: 0, output: 0, reasoning: 0, total: tokens, source: "provider_reported" }, cost: { usd: cost / 5, brl: cost, fx_rate: 5 }, provider: "openai", model: "gpt-5", source_segment_key: id, model_call_id: `MCALL-${id}`, runtime: { reasoning_effort: null, service_tier: null }, supersedes_event_id: null, duration_ms: null, duration_provenance: null },
1425
+ { schema_version: 2, event_id: `${id}:3`, run_id: id, sequence: 3, occurred_at: "2026-08-12T12:02:00.000Z", type: "run.finished", phase: "planning", status: "completed", duration_ms: duration, duration_provenance: "observed" },
1426
+ ];
1427
+ for (const [id, specId, prdId, tokens, cost, duration] of [["RUN-UNIQUE", "SPEC-UNIQUE", "PRD-UNIQUE", 7, 3, 120_000], ["RUN-SHARED", "SPEC-SHARED", "PRD-SHARED", 11, 5, 60_000], ["RUN-PRD-ONLY", null, "PRD-ONLY", 13, 6, 90_000], ["RUN-NO-MACRO", null, null, 17, 8, 30_000]] as const)
1428
+ await writeFile(join(root, ".agents", "records", "runs", "2026", "08", `${id}.jsonl`), `${run(id, specId, prdId, tokens, cost, duration).map((record) => JSON.stringify(record)).join("\n")}\n`, "utf8");
1429
+ await rebuildSqliteReadModel(root);
1430
+ const filesystem = new SqliteReadModelFileSystem(root);
1431
+ const tasks = filesystem.summaryPage("task", { from: null, to: null }, 0, 10).rows;
1432
+ expect(tasks).toContainEqual(expect.objectContaining({ id: "TASK-UNIQUE", invocation_count: 1, total_duration: 62_500, total_tokens: 7, total_cost_brl: 3 }));
1433
+ expect(tasks).toContainEqual(expect.objectContaining({ id: "TASK-SHARED-A", invocation_count: null, total_duration: null, total_tokens: null, total_cost_brl: null }));
1434
+ expect(tasks).toContainEqual(expect.objectContaining({ id: "TASK-SHARED-B", invocation_count: null, total_duration: null, total_tokens: null, total_cost_brl: null }));
1435
+ expect(tasks).toContainEqual(expect.objectContaining({ id: "TASK-PRD-ONLY", invocation_count: 1, total_duration: 32_500, total_tokens: 13, total_cost_brl: 6 }));
1436
+ expect(tasks).toContainEqual(expect.objectContaining({ id: "TASK-NO-MACRO", invocation_count: null, total_duration: null, total_tokens: null, total_cost_brl: null }));
1437
+ filesystem.close();
1438
+ });
1439
+
1440
+ it("counts every root execution day and returns task duration leaders", async () => {
1441
+ const root = await createSqliteTestRoot();
1442
+ roots.push(root);
1443
+ await mkdir(join(root, ".agents", "records", "runs", "2026", "08"), { recursive: true });
1444
+ for (const kind of ["tasks", "specs", "prds"])
1445
+ await mkdir(join(root, ".agents", "records", kind, "2026", "08"), { recursive: true });
1446
+ await mkdir(
1447
+ join(root, ".agents", "records", "changes", "2026", "08", "CHG-ART", "artifacts", "evidence"),
1448
+ { recursive: true },
1449
+ );
1450
+ await mkdir(
1451
+ join(root, ".agents", "records", "changes", "2026", "08", "CHG-ART", "artifacts", "validations"),
1452
+ { recursive: true },
1453
+ );
1454
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
1455
+ await writeFile(
1456
+ join(root, ".agents", "config", "project.yaml"),
1457
+ "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",
1458
+ "utf8",
1459
+ );
1460
+ await writeFile(
1461
+ join(root, ".agents", "config", "framework.lock.yaml"),
1462
+ "lock_version: 1\nframework:\n version: 1.0.8\n distribution_sha256: null\nlayout:\n version: 3\nresolved_profile:\n id: null\n sha256: null\nmanaged_files: {}\n",
1463
+ "utf8",
1464
+ );
1465
+ const run = (
1466
+ id: string,
1467
+ day: string,
1468
+ task: string,
1469
+ duration: number,
1470
+ time = "12:00:00.000Z",
1471
+ spec: string | null = null,
1472
+ prd: string | null = null,
1473
+ ) => [
1474
+ {
1475
+ schema_version: 2,
1476
+ event_id: `${id}:1`,
1477
+ run_id: id,
1478
+ sequence: 1,
1479
+ occurred_at: `${day}T${time}`,
1480
+ type: "run.started",
1481
+ attribution_status: "attributed",
1482
+ phase: "execution",
1483
+ run_role: "orchestrator",
1484
+ run_purpose: "execution",
1485
+ requires_finalizer: false,
1486
+ execution_kind: "delivery",
1487
+ execution_eligibility: ["delivery"],
1488
+ entrypoint: "task",
1489
+ work_type: "new",
1490
+ storage_mode: "tracked_sanitized",
1491
+ client: "codex",
1492
+ agent: "agent",
1493
+ agent_name: "agent",
1494
+ provider: "openai",
1495
+ model: "gpt-5",
1496
+ reasoning_effort: null,
1497
+ client_thread_id: id,
1498
+ client_turn_id: id,
1499
+ conversation_id: `CONV-${id}`,
1500
+ execution_id: `EXEC-${id}`,
1501
+ task_id: task,
1502
+ spec_id: spec,
1503
+ prd_id: prd,
1504
+ change_id: null,
1505
+ parent_run_id: null,
1506
+ continuation_of_run_id: null,
1507
+ retry_of_run_id: null,
1508
+ agent_invocation_id: `AINV-${id}`,
1509
+ root_agent_invocation_id: `AINV-${id}`,
1510
+ parent_agent_invocation_id: null,
1511
+ invocation_kind: "orchestrator",
1512
+ delegation_depth: 0,
1513
+ },
1514
+ {
1515
+ schema_version: 2,
1516
+ event_id: `${id}:2`,
1517
+ run_id: id,
1518
+ sequence: 2,
1519
+ occurred_at: `${day}T12:00:01.000Z`,
1520
+ type: "usage.recorded",
1521
+ phase: "execution",
1522
+ activity_kind: "model_usage",
1523
+ classification_source: "explicit",
1524
+ tokens: {
1525
+ input: 1,
1526
+ cached_input: 0,
1527
+ output: 1,
1528
+ reasoning: 0,
1529
+ total: 2,
1530
+ source: "provider_reported",
1531
+ },
1532
+ cost: { usd: 1, brl: 5, fx_rate: 5 },
1533
+ provider: "openai",
1534
+ model: "gpt-5",
1535
+ source_segment_key: id,
1536
+ model_call_id: `MCALL-${id}`,
1537
+ runtime: { reasoning_effort: null, service_tier: null },
1538
+ supersedes_event_id: null,
1539
+ duration_ms: null,
1540
+ duration_provenance: null,
1541
+ },
1542
+ {
1543
+ schema_version: 2,
1544
+ event_id: `${id}:3`,
1545
+ run_id: id,
1546
+ sequence: 3,
1547
+ occurred_at: `${day}T12:00:02.000Z`,
1548
+ type: "run.finished",
1549
+ phase: "execution",
1550
+ status: "completed",
1551
+ duration_ms: duration,
1552
+ duration_provenance: "observed",
1553
+ },
1554
+ ];
1555
+ for (const [id, records] of [
1556
+ ["RUN-R1", run("RUN-R1", "2026-08-01", "TASK-A", 600_000, "12:00:00.000Z", "SPEC-A", "PRD-A")],
1557
+ ["RUN-R2", run("RUN-R2", "2026-08-02", "TASK-B", 1_200_000, "12:00:00.000Z", "SPEC-B", "PRD-A")],
1558
+ ["RUN-R3", run("RUN-R3", "2026-08-02", "TASK-B", 1_200_000, "12:10:00.000Z", "SPEC-B", "PRD-A")],
1559
+ ] as const) {
1560
+ await writeFile(
1561
+ join(root, ".agents", "records", "runs", "2026", "08", `${id}.jsonl`),
1562
+ `${records.map((record) => JSON.stringify(record)).join("\n")}\n`,
1563
+ "utf8",
1564
+ );
1565
+ }
1566
+ await writeFile(
1567
+ join(root, ".agents", "records", "changes", "2026", "08", "CHG-ART", "artifacts", "evidence", "EVD-1.md"),
1568
+ "---\nid: EVD-1\nobserved_at: 2026-08-01T12:00:00.000Z\n---\n# Evidence\n",
1569
+ "utf8",
1570
+ );
1571
+ await writeFile(
1572
+ join(root, ".agents", "records", "changes", "2026", "08", "CHG-ART", "artifacts", "validations", "VAL-1.md"),
1573
+ "---\nid: VAL-1\nexecuted_at: 2026-08-02T12:00:00.000Z\n---\n# Validation\n",
1574
+ "utf8",
1575
+ );
1576
+ for (const [kind, id] of [["tasks", "TASK-NO-RUN"], ["specs", "SPEC-NO-RUN"], ["prds", "PRD-NO-RUN"]] as const)
1577
+ await writeFile(
1578
+ join(root, ".agents", "records", kind, "2026", "08", `${id}.md`),
1579
+ `---\nid: ${id}\ntitle: ${id}\nstatus: draft\ncreated_at: 2026-08-03T12:00:00.000Z\n---\n# ${id}\n`,
1580
+ "utf8",
1581
+ );
1582
+ await rebuildSqliteReadModel(root);
1583
+ const filesystem = new SqliteReadModelFileSystem(root);
1584
+ expect(filesystem.firstFoldAggregate({ from: null, to: null }).execution_day_count).toBe(2);
1585
+ expect(filesystem.firstFoldAggregate({ from: null, to: null }).duration_ms).toBe(2_400_000);
1586
+ expect(filesystem.topTasks({ from: null, to: null })).toMatchObject([
1587
+ { task_id: "TASK-B", duration_ms: 1_800_000 },
1588
+ { task_id: "TASK-A", duration_ms: 600_000 },
1589
+ ]);
1590
+ expect(filesystem.summaryPage("task", { from: null, to: null }, 0, 50).rows).toContainEqual(
1591
+ expect.objectContaining({
1592
+ id: "TASK-B",
1593
+ invocation_count: 2,
1594
+ total_duration: 1_800_000,
1595
+ total_tokens: 4,
1596
+ total_cost_brl: 10,
1597
+ }),
1598
+ );
1599
+ expect(filesystem.summaryPage("spec", { from: null, to: null }, 0, 50).rows).toContainEqual(
1600
+ expect.objectContaining({
1601
+ id: "SPEC-B",
1602
+ invocation_count: 2,
1603
+ total_duration: 1_800_000,
1604
+ total_tokens: 4,
1605
+ total_cost_brl: 10,
1606
+ }),
1607
+ );
1608
+ expect(filesystem.summaryPage("prd", { from: null, to: null }, 0, 50).rows).toContainEqual(
1609
+ expect.objectContaining({
1610
+ id: "PRD-A",
1611
+ invocation_count: 3,
1612
+ total_duration: 2_400_000,
1613
+ total_tokens: 6,
1614
+ total_cost_brl: 15,
1615
+ }),
1616
+ );
1617
+ expect(filesystem.summaryPage("task", { from: null, to: null }, 0, 50).rows).toContainEqual(
1618
+ expect.objectContaining({
1619
+ id: "TASK-NO-RUN",
1620
+ invocation_count: null,
1621
+ total_duration: null,
1622
+ total_tokens: null,
1623
+ total_cost_brl: null,
1624
+ }),
1625
+ );
1626
+ const allTime = filesystem.firstFoldAllTime();
1627
+ expect(allTime.aggregate).toMatchObject({
1628
+ start_day: "2026-08-01",
1629
+ end_day: "2026-08-03",
1630
+ task_count: 3,
1631
+ spec_count: 3,
1632
+ prd_count: 2,
1633
+ total_run_count: 3,
1634
+ duration_ms: 2_400_000,
1635
+ total_tokens: 6,
1636
+ cost_brl: 15,
1637
+ execution_day_count: 2,
1638
+ });
1639
+ expect(allTime.task_status_counts).toEqual({ draft: 3 });
1640
+ // The served quick projection uses this deliberately lean subset. It must
1641
+ // remain factually identical to the rich projection for the six cards and
1642
+ // two first-fold charts, while leaving rankings and macro metrics lazy.
1643
+ const viewport = filesystem.firstFoldViewportAllTime();
1644
+ expect(viewport.aggregate).toEqual(allTime.aggregate);
1645
+ expect(viewport.task_status_counts).toEqual(allTime.task_status_counts);
1646
+ expect(viewport.task_risk_counts).toEqual(allTime.task_risk_counts);
1647
+ expect(allTime.top_tasks).toMatchObject([
1648
+ { rank: 1, task_id: "TASK-B", total_duration_ms: 1_800_000 },
1649
+ { rank: 2, task_id: "TASK-A", total_duration_ms: 600_000 },
1650
+ ]);
1651
+ expect(filesystem.firstFoldTopTasksAllTime()).toEqual(allTime.top_tasks);
1652
+ expect(
1653
+ projectAllTimeTopTasks([
1654
+ {
1655
+ task_id: "TASK-NO-USAGE",
1656
+ total_duration_ms: null,
1657
+ total_tokens: null,
1658
+ total_cost_usd: null,
1659
+ total_cost_brl: null,
1660
+ total_run_count: 1,
1661
+ },
1662
+ ]),
1663
+ ).toEqual([
1664
+ {
1665
+ task_id: "TASK-NO-USAGE",
1666
+ duration_ms: null,
1667
+ total_tokens: null,
1668
+ total_cost_usd: null,
1669
+ cost_brl: null,
1670
+ run_count: 1,
1671
+ },
1672
+ ]);
1673
+ const app = createDashboardApp(root, "test-session", undefined, { filesystem });
1674
+ const allTimeQuick = await app.request("/api/v2/operations?scope=quick", {
1675
+ headers: { "x-zaip-session": "test-session" },
1676
+ });
1677
+ const boundedQuick = await app.request(
1678
+ "/api/v2/operations?scope=quick&from=2026-08-02&to=2026-08-02",
1679
+ { headers: { "x-zaip-session": "test-session" } },
1680
+ );
1681
+ const topTasks = (await allTimeQuick.json()) as {
1682
+ summary: { overview_metrics?: { top_tasks?: unknown[] } };
1683
+ };
1684
+ const boundedTopTasks = (await boundedQuick.json()) as {
1685
+ summary: { overview_metrics?: { top_tasks?: unknown[] } };
1686
+ };
1687
+ expect(topTasks.summary.overview_metrics?.top_tasks).toEqual(
1688
+ expect.arrayContaining([
1689
+ expect.objectContaining({
1690
+ task_id: "TASK-B",
1691
+ duration_ms: 1_800_000,
1692
+ total_tokens: 4,
1693
+ cost_brl: 10,
1694
+ }),
1695
+ ]),
1696
+ );
1697
+ expect(boundedTopTasks.summary.overview_metrics?.top_tasks).toEqual([
1698
+ expect.objectContaining({
1699
+ task_id: "TASK-B",
1700
+ duration_ms: 1_800_000,
1701
+ total_tokens: 4,
1702
+ cost_brl: 10,
1703
+ }),
1704
+ ]);
1705
+ // A new calendar month after this immutable snapshot has no factual rows.
1706
+ // It must be a fast exact empty projection, not a slow scan caused by the
1707
+ // default date range changing at midnight on the first day of the month.
1708
+ const nextMonthQuick = await app.request(
1709
+ "/api/v2/operations?scope=quick&from=2026-09-01&to=2026-09-01",
1710
+ { headers: { "x-zaip-session": "test-session" } },
1711
+ );
1712
+ const nextMonth = (await nextMonthQuick.json()) as {
1713
+ summary: { run_count: number; task_count: number; overview_metrics: { tokens: number | null; duration_ms: number | null } };
1714
+ };
1715
+ expect(nextMonth.summary).toMatchObject({
1716
+ run_count: 0,
1717
+ task_count: 0,
1718
+ overview_metrics: { tokens: null, duration_ms: null },
1719
+ });
1720
+ expect(allTime.macro_metrics.specs).toMatchObject({
1721
+ entity_count: 3,
1722
+ status_counts: { planning: 3 },
1723
+ classification_counts: { structured: 2 },
1724
+ total_run_count: 3,
1725
+ total_duration_ms: 2_400_000,
1726
+ total_tokens: 6,
1727
+ total_cost_brl: 15,
1728
+ average_duration_ms: 800_000,
1729
+ });
1730
+ expect(allTime.macro_metrics.specs.top_entities.slice(0, 2)).toMatchObject([
1731
+ { rank: 1, entity_id: "SPEC-B", total_duration_ms: 1_800_000 },
1732
+ { rank: 2, entity_id: "SPEC-A", total_duration_ms: 600_000 },
1733
+ ]);
1734
+ expect(allTime.macro_metrics.prds).toMatchObject({
1735
+ entity_count: 2,
1736
+ status_counts: { definition: 2 },
1737
+ classification_counts: { structured: 1 },
1738
+ total_run_count: 3,
1739
+ total_duration_ms: 2_400_000,
1740
+ total_tokens: 6,
1741
+ total_cost_brl: 15,
1742
+ average_duration_ms: 1_200_000,
1743
+ });
1744
+ const operationDays = filesystem.dailyPeriods({ from: null, to: null });
1745
+ expect(Object.keys(operationDays)).toEqual(["2026-08-01", "2026-08-02", "2026-08-03"]);
1746
+ expect(operationDays["2026-08-02"]).toMatchObject({
1747
+ runs: 2,
1748
+ specs: 1,
1749
+ tasks: 1,
1750
+ validations: 1,
1751
+ evidences: 0,
1752
+ usage: {
1753
+ total_tokens: { known_sum: 4, unknown: 0 },
1754
+ cost: { known_sum: 10, unknown: 0 },
1755
+ },
1756
+ duration: { known_sum: 1_800_000, unknown: 0 },
1757
+ });
1758
+ expect(filesystem.viewPage("operations_daily", { limit: 50 }).rows).toContainEqual(
1759
+ expect.objectContaining({
1760
+ day: "2026-08-02",
1761
+ task_count: 1,
1762
+ spec_count: 1,
1763
+ validation_count: 1,
1764
+ evidence_count: 0,
1765
+ total_run_count: 2,
1766
+ operations_total_duration: 1_800_000,
1767
+ total_tokens: 4,
1768
+ total_cost_brl: 10,
1769
+ }),
1770
+ );
1771
+ for (const [view, key] of [
1772
+ ["operations_monthly", "2026-08"],
1773
+ ["operations_annual", "2026"],
1774
+ ] as const) {
1775
+ expect(filesystem.viewPage(view, { limit: 50 }).rows).toContainEqual(
1776
+ expect.objectContaining({
1777
+ period_key: key,
1778
+ prd_count: 2,
1779
+ spec_count: 3,
1780
+ task_count: 3,
1781
+ validation_count: 1,
1782
+ evidence_count: 1,
1783
+ total_run_count: 3,
1784
+ operations_total_duration: 2_400_000,
1785
+ total_tokens: 6,
1786
+ total_cost_brl: 15,
1787
+ }),
1788
+ );
1789
+ }
1790
+ for (const view of ["operations_monthly", "operations_annual"] as const) {
1791
+ expect(
1792
+ filesystem.viewPage(view, {
1793
+ from: "2026-08-02T03:00:00.000Z",
1794
+ to: "2026-08-02T23:59:59.999Z",
1795
+ limit: 50,
1796
+ }).rows,
1797
+ ).toEqual([
1798
+ expect.objectContaining({
1799
+ start_day: "2026-08-02",
1800
+ end_day: "2026-08-02",
1801
+ total_run_count: 2,
1802
+ invocation_count: 2,
1803
+ operations_total_duration: 1_800_000,
1804
+ total_tokens: 4,
1805
+ total_cost_usd: 2,
1806
+ total_cost_brl: 10,
1807
+ }),
1808
+ ]);
1809
+ }
1810
+ for (const [view, key, counts] of [
1811
+ ["cost_monitor_daily", "2026-08-02", { prd_count: 0, spec_count: 1, task_count: 1, validation_count: 1, evidence_count: 0 }],
1812
+ ["cost_monitor_monthly", "2026-08", { prd_count: 2, spec_count: 3, task_count: 3, validation_count: 1, evidence_count: 1 }],
1813
+ ["cost_monitor_annual", "2026", { prd_count: 2, spec_count: 3, task_count: 3, validation_count: 1, evidence_count: 1 }],
1814
+ ] as const) {
1815
+ expect(filesystem.viewPage(view, { limit: 50 }).rows).toContainEqual(
1816
+ expect.objectContaining({ key, ...counts }),
1817
+ );
1818
+ }
1819
+ expect(filesystem.viewPage("cost_monitor_daily", { limit: 50 }).rows).toContainEqual(
1820
+ expect.objectContaining({ key: "2026-08-03", prd_count: 1, spec_count: 1, task_count: 1 }),
1821
+ );
1822
+ expect(filesystem.viewPage("cost_monitor_monthly", { limit: 50 }).rows).toContainEqual(
1823
+ expect.objectContaining({ key: "2026-08", prd_count: 2, spec_count: 3, task_count: 3 }),
1824
+ );
1825
+ expect(filesystem.viewPage("cost_monitor_annual", { limit: 50 }).rows).toContainEqual(
1826
+ expect.objectContaining({ key: "2026", prd_count: 2, spec_count: 3, task_count: 3 }),
1827
+ );
1828
+ const annualCostPage = filesystem.viewPage("cost_monitor_annual", { limit: 50 });
1829
+ const dailyCostRows = filesystem.viewPage("cost_monitor_daily", { limit: 50 }).rows;
1830
+ const pagedDailyCostPage = filesystem.viewPage("cost_monitor_daily", { limit: 1 });
1831
+ expect(pagedDailyCostPage).toMatchObject({ total_count: dailyCostRows.length, rows: [expect.any(Object)] });
1832
+ expect(pagedDailyCostPage.totals).toMatchObject({
1833
+ total_tokens: dailyCostRows.reduce((sum, row) => sum + Number(row.total_tokens ?? 0), 0),
1834
+ total_cost_brl: dailyCostRows.reduce((sum, row) => sum + Number(row.total_cost_brl ?? 0), 0),
1835
+ total_duration: dailyCostRows.reduce((sum, row) => sum + Number(row.total_duration ?? 0), 0),
1836
+ });
1837
+ expect(annualCostPage).toMatchObject({
1838
+ total_count: 1,
1839
+ rows: [expect.objectContaining({ key: "2026" })],
1840
+ });
1841
+ const annualCostRow = annualCostPage.rows[0];
1842
+ if (annualCostRow === undefined) throw new Error("Expected the consolidated 2026 Cost row");
1843
+ for (const metric of [
1844
+ "prd_count", "spec_count", "task_count", "validation_count", "evidence_count",
1845
+ "total_run_count", "total_duration", "duration_known",
1846
+ "total_tokens", "total_cost_usd", "total_cost_brl",
1847
+ ] as const) {
1848
+ const observed = dailyCostRows
1849
+ .map((row) => row[metric])
1850
+ .filter((value): value is number => typeof value === "number");
1851
+ expect(annualCostRow[metric]).toBe(
1852
+ observed.length === 0 ? null : observed.reduce((sum, value) => sum + value, 0),
1853
+ );
1854
+ }
1855
+ expect(
1856
+ filesystem.viewPage("cost_monitor_daily", { limit: 50, provider: "openai" }).rows,
1857
+ ).toContainEqual(
1858
+ expect.objectContaining({
1859
+ key: "2026-08-02",
1860
+ prd_count: 0,
1861
+ spec_count: 1,
1862
+ task_count: 1,
1863
+ validation_count: 1,
1864
+ evidence_count: 0,
1865
+ }),
1866
+ );
1867
+ for (const [view, entityCount] of [
1868
+ ["cost_monitor_tasks", 2],
1869
+ ["cost_monitor_specs", 3],
1870
+ ["cost_monitor_prds", 2],
1871
+ ["cost_monitor_conversations", 3],
1872
+ ] as const) {
1873
+ const page = filesystem.viewPage(view, { limit: 1 });
1874
+ // The subtotal is computed before pagination and reconciles the full
1875
+ // filtered population, rather than the single visible row.
1876
+ expect(page).toMatchObject({ total_count: entityCount, rows: [expect.any(Object)] });
1877
+ expect(page.totals).toEqual({
1878
+ entity_count: entityCount,
1879
+ invocation_count: 3,
1880
+ total_duration: 2_400_000,
1881
+ total_tokens: 6,
1882
+ total_cost_usd: 3,
1883
+ total_cost_brl: 15,
1884
+ });
1885
+ }
1886
+ expect(filesystem.viewPage("cost_monitor_tasks", { from: "2026-08-02", to: "2026-08-02", query: "TASK-B", limit: 1 }).totals).toEqual({
1887
+ entity_count: 1,
1888
+ invocation_count: 2,
1889
+ total_duration: 1_800_000,
1890
+ total_tokens: 4,
1891
+ total_cost_usd: 2,
1892
+ total_cost_brl: 10,
1893
+ });
1894
+ // The compact Cost Monitor tables share the canonical macro population
1895
+ // with their big numbers. A macro with no direct invocation must remain
1896
+ // paginable and retain unavailable financial facts as null.
1897
+ expect(filesystem.viewPage("cost_monitor_specs", { query: "SPEC-NO-RUN", limit: 1 })).toMatchObject({
1898
+ total_count: 1,
1899
+ totals: { entity_count: 1, invocation_count: 0, total_duration: null, total_tokens: null, total_cost_brl: null },
1900
+ rows: [expect.objectContaining({ id: "SPEC-NO-RUN", title: "SPEC-NO-RUN", invocation_count: 0, total_duration: null, total_tokens: null, total_cost_brl: null })],
1901
+ });
1902
+ expect(filesystem.viewPage("cost_monitor_prds", { query: "PRD-NO-RUN", limit: 1 })).toMatchObject({
1903
+ total_count: 1,
1904
+ totals: { entity_count: 1, invocation_count: 0, total_duration: null, total_tokens: null, total_cost_brl: null },
1905
+ rows: [expect.objectContaining({ id: "PRD-NO-RUN", title: "PRD-NO-RUN", invocation_count: 0, total_duration: null, total_tokens: null, total_cost_brl: null })],
1906
+ });
1907
+ for (const view of ["cost_monitor_tasks", "cost_monitor_specs", "cost_monitor_prds", "cost_monitor_conversations"] as const) {
1908
+ const filtered = filesystem.viewPage(view, { phase: "execution", provider: "openai", model: "gpt-5", limit: 1 });
1909
+ // Dimensional membership and the subtotal share one pre-pagination
1910
+ // invocation population for every compact lifecycle view.
1911
+ expect(filtered.rows).toHaveLength(1);
1912
+ expect(filtered.totals).toMatchObject({ invocation_count: 3, total_tokens: 6, total_cost_brl: 15 });
1913
+ }
1914
+ expect(filesystem.viewPage("cost_monitor_tasks", { workType: "new", limit: 1 })).toMatchObject({ total_count: 0, rows: [] });
1915
+ expect(filesystem.firstFoldAllTime()).toBe(allTime);
1916
+ filesystem.close();
1917
+
1918
+ // Period membership follows the canonical macro day, not an invocation
1919
+ // that happened on another day. Search accepts either the macro ID or
1920
+ // title, and totals are still calculated before its one-row page.
1921
+ await writeFile(
1922
+ join(root, ".agents", "records", "specs", "2026", "08", "SPEC-CANONICAL-DAY.md"),
1923
+ "---\nid: SPEC-CANONICAL-DAY\ntitle: Macro by canonical title\nstatus: draft\ncreated_at: 2026-08-03T12:00:00.000Z\n---\n# Macro\n",
1924
+ "utf8",
1925
+ );
1926
+ await writeFile(
1927
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-CANONICAL-DAY.jsonl"),
1928
+ `${run("RUN-CANONICAL-DAY", "2026-08-02", "TASK-CANONICAL-DAY", 1_000, "12:30:00.000Z", "SPEC-CANONICAL-DAY").map((record) => JSON.stringify(record)).join("\n")}\n`,
1929
+ "utf8",
1930
+ );
1931
+ await writeFile(
1932
+ join(root, ".agents", "records", "runs", "2026", "08", "RUN-CANONICAL-DAY-LATE.jsonl"),
1933
+ `${run("RUN-CANONICAL-DAY-LATE", "2026-08-04", "TASK-CANONICAL-DAY-LATE", 1_000, "12:30:00.000Z", "SPEC-CANONICAL-DAY").map((record) => JSON.stringify(record)).join("\n")}\n`,
1934
+ "utf8",
1935
+ );
1936
+ await rebuildSqliteReadModel(root);
1937
+ const canonicalDayFilesystem = new SqliteReadModelFileSystem(root);
1938
+ expect(canonicalDayFilesystem.viewPage("cost_monitor_specs", {
1939
+ from: "2026-08-03", to: "2026-08-03", query: "canonical title", limit: 1,
1940
+ })).toMatchObject({
1941
+ total_count: 1,
1942
+ totals: { entity_count: 1, invocation_count: 0, total_tokens: null, total_cost_brl: null },
1943
+ rows: [expect.objectContaining({ id: "SPEC-CANONICAL-DAY", invocation_count: 0, total_tokens: null, total_cost_brl: null })],
1944
+ });
1945
+ expect(canonicalDayFilesystem.viewPage("cost_monitor_specs", {
1946
+ from: "2026-08-02", to: "2026-08-02", query: "SPEC-CANONICAL-DAY", limit: 1,
1947
+ })).toMatchObject({ total_count: 0, rows: [] });
1948
+ canonicalDayFilesystem.close();
1949
+
1950
+ // A large result set keeps its subtotal for all 270 matching Tasks while
1951
+ // the page transports only 50 records.
1952
+ for (let index = 0; index < 270; index += 1) {
1953
+ const id = `RUN-PAGE-${index}`;
1954
+ await writeFile(
1955
+ join(root, ".agents", "records", "runs", "2026", "08", `${id}.jsonl`),
1956
+ `${run(id, "2026-08-04", `TASK-PAGE-${index}`, 1_000).map((record) => JSON.stringify(record)).join("\n")}\n`,
1957
+ "utf8",
1958
+ );
1959
+ }
1960
+ await rebuildSqliteReadModel(root);
1961
+ const pageFilesystem = new SqliteReadModelFileSystem(root);
1962
+ const paged = pageFilesystem.viewPage("cost_monitor_tasks", {
1963
+ from: "2026-08-04",
1964
+ to: "2026-08-04",
1965
+ query: "TASK-PAGE-",
1966
+ offset: 50,
1967
+ limit: 50,
1968
+ });
1969
+ expect(paged).toMatchObject({ total_count: 270, has_more: true });
1970
+ expect(paged.rows).toHaveLength(50);
1971
+ expect(paged.totals).toEqual({
1972
+ entity_count: 270,
1973
+ invocation_count: 270,
1974
+ total_duration: 1_000,
1975
+ total_tokens: 540,
1976
+ total_cost_usd: 270,
1977
+ total_cost_brl: 1_350,
1978
+ });
1979
+ pageFilesystem.close();
1980
+ });
1981
+
1982
+ it("rebuilds a disposable snapshot and serves ZAIP records from SQLite", async () => {
1983
+ const root = await createSqliteTestRoot();
1984
+ roots.push(root);
1985
+ await mkdir(join(root, ".agents", "records", "tasks"), { recursive: true });
1986
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
1987
+ await writeFile(join(root, ".agents", "records", "tasks", "task.md"), "# task\n", "utf8");
1988
+ await writeFile(
1989
+ join(root, ".agents", "config", "project.yaml"),
1990
+ "zaip:\n enabled: true\n",
1991
+ "utf8",
1992
+ );
1993
+ const result = await rebuildSqliteReadModel(root);
1994
+ const filesystem = new SqliteReadModelFileSystem(root);
1995
+ expect(result.files).toBe(2);
1996
+ await expect(filesystem.readText(".agents/records/tasks/task.md")).resolves.toContain("task");
1997
+ await expect(filesystem.listFiles(".agents/records", ".md")).resolves.toEqual([
1998
+ ".agents/records/tasks/task.md",
1999
+ ]);
2000
+ filesystem.close();
2001
+ });
2002
+
2003
+ it("serves canonical Task trace inputs without lifecycle manifests", async () => {
2004
+ const root = await createSqliteTestRoot();
2005
+ roots.push(root);
2006
+ await mkdir(join(root, ".agents", "records", "tasks", "2026", "08"), {
2007
+ recursive: true,
2008
+ });
2009
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
2010
+ await writeFile(
2011
+ join(root, ".agents", "config", "project.yaml"),
2012
+ "zaip:\n enabled: true\n",
2013
+ "utf8",
2014
+ );
2015
+ await writeFile(
2016
+ join(root, ".agents", "records", "tasks", "2026", "08", "TASK-TRACE.md"),
2017
+ "---\nid: TASK-TRACE\ntitle: Canonical trace\nstatus: completed\nrisk: standard\ncreated_at: 2026-08-01T12:00:00.000Z\n---\n# Canonical trace\n",
2018
+ "utf8",
2019
+ );
2020
+ await rebuildSqliteReadModel(root);
2021
+ const filesystem = new SqliteReadModelFileSystem(root);
2022
+ expect(filesystem.canonicalTraceFiles(["TASK-TRACE"])).toMatchObject([
2023
+ { path: ".agents/records/tasks/2026/08/TASK-TRACE.md" },
2024
+ ]);
2025
+ expect(filesystem.taskTraceUsage("TASK-TRACE")).toEqual({
2026
+ run_count: 0,
2027
+ duration_ms: { value: null, known_runs: 0, unknown_runs: 0, coverage_ratio: 1 },
2028
+ total_tokens: { value: null, known_runs: 0, unknown_runs: 0, coverage_ratio: 1 },
2029
+ cost: {
2030
+ value: null,
2031
+ known_runs: 0,
2032
+ unknown_runs: 0,
2033
+ coverage_ratio: 1,
2034
+ currency: null,
2035
+ },
2036
+ });
2037
+ filesystem.close();
2038
+ });
2039
+
2040
+ it("invalidates a nested canonical source change and ignores legacy aggregate telemetry", async () => {
2041
+ const root = await createSqliteTestRoot();
2042
+ roots.push(root);
2043
+ const nested = join(root, ".agents", "records", "tasks", "nested", "task.md");
2044
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
2045
+ await mkdir(join(root, ".agents", "records", "tasks", "nested"), { recursive: true });
2046
+ await writeFile(
2047
+ join(root, ".agents", "config", "project.yaml"),
2048
+ "zaip:\n enabled: true\n",
2049
+ "utf8",
2050
+ );
2051
+ await writeFile(nested, "# before\n", "utf8");
2052
+ await rebuildSqliteReadModel(root);
2053
+ await writeFile(nested, "# after\n", "utf8");
2054
+ const changedAt = new Date(Date.now() + 2_000);
2055
+ await utimes(nested, changedAt, changedAt);
2056
+ await rebuildSqliteReadModel(root);
2057
+ const updated = new SqliteReadModelFileSystem(root);
2058
+ await expect(updated.readText(".agents/records/tasks/nested/task.md")).resolves.toContain(
2059
+ "after",
2060
+ );
2061
+ updated.close();
2062
+
2063
+ const duplicate = join(
2064
+ root,
2065
+ ".agents",
2066
+ "records",
2067
+ "telemetry",
2068
+ "2026",
2069
+ "08",
2070
+ "writers",
2071
+ "w",
2072
+ "chunks",
2073
+ "c.jsonl",
2074
+ );
2075
+ await mkdir(
2076
+ join(root, ".agents", "records", "telemetry", "2026", "08", "writers", "w", "chunks"),
2077
+ { recursive: true },
2078
+ );
2079
+ await writeFile(
2080
+ join(root, ".agents", "records", "telemetry", "2026", "08", "writers", "w", "manifest.json"),
2081
+ JSON.stringify({
2082
+ chunks: [{ path: ".agents/records/telemetry/2026/08/writers/w/chunks/c.jsonl" }],
2083
+ }),
2084
+ "utf8",
2085
+ );
2086
+ await writeFile(
2087
+ duplicate,
2088
+ `${JSON.stringify({ agent_invocation_id: "same", summary_hash: "one" })}\n${JSON.stringify({ agent_invocation_id: "same", summary_hash: "two" })}\n`,
2089
+ "utf8",
2090
+ );
2091
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ files: 2 });
2092
+ expect(await readFile(nested, "utf8")).toContain("after");
2093
+ const retained = new SqliteReadModelFileSystem(root);
2094
+ await expect(retained.readText(".agents/records/tasks/nested/task.md")).resolves.toContain(
2095
+ "after",
2096
+ );
2097
+ retained.close();
2098
+ });
2099
+
2100
+ it("does not promote legacy aggregate telemetry into canonical activity facts", async () => {
2101
+ const root = await createSqliteTestRoot();
2102
+ roots.push(root);
2103
+ await mkdir(
2104
+ join(root, ".agents", "records", "telemetry", "2026", "08", "writers", "w", "chunks"),
2105
+ { recursive: true },
2106
+ );
2107
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
2108
+ await writeFile(
2109
+ join(root, ".agents", "config", "project.yaml"),
2110
+ "zaip:\n enabled: true\n",
2111
+ "utf8",
2112
+ );
2113
+ const summary = {
2114
+ agent_invocation_id: "root",
2115
+ summary_hash: "hash",
2116
+ root_agent_invocation_id: "root",
2117
+ parent_agent_invocation_id: null,
2118
+ execution_id: null,
2119
+ task_id: null,
2120
+ spec_id: null,
2121
+ prd_id: null,
2122
+ change_id: null,
2123
+ run_id: "RUN-1",
2124
+ occurred_at: "2026-08-01T12:00:00.000Z",
2125
+ invocation_kind: "orchestrator",
2126
+ delegation_depth: 0,
2127
+ source_event_count: 1,
2128
+ historical_cost: {
2129
+ estimated_usd: 2,
2130
+ estimated_brl: 10,
2131
+ fx_rate: 5,
2132
+ pricing_model: "model",
2133
+ pricing_source: "canonical",
2134
+ },
2135
+ quantitative_metrics: { duration_ms: { known_sum: 5 }, tokens_total: { known_sum: 0 } },
2136
+ usage_segments: [
2137
+ {
2138
+ provider: "openai",
2139
+ model: "model",
2140
+ service_tier: "default",
2141
+ reasoning_effort: "medium",
2142
+ tokens: { input: 0, cached_input: 0, output: 0, reasoning: 0, total: 0 },
2143
+ historical_cost: {
2144
+ estimated_usd: 2,
2145
+ estimated_brl: 10,
2146
+ fx_rate: 5,
2147
+ pricing_model: "model",
2148
+ pricing_source: "canonical",
2149
+ },
2150
+ },
2151
+ ],
2152
+ };
2153
+ await writeFile(
2154
+ join(
2155
+ root,
2156
+ ".agents",
2157
+ "records",
2158
+ "telemetry",
2159
+ "2026",
2160
+ "08",
2161
+ "writers",
2162
+ "w",
2163
+ "chunks",
2164
+ "c.jsonl",
2165
+ ),
2166
+ `${JSON.stringify(summary)}\n`,
2167
+ "utf8",
2168
+ );
2169
+ await writeFile(
2170
+ join(root, ".agents", "records", "telemetry", "2026", "08", "writers", "w", "manifest.json"),
2171
+ JSON.stringify({
2172
+ chunks: [{ path: ".agents/records/telemetry/2026/08/writers/w/chunks/c.jsonl" }],
2173
+ }),
2174
+ "utf8",
2175
+ );
2176
+ await rebuildSqliteReadModel(root);
2177
+ const filesystem = new SqliteReadModelFileSystem(root);
2178
+ expect(filesystem.firstFoldAggregate({ from: "2026-08-01", to: "2026-08-01" })).toEqual({
2179
+ days: 0,
2180
+ total_run_count: 0,
2181
+ agent_invocation_count: 0,
2182
+ llm_call_count: null,
2183
+ duration_ms: null,
2184
+ total_tokens: null,
2185
+ cost_brl: null,
2186
+ execution_day_count: 0,
2187
+ });
2188
+ const database = new DatabaseSync(resolve(root, ".local/dashboard-sqlite/read-model.sqlite"), {
2189
+ readOnly: true,
2190
+ });
2191
+ const columns = (table: string) =>
2192
+ (database.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>).map(
2193
+ (row) => row.name,
2194
+ );
2195
+ expect(columns("zaip_vw_daily_aggregates")).toEqual(
2196
+ expect.arrayContaining([
2197
+ "day",
2198
+ "invocation_count",
2199
+ "total_duration",
2200
+ "total_tokens",
2201
+ "total_cost_usd",
2202
+ "total_cost_brl",
2203
+ ]),
2204
+ );
2205
+ expect(columns("zaip_vw_daily_aggregates")).not.toContain(["sub", "run_count"].join(""));
2206
+ expect(columns("zaip_vw_invocations")).toEqual(
2207
+ expect.arrayContaining([
2208
+ "cost_usd",
2209
+ "cost_brl",
2210
+ "cost_method",
2211
+ "pricing_model",
2212
+ "pricing_source",
2213
+ "fx_rate",
2214
+ ]),
2215
+ );
2216
+ expect(columns("zaip_vw_invocations")).not.toEqual(
2217
+ expect.arrayContaining([
2218
+ "coverage",
2219
+ "_coverage",
2220
+ "usage_segments_json",
2221
+ "estimated_usd",
2222
+ "estimated_brl",
2223
+ ]),
2224
+ );
2225
+ expect(columns("zaip_vw_invocation_usage")).not.toContain("coverage");
2226
+ expect(database.prepare("SELECT COUNT(*) AS count FROM zaip_vw_invocations").get()).toEqual({
2227
+ count: 0,
2228
+ });
2229
+ database.close();
2230
+ filesystem.close();
2231
+ });
2232
+ });