@zaip-ai/cli 1.3.8 → 1.4.2
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.
- package/README.md +58 -3
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +17 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/services/adapters.js +4 -4
- package/dist/services/adapters.js.map +1 -1
- package/dist/services/codex-config-template.d.ts +2 -2
- package/dist/services/codex-config-template.d.ts.map +1 -1
- package/dist/services/codex-config-template.js +3 -3
- package/dist/services/codex-config-template.js.map +1 -1
- package/dist/services/codex-rules.d.ts +1 -1
- package/dist/services/codex-rules.d.ts.map +1 -1
- package/dist/services/codex-rules.js +9 -24
- package/dist/services/codex-rules.js.map +1 -1
- package/dist/services/delivery-git-preflight.d.ts +23 -0
- package/dist/services/delivery-git-preflight.d.ts.map +1 -0
- package/dist/services/delivery-git-preflight.js +69 -0
- package/dist/services/delivery-git-preflight.js.map +1 -0
- package/dist/services/doctor.d.ts.map +1 -1
- package/dist/services/doctor.js +13 -1
- package/dist/services/doctor.js.map +1 -1
- package/dist/services/install-agents-template.d.ts.map +1 -1
- package/dist/services/install-agents-template.js +22 -28
- package/dist/services/install-agents-template.js.map +1 -1
- package/dist/services/install-profile.d.ts +8 -0
- package/dist/services/install-profile.d.ts.map +1 -0
- package/dist/services/install-profile.js +56 -0
- package/dist/services/install-profile.js.map +1 -0
- package/dist/services/logger.d.ts.map +1 -1
- package/dist/services/logger.js +6 -5
- package/dist/services/logger.js.map +1 -1
- package/dist/services/stack-git.d.ts +9 -0
- package/dist/services/stack-git.d.ts.map +1 -0
- package/dist/services/stack-git.js +188 -0
- package/dist/services/stack-git.js.map +1 -0
- package/dist/services/stack-plan.d.ts +3 -1
- package/dist/services/stack-plan.d.ts.map +1 -1
- package/dist/services/stack-plan.js +15 -1
- package/dist/services/stack-plan.js.map +1 -1
- package/dist/services/stack-terraform.d.ts +13 -0
- package/dist/services/stack-terraform.d.ts.map +1 -0
- package/dist/services/stack-terraform.js +274 -0
- package/dist/services/stack-terraform.js.map +1 -0
- package/dist/services/template-manifest.d.ts.map +1 -1
- package/dist/services/template-manifest.js +14 -1
- package/dist/services/template-manifest.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/.env.example +34 -0
- package/templates/{README.md → .agents/README.md} +124 -60
- package/templates/.agents/config/agents/catalog.yaml +23 -23
- package/templates/.agents/config/clients/codex/agents/zaip-agent-analyst.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-auditor.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-backend.toml +5 -4
- package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-builder.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-cloud-engineer.toml +10 -7
- package/templates/.agents/config/clients/codex/agents/zaip-agent-data-analyst.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-frontend.toml +5 -4
- package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-integrator.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +5 -4
- package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-refactoring-specialist.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-reviewer.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-security-auditor.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-task-executor.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-website-developer.toml +5 -4
- package/templates/.agents/config/clients/codex/agents/zaip-agent-writer.toml +1 -0
- package/templates/.agents/config/pricing/models.json +25 -1
- package/templates/.agents/config/project.yaml +2 -0
- package/templates/.agents/dashboard/.zaip-managed.json +208 -88
- package/templates/.agents/dashboard/DATA-DICTIONARY.md +1 -1
- package/templates/.agents/dashboard/README.md +58 -19
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-DwVZOmMV.js → CostCalculationGuidePage-snUCQYTC.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-VcLx9iOO.js → DashboardHelpDialog-oZVlf-l5.js} +72 -8
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-CyjMXkm8.js → PricingSettingsDialog-BPtVUGgs.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-3Awyaovx.js +3 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-DFZwIXSC.js → TraceabilityWorkspace-BYjVOHI_.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-DbaLbU2q.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DXBOtWNI.js → icons-vendor-TPUT2IUt.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Cmlx8OYH.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-ZJFXmK7m.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-DaNXQPyk.js → react-vendor-Ct48XHGL.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-I4AKqVDX.js → traceability-BDe4_Ubl.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
- package/templates/.agents/dashboard/apps/frontend/src/App.tsx +73 -67
- package/templates/.agents/dashboard/apps/frontend/src/AppLoadingBoundary.tsx +35 -7
- package/templates/.agents/dashboard/apps/frontend/src/AppWorkBoard.ts +55 -0
- package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +11 -0
- package/templates/.agents/dashboard/apps/frontend/src/app-loading-boundary.css +98 -0
- package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +5 -5
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-reconciliation.ts +33 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +75 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +7 -8
- package/templates/.agents/dashboard/apps/frontend/src/project-settings-model.ts +14 -2
- package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +35 -0
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +78 -2
- package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +28 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +8 -5
- package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +180 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +7 -4
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +13 -7
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +14 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTableDisplay.ts +211 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +152 -157
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +155 -142
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/InvocationManifestQuery.test.ts +145 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +7 -5
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +4 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +4 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +4 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +4 -4
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +79 -1
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +105 -11
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +482 -332
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +430 -285
- package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +14 -2
- package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +6 -26
- package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-persistent-launch.mjs +268 -0
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +482 -332
- package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +90 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +112 -22
- package/templates/.agents/dashboard/packages/local-runtime/src/server-agent-assets.ts +39 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-browser-lease.ts +33 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-refresh.test.ts +148 -11
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts +68 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.test.ts +19 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.ts +39 -43
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-revision.test.ts +42 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-revision.ts +26 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +34 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-startup.ts +30 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +89 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +88 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +68 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +11 -5
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +129 -99
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-agent-productivity.test.ts +111 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-agent-productivity.ts +140 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.test.ts +127 -4
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +145 -5
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-aggregates.ts +156 -23
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.ts +18 -9
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-projection.ts +52 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-persistence.ts +35 -13
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-force-canonical.test.ts +56 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-cache.test.ts +29 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-cache.ts +136 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-refresh.test.ts +251 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-projection.ts +22 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-cache.test-support.ts +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical-parsing.ts +42 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +793 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-duration.test.ts +270 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-fixture.ts +3 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider.test.ts +18 -4
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.test.ts +87 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +105 -16
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +131 -59
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +1000 -1115
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-cache.ts +13 -92
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-claims.test.mjs +79 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.mjs +27 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.mjs +6 -6
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-prerequisites.mjs +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-refresh.mjs +2 -10
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-runtime-selection.mjs +36 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-runtime-selection.test.mjs +63 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +142 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +262 -267
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +18 -29
- package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +6 -0
- package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
- package/templates/.agents/requirements/autonomous-assurance.md +1 -1
- package/templates/.agents/requirements/cloud-hostinger-autonomy.md +47 -0
- package/templates/.agents/requirements/comparative-audit.md +1 -1
- package/templates/.agents/requirements/context-engine.md +1 -1
- package/templates/.agents/requirements/cost-observability.md +1 -1
- package/templates/.agents/requirements/dashboard-launch.md +2 -2
- package/templates/.agents/requirements/data-export.md +1 -1
- package/templates/.agents/requirements/design-system.md +1 -1
- package/templates/.agents/requirements/execution-engine.md +1 -1
- package/templates/.agents/requirements/kanban-dashboard.md +1 -1
- package/templates/.agents/requirements/model-orchestration.md +1 -1
- package/templates/.agents/requirements/public-distribution.md +1 -1
- package/templates/.agents/requirements/software-architecture.md +1 -1
- package/templates/.agents/requirements/strategic-improvements.md +1 -1
- package/templates/.agents/requirements/work-lifecycle.md +1 -1
- package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
- package/templates/.agents/rules/autonomy-decision.md +1 -1
- package/templates/.agents/rules/cloud-hostinger-autonomy.md +21 -0
- package/templates/.agents/rules/core.md +5 -4
- package/templates/.agents/rules/local-sqlite-storage.md +18 -0
- package/templates/.agents/rules/meta-mcp.md +21 -0
- package/templates/.agents/rules/workflow.md +1 -0
- package/templates/.agents/runtime/bin/zaip.mjs +305 -137
- package/templates/.agents/runtime/checksums.sha256 +2 -2
- package/templates/.agents/runtime/provenance.json +2 -2
- package/templates/.agents/skills/zaip-skill-api-to-mcp/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +25 -9
- package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +2 -2
- package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +2 -2
- package/templates/.agents/skills/zaip-skill-change/references/observable-evidence.md +15 -0
- package/templates/.agents/skills/zaip-skill-dashboard-sqlite/SKILL.md +13 -5
- package/templates/.agents/skills/zaip-skill-data-analyst/SKILL.md +10 -5
- package/templates/.agents/skills/zaip-skill-execution/SKILL.md +5 -4
- package/templates/.agents/skills/zaip-skill-frontend-launcher/SKILL.md +4 -3
- package/templates/.agents/skills/zaip-skill-frontend-launcher/references/launcher-contract.md +5 -3
- package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +5 -5
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.mjs +78 -13
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.test.mjs +59 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/yaml-parser.mjs +157 -0
- package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +11 -6
- package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/SKILL.md +17 -0
- package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/agents/openai.yaml +7 -0
- package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/autonomous-cloud-inputs.example.json +41 -0
- package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/autonomous-runner.md +84 -0
- package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/operational-contract.md +19 -0
- package/templates/.agents/skills/zaip-skill-local-credentials/SKILL.md +43 -0
- package/templates/.agents/skills/zaip-skill-local-credentials/agents/openai.yaml +6 -0
- package/templates/.agents/skills/zaip-skill-local-credentials/references/dotenv-resolution.md +47 -0
- package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +8 -3
- package/templates/.agents/skills/zaip-skill-meta-ads/SKILL.md +16 -0
- package/templates/.agents/skills/zaip-skill-meta-ads/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-ads/references/setup.md +3 -0
- package/templates/.agents/skills/zaip-skill-meta-ads-setup/SKILL.md +24 -0
- package/templates/.agents/skills/zaip-skill-meta-ads-setup/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-ads-setup/references/diagnostics.md +13 -0
- package/templates/.agents/skills/zaip-skill-meta-ads-setup/references/official-sources.md +10 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/references/upstream.md +77 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/references/upstream.md +126 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/references/upstream.md +67 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/references/upstream.md +69 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/references/upstream.md +79 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/SKILL.md +14 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/references/upstream.md +144 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/references/upstream.md +83 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/SKILL.md +10 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/setup.md +11 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/upstream.md +55 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/references/upstream.md +56 -0
- package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +12 -7
- package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +11 -4
- package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +6 -4
- package/templates/.agents/skills/zaip-skill-tool-setup/references/codex-config.md +4 -2
- package/templates/.agents/skills/zaip-skill-tool-setup/references/secrets.md +1 -0
- package/templates/.agents/tools/catalog.yaml +6 -0
- package/templates/.agents/tools/cloud-runner-dns.mjs +28 -0
- package/templates/.agents/tools/cloud-runner-terraform.mjs +212 -0
- package/templates/.agents/tools/cloud-runner.integration.test.mjs +239 -0
- package/templates/.agents/tools/cloud-runner.mjs +786 -0
- package/templates/.agents/tools/cloud-runner.test.mjs +534 -0
- package/templates/.agents/tools/cloud-terraform-plan-policy.mjs +262 -0
- package/templates/.agents/tools/mcp/development/meta-devtools/README.md +14 -0
- package/templates/.agents/tools/mcp/development/meta-devtools/manifest.yaml +28 -0
- package/templates/.agents/tools/mcp/infra/cloudflare/manifest.yaml +2 -3
- package/templates/.agents/tools/mcp/infra/hostinger/manifest.yaml +1 -3
- package/templates/.agents/tools/mcp/marketing/meta-ads/README.md +15 -13
- package/templates/.agents/tools/mcp/marketing/meta-ads/manifest.yaml +15 -10
- package/templates/.agents/tools/profiles/development.yaml +3 -0
- package/templates/infra/terraform/.terraform.lock.hcl +42 -0
- package/templates/infra/terraform/README.md +31 -0
- package/templates/infra/terraform/backend.tf.json +13 -0
- package/templates/infra/terraform/deployment-inputs.example.yaml +22 -0
- package/templates/infra/terraform/fixtures/approved-targets.json +32 -0
- package/templates/infra/terraform/fixtures/plan-approved.json +67 -0
- package/templates/infra/terraform/main.tf +77 -0
- package/templates/infra/terraform/outputs.tf +20 -0
- package/templates/infra/terraform/terraform.tfvars.example +26 -0
- package/templates/infra/terraform/variables.tf +91 -0
- package/templates/infra/terraform/versions.tf +18 -0
- package/templates/scripts/validate-cloud-hostinger-contract.mjs +89 -0
- package/templates/scripts/validate-cloud-terraform-plan.mjs +1 -0
- package/templates/template-manifest.json +607 -167
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-BwnJpl02.js +0 -3
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-C09KmlSw.js +0 -68
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Dyo_wpL4.js +0 -2
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-hPPfVGQv.css +0 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-stale-seed.ts +0 -50
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
4
5
|
import { DatabaseSync } from "node:sqlite";
|
|
5
6
|
import { parseYaml, WorkspaceFileSystem } from "@zaip-ai/core";
|
|
6
7
|
import {
|
|
@@ -21,17 +22,24 @@ import {
|
|
|
21
22
|
type RunV2Fold,
|
|
22
23
|
type UsageRecordedV2,
|
|
23
24
|
} from "../../../../../packages/decisions/src/index.js";
|
|
25
|
+
import { attachAgentProductivity } from "./sqlite-agent-productivity.js";
|
|
24
26
|
import { artifactStructuralRowsSql, developerArtifactCountsCte } from "./sqlite-artifact-counts.js";
|
|
25
27
|
import {
|
|
26
28
|
costDimensionFactDateFilter,
|
|
29
|
+
creationProductivityByModel,
|
|
27
30
|
isDateOnlyCostPage,
|
|
28
31
|
readDateOnlyCostDimensionPage,
|
|
32
|
+
sumKnownProductivity,
|
|
29
33
|
} from "./sqlite-cost-agent-page.js";
|
|
30
34
|
import { buildCostDimensionAggregates } from "./sqlite-cost-aggregates.js";
|
|
31
35
|
import { rebuildCostInvocationDimensionFactsSql } from "./sqlite-cost-dimension-facts.js";
|
|
32
36
|
import { type CostGroupKind, readCostGroupPage } from "./sqlite-cost-group-page.js";
|
|
33
37
|
import { materializeCostGroupDailyProjection } from "./sqlite-cost-group-projection.js";
|
|
34
38
|
import { filterCostInvocationProjection } from "./sqlite-cost-invocation-page.js";
|
|
39
|
+
import {
|
|
40
|
+
costInvocationProjectionSelect,
|
|
41
|
+
readCostInvocationProjection,
|
|
42
|
+
} from "./sqlite-cost-invocation-projection.js";
|
|
35
43
|
import {
|
|
36
44
|
readFirstFoldAllTime,
|
|
37
45
|
readFirstFoldBoardAllTime,
|
|
@@ -41,9 +49,14 @@ import {
|
|
|
41
49
|
} from "./sqlite-first-fold-cache.js";
|
|
42
50
|
import {
|
|
43
51
|
hasRequiredFirstFoldPayloads,
|
|
44
|
-
|
|
52
|
+
persistFirstFoldPayloadsInDatabase,
|
|
45
53
|
} from "./sqlite-first-fold-persistence.js";
|
|
46
54
|
import { measureFirstFold } from "./sqlite-first-fold-timing.js";
|
|
55
|
+
import {
|
|
56
|
+
affectedTemporalIntervals,
|
|
57
|
+
SqliteParsedSourceCache,
|
|
58
|
+
synchronizePayloadRows,
|
|
59
|
+
} from "./sqlite-incremental-cache.js";
|
|
47
60
|
import { materializeOperationsWorkProjection } from "./sqlite-operations-projection.js";
|
|
48
61
|
import {
|
|
49
62
|
attachOperationsWorkListDevelopers,
|
|
@@ -52,12 +65,7 @@ import {
|
|
|
52
65
|
operationsWorkListSqlRows,
|
|
53
66
|
operationsWorkRowsInRange,
|
|
54
67
|
} from "./sqlite-operations-work-list.js";
|
|
55
|
-
import {
|
|
56
|
-
installLegacyViewAliases,
|
|
57
|
-
materializeSnapshot,
|
|
58
|
-
pruneSnapshotCache,
|
|
59
|
-
readSnapshotFileCount,
|
|
60
|
-
} from "./sqlite-snapshot-cache.js";
|
|
68
|
+
import { installLegacyViewAliases, readSnapshotFileCount } from "./sqlite-snapshot-cache.js";
|
|
61
69
|
|
|
62
70
|
export {
|
|
63
71
|
type SqliteReadModelSourceCandidate,
|
|
@@ -65,13 +73,13 @@ export {
|
|
|
65
73
|
} from "./sqlite-read-model-source-fingerprint.js";
|
|
66
74
|
|
|
67
75
|
import { reportSqliteReadModelLaunchProgress } from "./sqlite-read-model-launch-progress.js";
|
|
76
|
+
import type { SqliteReadModelSourceCheckpoint } from "./sqlite-read-model-source-fingerprint.js";
|
|
68
77
|
import { sourceFingerprint } from "./sqlite-read-model-source-fingerprint.js";
|
|
69
|
-
import { materializePreferredStaleSeed } from "./sqlite-read-model-stale-seed.js";
|
|
70
78
|
|
|
71
|
-
const dbRelativePath = ".local/
|
|
72
|
-
// Schema
|
|
73
|
-
//
|
|
74
|
-
const readModelSchemaVersion = "
|
|
79
|
+
const dbRelativePath = ".local/sqlite/zaip-aios-dashboard.sqlite";
|
|
80
|
+
// Schema 73 persists exact source checkpoints and parsed records for delta refresh.
|
|
81
|
+
// Older snapshots initialize these inputs once in the same database transaction.
|
|
82
|
+
const readModelSchemaVersion = "73";
|
|
75
83
|
const requiredPersistedTables = [
|
|
76
84
|
"zaip_aios_activities",
|
|
77
85
|
"zaip_dim_llm_model",
|
|
@@ -114,818 +122,107 @@ const requiredViews = [
|
|
|
114
122
|
] as const;
|
|
115
123
|
const MAX_RECONCILED_RUN_INTERVAL_MS = 2 * 3_600_000;
|
|
116
124
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
id: string;
|
|
153
|
-
occurredAt: string;
|
|
154
|
-
occurredDaySp: string;
|
|
155
|
-
taskId: string | null;
|
|
156
|
-
changeId: string | null;
|
|
157
|
-
runId: string | null;
|
|
158
|
-
};
|
|
159
|
-
type CanonicalExecution = {
|
|
160
|
-
id: string;
|
|
161
|
-
status: string | null;
|
|
162
|
-
kind: string | null;
|
|
163
|
-
originType: string | null;
|
|
164
|
-
originId: string | null;
|
|
165
|
-
createdAt: string;
|
|
166
|
-
finishedAt: string | null;
|
|
167
|
-
sourcePath: string;
|
|
168
|
-
};
|
|
169
|
-
type CanonicalChange = {
|
|
170
|
-
id: string;
|
|
171
|
-
title: string;
|
|
172
|
-
status: string;
|
|
173
|
-
risk: string;
|
|
174
|
-
sourceType: string;
|
|
175
|
-
sourceId: string;
|
|
176
|
-
sourceSpecId: string | null;
|
|
177
|
-
sourcePrdId: string | null;
|
|
178
|
-
requirementIds: string[];
|
|
179
|
-
acceptanceIds: string[];
|
|
180
|
-
taskIds: string[];
|
|
181
|
-
result: string | null;
|
|
182
|
-
createdAt: string;
|
|
183
|
-
sourcePath: string;
|
|
184
|
-
};
|
|
185
|
-
type CanonicalDecision = {
|
|
186
|
-
id: string;
|
|
187
|
-
title: string;
|
|
188
|
-
origin: "human" | "agent";
|
|
189
|
-
scopeType: "change" | "execution";
|
|
190
|
-
scopeId: string;
|
|
191
|
-
createdAt: string;
|
|
192
|
-
sourcePath: string;
|
|
193
|
-
};
|
|
194
|
-
function frontMatterRecord(content: string): Record<string, unknown> | null {
|
|
195
|
-
const source = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content)?.[1];
|
|
196
|
-
if (source === undefined) return null;
|
|
197
|
-
try {
|
|
198
|
-
const parsed = parseYaml(source);
|
|
199
|
-
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
|
|
200
|
-
? (parsed as Record<string, unknown>)
|
|
201
|
-
: null;
|
|
202
|
-
} catch {
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function recordValue(value: unknown): Record<string, unknown> | null {
|
|
208
|
-
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
209
|
-
? (value as Record<string, unknown>)
|
|
210
|
-
: null;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function stringValue(value: unknown): string | null {
|
|
214
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
215
|
-
}
|
|
125
|
+
import {
|
|
126
|
+
type ActivityMetric,
|
|
127
|
+
type AgentProfile,
|
|
128
|
+
activityKind,
|
|
129
|
+
aggregateCost,
|
|
130
|
+
allocateAttributedTokenActiveMs,
|
|
131
|
+
type CanonicalArtifact,
|
|
132
|
+
type CanonicalChange,
|
|
133
|
+
type CanonicalDecision,
|
|
134
|
+
type CanonicalEntity,
|
|
135
|
+
type CanonicalExecution,
|
|
136
|
+
canonicalProvider,
|
|
137
|
+
consolidatedValue,
|
|
138
|
+
costMonitorFilterDay,
|
|
139
|
+
factualDuration,
|
|
140
|
+
frontMatterRecord,
|
|
141
|
+
knownSum,
|
|
142
|
+
latestFxRate,
|
|
143
|
+
pricingModelForUsage,
|
|
144
|
+
type ResolvedSourceInterval,
|
|
145
|
+
readAgentProfiles,
|
|
146
|
+
readCanonicalArtifact,
|
|
147
|
+
readCanonicalEntities,
|
|
148
|
+
readCanonicalLifecycleRecords,
|
|
149
|
+
recordValue,
|
|
150
|
+
resolveDurations,
|
|
151
|
+
resolvedSourceInterval,
|
|
152
|
+
saoPauloCurrentMonth,
|
|
153
|
+
saoPauloDay,
|
|
154
|
+
stringValue,
|
|
155
|
+
stringValues,
|
|
156
|
+
sumKnownDeveloperMetric,
|
|
157
|
+
type TemporalInterval,
|
|
158
|
+
usagePricingAudit,
|
|
159
|
+
} from "./sqlite-read-model-canonical.js";
|
|
216
160
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
: [];
|
|
161
|
+
export { allocateAttributedTokenActiveMs } from "./sqlite-read-model-canonical.js";
|
|
162
|
+
export function sqliteReadModelPath(workspaceRoot: string): string {
|
|
163
|
+
return resolve(process.env.ZAIP_SQLITE_DB_PATH ?? resolve(workspaceRoot, dbRelativePath));
|
|
221
164
|
}
|
|
222
165
|
|
|
223
|
-
function
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (metadata === null) continue;
|
|
234
|
-
const id = stringValue(metadata.id);
|
|
235
|
-
const createdAt = stringValue(metadata.created_at);
|
|
236
|
-
if (id === null || createdAt === null || Number.isNaN(Date.parse(createdAt))) continue;
|
|
237
|
-
if (/^\.agents\/records\/executions\/.*\/execution\.md$/.test(row.path)) {
|
|
238
|
-
const origin = recordValue(metadata.origin);
|
|
239
|
-
const originType = stringValue(origin?.type);
|
|
240
|
-
const originId =
|
|
241
|
-
originType === "interaction"
|
|
242
|
-
? stringValue(origin?.conversation_id)
|
|
243
|
-
: originType === "task"
|
|
244
|
-
? stringValue(origin?.task_id)
|
|
245
|
-
: originType === "spec"
|
|
246
|
-
? stringValue(origin?.spec_id)
|
|
247
|
-
: originType === "prd"
|
|
248
|
-
? stringValue(origin?.prd_id)
|
|
249
|
-
: null;
|
|
250
|
-
executions.push({
|
|
251
|
-
id,
|
|
252
|
-
status: stringValue(metadata.status),
|
|
253
|
-
kind: stringValue(metadata.kind),
|
|
254
|
-
originType,
|
|
255
|
-
originId,
|
|
256
|
-
createdAt,
|
|
257
|
-
finishedAt: stringValue(metadata.finished_at),
|
|
258
|
-
sourcePath: row.path,
|
|
259
|
-
});
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
if (/^\.agents\/records\/changes\/.*\/change\.md$/.test(row.path)) {
|
|
263
|
-
const source = recordValue(metadata.source);
|
|
264
|
-
const sourceType = stringValue(source?.type);
|
|
265
|
-
const sourceId =
|
|
266
|
-
sourceType === "task"
|
|
267
|
-
? stringValue(source?.task_id)
|
|
268
|
-
: sourceType === "spec"
|
|
269
|
-
? stringValue(source?.spec_id)
|
|
270
|
-
: sourceType === "prd"
|
|
271
|
-
? stringValue(source?.prd_id)
|
|
272
|
-
: null;
|
|
273
|
-
if (sourceType === null || sourceId === null) continue;
|
|
274
|
-
changes.push({
|
|
275
|
-
id,
|
|
276
|
-
title: stringValue(metadata.title) ?? id,
|
|
277
|
-
status: stringValue(metadata.status) ?? "unclassified",
|
|
278
|
-
risk: stringValue(metadata.risk) ?? "unclassified",
|
|
279
|
-
sourceType,
|
|
280
|
-
sourceId,
|
|
281
|
-
// A Change sourced from a PRD can still name its selected SPEC. Keep
|
|
282
|
-
// both canonical lineage links: choosing one by source.type loses the
|
|
283
|
-
// only evidence that a planned Task belongs to that SPEC.
|
|
284
|
-
sourceSpecId: stringValue(source?.spec_id),
|
|
285
|
-
sourcePrdId: stringValue(source?.prd_id),
|
|
286
|
-
requirementIds: stringValues(metadata.requirement_ids),
|
|
287
|
-
acceptanceIds: stringValues(metadata.acceptance_ids),
|
|
288
|
-
taskIds: stringValues(metadata.task_ids),
|
|
289
|
-
result: stringValue(metadata.result),
|
|
290
|
-
createdAt,
|
|
291
|
-
sourcePath: row.path,
|
|
292
|
-
});
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
if (/^\.agents\/records\/(changes|executions)\/.*\/decisions\/.*\.md$/.test(row.path)) {
|
|
296
|
-
const origin = metadata.origin;
|
|
297
|
-
const scopeType = metadata.scope_type;
|
|
298
|
-
const scopeId = stringValue(metadata.scope_id);
|
|
299
|
-
if (
|
|
300
|
-
(origin !== "human" && origin !== "agent") ||
|
|
301
|
-
(scopeType !== "change" && scopeType !== "execution") ||
|
|
302
|
-
scopeId === null
|
|
166
|
+
function persistedSourceCheckpoint(path: string): Map<string, SqliteReadModelSourceCheckpoint> {
|
|
167
|
+
// The first materialization has no persistent database to checkpoint.
|
|
168
|
+
// Opening SQLite read-only before this guard throws ERR_SQLITE_ERROR.
|
|
169
|
+
if (!existsSync(path)) return new Map();
|
|
170
|
+
let database: DatabaseSync | undefined;
|
|
171
|
+
try {
|
|
172
|
+
database = new DatabaseSync(path, { readOnly: true });
|
|
173
|
+
const rows = database
|
|
174
|
+
.prepare(
|
|
175
|
+
"SELECT source_path, source_hash, mtime_ms, ctime_ms, size_bytes, file_identity FROM zaip_vw_read_model_source_state",
|
|
303
176
|
)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (frontMatter === undefined) continue;
|
|
331
|
-
const scalar = (key: string): string | null => {
|
|
332
|
-
const value = new RegExp(`^${key}:\\s*(.+?)\\s*$`, "m").exec(frontMatter)?.[1]?.trim();
|
|
333
|
-
if (value === undefined || value === "null") return null;
|
|
334
|
-
return value.replace(/^['"]|['"]$/g, "");
|
|
335
|
-
};
|
|
336
|
-
const id = scalar("id");
|
|
337
|
-
if (id === null) continue;
|
|
338
|
-
const kind = match[1]?.slice(0, -1);
|
|
339
|
-
if (kind === undefined) continue;
|
|
340
|
-
entities.set(`${kind}:${id}`, {
|
|
341
|
-
kind,
|
|
342
|
-
id,
|
|
343
|
-
title: scalar("title"),
|
|
344
|
-
status: scalar("status"),
|
|
345
|
-
stage: scalar("stage"),
|
|
346
|
-
risk: scalar("risk"),
|
|
347
|
-
specId: scalar("spec_id"),
|
|
348
|
-
changeId: scalar("change_id"),
|
|
349
|
-
occurredAt: scalar("created_at") ?? scalar("started_at") ?? scalar("recorded_at"),
|
|
350
|
-
creationRunId: scalar("creation_run_id"),
|
|
351
|
-
completedRunId: scalar("completed_run_id"),
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
return entities;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/** Catalog data is snapshotted into the fact so historical labels never change after a catalog edit. */
|
|
358
|
-
function readAgentProfiles(
|
|
359
|
-
catalog: string | undefined,
|
|
360
|
-
clients: string | undefined,
|
|
361
|
-
): Map<string, AgentProfile> {
|
|
362
|
-
const profiles = new Map<string, AgentProfile>();
|
|
363
|
-
for (const block of (catalog ?? "").split(/^ {2}- id: /m).slice(1)) {
|
|
364
|
-
const catalogId = block.slice(0, block.indexOf("\n")).trim();
|
|
365
|
-
const agent = /\bcodex_name:\s*([^\s#]+)/.exec(block)?.[1] ?? null;
|
|
366
|
-
if (agent === null) continue;
|
|
367
|
-
const persona = /persona:\s*\{\s*nome:\s*([^,}\s]+),\s*cargo:\s*([^,}\s]+)\s*\}/.exec(block);
|
|
368
|
-
const role = /\brole:\s*([^\s#]+)/.exec(block)?.[1] ?? null;
|
|
369
|
-
const profile = {
|
|
370
|
-
catalogId,
|
|
371
|
-
persona: persona?.[1] ?? null,
|
|
372
|
-
job: persona?.[2] ?? null,
|
|
373
|
-
role,
|
|
374
|
-
};
|
|
375
|
-
// Runs before canonicalization used catalog IDs directly, while older
|
|
376
|
-
// host records used `zaip-` forms of deprecated IDs. All of these are
|
|
377
|
-
// explicit identities from the catalog; no UI fallback is involved.
|
|
378
|
-
const identities = new Set([agent, catalogId]);
|
|
379
|
-
for (const alias of block.matchAll(
|
|
380
|
-
/^ {8}- kind:\s*(?:id|codex_name|codex_task_name)\s*\r?\n {10}value:\s*([^\s#]+)/gm,
|
|
381
|
-
)) {
|
|
382
|
-
const value = alias[1];
|
|
383
|
-
if (value !== undefined) identities.add(value);
|
|
384
|
-
}
|
|
385
|
-
for (const identity of [...identities]) {
|
|
386
|
-
if (identity.startsWith("agent-")) identities.add(`zaip-${identity}`);
|
|
387
|
-
}
|
|
388
|
-
for (const identity of identities) profiles.set(identity, profile);
|
|
389
|
-
}
|
|
390
|
-
// `codex` is the host client identity, not a delegable Agent Catalog
|
|
391
|
-
// member. Its canonical client profile makes the host classification
|
|
392
|
-
// explicit, while keeping it separate from ZAIP subagent routing.
|
|
393
|
-
if (/^ {2}- id:\s*codex\s*$/m.test(clients ?? "")) {
|
|
394
|
-
profiles.set("codex", {
|
|
395
|
-
catalogId: "client:codex",
|
|
396
|
-
persona: "codex",
|
|
397
|
-
job: "client_host",
|
|
398
|
-
role: "host",
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
return profiles;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function activityKind(value: {
|
|
405
|
-
execution_id: string | null;
|
|
406
|
-
task_id: string | null;
|
|
407
|
-
spec_id: string | null;
|
|
408
|
-
prd_id: string | null;
|
|
409
|
-
phase: string | null;
|
|
410
|
-
purpose: string | null;
|
|
411
|
-
}): "conversation" | "planning" | "development" {
|
|
412
|
-
if (value.prd_id !== null && value.execution_id === null) return "planning";
|
|
413
|
-
if (value.spec_id !== null && value.execution_id === null) return "planning";
|
|
414
|
-
if (value.phase === "planning" || value.purpose === "planning") return "planning";
|
|
415
|
-
if (
|
|
416
|
-
value.execution_id !== null ||
|
|
417
|
-
value.task_id !== null ||
|
|
418
|
-
value.phase === "execution" ||
|
|
419
|
-
value.purpose === "implementation"
|
|
420
|
-
)
|
|
421
|
-
return "development";
|
|
422
|
-
return "conversation";
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
type ActivityMetric = number | null;
|
|
426
|
-
|
|
427
|
-
function knownSum(
|
|
428
|
-
records: readonly UsageRecordedV2[],
|
|
429
|
-
key: keyof PersistedTokenUsage,
|
|
430
|
-
): ActivityMetric {
|
|
431
|
-
const values = records
|
|
432
|
-
.map((record) => record.tokens[key])
|
|
433
|
-
.filter((value): value is number => typeof value === "number");
|
|
434
|
-
return values.length === 0 ? null : values.reduce((sum, value) => sum + value, 0);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function consolidatedValue(values: readonly (string | null | undefined)[]): string | null {
|
|
438
|
-
const known = [
|
|
439
|
-
...new Set(
|
|
440
|
-
values.filter(
|
|
441
|
-
(value): value is string =>
|
|
442
|
-
typeof value === "string" && value.length > 0 && value !== "unavailable",
|
|
443
|
-
),
|
|
444
|
-
),
|
|
445
|
-
];
|
|
446
|
-
return known.length === 0 ? null : known.length === 1 ? known[0]! : "mixed";
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
function canonicalProvider(
|
|
450
|
-
value: string | null | undefined,
|
|
451
|
-
model: string | null,
|
|
452
|
-
client: string,
|
|
453
|
-
): "openai" | "anthropic" | null {
|
|
454
|
-
if (value === "openai" || value === "anthropic") return value;
|
|
455
|
-
// The canonical sentinel denotes an absent source attribute. It must not be
|
|
456
|
-
// re-inferred from the dashboard client as a known provider.
|
|
457
|
-
if (value === "unavailable" || model === "unavailable") return null;
|
|
458
|
-
return model === null ? null : inferProvider(model, client, "");
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
function factualDuration(record: UsageRecordedV2): boolean {
|
|
462
|
-
return (
|
|
463
|
-
typeof record.duration_ms === "number" &&
|
|
464
|
-
(record.duration_provenance === "observed" ||
|
|
465
|
-
record.duration_provenance === "reconciled" ||
|
|
466
|
-
record.duration_provenance === "legacy_inferred")
|
|
467
|
-
);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
type ResolvedSourceInterval = { start: string; end: string; duration: number };
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Reconciled usage is the closest canonical observation of when model work
|
|
474
|
-
* happened. It wins over Run finalization elapsed time, which may include
|
|
475
|
-
* orchestration/capture lag or be recorded much later.
|
|
476
|
-
*/
|
|
477
|
-
function resolvedSourceInterval(
|
|
478
|
-
records: readonly UsageRecordedV2[],
|
|
479
|
-
): ResolvedSourceInterval | null {
|
|
480
|
-
const intervals = records.flatMap((record) => {
|
|
481
|
-
const start = record.source_started_at;
|
|
482
|
-
const end = record.source_finished_at;
|
|
483
|
-
const startMs = typeof start === "string" ? Date.parse(start) : Number.NaN;
|
|
484
|
-
const endMs = typeof end === "string" ? Date.parse(end) : Number.NaN;
|
|
485
|
-
return typeof start === "string" &&
|
|
486
|
-
typeof end === "string" &&
|
|
487
|
-
Number.isFinite(startMs) &&
|
|
488
|
-
Number.isFinite(endMs) &&
|
|
489
|
-
endMs > startMs &&
|
|
490
|
-
endMs - startMs <= MAX_RECONCILED_RUN_INTERVAL_MS
|
|
491
|
-
? [{ start, end, startMs, endMs }]
|
|
492
|
-
: [];
|
|
493
|
-
});
|
|
494
|
-
if (intervals.length === 0) return null;
|
|
495
|
-
const start = intervals.reduce((earliest, interval) =>
|
|
496
|
-
interval.startMs < earliest.startMs ? interval : earliest,
|
|
497
|
-
);
|
|
498
|
-
const end = intervals.reduce((latest, interval) =>
|
|
499
|
-
interval.endMs > latest.endMs ? interval : latest,
|
|
500
|
-
);
|
|
501
|
-
return end.endMs - start.startMs <= MAX_RECONCILED_RUN_INTERVAL_MS
|
|
502
|
-
? { start: start.start, end: end.end, duration: end.endMs - start.startMs }
|
|
503
|
-
: null;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function resolveDurations(
|
|
507
|
-
records: readonly UsageRecordedV2[],
|
|
508
|
-
fold: RunV2Fold,
|
|
509
|
-
): {
|
|
510
|
-
source: number | null;
|
|
511
|
-
effective: number | null;
|
|
512
|
-
provenance: string | null;
|
|
513
|
-
sourceStartedAt: string | null;
|
|
514
|
-
sourceFinishedAt: string | null;
|
|
515
|
-
} {
|
|
516
|
-
const sourceInterval = resolvedSourceInterval(records);
|
|
517
|
-
if (sourceInterval !== null) {
|
|
518
|
-
return {
|
|
519
|
-
source: sourceInterval.duration,
|
|
520
|
-
effective: sourceInterval.duration,
|
|
521
|
-
provenance: "reconciled",
|
|
522
|
-
sourceStartedAt: sourceInterval.start,
|
|
523
|
-
sourceFinishedAt: sourceInterval.end,
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
if (
|
|
527
|
-
typeof fold.finished?.duration_ms === "number" &&
|
|
528
|
-
fold.finished.duration_provenance !== "estimated" &&
|
|
529
|
-
fold.finished.duration_provenance !== "unavailable"
|
|
530
|
-
) {
|
|
531
|
-
return {
|
|
532
|
-
source: fold.finished.duration_ms,
|
|
533
|
-
effective: fold.finished.duration_ms,
|
|
534
|
-
provenance: fold.finished.duration_provenance ?? "observed",
|
|
535
|
-
sourceStartedAt: null,
|
|
536
|
-
sourceFinishedAt: null,
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
const factual = records.filter(factualDuration);
|
|
540
|
-
if (factual.length > 0) {
|
|
541
|
-
const source = factual.reduce((sum, record) => sum + (record.duration_ms ?? 0), 0);
|
|
542
|
-
const provenance =
|
|
543
|
-
consolidatedValue(factual.map((record) => record.duration_provenance)) ?? "observed";
|
|
544
|
-
return { source, effective: source, provenance, sourceStartedAt: null, sourceFinishedAt: null };
|
|
545
|
-
}
|
|
546
|
-
// A canonical Run pair can supply elapsed time when terminal records predate
|
|
547
|
-
// the explicit duration field. Historical finalizers can however close a
|
|
548
|
-
// Run days later, so only a bounded interval is trusted as reconciliation.
|
|
549
|
-
const startedAt = Date.parse(fold.run.occurred_at);
|
|
550
|
-
const finishedAt =
|
|
551
|
-
typeof fold.finished?.occurred_at === "string"
|
|
552
|
-
? Date.parse(fold.finished.occurred_at)
|
|
553
|
-
: Number.NaN;
|
|
554
|
-
if (
|
|
555
|
-
Number.isFinite(startedAt) &&
|
|
556
|
-
Number.isFinite(finishedAt) &&
|
|
557
|
-
finishedAt > startedAt &&
|
|
558
|
-
finishedAt - startedAt <= MAX_RECONCILED_RUN_INTERVAL_MS
|
|
559
|
-
) {
|
|
560
|
-
return {
|
|
561
|
-
source: null,
|
|
562
|
-
effective: finishedAt - startedAt,
|
|
563
|
-
provenance: "reconciled",
|
|
564
|
-
sourceStartedAt: null,
|
|
565
|
-
sourceFinishedAt: null,
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
// Tokens and lifecycle timestamps are not a time source. Absence stays
|
|
569
|
-
// absent: consumers must display unavailable rather than manufacture time.
|
|
570
|
-
return {
|
|
571
|
-
source: null,
|
|
572
|
-
effective: null,
|
|
573
|
-
provenance: null,
|
|
574
|
-
sourceStartedAt: null,
|
|
575
|
-
sourceFinishedAt: null,
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
function latestFxRate(rows: Array<{ path: string; content: string }>): FxRate | null {
|
|
580
|
-
return (
|
|
581
|
-
rows
|
|
582
|
-
.flatMap((row) => {
|
|
583
|
-
try {
|
|
584
|
-
return [fxRateSchema.parse(JSON.parse(row.content))];
|
|
585
|
-
} catch {
|
|
177
|
+
.all() as Array<Record<string, unknown>>;
|
|
178
|
+
return new Map(
|
|
179
|
+
rows.flatMap((row) => {
|
|
180
|
+
const source = typeof row.source_path === "string" ? row.source_path : null;
|
|
181
|
+
const contentHash = typeof row.source_hash === "string" ? row.source_hash : null;
|
|
182
|
+
const fileIdentity = typeof row.file_identity === "string" ? row.file_identity : null;
|
|
183
|
+
const mtimeMs =
|
|
184
|
+
typeof row.mtime_ms === "number"
|
|
185
|
+
? row.mtime_ms
|
|
186
|
+
: typeof row.mtime_ms === "string"
|
|
187
|
+
? Number(row.mtime_ms)
|
|
188
|
+
: Number.NaN;
|
|
189
|
+
const ctimeMs =
|
|
190
|
+
typeof row.ctime_ms === "number"
|
|
191
|
+
? row.ctime_ms
|
|
192
|
+
: typeof row.ctime_ms === "string"
|
|
193
|
+
? Number(row.ctime_ms)
|
|
194
|
+
: Number.NaN;
|
|
195
|
+
if (
|
|
196
|
+
source === null ||
|
|
197
|
+
contentHash === null ||
|
|
198
|
+
fileIdentity === null ||
|
|
199
|
+
!Number.isFinite(mtimeMs) ||
|
|
200
|
+
!Number.isFinite(ctimeMs) ||
|
|
201
|
+
typeof row.size_bytes !== "number"
|
|
202
|
+
)
|
|
586
203
|
return [];
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
let knownUsd = false;
|
|
602
|
-
let knownBrl = false;
|
|
603
|
-
let estimated = false;
|
|
604
|
-
const pricingModels: string[] = [];
|
|
605
|
-
const pricingSources: string[] = [];
|
|
606
|
-
for (const record of records) {
|
|
607
|
-
if (typeof record.cost.usd === "number") {
|
|
608
|
-
usd += record.cost.usd;
|
|
609
|
-
knownUsd = true;
|
|
610
|
-
}
|
|
611
|
-
if (typeof record.cost.brl === "number") {
|
|
612
|
-
brl += record.cost.brl;
|
|
613
|
-
knownBrl = true;
|
|
614
|
-
}
|
|
615
|
-
if (typeof record.cost.usd === "number" && typeof record.cost.brl === "number") continue;
|
|
616
|
-
if (record.tokens.total === null) continue;
|
|
617
|
-
if (record.tokens.total === 0) {
|
|
618
|
-
knownUsd = true;
|
|
619
|
-
knownBrl = true;
|
|
620
|
-
continue;
|
|
621
|
-
}
|
|
622
|
-
const model = record.model ?? fold.run.model;
|
|
623
|
-
if (model === null || model === "unavailable") continue;
|
|
624
|
-
const priced = estimateUsageCost({
|
|
625
|
-
provider: canonicalProvider(record.provider, model, fold.run.client),
|
|
626
|
-
model,
|
|
627
|
-
usage: record.tokens,
|
|
628
|
-
catalog,
|
|
629
|
-
fxRate,
|
|
630
|
-
serviceTier: record.runtime?.service_tier ?? null,
|
|
631
|
-
});
|
|
632
|
-
if (typeof record.cost.usd !== "number" && priced.estimated_usd !== null) {
|
|
633
|
-
usd += priced.estimated_usd;
|
|
634
|
-
knownUsd = true;
|
|
635
|
-
estimated = true;
|
|
636
|
-
}
|
|
637
|
-
if (typeof record.cost.brl !== "number" && priced.estimated_brl !== null) {
|
|
638
|
-
brl += priced.estimated_brl;
|
|
639
|
-
knownBrl = true;
|
|
640
|
-
estimated = true;
|
|
641
|
-
}
|
|
642
|
-
if (priced.pricing_model !== null) pricingModels.push(priced.pricing_model);
|
|
643
|
-
if (priced.pricing_source !== null) pricingSources.push(priced.pricing_source);
|
|
644
|
-
}
|
|
645
|
-
const totalTokens = knownSum(records, "total");
|
|
646
|
-
return {
|
|
647
|
-
usd: knownUsd ? Math.round(usd * 1_000_000) / 1_000_000 : null,
|
|
648
|
-
brl: knownBrl ? Math.round(brl * 1_000_000) / 1_000_000 : null,
|
|
649
|
-
method:
|
|
650
|
-
totalTokens === 0
|
|
651
|
-
? "zero_no_llm_usage"
|
|
652
|
-
: estimated
|
|
653
|
-
? "token_pricing_estimate"
|
|
654
|
-
: knownUsd || knownBrl
|
|
655
|
-
? "canonical_recorded"
|
|
656
|
-
: null,
|
|
657
|
-
pricingModel: consolidatedValue(pricingModels),
|
|
658
|
-
pricingSource: consolidatedValue(pricingSources),
|
|
659
|
-
fxRate: knownBrl ? (fxRate?.rate ?? null) : null,
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
function pricingModelForUsage(catalog: PricingCatalog, model: string | null) {
|
|
664
|
-
if (model === null) return null;
|
|
665
|
-
const normalizedModel = model.toLowerCase();
|
|
666
|
-
const matches = (identity: string) => {
|
|
667
|
-
const normalizedIdentity = identity.toLowerCase();
|
|
668
|
-
if (normalizedModel === normalizedIdentity) return true;
|
|
669
|
-
const suffix = normalizedModel.slice(normalizedIdentity.length);
|
|
670
|
-
return (
|
|
671
|
-
suffix.startsWith("@") ||
|
|
672
|
-
suffix.startsWith(":") ||
|
|
673
|
-
suffix.startsWith("/") ||
|
|
674
|
-
/^-\d{4}-\d{2}-\d{2}(?:$|[.:@/])/.test(suffix)
|
|
675
|
-
);
|
|
676
|
-
};
|
|
677
|
-
return (
|
|
678
|
-
catalog.models.find(
|
|
679
|
-
(candidate) => matches(candidate.id) || candidate.aliases.some((alias) => matches(alias)),
|
|
680
|
-
) ?? null
|
|
681
|
-
);
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
/** Pricing columns needed to reproduce the Guide calculation at invocation grain. */
|
|
685
|
-
function usagePricingAudit(
|
|
686
|
-
record: UsageRecordedV2,
|
|
687
|
-
fold: RunV2Fold,
|
|
688
|
-
catalog: PricingCatalog,
|
|
689
|
-
fxRate: FxRate | null,
|
|
690
|
-
) {
|
|
691
|
-
const model = record.model ?? fold.run.model;
|
|
692
|
-
const provider = canonicalProvider(record.provider, model, fold.run.client);
|
|
693
|
-
const serviceTier = record.runtime?.service_tier ?? null;
|
|
694
|
-
const pricingModel = pricingModelForUsage(catalog, model);
|
|
695
|
-
const actual =
|
|
696
|
-
model === null
|
|
697
|
-
? null
|
|
698
|
-
: estimateUsageCost({
|
|
699
|
-
provider,
|
|
700
|
-
model,
|
|
701
|
-
usage: record.tokens,
|
|
702
|
-
catalog,
|
|
703
|
-
fxRate,
|
|
704
|
-
serviceTier,
|
|
705
|
-
});
|
|
706
|
-
const standard =
|
|
707
|
-
model === null
|
|
708
|
-
? null
|
|
709
|
-
: estimateUsageCost({
|
|
710
|
-
provider,
|
|
711
|
-
model,
|
|
712
|
-
usage: record.tokens,
|
|
713
|
-
catalog,
|
|
714
|
-
fxRate,
|
|
715
|
-
serviceTier: "default",
|
|
716
|
-
});
|
|
717
|
-
const longContext =
|
|
718
|
-
pricingModel?.long_context_threshold_tokens !== null &&
|
|
719
|
-
pricingModel?.long_context_threshold_tokens !== undefined &&
|
|
720
|
-
(record.tokens.input ?? 0) > pricingModel.long_context_threshold_tokens;
|
|
721
|
-
const priority = serviceTier === "priority" || serviceTier === "fast";
|
|
722
|
-
const priorityAvailable = priority && !longContext;
|
|
723
|
-
const inputRate = pricingModel
|
|
724
|
-
? priorityAvailable
|
|
725
|
-
? (pricingModel.priority_input_per_million_usd ?? null)
|
|
726
|
-
: pricingModel.input_per_million_usd
|
|
727
|
-
: null;
|
|
728
|
-
const cachedRate = pricingModel
|
|
729
|
-
? priorityAvailable
|
|
730
|
-
? (pricingModel.priority_cached_input_per_million_usd ?? null)
|
|
731
|
-
: pricingModel.cached_input_per_million_usd
|
|
732
|
-
: null;
|
|
733
|
-
const outputRate = pricingModel
|
|
734
|
-
? priorityAvailable
|
|
735
|
-
? (pricingModel.priority_output_per_million_usd ?? null)
|
|
736
|
-
: pricingModel.output_per_million_usd
|
|
737
|
-
: null;
|
|
738
|
-
const reasoningRate = pricingModel
|
|
739
|
-
? priorityAvailable
|
|
740
|
-
? (pricingModel.priority_reasoning_per_million_usd ??
|
|
741
|
-
pricingModel.priority_output_per_million_usd ??
|
|
742
|
-
null)
|
|
743
|
-
: (pricingModel.reasoning_per_million_usd ?? pricingModel.output_per_million_usd)
|
|
744
|
-
: null;
|
|
745
|
-
// Priority + long-context has no published combined tariff. The Guide uses
|
|
746
|
-
// the published Standard long-context scenario in that case, matching the
|
|
747
|
-
// canonical historical estimate without inventing a price table.
|
|
748
|
-
const priced = actual?.estimated_usd !== null && actual !== null ? actual : standard;
|
|
749
|
-
const calculatedUsd = priced?.estimated_usd ?? null;
|
|
750
|
-
const standardUsd = standard?.estimated_usd ?? null;
|
|
751
|
-
const recordedUsd = record.cost.usd;
|
|
752
|
-
const effectiveUsd = recordedUsd ?? calculatedUsd;
|
|
753
|
-
const effectiveBrl =
|
|
754
|
-
record.cost.brl ??
|
|
755
|
-
actual?.estimated_brl ??
|
|
756
|
-
(effectiveUsd !== null && fxRate !== null
|
|
757
|
-
? Math.round(effectiveUsd * fxRate.rate * 1_000_000) / 1_000_000
|
|
758
|
-
: null);
|
|
759
|
-
return {
|
|
760
|
-
provider,
|
|
761
|
-
model,
|
|
762
|
-
serviceTier,
|
|
763
|
-
uncachedInputTokens:
|
|
764
|
-
record.tokens.input === null
|
|
765
|
-
? null
|
|
766
|
-
: Math.max(record.tokens.input - (record.tokens.cached_input ?? 0), 0),
|
|
767
|
-
visibleOutputTokens:
|
|
768
|
-
record.tokens.output === null
|
|
769
|
-
? null
|
|
770
|
-
: Math.max(record.tokens.output - (record.tokens.reasoning ?? 0), 0),
|
|
771
|
-
inputRate,
|
|
772
|
-
cachedRate,
|
|
773
|
-
outputRate,
|
|
774
|
-
reasoningRate,
|
|
775
|
-
contextInputMultiplier: pricingModel
|
|
776
|
-
? longContext
|
|
777
|
-
? pricingModel.long_context_input_multiplier
|
|
778
|
-
: 1
|
|
779
|
-
: null,
|
|
780
|
-
contextOutputMultiplier: pricingModel
|
|
781
|
-
? longContext
|
|
782
|
-
? pricingModel.long_context_output_multiplier
|
|
783
|
-
: 1
|
|
784
|
-
: null,
|
|
785
|
-
speedCostMultiplier:
|
|
786
|
-
calculatedUsd !== null && standardUsd !== null && standardUsd > 0
|
|
787
|
-
? Math.round((calculatedUsd / standardUsd) * 1000) / 1000
|
|
788
|
-
: null,
|
|
789
|
-
pricingScenario:
|
|
790
|
-
priority && longContext
|
|
791
|
-
? "standard_long_context_fallback"
|
|
792
|
-
: priority
|
|
793
|
-
? "priority"
|
|
794
|
-
: "standard",
|
|
795
|
-
standardUsd,
|
|
796
|
-
speedUpliftUsd:
|
|
797
|
-
calculatedUsd !== null && standardUsd !== null
|
|
798
|
-
? Math.round((calculatedUsd - standardUsd) * 1_000_000) / 1_000_000
|
|
799
|
-
: null,
|
|
800
|
-
inputUsd: priced?.input_usd ?? null,
|
|
801
|
-
cachedInputUsd: priced?.cached_input_usd ?? null,
|
|
802
|
-
outputUsd: priced?.output_usd ?? null,
|
|
803
|
-
reasoningUsd: priced?.reasoning_usd ?? null,
|
|
804
|
-
calculatedUsd,
|
|
805
|
-
recordedUsd,
|
|
806
|
-
effectiveUsd,
|
|
807
|
-
effectiveBrl,
|
|
808
|
-
varianceUsd:
|
|
809
|
-
recordedUsd !== null && calculatedUsd !== null
|
|
810
|
-
? Math.round((recordedUsd - calculatedUsd) * 1_000_000) / 1_000_000
|
|
811
|
-
: null,
|
|
812
|
-
pricingModel: priced?.pricing_model ?? null,
|
|
813
|
-
pricingSource: priced?.pricing_source ?? null,
|
|
814
|
-
fxRate: record.cost.fx_rate ?? fxRate?.rate ?? null,
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
function saoPauloDay(timestamp: string): string {
|
|
819
|
-
const values = Object.fromEntries(
|
|
820
|
-
new Intl.DateTimeFormat("en", {
|
|
821
|
-
timeZone: "America/Sao_Paulo",
|
|
822
|
-
year: "numeric",
|
|
823
|
-
month: "2-digit",
|
|
824
|
-
day: "2-digit",
|
|
825
|
-
})
|
|
826
|
-
.formatToParts(new Date(timestamp))
|
|
827
|
-
.filter((part) => part.type !== "literal")
|
|
828
|
-
.map((part) => [part.type, part.value]),
|
|
829
|
-
) as { year: string; month: string; day: string };
|
|
830
|
-
return `${values.year}-${values.month}-${values.day}`;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
type TemporalInterval = { activityRowId: string; startMs: number; endMs: number };
|
|
834
|
-
|
|
835
|
-
/**
|
|
836
|
-
* Splits every observed interval at every activity boundary and São Paulo day
|
|
837
|
-
* boundary. Each elementary segment is divided equally among the token-active
|
|
838
|
-
* activities covering it, making the materialized fact additive by activity,
|
|
839
|
-
* day, and every parent dimension.
|
|
840
|
-
*/
|
|
841
|
-
export function allocateAttributedTokenActiveMs(intervals: readonly TemporalInterval[]) {
|
|
842
|
-
const boundaries = new Set<number>();
|
|
843
|
-
for (const interval of intervals) {
|
|
844
|
-
boundaries.add(interval.startMs);
|
|
845
|
-
boundaries.add(interval.endMs);
|
|
846
|
-
for (let cursor = interval.startMs; cursor < interval.endMs; ) {
|
|
847
|
-
const nextDay =
|
|
848
|
-
Date.parse(`${saoPauloDay(new Date(cursor).toISOString())}T03:00:00.000Z`) + 86_400_000;
|
|
849
|
-
if (nextDay >= interval.endMs) break;
|
|
850
|
-
boundaries.add(nextDay);
|
|
851
|
-
cursor = nextDay;
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
const points = [...boundaries].sort((left, right) => left - right);
|
|
855
|
-
const allocated = new Map<string, { activityRowId: string; day: string; value: number }>();
|
|
856
|
-
for (let index = 0; index < points.length - 1; index += 1) {
|
|
857
|
-
const startMs = points[index]!;
|
|
858
|
-
const endMs = points[index + 1]!;
|
|
859
|
-
const active = intervals.filter(
|
|
860
|
-
(interval) => interval.startMs < endMs && interval.endMs > startMs,
|
|
204
|
+
return [
|
|
205
|
+
[
|
|
206
|
+
source,
|
|
207
|
+
{
|
|
208
|
+
source,
|
|
209
|
+
mtimeMs,
|
|
210
|
+
ctimeMs,
|
|
211
|
+
size: row.size_bytes,
|
|
212
|
+
contentHash,
|
|
213
|
+
fileIdentity,
|
|
214
|
+
} satisfies SqliteReadModelSourceCheckpoint,
|
|
215
|
+
],
|
|
216
|
+
];
|
|
217
|
+
}),
|
|
861
218
|
);
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const key = `${interval.activityRowId}:${day}`;
|
|
867
|
-
const previous = allocated.get(key);
|
|
868
|
-
allocated.set(key, {
|
|
869
|
-
activityRowId: interval.activityRowId,
|
|
870
|
-
day,
|
|
871
|
-
value: (previous?.value ?? 0) + share,
|
|
872
|
-
});
|
|
873
|
-
}
|
|
219
|
+
} catch {
|
|
220
|
+
return new Map();
|
|
221
|
+
} finally {
|
|
222
|
+
database?.close();
|
|
874
223
|
}
|
|
875
|
-
return [...allocated.values()];
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
/** A dashboard API boundary can be a date or an ISO instant. Normalize both
|
|
879
|
-
* to the same Sao Paulo calendar key used by Cost Monitor SQL buckets. */
|
|
880
|
-
function costMonitorFilterDay(value: string | null | undefined): string | null {
|
|
881
|
-
if (value === null || value === undefined) return null;
|
|
882
|
-
return /^\d{4}-\d{2}-\d{2}$/.test(value) ? value : saoPauloDay(value);
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
function sumKnownDeveloperMetric(
|
|
886
|
-
rows: readonly Record<string, unknown>[],
|
|
887
|
-
field: string,
|
|
888
|
-
): number | null {
|
|
889
|
-
const values = rows
|
|
890
|
-
.map((row) => row[field])
|
|
891
|
-
.filter((value): value is number => typeof value === "number" && Number.isFinite(value));
|
|
892
|
-
return values.length === 0 ? null : values.reduce((sum, value) => sum + value, 0);
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
/** Extracts the small artifact projection while the canonical file is already being read. */
|
|
896
|
-
function readCanonicalArtifact(source: string, content: string): CanonicalArtifact | null {
|
|
897
|
-
const kind = source.includes("/artifacts/evidence/")
|
|
898
|
-
? "evidence"
|
|
899
|
-
: source.includes("/artifacts/validations/") || source.includes("/artifacts/tests/")
|
|
900
|
-
? "validation"
|
|
901
|
-
: null;
|
|
902
|
-
if (kind === null) return null;
|
|
903
|
-
const frontMatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content)?.[1] ?? "";
|
|
904
|
-
const scalar = (key: string): string | null => {
|
|
905
|
-
const value = new RegExp(`^${key}:\\s*['"]?(.+?)['"]?\\s*$`, "m")
|
|
906
|
-
.exec(frontMatter)?.[1]
|
|
907
|
-
?.trim();
|
|
908
|
-
return value === undefined || value === "null" ? null : value;
|
|
909
|
-
};
|
|
910
|
-
const occurredAt =
|
|
911
|
-
scalar("observed_at") ?? scalar("executed_at") ?? scalar("created_at") ?? scalar("recorded_at");
|
|
912
|
-
if (occurredAt === null || Number.isNaN(Date.parse(occurredAt))) return null;
|
|
913
|
-
return {
|
|
914
|
-
kind,
|
|
915
|
-
id: scalar("id") ?? source.slice(source.lastIndexOf("/") + 1, -3),
|
|
916
|
-
occurredAt,
|
|
917
|
-
occurredDaySp: saoPauloDay(occurredAt),
|
|
918
|
-
taskId: scalar("task_id"),
|
|
919
|
-
changeId: scalar("change_id"),
|
|
920
|
-
runId: scalar("run_id"),
|
|
921
|
-
};
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
export function sqliteReadModelPath(workspaceRoot: string): string {
|
|
925
|
-
return resolve(process.env.ZAIP_SQLITE_DB_PATH ?? resolve(workspaceRoot, dbRelativePath));
|
|
926
224
|
}
|
|
927
225
|
|
|
928
|
-
/** Atomically installs a worker-built immutable generation after its schema is validated. */
|
|
929
226
|
/**
|
|
930
227
|
* Refuses stale or incomplete SQLite generations before they are served. This
|
|
931
228
|
* is intentionally independent of source fingerprints: a matching cache key
|
|
@@ -1006,31 +303,79 @@ function hasReusableSnapshotPayloads(database: DatabaseSync): boolean {
|
|
|
1006
303
|
/** Recreates the disposable comparison read model from ZAIP records and configuration. */
|
|
1007
304
|
export async function rebuildSqliteReadModel(
|
|
1008
305
|
workspaceRoot: string,
|
|
1009
|
-
options: { forceCanonical?: boolean } = {},
|
|
306
|
+
options: { forceCanonical?: boolean; preferExisting?: boolean } = {},
|
|
1010
307
|
): Promise<{
|
|
1011
308
|
files: number;
|
|
1012
309
|
path: string;
|
|
1013
310
|
cache: "generation" | "seed" | "stale_seed" | "rebuilt";
|
|
1014
311
|
fingerprint_ms: number;
|
|
1015
312
|
materialize_ms: number;
|
|
313
|
+
refresh: {
|
|
314
|
+
reused_sources: number;
|
|
315
|
+
processed_sources: number;
|
|
316
|
+
added_sources: number;
|
|
317
|
+
changed_sources: number;
|
|
318
|
+
removed_sources: number;
|
|
319
|
+
canonical_runs_refolded: number;
|
|
320
|
+
};
|
|
1016
321
|
}> {
|
|
1017
322
|
const path = sqliteReadModelPath(workspaceRoot);
|
|
323
|
+
// Bind immediately from the last complete transaction. The serving process
|
|
324
|
+
// checks canonical freshness in its worker before declaring this current.
|
|
325
|
+
if (options.preferExisting && !options.forceCanonical) {
|
|
326
|
+
try {
|
|
327
|
+
validateSqliteReadModelSchema(path);
|
|
328
|
+
const database = new DatabaseSync(path, { readOnly: true });
|
|
329
|
+
try {
|
|
330
|
+
if (hasReusableSnapshotPayloads(database)) {
|
|
331
|
+
const row = database
|
|
332
|
+
.prepare(
|
|
333
|
+
"SELECT value FROM zaip_vw_read_model_metadata WHERE key = 'source_file_count'",
|
|
334
|
+
)
|
|
335
|
+
.get() as { value: string };
|
|
336
|
+
return {
|
|
337
|
+
files: Number(row.value),
|
|
338
|
+
path,
|
|
339
|
+
cache: "stale_seed",
|
|
340
|
+
fingerprint_ms: 0,
|
|
341
|
+
materialize_ms: 0,
|
|
342
|
+
refresh: {
|
|
343
|
+
reused_sources: 0,
|
|
344
|
+
processed_sources: 0,
|
|
345
|
+
added_sources: 0,
|
|
346
|
+
changed_sources: 0,
|
|
347
|
+
removed_sources: 0,
|
|
348
|
+
canonical_runs_refolded: 0,
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
} finally {
|
|
353
|
+
database.close();
|
|
354
|
+
}
|
|
355
|
+
} catch {
|
|
356
|
+
// Missing, incompatible, or incomplete models require a canonical build.
|
|
357
|
+
}
|
|
358
|
+
}
|
|
1018
359
|
const reportLaunchProgress = reportSqliteReadModelLaunchProgress;
|
|
1019
|
-
const cacheDirectory = process.env.ZAIP_SQLITE_CACHE_DIR ?? join(dirname(path), "cache");
|
|
1020
|
-
const preferredSeed = await materializePreferredStaleSeed({
|
|
1021
|
-
destination: path,
|
|
1022
|
-
enabled: !options.forceCanonical && process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED === "1",
|
|
1023
|
-
fingerprintMs: 0,
|
|
1024
|
-
hasRequiredPayloads: hasReusableSnapshotPayloads,
|
|
1025
|
-
reportProgress: true,
|
|
1026
|
-
schemaVersion: readModelSchemaVersion,
|
|
1027
|
-
});
|
|
1028
|
-
if (preferredSeed !== null) return preferredSeed;
|
|
1029
360
|
const fingerprintStartedAt = performance.now();
|
|
1030
361
|
reportLaunchProgress("source_fingerprint_started", 8, "Inventariando fontes canônicas.");
|
|
1031
|
-
|
|
362
|
+
let sourceSnapshot = await sourceFingerprint(
|
|
363
|
+
workspaceRoot,
|
|
364
|
+
options.forceCanonical
|
|
365
|
+
? { verifyContent: true }
|
|
366
|
+
: { previous: persistedSourceCheckpoint(path) },
|
|
367
|
+
);
|
|
368
|
+
let refresh = {
|
|
369
|
+
reused_sources: sourceSnapshot.reusedFiles,
|
|
370
|
+
processed_sources: sourceSnapshot.hashedFiles,
|
|
371
|
+
added_sources: 0,
|
|
372
|
+
changed_sources: 0,
|
|
373
|
+
removed_sources: 0,
|
|
374
|
+
canonical_runs_refolded: 0,
|
|
375
|
+
};
|
|
1032
376
|
reportLaunchProgress("source_fingerprint_ready", 20, "Inventário canônico concluído.");
|
|
1033
|
-
|
|
377
|
+
// Revalidate date-dependent payloads on every São Paulo calendar day.
|
|
378
|
+
let fingerprint = `${sourceSnapshot.fingerprint}\ncurrent_day=${saoPauloDay(new Date().toISOString())}`;
|
|
1034
379
|
const fingerprintMs = Math.round(performance.now() - fingerprintStartedAt);
|
|
1035
380
|
const existingFiles = readSnapshotFileCount(
|
|
1036
381
|
path,
|
|
@@ -1038,7 +383,7 @@ export async function rebuildSqliteReadModel(
|
|
|
1038
383
|
readModelSchemaVersion,
|
|
1039
384
|
hasReusableSnapshotPayloads,
|
|
1040
385
|
);
|
|
1041
|
-
if (existingFiles !== null) {
|
|
386
|
+
if (existingFiles !== null && !options.forceCanonical && sourceSnapshot.hashedFiles === 0) {
|
|
1042
387
|
validateSqliteReadModelSchema(path);
|
|
1043
388
|
return {
|
|
1044
389
|
files: existingFiles,
|
|
@@ -1046,71 +391,275 @@ export async function rebuildSqliteReadModel(
|
|
|
1046
391
|
cache: "generation",
|
|
1047
392
|
fingerprint_ms: fingerprintMs,
|
|
1048
393
|
materialize_ms: 0,
|
|
394
|
+
refresh,
|
|
1049
395
|
};
|
|
1050
396
|
}
|
|
1051
397
|
|
|
1052
|
-
// The launcher intentionally gives every live server a private database path
|
|
1053
|
-
// (Windows keeps read-only handles open). Cache an immutable snapshot by
|
|
1054
|
-
// source fingerprint, then atomically materialize that cache into the
|
|
1055
|
-
// generation path. A normal fresh-port handoff can therefore be warm.
|
|
1056
|
-
const cachePath = join(
|
|
1057
|
-
cacheDirectory,
|
|
1058
|
-
// The cache payload is part of the immutable generation contract. Keep
|
|
1059
|
-
// a separate namespace when that payload changes, so a schema-compatible
|
|
1060
|
-
// older seed cannot bypass the new all-time precomputation.
|
|
1061
|
-
// Keep the immutable name short enough that SQLite can create its
|
|
1062
|
-
// temporary journal beside deep Windows fixture/workspace paths.
|
|
1063
|
-
`v${readModelSchemaVersion}-ff9-${createHash("sha256").update(fingerprint).digest("hex")}.sqlite`,
|
|
1064
|
-
);
|
|
1065
|
-
const cachedFiles = readSnapshotFileCount(
|
|
1066
|
-
cachePath,
|
|
1067
|
-
fingerprint,
|
|
1068
|
-
readModelSchemaVersion,
|
|
1069
|
-
hasReusableSnapshotPayloads,
|
|
1070
|
-
);
|
|
1071
|
-
if (cachedFiles !== null) {
|
|
1072
|
-
const materializeStartedAt = performance.now();
|
|
1073
|
-
await materializeSnapshot(cachePath, path);
|
|
1074
|
-
validateSqliteReadModelSchema(path);
|
|
1075
|
-
return {
|
|
1076
|
-
files: cachedFiles,
|
|
1077
|
-
path,
|
|
1078
|
-
cache: "seed",
|
|
1079
|
-
fingerprint_ms: fingerprintMs,
|
|
1080
|
-
materialize_ms: Math.round(performance.now() - materializeStartedAt),
|
|
1081
|
-
};
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
// A normal launcher click must not block the browser on a full canonical
|
|
1085
|
-
// reconstruction merely because a record changed since the last snapshot.
|
|
1086
|
-
// The launcher labels this state as updating and starts an isolated refresh;
|
|
1087
|
-
// this process still serves only a schema-validated immutable seed.
|
|
1088
|
-
const fallback = await materializePreferredStaleSeed({
|
|
1089
|
-
destination: path,
|
|
1090
|
-
enabled: !options.forceCanonical && process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED === "1",
|
|
1091
|
-
fingerprintMs,
|
|
1092
|
-
hasRequiredPayloads: hasReusableSnapshotPayloads,
|
|
1093
|
-
schemaVersion: readModelSchemaVersion,
|
|
1094
|
-
});
|
|
1095
|
-
if (fallback !== null) return fallback;
|
|
1096
|
-
|
|
1097
398
|
reportLaunchProgress(
|
|
1098
399
|
"source_inventory",
|
|
1099
400
|
52,
|
|
1100
401
|
`Encontradas ${sourceSnapshot.candidates.length} fontes canônicas.`,
|
|
1101
402
|
);
|
|
1102
|
-
await mkdir(dirname(
|
|
1103
|
-
const
|
|
1104
|
-
const database = new DatabaseSync(temporaryPath);
|
|
403
|
+
await mkdir(dirname(path), { recursive: true });
|
|
404
|
+
const database = new DatabaseSync(path);
|
|
1105
405
|
let completedFiles = 0;
|
|
1106
406
|
try {
|
|
1107
|
-
database.exec("PRAGMA journal_mode=
|
|
407
|
+
database.exec("PRAGMA busy_timeout=180000; PRAGMA journal_mode=WAL; BEGIN IMMEDIATE;");
|
|
1108
408
|
try {
|
|
409
|
+
// Another process may have finished an update while this writer waited.
|
|
410
|
+
// Capture canonical inputs only after acquiring exclusive writer ownership.
|
|
411
|
+
sourceSnapshot = await sourceFingerprint(
|
|
412
|
+
workspaceRoot,
|
|
413
|
+
options.forceCanonical
|
|
414
|
+
? { verifyContent: true }
|
|
415
|
+
: { previous: persistedSourceCheckpoint(path) },
|
|
416
|
+
);
|
|
417
|
+
refresh = {
|
|
418
|
+
reused_sources: sourceSnapshot.reusedFiles,
|
|
419
|
+
processed_sources: sourceSnapshot.hashedFiles,
|
|
420
|
+
added_sources: 0,
|
|
421
|
+
changed_sources: 0,
|
|
422
|
+
removed_sources: 0,
|
|
423
|
+
canonical_runs_refolded: 0,
|
|
424
|
+
};
|
|
425
|
+
fingerprint = `${sourceSnapshot.fingerprint}\ncurrent_day=${saoPauloDay(new Date().toISOString())}`;
|
|
426
|
+
// Keep the previous canonical bytes available while the transaction
|
|
427
|
+
// decides which sources changed. `files` is a persisted view over
|
|
428
|
+
// typed technical payloads, so a matching checkpoint can be reused
|
|
429
|
+
// without touching the canonical filesystem again. A missing source
|
|
430
|
+
// is deliberately absent from `sourceSnapshot` and is therefore not
|
|
431
|
+
// copied into the next materialization.
|
|
432
|
+
const persistedFiles = new Map(
|
|
433
|
+
(() => {
|
|
434
|
+
try {
|
|
435
|
+
return database
|
|
436
|
+
.prepare("SELECT path, content, content_sha256 FROM zaip_vw_files")
|
|
437
|
+
.all() as Array<{ path: string; content: string; content_sha256: string }>;
|
|
438
|
+
} catch {
|
|
439
|
+
// An older or incomplete schema takes the bootstrap path below.
|
|
440
|
+
return [] as Array<{ path: string; content: string; content_sha256: string }>;
|
|
441
|
+
}
|
|
442
|
+
})().map((row) => [row.path, row] as const),
|
|
443
|
+
);
|
|
444
|
+
const existing = database
|
|
445
|
+
.prepare("SELECT name, type FROM sqlite_master WHERE name NOT LIKE 'sqlite_%'")
|
|
446
|
+
.all() as Array<{ name: string; type: string }>;
|
|
447
|
+
const hasPersistentSchema =
|
|
448
|
+
!options.forceCanonical &&
|
|
449
|
+
requiredPersistedTables.every((name) =>
|
|
450
|
+
existing.some((entry) => entry.type === "table" && entry.name === name),
|
|
451
|
+
) &&
|
|
452
|
+
requiredIndexes.every((name) =>
|
|
453
|
+
existing.some((entry) => entry.type === "index" && entry.name === name),
|
|
454
|
+
) &&
|
|
455
|
+
requiredViews.every((name) =>
|
|
456
|
+
existing.some((entry) => entry.type === "view" && entry.name === name),
|
|
457
|
+
) &&
|
|
458
|
+
(() => {
|
|
459
|
+
try {
|
|
460
|
+
const schemaVersion = database
|
|
461
|
+
.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key='schema_version'")
|
|
462
|
+
.get() as { value?: string } | undefined;
|
|
463
|
+
database
|
|
464
|
+
.prepare(
|
|
465
|
+
"SELECT source_path, ctime_ms, file_identity FROM zaip_vw_read_model_source_state LIMIT 1",
|
|
466
|
+
)
|
|
467
|
+
.all();
|
|
468
|
+
return schemaVersion?.value === readModelSchemaVersion;
|
|
469
|
+
} catch {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
})();
|
|
473
|
+
const persistedSourceHashes = new Map(
|
|
474
|
+
(() => {
|
|
475
|
+
try {
|
|
476
|
+
return database
|
|
477
|
+
.prepare("SELECT source_path, source_hash FROM zaip_vw_read_model_source_state")
|
|
478
|
+
.all() as Array<{ source_path: string; source_hash: string }>;
|
|
479
|
+
} catch {
|
|
480
|
+
return [] as Array<{ source_path: string; source_hash: string }>;
|
|
481
|
+
}
|
|
482
|
+
})().map((row) => [row.source_path, row.source_hash] as const),
|
|
483
|
+
);
|
|
484
|
+
refresh = {
|
|
485
|
+
...refresh,
|
|
486
|
+
added_sources: sourceSnapshot.candidates.filter(
|
|
487
|
+
(candidate) => !persistedSourceHashes.has(candidate.source),
|
|
488
|
+
).length,
|
|
489
|
+
changed_sources: sourceSnapshot.candidates.filter((candidate) => {
|
|
490
|
+
const previous = persistedSourceHashes.get(candidate.source);
|
|
491
|
+
return previous !== undefined && previous !== candidate.contentHash;
|
|
492
|
+
}).length,
|
|
493
|
+
removed_sources: [...persistedSourceHashes.keys()].filter(
|
|
494
|
+
(source) => !sourceSnapshot.candidates.some((candidate) => candidate.source === source),
|
|
495
|
+
).length,
|
|
496
|
+
};
|
|
497
|
+
const changedCandidates = sourceSnapshot.candidates.filter(
|
|
498
|
+
(candidate) => persistedSourceHashes.get(candidate.source) !== candidate.contentHash,
|
|
499
|
+
);
|
|
500
|
+
const removedSources = [...persistedSourceHashes.keys()].filter(
|
|
501
|
+
(source) => !sourceSnapshot.candidates.some((candidate) => candidate.source === source),
|
|
502
|
+
);
|
|
503
|
+
if (hasPersistentSchema && changedCandidates.length === 0 && removedSources.length === 0) {
|
|
504
|
+
const oldFingerprint = database
|
|
505
|
+
.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key='source_fingerprint'")
|
|
506
|
+
.get() as { value: string };
|
|
507
|
+
const checkpoint = persistedSourceCheckpoint(path);
|
|
508
|
+
for (const candidate of sourceSnapshot.candidates) {
|
|
509
|
+
const old = checkpoint.get(candidate.source);
|
|
510
|
+
if (
|
|
511
|
+
old &&
|
|
512
|
+
(old.mtimeMs !== candidate.mtimeMs ||
|
|
513
|
+
old.ctimeMs !== candidate.ctimeMs ||
|
|
514
|
+
old.fileIdentity !== candidate.fileIdentity)
|
|
515
|
+
)
|
|
516
|
+
database
|
|
517
|
+
.prepare(`UPDATE zaip_aios_activities SET payload_json=json_set(payload_json,'$.mtime_ms',?,'$.ctime_ms',?,'$.file_identity',?,'$.size_bytes',?)
|
|
518
|
+
WHERE payload_type='source_state' AND activity_key=?`)
|
|
519
|
+
.run(
|
|
520
|
+
String(candidate.mtimeMs),
|
|
521
|
+
String(candidate.ctimeMs),
|
|
522
|
+
candidate.fileIdentity,
|
|
523
|
+
candidate.size,
|
|
524
|
+
`source_state:${candidate.source}`,
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
database
|
|
528
|
+
.prepare(
|
|
529
|
+
"UPDATE zaip_aios_activities SET payload_json=json_set(payload_json,'$.value',?) WHERE payload_type='metadata' AND activity_key='metadata:source_fingerprint'",
|
|
530
|
+
)
|
|
531
|
+
.run(fingerprint);
|
|
532
|
+
if (
|
|
533
|
+
oldFingerprint.value.split("\ncurrent_day=")[1] !== fingerprint.split("\ncurrent_day=")[1]
|
|
534
|
+
) {
|
|
535
|
+
database.exec(
|
|
536
|
+
"DELETE FROM zaip_aios_activities WHERE payload_type IN ('cost_monitor_summary_month_cache','first_fold_monthly_quick_cache')",
|
|
537
|
+
);
|
|
538
|
+
refreshFirstFoldPayloads(workspaceRoot, database);
|
|
539
|
+
}
|
|
540
|
+
database.exec("COMMIT;");
|
|
541
|
+
return {
|
|
542
|
+
files: sourceSnapshot.candidates.length,
|
|
543
|
+
path,
|
|
544
|
+
cache: "rebuilt",
|
|
545
|
+
fingerprint_ms: fingerprintMs,
|
|
546
|
+
materialize_ms: Math.round(performance.now() - fingerprintStartedAt),
|
|
547
|
+
refresh,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
const candidateBySource = new Map(
|
|
551
|
+
sourceSnapshot.candidates.map((candidate) => [candidate.source, candidate]),
|
|
552
|
+
);
|
|
553
|
+
const changedSources = new Set([
|
|
554
|
+
...changedCandidates.map((candidate) => candidate.source),
|
|
555
|
+
...removedSources,
|
|
556
|
+
]);
|
|
557
|
+
const previousActivities = hasPersistentSchema
|
|
558
|
+
? (database
|
|
559
|
+
.prepare("SELECT * FROM zaip_aios_activities WHERE payload_type IS NULL")
|
|
560
|
+
.all() as Array<Record<string, any>>)
|
|
561
|
+
: [];
|
|
562
|
+
const previousRunIndex = hasPersistentSchema
|
|
563
|
+
? (database.prepare("SELECT * FROM zaip_vw_canonical_run_index").all() as Array<
|
|
564
|
+
Record<string, any>
|
|
565
|
+
>)
|
|
566
|
+
: [];
|
|
567
|
+
const previousUsage = hasPersistentSchema
|
|
568
|
+
? (database.prepare("SELECT * FROM zaip_vw_invocation_usage_segments").all() as Array<
|
|
569
|
+
Record<string, any>
|
|
570
|
+
>)
|
|
571
|
+
: [];
|
|
572
|
+
const previousDurations = hasPersistentSchema
|
|
573
|
+
? (database.prepare("SELECT * FROM zaip_vw_attributed_token_active_facts").all() as Array<
|
|
574
|
+
Record<string, any>
|
|
575
|
+
>)
|
|
576
|
+
: [];
|
|
577
|
+
const durationByRun = new Map(previousRunIndex.map((row) => [row.run_id, row.duration_ms]));
|
|
578
|
+
const intervalFor = (
|
|
579
|
+
row: Record<string, any>,
|
|
580
|
+
duration: number | null,
|
|
581
|
+
): TemporalInterval | null => {
|
|
582
|
+
if (!(row.total_tokens > 0) || duration === null || duration < 0) return null;
|
|
583
|
+
const startMs = Date.parse(row.source_started_at ?? row.occurred_at);
|
|
584
|
+
const endMs = Date.parse(row.source_finished_at ?? "") || startMs + duration;
|
|
585
|
+
return Number.isFinite(startMs) && Number.isFinite(endMs) && endMs > startMs
|
|
586
|
+
? { activityRowId: row.activity_row_id, startMs, endMs }
|
|
587
|
+
: null;
|
|
588
|
+
};
|
|
589
|
+
const oldIntervals = previousActivities.flatMap((row) => {
|
|
590
|
+
const interval = intervalFor(row, durationByRun.get(row.run_id) ?? null);
|
|
591
|
+
return interval ? [interval] : [];
|
|
592
|
+
});
|
|
593
|
+
const configurationChanged = [...changedSources].some(
|
|
594
|
+
(source) =>
|
|
595
|
+
source.startsWith(".agents/config/pricing/") ||
|
|
596
|
+
source === ".agents/config/agents/catalog.yaml" ||
|
|
597
|
+
source === ".agents/config/clients.yaml" ||
|
|
598
|
+
source.includes("/historical-developer-attribution/"),
|
|
599
|
+
);
|
|
600
|
+
const entityChanged = [...changedSources].some((source) =>
|
|
601
|
+
/^\.agents\/records\/(tasks|specs|prds)\//.test(source),
|
|
602
|
+
);
|
|
603
|
+
const changedCreationRunIds = new Set<string>();
|
|
604
|
+
if (hasPersistentSchema)
|
|
605
|
+
for (const source of changedSources) {
|
|
606
|
+
if (!source.startsWith(".agents/records/specs/")) continue;
|
|
607
|
+
const cached = database
|
|
608
|
+
.prepare(
|
|
609
|
+
"SELECT payload_json FROM zaip_aios_activities WHERE payload_type='parsed_source' AND activity_key=?",
|
|
610
|
+
)
|
|
611
|
+
.get(`parsed_source:entities:${source}`) as { payload_json: string } | undefined;
|
|
612
|
+
if (cached)
|
|
613
|
+
for (const [, entity] of JSON.parse(cached.payload_json) as Array<
|
|
614
|
+
[string, CanonicalEntity]
|
|
615
|
+
>)
|
|
616
|
+
if (entity.creationRunId) changedCreationRunIds.add(entity.creationRunId);
|
|
617
|
+
}
|
|
618
|
+
const dirtyRunIds = new Set(
|
|
619
|
+
previousActivities
|
|
620
|
+
.filter((row) => configurationChanged || changedSources.has(row.source_path))
|
|
621
|
+
.map((row) => String(row.run_id)),
|
|
622
|
+
);
|
|
623
|
+
// A parent identity change may affect descendants even when their own bytes are unchanged.
|
|
624
|
+
let expanded = true;
|
|
625
|
+
while (expanded) {
|
|
626
|
+
expanded = false;
|
|
627
|
+
for (const row of previousActivities)
|
|
628
|
+
if (dirtyRunIds.has(row.parent_run_id) && !dirtyRunIds.has(row.run_id)) {
|
|
629
|
+
dirtyRunIds.add(row.run_id);
|
|
630
|
+
expanded = true;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
const quote = (name: string) => `"${name.replaceAll('"', '""')}"`;
|
|
634
|
+
if (!hasPersistentSchema) {
|
|
635
|
+
for (const entry of existing.filter((entry) => entry.type === "view"))
|
|
636
|
+
database.exec(`DROP VIEW IF EXISTS ${quote(entry.name)}`);
|
|
637
|
+
for (const entry of existing.filter((entry) => entry.type === "table"))
|
|
638
|
+
database.exec(`DROP TABLE IF EXISTS ${quote(entry.name)}`);
|
|
639
|
+
} else {
|
|
640
|
+
// A refresh keeps the physical schema and source payload identities.
|
|
641
|
+
// Only derived facts are replaced; stable files/checkpoints survive
|
|
642
|
+
// untouched, while changed and removed sources are deleted below.
|
|
643
|
+
database.exec(`
|
|
644
|
+
DELETE FROM zaip_aios_activities
|
|
645
|
+
WHERE payload_type IS NOT NULL AND payload_type NOT IN ('file', 'source_state', 'parsed_source', 'usage_segment', 'run_index', 'duration_allocation', 'shared_creation_allocation', 'work_item', 'execution', 'change', 'decision', 'cost_dimension_aggregate_v1', 'cost_agent_invocation_projection_v1');
|
|
646
|
+
DELETE FROM zaip_dim_zaip_agent;
|
|
647
|
+
DELETE FROM zaip_dim_llm_model;
|
|
648
|
+
DELETE FROM zaip_dim_provider;
|
|
649
|
+
`);
|
|
650
|
+
try {
|
|
651
|
+
database.exec(
|
|
652
|
+
"DELETE FROM developer_cost_projection; DELETE FROM cost_invocation_dimension_facts;",
|
|
653
|
+
);
|
|
654
|
+
} catch {
|
|
655
|
+
// Older compatible models do not have every optional projection.
|
|
656
|
+
}
|
|
657
|
+
}
|
|
1109
658
|
database.exec(`
|
|
1110
|
-
CREATE TEMP TABLE files (path TEXT PRIMARY KEY, content TEXT NOT NULL, mtime_ms
|
|
1111
|
-
CREATE TEMP TABLE read_model_source_state (source_path TEXT PRIMARY KEY, source_hash TEXT NOT NULL, mtime_ms
|
|
659
|
+
CREATE TEMP TABLE files (path TEXT PRIMARY KEY, content TEXT NOT NULL, mtime_ms REAL NOT NULL, size_bytes INTEGER NOT NULL, content_sha256 TEXT NOT NULL, artifact_kind TEXT, artifact_id TEXT, artifact_occurred_at TEXT, artifact_occurred_day_sp TEXT, artifact_task_id TEXT, artifact_change_id TEXT, artifact_run_id TEXT);
|
|
660
|
+
CREATE TEMP TABLE read_model_source_state (source_path TEXT PRIMARY KEY, source_hash TEXT NOT NULL, mtime_ms TEXT NOT NULL, ctime_ms TEXT NOT NULL, size_bytes INTEGER NOT NULL, file_identity TEXT NOT NULL, source_kind TEXT NOT NULL, last_processed_at TEXT NOT NULL);
|
|
1112
661
|
CREATE TEMP TABLE read_model_metadata (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
1113
|
-
CREATE TEMP TABLE canonical_work_items (entity_kind TEXT NOT NULL, id TEXT NOT NULL, title TEXT, status TEXT, stage TEXT, risk TEXT, spec_id TEXT, change_id TEXT, completed_run_id TEXT, occurred_at TEXT NOT NULL, occurred_day_sp TEXT NOT NULL, PRIMARY KEY(entity_kind, id));
|
|
662
|
+
CREATE TEMP TABLE canonical_work_items (entity_kind TEXT NOT NULL, id TEXT NOT NULL, title TEXT, status TEXT, stage TEXT, risk TEXT, spec_id TEXT, change_id TEXT, creation_run_id TEXT, completed_run_id TEXT, occurred_at TEXT NOT NULL, occurred_day_sp TEXT NOT NULL, PRIMARY KEY(entity_kind, id));
|
|
1114
663
|
CREATE TEMP TABLE canonical_executions (id TEXT PRIMARY KEY, status TEXT, kind TEXT, origin_type TEXT, origin_id TEXT, created_at TEXT NOT NULL, occurred_day_sp TEXT NOT NULL, finished_at TEXT, source_path TEXT NOT NULL);
|
|
1115
664
|
CREATE TEMP TABLE canonical_changes (id TEXT PRIMARY KEY, title TEXT NOT NULL, status TEXT NOT NULL, risk TEXT NOT NULL, source_type TEXT NOT NULL, source_id TEXT NOT NULL, source_spec_id TEXT, source_prd_id TEXT, requirement_ids_json TEXT NOT NULL, acceptance_ids_json TEXT NOT NULL, task_ids_json TEXT NOT NULL, result TEXT, created_at TEXT NOT NULL, occurred_day_sp TEXT NOT NULL, source_path TEXT NOT NULL);
|
|
1116
665
|
CREATE TEMP TABLE canonical_decisions (id TEXT PRIMARY KEY, title TEXT NOT NULL, origin TEXT NOT NULL, scope_type TEXT NOT NULL, scope_id TEXT NOT NULL, created_at TEXT NOT NULL, occurred_day_sp TEXT NOT NULL, source_path TEXT NOT NULL);
|
|
@@ -1127,7 +676,7 @@ export async function rebuildSqliteReadModel(
|
|
|
1127
676
|
calculated_cost_usd REAL, recorded_cost_usd REAL, cost_variance_usd REAL,
|
|
1128
677
|
cost_usd REAL, cost_brl REAL, cost_method TEXT, pricing_model TEXT, pricing_source TEXT, fx_rate REAL
|
|
1129
678
|
);
|
|
1130
|
-
CREATE TABLE zaip_aios_activities (
|
|
679
|
+
CREATE TABLE IF NOT EXISTS zaip_aios_activities (
|
|
1131
680
|
activity_row_id TEXT PRIMARY KEY, activity_source_kind TEXT NOT NULL, activity_key TEXT NOT NULL,
|
|
1132
681
|
source_path TEXT NOT NULL, source_hash TEXT NOT NULL, source_occurred_at TEXT, recorded_at TEXT NOT NULL, version_activity_id TEXT,
|
|
1133
682
|
source_last_event_id TEXT, source_last_sequence INTEGER,
|
|
@@ -1146,13 +695,13 @@ export async function rebuildSqliteReadModel(
|
|
|
1146
695
|
payload_type TEXT, payload_json TEXT
|
|
1147
696
|
);`);
|
|
1148
697
|
database.exec(`
|
|
1149
|
-
CREATE TABLE zaip_dim_zaip_agent (
|
|
698
|
+
CREATE TABLE IF NOT EXISTS zaip_dim_zaip_agent (
|
|
1150
699
|
zaip_agent TEXT PRIMARY KEY, catalog_id TEXT, persona TEXT, job TEXT, role TEXT, catalog_hash TEXT
|
|
1151
700
|
);
|
|
1152
|
-
CREATE TABLE zaip_dim_llm_model (
|
|
701
|
+
CREATE TABLE IF NOT EXISTS zaip_dim_llm_model (
|
|
1153
702
|
model TEXT PRIMARY KEY
|
|
1154
703
|
);
|
|
1155
|
-
CREATE TABLE zaip_dim_provider (
|
|
704
|
+
CREATE TABLE IF NOT EXISTS zaip_dim_provider (
|
|
1156
705
|
provider TEXT PRIMARY KEY
|
|
1157
706
|
);
|
|
1158
707
|
`);
|
|
@@ -1165,11 +714,48 @@ export async function rebuildSqliteReadModel(
|
|
|
1165
714
|
attributed_token_active_ms REAL NOT NULL,
|
|
1166
715
|
PRIMARY KEY(activity_row_id, occurred_day_sp)
|
|
1167
716
|
);`);
|
|
717
|
+
database.exec(
|
|
718
|
+
"CREATE INDEX IF NOT EXISTS zaip_activities_payload ON zaip_aios_activities(payload_type, activity_key)",
|
|
719
|
+
);
|
|
720
|
+
const parsedSources = new SqliteParsedSourceCache(database);
|
|
721
|
+
const sourceHashFor = (source: string) => candidateBySource.get(source)?.contentHash ?? "";
|
|
722
|
+
const insertRows = (table: string, rows: Array<Record<string, any>>) => {
|
|
723
|
+
if (!rows.length) return;
|
|
724
|
+
const columns = Object.keys(rows[0]!);
|
|
725
|
+
const statement = database.prepare(
|
|
726
|
+
`INSERT INTO ${table}(${columns.join(",")}) VALUES (${columns.map(() => "?").join(",")})`,
|
|
727
|
+
);
|
|
728
|
+
for (const row of rows) statement.run(...columns.map((column) => row[column] ?? null));
|
|
729
|
+
};
|
|
730
|
+
insertRows(
|
|
731
|
+
"canonical_run_index",
|
|
732
|
+
previousRunIndex.filter((row) => !dirtyRunIds.has(row.run_id)),
|
|
733
|
+
);
|
|
734
|
+
const dirtyInvocationIds = new Set(
|
|
735
|
+
previousActivities
|
|
736
|
+
.filter((row) => dirtyRunIds.has(row.run_id))
|
|
737
|
+
.map((row) => row.agent_invocation_id),
|
|
738
|
+
);
|
|
739
|
+
insertRows(
|
|
740
|
+
"invocation_usage_segments",
|
|
741
|
+
previousUsage.filter((row) => !dirtyInvocationIds.has(row.agent_invocation_id)),
|
|
742
|
+
);
|
|
743
|
+
insertRows("attributed_token_active_facts", previousDurations);
|
|
744
|
+
for (const runId of dirtyRunIds)
|
|
745
|
+
database
|
|
746
|
+
.prepare("DELETE FROM zaip_aios_activities WHERE payload_type IS NULL AND run_id=?")
|
|
747
|
+
.run(runId);
|
|
748
|
+
for (const source of removedSources)
|
|
749
|
+
database
|
|
750
|
+
.prepare(
|
|
751
|
+
"DELETE FROM zaip_aios_activities WHERE payload_type='parsed_source' AND source_path=?",
|
|
752
|
+
)
|
|
753
|
+
.run(source);
|
|
1168
754
|
const insert = database.prepare(
|
|
1169
755
|
"INSERT INTO files(path, content, mtime_ms, size_bytes, content_sha256, artifact_kind, artifact_id, artifact_occurred_at, artifact_occurred_day_sp, artifact_task_id, artifact_change_id, artifact_run_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
1170
756
|
);
|
|
1171
757
|
const insertSourceState = database.prepare(
|
|
1172
|
-
"INSERT INTO read_model_source_state(source_path, source_hash, mtime_ms, size_bytes, source_kind, last_processed_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
758
|
+
"INSERT INTO read_model_source_state(source_path, source_hash, mtime_ms, ctime_ms, size_bytes, file_identity, source_kind, last_processed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
1173
759
|
);
|
|
1174
760
|
let files = 0;
|
|
1175
761
|
const skipped = 0;
|
|
@@ -1183,14 +769,25 @@ export async function rebuildSqliteReadModel(
|
|
|
1183
769
|
// Parallel reads reduce cold I/O latency, while the following ordered
|
|
1184
770
|
// loop retains deterministic SQLite inserts and canonical projections.
|
|
1185
771
|
const contents = await Promise.all(
|
|
1186
|
-
batch.map((candidate) =>
|
|
772
|
+
batch.map(async (candidate) => {
|
|
773
|
+
const previous = persistedFiles.get(candidate.source);
|
|
774
|
+
if (previous?.content_sha256 === candidate.contentHash) return previous.content;
|
|
775
|
+
// The fingerprint captured a stable stat/hash pair. Any read
|
|
776
|
+
// failure here must abort the transaction, preserving the prior
|
|
777
|
+
// complete model instead of treating an inaccessible file as a
|
|
778
|
+
// deletion.
|
|
779
|
+
const content = await readFile(candidate.path, "utf8");
|
|
780
|
+
if (createHash("sha256").update(content).digest("hex") !== candidate.contentHash)
|
|
781
|
+
throw new Error(`Canonical source changed during refresh: ${candidate.source}`);
|
|
782
|
+
return content;
|
|
783
|
+
}),
|
|
1187
784
|
);
|
|
1188
785
|
for (let index = 0; index < batch.length; index += 1) {
|
|
1189
786
|
const candidate = batch[index];
|
|
1190
787
|
const content = contents[index];
|
|
1191
788
|
if (candidate === undefined || content === undefined) continue;
|
|
1192
|
-
const { source, mtimeMs, size } = candidate;
|
|
1193
|
-
const sourceHash =
|
|
789
|
+
const { source, mtimeMs, ctimeMs, size, fileIdentity } = candidate;
|
|
790
|
+
const sourceHash = candidate.contentHash;
|
|
1194
791
|
const sourceKind = source.includes("/runs/")
|
|
1195
792
|
? "run"
|
|
1196
793
|
: source.includes("/artifacts/evidence/")
|
|
@@ -1200,7 +797,9 @@ export async function rebuildSqliteReadModel(
|
|
|
1200
797
|
: source.startsWith(".agents/config/")
|
|
1201
798
|
? "config"
|
|
1202
799
|
: "entity";
|
|
1203
|
-
const artifact =
|
|
800
|
+
const artifact = parsedSources.read("artifact", source, sourceHash, () =>
|
|
801
|
+
readCanonicalArtifact(source, content),
|
|
802
|
+
);
|
|
1204
803
|
insert.run(
|
|
1205
804
|
source,
|
|
1206
805
|
content,
|
|
@@ -1218,8 +817,10 @@ export async function rebuildSqliteReadModel(
|
|
|
1218
817
|
insertSourceState.run(
|
|
1219
818
|
source,
|
|
1220
819
|
sourceHash,
|
|
1221
|
-
mtimeMs,
|
|
820
|
+
String(mtimeMs),
|
|
821
|
+
String(ctimeMs),
|
|
1222
822
|
size,
|
|
823
|
+
fileIdentity,
|
|
1223
824
|
sourceKind,
|
|
1224
825
|
new Date().toISOString(),
|
|
1225
826
|
);
|
|
@@ -1234,13 +835,32 @@ export async function rebuildSqliteReadModel(
|
|
|
1234
835
|
);
|
|
1235
836
|
}
|
|
1236
837
|
reportLaunchProgress("projection_build", 74, "Organizando fatos e projeções do banco local.");
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
838
|
+
const canonicalRows = database
|
|
839
|
+
.prepare(
|
|
840
|
+
"SELECT path, content FROM files WHERE path LIKE '.agents/records/%' AND path NOT LIKE '.agents/records/runs/%'",
|
|
841
|
+
)
|
|
842
|
+
.all() as Array<{ path: string; content: string }>;
|
|
843
|
+
const canonicalEntities = new Map<string, CanonicalEntity>(
|
|
844
|
+
canonicalRows.flatMap((row) =>
|
|
845
|
+
parsedSources.read<Array<[string, CanonicalEntity]>>(
|
|
846
|
+
"entities",
|
|
847
|
+
row.path,
|
|
848
|
+
sourceHashFor(row.path),
|
|
849
|
+
() => [...readCanonicalEntities([row])],
|
|
850
|
+
),
|
|
851
|
+
),
|
|
1241
852
|
);
|
|
853
|
+
for (const row of canonicalRows)
|
|
854
|
+
if (changedSources.has(row.path) && row.path.startsWith(".agents/records/specs/"))
|
|
855
|
+
for (const [, entity] of parsedSources.read<Array<[string, CanonicalEntity]>>(
|
|
856
|
+
"entities",
|
|
857
|
+
row.path,
|
|
858
|
+
sourceHashFor(row.path),
|
|
859
|
+
() => [...readCanonicalEntities([row])],
|
|
860
|
+
))
|
|
861
|
+
if (entity.creationRunId) changedCreationRunIds.add(entity.creationRunId);
|
|
1242
862
|
const insertCanonicalWorkItem = database.prepare(
|
|
1243
|
-
"INSERT INTO canonical_work_items(entity_kind, id, title, status, stage, risk, spec_id, change_id, completed_run_id, occurred_at, occurred_day_sp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
863
|
+
"INSERT INTO canonical_work_items(entity_kind, id, title, status, stage, risk, spec_id, change_id, creation_run_id, completed_run_id, occurred_at, occurred_day_sp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
1244
864
|
);
|
|
1245
865
|
for (const entity of canonicalEntities.values()) {
|
|
1246
866
|
if (
|
|
@@ -1258,16 +878,25 @@ export async function rebuildSqliteReadModel(
|
|
|
1258
878
|
entity.risk,
|
|
1259
879
|
entity.specId,
|
|
1260
880
|
entity.changeId,
|
|
881
|
+
entity.creationRunId,
|
|
1261
882
|
entity.completedRunId,
|
|
1262
883
|
entity.occurredAt,
|
|
1263
884
|
saoPauloDay(entity.occurredAt),
|
|
1264
885
|
);
|
|
1265
886
|
}
|
|
1266
|
-
const canonicalLifecycle =
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
887
|
+
const canonicalLifecycle = {
|
|
888
|
+
executions: [] as CanonicalExecution[],
|
|
889
|
+
changes: [] as CanonicalChange[],
|
|
890
|
+
decisions: [] as CanonicalDecision[],
|
|
891
|
+
};
|
|
892
|
+
for (const row of canonicalRows) {
|
|
893
|
+
const parsed = parsedSources.read("lifecycle", row.path, sourceHashFor(row.path), () =>
|
|
894
|
+
readCanonicalLifecycleRecords([row]),
|
|
895
|
+
);
|
|
896
|
+
canonicalLifecycle.executions.push(...parsed.executions);
|
|
897
|
+
canonicalLifecycle.changes.push(...parsed.changes);
|
|
898
|
+
canonicalLifecycle.decisions.push(...parsed.decisions);
|
|
899
|
+
}
|
|
1271
900
|
const insertCanonicalExecution = database.prepare(
|
|
1272
901
|
"INSERT INTO canonical_executions(id, status, kind, origin_type, origin_id, created_at, occurred_day_sp, finished_at, source_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
1273
902
|
);
|
|
@@ -1356,6 +985,7 @@ export async function rebuildSqliteReadModel(
|
|
|
1356
985
|
"SELECT path, content, content_sha256 FROM files WHERE path LIKE '.agents/records/runs/%.jsonl' ORDER BY path",
|
|
1357
986
|
)
|
|
1358
987
|
.all() as Array<{ path: string; content: string; content_sha256: string }>;
|
|
988
|
+
|
|
1359
989
|
// Historical attribution is an append-only, self-verifying overlay. It
|
|
1360
990
|
// never changes a Run stream and therefore participates only after the
|
|
1361
991
|
// native identity carried by the Run itself.
|
|
@@ -1406,18 +1036,51 @@ export async function rebuildSqliteReadModel(
|
|
|
1406
1036
|
}
|
|
1407
1037
|
return null;
|
|
1408
1038
|
};
|
|
1409
|
-
const
|
|
1410
|
-
runRows.map((row) =>
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1039
|
+
const runRowsById = new Map(
|
|
1040
|
+
runRows.map((row) => [
|
|
1041
|
+
row.path
|
|
1042
|
+
.split("/")
|
|
1043
|
+
.at(-1)!
|
|
1044
|
+
.replace(/\.jsonl$/, ""),
|
|
1045
|
+
row,
|
|
1046
|
+
]),
|
|
1414
1047
|
);
|
|
1048
|
+
for (const previous of previousActivities) {
|
|
1049
|
+
const row = runRows.find((row) => row.path === previous.source_path);
|
|
1050
|
+
if (row) runRowsById.set(previous.run_id, row);
|
|
1051
|
+
}
|
|
1052
|
+
const folds = new Map<string, { row: (typeof runRows)[number]; fold: RunV2Fold }>();
|
|
1053
|
+
const readFold = (row: (typeof runRows)[number]) =>
|
|
1054
|
+
parsedSources.read("fold", row.path, row.content_sha256, () => {
|
|
1055
|
+
refresh.canonical_runs_refolded += 1;
|
|
1056
|
+
return foldRunV2Records(parseRunV2JsonLines(row.content));
|
|
1057
|
+
});
|
|
1058
|
+
const getFold = (runId: string) => {
|
|
1059
|
+
const available = folds.get(runId);
|
|
1060
|
+
if (available) return available;
|
|
1061
|
+
const row = runRowsById.get(runId);
|
|
1062
|
+
if (!row) return undefined;
|
|
1063
|
+
return { row, fold: readFold(row) };
|
|
1064
|
+
};
|
|
1065
|
+
for (const row of runRows) {
|
|
1066
|
+
const previous = previousActivities.find((activity) => activity.source_path === row.path);
|
|
1067
|
+
if (
|
|
1068
|
+
hasPersistentSchema &&
|
|
1069
|
+
previous &&
|
|
1070
|
+
!dirtyRunIds.has(previous.run_id) &&
|
|
1071
|
+
!changedSources.has(row.path)
|
|
1072
|
+
)
|
|
1073
|
+
continue;
|
|
1074
|
+
const fold = readFold(row);
|
|
1075
|
+
dirtyRunIds.add(fold.run.run_id);
|
|
1076
|
+
folds.set(fold.run.run_id, { row, fold });
|
|
1077
|
+
}
|
|
1415
1078
|
const rootRunId = (fold: RunV2Fold): string => {
|
|
1416
1079
|
let current = fold;
|
|
1417
1080
|
const seen = new Set<string>();
|
|
1418
1081
|
while (current.run.parent_run_id !== null && !seen.has(current.run.run_id)) {
|
|
1419
1082
|
seen.add(current.run.run_id);
|
|
1420
|
-
const parent =
|
|
1083
|
+
const parent = getFold(current.run.parent_run_id)?.fold;
|
|
1421
1084
|
if (parent === undefined) break;
|
|
1422
1085
|
current = parent;
|
|
1423
1086
|
}
|
|
@@ -1518,9 +1181,17 @@ export async function rebuildSqliteReadModel(
|
|
|
1518
1181
|
const insertCanonicalRun = database.prepare(
|
|
1519
1182
|
"INSERT INTO canonical_run_index(run_id, occurred_at, occurred_day_sp, task_id, phase, run_role, duration_ms, source_event_count, zaip_agent, provider, model, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
1520
1183
|
);
|
|
1521
|
-
let unassistedActivityCount =
|
|
1184
|
+
let unassistedActivityCount = previousActivities.filter(
|
|
1185
|
+
(row) => !dirtyRunIds.has(row.run_id) && row.agent_invocation_id === null,
|
|
1186
|
+
).length;
|
|
1522
1187
|
const excludedUnknownTokenRunCount = 0;
|
|
1523
|
-
const temporalIntervals: TemporalInterval[] =
|
|
1188
|
+
const temporalIntervals: TemporalInterval[] = oldIntervals.filter(
|
|
1189
|
+
(interval) =>
|
|
1190
|
+
!previousActivities.some(
|
|
1191
|
+
(row) => row.activity_row_id === interval.activityRowId && dirtyRunIds.has(row.run_id),
|
|
1192
|
+
),
|
|
1193
|
+
);
|
|
1194
|
+
const zeroDurationFacts: Array<{ activityRowId: string; day: string }> = [];
|
|
1524
1195
|
for (const { row, fold } of folds.values()) {
|
|
1525
1196
|
const started = fold.run;
|
|
1526
1197
|
const developer = resolveDeveloper(row, started.developer_identity ?? null);
|
|
@@ -1702,7 +1373,7 @@ export async function rebuildSqliteReadModel(
|
|
|
1702
1373
|
if (Number.isFinite(startMs) && Number.isFinite(endMs) && endMs > startMs)
|
|
1703
1374
|
temporalIntervals.push({ activityRowId, startMs, endMs });
|
|
1704
1375
|
else if (durations.effective === 0)
|
|
1705
|
-
|
|
1376
|
+
zeroDurationFacts.push({ activityRowId, day: saoPauloDay(started.occurred_at) });
|
|
1706
1377
|
}
|
|
1707
1378
|
if (agentRunId !== null) {
|
|
1708
1379
|
for (const record of usage) {
|
|
@@ -1749,8 +1420,74 @@ export async function rebuildSqliteReadModel(
|
|
|
1749
1420
|
}
|
|
1750
1421
|
if (!isAgentRun) unassistedActivityCount += 1;
|
|
1751
1422
|
}
|
|
1752
|
-
|
|
1423
|
+
const dirtyActivityIds = new Set(
|
|
1424
|
+
previousActivities
|
|
1425
|
+
.filter((row) => dirtyRunIds.has(row.run_id))
|
|
1426
|
+
.map((row) => String(row.activity_row_id)),
|
|
1427
|
+
);
|
|
1428
|
+
for (const row of database
|
|
1429
|
+
.prepare(
|
|
1430
|
+
"SELECT activity_row_id,run_id FROM zaip_aios_activities WHERE payload_type IS NULL",
|
|
1431
|
+
)
|
|
1432
|
+
.all() as Array<{ activity_row_id: string; run_id: string }>)
|
|
1433
|
+
if (dirtyRunIds.has(row.run_id)) dirtyActivityIds.add(row.activity_row_id);
|
|
1434
|
+
const temporalDelta = affectedTemporalIntervals(
|
|
1435
|
+
oldIntervals,
|
|
1436
|
+
temporalIntervals,
|
|
1437
|
+
dirtyActivityIds,
|
|
1438
|
+
);
|
|
1439
|
+
for (const id of temporalDelta.ids)
|
|
1440
|
+
database
|
|
1441
|
+
.prepare("DELETE FROM attributed_token_active_facts WHERE activity_row_id=?")
|
|
1442
|
+
.run(id);
|
|
1443
|
+
for (const fact of allocateAttributedTokenActiveMs(temporalDelta.intervals))
|
|
1753
1444
|
insertTemporalFact.run(fact.activityRowId, fact.day, fact.value);
|
|
1445
|
+
for (const fact of zeroDurationFacts) insertTemporalFact.run(fact.activityRowId, fact.day, 0);
|
|
1446
|
+
const updateDuration =
|
|
1447
|
+
database.prepare(`UPDATE zaip_aios_activities SET duration_ms=?,duration_provenance=?
|
|
1448
|
+
WHERE activity_row_id=? AND (duration_ms IS NOT ? OR duration_provenance IS NOT ?)`);
|
|
1449
|
+
const temporalRuns = new Set(dirtyRunIds);
|
|
1450
|
+
for (const id of temporalDelta.ids) {
|
|
1451
|
+
const fact = database
|
|
1452
|
+
.prepare(
|
|
1453
|
+
"SELECT SUM(attributed_token_active_ms) value,COUNT(*) count FROM attributed_token_active_facts WHERE activity_row_id=?",
|
|
1454
|
+
)
|
|
1455
|
+
.get(id) as { value: number | null; count: number };
|
|
1456
|
+
const provenance = fact.count ? "attributed_token_active" : null;
|
|
1457
|
+
updateDuration.run(fact.value, provenance, id, fact.value, provenance);
|
|
1458
|
+
const run = database
|
|
1459
|
+
.prepare("SELECT run_id FROM zaip_aios_activities WHERE activity_row_id=?")
|
|
1460
|
+
.get(id) as { run_id: string } | undefined;
|
|
1461
|
+
if (run) temporalRuns.add(run.run_id);
|
|
1462
|
+
}
|
|
1463
|
+
// Entity links and titles are projections of canonical metadata, not new Run usage.
|
|
1464
|
+
if (entityChanged)
|
|
1465
|
+
for (const row of database
|
|
1466
|
+
.prepare(
|
|
1467
|
+
"SELECT activity_row_id,task_id,spec_id,prd_id,change_id,execution_id,conversation_id FROM zaip_aios_activities WHERE payload_type IS NULL",
|
|
1468
|
+
)
|
|
1469
|
+
.all() as Array<Record<string, any>>) {
|
|
1470
|
+
const entity =
|
|
1471
|
+
row.task_id !== null
|
|
1472
|
+
? canonicalEntities.get(`task:${row.task_id}`)
|
|
1473
|
+
: row.spec_id !== null
|
|
1474
|
+
? canonicalEntities.get(`spec:${row.spec_id}`)
|
|
1475
|
+
: row.prd_id !== null
|
|
1476
|
+
? canonicalEntities.get(`prd:${row.prd_id}`)
|
|
1477
|
+
: null;
|
|
1478
|
+
const values = [
|
|
1479
|
+
entity?.kind ?? null,
|
|
1480
|
+
entity?.title ?? null,
|
|
1481
|
+
entity?.status ?? null,
|
|
1482
|
+
entity?.stage ?? null,
|
|
1483
|
+
entity?.risk ?? null,
|
|
1484
|
+
];
|
|
1485
|
+
database
|
|
1486
|
+
.prepare(
|
|
1487
|
+
`UPDATE zaip_aios_activities SET work_item_kind=?,title=?,work_item_status=?,work_stage=?,risk=? WHERE activity_row_id=? AND (work_item_kind IS NOT ? OR title IS NOT ? OR work_item_status IS NOT ? OR work_stage IS NOT ? OR risk IS NOT ?)`,
|
|
1488
|
+
)
|
|
1489
|
+
.run(...values, row.activity_row_id, ...values);
|
|
1490
|
+
}
|
|
1754
1491
|
// A single authoring sub-Run can create several sibling SPECs. Keep the
|
|
1755
1492
|
// Run canonical and attach deterministic shares only to the macro read
|
|
1756
1493
|
// model. Payload rows stay outside `activities`, preventing global
|
|
@@ -1763,10 +1500,23 @@ export async function rebuildSqliteReadModel(
|
|
|
1763
1500
|
entity,
|
|
1764
1501
|
]);
|
|
1765
1502
|
}
|
|
1503
|
+
for (const row of database
|
|
1504
|
+
.prepare(
|
|
1505
|
+
"SELECT DISTINCT run_id FROM zaip_aios_activities WHERE payload_type='shared_creation_allocation'",
|
|
1506
|
+
)
|
|
1507
|
+
.all() as Array<{ run_id: string }>)
|
|
1508
|
+
if (changedCreationRunIds.has(row.run_id) || temporalRuns.has(row.run_id))
|
|
1509
|
+
database
|
|
1510
|
+
.prepare(
|
|
1511
|
+
"DELETE FROM zaip_aios_activities WHERE payload_type='shared_creation_allocation' AND run_id=?",
|
|
1512
|
+
)
|
|
1513
|
+
.run(row.run_id);
|
|
1766
1514
|
const materializeSharedCreationAllocations = () => {
|
|
1767
1515
|
for (const [runId, recipients] of creationSpecsByRun) {
|
|
1768
1516
|
if (recipients.length === 0) continue;
|
|
1769
|
-
|
|
1517
|
+
if (hasPersistentSchema && !changedCreationRunIds.has(runId) && !temporalRuns.has(runId))
|
|
1518
|
+
continue;
|
|
1519
|
+
const source = getFold(runId);
|
|
1770
1520
|
if (source === undefined) continue;
|
|
1771
1521
|
const { fold, row } = source;
|
|
1772
1522
|
const started = fold.run;
|
|
@@ -1933,18 +1683,14 @@ export async function rebuildSqliteReadModel(
|
|
|
1933
1683
|
}
|
|
1934
1684
|
}
|
|
1935
1685
|
};
|
|
1936
|
-
database.exec(`UPDATE zaip_aios_activities
|
|
1937
|
-
SET duration_ms = (
|
|
1938
|
-
SELECT SUM(f.attributed_token_active_ms)
|
|
1939
|
-
FROM attributed_token_active_facts f
|
|
1940
|
-
WHERE f.activity_row_id=zaip_aios_activities.activity_row_id
|
|
1941
|
-
),
|
|
1942
|
-
duration_provenance = CASE
|
|
1943
|
-
WHEN EXISTS (SELECT 1 FROM attributed_token_active_facts f WHERE f.activity_row_id=zaip_aios_activities.activity_row_id)
|
|
1944
|
-
THEN 'attributed_token_active' ELSE NULL END
|
|
1945
|
-
WHERE payload_type IS NULL;`);
|
|
1946
1686
|
materializeSharedCreationAllocations();
|
|
1947
|
-
const canonicalRunCount =
|
|
1687
|
+
const canonicalRunCount = Number(
|
|
1688
|
+
(
|
|
1689
|
+
database
|
|
1690
|
+
.prepare("SELECT COUNT(*) count FROM zaip_aios_activities WHERE payload_type IS NULL")
|
|
1691
|
+
.get() as { count: number }
|
|
1692
|
+
).count,
|
|
1693
|
+
);
|
|
1948
1694
|
const invocationCount = (
|
|
1949
1695
|
database
|
|
1950
1696
|
.prepare(
|
|
@@ -1966,7 +1712,11 @@ export async function rebuildSqliteReadModel(
|
|
|
1966
1712
|
.get() as { count: number }
|
|
1967
1713
|
).count;
|
|
1968
1714
|
const activityCount = (
|
|
1969
|
-
database
|
|
1715
|
+
database
|
|
1716
|
+
.prepare(
|
|
1717
|
+
"SELECT COUNT(*) AS count FROM zaip_aios_activities WHERE payload_type IS NULL OR payload_type='shared_creation_allocation'",
|
|
1718
|
+
)
|
|
1719
|
+
.get() as {
|
|
1970
1720
|
count: number;
|
|
1971
1721
|
}
|
|
1972
1722
|
).count;
|
|
@@ -2029,29 +1779,28 @@ export async function rebuildSqliteReadModel(
|
|
|
2029
1779
|
// their technical inputs as typed payload activities so the on-disk
|
|
2030
1780
|
// model has one fact plus its three declared dimensions, never hidden
|
|
2031
1781
|
// canonical/index/cache tables.
|
|
2032
|
-
const persistPayload = (
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
'TECH-' || lower(hex(randomblob(16))), 'technical_payload', '${payloadType}:' || row_key,
|
|
2038
|
-
'sqlite://${payloadType}', '', datetime('now'), 'technical', 'technical', '${payloadType}', payload_json
|
|
2039
|
-
FROM (${selectSql});`);
|
|
1782
|
+
const persistPayload = (
|
|
1783
|
+
payloadType: string,
|
|
1784
|
+
selectSql: string,
|
|
1785
|
+
_preserveExistingSource = false,
|
|
1786
|
+
) => synchronizePayloadRows(database, payloadType, selectSql);
|
|
2040
1787
|
persistPayload(
|
|
2041
1788
|
"metadata",
|
|
2042
1789
|
"SELECT key AS row_key, json_object('key', key, 'value', value) AS payload_json FROM read_model_metadata",
|
|
2043
1790
|
);
|
|
2044
1791
|
persistPayload(
|
|
2045
1792
|
"source_state",
|
|
2046
|
-
"SELECT source_path AS row_key, json_object('source_path', source_path, 'source_hash', source_hash, 'mtime_ms', mtime_ms, 'size_bytes', size_bytes, 'source_kind', source_kind, 'last_processed_at', last_processed_at) AS payload_json FROM read_model_source_state",
|
|
1793
|
+
"SELECT source_path AS row_key, json_object('source_path', source_path, 'source_hash', source_hash, 'mtime_ms', mtime_ms, 'ctime_ms', ctime_ms, 'size_bytes', size_bytes, 'file_identity', file_identity, 'source_kind', source_kind, 'last_processed_at', last_processed_at) AS payload_json FROM read_model_source_state",
|
|
1794
|
+
true,
|
|
2047
1795
|
);
|
|
2048
1796
|
persistPayload(
|
|
2049
1797
|
"file",
|
|
2050
1798
|
"SELECT path AS row_key, json_object('path', path, 'content', content, 'mtime_ms', mtime_ms, 'size_bytes', size_bytes, 'content_sha256', content_sha256, 'artifact_kind', artifact_kind, 'artifact_id', artifact_id, 'artifact_occurred_at', artifact_occurred_at, 'artifact_occurred_day_sp', artifact_occurred_day_sp, 'artifact_task_id', artifact_task_id, 'artifact_change_id', artifact_change_id, 'artifact_run_id', artifact_run_id) AS payload_json FROM files",
|
|
1799
|
+
true,
|
|
2051
1800
|
);
|
|
2052
1801
|
persistPayload(
|
|
2053
1802
|
"work_item",
|
|
2054
|
-
"SELECT entity_kind || ':' || id AS row_key, json_object('entity_kind', entity_kind, 'id', id, 'title', title, 'status', status, 'stage', stage, 'risk', risk, 'spec_id', spec_id, 'change_id', change_id, 'completed_run_id', completed_run_id, 'occurred_at', occurred_at, 'occurred_day_sp', occurred_day_sp) AS payload_json FROM canonical_work_items",
|
|
1803
|
+
"SELECT entity_kind || ':' || id AS row_key, json_object('entity_kind', entity_kind, 'id', id, 'title', title, 'status', status, 'stage', stage, 'risk', risk, 'spec_id', spec_id, 'change_id', change_id, 'creation_run_id', creation_run_id, 'completed_run_id', completed_run_id, 'occurred_at', occurred_at, 'occurred_day_sp', occurred_day_sp) AS payload_json FROM canonical_work_items",
|
|
2055
1804
|
);
|
|
2056
1805
|
persistPayload(
|
|
2057
1806
|
"execution",
|
|
@@ -2071,7 +1820,7 @@ export async function rebuildSqliteReadModel(
|
|
|
2071
1820
|
);
|
|
2072
1821
|
persistPayload(
|
|
2073
1822
|
"usage_segment",
|
|
2074
|
-
"SELECT agent_invocation_id || ':' || rowid AS row_key, json_object('agent_invocation_id', agent_invocation_id, 'provider', provider, 'model', model, 'service_tier', service_tier, 'reasoning_effort', reasoning_effort, 'input_tokens', input_tokens, 'cached_input_tokens', cached_input_tokens, 'uncached_input_tokens', uncached_input_tokens, 'output_tokens', output_tokens, 'reasoning_tokens', reasoning_tokens, 'visible_output_tokens', visible_output_tokens, 'total_tokens', total_tokens, 'input_rate_usd_per_million', input_rate_usd_per_million, 'cached_input_rate_usd_per_million', cached_input_rate_usd_per_million, 'output_rate_usd_per_million', output_rate_usd_per_million, 'reasoning_rate_usd_per_million', reasoning_rate_usd_per_million, 'context_input_multiplier', context_input_multiplier, 'context_output_multiplier', context_output_multiplier, 'speed_cost_multiplier', speed_cost_multiplier, 'pricing_scenario', pricing_scenario, 'standard_cost_usd', standard_cost_usd, 'speed_uplift_usd', speed_uplift_usd, 'input_cost_usd', input_cost_usd, 'cached_input_cost_usd', cached_input_cost_usd, 'output_cost_usd', output_cost_usd, 'reasoning_cost_usd', reasoning_cost_usd, 'calculated_cost_usd', calculated_cost_usd, 'recorded_cost_usd', recorded_cost_usd, 'cost_variance_usd', cost_variance_usd, 'cost_usd', cost_usd, 'cost_brl', cost_brl, 'cost_method', cost_method, 'pricing_model', pricing_model, 'pricing_source', pricing_source, 'fx_rate', fx_rate) AS payload_json FROM invocation_usage_segments",
|
|
1823
|
+
"SELECT agent_invocation_id || ':' || ROW_NUMBER() OVER(PARTITION BY agent_invocation_id ORDER BY rowid) AS row_key, json_object('agent_invocation_id', agent_invocation_id, 'provider', provider, 'model', model, 'service_tier', service_tier, 'reasoning_effort', reasoning_effort, 'input_tokens', input_tokens, 'cached_input_tokens', cached_input_tokens, 'uncached_input_tokens', uncached_input_tokens, 'output_tokens', output_tokens, 'reasoning_tokens', reasoning_tokens, 'visible_output_tokens', visible_output_tokens, 'total_tokens', total_tokens, 'input_rate_usd_per_million', input_rate_usd_per_million, 'cached_input_rate_usd_per_million', cached_input_rate_usd_per_million, 'output_rate_usd_per_million', output_rate_usd_per_million, 'reasoning_rate_usd_per_million', reasoning_rate_usd_per_million, 'context_input_multiplier', context_input_multiplier, 'context_output_multiplier', context_output_multiplier, 'speed_cost_multiplier', speed_cost_multiplier, 'pricing_scenario', pricing_scenario, 'standard_cost_usd', standard_cost_usd, 'speed_uplift_usd', speed_uplift_usd, 'input_cost_usd', input_cost_usd, 'cached_input_cost_usd', cached_input_cost_usd, 'output_cost_usd', output_cost_usd, 'reasoning_cost_usd', reasoning_cost_usd, 'calculated_cost_usd', calculated_cost_usd, 'recorded_cost_usd', recorded_cost_usd, 'cost_variance_usd', cost_variance_usd, 'cost_usd', cost_usd, 'cost_brl', cost_brl, 'cost_method', cost_method, 'pricing_model', pricing_model, 'pricing_source', pricing_source, 'fx_rate', fx_rate) AS payload_json FROM invocation_usage_segments",
|
|
2075
1824
|
);
|
|
2076
1825
|
persistPayload(
|
|
2077
1826
|
"duration_allocation",
|
|
@@ -2093,45 +1842,41 @@ export async function rebuildSqliteReadModel(
|
|
|
2093
1842
|
profile.role,
|
|
2094
1843
|
catalogHash,
|
|
2095
1844
|
);
|
|
2096
|
-
const
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
for (const row of costDimensionAggregates[dimension]) {
|
|
2104
|
-
insertCostDimensionAggregate.run(
|
|
2105
|
-
`TECH-${randomUUID().toLowerCase()}`,
|
|
2106
|
-
`cost_dimension_aggregate_v1:${dimension}:${row.key}`,
|
|
2107
|
-
`sqlite://cost_dimension_aggregate_v1/${dimension}`,
|
|
2108
|
-
JSON.stringify({ schema_version: 1, dimension, ...row }),
|
|
2109
|
-
);
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
1845
|
+
const dimensionPayloads: Array<{ row_key: string; payload_json: string }> = [];
|
|
1846
|
+
for (const dimension of ["model", "provider"] as const)
|
|
1847
|
+
for (const row of costDimensionAggregates[dimension])
|
|
1848
|
+
dimensionPayloads.push({
|
|
1849
|
+
row_key: `${dimension}:${row.key}`,
|
|
1850
|
+
payload_json: JSON.stringify({ schema_version: 1, dimension, ...row }),
|
|
1851
|
+
});
|
|
2112
1852
|
for (const [dimension, projection] of Object.entries(costDimensionAggregates.remote)) {
|
|
2113
|
-
for (const row of projection.rows)
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
insertCostDimensionAggregate.run(
|
|
2122
|
-
`TECH-${randomUUID().toLowerCase()}`,
|
|
2123
|
-
`cost_dimension_aggregate_v1:${dimension}:__totals__`,
|
|
2124
|
-
`sqlite://cost_dimension_aggregate_v1/${dimension}`,
|
|
2125
|
-
JSON.stringify({
|
|
1853
|
+
for (const row of projection.rows)
|
|
1854
|
+
dimensionPayloads.push({
|
|
1855
|
+
row_key: `${dimension}:${row.key}`,
|
|
1856
|
+
payload_json: JSON.stringify({ schema_version: 1, dimension, row_kind: "row", ...row }),
|
|
1857
|
+
});
|
|
1858
|
+
dimensionPayloads.push({
|
|
1859
|
+
row_key: `${dimension}:__totals__`,
|
|
1860
|
+
payload_json: JSON.stringify({
|
|
2126
1861
|
schema_version: 1,
|
|
2127
1862
|
dimension,
|
|
2128
1863
|
row_kind: "totals",
|
|
2129
1864
|
...projection.totals,
|
|
2130
1865
|
}),
|
|
2131
|
-
);
|
|
1866
|
+
});
|
|
2132
1867
|
}
|
|
1868
|
+
synchronizePayloadRows(database, "cost_dimension_aggregate_v1", dimensionPayloads);
|
|
2133
1869
|
// Keep canonical TEMP references outside the public-view name rewriter.
|
|
2134
|
-
database.exec(
|
|
1870
|
+
database.exec(
|
|
1871
|
+
hasPersistentSchema
|
|
1872
|
+
? rebuildCostInvocationDimensionFactsSql
|
|
1873
|
+
.replace(
|
|
1874
|
+
"CREATE TABLE cost_invocation_dimension_facts AS",
|
|
1875
|
+
"DELETE FROM cost_invocation_dimension_facts; INSERT INTO cost_invocation_dimension_facts",
|
|
1876
|
+
)
|
|
1877
|
+
.replace(/CREATE INDEX cost_dimension_facts_[A-Za-z_]+ ON [^;]+;/g, "")
|
|
1878
|
+
: rebuildCostInvocationDimensionFactsSql,
|
|
1879
|
+
);
|
|
2135
1880
|
const viewSql = `
|
|
2136
1881
|
CREATE INDEX zaip_activities_agent_run ON zaip_aios_activities(activity_source_kind, agent_run_id, occurred_day_sp, occurred_at DESC);
|
|
2137
1882
|
CREATE INDEX zaip_activities_relationships ON zaip_aios_activities(task_id, spec_id, prd_id, execution_id, conversation_id);
|
|
@@ -2148,12 +1893,12 @@ export async function rebuildSqliteReadModel(
|
|
|
2148
1893
|
CREATE INDEX zaip_activities_period_invocation ON zaip_aios_activities(occurred_day_sp, agent_invocation_id)
|
|
2149
1894
|
WHERE agent_invocation_id IS NOT NULL;
|
|
2150
1895
|
CREATE INDEX zaip_activities_status ON zaip_aios_activities(activity_source_kind, status, phase);
|
|
2151
|
-
CREATE INDEX zaip_activities_payload ON zaip_aios_activities(payload_type, activity_key);
|
|
1896
|
+
CREATE INDEX IF NOT EXISTS zaip_activities_payload ON zaip_aios_activities(payload_type, activity_key);
|
|
2152
1897
|
CREATE INDEX zaip_activities_work_item_identity ON zaip_aios_activities(json_extract(payload_json, '$.entity_kind'), json_extract(payload_json, '$.id')) WHERE payload_type='work_item';
|
|
2153
1898
|
CREATE VIEW files AS SELECT json_extract(payload_json, '$.path') AS path, json_extract(payload_json, '$.content') AS content, json_extract(payload_json, '$.mtime_ms') AS mtime_ms, json_extract(payload_json, '$.size_bytes') AS size_bytes, json_extract(payload_json, '$.content_sha256') AS content_sha256, json_extract(payload_json, '$.artifact_kind') AS artifact_kind, json_extract(payload_json, '$.artifact_id') AS artifact_id, json_extract(payload_json, '$.artifact_occurred_at') AS artifact_occurred_at, json_extract(payload_json, '$.artifact_occurred_day_sp') AS artifact_occurred_day_sp, json_extract(payload_json, '$.artifact_task_id') AS artifact_task_id, json_extract(payload_json, '$.artifact_change_id') AS artifact_change_id, json_extract(payload_json, '$.artifact_run_id') AS artifact_run_id FROM zaip_aios_activities WHERE payload_type='file';
|
|
2154
|
-
CREATE VIEW read_model_source_state AS SELECT json_extract(payload_json, '$.source_path') AS source_path, json_extract(payload_json, '$.source_hash') AS source_hash, json_extract(payload_json, '$.mtime_ms') AS mtime_ms, json_extract(payload_json, '$.size_bytes') AS size_bytes, json_extract(payload_json, '$.source_kind') AS source_kind, json_extract(payload_json, '$.last_processed_at') AS last_processed_at FROM zaip_aios_activities WHERE payload_type='source_state';
|
|
1899
|
+
CREATE VIEW read_model_source_state AS SELECT json_extract(payload_json, '$.source_path') AS source_path, json_extract(payload_json, '$.source_hash') AS source_hash, json_extract(payload_json, '$.mtime_ms') AS mtime_ms, json_extract(payload_json, '$.ctime_ms') AS ctime_ms, json_extract(payload_json, '$.size_bytes') AS size_bytes, json_extract(payload_json, '$.file_identity') AS file_identity, json_extract(payload_json, '$.source_kind') AS source_kind, json_extract(payload_json, '$.last_processed_at') AS last_processed_at FROM zaip_aios_activities WHERE payload_type='source_state';
|
|
2155
1900
|
CREATE VIEW read_model_metadata AS SELECT json_extract(payload_json, '$.key') AS key, json_extract(payload_json, '$.value') AS value FROM zaip_aios_activities WHERE payload_type='metadata';
|
|
2156
|
-
CREATE VIEW canonical_work_items AS SELECT json_extract(payload_json, '$.entity_kind') AS entity_kind, json_extract(payload_json, '$.id') AS id, json_extract(payload_json, '$.title') AS title, json_extract(payload_json, '$.status') AS status, json_extract(payload_json, '$.stage') AS stage, json_extract(payload_json, '$.risk') AS risk, json_extract(payload_json, '$.spec_id') AS spec_id, json_extract(payload_json, '$.change_id') AS change_id, json_extract(payload_json, '$.completed_run_id') AS completed_run_id, json_extract(payload_json, '$.occurred_at') AS occurred_at, json_extract(payload_json, '$.occurred_day_sp') AS occurred_day_sp FROM zaip_aios_activities WHERE payload_type='work_item';
|
|
1901
|
+
CREATE VIEW canonical_work_items AS SELECT json_extract(payload_json, '$.entity_kind') AS entity_kind, json_extract(payload_json, '$.id') AS id, json_extract(payload_json, '$.title') AS title, json_extract(payload_json, '$.status') AS status, json_extract(payload_json, '$.stage') AS stage, json_extract(payload_json, '$.risk') AS risk, json_extract(payload_json, '$.spec_id') AS spec_id, json_extract(payload_json, '$.change_id') AS change_id, json_extract(payload_json, '$.creation_run_id') AS creation_run_id, json_extract(payload_json, '$.completed_run_id') AS completed_run_id, json_extract(payload_json, '$.occurred_at') AS occurred_at, json_extract(payload_json, '$.occurred_day_sp') AS occurred_day_sp FROM zaip_aios_activities WHERE payload_type='work_item';
|
|
2157
1902
|
CREATE VIEW canonical_executions AS SELECT json_extract(payload_json, '$.id') AS id, json_extract(payload_json, '$.status') AS status, json_extract(payload_json, '$.kind') AS kind, json_extract(payload_json, '$.origin_type') AS origin_type, json_extract(payload_json, '$.origin_id') AS origin_id, json_extract(payload_json, '$.created_at') AS created_at, json_extract(payload_json, '$.occurred_day_sp') AS occurred_day_sp, json_extract(payload_json, '$.finished_at') AS finished_at, json_extract(payload_json, '$.source_path') AS source_path FROM zaip_aios_activities WHERE payload_type='execution';
|
|
2158
1903
|
CREATE VIEW canonical_changes AS SELECT json_extract(payload_json, '$.id') AS id, json_extract(payload_json, '$.title') AS title, json_extract(payload_json, '$.status') AS status, json_extract(payload_json, '$.risk') AS risk, json_extract(payload_json, '$.source_type') AS source_type, json_extract(payload_json, '$.source_id') AS source_id, json_extract(payload_json, '$.source_spec_id') AS source_spec_id, json_extract(payload_json, '$.source_prd_id') AS source_prd_id, json_extract(payload_json, '$.requirement_ids_json') AS requirement_ids_json, json_extract(payload_json, '$.acceptance_ids_json') AS acceptance_ids_json, json_extract(payload_json, '$.task_ids_json') AS task_ids_json, json_extract(payload_json, '$.result') AS result, json_extract(payload_json, '$.created_at') AS created_at, json_extract(payload_json, '$.occurred_day_sp') AS occurred_day_sp, json_extract(payload_json, '$.source_path') AS source_path FROM zaip_aios_activities WHERE payload_type='change';
|
|
2159
1904
|
CREATE VIEW canonical_decisions AS SELECT json_extract(payload_json, '$.id') AS id, json_extract(payload_json, '$.title') AS title, json_extract(payload_json, '$.origin') AS origin, json_extract(payload_json, '$.scope_type') AS scope_type, json_extract(payload_json, '$.scope_id') AS scope_id, json_extract(payload_json, '$.created_at') AS created_at, json_extract(payload_json, '$.occurred_day_sp') AS occurred_day_sp, json_extract(payload_json, '$.source_path') AS source_path FROM zaip_aios_activities WHERE payload_type='decision';
|
|
@@ -2507,44 +2252,7 @@ export async function rebuildSqliteReadModel(
|
|
|
2507
2252
|
SUM(cost_monitor_total_duration)
|
|
2508
2253
|
FROM daily GROUP BY substr(day, 1, 4);
|
|
2509
2254
|
CREATE VIEW vw_cost_executions AS SELECT i.execution_id, i.execution_id AS title, e.status, NULL AS risk, MIN(i.occurred_at) AS occurred_at, MIN(i.occurred_day_sp) AS occurred_day_sp, 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, COUNT(*) AS invocation_count FROM invocations i LEFT JOIN executions e ON e.id = i.execution_id WHERE i.execution_id IS NOT NULL GROUP BY i.execution_id, e.status;
|
|
2510
|
-
CREATE VIEW vw_cost_agent_invocations AS
|
|
2511
|
-
SELECT i.*,
|
|
2512
|
-
COUNT(u.usage_index) AS pricing_segment_count,
|
|
2513
|
-
CASE WHEN COUNT(DISTINCT COALESCE(u.provider, '') || '|' || COALESCE(u.model, '') || '|' || COALESCE(u.service_tier, 'default') || '|' || COALESCE(CAST(u.context_input_multiplier AS TEXT), '') || '|' || COALESCE(CAST(u.context_output_multiplier AS TEXT), '')) > 1 THEN 1 ELSE 0 END AS pricing_mixed,
|
|
2514
|
-
SUM(u.uncached_input_tokens) AS uncached_input_tokens,
|
|
2515
|
-
SUM(u.visible_output_tokens) AS visible_output_tokens,
|
|
2516
|
-
CASE COUNT(DISTINCT u.input_rate_usd_per_million) WHEN 1 THEN MIN(u.input_rate_usd_per_million) ELSE NULL END AS input_rate_usd_per_million,
|
|
2517
|
-
CASE COUNT(DISTINCT u.cached_input_rate_usd_per_million) WHEN 1 THEN MIN(u.cached_input_rate_usd_per_million) ELSE NULL END AS cached_input_rate_usd_per_million,
|
|
2518
|
-
CASE COUNT(DISTINCT u.output_rate_usd_per_million) WHEN 1 THEN MIN(u.output_rate_usd_per_million) ELSE NULL END AS output_rate_usd_per_million,
|
|
2519
|
-
CASE COUNT(DISTINCT u.reasoning_rate_usd_per_million) WHEN 1 THEN MIN(u.reasoning_rate_usd_per_million) ELSE NULL END AS reasoning_rate_usd_per_million,
|
|
2520
|
-
CASE COUNT(DISTINCT u.context_input_multiplier) WHEN 1 THEN MIN(u.context_input_multiplier) ELSE NULL END AS context_input_multiplier,
|
|
2521
|
-
CASE COUNT(DISTINCT u.context_output_multiplier) WHEN 1 THEN MIN(u.context_output_multiplier) ELSE NULL END AS context_output_multiplier,
|
|
2522
|
-
CASE COUNT(DISTINCT u.speed_cost_multiplier) WHEN 1 THEN MIN(u.speed_cost_multiplier) ELSE NULL END AS speed_cost_multiplier,
|
|
2523
|
-
CASE COUNT(DISTINCT u.pricing_scenario) WHEN 1 THEN MIN(u.pricing_scenario) ELSE 'mixed' END AS pricing_scenario,
|
|
2524
|
-
SUM(u.standard_cost_usd) AS standard_cost_usd,
|
|
2525
|
-
SUM(u.speed_uplift_usd) AS speed_uplift_usd,
|
|
2526
|
-
SUM(u.input_cost_usd) AS input_cost_usd,
|
|
2527
|
-
SUM(u.cached_input_cost_usd) AS cached_input_cost_usd,
|
|
2528
|
-
SUM(u.output_cost_usd) AS output_cost_usd,
|
|
2529
|
-
SUM(u.reasoning_cost_usd) AS reasoning_cost_usd,
|
|
2530
|
-
SUM(u.calculated_cost_usd) AS calculated_cost_usd,
|
|
2531
|
-
SUM(u.recorded_cost_usd) AS recorded_cost_usd,
|
|
2532
|
-
CASE COUNT(DISTINCT CASE WHEN u.recorded_cost_usd IS NOT NULL THEN u.fx_rate END)
|
|
2533
|
-
WHEN 1 THEN MIN(CASE WHEN u.recorded_cost_usd IS NOT NULL THEN u.fx_rate END)
|
|
2534
|
-
ELSE NULL
|
|
2535
|
-
END AS recorded_fx_rate,
|
|
2536
|
-
SUM(CASE WHEN u.recorded_cost_usd IS NOT NULL THEN u.cost_brl END) AS recorded_brl,
|
|
2537
|
-
(SELECT json_extract(fx.content, '$.rate')
|
|
2538
|
-
FROM files fx
|
|
2539
|
-
WHERE fx.path LIKE '.agents/config/pricing/fx/%.json'
|
|
2540
|
-
ORDER BY CASE WHEN fx.path LIKE '%/current-usd-brl.json' THEN 0 ELSE 1 END,
|
|
2541
|
-
json_extract(fx.content, '$.as_of') DESC
|
|
2542
|
-
LIMIT 1) AS current_fx_rate,
|
|
2543
|
-
SUM(u.cost_variance_usd) AS cost_variance_usd,
|
|
2544
|
-
CASE COUNT(DISTINCT u.pricing_model) WHEN 1 THEN MIN(u.pricing_model) ELSE NULL END AS audit_pricing_model,
|
|
2545
|
-
CASE COUNT(DISTINCT u.pricing_source) WHEN 1 THEN MIN(u.pricing_source) ELSE NULL END AS audit_pricing_source
|
|
2546
|
-
FROM invocations i LEFT JOIN invocation_usage u USING(agent_invocation_id)
|
|
2547
|
-
GROUP BY i.agent_invocation_id;
|
|
2255
|
+
CREATE VIEW vw_cost_agent_invocations AS ${costInvocationProjectionSelect};
|
|
2548
2256
|
CREATE VIEW vw_cost_zaip_agents AS SELECT zaip_agent, SUM(llm_call_count) AS llm_call_count, SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS cost_usd, SUM(cost_brl) AS cost_brl, COUNT(*) AS invocation_count FROM invocations GROUP BY zaip_agent;
|
|
2549
2257
|
CREATE VIEW vw_cost_models AS WITH usage AS (SELECT model, agent_invocation_id, SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS cost_usd, SUM(cost_brl) AS cost_brl FROM invocation_usage GROUP BY model, agent_invocation_id), calls AS (SELECT i.agent_invocation_id, CASE WHEN (SELECT COUNT(DISTINCT m.model) FROM invocation_usage m WHERE m.agent_invocation_id = i.agent_invocation_id AND m.model IS NOT NULL) = 1 THEN i.llm_call_count END AS llm_call_count FROM invocations i) SELECT u.model, SUM(c.llm_call_count) AS llm_call_count, SUM(u.total_tokens) AS total_tokens, SUM(u.cost_usd) AS cost_usd, SUM(u.cost_brl) AS cost_brl, COUNT(*) AS invocation_count FROM usage u JOIN calls c USING(agent_invocation_id) GROUP BY u.model;
|
|
2550
2258
|
CREATE VIEW vw_cost_providers AS WITH usage AS (SELECT provider, agent_invocation_id, SUM(total_tokens) AS total_tokens, SUM(cost_usd) AS cost_usd, SUM(cost_brl) AS cost_brl FROM invocation_usage GROUP BY provider, agent_invocation_id), calls AS (SELECT i.agent_invocation_id, CASE WHEN (SELECT COUNT(DISTINCT p.provider) FROM invocation_usage p WHERE p.agent_invocation_id = i.agent_invocation_id AND p.provider IS NOT NULL) = 1 THEN i.llm_call_count END AS llm_call_count FROM invocations i) SELECT u.provider, SUM(c.llm_call_count) AS llm_call_count, SUM(u.total_tokens) AS total_tokens, SUM(u.cost_usd) AS cost_usd, SUM(u.cost_brl) AS cost_brl, COUNT(*) AS invocation_count FROM usage u JOIN calls c USING(agent_invocation_id) GROUP BY u.provider;
|
|
@@ -2963,7 +2671,7 @@ export async function rebuildSqliteReadModel(
|
|
|
2963
2671
|
CREATE VIEW prd_summary_daily AS SELECT * FROM vw_prd_summary_daily;
|
|
2964
2672
|
CREATE VIEW task_status_daily AS SELECT * FROM vw_task_status_daily;
|
|
2965
2673
|
CREATE VIEW task_risk_daily AS SELECT * FROM vw_task_risk_daily;
|
|
2966
|
-
|
|
2674
|
+
`;
|
|
2967
2675
|
// Every SQLite view has a single discoverable namespace. References in
|
|
2968
2676
|
// the view definitions are rewritten from their concise source aliases
|
|
2969
2677
|
// at build time, so DBeaver and direct SQL never expose unprefixed views.
|
|
@@ -2986,11 +2694,11 @@ export async function rebuildSqliteReadModel(
|
|
|
2986
2694
|
return statement;
|
|
2987
2695
|
},
|
|
2988
2696
|
);
|
|
2989
|
-
database.exec(deduplicatedViewSql);
|
|
2697
|
+
if (!hasPersistentSchema) database.exec(deduplicatedViewSql);
|
|
2990
2698
|
// One row per stable GitHub account. Costs are summed from the single
|
|
2991
2699
|
// canonical Run fact, while delivery is joined exclusively through the
|
|
2992
2700
|
// Task's completed_run_id (never inferred from a branch or latest Run).
|
|
2993
|
-
|
|
2701
|
+
const developerProjectionSql = `
|
|
2994
2702
|
CREATE TABLE developer_cost_projection AS
|
|
2995
2703
|
WITH completed AS (
|
|
2996
2704
|
SELECT a.developer_account_id, COUNT(DISTINCT w.id) AS completed_task_count
|
|
@@ -3017,51 +2725,91 @@ export async function rebuildSqliteReadModel(
|
|
|
3017
2725
|
GROUP BY a.developer_account_id
|
|
3018
2726
|
ORDER BY total_cost_brl DESC, account_id;
|
|
3019
2727
|
CREATE INDEX developer_cost_projection_lookup ON developer_cost_projection(account_id);
|
|
3020
|
-
|
|
2728
|
+
`;
|
|
2729
|
+
database.exec(
|
|
2730
|
+
hasPersistentSchema
|
|
2731
|
+
? developerProjectionSql
|
|
2732
|
+
.replace(
|
|
2733
|
+
"CREATE TABLE developer_cost_projection AS",
|
|
2734
|
+
"DELETE FROM developer_cost_projection; INSERT INTO developer_cost_projection",
|
|
2735
|
+
)
|
|
2736
|
+
.replace(
|
|
2737
|
+
"CREATE INDEX developer_cost_projection_lookup ON developer_cost_projection(account_id);",
|
|
2738
|
+
"",
|
|
2739
|
+
)
|
|
2740
|
+
: developerProjectionSql,
|
|
2741
|
+
);
|
|
3021
2742
|
materializeCostGroupDailyProjection(database);
|
|
3022
2743
|
materializeOperationsWorkProjection(database);
|
|
3023
2744
|
// Persist the complete unfiltered invocation projection while the seed
|
|
3024
2745
|
// still owns the compact canonical facts. Runtime pages can then slice
|
|
3025
2746
|
// 10/100 rows without expanding the pricing view for every request.
|
|
3026
|
-
const costAgentInvocationProjection = database
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
)
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
2747
|
+
const costAgentInvocationProjection = readCostInvocationProjection(database);
|
|
2748
|
+
synchronizePayloadRows(
|
|
2749
|
+
database,
|
|
2750
|
+
"cost_agent_invocation_projection_v1",
|
|
2751
|
+
costAgentInvocationProjection.flatMap((row) => {
|
|
2752
|
+
const invocationId = String(row.agent_invocation_id ?? "");
|
|
2753
|
+
return invocationId
|
|
2754
|
+
? [
|
|
2755
|
+
{
|
|
2756
|
+
row_key: invocationId,
|
|
2757
|
+
payload_json: JSON.stringify(row),
|
|
2758
|
+
occurred_day_sp:
|
|
2759
|
+
typeof row.occurred_day_sp === "string" ? row.occurred_day_sp : null,
|
|
2760
|
+
},
|
|
2761
|
+
]
|
|
2762
|
+
: [];
|
|
2763
|
+
}),
|
|
3038
2764
|
);
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
2765
|
+
// Source staging tables are no longer needed after the projections are
|
|
2766
|
+
// materialized. Release their TEMP names before installing the public
|
|
2767
|
+
// view aliases on this same connection.
|
|
2768
|
+
database.exec(`
|
|
2769
|
+
DROP TABLE IF EXISTS files;
|
|
2770
|
+
DROP TABLE IF EXISTS read_model_source_state;
|
|
2771
|
+
DROP TABLE IF EXISTS read_model_metadata;
|
|
2772
|
+
DROP TABLE IF EXISTS canonical_work_items;
|
|
2773
|
+
DROP TABLE IF EXISTS canonical_executions;
|
|
2774
|
+
DROP TABLE IF EXISTS canonical_changes;
|
|
2775
|
+
DROP TABLE IF EXISTS canonical_decisions;
|
|
2776
|
+
DROP TABLE IF EXISTS canonical_run_index;
|
|
2777
|
+
DROP TABLE IF EXISTS invocation_usage_segments;
|
|
2778
|
+
`);
|
|
2779
|
+
refreshFirstFoldPayloads(workspaceRoot, database);
|
|
3050
2780
|
database.exec("COMMIT;");
|
|
3051
2781
|
reportLaunchProgress(
|
|
3052
2782
|
"projection_finalize",
|
|
3053
2783
|
80,
|
|
3054
|
-
"Finalizando índices
|
|
2784
|
+
"Finalizando índices do banco persistente do dashboard.",
|
|
3055
2785
|
);
|
|
3056
2786
|
completedFiles = files;
|
|
3057
2787
|
} catch (error) {
|
|
3058
2788
|
database.exec("ROLLBACK;");
|
|
3059
2789
|
throw error;
|
|
3060
2790
|
}
|
|
2791
|
+
// Reclaim the transaction log when no reader pins an older snapshot.
|
|
2792
|
+
// With the default zero busy timeout this never waits on a dashboard read;
|
|
2793
|
+
// a busy checkpoint leaves the valid WAL for the next synchronization.
|
|
2794
|
+
database.exec("PRAGMA busy_timeout=0; PRAGMA wal_checkpoint(TRUNCATE);");
|
|
3061
2795
|
} finally {
|
|
3062
2796
|
database.close();
|
|
3063
2797
|
}
|
|
3064
|
-
const materializeStartedAt =
|
|
2798
|
+
const materializeStartedAt = fingerprintStartedAt + fingerprintMs;
|
|
2799
|
+
reportLaunchProgress("snapshot_ready", 86, "O banco SQLite persistente está pronto.");
|
|
2800
|
+
validateSqliteReadModelSchema(path);
|
|
2801
|
+
return {
|
|
2802
|
+
files: completedFiles,
|
|
2803
|
+
path,
|
|
2804
|
+
cache: "rebuilt",
|
|
2805
|
+
fingerprint_ms: fingerprintMs,
|
|
2806
|
+
materialize_ms: Math.round(performance.now() - materializeStartedAt),
|
|
2807
|
+
refresh,
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
function refreshFirstFoldPayloads(workspaceRoot: string, database: DatabaseSync): void {
|
|
2812
|
+
const reportLaunchProgress = reportSqliteReadModelLaunchProgress;
|
|
3065
2813
|
const timed = <T>(phase: string, action: () => T) =>
|
|
3066
2814
|
measureFirstFold(
|
|
3067
2815
|
process.env.ZAIP_DASHBOARD_LAUNCH_PROGRESS === "1",
|
|
@@ -3069,11 +2817,10 @@ export async function rebuildSqliteReadModel(
|
|
|
3069
2817
|
phase,
|
|
3070
2818
|
action,
|
|
3071
2819
|
);
|
|
3072
|
-
//
|
|
3073
|
-
//
|
|
3074
|
-
//
|
|
3075
|
-
|
|
3076
|
-
const firstFoldFilesystem = new SqliteReadModelFileSystem(workspaceRoot);
|
|
2820
|
+
// This reader shares the writer connection, therefore it sees the
|
|
2821
|
+
// uncommitted canonical facts and projections. Its payloads join the
|
|
2822
|
+
// same BEGIN IMMEDIATE transaction below.
|
|
2823
|
+
const firstFoldFilesystem = new SqliteReadModelFileSystem(workspaceRoot, database);
|
|
3077
2824
|
const firstFoldViewport = timed("first_fold_viewport", () =>
|
|
3078
2825
|
firstFoldFilesystem.firstFoldViewportAllTime(),
|
|
3079
2826
|
);
|
|
@@ -3119,7 +2866,7 @@ export async function rebuildSqliteReadModel(
|
|
|
3119
2866
|
top_tasks: timed("first_fold_month_top_tasks", () => firstFoldFilesystem.topTasks(quickMonth)),
|
|
3120
2867
|
};
|
|
3121
2868
|
firstFoldFilesystem.close();
|
|
3122
|
-
|
|
2869
|
+
persistFirstFoldPayloadsInDatabase(database, {
|
|
3123
2870
|
first_fold_viewport_cache: JSON.stringify(firstFoldViewport),
|
|
3124
2871
|
first_fold_all_time_cache: JSON.stringify(firstFoldAllTime),
|
|
3125
2872
|
first_fold_all_time_board_cache: JSON.stringify(firstFoldAllTimeBoard),
|
|
@@ -3130,34 +2877,6 @@ export async function rebuildSqliteReadModel(
|
|
|
3130
2877
|
}),
|
|
3131
2878
|
first_fold_monthly_quick_cache: JSON.stringify(firstFoldMonthlyQuick),
|
|
3132
2879
|
});
|
|
3133
|
-
try {
|
|
3134
|
-
await rename(temporaryPath, cachePath);
|
|
3135
|
-
} catch (error: unknown) {
|
|
3136
|
-
// A concurrent builder wins only when it published a complete seed.
|
|
3137
|
-
if (
|
|
3138
|
-
readSnapshotFileCount(
|
|
3139
|
-
cachePath,
|
|
3140
|
-
fingerprint,
|
|
3141
|
-
readModelSchemaVersion,
|
|
3142
|
-
hasReusableSnapshotPayloads,
|
|
3143
|
-
) === null
|
|
3144
|
-
) {
|
|
3145
|
-
await rm(temporaryPath, { force: true });
|
|
3146
|
-
throw error;
|
|
3147
|
-
}
|
|
3148
|
-
await rm(temporaryPath, { force: true });
|
|
3149
|
-
await materializeSnapshot(cachePath, path);
|
|
3150
|
-
}
|
|
3151
|
-
reportLaunchProgress("snapshot_ready", 86, "A cópia privada do banco local está pronta.");
|
|
3152
|
-
validateSqliteReadModelSchema(path);
|
|
3153
|
-
await pruneSnapshotCache(dirname(cachePath), cachePath);
|
|
3154
|
-
return {
|
|
3155
|
-
files: completedFiles,
|
|
3156
|
-
path,
|
|
3157
|
-
cache: "rebuilt",
|
|
3158
|
-
fingerprint_ms: fingerprintMs,
|
|
3159
|
-
materialize_ms: Math.round(performance.now() - materializeStartedAt),
|
|
3160
|
-
};
|
|
3161
2880
|
}
|
|
3162
2881
|
|
|
3163
2882
|
export type SqliteFirstFoldAggregate = {
|
|
@@ -3353,10 +3072,15 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3353
3072
|
return rows;
|
|
3354
3073
|
}
|
|
3355
3074
|
private database: DatabaseSync;
|
|
3075
|
+
private readonly ownsDatabase: boolean;
|
|
3356
3076
|
|
|
3357
|
-
constructor(root: string) {
|
|
3077
|
+
constructor(root: string, database?: DatabaseSync) {
|
|
3358
3078
|
super(root);
|
|
3359
|
-
this.
|
|
3079
|
+
this.ownsDatabase = database === undefined;
|
|
3080
|
+
this.database = database ?? new DatabaseSync(sqliteReadModelPath(root), { readOnly: true });
|
|
3081
|
+
// The aliases are TEMP views on this connection. The rebuilding writer
|
|
3082
|
+
// needs them too because first-fold readers deliberately query through
|
|
3083
|
+
// the same uncommitted transaction.
|
|
3360
3084
|
installLegacyViewAliases(this.database);
|
|
3361
3085
|
}
|
|
3362
3086
|
|
|
@@ -3417,7 +3141,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3417
3141
|
}
|
|
3418
3142
|
|
|
3419
3143
|
close(): void {
|
|
3420
|
-
this.database.close();
|
|
3144
|
+
if (this.ownsDatabase) this.database.close();
|
|
3421
3145
|
}
|
|
3422
3146
|
|
|
3423
3147
|
private reopen(): void {
|
|
@@ -5038,6 +4762,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5038
4762
|
if (cached !== undefined)
|
|
5039
4763
|
return structuredClone(cached) as ReturnType<SqliteReadModelFileSystem["uncachedViewPage"]>;
|
|
5040
4764
|
const result = this.uncachedViewPage(view, input);
|
|
4765
|
+
if (view === "cost_monitor_zaip_agents") attachAgentProductivity(this.database, input, result);
|
|
5041
4766
|
if (this.viewPageResultCache.size >= 256) {
|
|
5042
4767
|
const oldest = this.viewPageResultCache.keys().next().value as string | undefined;
|
|
5043
4768
|
if (oldest !== undefined) this.viewPageResultCache.delete(oldest);
|
|
@@ -5340,9 +5065,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5340
5065
|
};
|
|
5341
5066
|
const filteredCostGroup = filteredCostGroups[view];
|
|
5342
5067
|
if (
|
|
5343
|
-
(filteredCostGroup === "agent" ||
|
|
5344
|
-
filteredCostGroup === "model" ||
|
|
5345
|
-
filteredCostGroup === "provider") &&
|
|
5068
|
+
(filteredCostGroup === "agent" || filteredCostGroup === "provider") &&
|
|
5346
5069
|
isDateOnlyCostPage(input)
|
|
5347
5070
|
) {
|
|
5348
5071
|
const from = costMonitorFilterDay(input.from) ?? "0000-01-01";
|
|
@@ -5390,6 +5113,11 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5390
5113
|
json_extract(payload_json, '$.label') AS label,
|
|
5391
5114
|
json_extract(payload_json, '$.invocation_count') AS invocation_count,
|
|
5392
5115
|
json_extract(payload_json, '$.task_count') AS task_count,
|
|
5116
|
+
json_extract(payload_json, '$.conversation_count') AS conversation_count,
|
|
5117
|
+
json_extract(payload_json, '$.prd_count') AS prd_count,
|
|
5118
|
+
json_extract(payload_json, '$.spec_count') AS spec_count,
|
|
5119
|
+
json_extract(payload_json, '$.test_count') AS validation_count,
|
|
5120
|
+
json_extract(payload_json, '$.evidence_count') AS evidence_count,
|
|
5393
5121
|
json_extract(payload_json, '$.total_duration') AS total_duration,
|
|
5394
5122
|
json_extract(payload_json, '$.source_event_count') AS source_event_count,
|
|
5395
5123
|
json_extract(payload_json, '$.total_tokens') AS total_tokens,
|
|
@@ -5400,18 +5128,77 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5400
5128
|
AND json_extract(payload_json, '$.dimension')=?
|
|
5401
5129
|
ORDER BY total_cost_brl DESC, key`)
|
|
5402
5130
|
.all(dimension) as Array<Record<string, unknown>>;
|
|
5131
|
+
// The Models surface is an explicit comparison of identified models.
|
|
5132
|
+
// Keep unattributed facts in storage and in the other dimensions, but
|
|
5133
|
+
// do not let their sentinel become a displayed model or its footer.
|
|
5134
|
+
const visiblePayloadRows =
|
|
5135
|
+
dimension === "model"
|
|
5136
|
+
? payloadRows.filter((row) => row.key !== "unavailable" && row.key !== null)
|
|
5137
|
+
: payloadRows;
|
|
5403
5138
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5404
5139
|
const offset = Math.max(0, input.offset ?? 0);
|
|
5405
|
-
const rows =
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5140
|
+
const rows = visiblePayloadRows.slice(offset, offset + limit);
|
|
5141
|
+
// Older cached aggregate payloads predate conversation_count. Route
|
|
5142
|
+
// Models through the physical fact query rather than presenting a
|
|
5143
|
+
// misleading zero until the next projection refresh persists it.
|
|
5144
|
+
const modelConversationPayloadComplete =
|
|
5145
|
+
dimension !== "model" ||
|
|
5146
|
+
visiblePayloadRows.every((row) => typeof row.conversation_count === "number");
|
|
5147
|
+
const modelNumericFields = [
|
|
5148
|
+
"invocation_count",
|
|
5149
|
+
"task_count",
|
|
5150
|
+
"conversation_count",
|
|
5151
|
+
"prd_count",
|
|
5152
|
+
"spec_count",
|
|
5153
|
+
"validation_count",
|
|
5154
|
+
"evidence_count",
|
|
5155
|
+
"total_duration",
|
|
5156
|
+
"source_event_count",
|
|
5157
|
+
"total_tokens",
|
|
5158
|
+
"total_cost_usd",
|
|
5159
|
+
"total_cost_brl",
|
|
5160
|
+
];
|
|
5161
|
+
if (dimension === "model") {
|
|
5162
|
+
for (const row of visiblePayloadRows)
|
|
5163
|
+
for (const field of modelNumericFields)
|
|
5164
|
+
row[field] = typeof row[field] === "number" ? row[field] : 0;
|
|
5165
|
+
}
|
|
5166
|
+
const nullableSum = (field: string) =>
|
|
5167
|
+
visiblePayloadRows.some((row) => typeof row[field] === "number")
|
|
5168
|
+
? visiblePayloadRows.reduce(
|
|
5169
|
+
(sum, row) => sum + (typeof row[field] === "number" ? Number(row[field]) : 0),
|
|
5170
|
+
0,
|
|
5171
|
+
)
|
|
5172
|
+
: null;
|
|
5173
|
+
if (modelConversationPayloadComplete)
|
|
5174
|
+
return {
|
|
5175
|
+
data_source: "sqlite_read_model" as const,
|
|
5176
|
+
view,
|
|
5177
|
+
offset,
|
|
5178
|
+
limit,
|
|
5179
|
+
total_count: visiblePayloadRows.length,
|
|
5180
|
+
has_more: offset + rows.length < visiblePayloadRows.length,
|
|
5181
|
+
totals: {
|
|
5182
|
+
invocation_count: visiblePayloadRows.reduce(
|
|
5183
|
+
(sum, row) => sum + Number(row.invocation_count ?? 0),
|
|
5184
|
+
0,
|
|
5185
|
+
),
|
|
5186
|
+
task_count: nullableSum("task_count") ?? (dimension === "model" ? 0 : null),
|
|
5187
|
+
conversation_count:
|
|
5188
|
+
nullableSum("conversation_count") ?? (dimension === "model" ? 0 : null),
|
|
5189
|
+
prd_count: nullableSum("prd_count") ?? (dimension === "model" ? 0 : null),
|
|
5190
|
+
spec_count: nullableSum("spec_count") ?? (dimension === "model" ? 0 : null),
|
|
5191
|
+
validation_count: nullableSum("validation_count") ?? (dimension === "model" ? 0 : null),
|
|
5192
|
+
evidence_count: nullableSum("evidence_count") ?? (dimension === "model" ? 0 : null),
|
|
5193
|
+
total_duration: nullableSum("total_duration") ?? (dimension === "model" ? 0 : null),
|
|
5194
|
+
source_event_count:
|
|
5195
|
+
nullableSum("source_event_count") ?? (dimension === "model" ? 0 : null),
|
|
5196
|
+
total_tokens: nullableSum("total_tokens") ?? (dimension === "model" ? 0 : null),
|
|
5197
|
+
total_cost_usd: nullableSum("total_cost_usd") ?? (dimension === "model" ? 0 : null),
|
|
5198
|
+
total_cost_brl: nullableSum("total_cost_brl") ?? (dimension === "model" ? 0 : null),
|
|
5199
|
+
},
|
|
5200
|
+
rows,
|
|
5201
|
+
};
|
|
5415
5202
|
}
|
|
5416
5203
|
if (filteredCostGroup === "agent") {
|
|
5417
5204
|
const clauses = ["i.zaip_agent IS NOT NULL"];
|
|
@@ -5841,7 +5628,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5841
5628
|
SELECT agent_invocation_id, COUNT(DISTINCT model_key) AS model_count
|
|
5842
5629
|
FROM model_segments WHERE model_key <> 'unavailable' GROUP BY agent_invocation_id
|
|
5843
5630
|
), filtered_segments AS (
|
|
5844
|
-
SELECT s.*, i.task_id, i.duration_ms, i.source_event_count, km.model_count
|
|
5631
|
+
SELECT s.*, i.task_id, i.conversation_id, i.duration_ms, i.source_event_count, km.model_count
|
|
5845
5632
|
FROM segments s JOIN invocations i USING(agent_invocation_id)
|
|
5846
5633
|
LEFT JOIN tasks t ON t.id=i.task_id LEFT JOIN known_models km USING(agent_invocation_id)
|
|
5847
5634
|
WHERE ${where}`;
|
|
@@ -5859,7 +5646,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5859
5646
|
SELECT key, SUM(total_tokens) AS total_tokens, SUM(total_cost_usd) AS total_cost_usd, SUM(total_cost_brl) AS total_cost_brl
|
|
5860
5647
|
FROM financial_components GROUP BY key
|
|
5861
5648
|
), population_models AS (
|
|
5862
|
-
SELECT agent_invocation_id, model_key, MAX(task_id) AS task_id, MAX(duration_ms) AS duration_ms,
|
|
5649
|
+
SELECT agent_invocation_id, model_key, MAX(task_id) AS task_id, MAX(conversation_id) AS conversation_id, MAX(duration_ms) AS duration_ms,
|
|
5863
5650
|
MAX(source_event_count) AS source_event_count, MAX(model_count) AS model_count
|
|
5864
5651
|
FROM filtered_segments GROUP BY agent_invocation_id, model_key
|
|
5865
5652
|
), population_ranked AS (
|
|
@@ -5872,52 +5659,150 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
5872
5659
|
SUM(source_event_count) AS source_event_count
|
|
5873
5660
|
FROM population_ranked WHERE model_rank = 1 AND model_count = 1
|
|
5874
5661
|
GROUP BY model_key
|
|
5875
|
-
),
|
|
5662
|
+
), agent_task_models AS (
|
|
5876
5663
|
SELECT task_id, model_key
|
|
5877
|
-
FROM population_models WHERE task_id IS NOT NULL
|
|
5664
|
+
FROM population_models WHERE task_id IS NOT NULL AND model_key <> 'unavailable'
|
|
5878
5665
|
GROUP BY task_id, model_key
|
|
5666
|
+
), run_conversations AS (
|
|
5667
|
+
SELECT run_id, MAX(conversation_id) AS conversation_id
|
|
5668
|
+
FROM activities
|
|
5669
|
+
WHERE run_id IS NOT NULL AND conversation_id IS NOT NULL
|
|
5670
|
+
GROUP BY run_id
|
|
5671
|
+
), direct_model_activities AS (
|
|
5672
|
+
SELECT CASE WHEN a.model IS NULL OR trim(a.model) = '' OR lower(a.model) IN ('unavailable', 'unknown', 'mixed') THEN 'unavailable' ELSE a.model END AS model_key,
|
|
5673
|
+
a.task_id, COALESCE(a.conversation_id, run_conversations.conversation_id) AS conversation_id,
|
|
5674
|
+
a.activity_source_kind
|
|
5675
|
+
FROM activities a
|
|
5676
|
+
LEFT JOIN tasks activity_task ON activity_task.id=a.task_id
|
|
5677
|
+
LEFT JOIN run_conversations ON run_conversations.run_id=a.run_id
|
|
5678
|
+
WHERE ${activityClauses.join(" AND ")}
|
|
5679
|
+
AND (a.total_tokens IS NOT NULL OR a.cost_usd IS NOT NULL OR a.cost_brl IS NOT NULL)
|
|
5680
|
+
), direct_task_models AS (
|
|
5681
|
+
SELECT task_id, model_key FROM direct_model_activities
|
|
5682
|
+
-- A legacy task_id on an unassisted turn is correlation metadata, not
|
|
5683
|
+
-- an agent-run Task participation. That turn still contributes its
|
|
5684
|
+
-- conversation below.
|
|
5685
|
+
WHERE task_id IS NOT NULL AND COALESCE(activity_source_kind, '') <> 'unassisted_turn'
|
|
5686
|
+
AND model_key <> 'unavailable'
|
|
5687
|
+
), task_models AS (
|
|
5688
|
+
SELECT task_id, model_key FROM agent_task_models
|
|
5689
|
+
UNION SELECT task_id, model_key FROM direct_task_models
|
|
5879
5690
|
), task_population AS (
|
|
5880
5691
|
SELECT model_key AS key,
|
|
5881
5692
|
COUNT(*) AS task_count
|
|
5882
5693
|
FROM task_models
|
|
5883
5694
|
GROUP BY model_key
|
|
5695
|
+
), direct_conversations AS (
|
|
5696
|
+
SELECT model_key, conversation_id FROM direct_model_activities
|
|
5697
|
+
WHERE conversation_id IS NOT NULL
|
|
5698
|
+
AND (activity_source_kind='unassisted_turn' OR task_id IS NULL)
|
|
5699
|
+
), conversation_models AS (
|
|
5700
|
+
SELECT conversation_id, model_key FROM population_models
|
|
5701
|
+
WHERE conversation_id IS NOT NULL AND task_id IS NULL
|
|
5702
|
+
UNION
|
|
5703
|
+
SELECT conversation_id, model_key FROM direct_conversations
|
|
5704
|
+
), conversation_population AS (
|
|
5705
|
+
SELECT model_key AS key, COUNT(*) AS conversation_count
|
|
5706
|
+
FROM conversation_models GROUP BY model_key
|
|
5884
5707
|
), keys AS (
|
|
5885
|
-
SELECT key FROM financial UNION SELECT key FROM population UNION SELECT key FROM task_population
|
|
5708
|
+
SELECT key FROM financial UNION SELECT key FROM population UNION SELECT key FROM task_population UNION SELECT key FROM conversation_population
|
|
5709
|
+
), displayed_keys AS (
|
|
5710
|
+
SELECT key FROM keys WHERE key <> 'unavailable'
|
|
5886
5711
|
)`;
|
|
5887
5712
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
5888
5713
|
const offset = Math.max(0, input.offset ?? 0);
|
|
5889
5714
|
const total_count = Number(
|
|
5890
5715
|
(
|
|
5891
5716
|
this.database
|
|
5892
|
-
.prepare(`${ctes} ${facts} SELECT COUNT(*) AS count FROM
|
|
5893
|
-
.get(...values, ...activityValues) as { count: number }
|
|
5717
|
+
.prepare(`${ctes} ${facts} SELECT COUNT(*) AS count FROM displayed_keys`)
|
|
5718
|
+
.get(...values, ...activityValues, ...activityValues) as { count: number }
|
|
5894
5719
|
).count,
|
|
5895
5720
|
);
|
|
5896
|
-
//
|
|
5897
|
-
//
|
|
5898
|
-
// a footer never reports the inflated sum of row memberships as total.
|
|
5721
|
+
// Model membership is intentionally additive in this view: a Task is
|
|
5722
|
+
// counted once for every model it used, so the footer sums the rows.
|
|
5899
5723
|
const totals = this.database
|
|
5900
5724
|
.prepare(`${ctes} ${facts} SELECT
|
|
5901
|
-
(
|
|
5902
|
-
(
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
SUM(
|
|
5908
|
-
SUM(
|
|
5909
|
-
SUM(
|
|
5910
|
-
SUM(
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5725
|
+
SUM(COALESCE(population.invocation_count, 0)) AS invocation_count,
|
|
5726
|
+
SUM(COALESCE(task_population.task_count, 0)) AS task_count,
|
|
5727
|
+
SUM(COALESCE(conversation_population.conversation_count, 0)) AS conversation_count,
|
|
5728
|
+
SUM(COALESCE(population.total_duration, 0)) AS total_duration,
|
|
5729
|
+
SUM(CASE WHEN population.invocation_count > 0 AND population.total_duration IS NULL THEN population.invocation_count ELSE 0 END) AS duration_unknown,
|
|
5730
|
+
SUM(COALESCE(population.total_duration, 0)) AS duration_known,
|
|
5731
|
+
SUM(COALESCE(population.source_event_count, 0)) AS source_event_count,
|
|
5732
|
+
SUM(COALESCE(financial.total_tokens, 0)) AS total_tokens,
|
|
5733
|
+
SUM(COALESCE(financial.total_cost_usd, 0)) AS total_cost_usd,
|
|
5734
|
+
SUM(COALESCE(financial.total_cost_brl, 0)) AS total_cost_brl
|
|
5735
|
+
FROM displayed_keys
|
|
5736
|
+
LEFT JOIN population USING(key)
|
|
5737
|
+
LEFT JOIN task_population USING(key)
|
|
5738
|
+
LEFT JOIN conversation_population USING(key)
|
|
5739
|
+
LEFT JOIN financial USING(key)`)
|
|
5740
|
+
.get(...values, ...activityValues, ...activityValues) as Record<string, unknown>;
|
|
5916
5741
|
const rows = this.database
|
|
5917
5742
|
.prepare(
|
|
5918
|
-
`${ctes} ${facts} SELECT
|
|
5743
|
+
`${ctes} ${facts} SELECT displayed_keys.key, displayed_keys.key AS label, COALESCE(population.invocation_count, 0) AS invocation_count, COALESCE(task_population.task_count, 0) AS task_count, COALESCE(conversation_population.conversation_count, 0) AS conversation_count, population.total_duration, population.source_event_count, financial.total_tokens, financial.total_cost_usd, financial.total_cost_brl FROM displayed_keys LEFT JOIN population USING(key) LEFT JOIN task_population USING(key) LEFT JOIN conversation_population USING(key) LEFT JOIN financial USING(key) ORDER BY financial.total_cost_brl DESC, displayed_keys.key LIMIT ? OFFSET ?`,
|
|
5919
5744
|
)
|
|
5920
|
-
.all(...values, ...activityValues, limit, offset) as Array<
|
|
5745
|
+
.all(...values, ...activityValues, ...activityValues, limit, offset) as Array<
|
|
5746
|
+
Record<string, unknown>
|
|
5747
|
+
>;
|
|
5748
|
+
const productivityRows = creationProductivityByModel(
|
|
5749
|
+
this.database,
|
|
5750
|
+
input.from?.slice(0, 10) ?? "0000-01-01",
|
|
5751
|
+
input.to?.slice(0, 10) ?? "9999-12-31",
|
|
5752
|
+
{
|
|
5753
|
+
...(input.model ? { model: input.model } : {}),
|
|
5754
|
+
...(input.provider ? { provider: input.provider } : {}),
|
|
5755
|
+
},
|
|
5756
|
+
);
|
|
5757
|
+
const productivity = new Map(productivityRows.map((row) => [String(row.key), row]));
|
|
5758
|
+
for (const row of rows) {
|
|
5759
|
+
const extra = productivity.get(String(row.key));
|
|
5760
|
+
Object.assign(row, {
|
|
5761
|
+
prd_count: extra?.prd_count ?? 0,
|
|
5762
|
+
spec_count: extra?.spec_count ?? 0,
|
|
5763
|
+
validation_count: extra?.validation_count ?? 0,
|
|
5764
|
+
evidence_count: extra?.evidence_count ?? 0,
|
|
5765
|
+
});
|
|
5766
|
+
}
|
|
5767
|
+
Object.assign(totals, {
|
|
5768
|
+
task_count: Number(totals.task_count ?? 0),
|
|
5769
|
+
conversation_count: Number(totals.conversation_count ?? 0),
|
|
5770
|
+
prd_count: sumKnownProductivity(productivityRows, "prd_count") ?? 0,
|
|
5771
|
+
spec_count: sumKnownProductivity(productivityRows, "spec_count") ?? 0,
|
|
5772
|
+
validation_count: sumKnownProductivity(productivityRows, "validation_count") ?? 0,
|
|
5773
|
+
evidence_count: sumKnownProductivity(productivityRows, "evidence_count") ?? 0,
|
|
5774
|
+
});
|
|
5775
|
+
for (const row of rows)
|
|
5776
|
+
for (const field of [
|
|
5777
|
+
"invocation_count",
|
|
5778
|
+
"task_count",
|
|
5779
|
+
"conversation_count",
|
|
5780
|
+
"prd_count",
|
|
5781
|
+
"spec_count",
|
|
5782
|
+
"validation_count",
|
|
5783
|
+
"evidence_count",
|
|
5784
|
+
"total_duration",
|
|
5785
|
+
"source_event_count",
|
|
5786
|
+
"total_tokens",
|
|
5787
|
+
"total_cost_usd",
|
|
5788
|
+
"total_cost_brl",
|
|
5789
|
+
])
|
|
5790
|
+
row[field] = typeof row[field] === "number" ? row[field] : 0;
|
|
5791
|
+
for (const field of [
|
|
5792
|
+
"invocation_count",
|
|
5793
|
+
"task_count",
|
|
5794
|
+
"conversation_count",
|
|
5795
|
+
"prd_count",
|
|
5796
|
+
"spec_count",
|
|
5797
|
+
"validation_count",
|
|
5798
|
+
"evidence_count",
|
|
5799
|
+
"total_duration",
|
|
5800
|
+
"source_event_count",
|
|
5801
|
+
"total_tokens",
|
|
5802
|
+
"total_cost_usd",
|
|
5803
|
+
"total_cost_brl",
|
|
5804
|
+
])
|
|
5805
|
+
totals[field] = typeof totals[field] === "number" ? totals[field] : 0;
|
|
5921
5806
|
return {
|
|
5922
5807
|
data_source: "sqlite_read_model" as const,
|
|
5923
5808
|
view,
|