@zaip-ai/cli 4.5.0 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/services/adapters.d.ts +1 -1
- package/dist/services/adapters.d.ts.map +1 -1
- package/dist/services/adapters.js +6 -6
- package/dist/services/adapters.js.map +1 -1
- package/dist/services/codex-config-overlay.d.ts.map +1 -1
- package/dist/services/codex-config-overlay.js +27 -10
- package/dist/services/codex-config-overlay.js.map +1 -1
- package/dist/services/codex-native-tool-authority-template.d.ts.map +1 -1
- package/dist/services/codex-native-tool-authority-template.js +188 -9
- package/dist/services/codex-native-tool-authority-template.js.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.js +50 -128
- package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.test.js +4 -0
- package/dist/services/codex-pre-tool-hook-template.test.js.map +1 -1
- package/dist/services/codex-pre-tool-support-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-support-template.js +1 -4
- package/dist/services/codex-pre-tool-support-template.js.map +1 -1
- package/dist/services/codex-stale-git-lock-template.d.ts +2 -0
- package/dist/services/codex-stale-git-lock-template.d.ts.map +1 -0
- package/dist/services/codex-stale-git-lock-template.js +64 -0
- package/dist/services/codex-stale-git-lock-template.js.map +1 -0
- package/dist/services/global-codex-config-migration.test.js +1 -4
- package/dist/services/global-codex-config-migration.test.js.map +1 -1
- package/dist/services/global-codex-config.d.ts.map +1 -1
- package/dist/services/global-codex-config.js +2 -1
- package/dist/services/global-codex-config.js.map +1 -1
- package/dist/services/global-codex-config.test.js +3 -1
- package/dist/services/global-codex-config.test.js.map +1 -1
- package/dist/services/hook-readiness.d.ts.map +1 -1
- package/dist/services/hook-readiness.js.map +1 -1
- package/dist/services/install-agents-template.js +2 -2
- package/dist/services/install-agents-template.js.map +1 -1
- package/dist/services/install-template-policy.d.ts.map +1 -1
- package/dist/services/install-template-policy.js +2 -0
- package/dist/services/install-template-policy.js.map +1 -1
- package/dist/services/logger.d.ts +1 -1
- package/dist/services/logger.d.ts.map +1 -1
- package/dist/services/logger.js +2 -1
- package/dist/services/logger.js.map +1 -1
- package/dist/services/logger.test.js +77 -0
- package/dist/services/logger.test.js.map +1 -1
- package/dist/services/runtime-template-integrity.test.d.ts +2 -0
- package/dist/services/runtime-template-integrity.test.d.ts.map +1 -0
- package/dist/services/runtime-template-integrity.test.js +26 -0
- package/dist/services/runtime-template-integrity.test.js.map +1 -0
- package/dist/services/runtime-template-test-support.d.ts +3 -0
- package/dist/services/runtime-template-test-support.d.ts.map +1 -0
- package/dist/services/runtime-template-test-support.js +32 -0
- package/dist/services/runtime-template-test-support.js.map +1 -0
- package/dist/services/runtime-template.test.js +37 -48
- package/dist/services/runtime-template.test.js.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/README.md +8 -0
- package/templates/.agents/config/agents/discovery.json +1 -1
- package/templates/.agents/config/deploy-policy.json +5 -0
- package/templates/.agents/dashboard/.zaip-managed.json +235 -83
- package/templates/.agents/dashboard/README.md +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-f4ZGJRg3.js → CostCalculationGuidePage-OUjdwEcM.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-CkQ_y8kg.js → DashboardHelpDialog-Cei1ChjE.js} +99 -10
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-v9zGRTlk.js → PricingSettingsDialog-_woNCrTT.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-doIMhei7.js → ProjectSettingsPage-B2_7MVOP.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-C_4ftjyB.js → TraceabilityWorkspace-C_jCsSZe.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-C8zQDBT7.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-Cv3WBFIF.js → icons-vendor-iMPXot7Y.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DZhppOIk.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DiCwz0sr.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-mHgsmxb6.js → react-vendor-721Re3Ss.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-B0CPyEVq.js → traceability-BjdTkWS0.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
- package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
- package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +6 -34
- package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.dom.test.ts +130 -0
- package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.tsx +226 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-sqlite-view.ts +45 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +15 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +17 -46
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-fixtures.ts +23 -0
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.test.ts +69 -0
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.ts +59 -0
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback.css +131 -0
- package/templates/.agents/dashboard/apps/frontend/src/styles.css +0 -32
- package/templates/.agents/dashboard/apps/frontend/src/use-dashboard-app-model.tsx +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +1 -1
- package/templates/.agents/dashboard/modules/operations/package.json +1 -1
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.macro-metrics.test.ts +7 -2
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +6 -6
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +1 -4
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.navigation.test.ts +236 -0
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +1 -24
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +3 -2
- package/templates/.agents/dashboard/modules/operations/src/react/overview-duration-hint.ts +14 -0
- package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
- package/templates/.agents/dashboard/packages/branding/package.json +1 -1
- package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
- package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/DIAGNOSTICS.md +42 -0
- package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.d.mts +10 -0
- package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.mjs +101 -0
- package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.test.mjs +58 -0
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +318 -236
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +285 -205
- package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-dashboard-startup.mjs +233 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-provider-nonzero.mjs +45 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-executable-startup.mjs +107 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-reconciliation-executable.mjs +271 -0
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +318 -236
- package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.test.ts +78 -12
- package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.ts +36 -56
- package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.test.ts +125 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.ts +168 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/dashboard-diagnostics.test.ts +57 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-agent-catalog-routes.ts +76 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-dual-reads-manifest-summaries-without-losing.test.ts +5 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-diagnostics.test.ts +118 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts +35 -6
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-route.ts +105 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.test.ts +64 -9
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +19 -27
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-command.test.ts +111 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.test.ts +59 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.ts +44 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +20 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +12 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +48 -6
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +2 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +126 -134
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +14 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-summary-elapsed.ts +107 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.test.ts +163 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.ts +78 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-header-timestamp.test.ts +31 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-nonzero.test.ts +271 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-visibility.ts +32 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-operations-duration.ts +58 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-provider-dimension.ts +12 -3
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-summary.ts +41 -54
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-view-page.ts +9 -58
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +7 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-counts-every-root-execution-day-returns.test.ts +5 -5
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider.test.ts +4 -4
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-rebuild.ts +9 -35
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-schema.ts +13 -9
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +43 -45
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +5 -9
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +5 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-base-views.ts +9 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-dashboard-views.ts +10 -8
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-spec-root-elapsed-view.ts +61 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-spec-root-elapsed.test.ts +210 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-startup-seed.test.ts +87 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-logging.mjs +6 -2
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +7 -1
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +4 -5
- package/templates/.agents/dashboard/packages/ui/package.json +1 -1
- package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.test.mjs +33 -4
- package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.tsx +8 -7
- package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +46 -3
- package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/qualify-kanban-popup.mjs +56 -0
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard-update-label.spec.ts +49 -0
- package/templates/.agents/design/.zaip-managed.json +17 -5
- package/templates/.agents/design/apps/catalog/dist/assets/index-D3uhpycC.js +72 -0
- package/templates/.agents/design/apps/catalog/dist/index.html +1 -1
- package/templates/.agents/design/apps/catalog/package.json +1 -1
- package/templates/.agents/design/apps/catalog/src/pages/ComponentsPage.tsx +13 -1
- package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.stories.tsx +6 -0
- package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.tsx +101 -0
- package/templates/.agents/design/packages/local-runtime/package.json +1 -1
- package/templates/.agents/requirements/autonomous-assurance.md +1 -1
- package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
- 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 +1 -1
- 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 +21 -6
- package/templates/.agents/requirements/public-distribution.md +4 -4
- 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/core.md +3 -1
- package/templates/.agents/rules/workflow.md +3 -2
- package/templates/.agents/runtime/VERSION +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +368 -199
- package/templates/.agents/runtime/checksums.sha256 +5 -5
- package/templates/.agents/runtime/manifest.json +1 -1
- package/templates/.agents/runtime/provenance.json +4 -4
- package/templates/.agents/runtime/sbom.cdx.json +1 -1
- package/templates/.agents/skills/zaip-skill-config/SKILL.md +25 -3
- package/templates/.agents/skills/zaip-skill-execution/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-execution/references/autonomous-continuation.md +4 -0
- package/templates/.agents/skills/zaip-skill-execution/references/candidate-workflow.md +1 -1
- package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +2 -2
- package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +1 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +38 -48
- package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
- package/templates/.agents/skills/zaip-skill-github-deploy/references/deployment-workflow.md +67 -99
- package/templates/.agents/skills/zaip-skill-github-deploy/references/release-identity.md +33 -30
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.mjs +307 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +337 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +41 -28
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/guarded-commits.mjs +212 -0
- package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-prd/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +46 -68
- package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +100 -44
- package/templates/.agents/skills/zaip-skill-send-email/SKILL.md +15 -0
- package/templates/.agents/skills/zaip-skill-send-email/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -2
- package/templates/.agents/skills/zaip-skill-spec/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-task/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-testing-strategy/SKILL.md +19 -0
- package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +2 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/SKILL.md +15 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/scripts/connector-guidance.test.mjs +29 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/scripts/verify-connector-lock.mjs +30 -0
- package/templates/.agents/tools/cloud-runner.integration.test.mjs +3 -0
- package/templates/.agents/zaip-aios/VERSION +1 -1
- package/templates/.agents/zaip-aios/guides/03-tasks.md +6 -0
- package/templates/.agents/zaip-aios/guides/04-specs.md +6 -0
- package/templates/.agents/zaip-aios/guides/05-prds.md +4 -0
- package/templates/.agents/zaip-aios/guides/06-execucao.md +6 -0
- package/templates/.agents/zaip-aios/guides/16-lifecycle-e-reconciliacao.md +6 -0
- package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +4 -0
- package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +6 -0
- package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +36 -4
- package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +15 -4
- package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +8 -0
- package/templates/template-manifest.json +354 -150
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-D7PrDZy9.js +0 -68
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-OBJXZOwe.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Rbmpz4UO.js +0 -2
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { CostSummarySource } from "@zaip-ai/dashboard-cost-monitor/server";
|
|
2
2
|
import { artifactStructuralRowsSql } from "./sqlite-artifact-counts.js";
|
|
3
3
|
import { buildCostSummaryDetails } from "./sqlite-cost-summary-details.js";
|
|
4
|
+
import {
|
|
5
|
+
costSummaryElapsedByDay,
|
|
6
|
+
filteredCostSummaryElapsedByDay,
|
|
7
|
+
} from "./sqlite-cost-summary-elapsed.js";
|
|
4
8
|
import type { SqliteQueryContext } from "./sqlite-query-context.js";
|
|
5
9
|
import { costMonitorFilterDay } from "./sqlite-read-model-canonical.js";
|
|
6
10
|
import type { SqliteReadModelFileSystem } from "./sqlite-read-model-filesystem.js";
|
|
@@ -11,21 +15,8 @@ export function queryCostMonitorSummaryAggregate(
|
|
|
11
15
|
const { from, to, providers = [], models = [] } = filter;
|
|
12
16
|
const sourceFrom = costMonitorFilterDay(from);
|
|
13
17
|
const sourceTo = costMonitorFilterDay(to);
|
|
14
|
-
const
|
|
15
|
-
const generationCached = context.costMonitorSummaryCache.get(cacheKey);
|
|
16
|
-
if (generationCached !== undefined) return structuredClone(generationCached);
|
|
17
|
-
const remember = (value: CostSummarySource) => {
|
|
18
|
-
if (context.costMonitorSummaryCache.size >= 64) {
|
|
19
|
-
const oldest = context.costMonitorSummaryCache.keys().next().value as string | undefined;
|
|
20
|
-
if (oldest !== undefined) context.costMonitorSummaryCache.delete(oldest);
|
|
21
|
-
}
|
|
22
|
-
context.costMonitorSummaryCache.set(cacheKey, structuredClone(value));
|
|
23
|
-
return structuredClone(value);
|
|
24
|
-
};
|
|
25
|
-
const unfilteredAllTime =
|
|
18
|
+
const noDimensionFilters =
|
|
26
19
|
!filter.query &&
|
|
27
|
-
sourceFrom === null &&
|
|
28
|
-
sourceTo === null &&
|
|
29
20
|
[
|
|
30
21
|
providers,
|
|
31
22
|
models,
|
|
@@ -41,7 +32,19 @@ export function queryCostMonitorSummaryAggregate(
|
|
|
41
32
|
filter.risks,
|
|
42
33
|
filter.origins,
|
|
43
34
|
filter.workTypes,
|
|
44
|
-
].every((
|
|
35
|
+
].every((items) => items === undefined || items.length === 0);
|
|
36
|
+
const cacheKey = JSON.stringify({ ...filter, from: sourceFrom, to: sourceTo });
|
|
37
|
+
const generationCached = context.costMonitorSummaryCache.get(cacheKey);
|
|
38
|
+
if (generationCached !== undefined) return structuredClone(generationCached);
|
|
39
|
+
const remember = (value: CostSummarySource) => {
|
|
40
|
+
if (context.costMonitorSummaryCache.size >= 64) {
|
|
41
|
+
const oldest = context.costMonitorSummaryCache.keys().next().value as string | undefined;
|
|
42
|
+
if (oldest !== undefined) context.costMonitorSummaryCache.delete(oldest);
|
|
43
|
+
}
|
|
44
|
+
context.costMonitorSummaryCache.set(cacheKey, structuredClone(value));
|
|
45
|
+
return structuredClone(value);
|
|
46
|
+
};
|
|
47
|
+
const unfilteredAllTime = noDimensionFilters && sourceFrom === null && sourceTo === null;
|
|
45
48
|
if (unfilteredAllTime) {
|
|
46
49
|
const cached = context.database
|
|
47
50
|
.prepare(
|
|
@@ -55,27 +58,7 @@ export function queryCostMonitorSummaryAggregate(
|
|
|
55
58
|
if (summary.filters_available !== undefined) return remember(summary);
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
|
-
if (
|
|
59
|
-
!filter.query &&
|
|
60
|
-
sourceFrom !== null &&
|
|
61
|
-
sourceTo !== null &&
|
|
62
|
-
[
|
|
63
|
-
providers,
|
|
64
|
-
models,
|
|
65
|
-
filter.agents,
|
|
66
|
-
filter.phases,
|
|
67
|
-
filter.statuses,
|
|
68
|
-
filter.tasks,
|
|
69
|
-
filter.specs,
|
|
70
|
-
filter.prds,
|
|
71
|
-
filter.executions,
|
|
72
|
-
filter.changes,
|
|
73
|
-
filter.priorities,
|
|
74
|
-
filter.risks,
|
|
75
|
-
filter.origins,
|
|
76
|
-
filter.workTypes,
|
|
77
|
-
].every((values) => values === undefined || values.length === 0)
|
|
78
|
-
) {
|
|
61
|
+
if (noDimensionFilters && sourceFrom !== null && sourceTo !== null) {
|
|
79
62
|
const cached = context.database
|
|
80
63
|
.prepare(
|
|
81
64
|
"SELECT payload_json FROM zaip_aios_activities WHERE payload_type='cost_monitor_summary_month_cache' ORDER BY recorded_at DESC LIMIT 1",
|
|
@@ -384,12 +367,25 @@ export function queryCostMonitorSummaryAggregate(
|
|
|
384
367
|
);
|
|
385
368
|
const financialByDay = new Map(dailyRows.map((row) => [String(row.key), row]));
|
|
386
369
|
const structuralByDay = new Map(structuralRows.map((row) => [String(row.day), row]));
|
|
387
|
-
|
|
370
|
+
// Financial events belong to their source start day. Elapsed wall time
|
|
371
|
+
// belongs to every local day actually crossed by its observed interval.
|
|
372
|
+
const elapsedByDay = noDimensionFilters
|
|
373
|
+
? costSummaryElapsedByDay(context.database, sourceFrom, sourceTo)
|
|
374
|
+
: filteredCostSummaryElapsedByDay(
|
|
375
|
+
context.database,
|
|
376
|
+
sourceFrom,
|
|
377
|
+
sourceTo,
|
|
378
|
+
clauses.slice(2),
|
|
379
|
+
values.slice(4),
|
|
380
|
+
);
|
|
381
|
+
const days = [
|
|
382
|
+
...new Set([...financialByDay.keys(), ...structuralByDay.keys(), ...elapsedByDay.keys()]),
|
|
383
|
+
].sort();
|
|
388
384
|
const compactDailyRows = days.map((day) => {
|
|
389
385
|
const financial = financialByDay.get(day);
|
|
390
386
|
const structural = structuralByDay.get(day);
|
|
391
387
|
const eligible = number(financial?.eligible_runs);
|
|
392
|
-
const
|
|
388
|
+
const elapsed = elapsedByDay.get(day);
|
|
393
389
|
return {
|
|
394
390
|
key: day,
|
|
395
391
|
prd_count: number(structural?.prd_count),
|
|
@@ -398,9 +394,12 @@ export function queryCostMonitorSummaryAggregate(
|
|
|
398
394
|
validation_count: number(structural?.validation_count),
|
|
399
395
|
evidence_count: number(structural?.evidence_count),
|
|
400
396
|
total_run_count: eligible,
|
|
401
|
-
total_duration:
|
|
402
|
-
duration_known:
|
|
403
|
-
duration_unknown: Math.max(
|
|
397
|
+
total_duration: elapsed?.known_runs ? elapsed.known_sum : null,
|
|
398
|
+
duration_known: elapsed?.known_sum ?? 0,
|
|
399
|
+
duration_unknown: Math.max(
|
|
400
|
+
0,
|
|
401
|
+
(elapsed?.eligible_runs ?? eligible) - (elapsed?.known_runs ?? 0),
|
|
402
|
+
),
|
|
404
403
|
total_tokens: nullable(financial?.total_tokens),
|
|
405
404
|
total_cost_usd: nullable(financial?.cost_usd),
|
|
406
405
|
total_cost_brl: nullable(financial?.cost_brl),
|
|
@@ -447,19 +446,7 @@ export function queryCostMonitorSummaryAggregate(
|
|
|
447
446
|
];
|
|
448
447
|
}),
|
|
449
448
|
);
|
|
450
|
-
const durations = Object.fromEntries(
|
|
451
|
-
dailyRows.map((row) => {
|
|
452
|
-
const value = metric(row);
|
|
453
|
-
return [
|
|
454
|
-
String(row.key),
|
|
455
|
-
{
|
|
456
|
-
known_sum: number(row.duration_ms),
|
|
457
|
-
eligible_runs: value._duration_population.eligible_runs,
|
|
458
|
-
known_runs: value._duration_population.known_runs,
|
|
459
|
-
},
|
|
460
|
-
];
|
|
461
|
-
}),
|
|
462
|
-
);
|
|
449
|
+
const durations = Object.fromEntries(elapsedByDay);
|
|
463
450
|
const { model_groups, filters_available } = buildCostSummaryDetails(
|
|
464
451
|
context,
|
|
465
452
|
selected,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
operationsWorkListPayloadPage,
|
|
5
5
|
operationsWorkListSqlPage,
|
|
6
6
|
} from "./sqlite-operations-work-list.js";
|
|
7
|
+
import { hasVisibleProviderMetrics } from "./sqlite-provider-visibility.js";
|
|
7
8
|
import { readAgentDimension } from "./sqlite-query-agent-dimension.js";
|
|
8
9
|
import {
|
|
9
10
|
readCachedCostViewPage,
|
|
@@ -13,6 +14,7 @@ import type { SqliteQueryContext, SqliteViewPage } from "./sqlite-query-context.
|
|
|
13
14
|
import { readEntityGroup } from "./sqlite-query-entity-group.js";
|
|
14
15
|
import { readFilteredDimension } from "./sqlite-query-filtered-dimension.js";
|
|
15
16
|
import { readModelDimension } from "./sqlite-query-model-dimension.js";
|
|
17
|
+
import { attachOperationsDuration } from "./sqlite-query-operations-duration.js";
|
|
16
18
|
import { readProviderDimension } from "./sqlite-query-provider-dimension.js";
|
|
17
19
|
import { readTemporalDimension } from "./sqlite-query-temporal-dimension.js";
|
|
18
20
|
import { costMonitorFilterDay } from "./sqlite-read-model-canonical.js";
|
|
@@ -239,7 +241,7 @@ export function queryUncachedViewPage(
|
|
|
239
241
|
const visiblePayloadRows =
|
|
240
242
|
dimension === "model"
|
|
241
243
|
? payloadRows.filter((row) => row.key !== "unavailable" && row.key !== null)
|
|
242
|
-
: payloadRows;
|
|
244
|
+
: payloadRows.filter(hasVisibleProviderMetrics);
|
|
243
245
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
244
246
|
const offset = Math.max(0, input.offset ?? 0);
|
|
245
247
|
const rows = visiblePayloadRows.slice(offset, offset + limit);
|
|
@@ -268,9 +270,11 @@ export function queryUncachedViewPage(
|
|
|
268
270
|
for (const field of modelNumericFields)
|
|
269
271
|
row[field] = typeof row[field] === "number" ? row[field] : 0;
|
|
270
272
|
}
|
|
273
|
+
// Provider visibility must not change factual totals or known-zero coverage.
|
|
274
|
+
const totalRows = dimension === "provider" ? payloadRows : visiblePayloadRows;
|
|
271
275
|
const nullableSum = (field: string) =>
|
|
272
|
-
|
|
273
|
-
?
|
|
276
|
+
totalRows.some((row) => typeof row[field] === "number")
|
|
277
|
+
? totalRows.reduce(
|
|
274
278
|
(sum, row) => sum + (typeof row[field] === "number" ? Number(row[field]) : 0),
|
|
275
279
|
0,
|
|
276
280
|
)
|
|
@@ -284,7 +288,7 @@ export function queryUncachedViewPage(
|
|
|
284
288
|
total_count: visiblePayloadRows.length,
|
|
285
289
|
has_more: offset + rows.length < visiblePayloadRows.length,
|
|
286
290
|
totals: {
|
|
287
|
-
invocation_count:
|
|
291
|
+
invocation_count: totalRows.reduce(
|
|
288
292
|
(sum, row) => sum + Number(row.invocation_count ?? 0),
|
|
289
293
|
0,
|
|
290
294
|
),
|
|
@@ -490,60 +494,7 @@ export function queryUncachedViewPage(
|
|
|
490
494
|
const rows = context.database
|
|
491
495
|
.prepare(`SELECT * FROM ${view}${where} ORDER BY ${meta.order} LIMIT ? OFFSET ?`)
|
|
492
496
|
.all(...values, limit, offset) as Array<Record<string, unknown>>;
|
|
493
|
-
|
|
494
|
-
view === "operations_daily" ||
|
|
495
|
-
view === "operations_monthly" ||
|
|
496
|
-
view === "operations_annual"
|
|
497
|
-
) {
|
|
498
|
-
// A partial date interval intersects a coarse month/year row but does
|
|
499
|
-
// not make its all-time additive metrics applicable. Recompose it from
|
|
500
|
-
// the same daily facts used by Operations cards.
|
|
501
|
-
const partialCoarse = view !== "operations_daily" && (input.from || input.to);
|
|
502
|
-
const partialFromDay = input.from?.slice(0, 10) ?? null;
|
|
503
|
-
const partialToDay = input.to?.slice(0, 10) ?? null;
|
|
504
|
-
const dailyRows = partialCoarse
|
|
505
|
-
? (context.database
|
|
506
|
-
.prepare(
|
|
507
|
-
`SELECT * FROM operations_daily WHERE (? IS NULL OR day >= ?) AND (? IS NULL OR day <= ?)`,
|
|
508
|
-
)
|
|
509
|
-
.all(partialFromDay, partialFromDay, partialToDay, partialToDay) as Array<
|
|
510
|
-
Record<string, unknown>
|
|
511
|
-
>)
|
|
512
|
-
: [];
|
|
513
|
-
const daily = context.executionDurationByDay({
|
|
514
|
-
from: input.from ?? null,
|
|
515
|
-
to: input.to ?? null,
|
|
516
|
-
});
|
|
517
|
-
for (const row of rows) {
|
|
518
|
-
const key = String(row.day ?? row.period_key);
|
|
519
|
-
if (partialCoarse) {
|
|
520
|
-
const matching = dailyRows.filter((day) => String(day.day).startsWith(key));
|
|
521
|
-
const matchingDays = matching.map((day) => String(day.day)).sort();
|
|
522
|
-
row.start_day = matchingDays[0] ?? null;
|
|
523
|
-
row.end_day = matchingDays.at(-1) ?? null;
|
|
524
|
-
for (const column of [
|
|
525
|
-
"prd_count",
|
|
526
|
-
"spec_count",
|
|
527
|
-
"task_count",
|
|
528
|
-
"validation_count",
|
|
529
|
-
"evidence_count",
|
|
530
|
-
"total_run_count",
|
|
531
|
-
"invocation_count",
|
|
532
|
-
"llm_call_count",
|
|
533
|
-
"total_tokens",
|
|
534
|
-
"total_cost_usd",
|
|
535
|
-
"total_cost_brl",
|
|
536
|
-
] as const)
|
|
537
|
-
row[column] = matching.reduce(
|
|
538
|
-
(sum, day) => sum + (typeof day[column] === "number" ? Number(day[column]) : 0),
|
|
539
|
-
0,
|
|
540
|
-
);
|
|
541
|
-
}
|
|
542
|
-
row.operations_total_duration = [...daily]
|
|
543
|
-
.filter(([day]) => (view === "operations_daily" ? day === key : day.startsWith(key)))
|
|
544
|
-
.reduce((sum, [, duration]) => sum + duration, 0);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
497
|
+
attachOperationsDuration(context, view, input, rows);
|
|
547
498
|
return {
|
|
548
499
|
data_source: "sqlite_read_model" as const,
|
|
549
500
|
view,
|
package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts
CHANGED
|
@@ -26,11 +26,13 @@ import {
|
|
|
26
26
|
} from "@zaip-ai/costs";
|
|
27
27
|
import type { RunV2Fold, UsageRecordedV2 } from "../../../../../packages/decisions/src/index.js";
|
|
28
28
|
import { saoPauloDay } from "./sqlite-calendar.js";
|
|
29
|
+
import {
|
|
30
|
+
MAX_RECONCILED_RUN_INTERVAL_MS,
|
|
31
|
+
MAX_SOURCE_INTERVAL_MS,
|
|
32
|
+
} from "./sqlite-read-model-schema.js";
|
|
29
33
|
|
|
30
34
|
export { saoPauloDay } from "./sqlite-calendar.js";
|
|
31
35
|
|
|
32
|
-
const MAX_RECONCILED_RUN_INTERVAL_MS = 2 * 3_600_000;
|
|
33
|
-
|
|
34
36
|
export function saoPauloCurrentMonth(): { from: string; to: string } {
|
|
35
37
|
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
36
38
|
timeZone: "America/Sao_Paulo",
|
|
@@ -362,9 +364,7 @@ export function factualDuration(record: UsageRecordedV2): boolean {
|
|
|
362
364
|
export type ResolvedSourceInterval = { start: string; end: string; duration: number };
|
|
363
365
|
|
|
364
366
|
/**
|
|
365
|
-
*
|
|
366
|
-
* happened. It wins over Run finalization elapsed time, which may include
|
|
367
|
-
* orchestration/capture lag or be recorded much later.
|
|
367
|
+
* Observed source boundaries outrank delayed administrative Run finalization.
|
|
368
368
|
*/
|
|
369
369
|
export function resolvedSourceInterval(
|
|
370
370
|
records: readonly UsageRecordedV2[],
|
|
@@ -379,7 +379,7 @@ export function resolvedSourceInterval(
|
|
|
379
379
|
Number.isFinite(startMs) &&
|
|
380
380
|
Number.isFinite(endMs) &&
|
|
381
381
|
endMs > startMs &&
|
|
382
|
-
endMs - startMs <=
|
|
382
|
+
endMs - startMs <= MAX_SOURCE_INTERVAL_MS
|
|
383
383
|
? [{ start, end, startMs, endMs }]
|
|
384
384
|
: [];
|
|
385
385
|
});
|
|
@@ -390,7 +390,7 @@ export function resolvedSourceInterval(
|
|
|
390
390
|
const end = intervals.reduce((latest, interval) =>
|
|
391
391
|
interval.endMs > latest.endMs ? interval : latest,
|
|
392
392
|
);
|
|
393
|
-
return end.endMs - start.startMs <=
|
|
393
|
+
return end.endMs - start.startMs <= MAX_SOURCE_INTERVAL_MS
|
|
394
394
|
? { start: start.start, end: end.end, duration: end.endMs - start.startMs }
|
|
395
395
|
: null;
|
|
396
396
|
}
|
|
@@ -49,7 +49,7 @@ describe("SQLite dashboard read model", () => {
|
|
|
49
49
|
expect.objectContaining({
|
|
50
50
|
id: "SPEC-B",
|
|
51
51
|
invocation_count: 2,
|
|
52
|
-
total_duration:
|
|
52
|
+
total_duration: 2_400_000,
|
|
53
53
|
total_tokens: 4,
|
|
54
54
|
total_cost_brl: 10,
|
|
55
55
|
}),
|
|
@@ -189,13 +189,13 @@ describe("SQLite dashboard read model", () => {
|
|
|
189
189
|
status_counts: { planning: 3 },
|
|
190
190
|
classification_counts: { structured: 2 },
|
|
191
191
|
total_run_count: 3,
|
|
192
|
-
total_duration_ms:
|
|
192
|
+
total_duration_ms: 3_000_000,
|
|
193
193
|
total_tokens: 6,
|
|
194
194
|
total_cost_brl: 15,
|
|
195
|
-
average_duration_ms:
|
|
195
|
+
average_duration_ms: 1_000_000,
|
|
196
196
|
});
|
|
197
197
|
expect(allTime.macro_metrics.specs.top_entities.slice(0, 2)).toMatchObject([
|
|
198
|
-
{ rank: 1, entity_id: "SPEC-B", total_duration_ms:
|
|
198
|
+
{ rank: 1, entity_id: "SPEC-B", total_duration_ms: 2_400_000 },
|
|
199
199
|
{ rank: 2, entity_id: "SPEC-A", total_duration_ms: 600_000 },
|
|
200
200
|
]);
|
|
201
201
|
expect(allTime.macro_metrics.prds).toMatchObject({
|
|
@@ -314,7 +314,7 @@ describe("SQLite dashboard read model", () => {
|
|
|
314
314
|
expect(pagedDailyCostPage.totals).toMatchObject({
|
|
315
315
|
total_tokens: dailyCostRows.reduce((sum, row) => sum + Number(row.total_tokens ?? 0), 0),
|
|
316
316
|
total_cost_brl: dailyCostRows.reduce((sum, row) => sum + Number(row.total_cost_brl ?? 0), 0),
|
|
317
|
-
total_duration:
|
|
317
|
+
total_duration: null,
|
|
318
318
|
});
|
|
319
319
|
expect(annualCostPage).toMatchObject({
|
|
320
320
|
total_count: 1,
|
|
@@ -130,10 +130,10 @@ describe("SQLite dashboard read model multi-provider", () => {
|
|
|
130
130
|
expect(
|
|
131
131
|
allAgentGroups.rows.reduce((total, row) => total + Number(row.total_cost_usd ?? 0), 0),
|
|
132
132
|
).toBe(allCostTotals.total_cost_usd);
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
expect(allCostTotals.cost_monitor_total_duration).toBe(
|
|
133
|
+
// The calendar overview uses three observed one-minute source intervals.
|
|
134
|
+
// The agent table reports 120 seconds attributable to token-active facts;
|
|
135
|
+
// it does not assign the remaining source elapsed minute to an Agent.
|
|
136
|
+
expect(allCostTotals.cost_monitor_total_duration).toBe(180_000);
|
|
137
137
|
expect(allAgentGroups.totals).toMatchObject({
|
|
138
138
|
invocation_count: allCostTotals.total_run_count,
|
|
139
139
|
total_duration: 120_000,
|
|
@@ -42,16 +42,10 @@ export async function rebuildSqliteReadModel(
|
|
|
42
42
|
options: SqliteRebuildOptions = {},
|
|
43
43
|
): Promise<SqliteRebuildResult> {
|
|
44
44
|
const path = sqliteReadModelPath(workspaceRoot);
|
|
45
|
-
//
|
|
45
|
+
// Startup serves a validated generation; the worker verifies canonical freshness.
|
|
46
46
|
if (options.preferExisting && !options.forceCanonical) {
|
|
47
47
|
try {
|
|
48
|
-
validateSqliteReadModelSchema(path);
|
|
49
|
-
const fingerprintStartedAt = performance.now();
|
|
50
|
-
const sourceSnapshot = await sourceFingerprint(
|
|
51
|
-
workspaceRoot,
|
|
52
|
-
rebuildFingerprintOptions(path, options),
|
|
53
|
-
);
|
|
54
|
-
const fingerprint = `${sourceSnapshot.fingerprint}\ncurrent_day=${saoPauloDay(new Date().toISOString())}`;
|
|
48
|
+
validateSqliteReadModelSchema(path, { integrity: "quick" });
|
|
55
49
|
const database = new DatabaseSync(path, { readOnly: true });
|
|
56
50
|
try {
|
|
57
51
|
if (hasReusableSnapshotPayloads(database)) {
|
|
@@ -60,38 +54,18 @@ export async function rebuildSqliteReadModel(
|
|
|
60
54
|
"SELECT value FROM zaip_vw_read_model_metadata WHERE key = 'source_file_count'",
|
|
61
55
|
)
|
|
62
56
|
.get() as { value: string };
|
|
63
|
-
const files =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
readModelSchemaVersion,
|
|
67
|
-
hasReusableSnapshotPayloads,
|
|
68
|
-
);
|
|
69
|
-
if (files !== null) {
|
|
70
|
-
return {
|
|
71
|
-
files,
|
|
72
|
-
path,
|
|
73
|
-
cache: "generation",
|
|
74
|
-
fingerprint_ms: Math.round(performance.now() - fingerprintStartedAt),
|
|
75
|
-
materialize_ms: 0,
|
|
76
|
-
refresh: {
|
|
77
|
-
reused_sources: sourceSnapshot.reusedFiles,
|
|
78
|
-
processed_sources: sourceSnapshot.hashedFiles,
|
|
79
|
-
added_sources: 0,
|
|
80
|
-
changed_sources: 0,
|
|
81
|
-
removed_sources: 0,
|
|
82
|
-
canonical_runs_refolded: 0,
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
}
|
|
57
|
+
const files = Number(row?.value);
|
|
58
|
+
if (!Number.isSafeInteger(files) || files < 0)
|
|
59
|
+
throw new Error("Invalid snapshot file count");
|
|
86
60
|
return {
|
|
87
|
-
files
|
|
61
|
+
files,
|
|
88
62
|
path,
|
|
89
63
|
cache: "stale_seed",
|
|
90
|
-
fingerprint_ms:
|
|
64
|
+
fingerprint_ms: 0,
|
|
91
65
|
materialize_ms: 0,
|
|
92
66
|
refresh: {
|
|
93
|
-
reused_sources:
|
|
94
|
-
processed_sources:
|
|
67
|
+
reused_sources: 0,
|
|
68
|
+
processed_sources: 0,
|
|
95
69
|
added_sources: 0,
|
|
96
70
|
changed_sources: 0,
|
|
97
71
|
removed_sources: 0,
|
|
@@ -7,7 +7,7 @@ export const dbRelativePath = ".local/sqlite/zaip-aios-dashboard.sqlite";
|
|
|
7
7
|
// Schema 83 adds canonical priority, Run work type and reconciled macro stages.
|
|
8
8
|
// Old snapshots must rebuild even when their canonical hashes still match.
|
|
9
9
|
// Refresh persisted first-fold summaries after factual duration/coverage semantics change.
|
|
10
|
-
export const readModelSchemaVersion = "
|
|
10
|
+
export const readModelSchemaVersion = "85";
|
|
11
11
|
export const requiredPersistedTables = [
|
|
12
12
|
"zaip_aios_activities",
|
|
13
13
|
"zaip_dim_llm_model",
|
|
@@ -51,6 +51,8 @@ export const requiredViews = [
|
|
|
51
51
|
"zaip_vw_operations_agent_invocations",
|
|
52
52
|
] as const;
|
|
53
53
|
export const MAX_RECONCILED_RUN_INTERVAL_MS = 2 * 3_600_000;
|
|
54
|
+
/** Explicit source observations can span a workday; inferred Run pairs cannot. */
|
|
55
|
+
export const MAX_SOURCE_INTERVAL_MS = 48 * 3_600_000;
|
|
54
56
|
export function sqliteReadModelPath(workspaceRoot: string): string {
|
|
55
57
|
return resolve(process.env.ZAIP_SQLITE_DB_PATH ?? resolve(workspaceRoot, dbRelativePath));
|
|
56
58
|
}
|
|
@@ -122,7 +124,10 @@ export function persistedSourceCheckpoint(
|
|
|
122
124
|
* is intentionally independent of source fingerprints: a matching cache key
|
|
123
125
|
* is not evidence that a partially copied or old-schema file is safe.
|
|
124
126
|
*/
|
|
125
|
-
export function validateSqliteReadModelSchema(
|
|
127
|
+
export function validateSqliteReadModelSchema(
|
|
128
|
+
path: string,
|
|
129
|
+
options: { integrity?: "full" | "quick" } = {},
|
|
130
|
+
): {
|
|
126
131
|
schemaVersion: string;
|
|
127
132
|
tableCount: number;
|
|
128
133
|
viewCount: number;
|
|
@@ -130,13 +135,12 @@ export function validateSqliteReadModelSchema(path: string): {
|
|
|
130
135
|
} {
|
|
131
136
|
const database = new DatabaseSync(path, { readOnly: true });
|
|
132
137
|
try {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
);
|
|
138
|
+
// Only startup opts into quick_check. Workers and qualification retain the
|
|
139
|
+
// full cross-index integrity check, independently of source cache reuse.
|
|
140
|
+
const pragma = options.integrity === "quick" ? "quick_check" : "integrity_check";
|
|
141
|
+
const integrity = database.prepare(`PRAGMA ${pragma}`).get() as Record<string, unknown>;
|
|
142
|
+
if (integrity[pragma] !== "ok")
|
|
143
|
+
throw new Error(`SQLite ${pragma} failed for ${path}: ${integrity[pragma] ?? "unknown"}`);
|
|
140
144
|
const objects = database
|
|
141
145
|
.prepare("SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view', 'index')")
|
|
142
146
|
.all() as Array<{ name: string; type: "table" | "view" | "index" }>;
|
|
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
4
4
|
import { join, relative, resolve } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
|
+
import { CanonicalSourceRevisionCache } from "./canonical-source-revision-cache.js";
|
|
6
7
|
|
|
7
8
|
const execFileAsync = promisify(execFile);
|
|
8
9
|
|
|
@@ -87,23 +88,9 @@ export function isCodexManagedWorktreeRoot(root: string): boolean {
|
|
|
87
88
|
return /[\\/]\.codex[\\/]worktrees(?:[\\/][^\\/]+){1,2}$/iu.test(root);
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
function canonicalRevision(content: string, source: string): [string, number, number] {
|
|
91
|
-
if (source.endsWith(".jsonl")) {
|
|
92
|
-
const dates = [...content.matchAll(/"occurred_at"\s*:\s*"([^"]+)"/gu)];
|
|
93
|
-
return [
|
|
94
|
-
dates.reduce((latest, match) => ((match[1] ?? "") > latest ? (match[1] ?? "") : latest), ""),
|
|
95
|
-
dates.length,
|
|
96
|
-
content.length,
|
|
97
|
-
];
|
|
98
|
-
}
|
|
99
|
-
const updated = /^updated_at:\s*([^\r\n]+)/mu.exec(content)?.[1]?.trim() ?? "";
|
|
100
|
-
const created = /^created_at:\s*([^\r\n]+)/mu.exec(content)?.[1]?.trim() ?? "";
|
|
101
|
-
const revision = Number(/^revision:\s*(\d+)/mu.exec(content)?.[1] ?? 0);
|
|
102
|
-
return [updated || created, revision, content.length];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
91
|
async function selectCanonicalSources(
|
|
106
92
|
roots: readonly string[],
|
|
93
|
+
cache: CanonicalSourceRevisionCache,
|
|
107
94
|
): Promise<Array<{ path: string; source: string }>> {
|
|
108
95
|
const locations = new Map<string, string[]>();
|
|
109
96
|
for (const [index, root] of roots.entries()) {
|
|
@@ -149,36 +136,37 @@ async function selectCanonicalSources(
|
|
|
149
136
|
const entries = [...locations.entries()];
|
|
150
137
|
for (let offset = 0; offset < entries.length; offset += 32) {
|
|
151
138
|
selected.push(
|
|
152
|
-
...(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
paths.map(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
throw new Error(`Incomplete canonical revisions for ${source}`);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
139
|
+
...(
|
|
140
|
+
await Promise.all(
|
|
141
|
+
entries.slice(offset, offset + 32).map(async ([source, paths]) => {
|
|
142
|
+
const firstPath = paths[0];
|
|
143
|
+
if (firstPath === undefined) throw new Error(`No canonical source for ${source}`);
|
|
144
|
+
if (paths.length === 1) return { source, path: firstPath };
|
|
145
|
+
// Worktree checkout mtimes do not represent canonical record recency.
|
|
146
|
+
// Compare embedded revision timestamps and prefer the first registered
|
|
147
|
+
// source on an exact tie, so repeated scans remain deterministic.
|
|
148
|
+
const revisions = await Promise.all(paths.map((path) => cache.read(path, source)));
|
|
149
|
+
let winner = revisions.findIndex((item) => item !== null);
|
|
150
|
+
if (winner < 0) return null;
|
|
151
|
+
for (let index = 1; index < paths.length; index += 1) {
|
|
152
|
+
const left = revisions[index]?.entry.revision;
|
|
153
|
+
const right = revisions[winner]?.entry.revision;
|
|
154
|
+
if (!left) continue;
|
|
155
|
+
if (!right) throw new Error(`Incomplete canonical revisions for ${source}`);
|
|
156
|
+
if (
|
|
157
|
+
left[0] > right[0] ||
|
|
158
|
+
(left[0] === right[0] &&
|
|
159
|
+
(left[1] > right[1] || (left[1] === right[1] && left[2] > right[2])))
|
|
160
|
+
)
|
|
161
|
+
winner = index;
|
|
162
|
+
}
|
|
163
|
+
const selectedPath = paths[winner];
|
|
164
|
+
if (selectedPath === undefined)
|
|
165
|
+
throw new Error(`No selected canonical source for ${source}`);
|
|
166
|
+
return { source, path: selectedPath };
|
|
167
|
+
}),
|
|
168
|
+
)
|
|
169
|
+
).filter((item): item is { source: string; path: string } => item !== null),
|
|
182
170
|
);
|
|
183
171
|
}
|
|
184
172
|
return selected.sort((left, right) => left.source.localeCompare(right.source));
|
|
@@ -192,9 +180,16 @@ export async function sourceFingerprint(
|
|
|
192
180
|
candidates: SqliteReadModelSourceCandidate[];
|
|
193
181
|
hashedFiles: number;
|
|
194
182
|
reusedFiles: number;
|
|
183
|
+
duplicateReads: number;
|
|
184
|
+
duplicateReused: number;
|
|
195
185
|
}> {
|
|
186
|
+
const revisionCache = await CanonicalSourceRevisionCache.open(
|
|
187
|
+
workspaceRoot,
|
|
188
|
+
options.verifyContent,
|
|
189
|
+
);
|
|
196
190
|
const candidates = await selectCanonicalSources(
|
|
197
191
|
options.sourceRoots ?? (await dashboardCanonicalSourceRoots(workspaceRoot)),
|
|
192
|
+
revisionCache,
|
|
198
193
|
);
|
|
199
194
|
const candidatesWithMetadata: SqliteReadModelSourceCandidate[] = [];
|
|
200
195
|
let hashedFiles = 0;
|
|
@@ -261,6 +256,7 @@ export async function sourceFingerprint(
|
|
|
261
256
|
).filter((candidate): candidate is SqliteReadModelSourceCandidate => candidate !== null),
|
|
262
257
|
);
|
|
263
258
|
}
|
|
259
|
+
await revisionCache.persist();
|
|
264
260
|
return {
|
|
265
261
|
fingerprint: candidatesWithMetadata
|
|
266
262
|
.map(
|
|
@@ -271,6 +267,8 @@ export async function sourceFingerprint(
|
|
|
271
267
|
candidates: candidatesWithMetadata,
|
|
272
268
|
hashedFiles,
|
|
273
269
|
reusedFiles,
|
|
270
|
+
duplicateReads: revisionCache.reads,
|
|
271
|
+
duplicateReused: revisionCache.reused,
|
|
274
272
|
};
|
|
275
273
|
}
|
|
276
274
|
|
|
@@ -23,13 +23,7 @@ async function createSqliteTestRoot(): Promise<string> {
|
|
|
23
23
|
afterEach(async () => {
|
|
24
24
|
vi.useRealTimers();
|
|
25
25
|
vi.restoreAllMocks();
|
|
26
|
-
await Promise.all(
|
|
27
|
-
roots
|
|
28
|
-
.splice(0)
|
|
29
|
-
.map((root) =>
|
|
30
|
-
import("node:fs/promises").then(({ rm }) => rm(root, { recursive: true, force: true })),
|
|
31
|
-
),
|
|
32
|
-
);
|
|
26
|
+
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
33
27
|
});
|
|
34
28
|
describe("SQLite dashboard read model", () => {
|
|
35
29
|
it("materializes a fresh workspace without opening a nonexistent checkpoint database", async () => {
|
|
@@ -87,7 +81,7 @@ describe("SQLite dashboard read model", () => {
|
|
|
87
81
|
filesystem.close();
|
|
88
82
|
}
|
|
89
83
|
});
|
|
90
|
-
it("
|
|
84
|
+
it("serves an unchanged validated snapshot as a stale seed pending canonical verification", async () => {
|
|
91
85
|
const root = await createSqliteTestRoot();
|
|
92
86
|
roots.push(root);
|
|
93
87
|
await mkdir(join(root, ".agents/config"), { recursive: true });
|
|
@@ -95,7 +89,9 @@ describe("SQLite dashboard read model", () => {
|
|
|
95
89
|
vi.spyOn(Date, "now").mockReturnValue(Date.now() + 5_000);
|
|
96
90
|
await rebuildSqliteReadModel(root);
|
|
97
91
|
await expect(rebuildSqliteReadModel(root, { preferExisting: true })).resolves.toMatchObject({
|
|
98
|
-
cache: "
|
|
92
|
+
cache: "stale_seed",
|
|
93
|
+
fingerprint_ms: 0,
|
|
94
|
+
materialize_ms: 0,
|
|
99
95
|
});
|
|
100
96
|
});
|
|
101
97
|
it("rolls back a failed refresh including first-fold payloads while a reader remains open", async () => {
|
|
@@ -9,7 +9,11 @@ export type {
|
|
|
9
9
|
SqliteFirstFoldMonthlyQuick,
|
|
10
10
|
} from "./sqlite-read-model-first-fold-types.js";
|
|
11
11
|
export { rebuildSqliteReadModel } from "./sqlite-read-model-rebuild.js";
|
|
12
|
-
export {
|
|
12
|
+
export {
|
|
13
|
+
hasReusableSnapshotPayloads,
|
|
14
|
+
sqliteReadModelPath,
|
|
15
|
+
validateSqliteReadModelSchema,
|
|
16
|
+
} from "./sqlite-read-model-schema.js";
|
|
13
17
|
export {
|
|
14
18
|
type SqliteReadModelSourceCandidate,
|
|
15
19
|
sourceFingerprint,
|