@zaip-ai/cli 4.5.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +2 -0
  2. package/dist/commands/init.js +1 -1
  3. package/dist/commands/init.js.map +1 -1
  4. package/dist/services/adapters.d.ts +1 -1
  5. package/dist/services/adapters.d.ts.map +1 -1
  6. package/dist/services/adapters.js +6 -6
  7. package/dist/services/adapters.js.map +1 -1
  8. package/dist/services/codex-config-overlay.d.ts.map +1 -1
  9. package/dist/services/codex-config-overlay.js +27 -10
  10. package/dist/services/codex-config-overlay.js.map +1 -1
  11. package/dist/services/codex-native-tool-authority-template.d.ts.map +1 -1
  12. package/dist/services/codex-native-tool-authority-template.js +188 -9
  13. package/dist/services/codex-native-tool-authority-template.js.map +1 -1
  14. package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
  15. package/dist/services/codex-pre-tool-hook-template.js +50 -128
  16. package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
  17. package/dist/services/codex-pre-tool-hook-template.test.js +4 -0
  18. package/dist/services/codex-pre-tool-hook-template.test.js.map +1 -1
  19. package/dist/services/codex-pre-tool-support-template.d.ts.map +1 -1
  20. package/dist/services/codex-pre-tool-support-template.js +1 -4
  21. package/dist/services/codex-pre-tool-support-template.js.map +1 -1
  22. package/dist/services/codex-stale-git-lock-template.d.ts +2 -0
  23. package/dist/services/codex-stale-git-lock-template.d.ts.map +1 -0
  24. package/dist/services/codex-stale-git-lock-template.js +64 -0
  25. package/dist/services/codex-stale-git-lock-template.js.map +1 -0
  26. package/dist/services/global-codex-config-migration.test.js +1 -4
  27. package/dist/services/global-codex-config-migration.test.js.map +1 -1
  28. package/dist/services/global-codex-config.d.ts.map +1 -1
  29. package/dist/services/global-codex-config.js +2 -1
  30. package/dist/services/global-codex-config.js.map +1 -1
  31. package/dist/services/global-codex-config.test.js +3 -1
  32. package/dist/services/global-codex-config.test.js.map +1 -1
  33. package/dist/services/hook-readiness.d.ts.map +1 -1
  34. package/dist/services/hook-readiness.js.map +1 -1
  35. package/dist/services/install-agents-template.js +2 -2
  36. package/dist/services/install-agents-template.js.map +1 -1
  37. package/dist/services/install-template-policy.d.ts.map +1 -1
  38. package/dist/services/install-template-policy.js +2 -0
  39. package/dist/services/install-template-policy.js.map +1 -1
  40. package/dist/services/logger.d.ts +1 -1
  41. package/dist/services/logger.d.ts.map +1 -1
  42. package/dist/services/logger.js +2 -1
  43. package/dist/services/logger.js.map +1 -1
  44. package/dist/services/logger.test.js +77 -0
  45. package/dist/services/logger.test.js.map +1 -1
  46. package/dist/services/runtime-template-integrity.test.d.ts +2 -0
  47. package/dist/services/runtime-template-integrity.test.d.ts.map +1 -0
  48. package/dist/services/runtime-template-integrity.test.js +26 -0
  49. package/dist/services/runtime-template-integrity.test.js.map +1 -0
  50. package/dist/services/runtime-template-test-support.d.ts +3 -0
  51. package/dist/services/runtime-template-test-support.d.ts.map +1 -0
  52. package/dist/services/runtime-template-test-support.js +32 -0
  53. package/dist/services/runtime-template-test-support.js.map +1 -0
  54. package/dist/services/runtime-template.test.js +37 -48
  55. package/dist/services/runtime-template.test.js.map +1 -1
  56. package/package.json +1 -1
  57. package/templates/.agents/README.md +8 -0
  58. package/templates/.agents/config/agents/discovery.json +1 -1
  59. package/templates/.agents/config/deploy-policy.json +5 -0
  60. package/templates/.agents/dashboard/.zaip-managed.json +235 -83
  61. package/templates/.agents/dashboard/README.md +2 -0
  62. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-f4ZGJRg3.js → CostCalculationGuidePage-OUjdwEcM.js} +1 -1
  63. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-CkQ_y8kg.js → DashboardHelpDialog-Cei1ChjE.js} +99 -10
  64. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-v9zGRTlk.js → PricingSettingsDialog-_woNCrTT.js} +1 -1
  65. package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-doIMhei7.js → ProjectSettingsPage-B2_7MVOP.js} +1 -1
  66. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-C_4ftjyB.js → TraceabilityWorkspace-C_jCsSZe.js} +1 -1
  67. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-C8zQDBT7.js +68 -0
  68. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-Cv3WBFIF.js → icons-vendor-iMPXot7Y.js} +1 -1
  69. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DZhppOIk.css +1 -0
  70. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DiCwz0sr.js +2 -0
  71. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-mHgsmxb6.js → react-vendor-721Re3Ss.js} +1 -1
  72. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-B0CPyEVq.js → traceability-BjdTkWS0.js} +1 -1
  73. package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
  74. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  75. package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +6 -34
  76. package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.dom.test.ts +130 -0
  77. package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.tsx +226 -0
  78. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-sqlite-view.ts +45 -0
  79. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +15 -0
  80. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +17 -46
  81. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-fixtures.ts +23 -0
  82. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.test.ts +69 -0
  83. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.ts +59 -0
  84. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback.css +131 -0
  85. package/templates/.agents/dashboard/apps/frontend/src/styles.css +0 -32
  86. package/templates/.agents/dashboard/apps/frontend/src/use-dashboard-app-model.tsx +2 -2
  87. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  88. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +2 -2
  89. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +1 -1
  90. package/templates/.agents/dashboard/modules/operations/package.json +1 -1
  91. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.macro-metrics.test.ts +7 -2
  92. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +6 -6
  93. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +1 -4
  94. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.navigation.test.ts +236 -0
  95. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +1 -24
  96. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +3 -2
  97. package/templates/.agents/dashboard/modules/operations/src/react/overview-duration-hint.ts +14 -0
  98. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  99. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  100. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  101. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  102. package/templates/.agents/dashboard/packages/local-runtime/DIAGNOSTICS.md +42 -0
  103. package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.d.mts +10 -0
  104. package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.mjs +101 -0
  105. package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.test.mjs +58 -0
  106. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +318 -236
  107. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +285 -205
  108. package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
  109. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-dashboard-startup.mjs +233 -0
  110. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-provider-nonzero.mjs +45 -0
  111. package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-executable-startup.mjs +107 -0
  112. package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-reconciliation-executable.mjs +271 -0
  113. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +318 -236
  114. package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.test.ts +78 -12
  115. package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.ts +36 -56
  116. package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.test.ts +125 -0
  117. package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.ts +168 -0
  118. package/templates/.agents/dashboard/packages/local-runtime/src/dashboard-diagnostics.test.ts +57 -0
  119. package/templates/.agents/dashboard/packages/local-runtime/src/server-agent-catalog-routes.ts +76 -0
  120. package/templates/.agents/dashboard/packages/local-runtime/src/server-dual-reads-manifest-summaries-without-losing.test.ts +5 -0
  121. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-diagnostics.test.ts +118 -0
  122. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts +35 -6
  123. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-route.ts +105 -0
  124. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.test.ts +64 -9
  125. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +19 -27
  126. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-command.test.ts +111 -0
  127. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.test.ts +59 -0
  128. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.ts +44 -0
  129. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +20 -0
  130. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +12 -7
  131. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +48 -6
  132. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +2 -2
  133. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +126 -134
  134. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +14 -2
  135. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-summary-elapsed.ts +107 -0
  136. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.test.ts +163 -0
  137. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.ts +78 -0
  138. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-header-timestamp.test.ts +31 -0
  139. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-nonzero.test.ts +271 -0
  140. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-visibility.ts +32 -0
  141. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-operations-duration.ts +58 -0
  142. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-provider-dimension.ts +12 -3
  143. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-summary.ts +41 -54
  144. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-view-page.ts +9 -58
  145. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +7 -7
  146. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-counts-every-root-execution-day-returns.test.ts +5 -5
  147. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider.test.ts +4 -4
  148. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-rebuild.ts +9 -35
  149. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-schema.ts +13 -9
  150. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +43 -45
  151. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +5 -9
  152. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +5 -1
  153. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-base-views.ts +9 -7
  154. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-dashboard-views.ts +10 -8
  155. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-spec-root-elapsed-view.ts +61 -0
  156. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-spec-root-elapsed.test.ts +210 -0
  157. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-startup-seed.test.ts +87 -0
  158. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-logging.mjs +6 -2
  159. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +7 -1
  160. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +4 -5
  161. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  162. package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.test.mjs +33 -4
  163. package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.tsx +8 -7
  164. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +46 -3
  165. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  166. package/templates/.agents/dashboard/tests/e2e/qualify-kanban-popup.mjs +56 -0
  167. package/templates/.agents/dashboard/tests/e2e/specs/dashboard-update-label.spec.ts +49 -0
  168. package/templates/.agents/design/.zaip-managed.json +17 -5
  169. package/templates/.agents/design/apps/catalog/dist/assets/index-D3uhpycC.js +72 -0
  170. package/templates/.agents/design/apps/catalog/dist/index.html +1 -1
  171. package/templates/.agents/design/apps/catalog/package.json +1 -1
  172. package/templates/.agents/design/apps/catalog/src/pages/ComponentsPage.tsx +13 -1
  173. package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.stories.tsx +6 -0
  174. package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.tsx +101 -0
  175. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  176. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  177. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
  178. package/templates/.agents/requirements/comparative-audit.md +1 -1
  179. package/templates/.agents/requirements/context-engine.md +1 -1
  180. package/templates/.agents/requirements/cost-observability.md +1 -1
  181. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  182. package/templates/.agents/requirements/data-export.md +1 -1
  183. package/templates/.agents/requirements/design-system.md +1 -1
  184. package/templates/.agents/requirements/execution-engine.md +1 -1
  185. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  186. package/templates/.agents/requirements/model-orchestration.md +21 -6
  187. package/templates/.agents/requirements/public-distribution.md +4 -4
  188. package/templates/.agents/requirements/software-architecture.md +1 -1
  189. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  190. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  191. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  192. package/templates/.agents/rules/core.md +3 -1
  193. package/templates/.agents/rules/workflow.md +3 -2
  194. package/templates/.agents/runtime/VERSION +1 -1
  195. package/templates/.agents/runtime/bin/zaip.mjs +368 -199
  196. package/templates/.agents/runtime/checksums.sha256 +5 -5
  197. package/templates/.agents/runtime/manifest.json +1 -1
  198. package/templates/.agents/runtime/provenance.json +4 -4
  199. package/templates/.agents/runtime/sbom.cdx.json +1 -1
  200. package/templates/.agents/skills/zaip-skill-config/SKILL.md +25 -3
  201. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +4 -0
  202. package/templates/.agents/skills/zaip-skill-execution/references/autonomous-continuation.md +4 -0
  203. package/templates/.agents/skills/zaip-skill-execution/references/candidate-workflow.md +1 -1
  204. package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +2 -2
  205. package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +1 -0
  206. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +38 -48
  207. package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
  208. package/templates/.agents/skills/zaip-skill-github-deploy/references/deployment-workflow.md +67 -99
  209. package/templates/.agents/skills/zaip-skill-github-deploy/references/release-identity.md +33 -30
  210. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.mjs +307 -0
  211. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +337 -0
  212. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +41 -28
  213. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/guarded-commits.mjs +212 -0
  214. package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +4 -0
  215. package/templates/.agents/skills/zaip-skill-prd/SKILL.md +4 -0
  216. package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +46 -68
  217. package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +100 -44
  218. package/templates/.agents/skills/zaip-skill-send-email/SKILL.md +15 -0
  219. package/templates/.agents/skills/zaip-skill-send-email/agents/openai.yaml +4 -0
  220. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -2
  221. package/templates/.agents/skills/zaip-skill-spec/SKILL.md +4 -0
  222. package/templates/.agents/skills/zaip-skill-task/SKILL.md +4 -0
  223. package/templates/.agents/skills/zaip-skill-testing-strategy/SKILL.md +19 -0
  224. package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +2 -0
  225. package/templates/.agents/skills/zaip-skill-use-connectors/SKILL.md +15 -0
  226. package/templates/.agents/skills/zaip-skill-use-connectors/agents/openai.yaml +4 -0
  227. package/templates/.agents/skills/zaip-skill-use-connectors/scripts/connector-guidance.test.mjs +29 -0
  228. package/templates/.agents/skills/zaip-skill-use-connectors/scripts/verify-connector-lock.mjs +30 -0
  229. package/templates/.agents/tools/cloud-runner.integration.test.mjs +3 -0
  230. package/templates/.agents/zaip-aios/VERSION +1 -1
  231. package/templates/.agents/zaip-aios/guides/03-tasks.md +6 -0
  232. package/templates/.agents/zaip-aios/guides/04-specs.md +6 -0
  233. package/templates/.agents/zaip-aios/guides/05-prds.md +4 -0
  234. package/templates/.agents/zaip-aios/guides/06-execucao.md +6 -0
  235. package/templates/.agents/zaip-aios/guides/16-lifecycle-e-reconciliacao.md +6 -0
  236. package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +4 -0
  237. package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +6 -0
  238. package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +36 -4
  239. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +15 -4
  240. package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +8 -0
  241. package/templates/template-manifest.json +354 -150
  242. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-D7PrDZy9.js +0 -68
  243. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-OBJXZOwe.css +0 -1
  244. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Rbmpz4UO.js +0 -2
@@ -1,4 +1,5 @@
1
1
  import { costInvocationProjectionSelect } from "./sqlite-cost-invocation-projection.js";
2
+ import { specRootElapsedViewSql } from "./sqlite-spec-root-elapsed-view.js";
2
3
  export const baseViewSql = `
3
4
  CREATE INDEX zaip_activities_agent_run ON zaip_aios_activities(activity_source_kind, agent_run_id, occurred_day_sp, occurred_at DESC);
4
5
  CREATE INDEX zaip_activities_relationships ON zaip_aios_activities(task_id, spec_id, prd_id, execution_id, conversation_id);
@@ -55,6 +56,7 @@ export const baseViewSql = `
55
56
  WHERE allocation.spec_id=a.spec_id AND allocation.run_id=a.run_id
56
57
  )
57
58
  UNION ALL SELECT * FROM shared_creation_allocations;
59
+ ${specRootElapsedViewSql}
58
60
  CREATE VIEW attributed_token_active AS
59
61
  SELECT f.activity_row_id, f.occurred_day_sp, f.attributed_token_active_ms,
60
62
  a.run_id, a.execution_id, a.task_id, a.spec_id, a.prd_id, a.conversation_id,
@@ -214,11 +216,11 @@ export const baseViewSql = `
214
216
  SELECT day, interval_group, MIN(start_ms) AS start_ms, MAX(end_ms) AS end_ms
215
217
  FROM cost_monitor_grouped_intervals GROUP BY day, interval_group
216
218
  ), cost_monitor_daily_duration AS (
217
- -- Tempo is the shared attributed activity fact. Financial
218
- -- coverage is reported independently below; an incomplete price
219
- -- record must never erase an observed temporal fact.
220
- SELECT occurred_day_sp AS day, SUM(attributed_token_active_ms) AS total_duration
221
- FROM attributed_token_active_facts GROUP BY occurred_day_sp
219
+ -- Cost daily duration is factual elapsed time from source intervals.
220
+ -- The union removes root/subrun overlap; token-active allocation
221
+ -- remains a distinct Operations and attribution measure.
222
+ SELECT day, ROUND(SUM(end_ms - start_ms)) AS total_duration
223
+ FROM cost_monitor_merged_intervals GROUP BY day
222
224
  ), cost_monitor_duration_coverage AS (
223
225
  SELECT day, COUNT(DISTINCT activity_id) AS eligible_runs,
224
226
  COUNT(DISTINCT activity_id) AS known_runs
@@ -267,7 +269,7 @@ export const baseViewSql = `
267
269
  i.total_root_run_duration,
268
270
  i.total_sub_run_duration,
269
271
  o.total_duration AS operations_total_duration,
270
- COALESCE(dcm.total_duration, o.total_duration) AS cost_monitor_total_duration,
272
+ dcm.total_duration AS cost_monitor_total_duration,
271
273
  coverage.eligible_runs AS cost_monitor_duration_eligible_runs,
272
274
  coverage.known_runs AS cost_monitor_duration_known_runs,
273
275
  o.total_duration AS total_duration,
@@ -305,7 +307,7 @@ export const baseViewSql = `
305
307
  -- population rather than Operations' broader Run/activity totals.
306
308
  CREATE VIEW cost_monitor_overview AS SELECT period_kind, period_key, cost_monitor_invocation_count AS total_run_count, cost_monitor_invocation_count AS invocation_count, llm_call_count, source_event_count, cost_monitor_total_duration, total_tokens, total_cost_usd, total_cost_brl FROM overview;
307
309
  CREATE VIEW task_summary AS SELECT t.id, t.title, t.status, t.risk, t.occurred_at, t.occurred_day_sp, COUNT(i.run_id) AS invocation_count, SUM(i.llm_call_count) AS llm_call_count, SUM(i.duration_ms) AS total_duration, SUM(i.total_tokens) AS total_tokens, SUM(i.cost_usd) AS total_cost_usd, SUM(i.cost_brl) AS total_cost_brl FROM tasks t LEFT JOIN activities i ON i.task_id = t.id GROUP BY t.id, t.title, t.status, t.risk, t.occurred_at, t.occurred_day_sp;
308
- CREATE VIEW spec_summary AS SELECT s.id, s.title, s.status, s.risk, s.occurred_at, s.occurred_day_sp, COUNT(i.run_id) AS invocation_count, SUM(i.llm_call_count) AS llm_call_count, SUM(i.duration_ms) AS total_duration, SUM(i.total_tokens) AS total_tokens, SUM(i.cost_usd) AS total_cost_usd, SUM(i.cost_brl) AS total_cost_brl FROM specs s LEFT JOIN macro_spec_activities i ON i.spec_id = s.id GROUP BY s.id, s.title, s.status, s.risk, s.occurred_at, s.occurred_day_sp;
310
+ CREATE VIEW spec_summary AS SELECT s.id, s.title, s.status, s.risk, s.occurred_at, s.occurred_day_sp, COUNT(i.run_id) AS invocation_count, SUM(i.llm_call_count) AS llm_call_count, elapsed.total_duration_ms AS total_duration, SUM(i.total_tokens) AS total_tokens, SUM(i.cost_usd) AS total_cost_usd, SUM(i.cost_brl) AS total_cost_brl FROM specs s LEFT JOIN macro_spec_activities i ON i.spec_id = s.id LEFT JOIN spec_root_elapsed elapsed ON elapsed.spec_id=s.id GROUP BY s.id, s.title, s.status, s.risk, s.occurred_at, s.occurred_day_sp;
309
311
  CREATE VIEW prd_summary AS SELECT p.id, p.title, p.status, p.risk, p.occurred_at, p.occurred_day_sp, COUNT(i.run_id) AS invocation_count, SUM(i.llm_call_count) AS llm_call_count, SUM(i.duration_ms) AS total_duration, SUM(i.total_tokens) AS total_tokens, SUM(i.cost_usd) AS total_cost_usd, SUM(i.cost_brl) AS total_cost_brl FROM prds p LEFT JOIN activities i ON i.prd_id = p.id GROUP BY p.id, p.title, p.status, p.risk, p.occurred_at, p.occurred_day_sp;
310
312
  CREATE VIEW cost_monitor_group_daily AS SELECT * FROM cost_monitor_group_daily_cache;
311
313
  CREATE VIEW vw_overview AS SELECT * FROM overview;
@@ -51,7 +51,9 @@ export const dashboardViewSql = ` CREATE VIEW vw_operations_work_list AS
51
51
  FROM entity_activity GROUP BY entity_kind, entity_id
52
52
  ), durations AS (
53
53
  SELECT entity_kind, entity_id, ROUND(SUM(duration_ms)) AS total_duration
54
- FROM entity_activity GROUP BY entity_kind, entity_id
54
+ FROM entity_activity WHERE entity_kind <> 'spec' GROUP BY entity_kind, entity_id
55
+ UNION ALL
56
+ SELECT 'spec', spec_id, total_duration_ms FROM spec_root_elapsed
55
57
  )
56
58
  SELECT w.entity_kind, w.id, w.title, w.status, w.risk, w.occurred_at, w.occurred_day_sp,
57
59
  f.invocation_count, f.llm_call_count, d.total_duration,
@@ -231,7 +233,6 @@ export const dashboardViewSql = ` CREATE VIEW vw_operations_work_list AS
231
233
  WITH usage AS (
232
234
  SELECT spec_id,
233
235
  COUNT(*) AS total_run_count,
234
- ROUND(SUM(duration_ms)) AS total_duration_ms,
235
236
  SUM(total_tokens) AS total_tokens,
236
237
  SUM(cost_usd) AS total_cost_usd,
237
238
  SUM(cost_brl) AS total_cost_brl
@@ -239,16 +240,17 @@ export const dashboardViewSql = ` CREATE VIEW vw_operations_work_list AS
239
240
  )
240
241
  SELECT COUNT(*) AS entity_count,
241
242
  SUM(usage.total_run_count) AS total_run_count,
242
- SUM(usage.total_duration_ms) AS total_duration_ms,
243
+ SUM(elapsed.total_duration_ms) AS total_duration_ms,
243
244
  SUM(usage.total_tokens) AS total_tokens,
244
245
  SUM(usage.total_cost_usd) AS total_cost_usd,
245
246
  SUM(usage.total_cost_brl) AS total_cost_brl,
246
- CASE WHEN COUNT(*)=0 THEN NULL ELSE SUM(usage.total_duration_ms)/COUNT(*) END AS average_duration_ms,
247
+ CASE WHEN COUNT(*)=0 THEN NULL ELSE SUM(elapsed.total_duration_ms)/COUNT(*) END AS average_duration_ms,
247
248
  CASE WHEN COUNT(*)=0 THEN NULL ELSE SUM(usage.total_tokens)/COUNT(*) END AS average_tokens,
248
249
  CASE WHEN COUNT(*)=0 THEN NULL ELSE SUM(usage.total_cost_usd)/COUNT(*) END AS average_cost_usd,
249
250
  CASE WHEN COUNT(*)=0 THEN NULL ELSE SUM(usage.total_cost_brl)/COUNT(*) END AS average_cost_brl,
250
251
  MAX(s.occurred_at) AS latest_created_at
251
- FROM spec_summary s LEFT JOIN usage ON usage.spec_id=s.id;
252
+ FROM spec_summary s LEFT JOIN usage ON usage.spec_id=s.id
253
+ LEFT JOIN spec_root_elapsed elapsed ON elapsed.spec_id=s.id;
252
254
  CREATE VIEW vw_first_fold_prd_metrics_all_time AS
253
255
  WITH usage AS (
254
256
  SELECT prd_id,
@@ -276,16 +278,16 @@ export const dashboardViewSql = ` CREATE VIEW vw_operations_work_list AS
276
278
  SELECT spec_id,
277
279
  COUNT(*) AS total_run_count,
278
280
  SUM(llm_call_count) AS llm_call_count,
279
- ROUND(SUM(duration_ms)) AS total_duration_ms,
280
281
  SUM(total_tokens) AS total_tokens,
281
282
  SUM(cost_usd) AS total_cost_usd,
282
283
  SUM(cost_brl) AS total_cost_brl
283
284
  FROM macro_spec_activities GROUP BY spec_id
284
285
  ), ranked AS (
285
286
  SELECT s.id AS entity_id, s.title, s.status, usage.total_run_count, usage.llm_call_count,
286
- usage.total_duration_ms, usage.total_tokens, usage.total_cost_usd, usage.total_cost_brl,
287
- ROW_NUMBER() OVER (ORDER BY usage.total_duration_ms IS NOT NULL DESC, usage.total_duration_ms DESC, usage.total_run_count DESC, s.id) AS rank
287
+ elapsed.total_duration_ms, usage.total_tokens, usage.total_cost_usd, usage.total_cost_brl,
288
+ ROW_NUMBER() OVER (ORDER BY elapsed.total_duration_ms IS NOT NULL DESC, elapsed.total_duration_ms DESC, usage.total_run_count DESC, s.id) AS rank
288
289
  FROM spec_summary s LEFT JOIN usage ON usage.spec_id=s.id
290
+ LEFT JOIN spec_root_elapsed elapsed ON elapsed.spec_id=s.id
289
291
  ), total AS (SELECT SUM(total_duration_ms) AS total_duration_ms FROM ranked)
290
292
  SELECT ranked.rank, ranked.entity_id, ranked.title, ranked.status,
291
293
  COALESCE(ranked.total_run_count,0) AS total_run_count, ranked.llm_call_count,
@@ -0,0 +1,61 @@
1
+ /**
2
+ * SPEC elapsed time is the union of factual root Run intervals within each
3
+ * SPEC. A root and its children must not both contribute elapsed time. A Run
4
+ * linked to two SPECs remains visible in each; the cross-SPEC sum is therefore
5
+ * a sum of entity durations, not a global wall-clock total.
6
+ */
7
+ export const specRootElapsedViewSql = `
8
+ CREATE VIEW spec_root_elapsed AS
9
+ WITH roots AS (
10
+ SELECT spec_id, run_id,
11
+ MAX(source_duration_ms) AS source_duration_ms,
12
+ MIN(source_started_at) AS source_started_at,
13
+ MAX(source_finished_at) AS source_finished_at
14
+ FROM macro_spec_activities
15
+ WHERE spec_id IS NOT NULL AND run_role='root'
16
+ GROUP BY spec_id, run_id
17
+ ), source_intervals AS (
18
+ SELECT spec_id,
19
+ (julianday(source_started_at) - 2440587.5) * 86400000.0 AS start_ms,
20
+ (julianday(source_finished_at) - 2440587.5) * 86400000.0 AS end_ms
21
+ FROM roots
22
+ WHERE source_started_at IS NOT NULL AND source_finished_at IS NOT NULL
23
+ AND julianday(source_finished_at) > julianday(source_started_at)
24
+ ), ordered AS (
25
+ SELECT spec_id, start_ms, end_ms,
26
+ MAX(end_ms) OVER (
27
+ PARTITION BY spec_id ORDER BY start_ms, end_ms
28
+ ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
29
+ ) AS prior_end_ms
30
+ FROM source_intervals
31
+ ), grouped AS (
32
+ SELECT spec_id, start_ms, end_ms,
33
+ SUM(CASE WHEN prior_end_ms IS NULL OR start_ms > prior_end_ms THEN 1 ELSE 0 END)
34
+ OVER (PARTITION BY spec_id ORDER BY start_ms, end_ms) AS interval_group
35
+ FROM ordered
36
+ ), merged AS (
37
+ SELECT spec_id, interval_group, MIN(start_ms) AS start_ms, MAX(end_ms) AS end_ms
38
+ FROM grouped GROUP BY spec_id, interval_group
39
+ ), source_totals AS (
40
+ SELECT spec_id, SUM(end_ms - start_ms) AS duration_ms
41
+ FROM merged GROUP BY spec_id
42
+ ), fallback_totals AS (
43
+ -- A terminal factual duration without source boundaries remains
44
+ -- available, but cannot be unioned against another Run interval.
45
+ SELECT spec_id, SUM(source_duration_ms) AS duration_ms
46
+ FROM roots
47
+ WHERE source_duration_ms IS NOT NULL
48
+ AND NOT (
49
+ source_started_at IS NOT NULL AND source_finished_at IS NOT NULL
50
+ AND julianday(source_finished_at) > julianday(source_started_at)
51
+ )
52
+ GROUP BY spec_id
53
+ ), known_specs AS (
54
+ SELECT spec_id FROM source_totals UNION SELECT spec_id FROM fallback_totals
55
+ )
56
+ SELECT known_specs.spec_id,
57
+ ROUND(COALESCE(source_totals.duration_ms, 0) + COALESCE(fallback_totals.duration_ms, 0)) AS total_duration_ms
58
+ FROM known_specs
59
+ LEFT JOIN source_totals USING(spec_id)
60
+ LEFT JOIN fallback_totals USING(spec_id);
61
+ `;
@@ -0,0 +1,210 @@
1
+ import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { afterEach, expect, it } from "vitest";
5
+ import { rebuildSqliteReadModel, SqliteReadModelFileSystem } from "./sqlite-read-model.js";
6
+
7
+ const roots: string[] = [];
8
+ afterEach(async () => {
9
+ await Promise.all(
10
+ roots
11
+ .splice(0)
12
+ .map((root) =>
13
+ import("node:fs/promises").then(({ rm }) => rm(root, { recursive: true, force: true })),
14
+ ),
15
+ );
16
+ });
17
+
18
+ it("shows a 21-hour source-observed SPEC root and splits Cost elapsed without child double counting", async () => {
19
+ const temp = resolve(import.meta.dirname, "../../../../../..", ".tmp", "sqlite-read-model-tests");
20
+ await mkdir(temp, { recursive: true });
21
+ const root = await mkdtemp(join(temp, "spec-elapsed-"));
22
+ roots.push(root);
23
+ const records = join(root, ".agents", "records");
24
+ await mkdir(join(records, "runs", "2026", "09"), { recursive: true });
25
+ await mkdir(join(records, "specs", "2026", "09"), { recursive: true });
26
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
27
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
28
+ for (const id of ["SPEC-ROOT", "SPEC-CHILD"])
29
+ await writeFile(
30
+ join(records, "specs", "2026", "09", `${id}.md`),
31
+ `---\nid: ${id}\ntitle: ${id}\nstatus: active\nrisk: standard\ncreated_at: 2026-09-30T22:00:00.000Z\n---\n# ${id}\n`,
32
+ );
33
+
34
+ const sourceStart = "2026-09-30T22:41:41.072Z";
35
+ const sourceEnd = "2026-10-01T19:55:46.569Z";
36
+ const run = (id: string, specId: string, start: string, end: string, parent: string | null) => {
37
+ const invocation = `AINV-${id}`;
38
+ return [
39
+ {
40
+ schema_version: 2,
41
+ event_id: `${id}:1`,
42
+ run_id: id,
43
+ sequence: 1,
44
+ occurred_at: start,
45
+ type: "run.started",
46
+ attribution_status: "attributed",
47
+ phase: "execution",
48
+ run_role: parent === null ? "orchestrator" : "subagent",
49
+ run_purpose: "execution",
50
+ requires_finalizer: false,
51
+ execution_kind: "delivery",
52
+ execution_eligibility: ["delivery"],
53
+ entrypoint: "spec",
54
+ work_type: "new",
55
+ storage_mode: "tracked_sanitized",
56
+ client: "codex",
57
+ agent: "agent",
58
+ agent_name: "agent",
59
+ provider: "openai",
60
+ model: "gpt-6-sol",
61
+ reasoning_effort: null,
62
+ client_thread_id: `thread-${id}`,
63
+ client_turn_id: `turn-${id}`,
64
+ conversation_id: "CONV-SPEC",
65
+ execution_id: "EXEC-SPEC",
66
+ task_id: null,
67
+ spec_id: specId,
68
+ prd_id: null,
69
+ change_id: null,
70
+ parent_run_id: parent,
71
+ continuation_of_run_id: null,
72
+ retry_of_run_id: null,
73
+ agent_invocation_id: invocation,
74
+ root_agent_invocation_id: parent === null ? invocation : "AINV-RUN-ROOT",
75
+ parent_agent_invocation_id: parent === null ? null : "AINV-RUN-ROOT",
76
+ invocation_kind: parent === null ? "orchestrator" : "delegated",
77
+ delegation_depth: parent === null ? 0 : 1,
78
+ },
79
+ {
80
+ schema_version: 2,
81
+ event_id: `${id}:2`,
82
+ run_id: id,
83
+ sequence: 2,
84
+ occurred_at: end,
85
+ type: "usage.recorded",
86
+ phase: "execution",
87
+ activity_kind: "model_usage",
88
+ classification_source: "explicit",
89
+ tokens: {
90
+ input: 100,
91
+ cached_input: 0,
92
+ output: 20,
93
+ reasoning: 0,
94
+ total: 120,
95
+ source: "provider_reported",
96
+ },
97
+ cost: { usd: 1, brl: 5, fx_rate: 5 },
98
+ provider: "openai",
99
+ model: "gpt-6-sol",
100
+ source_segment_key: id,
101
+ model_call_id: `MCALL-${id}`,
102
+ runtime: { reasoning_effort: null, service_tier: null },
103
+ supersedes_event_id: null,
104
+ source_occurred_at: end,
105
+ source_started_at: start,
106
+ source_finished_at: end,
107
+ duration_ms: null,
108
+ duration_provenance: null,
109
+ },
110
+ ];
111
+ };
112
+ for (const [id, specId, start, end, parent] of [
113
+ ["RUN-ROOT", "SPEC-ROOT", sourceStart, sourceEnd, null],
114
+ ["RUN-OVERLAP", "SPEC-ROOT", "2026-10-01T04:30:00.000Z", "2026-10-01T05:30:00.000Z", null],
115
+ ["RUN-CHILD", "SPEC-CHILD", "2026-10-01T04:00:00.000Z", "2026-10-01T05:00:00.000Z", "RUN-ROOT"],
116
+ ["RUN-UNKNOWN", "SPEC-CHILD", "2026-10-02T12:00:00.000Z", "2026-10-02T12:00:00.000Z", null],
117
+ ] as const)
118
+ await writeFile(
119
+ join(records, "runs", "2026", "09", `${id}.jsonl`),
120
+ `${run(id, specId, start, end, parent)
121
+ .map((event) => JSON.stringify(event))
122
+ .join("\n")}\n`,
123
+ );
124
+
125
+ await rebuildSqliteReadModel(root);
126
+ const db = new DatabaseSync(resolve(root, ".local/sqlite/zaip-aios-dashboard.sqlite"), {
127
+ readOnly: true,
128
+ });
129
+ const observed = Date.parse(sourceEnd) - Date.parse(sourceStart);
130
+ expect(observed).toBe(76_445_497);
131
+ expect(
132
+ db.prepare("SELECT source_duration_ms FROM zaip_aios_activities WHERE run_id='RUN-ROOT'").get(),
133
+ ).toEqual({ source_duration_ms: observed });
134
+ expect(
135
+ db
136
+ .prepare("SELECT spec_id, total_duration_ms FROM zaip_vw_spec_root_elapsed ORDER BY spec_id")
137
+ .all(),
138
+ ).toEqual([{ spec_id: "SPEC-ROOT", total_duration_ms: observed }]);
139
+ expect(
140
+ db
141
+ .prepare(
142
+ "SELECT entity_id, total_duration_ms FROM zaip_vw_first_fold_top_specs_all_time ORDER BY rank LIMIT 1",
143
+ )
144
+ .get(),
145
+ ).toEqual({ entity_id: "SPEC-ROOT", total_duration_ms: observed });
146
+ expect(
147
+ db.prepare("SELECT total_duration_ms FROM zaip_vw_first_fold_spec_metrics_all_time").get(),
148
+ ).toEqual({ total_duration_ms: observed });
149
+ expect(
150
+ db
151
+ .prepare("SELECT period_key, total_duration FROM zaip_vw_cost_daily ORDER BY period_key")
152
+ .all(),
153
+ ).toEqual([
154
+ { period_key: "2026-09-30", total_duration: 15_498_928 },
155
+ { period_key: "2026-10-01", total_duration: 60_946_569 },
156
+ { period_key: "2026-10-02", total_duration: null },
157
+ ]);
158
+ db.close();
159
+ const filesystem = new SqliteReadModelFileSystem(root);
160
+ const allTimeCost = filesystem.costMonitorSummaryAggregate({ from: null, to: null });
161
+ expect(allTimeCost.cost_monitor_daily_duration).toMatchObject({
162
+ "2026-09-30": { known_sum: 15_498_928 },
163
+ "2026-10-01": { known_sum: 60_946_569 },
164
+ "2026-10-02": { known_sum: 0, known_runs: 0, eligible_runs: 1 },
165
+ });
166
+ expect(allTimeCost.daily_rows).toContainEqual(
167
+ expect.objectContaining({ key: "2026-10-02", total_duration: null }),
168
+ );
169
+ const unknownFiltered = filesystem.costMonitorSummaryAggregate({
170
+ from: "2026-10-02",
171
+ to: "2026-10-02",
172
+ specs: ["SPEC-CHILD"],
173
+ });
174
+ expect(unknownFiltered.cost_monitor_daily_duration).toMatchObject({
175
+ "2026-10-02": { known_sum: 0, known_runs: 0, eligible_runs: 1 },
176
+ });
177
+ expect(unknownFiltered.daily_rows).toContainEqual(
178
+ expect.objectContaining({ key: "2026-10-02", total_duration: null }),
179
+ );
180
+ const secondDayCost = filesystem.costMonitorSummaryAggregate({
181
+ from: "2026-10-01",
182
+ to: "2026-10-01",
183
+ });
184
+ expect(secondDayCost.cost_monitor_daily_duration).toMatchObject({
185
+ "2026-10-01": { known_sum: 60_946_569 },
186
+ });
187
+ expect(secondDayCost.cost_monitor_daily_duration).not.toHaveProperty("2026-09-30");
188
+ const filteredCost = filesystem.costMonitorSummaryAggregate({
189
+ from: "2026-10-01",
190
+ to: "2026-10-01",
191
+ specs: ["SPEC-ROOT"],
192
+ });
193
+ expect(filteredCost.cost_monitor_daily_duration).toMatchObject({
194
+ "2026-10-01": { known_sum: 60_946_569 },
195
+ });
196
+ const unknownDay = { from: "2026-10-02", to: "2026-10-02" };
197
+ expect(
198
+ filesystem.costMonitorSummaryAggregate(unknownDay).cost_monitor_daily_duration,
199
+ ).toMatchObject({
200
+ "2026-10-02": { known_sum: 0, eligible_runs: 1, known_runs: 0 },
201
+ });
202
+ expect(
203
+ filesystem.costMonitorSummaryAggregate({ ...unknownDay, specs: ["SPEC-CHILD"] })
204
+ .cost_monitor_daily_duration,
205
+ ).toMatchObject({ "2026-10-02": { known_sum: 0, eligible_runs: 1, known_runs: 0 } });
206
+ expect(filesystem.summaryPage("spec", { from: null, to: null }, 0, 10).rows).toContainEqual(
207
+ expect.objectContaining({ id: "SPEC-ROOT", total_duration: observed }),
208
+ );
209
+ filesystem.close();
210
+ });
@@ -0,0 +1,87 @@
1
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+ import { SqliteReadModelFileSystem } from "./sqlite-read-model-filesystem.js";
6
+ import { rebuildSqliteReadModel } from "./sqlite-read-model-rebuild.js";
7
+ import { validateSqliteReadModelSchema } from "./sqlite-read-model-schema.js";
8
+ import * as fingerprints from "./sqlite-read-model-source-fingerprint.js";
9
+
10
+ const roots: string[] = [];
11
+ const source = ".agents/records/tasks/TASK-STARTUP.md";
12
+ const record = (title: string) =>
13
+ `---\nid: TASK-STARTUP\ntitle: ${title}\nstatus: executing\ncreated_at: 2026-09-29T12:00:00Z\n---\n`;
14
+ async function fixture() {
15
+ const parent = resolve(import.meta.dirname, "../../../../..", ".local/sqlite/tests");
16
+ await mkdir(parent, { recursive: true });
17
+ const root = await mkdtemp(join(parent, "startup-seed-"));
18
+ roots.push(root);
19
+ await mkdir(join(root, ".agents/records/tasks"), { recursive: true });
20
+ await writeFile(join(root, source), record("Before"));
21
+ return root;
22
+ }
23
+ afterEach(async () => {
24
+ vi.restoreAllMocks();
25
+ await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
26
+ });
27
+ describe("validated startup generation", () => {
28
+ it("serves a seed without traversing sources, then canonical refresh observes the changed record", async () => {
29
+ const root = await fixture();
30
+ await rebuildSqliteReadModel(root);
31
+ await writeFile(join(root, source), record("After"));
32
+ const fingerprint = vi.spyOn(fingerprints, "sourceFingerprint");
33
+ const sql = vi.spyOn(DatabaseSync.prototype, "prepare");
34
+ const result = await rebuildSqliteReadModel(root, {
35
+ preferExisting: true,
36
+ sourceRoots: ["invalid-startup-root"],
37
+ });
38
+ expect(result).toMatchObject({ cache: "stale_seed", fingerprint_ms: 0, materialize_ms: 0 });
39
+ expect(fingerprint).not.toHaveBeenCalled();
40
+ expect(sql).toHaveBeenCalledWith("PRAGMA quick_check");
41
+ expect(sql).not.toHaveBeenCalledWith("PRAGMA integrity_check");
42
+ const reader = new SqliteReadModelFileSystem(root);
43
+ try {
44
+ expect(await reader.readText(source)).toContain("title: Before");
45
+ await rebuildSqliteReadModel(root);
46
+ expect(sql).toHaveBeenCalledWith("PRAGMA integrity_check");
47
+ reader.refresh();
48
+ expect(await reader.readText(source)).toContain("title: After");
49
+ } finally {
50
+ reader.close();
51
+ }
52
+ });
53
+
54
+ it.each([
55
+ "old-schema",
56
+ "missing-index",
57
+ "missing-payload",
58
+ ])("rebuilds %s rather than serving incomplete seed", async (damage) => {
59
+ const root = await fixture();
60
+ const first = await rebuildSqliteReadModel(root);
61
+ const database = new DatabaseSync(first.path);
62
+ try {
63
+ if (damage === "old-schema")
64
+ database.exec(
65
+ "UPDATE zaip_aios_activities SET payload_json=json_set(payload_json,'$.value','0') WHERE payload_type='metadata' AND json_extract(payload_json,'$.key')='schema_version'",
66
+ );
67
+ else if (damage === "missing-index")
68
+ database.exec("DROP INDEX zaip_activities_work_item_identity");
69
+ else
70
+ database.exec(
71
+ "DELETE FROM zaip_aios_activities WHERE payload_type='first_fold_all_time_board_cache'",
72
+ );
73
+ } finally {
74
+ database.close();
75
+ }
76
+ expect((await rebuildSqliteReadModel(root, { preferExisting: true })).cache).toBe("rebuilt");
77
+ expect(() => validateSqliteReadModelSchema(first.path)).not.toThrow();
78
+ });
79
+
80
+ it("rejects corrupt bytes and never serves them as a validated seed", async () => {
81
+ const root = await fixture();
82
+ const first = await rebuildSqliteReadModel(root);
83
+ await writeFile(first.path, "corrupt SQLite bytes");
84
+ expect(() => validateSqliteReadModelSchema(first.path, { integrity: "quick" })).toThrow();
85
+ await expect(rebuildSqliteReadModel(root, { preferExisting: true })).rejects.toThrow();
86
+ });
87
+ });
@@ -1,11 +1,15 @@
1
1
  import { appendFileSync, mkdirSync } from "node:fs";
2
- import { launcherLogPath, sessionDirectory } from "./start-dashboard-paths.mjs";
2
+ import { createDashboardDiagnostics, sanitizeDiagnostic } from "./dashboard-diagnostics.mjs";
3
+ import { launcherLogPath, sessionDirectory, workspaceRoot } from "./start-dashboard-paths.mjs";
4
+ export const launcherDiagnostics = createDashboardDiagnostics(workspaceRoot, "launcher");
5
+ process.env.ZAIP_DASHBOARD_LAUNCH_ID = launcherDiagnostics.correlationId;
3
6
  export function appendLauncherLog(message) {
7
+ launcherDiagnostics.log("launcher_message", { message });
4
8
  try {
5
9
  mkdirSync(sessionDirectory, { recursive: true });
6
10
  appendFileSync(
7
11
  launcherLogPath,
8
- `[${new Date().toISOString()}] ${stripTerminalControlCodes(message)}\n`,
12
+ `[${new Date().toISOString()}] ${stripTerminalControlCodes(sanitizeDiagnostic(message))}\n`,
9
13
  "utf8",
10
14
  );
11
15
  } catch {
@@ -55,12 +55,18 @@ export async function startDashboardServer({
55
55
  ZAIP_SQLITE_PREFER_LAST_VALID_SEED: "0",
56
56
  ZAIP_SQLITE_DB_PATH: dbPath,
57
57
  },
58
- stdio: ["ignore", "ignore", "pipe"],
58
+ stdio: ["ignore", "pipe", "pipe"],
59
59
  windowsHide: true,
60
60
  });
61
61
  onChildStarted(child);
62
+ appendLog(`Runtime spawned: pid=${child.pid} port=${port} attempt=${attempt}`);
63
+ child.stdout?.setEncoding("utf8");
64
+ child.stdout?.on("data", (chunk) =>
65
+ appendLog(`runtime stdout: ${String(chunk).slice(0, 16000)}`),
66
+ );
62
67
  child.stderr?.setEncoding("utf8");
63
68
  child.stderr?.on("data", (chunk) => {
69
+ appendLog(`runtime stderr: ${String(chunk).slice(0, 16000)}`);
64
70
  stderr = `${stderr}${chunk}`.slice(-4_000);
65
71
  startupTelemetryBuffer = `${startupTelemetryBuffer}${String(chunk)}`;
66
72
  const lines = startupTelemetryBuffer.split("\n");
@@ -3,7 +3,6 @@ import {
3
3
  dashboardUrl,
4
4
  findAvailablePortWithFallback,
5
5
  openBrowser,
6
- preferredPort,
7
6
  } from "./start-dashboard-navigation.mjs";
8
7
 
9
8
  export {
@@ -23,6 +22,7 @@ import {
23
22
  import {
24
23
  appendLauncherLog,
25
24
  formatLauncherError,
25
+ launcherDiagnostics,
26
26
  stripTerminalControlCodes,
27
27
  } from "./start-dashboard-logging.mjs";
28
28
  import {
@@ -36,7 +36,6 @@ import {
36
36
  lockHeartbeatFloorMs,
37
37
  lockPath,
38
38
  lockTimeoutMs,
39
- maxPortAttempts,
40
39
  maxPortHistory,
41
40
  maxServerStartAttempts,
42
41
  nodeCompileCacheDirectory,
@@ -494,6 +493,7 @@ async function startDashboardServer(
494
493
  });
495
494
  }
496
495
  async function main() {
496
+ launcherDiagnostics.log("launcher_entered");
497
497
  let ownedChild = null;
498
498
  let currentSession = null;
499
499
  let launchPage = null;
@@ -741,9 +741,8 @@ async function waitForDashboard(port, child) {
741
741
  await delay(100);
742
742
  }
743
743
  }
744
- function delay(milliseconds) {
745
- return new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));
746
- }
744
+ const delay = (milliseconds) =>
745
+ new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));
747
746
  const isMain = process.argv[1]
748
747
  ? resolve(process.argv[1]) === fileURLToPath(import.meta.url)
749
748
  : false;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zaip-ai/dashboard-ui",
3
- "version": "4.5.0",
3
+ "version": "4.7.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,8 +1,10 @@
1
1
  import { readFileSync } from "node:fs";
2
+ import { createElement } from "react";
3
+ import { renderToStaticMarkup } from "react-dom/server";
2
4
  import { describe, expect, it } from "vitest";
5
+ import { DashboardHeader } from "./DashboardHeader.tsx";
3
6
 
4
7
  const styles = readFileSync(new URL("ui.css", import.meta.url), "utf8");
5
- const headerSource = readFileSync(new URL("DashboardHeader.tsx", import.meta.url), "utf8");
6
8
 
7
9
  describe("DashboardHeader", () => {
8
10
  it("optically balances the repository badge horizontal spacing", () => {
@@ -10,12 +12,39 @@ describe("DashboardHeader", () => {
10
12
  expect(rule).toContain("padding-block: 0.3rem");
11
13
  expect(rule).toContain("padding-inline: 0.6rem 0.72rem");
12
14
  });
13
- it("labels the timestamp as telemetry rather than overall snapshot freshness", () => {
14
- expect(headerSource).toContain('updated: "Última telemetria"');
15
- expect(headerSource).toContain('waiting: "Sem telemetria medida"');
15
+ it.each(["pt-BR", "en-US"])("shows only snapshot freshness in %s", (locale) => {
16
+ const telemetry = "2026-09-29T11:32:27Z";
17
+ const snapshot = "2026-09-29T12:59:35Z";
18
+ const markup = renderHeader({ locale, lastUpdatedAt: telemetry, snapshotUpdatedAt: snapshot });
19
+ const formatted = new Intl.DateTimeFormat(locale, { dateStyle: "short", timeStyle: "medium" });
20
+ expect(markup.match(/<small>/g)).toHaveLength(1);
21
+ expect(markup).toContain(locale === "pt-BR" ? "Última Atualização" : "Last Updated");
22
+ expect(markup).toContain(formatted.format(new Date(snapshot)));
23
+ expect(markup).not.toContain(formatted.format(new Date(telemetry)));
24
+ });
25
+ it("keeps an unavailable snapshot explicit even when telemetry exists", () => {
26
+ const markup = renderHeader({ lastUpdatedAt: "2026-09-29T11:32:27Z" });
27
+ expect(markup.match(/<small>/g)).toHaveLength(1);
28
+ expect(markup).toContain("Atualização indisponível");
29
+ expect(markup).not.toContain("11:32:27");
16
30
  });
17
31
  });
18
32
 
33
+ function renderHeader(props) {
34
+ return renderToStaticMarkup(
35
+ createElement(DashboardHeader, {
36
+ filters: null,
37
+ lastUpdatedAt: null,
38
+ loading: false,
39
+ locale: "pt-BR",
40
+ onLocaleChange() {},
41
+ onRefresh() {},
42
+ title: "Dashboard ZAIP AIOS",
43
+ ...props,
44
+ }),
45
+ );
46
+ }
47
+
19
48
  function cssRule(source, selector) {
20
49
  const match = [...source.matchAll(/([^{}]+)\{([^}]*)\}/g)].find((candidate) =>
21
50
  candidate[1]?.split(",").some((item) => item.trim() === selector),
@@ -7,7 +7,7 @@ const zaipLogoUrl = new URL("../../../../config/src/logo_novo_azul.png", import.
7
7
  export function DashboardHeader({
8
8
  filters,
9
9
  filtersActive = false,
10
- lastUpdatedAt,
10
+ snapshotUpdatedAt = null,
11
11
  loading,
12
12
  locale,
13
13
  onLocaleChange,
@@ -19,6 +19,7 @@ export function DashboardHeader({
19
19
  filters: ReactNode;
20
20
  filtersActive?: boolean;
21
21
  lastUpdatedAt: string | null;
22
+ snapshotUpdatedAt?: string | null;
22
23
  loading: boolean;
23
24
  locale: DashboardHeaderLocale;
24
25
  onLocaleChange: (locale: DashboardHeaderLocale) => void;
@@ -38,8 +39,8 @@ export function DashboardHeader({
38
39
  refresh: "Atualizar",
39
40
  refreshing: "Atualizando…",
40
41
  settings: "Configurações",
41
- updated: "Última telemetria",
42
- waiting: "Sem telemetria medida",
42
+ updated: "Última Atualização",
43
+ waiting: "Atualização indisponível",
43
44
  }
44
45
  : {
45
46
  filters: "Filters",
@@ -47,13 +48,13 @@ export function DashboardHeader({
47
48
  refresh: "Refresh",
48
49
  refreshing: "Refreshing…",
49
50
  settings: "Settings",
50
- updated: "Last telemetry",
51
- waiting: "No measured telemetry",
51
+ updated: "Last Updated",
52
+ waiting: "Update unavailable",
52
53
  };
53
54
  const updatedLabel =
54
- lastUpdatedAt === null
55
+ snapshotUpdatedAt === null
55
56
  ? copy.waiting
56
- : `${copy.updated} ${new Intl.DateTimeFormat(locale, { dateStyle: "short", timeStyle: "medium" }).format(new Date(lastUpdatedAt))}`;
57
+ : `${copy.updated} ${new Intl.DateTimeFormat(locale, { dateStyle: "short", timeStyle: "medium" }).format(new Date(snapshotUpdatedAt))}`;
57
58
  const changeLocale = (next: DashboardHeaderLocale) => {
58
59
  onLocaleChange(next);
59
60
  setLocaleOpen(false);