@zaip-ai/cli 4.4.0 → 4.6.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 (214) hide show
  1. package/README.md +2 -0
  2. package/dist/services/adapters.d.ts +1 -1
  3. package/dist/services/adapters.js +4 -4
  4. package/dist/services/adapters.js.map +1 -1
  5. package/dist/services/codex-conversation-lifecycle-hook.d.ts.map +1 -1
  6. package/dist/services/codex-conversation-lifecycle-hook.js +24 -5
  7. package/dist/services/codex-conversation-lifecycle-hook.js.map +1 -1
  8. package/dist/services/codex-native-tool-authority-template.d.ts.map +1 -1
  9. package/dist/services/codex-native-tool-authority-template.js +188 -9
  10. package/dist/services/codex-native-tool-authority-template.js.map +1 -1
  11. package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
  12. package/dist/services/codex-pre-tool-hook-template.js +50 -128
  13. package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
  14. package/dist/services/codex-pre-tool-hook-template.test.js +4 -0
  15. package/dist/services/codex-pre-tool-hook-template.test.js.map +1 -1
  16. package/dist/services/codex-pre-tool-support-template.d.ts.map +1 -1
  17. package/dist/services/codex-pre-tool-support-template.js +1 -4
  18. package/dist/services/codex-pre-tool-support-template.js.map +1 -1
  19. package/dist/services/codex-stale-git-lock-template.d.ts +2 -0
  20. package/dist/services/codex-stale-git-lock-template.d.ts.map +1 -0
  21. package/dist/services/codex-stale-git-lock-template.js +64 -0
  22. package/dist/services/codex-stale-git-lock-template.js.map +1 -0
  23. package/dist/services/global-codex-config-migration.test.js +1 -4
  24. package/dist/services/global-codex-config-migration.test.js.map +1 -1
  25. package/dist/services/install-template-policy.d.ts.map +1 -1
  26. package/dist/services/install-template-policy.js +2 -0
  27. package/dist/services/install-template-policy.js.map +1 -1
  28. package/dist/services/logger.js +2 -1
  29. package/dist/services/logger.js.map +1 -1
  30. package/dist/services/logger.test.js +71 -0
  31. package/dist/services/logger.test.js.map +1 -1
  32. package/dist/services/runtime-template-test-support.d.ts +3 -0
  33. package/dist/services/runtime-template-test-support.d.ts.map +1 -0
  34. package/dist/services/runtime-template-test-support.js +32 -0
  35. package/dist/services/runtime-template-test-support.js.map +1 -0
  36. package/dist/services/runtime-template.test.js +23 -31
  37. package/dist/services/runtime-template.test.js.map +1 -1
  38. package/package.json +1 -1
  39. package/templates/.agents/README.md +8 -0
  40. package/templates/.agents/config/agents/discovery.json +1 -1
  41. package/templates/.agents/config/deploy-policy.json +5 -0
  42. package/templates/.agents/dashboard/.zaip-managed.json +211 -75
  43. package/templates/.agents/dashboard/README.md +2 -0
  44. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-f4ZGJRg3.js → CostCalculationGuidePage-BRtiPijr.js} +1 -1
  45. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-CkQ_y8kg.js → DashboardHelpDialog-BR4ZeYpE.js} +99 -10
  46. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-v9zGRTlk.js → PricingSettingsDialog-Dr4QDFmn.js} +1 -1
  47. package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-doIMhei7.js → ProjectSettingsPage-BG0N5dw_.js} +1 -1
  48. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-C_4ftjyB.js → TraceabilityWorkspace-BvoM-YQ0.js} +1 -1
  49. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-fTQhSL85.js +68 -0
  50. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-Cv3WBFIF.js → icons-vendor-Bc6vux5t.js} +1 -1
  51. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-BMfzKaAR.js +2 -0
  52. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DZhppOIk.css +1 -0
  53. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-mHgsmxb6.js → react-vendor-DNJQFov3.js} +1 -1
  54. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-B0CPyEVq.js → traceability-B7tjIB7W.js} +1 -1
  55. package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
  56. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  57. package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +6 -34
  58. package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.dom.test.ts +130 -0
  59. package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.tsx +226 -0
  60. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-sqlite-view.ts +45 -0
  61. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +15 -0
  62. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +17 -46
  63. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-fixtures.ts +23 -0
  64. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.test.ts +69 -0
  65. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.ts +59 -0
  66. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback.css +131 -0
  67. package/templates/.agents/dashboard/apps/frontend/src/styles.css +0 -32
  68. package/templates/.agents/dashboard/apps/frontend/src/use-dashboard-app-model.tsx +2 -2
  69. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  70. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +2 -2
  71. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +1 -1
  72. package/templates/.agents/dashboard/modules/operations/package.json +1 -1
  73. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +1 -4
  74. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.navigation.test.ts +236 -0
  75. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +1 -24
  76. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +3 -2
  77. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  78. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  79. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  80. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  81. package/templates/.agents/dashboard/packages/local-runtime/DIAGNOSTICS.md +42 -0
  82. package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.d.mts +10 -0
  83. package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.mjs +101 -0
  84. package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.test.mjs +58 -0
  85. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +232 -217
  86. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +201 -188
  87. package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
  88. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-dashboard-startup.mjs +233 -0
  89. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-provider-nonzero.mjs +45 -0
  90. package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-executable-startup.mjs +107 -0
  91. package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-reconciliation-executable.mjs +271 -0
  92. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +232 -217
  93. package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.test.ts +78 -12
  94. package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.ts +36 -56
  95. package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.test.ts +125 -0
  96. package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.ts +168 -0
  97. package/templates/.agents/dashboard/packages/local-runtime/src/dashboard-diagnostics.test.ts +57 -0
  98. package/templates/.agents/dashboard/packages/local-runtime/src/server-agent-catalog-routes.ts +76 -0
  99. package/templates/.agents/dashboard/packages/local-runtime/src/server-dual-reads-manifest-summaries-without-losing.test.ts +5 -0
  100. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-diagnostics.test.ts +118 -0
  101. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts +35 -6
  102. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-route.ts +105 -0
  103. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.test.ts +64 -9
  104. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +19 -27
  105. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-command.test.ts +111 -0
  106. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.test.ts +59 -0
  107. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.ts +44 -0
  108. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +20 -0
  109. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +12 -7
  110. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +48 -6
  111. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +2 -2
  112. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +126 -134
  113. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +14 -2
  114. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.test.ts +163 -0
  115. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.ts +78 -0
  116. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-header-timestamp.test.ts +31 -0
  117. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-nonzero.test.ts +271 -0
  118. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-visibility.ts +32 -0
  119. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-operations-duration.ts +58 -0
  120. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-provider-dimension.ts +12 -3
  121. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-view-page.ts +9 -58
  122. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-rebuild.ts +9 -35
  123. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-schema.ts +10 -8
  124. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +43 -45
  125. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +5 -9
  126. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +5 -1
  127. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-startup-seed.test.ts +87 -0
  128. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-logging.mjs +6 -2
  129. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +7 -1
  130. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +4 -5
  131. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  132. package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.test.mjs +33 -4
  133. package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.tsx +8 -7
  134. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +46 -3
  135. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  136. package/templates/.agents/dashboard/tests/e2e/qualify-kanban-popup.mjs +56 -0
  137. package/templates/.agents/dashboard/tests/e2e/specs/dashboard-update-label.spec.ts +49 -0
  138. package/templates/.agents/design/.zaip-managed.json +17 -5
  139. package/templates/.agents/design/apps/catalog/dist/assets/index-D3uhpycC.js +72 -0
  140. package/templates/.agents/design/apps/catalog/dist/index.html +1 -1
  141. package/templates/.agents/design/apps/catalog/package.json +1 -1
  142. package/templates/.agents/design/apps/catalog/src/pages/ComponentsPage.tsx +13 -1
  143. package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.stories.tsx +6 -0
  144. package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.tsx +101 -0
  145. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  146. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  147. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
  148. package/templates/.agents/requirements/comparative-audit.md +1 -1
  149. package/templates/.agents/requirements/context-engine.md +1 -1
  150. package/templates/.agents/requirements/cost-observability.md +1 -1
  151. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  152. package/templates/.agents/requirements/data-export.md +1 -1
  153. package/templates/.agents/requirements/design-system.md +1 -1
  154. package/templates/.agents/requirements/execution-engine.md +1 -1
  155. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  156. package/templates/.agents/requirements/model-orchestration.md +21 -6
  157. package/templates/.agents/requirements/public-distribution.md +4 -4
  158. package/templates/.agents/requirements/software-architecture.md +1 -1
  159. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  160. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  161. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  162. package/templates/.agents/rules/core.md +3 -1
  163. package/templates/.agents/rules/workflow.md +3 -2
  164. package/templates/.agents/runtime/VERSION +1 -1
  165. package/templates/.agents/runtime/bin/zaip.mjs +705 -516
  166. package/templates/.agents/runtime/checksums.sha256 +5 -5
  167. package/templates/.agents/runtime/manifest.json +1 -1
  168. package/templates/.agents/runtime/provenance.json +4 -4
  169. package/templates/.agents/runtime/sbom.cdx.json +1 -1
  170. package/templates/.agents/skills/zaip-skill-config/SKILL.md +25 -3
  171. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +4 -0
  172. package/templates/.agents/skills/zaip-skill-execution/references/autonomous-continuation.md +4 -0
  173. package/templates/.agents/skills/zaip-skill-execution/references/candidate-workflow.md +1 -1
  174. package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +2 -2
  175. package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +1 -0
  176. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +36 -48
  177. package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
  178. package/templates/.agents/skills/zaip-skill-github-deploy/references/deployment-workflow.md +67 -99
  179. package/templates/.agents/skills/zaip-skill-github-deploy/references/release-identity.md +33 -30
  180. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.mjs +303 -0
  181. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +334 -0
  182. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +41 -28
  183. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/guarded-commits.mjs +212 -0
  184. package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +4 -0
  185. package/templates/.agents/skills/zaip-skill-prd/SKILL.md +4 -0
  186. package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +44 -68
  187. package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +91 -44
  188. package/templates/.agents/skills/zaip-skill-send-email/SKILL.md +15 -0
  189. package/templates/.agents/skills/zaip-skill-send-email/agents/openai.yaml +4 -0
  190. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -2
  191. package/templates/.agents/skills/zaip-skill-spec/SKILL.md +4 -0
  192. package/templates/.agents/skills/zaip-skill-task/SKILL.md +4 -0
  193. package/templates/.agents/skills/zaip-skill-testing-strategy/SKILL.md +19 -0
  194. package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +2 -0
  195. package/templates/.agents/skills/zaip-skill-use-connectors/SKILL.md +15 -0
  196. package/templates/.agents/skills/zaip-skill-use-connectors/agents/openai.yaml +4 -0
  197. package/templates/.agents/skills/zaip-skill-use-connectors/scripts/connector-guidance.test.mjs +29 -0
  198. package/templates/.agents/skills/zaip-skill-use-connectors/scripts/verify-connector-lock.mjs +30 -0
  199. package/templates/.agents/tools/cloud-runner.integration.test.mjs +3 -0
  200. package/templates/.agents/zaip-aios/VERSION +1 -1
  201. package/templates/.agents/zaip-aios/guides/03-tasks.md +6 -0
  202. package/templates/.agents/zaip-aios/guides/04-specs.md +6 -0
  203. package/templates/.agents/zaip-aios/guides/05-prds.md +4 -0
  204. package/templates/.agents/zaip-aios/guides/06-execucao.md +6 -0
  205. package/templates/.agents/zaip-aios/guides/16-lifecycle-e-reconciliacao.md +6 -0
  206. package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +4 -0
  207. package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +6 -0
  208. package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +36 -4
  209. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +15 -4
  210. package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +8 -0
  211. package/templates/template-manifest.json +330 -142
  212. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-D7PrDZy9.js +0 -68
  213. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-OBJXZOwe.css +0 -1
  214. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Rbmpz4UO.js +0 -2
@@ -0,0 +1,78 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { rebuildSqliteReadModel } from "./sqlite-read-model-rebuild.js";
3
+ import {
4
+ hasReusableSnapshotPayloads,
5
+ sqliteReadModelPath,
6
+ validateSqliteReadModelSchema,
7
+ } from "./sqlite-read-model-schema.js";
8
+ import type { SqliteRebuildOptions } from "./sqlite-rebuild-fingerprint-options.js";
9
+ import type { SqliteRebuildResult } from "./sqlite-rebuild-result.js";
10
+
11
+ export type SqliteDashboardStartupOptions = Pick<
12
+ SqliteRebuildOptions,
13
+ "forceCanonical" | "sourceRoots"
14
+ >;
15
+
16
+ function hasReadableFirstFoldPayloads(database: DatabaseSync): boolean {
17
+ const rows = database
18
+ .prepare(`SELECT payload_json FROM zaip_aios_activities
19
+ WHERE payload_type IN ('first_fold_viewport_cache', 'first_fold_all_time_cache',
20
+ 'first_fold_all_time_board_cache', 'cost_monitor_summary_cache',
21
+ 'cost_monitor_summary_month_cache', 'first_fold_monthly_quick_cache')`)
22
+ .all() as Array<{ payload_json: string }>;
23
+ return rows.every(({ payload_json }) => {
24
+ const payload: unknown = JSON.parse(payload_json);
25
+ return payload !== null && typeof payload === "object" && !Array.isArray(payload);
26
+ });
27
+ }
28
+
29
+ /**
30
+ * Startup validates only the committed database. Canonical source discovery
31
+ * belongs to the asynchronous refresh after the server starts listening.
32
+ * Even an unchanged seed is stale until that refresh proves its freshness.
33
+ */
34
+ export async function prepareDashboardSqliteStartup(
35
+ workspaceRoot: string,
36
+ options: SqliteDashboardStartupOptions = {},
37
+ ): Promise<SqliteRebuildResult> {
38
+ const path = sqliteReadModelPath(workspaceRoot);
39
+ if (!options.forceCanonical) {
40
+ try {
41
+ validateSqliteReadModelSchema(path);
42
+ const database = new DatabaseSync(path, { readOnly: true });
43
+ try {
44
+ if (hasReusableSnapshotPayloads(database) && hasReadableFirstFoldPayloads(database)) {
45
+ const row = database
46
+ .prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key='source_file_count'")
47
+ .get() as { value?: unknown } | undefined;
48
+ const files =
49
+ typeof row?.value === "string" && /^\d+$/.test(row.value)
50
+ ? Number(row.value)
51
+ : Number.NaN;
52
+ if (Number.isSafeInteger(files) && files >= 0) {
53
+ return {
54
+ files,
55
+ path,
56
+ cache: "stale_seed",
57
+ fingerprint_ms: 0,
58
+ materialize_ms: 0,
59
+ refresh: {
60
+ reused_sources: 0,
61
+ processed_sources: 0,
62
+ added_sources: 0,
63
+ changed_sources: 0,
64
+ removed_sources: 0,
65
+ canonical_runs_refolded: 0,
66
+ },
67
+ };
68
+ }
69
+ }
70
+ } finally {
71
+ database.close();
72
+ }
73
+ } catch {
74
+ // Missing, damaged, incompatible or incomplete databases are never served.
75
+ }
76
+ }
77
+ return rebuildSqliteReadModel(workspaceRoot, { ...options, preferExisting: false });
78
+ }
@@ -0,0 +1,31 @@
1
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { expect, it } from "vitest";
4
+ import { rebuildSqliteReadModel, SqliteReadModelFileSystem } from "./sqlite-read-model.js";
5
+ import { prepareMultiProviderFixture } from "./sqlite-read-model-multi-provider-fixture.js";
6
+
7
+ it("status-only reconciliation promotes the snapshot without faking a new token collection time", async () => {
8
+ const base = resolve(import.meta.dirname, "../../../../../../.tmp/dashboard-header-time");
9
+ await mkdir(base, { recursive: true });
10
+ const root = await mkdtemp(join(base, "fixture-"));
11
+ let filesystem: SqliteReadModelFileSystem | undefined;
12
+ try {
13
+ await prepareMultiProviderFixture(root);
14
+ await rebuildSqliteReadModel(root);
15
+ filesystem = new SqliteReadModelFileSystem(root);
16
+ const collected = filesystem.latestTokenCollectedAt();
17
+ const snapshot = filesystem.projectionUpdatedAt();
18
+ expect(collected).toBe("2026-08-04T16:00:01.000Z");
19
+ await mkdir(join(root, ".agents/records/tasks"), { recursive: true });
20
+ await writeFile(
21
+ join(root, ".agents/records/tasks/TASK-STATUS.md"),
22
+ "---\nschema_version: 2\nid: TASK-STATUS\ntitle: Status only\nstatus: blocked\ncreated_at: 2026-09-29T10:00:00Z\n---\n# Status\n",
23
+ );
24
+ await filesystem.rebuildAndRefresh(() => rebuildSqliteReadModel(root));
25
+ expect(filesystem.latestTokenCollectedAt()).toBe(collected);
26
+ expect(filesystem.projectionUpdatedAt()).not.toBe(snapshot);
27
+ } finally {
28
+ filesystem?.close();
29
+ await rm(root, { recursive: true, force: true });
30
+ }
31
+ });
@@ -0,0 +1,271 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { afterEach, describe, expect, it } from "vitest";
3
+ import { hasVisibleProviderMetrics, providerVisibleMetrics } from "./sqlite-provider-visibility.js";
4
+ import { readCachedCostViewPage } from "./sqlite-query-cached-cost-page.js";
5
+ import type { SqliteQueryContext } from "./sqlite-query-context.js";
6
+ import { queryUncachedViewPage } from "./sqlite-query-view-page.js";
7
+
8
+ const databases: DatabaseSync[] = [];
9
+ afterEach(() => {
10
+ while (databases.length) databases.pop()?.close();
11
+ });
12
+
13
+ function fixture() {
14
+ const database = new DatabaseSync(":memory:");
15
+ databases.push(database);
16
+ database.exec(`
17
+ CREATE TABLE invocations (
18
+ agent_invocation_id TEXT, task_id TEXT, duration_ms REAL, source_event_count REAL,
19
+ input_tokens REAL, cached_input_tokens REAL, output_tokens REAL, reasoning_tokens REAL,
20
+ total_tokens REAL, cost_usd REAL, cost_brl REAL, source_started_at TEXT,
21
+ source_occurred_at TEXT, occurred_at TEXT, zaip_agent TEXT, phase TEXT,
22
+ status TEXT, work_type TEXT, prd_id TEXT, spec_id TEXT
23
+ );
24
+ CREATE TABLE tasks (id TEXT, status TEXT, priority TEXT, risk TEXT);
25
+ CREATE TABLE cost_invocation_dimension_facts (
26
+ agent_invocation_id TEXT, provider TEXT, model TEXT, input_tokens REAL,
27
+ cached_input_tokens REAL, output_tokens REAL, reasoning_tokens REAL,
28
+ total_tokens REAL, cost_usd REAL, cost_brl REAL, occurred_day_sp TEXT
29
+ );
30
+ CREATE TABLE zaip_aios_activities (
31
+ payload_type TEXT, payload_json TEXT, activity_source_kind TEXT,
32
+ agent_invocation_id TEXT, task_id TEXT, duration_ms REAL, source_event_count REAL,
33
+ input_tokens REAL, cached_input_tokens REAL, output_tokens REAL, reasoning_tokens REAL,
34
+ total_tokens REAL, cost_usd REAL, cost_brl REAL, occurred_day_sp TEXT
35
+ );
36
+ `);
37
+ const context = {
38
+ database,
39
+ technicalPayloadRows: () => [],
40
+ } as unknown as SqliteQueryContext;
41
+ const payload = (row: Record<string, unknown>) =>
42
+ database
43
+ .prepare("INSERT INTO zaip_aios_activities(payload_type,payload_json) VALUES (?,?)")
44
+ .run("cost_dimension_aggregate_v1", JSON.stringify({ dimension: "provider", ...row }));
45
+ const invocation = (id: string, tokens: number, duration: number | null = null) => {
46
+ database
47
+ .prepare(`INSERT INTO invocations
48
+ (agent_invocation_id,total_tokens,cost_usd,cost_brl,duration_ms,source_event_count,
49
+ source_started_at,phase) VALUES (?,?,0,0,?,9,'2026-09-29T12:00:00Z','execution')`)
50
+ .run(id, tokens, duration);
51
+ database
52
+ .prepare(`INSERT INTO zaip_aios_activities
53
+ (activity_source_kind,agent_invocation_id,total_tokens,cost_usd,cost_brl,
54
+ duration_ms,source_event_count,occurred_day_sp) VALUES ('agent_run',?,?,0,0,?,9,'2026-09-29')`)
55
+ .run(id, tokens, duration);
56
+ };
57
+ const segment = (id: string, provider: string | null, tokens: number) =>
58
+ database
59
+ .prepare(`INSERT INTO cost_invocation_dimension_facts
60
+ (agent_invocation_id,provider,model,total_tokens,cost_usd,cost_brl,occurred_day_sp)
61
+ VALUES (?,?,'model',?,0,0,'2026-09-29')`)
62
+ .run(id, provider, tokens);
63
+ return { database, context, payload, invocation, segment };
64
+ }
65
+
66
+ const empty = {
67
+ invocation_count: 0,
68
+ task_count: 0,
69
+ total_duration: null,
70
+ total_tokens: 0,
71
+ total_cost_usd: 0,
72
+ total_cost_brl: null,
73
+ };
74
+
75
+ describe("provider visibility", () => {
76
+ it.each(
77
+ providerVisibleMetrics,
78
+ )("retains activity in %s independently of cost or invocation", (field) => {
79
+ const { context, payload } = fixture();
80
+ payload({ key: "unavailable", ...empty });
81
+ payload({ key: "active", ...empty, [field]: 1 });
82
+ const page = queryUncachedViewPage(context, "cost_monitor_providers", {});
83
+ expect(page.rows.map((row) => row.key)).toEqual(["active"]);
84
+ expect(page.total_count).toBe(1);
85
+ expect(page.rows[0]?.[field]).toBe(1);
86
+ });
87
+
88
+ it("ignores unavailable/zero metrics and hidden metrics, retaining negative adjustments", () => {
89
+ expect(hasVisibleProviderMetrics({ ...empty, source_event_count: 9 })).toBe(false);
90
+ expect(hasVisibleProviderMetrics({})).toBe(false);
91
+ expect(hasVisibleProviderMetrics({ ...empty, total_cost_usd: -1 })).toBe(true);
92
+ expect(hasVisibleProviderMetrics({ ...empty, total_cost_usd: 0.0049 })).toBe(false);
93
+ expect(hasVisibleProviderMetrics({ ...empty, total_cost_usd: 0.005 })).toBe(true);
94
+ expect(hasVisibleProviderMetrics({ ...empty, total_cost_brl: -0.005 })).toBe(true);
95
+ });
96
+
97
+ it("hides sub-cent residual costs from payload while preserving raw financial totals", () => {
98
+ const { context, payload } = fixture();
99
+ const residual = {
100
+ total_cost_usd: 0.00004640000001435857,
101
+ total_cost_brl: 0.000010780000006496704,
102
+ };
103
+ payload({ key: "unavailable", ...empty, ...residual });
104
+ const page = queryUncachedViewPage(context, "cost_monitor_providers", {});
105
+ expect(page).toMatchObject({ total_count: 0, rows: [], has_more: false });
106
+ expect(page.totals).toMatchObject(residual);
107
+ });
108
+
109
+ it.each([
110
+ { phase: "execution" },
111
+ { from: "2026-09-29" },
112
+ ])("hides SQL residual-cost-only rows at displayed precision: %j", (input) => {
113
+ const { context, invocation, segment, database } = fixture();
114
+ invocation("residual", 0);
115
+ segment("residual", null, 0);
116
+ database.exec(`
117
+ UPDATE invocations SET cost_usd=0.00004640000001435857,cost_brl=0.000010780000006496704;
118
+ UPDATE zaip_aios_activities SET cost_usd=0.00004640000001435857,cost_brl=0.000010780000006496704;
119
+ UPDATE cost_invocation_dimension_facts SET cost_usd=0.00004640000001435857,cost_brl=0.000010780000006496704;
120
+ `);
121
+ const page = queryUncachedViewPage(context, "cost_monitor_providers", input);
122
+ expect(page).toMatchObject({ total_count: 0, rows: [], has_more: false });
123
+ expect(page.totals).toMatchObject({
124
+ total_cost_usd: 0.00004640000001435857,
125
+ total_cost_brl: 0.000010780000006496704,
126
+ });
127
+ });
128
+
129
+ it("filters payload before pagination without changing factual totals or null coverage", () => {
130
+ const { context, payload } = fixture();
131
+ payload({ key: "unavailable", ...empty, source_event_count: 9 });
132
+ payload({ key: "known-empty", ...empty });
133
+ payload({ key: "active", ...empty, total_tokens: 7 });
134
+ const page = queryUncachedViewPage(context, "cost_monitor_providers", { limit: 1 });
135
+ expect(page).toMatchObject({ total_count: 1, has_more: false });
136
+ expect(page.rows).toEqual([
137
+ expect.objectContaining({ key: "active", total_duration: null, total_cost_brl: null }),
138
+ ]);
139
+ expect(page.totals).toMatchObject({
140
+ total_tokens: 7,
141
+ total_cost_usd: 0,
142
+ total_cost_brl: null,
143
+ total_duration: null,
144
+ source_event_count: 9,
145
+ });
146
+ const next = queryUncachedViewPage(context, "cost_monitor_providers", { limit: 1, offset: 1 });
147
+ expect(next).toMatchObject({ total_count: 1, rows: [], has_more: false });
148
+ });
149
+
150
+ it("retains genuine unattributed payload activity and returns a coherent empty page", () => {
151
+ const { context, payload } = fixture();
152
+ payload({ key: "unavailable", ...empty });
153
+ expect(queryUncachedViewPage(context, "cost_monitor_providers", {})).toMatchObject({
154
+ total_count: 0,
155
+ rows: [],
156
+ has_more: false,
157
+ });
158
+ payload({ key: null, ...empty, total_tokens: 3 });
159
+ expect(queryUncachedViewPage(context, "cost_monitor_providers", {}).rows).toEqual([
160
+ expect.objectContaining({ key: null, total_tokens: 3 }),
161
+ ]);
162
+ expect(readCachedCostViewPage(context, "cost_monitor_providers", {})).toBeNull();
163
+ });
164
+
165
+ it.each([
166
+ { phase: "execution" },
167
+ { from: "2026-09-29", to: "2026-09-29" },
168
+ ])("filters SQL before count and paging for %j while retaining factual populations", (input) => {
169
+ const { context, invocation, segment, database } = fixture();
170
+ invocation("mixed", 10, 100);
171
+ segment("mixed", "openai", 10);
172
+ segment("mixed", "empty-known", 0);
173
+ segment("mixed", null, 0);
174
+ invocation("unattributed", 7);
175
+ segment("unattributed", null, 7);
176
+ invocation("free", 0);
177
+ segment("free", "free-provider", 0);
178
+ const factsBefore = database.prepare("SELECT * FROM cost_invocation_dimension_facts").all();
179
+ const page = queryUncachedViewPage(context, "cost_monitor_providers", { ...input, limit: 2 });
180
+ expect(page).toMatchObject({ total_count: 3, has_more: true });
181
+ expect(page.rows.map((row) => row.key)).toEqual(["free-provider", "openai"]);
182
+ expect(page.rows[0]).toMatchObject({ invocation_count: 1, total_tokens: 0, total_cost_usd: 0 });
183
+ expect(page.rows[1]).toMatchObject({
184
+ invocation_count: 0,
185
+ total_tokens: 10,
186
+ total_cost_brl: 0,
187
+ total_duration: null,
188
+ });
189
+ expect(page.totals).toMatchObject({
190
+ invocation_count: 3,
191
+ total_tokens: 17,
192
+ total_duration: 100,
193
+ total_cost_usd: 0,
194
+ source_event_count: 27,
195
+ });
196
+ const next = queryUncachedViewPage(context, "cost_monitor_providers", {
197
+ ...input,
198
+ limit: 2,
199
+ offset: 2,
200
+ });
201
+ expect(next).toMatchObject({ total_count: 3, has_more: false });
202
+ expect(next.rows).toEqual([
203
+ expect.objectContaining({ key: "unavailable", invocation_count: 0, total_tokens: 7 }),
204
+ ]);
205
+ expect(next.totals).toEqual(page.totals);
206
+ expect(database.prepare("SELECT * FROM cost_invocation_dimension_facts").all()).toEqual(
207
+ factsBefore,
208
+ );
209
+ });
210
+
211
+ it.each([
212
+ { phase: "execution" },
213
+ { from: "2026-09-29" },
214
+ ])("returns empty SQL page for only zero/null rows: %j", (input) => {
215
+ const { context, invocation, segment } = fixture();
216
+ invocation("zero", 0);
217
+ segment("zero", null, 0);
218
+ expect(queryUncachedViewPage(context, "cost_monitor_providers", input)).toMatchObject({
219
+ total_count: 0,
220
+ rows: [],
221
+ has_more: false,
222
+ totals: { invocation_count: 1, total_tokens: 0, total_cost_usd: 0 },
223
+ });
224
+ });
225
+
226
+ it.each([
227
+ { phase: "execution" },
228
+ { from: "2026-09-29" },
229
+ ])("retains zero-cost task-only and meaningful unattributed cost in SQL: %j", (input) => {
230
+ const { context, invocation, segment, database } = fixture();
231
+ invocation("task-only", 0);
232
+ segment("task-only", "provider-a", 0);
233
+ segment("task-only", "provider-b", 0);
234
+ database.exec(
235
+ "UPDATE invocations SET task_id='TASK-1'; UPDATE zaip_aios_activities SET task_id='TASK-1'",
236
+ );
237
+ invocation("cost-only", 0);
238
+ segment("cost-only", null, 0);
239
+ database.exec(`
240
+ UPDATE invocations SET cost_usd=0.005 WHERE agent_invocation_id='cost-only';
241
+ UPDATE zaip_aios_activities SET cost_usd=0.005 WHERE agent_invocation_id='cost-only';
242
+ UPDATE cost_invocation_dimension_facts SET cost_usd=0.005 WHERE agent_invocation_id='cost-only';
243
+ `);
244
+ const page = queryUncachedViewPage(context, "cost_monitor_providers", input);
245
+ expect(page.total_count).toBe(3);
246
+ expect(page.rows).toEqual([
247
+ expect.objectContaining({
248
+ key: "provider-a",
249
+ invocation_count: 0,
250
+ task_count: 1,
251
+ total_tokens: 0,
252
+ total_cost_usd: 0,
253
+ }),
254
+ expect.objectContaining({
255
+ key: "provider-b",
256
+ invocation_count: 0,
257
+ task_count: 1,
258
+ total_tokens: 0,
259
+ total_cost_usd: 0,
260
+ }),
261
+ expect.objectContaining({
262
+ key: "unavailable",
263
+ invocation_count: 0,
264
+ task_count: 0,
265
+ total_tokens: 0,
266
+ total_cost_usd: 0.005,
267
+ }),
268
+ ]);
269
+ expect(page.totals).toMatchObject({ task_count: 1, total_cost_usd: 0.005 });
270
+ });
271
+ });
@@ -0,0 +1,32 @@
1
+ /** Only metrics displayed in Provedores determine whether a row has activity. */
2
+ export const providerVisibleMetrics = [
3
+ "invocation_count",
4
+ "task_count",
5
+ "total_duration",
6
+ "total_tokens",
7
+ "total_cost_usd",
8
+ "total_cost_brl",
9
+ ] as const;
10
+
11
+ // Costs are displayed with two decimal places; sub-cent reconciliation
12
+ // remainders remain factual totals but do not constitute visible activity.
13
+ const minimumVisibleCost = 0.005;
14
+
15
+ export function hasVisibleProviderMetrics(row: Record<string, unknown>): boolean {
16
+ return providerVisibleMetrics.some(
17
+ (field) =>
18
+ typeof row[field] === "number" &&
19
+ (field === "total_cost_usd" || field === "total_cost_brl"
20
+ ? Math.abs(row[field]) >= minimumVisibleCost
21
+ : row[field] !== 0),
22
+ );
23
+ }
24
+
25
+ /** Applied to aggregated rows, before COUNT, LIMIT and OFFSET. */
26
+ export const providerVisibleMetricsSql = providerVisibleMetrics
27
+ .map((field) =>
28
+ field === "total_cost_usd" || field === "total_cost_brl"
29
+ ? `ABS(COALESCE(${field}, 0)) >= ${minimumVisibleCost}`
30
+ : `COALESCE(${field}, 0) <> 0`,
31
+ )
32
+ .join(" OR ");
@@ -0,0 +1,58 @@
1
+ import type { SqliteViewPageInput } from "./sqlite-query-cached-cost-page.js";
2
+ import type { SqliteQueryContext } from "./sqlite-query-context.js";
3
+ import type { SqliteViewName } from "./sqlite-read-model-types.js";
4
+
5
+ /** Recompose coarse period rows from daily facts when a date range cuts through them. */
6
+ export function attachOperationsDuration(
7
+ context: SqliteQueryContext,
8
+ view: SqliteViewName,
9
+ input: SqliteViewPageInput,
10
+ rows: Array<Record<string, unknown>>,
11
+ ): void {
12
+ if (!["operations_daily", "operations_monthly", "operations_annual"].includes(view)) return;
13
+ const partialCoarse = view !== "operations_daily" && (input.from || input.to);
14
+ const partialFromDay = input.from?.slice(0, 10) ?? null;
15
+ const partialToDay = input.to?.slice(0, 10) ?? null;
16
+ const dailyRows = partialCoarse
17
+ ? (context.database
18
+ .prepare(
19
+ `SELECT * FROM operations_daily WHERE (? IS NULL OR day >= ?) AND (? IS NULL OR day <= ?)`,
20
+ )
21
+ .all(partialFromDay, partialFromDay, partialToDay, partialToDay) as Array<
22
+ Record<string, unknown>
23
+ >)
24
+ : [];
25
+ const daily = context.executionDurationByDay({
26
+ from: input.from ?? null,
27
+ to: input.to ?? null,
28
+ });
29
+ for (const row of rows) {
30
+ const key = String(row.day ?? row.period_key);
31
+ if (partialCoarse) {
32
+ const matching = dailyRows.filter((day) => String(day.day).startsWith(key));
33
+ const matchingDays = matching.map((day) => String(day.day)).sort();
34
+ row.start_day = matchingDays[0] ?? null;
35
+ row.end_day = matchingDays.at(-1) ?? null;
36
+ for (const column of [
37
+ "prd_count",
38
+ "spec_count",
39
+ "task_count",
40
+ "validation_count",
41
+ "evidence_count",
42
+ "total_run_count",
43
+ "invocation_count",
44
+ "llm_call_count",
45
+ "total_tokens",
46
+ "total_cost_usd",
47
+ "total_cost_brl",
48
+ ] as const)
49
+ row[column] = matching.reduce(
50
+ (sum, day) => sum + (typeof day[column] === "number" ? Number(day[column]) : 0),
51
+ 0,
52
+ );
53
+ }
54
+ row.operations_total_duration = [...daily]
55
+ .filter(([day]) => (view === "operations_daily" ? day === key : day.startsWith(key)))
56
+ .reduce((sum, [, duration]) => sum + duration, 0);
57
+ }
58
+ }
@@ -1,4 +1,5 @@
1
1
  import { costDimensionFactDateFilter } from "./sqlite-cost-agent-page.js";
2
+ import { providerVisibleMetricsSql } from "./sqlite-provider-visibility.js";
2
3
  import type { SqliteQueryContext, SqliteViewInput } from "./sqlite-query-context.js";
3
4
  import type { SqliteViewName } from "./sqlite-read-model-types.js";
4
5
  export function readProviderDimension(
@@ -131,13 +132,21 @@ export function readProviderDimension(
131
132
  ), task_population AS (
132
133
  SELECT provider_key AS key, COUNT(*) AS task_count
133
134
  FROM task_providers GROUP BY provider_key
134
- ), keys AS (SELECT key FROM financial UNION SELECT key FROM population UNION SELECT key FROM task_population)`;
135
+ ), keys AS (SELECT key FROM financial UNION SELECT key FROM population UNION SELECT key FROM task_population),
136
+ aggregated_rows AS (
137
+ SELECT keys.key, keys.key AS label, COALESCE(population.invocation_count,0) AS invocation_count,
138
+ COALESCE(task_population.task_count,0) AS task_count, population.total_duration,
139
+ population.source_event_count, financial.total_tokens, financial.total_cost_usd,
140
+ financial.total_cost_brl
141
+ FROM keys LEFT JOIN population USING(key) LEFT JOIN task_population USING(key)
142
+ LEFT JOIN financial USING(key)
143
+ ), visible_rows AS (SELECT * FROM aggregated_rows WHERE ${providerVisibleMetricsSql})`;
135
144
  const limit = Math.min(200, Math.max(1, input.limit ?? 50));
136
145
  const offset = Math.max(0, input.offset ?? 0);
137
146
  const total_count = Number(
138
147
  (
139
148
  context.database
140
- .prepare(`${ctes} ${facts} SELECT COUNT(*) AS count FROM keys`)
149
+ .prepare(`${ctes} ${facts} SELECT COUNT(*) AS count FROM visible_rows`)
141
150
  .get(...values) as { count: number }
142
151
  ).count,
143
152
  );
@@ -160,7 +169,7 @@ export function readProviderDimension(
160
169
  .get(...values) as Record<string, unknown>;
161
170
  const rows = context.database
162
171
  .prepare(
163
- `${ctes} ${facts} SELECT keys.key, keys.key AS label, COALESCE(population.invocation_count,0) AS invocation_count, COALESCE(task_population.task_count,0) AS task_count, population.total_duration, population.source_event_count, financial.total_tokens, financial.total_cost_usd, financial.total_cost_brl FROM keys LEFT JOIN population USING(key) LEFT JOIN task_population USING(key) LEFT JOIN financial USING(key) ORDER BY financial.total_cost_brl DESC, keys.key LIMIT ? OFFSET ?`,
172
+ `${ctes} ${facts} SELECT * FROM visible_rows ORDER BY total_cost_brl DESC, key LIMIT ? OFFSET ?`,
164
173
  )
165
174
  .all(...values, limit, offset) as Array<Record<string, unknown>>;
166
175
  return {
@@ -4,6 +4,7 @@ import {
4
4
  operationsWorkListPayloadPage,
5
5
  operationsWorkListSqlPage,
6
6
  } from "./sqlite-operations-work-list.js";
7
+ import { hasVisibleProviderMetrics } from "./sqlite-provider-visibility.js";
7
8
  import { readAgentDimension } from "./sqlite-query-agent-dimension.js";
8
9
  import {
9
10
  readCachedCostViewPage,
@@ -13,6 +14,7 @@ import type { SqliteQueryContext, SqliteViewPage } from "./sqlite-query-context.
13
14
  import { readEntityGroup } from "./sqlite-query-entity-group.js";
14
15
  import { readFilteredDimension } from "./sqlite-query-filtered-dimension.js";
15
16
  import { readModelDimension } from "./sqlite-query-model-dimension.js";
17
+ import { attachOperationsDuration } from "./sqlite-query-operations-duration.js";
16
18
  import { readProviderDimension } from "./sqlite-query-provider-dimension.js";
17
19
  import { readTemporalDimension } from "./sqlite-query-temporal-dimension.js";
18
20
  import { costMonitorFilterDay } from "./sqlite-read-model-canonical.js";
@@ -239,7 +241,7 @@ export function queryUncachedViewPage(
239
241
  const visiblePayloadRows =
240
242
  dimension === "model"
241
243
  ? payloadRows.filter((row) => row.key !== "unavailable" && row.key !== null)
242
- : payloadRows;
244
+ : payloadRows.filter(hasVisibleProviderMetrics);
243
245
  const limit = Math.min(200, Math.max(1, input.limit ?? 50));
244
246
  const offset = Math.max(0, input.offset ?? 0);
245
247
  const rows = visiblePayloadRows.slice(offset, offset + limit);
@@ -268,9 +270,11 @@ export function queryUncachedViewPage(
268
270
  for (const field of modelNumericFields)
269
271
  row[field] = typeof row[field] === "number" ? row[field] : 0;
270
272
  }
273
+ // Provider visibility must not change factual totals or known-zero coverage.
274
+ const totalRows = dimension === "provider" ? payloadRows : visiblePayloadRows;
271
275
  const nullableSum = (field: string) =>
272
- visiblePayloadRows.some((row) => typeof row[field] === "number")
273
- ? visiblePayloadRows.reduce(
276
+ totalRows.some((row) => typeof row[field] === "number")
277
+ ? totalRows.reduce(
274
278
  (sum, row) => sum + (typeof row[field] === "number" ? Number(row[field]) : 0),
275
279
  0,
276
280
  )
@@ -284,7 +288,7 @@ export function queryUncachedViewPage(
284
288
  total_count: visiblePayloadRows.length,
285
289
  has_more: offset + rows.length < visiblePayloadRows.length,
286
290
  totals: {
287
- invocation_count: visiblePayloadRows.reduce(
291
+ invocation_count: totalRows.reduce(
288
292
  (sum, row) => sum + Number(row.invocation_count ?? 0),
289
293
  0,
290
294
  ),
@@ -490,60 +494,7 @@ export function queryUncachedViewPage(
490
494
  const rows = context.database
491
495
  .prepare(`SELECT * FROM ${view}${where} ORDER BY ${meta.order} LIMIT ? OFFSET ?`)
492
496
  .all(...values, limit, offset) as Array<Record<string, unknown>>;
493
- if (
494
- view === "operations_daily" ||
495
- view === "operations_monthly" ||
496
- view === "operations_annual"
497
- ) {
498
- // A partial date interval intersects a coarse month/year row but does
499
- // not make its all-time additive metrics applicable. Recompose it from
500
- // the same daily facts used by Operations cards.
501
- const partialCoarse = view !== "operations_daily" && (input.from || input.to);
502
- const partialFromDay = input.from?.slice(0, 10) ?? null;
503
- const partialToDay = input.to?.slice(0, 10) ?? null;
504
- const dailyRows = partialCoarse
505
- ? (context.database
506
- .prepare(
507
- `SELECT * FROM operations_daily WHERE (? IS NULL OR day >= ?) AND (? IS NULL OR day <= ?)`,
508
- )
509
- .all(partialFromDay, partialFromDay, partialToDay, partialToDay) as Array<
510
- Record<string, unknown>
511
- >)
512
- : [];
513
- const daily = context.executionDurationByDay({
514
- from: input.from ?? null,
515
- to: input.to ?? null,
516
- });
517
- for (const row of rows) {
518
- const key = String(row.day ?? row.period_key);
519
- if (partialCoarse) {
520
- const matching = dailyRows.filter((day) => String(day.day).startsWith(key));
521
- const matchingDays = matching.map((day) => String(day.day)).sort();
522
- row.start_day = matchingDays[0] ?? null;
523
- row.end_day = matchingDays.at(-1) ?? null;
524
- for (const column of [
525
- "prd_count",
526
- "spec_count",
527
- "task_count",
528
- "validation_count",
529
- "evidence_count",
530
- "total_run_count",
531
- "invocation_count",
532
- "llm_call_count",
533
- "total_tokens",
534
- "total_cost_usd",
535
- "total_cost_brl",
536
- ] as const)
537
- row[column] = matching.reduce(
538
- (sum, day) => sum + (typeof day[column] === "number" ? Number(day[column]) : 0),
539
- 0,
540
- );
541
- }
542
- row.operations_total_duration = [...daily]
543
- .filter(([day]) => (view === "operations_daily" ? day === key : day.startsWith(key)))
544
- .reduce((sum, [, duration]) => sum + duration, 0);
545
- }
546
- }
497
+ attachOperationsDuration(context, view, input, rows);
547
498
  return {
548
499
  data_source: "sqlite_read_model" as const,
549
500
  view,