@zaip-ai/cli 4.5.0 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/services/adapters.d.ts +1 -1
- package/dist/services/adapters.js +4 -4
- package/dist/services/adapters.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/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.js +2 -1
- package/dist/services/logger.js.map +1 -1
- package/dist/services/logger.test.js +71 -0
- package/dist/services/logger.test.js.map +1 -1
- 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 +23 -31
- 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 +211 -75
- package/templates/.agents/dashboard/README.md +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-f4ZGJRg3.js → CostCalculationGuidePage-BRtiPijr.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-CkQ_y8kg.js → DashboardHelpDialog-BR4ZeYpE.js} +99 -10
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-v9zGRTlk.js → PricingSettingsDialog-Dr4QDFmn.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-doIMhei7.js → ProjectSettingsPage-BG0N5dw_.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-C_4ftjyB.js → TraceabilityWorkspace-BvoM-YQ0.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-fTQhSL85.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-Cv3WBFIF.js → icons-vendor-Bc6vux5t.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-BMfzKaAR.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DZhppOIk.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-mHgsmxb6.js → react-vendor-DNJQFov3.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-B0CPyEVq.js → traceability-B7tjIB7W.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/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/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 +232 -217
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +201 -188
- 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 +232 -217
- 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-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-view-page.ts +9 -58
- 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 +10 -8
- 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-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 +366 -196
- 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 +36 -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 +303 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +334 -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 +44 -68
- package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +91 -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 +330 -142
- 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,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { basename, resolve } from "node:path";
|
|
3
3
|
import { serve } from "@hono/node-server";
|
|
4
4
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
@@ -6,24 +6,20 @@ import { WorkspaceFileSystem, type WorkspaceLayout } from "@zaip-ai/core";
|
|
|
6
6
|
import { fxRateSchema, PricingRepository, pricingCatalogSchema } from "@zaip-ai/costs";
|
|
7
7
|
import { type Context, Hono } from "hono";
|
|
8
8
|
import { getCookie, setCookie } from "hono/cookie";
|
|
9
|
+
import { createDashboardDiagnostics } from "../dashboard-diagnostics.mjs";
|
|
9
10
|
import {
|
|
10
11
|
type ActiveCodexTelemetryCapture,
|
|
11
12
|
captureRepositoryActiveCodexTelemetry,
|
|
12
13
|
} from "./active-codex-telemetry.js";
|
|
13
14
|
import { runAgentAdapterSync } from "./agent-adapter-sync.js";
|
|
14
|
-
import {
|
|
15
|
-
type AgentAdapterSyncResult,
|
|
16
|
-
AgentCatalogUpdateError,
|
|
17
|
-
persistAgentCatalogUpdate,
|
|
18
|
-
readAgentCatalogSettings,
|
|
19
|
-
} from "./agent-catalog-settings.js";
|
|
15
|
+
import type { AgentAdapterSyncResult } from "./agent-catalog-settings.js";
|
|
20
16
|
import {
|
|
21
17
|
persistProjectSettings,
|
|
22
18
|
readProjectOverview,
|
|
23
19
|
readProjectSettings,
|
|
24
20
|
} from "./project-settings.js";
|
|
25
21
|
import { createProjectionRefreshDiagnostics } from "./projection-refresh-diagnostics.js";
|
|
26
|
-
import {
|
|
22
|
+
import { registerAgentCatalogRoutes } from "./server-agent-catalog-routes.js";
|
|
27
23
|
import { registerBrowserLeaseRoute } from "./server-browser-lease.js";
|
|
28
24
|
import { registerCostsRoutes } from "./server-costs-routes.js";
|
|
29
25
|
import {
|
|
@@ -36,12 +32,17 @@ import { lifecycleManifestState } from "./server-lifecycle-diagnostics.js";
|
|
|
36
32
|
import { registerOperationsRoutes } from "./server-operations-routes.js";
|
|
37
33
|
import { projectionPeriodFilter } from "./server-period.js";
|
|
38
34
|
import { buildFirstFoldQuickProjection, dashboardSnapshot } from "./server-read-projections.js";
|
|
35
|
+
import {
|
|
36
|
+
createLoggedWorkReconciliation,
|
|
37
|
+
registerReconciliationRoute,
|
|
38
|
+
} from "./server-reconciliation-route.js";
|
|
39
39
|
import { isAllowedDashboardAuthority } from "./server-request-authority.js";
|
|
40
40
|
import { refreshSqliteSnapshotInWorker } from "./server-snapshot-refresh.js";
|
|
41
41
|
import { readCommittedSnapshotRevision } from "./server-snapshot-revision.js";
|
|
42
42
|
import { startSnapshotSynchronization } from "./server-snapshot-synchronization.js";
|
|
43
43
|
import { writeReadModelStartup, writeStartupPhase } from "./server-startup.js";
|
|
44
44
|
import { selectedCurrency } from "./server-summary-helpers.js";
|
|
45
|
+
import { createTelemetryProjectionRefresh } from "./server-telemetry-refresh.js";
|
|
45
46
|
import { registerTraceRoutes } from "./server-trace-routes.js";
|
|
46
47
|
import {
|
|
47
48
|
defaultSessionToken,
|
|
@@ -50,20 +51,11 @@ import {
|
|
|
50
51
|
isSafeUiPath,
|
|
51
52
|
shouldBootstrapBrowserSession,
|
|
52
53
|
} from "./server-ui-security.js";
|
|
53
|
-
import {
|
|
54
|
-
createWorkReconciliation,
|
|
55
|
-
waitForWorkReconciliation,
|
|
56
|
-
} from "./server-work-reconciliation.js";
|
|
57
54
|
import { dashboardCanonicalSourceRoots } from "./sqlite-read-model-source-fingerprint.js";
|
|
58
55
|
|
|
59
56
|
export { reconciliationNeedsRefresh } from "./server-work-reconciliation.js";
|
|
60
57
|
|
|
61
|
-
import {
|
|
62
|
-
dashboardIdentity,
|
|
63
|
-
dashboardWorkspaceKey,
|
|
64
|
-
ReconciliationCommandTimeoutError,
|
|
65
|
-
reconcileOpenWork,
|
|
66
|
-
} from "./server-workspace.js";
|
|
58
|
+
import { dashboardIdentity, dashboardWorkspaceKey, reconcileOpenWork } from "./server-workspace.js";
|
|
67
59
|
import { rebuildSqliteReadModel, SqliteReadModelFileSystem } from "./sqlite-read-model.js";
|
|
68
60
|
|
|
69
61
|
export { manifestRun, mergeManifestRuns } from "./manifest-runs.js";
|
|
@@ -95,6 +87,7 @@ interface DashboardAppOptions {
|
|
|
95
87
|
snapshotReadOnly?: boolean;
|
|
96
88
|
reconcileOpenWork?: typeof reconcileOpenWork;
|
|
97
89
|
refreshReadModel?: () => Promise<void>;
|
|
90
|
+
captureTelemetry?: () => Promise<ActiveCodexTelemetryCapture>;
|
|
98
91
|
snapshotState?: "current" | "updating";
|
|
99
92
|
/** Enable filesystem and day rollover synchronization for the serving process. */
|
|
100
93
|
watchSources?: boolean;
|
|
@@ -123,6 +116,8 @@ export function createDashboardApp(
|
|
|
123
116
|
options: DashboardAppOptions = {},
|
|
124
117
|
): Hono {
|
|
125
118
|
const app = new Hono();
|
|
119
|
+
const diagnostics = createDashboardDiagnostics(workspaceRoot, "runtime");
|
|
120
|
+
diagnostics.log("runtime_app_started", { snapshot_read_only: options.snapshotReadOnly });
|
|
126
121
|
const filesystem = options.filesystem ?? new WorkspaceFileSystem(workspaceRoot);
|
|
127
122
|
const isSqliteSnapshot = filesystem instanceof SqliteReadModelFileSystem;
|
|
128
123
|
// Cache layout within a committed revision and reload after synchronization.
|
|
@@ -145,7 +140,6 @@ export function createDashboardApp(
|
|
|
145
140
|
const workspace = resolve(workspaceRoot);
|
|
146
141
|
const workspaceName = basename(workspace);
|
|
147
142
|
const syncAgentAdapters = options.syncAgentAdapters ?? (() => runAgentAdapterSync(workspaceRoot));
|
|
148
|
-
let catalogWriteTail = Promise.resolve();
|
|
149
143
|
type DashboardSnapshot = Awaited<ReturnType<typeof dashboardSnapshot>>;
|
|
150
144
|
const snapshotInFlight = new Map<string, Promise<DashboardSnapshot>>();
|
|
151
145
|
const cachedSnapshots = new Map<string, { snapshot: DashboardSnapshot; cachedAt: number }>();
|
|
@@ -198,12 +192,17 @@ export function createDashboardApp(
|
|
|
198
192
|
let activeCapture: ActiveCodexTelemetryCapture | null = null;
|
|
199
193
|
let projectionRefreshInFlight: Promise<void> | null = null;
|
|
200
194
|
let projectionRefreshRequested = false;
|
|
201
|
-
const
|
|
195
|
+
const associatedRefreshJobs = new Set<string>();
|
|
196
|
+
const rebuildProjection = async (associatedJobId: string | null = null) => {
|
|
197
|
+
if (associatedJobId !== null) associatedRefreshJobs.add(associatedJobId);
|
|
202
198
|
if (projectionRefreshInFlight !== null) {
|
|
203
199
|
projectionRefreshRequested = true;
|
|
204
200
|
return projectionRefreshInFlight;
|
|
205
201
|
}
|
|
206
202
|
snapshotState = "updating";
|
|
203
|
+
const refresh_id = randomUUID();
|
|
204
|
+
const job_id = associatedJobId;
|
|
205
|
+
diagnostics.log("projection_refresh_started", { refresh_id, job_id });
|
|
207
206
|
projectionRefreshInFlight = (async () => {
|
|
208
207
|
let passes = 0;
|
|
209
208
|
do {
|
|
@@ -217,36 +216,57 @@ export function createDashboardApp(
|
|
|
217
216
|
} while (projectionRefreshRequested && passes < 2);
|
|
218
217
|
snapshotState = "current";
|
|
219
218
|
projectionRefreshDiagnostics.clear();
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
219
|
+
diagnostics.log("projection_refresh_completed", {
|
|
220
|
+
refresh_id,
|
|
221
|
+
job_id: [...associatedRefreshJobs][0] ?? job_id,
|
|
222
|
+
job_ids: [...associatedRefreshJobs],
|
|
223
|
+
projection_updated_at: isSqliteSnapshot ? filesystem.projectionUpdatedAt() : null,
|
|
224
|
+
latest_token_collected_at: isSqliteSnapshot ? filesystem.latestTokenCollectedAt() : null,
|
|
225
|
+
});
|
|
226
|
+
if (!projectionRefreshRequested) associatedRefreshJobs.clear();
|
|
227
|
+
})()
|
|
228
|
+
.catch((error: unknown) => {
|
|
229
|
+
// A failed promotion is still an older snapshot; leave its readiness
|
|
230
|
+
// explicit and retain evidence instead of silently swallowing failure.
|
|
231
|
+
projectionRefreshDiagnostics.record({ trigger: "source_change", error });
|
|
232
|
+
diagnostics.log("projection_refresh_failed", {
|
|
233
|
+
refresh_id,
|
|
234
|
+
job_id: [...associatedRefreshJobs][0] ?? job_id,
|
|
235
|
+
job_ids: [...associatedRefreshJobs],
|
|
236
|
+
error,
|
|
237
|
+
});
|
|
238
|
+
associatedRefreshJobs.clear();
|
|
239
|
+
throw error;
|
|
240
|
+
})
|
|
241
|
+
.finally(() => {
|
|
242
|
+
projectionRefreshInFlight = null;
|
|
243
|
+
// Preserve a source change that arrived during the bounded catch-up.
|
|
244
|
+
// It starts after the committed generation becomes observable, rather
|
|
245
|
+
// than extending the current `updating` state without bound.
|
|
246
|
+
if (projectionRefreshRequested) {
|
|
247
|
+
projectionRefreshRequested = false;
|
|
248
|
+
// Yield a real event-loop turn with `current` exposed before starting
|
|
249
|
+
// another rebuild. `/ready` can then advertise the committed SQLite
|
|
250
|
+
// generation and open tabs can read it coherently.
|
|
251
|
+
const followUp = setTimeout(() => {
|
|
252
|
+
void rebuildProjection().catch((error: unknown) =>
|
|
253
|
+
projectionRefreshDiagnostics.record({ trigger: "source_change", error }),
|
|
254
|
+
);
|
|
255
|
+
}, 250);
|
|
256
|
+
followUp.unref();
|
|
257
|
+
}
|
|
258
|
+
});
|
|
238
259
|
return projectionRefreshInFlight;
|
|
239
260
|
};
|
|
240
|
-
|
|
241
|
-
workspaceRoot,
|
|
242
|
-
discoverSourceRoots: () => dashboardCanonicalSourceRoots(workspaceRoot),
|
|
243
|
-
staleSeed: isSqliteSnapshot && snapshotState === "updating",
|
|
244
|
-
watchSources: isSqliteSnapshot && options.watchSources === true,
|
|
261
|
+
const telemetryRefresh = createTelemetryProjectionRefresh({
|
|
245
262
|
refresh: rebuildProjection,
|
|
246
|
-
...(
|
|
247
|
-
? {
|
|
248
|
-
:
|
|
249
|
-
|
|
263
|
+
...(options.captureTelemetry !== undefined
|
|
264
|
+
? { capture: options.captureTelemetry }
|
|
265
|
+
: isSqliteSnapshot &&
|
|
266
|
+
options.watchSources === true &&
|
|
267
|
+
process.env.ZAIP_DASHBOARD_WATCH_SOURCES !== "0"
|
|
268
|
+
? { capture: () => captureRepositoryActiveCodexTelemetry(workspaceRoot) }
|
|
269
|
+
: {}),
|
|
250
270
|
onCaptureSuccess: (result) => {
|
|
251
271
|
activeCapture = result;
|
|
252
272
|
activeCaptureFailure = null;
|
|
@@ -259,12 +279,29 @@ export function createDashboardApp(
|
|
|
259
279
|
};
|
|
260
280
|
},
|
|
261
281
|
});
|
|
262
|
-
const
|
|
263
|
-
|
|
282
|
+
const visibleSnapshotState = () =>
|
|
283
|
+
telemetryRefresh.pending || activeCaptureFailure !== null ? "updating" : snapshotState;
|
|
284
|
+
startSnapshotSynchronization({
|
|
285
|
+
workspaceRoot,
|
|
286
|
+
discoverSourceRoots: () => dashboardCanonicalSourceRoots(workspaceRoot),
|
|
287
|
+
staleSeed: isSqliteSnapshot && snapshotState === "updating",
|
|
288
|
+
watchSources: isSqliteSnapshot && options.watchSources === true,
|
|
289
|
+
refresh: rebuildProjection,
|
|
290
|
+
synchronizeTelemetry: telemetryRefresh.refresh,
|
|
291
|
+
onRefreshFailure: projectionRefreshDiagnostics.record,
|
|
292
|
+
});
|
|
293
|
+
const reconcileWork = createLoggedWorkReconciliation({
|
|
294
|
+
diagnostics,
|
|
295
|
+
reconcile: (mode, jobId) =>
|
|
264
296
|
reconcileOpen(canonicalFilesystem, workspaceRoot, {
|
|
265
297
|
hostTerminalOnly: mode === "host-terminal",
|
|
298
|
+
jobId,
|
|
266
299
|
}),
|
|
267
|
-
|
|
300
|
+
snapshotTimes: () => ({
|
|
301
|
+
projection_updated_at: isSqliteSnapshot ? filesystem.projectionUpdatedAt() : null,
|
|
302
|
+
latest_token_collected_at: isSqliteSnapshot ? filesystem.latestTokenCollectedAt() : null,
|
|
303
|
+
}),
|
|
304
|
+
refresh: (jobId) => rebuildProjection(jobId),
|
|
268
305
|
});
|
|
269
306
|
// Terminal usage is persisted by the host/CLI lifecycle boundary. The
|
|
270
307
|
// source synchronizer above projects those records, but the Dashboard must
|
|
@@ -331,7 +368,9 @@ export function createDashboardApp(
|
|
|
331
368
|
const browserLeaseWrite =
|
|
332
369
|
context.req.method === "GET" && context.req.path === `${API_PREFIX}/browser-lease`;
|
|
333
370
|
const projectionRefreshWrite =
|
|
334
|
-
context.req.method === "POST" &&
|
|
371
|
+
context.req.method === "POST" &&
|
|
372
|
+
(context.req.path === `${API_PREFIX}/refresh-projection` ||
|
|
373
|
+
context.req.path === `${API_PREFIX}/capture-telemetry`);
|
|
335
374
|
if (
|
|
336
375
|
context.req.method !== "GET" &&
|
|
337
376
|
context.req.method !== "HEAD" &&
|
|
@@ -350,7 +389,8 @@ export function createDashboardApp(
|
|
|
350
389
|
? "GET, HEAD, PUT"
|
|
351
390
|
: context.req.path === `${API_PREFIX}/reconcile-open-work`
|
|
352
391
|
? "POST"
|
|
353
|
-
: context.req.path === `${API_PREFIX}/refresh-projection`
|
|
392
|
+
: context.req.path === `${API_PREFIX}/refresh-projection` ||
|
|
393
|
+
context.req.path === `${API_PREFIX}/capture-telemetry`
|
|
354
394
|
? "POST"
|
|
355
395
|
: "GET, HEAD",
|
|
356
396
|
);
|
|
@@ -408,7 +448,7 @@ export function createDashboardApp(
|
|
|
408
448
|
lifecycle_manifest_state: manifestState,
|
|
409
449
|
read_only: true,
|
|
410
450
|
storage: isSqliteSnapshot ? filesystem.metadata() : { storage_mode: "filesystem" },
|
|
411
|
-
snapshot_state:
|
|
451
|
+
snapshot_state: visibleSnapshotState(),
|
|
412
452
|
projection_refresh_failure: projectionRefreshDiagnostics.failure,
|
|
413
453
|
active_capture_failure: activeCaptureFailure,
|
|
414
454
|
active_capture: activeCapture,
|
|
@@ -468,11 +508,13 @@ export function createDashboardApp(
|
|
|
468
508
|
// monthly Operations projection was materialized during rebuild;
|
|
469
509
|
// /ready must never start a competing projection in the background.
|
|
470
510
|
day_count: metadata.daily_aggregate_count,
|
|
471
|
-
snapshot_state:
|
|
511
|
+
snapshot_state: visibleSnapshotState(),
|
|
472
512
|
projection_refresh_failure: projectionRefreshDiagnostics.failure,
|
|
473
513
|
active_capture_failure: activeCaptureFailure,
|
|
474
514
|
active_capture: activeCapture,
|
|
475
515
|
snapshot_revision: revision,
|
|
516
|
+
projection_updated_at: filesystem.projectionUpdatedAt(),
|
|
517
|
+
latest_token_collected_at: filesystem.latestTokenCollectedAt(),
|
|
476
518
|
});
|
|
477
519
|
}
|
|
478
520
|
const projection = await buildFirstFoldQuickProjection(filesystem, layout, {
|
|
@@ -518,48 +560,43 @@ export function createDashboardApp(
|
|
|
518
560
|
readAggregate,
|
|
519
561
|
clearSnapshotCache,
|
|
520
562
|
});
|
|
521
|
-
app
|
|
563
|
+
registerReconciliationRoute(app, {
|
|
564
|
+
readLayout,
|
|
565
|
+
diagnostics,
|
|
566
|
+
reconcileWork,
|
|
567
|
+
refresh: (jobId) => rebuildProjection(jobId),
|
|
568
|
+
snapshotState: visibleSnapshotState,
|
|
569
|
+
refreshCompleted: () =>
|
|
570
|
+
visibleSnapshotState() === "current" && projectionRefreshInFlight === null,
|
|
571
|
+
});
|
|
572
|
+
app.post(`${API_PREFIX}/refresh-projection`, async (context) => {
|
|
522
573
|
try {
|
|
523
574
|
await readLayout();
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const reconciledIds = await waitForWorkReconciliation(reconcileWork("manual"), () => {
|
|
527
|
-
void rebuildProjection().catch(() => undefined);
|
|
528
|
-
});
|
|
529
|
-
return context.json({
|
|
530
|
-
reconciliation: reconciledIds,
|
|
531
|
-
snapshot_state: snapshotState,
|
|
532
|
-
projection_refresh_completed:
|
|
533
|
-
snapshotState === "current" && projectionRefreshInFlight === null,
|
|
534
|
-
});
|
|
535
|
-
} catch (error) {
|
|
536
|
-
if (error instanceof V2WorkspaceRequiredError) throw error;
|
|
537
|
-
if (error instanceof ReconciliationCommandTimeoutError) {
|
|
538
|
-
// SIGTERM can occur after the CLI persisted some records. Start an
|
|
539
|
-
// atomic refresh without extending the request past the client's
|
|
540
|
-
// deadline, and make that uncertainty explicit to the caller.
|
|
575
|
+
await telemetryRefresh.refresh(true);
|
|
576
|
+
if (activeCaptureFailure !== null)
|
|
541
577
|
return context.json(
|
|
542
578
|
{
|
|
543
|
-
error:
|
|
544
|
-
|
|
545
|
-
|
|
579
|
+
error: "Telemetry capture is unavailable",
|
|
580
|
+
snapshot_state: visibleSnapshotState(),
|
|
581
|
+
active_capture_failure: activeCaptureFailure,
|
|
546
582
|
},
|
|
547
|
-
|
|
583
|
+
503,
|
|
548
584
|
);
|
|
549
|
-
}
|
|
550
|
-
return context.json({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
app.post(`${API_PREFIX}/refresh-projection`, async (context) => {
|
|
554
|
-
try {
|
|
555
|
-
await readLayout();
|
|
556
|
-
await rebuildProjection();
|
|
557
585
|
return context.json(await readSnapshot(context));
|
|
558
586
|
} catch (error) {
|
|
559
587
|
if (error instanceof V2WorkspaceRequiredError) throw error;
|
|
560
588
|
return context.json({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
561
589
|
}
|
|
562
590
|
});
|
|
591
|
+
app.post(`${API_PREFIX}/capture-telemetry`, async (context) => {
|
|
592
|
+
await readLayout();
|
|
593
|
+
void telemetryRefresh
|
|
594
|
+
.refresh()
|
|
595
|
+
.catch((error: unknown) =>
|
|
596
|
+
projectionRefreshDiagnostics.record({ trigger: "source_change", error }),
|
|
597
|
+
);
|
|
598
|
+
return context.json({ snapshot_state: visibleSnapshotState() }, 202);
|
|
599
|
+
});
|
|
563
600
|
registerCostsRoutes(app, {
|
|
564
601
|
filesystem,
|
|
565
602
|
canonicalFilesystem,
|
|
@@ -599,56 +636,11 @@ export function createDashboardApp(
|
|
|
599
636
|
clearSnapshotCache();
|
|
600
637
|
return context.json(rate);
|
|
601
638
|
});
|
|
602
|
-
app
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
context,
|
|
608
|
-
{ error: error instanceof Error ? error.message : String(error) },
|
|
609
|
-
400,
|
|
610
|
-
);
|
|
611
|
-
}
|
|
612
|
-
});
|
|
613
|
-
app.get(`${API_PREFIX}/agent-catalog-assets/:agentId`, async (context) => {
|
|
614
|
-
const portrait = agentPortraitFiles[context.req.param("agentId")];
|
|
615
|
-
if (portrait === undefined) return context.json({ error: "Agent portrait not found" }, 404);
|
|
616
|
-
try {
|
|
617
|
-
return context.body(
|
|
618
|
-
await readFile(resolve(workspaceRoot, ".agents/config/src", portrait)),
|
|
619
|
-
200,
|
|
620
|
-
{ "cache-control": "no-store", "content-type": "image/png" },
|
|
621
|
-
);
|
|
622
|
-
} catch (error) {
|
|
623
|
-
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
624
|
-
return context.json({ error: "Agent portrait not found" }, 404);
|
|
625
|
-
}
|
|
626
|
-
return context.json({ error: "Agent portrait could not be read" }, 500);
|
|
627
|
-
}
|
|
628
|
-
});
|
|
629
|
-
app.put(`${API_PREFIX}/agent-catalog`, async (context) => {
|
|
630
|
-
const update = async () => {
|
|
631
|
-
try {
|
|
632
|
-
const persisted = await persistAgentCatalogUpdate(
|
|
633
|
-
filesystem,
|
|
634
|
-
await context.req.json(),
|
|
635
|
-
syncAgentAdapters,
|
|
636
|
-
);
|
|
637
|
-
clearSnapshotCache();
|
|
638
|
-
return context.json(persisted);
|
|
639
|
-
} catch (error) {
|
|
640
|
-
if (error instanceof AgentCatalogUpdateError) {
|
|
641
|
-
return context.json({ error: error.message }, error.status);
|
|
642
|
-
}
|
|
643
|
-
return context.json({ error: "Agent catalog update failed" }, 500);
|
|
644
|
-
}
|
|
645
|
-
};
|
|
646
|
-
const result = catalogWriteTail.then(update, update);
|
|
647
|
-
catalogWriteTail = result.then(
|
|
648
|
-
() => undefined,
|
|
649
|
-
() => undefined,
|
|
650
|
-
);
|
|
651
|
-
return result;
|
|
639
|
+
registerAgentCatalogRoutes(app, {
|
|
640
|
+
filesystem,
|
|
641
|
+
workspaceRoot,
|
|
642
|
+
syncAgentAdapters,
|
|
643
|
+
clearSnapshotCache,
|
|
652
644
|
});
|
|
653
645
|
app.on(["GET", "HEAD"], `${API_PREFIX}/project-settings`, async (context) => {
|
|
654
646
|
try {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { providerVisibleMetricsSql } from "./sqlite-provider-visibility.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Date-only Cost pages can use immutable, indexed activity facts. Any
|
|
3
5
|
* additional filter deliberately falls back to the canonical SQL path, whose
|
|
@@ -181,8 +183,18 @@ export function readDateOnlyCostDimensionPage(
|
|
|
181
183
|
dimension === "model"
|
|
182
184
|
? "COALESCE(task_population.task_count,0) AS task_count,0 AS prd_count,0 AS spec_count,0 AS validation_count,0 AS evidence_count"
|
|
183
185
|
: "COALESCE(task_population.task_count,0) task_count";
|
|
186
|
+
const aggregatedRows = `${cte}, aggregated_rows AS (
|
|
187
|
+
SELECT keys.key,keys.key AS label,COALESCE(population.invocation_count,0) invocation_count,
|
|
188
|
+
${rowProductivity},population.total_duration,population.source_event_count,
|
|
189
|
+
financial.total_tokens,financial.total_cost_usd,financial.total_cost_brl
|
|
190
|
+
FROM keys LEFT JOIN population USING(key) LEFT JOIN task_population USING(key)
|
|
191
|
+
LEFT JOIN financial USING(key)
|
|
192
|
+
), visible_rows AS (SELECT * FROM aggregated_rows${
|
|
193
|
+
dimension === "provider" ? ` WHERE ${providerVisibleMetricsSql}` : ""
|
|
194
|
+
})`;
|
|
184
195
|
const total_count = Number(
|
|
185
|
-
database.prepare(`${
|
|
196
|
+
database.prepare(`${aggregatedRows} SELECT COUNT(*) AS count FROM visible_rows`).get(...args)
|
|
197
|
+
?.count ?? 0,
|
|
186
198
|
);
|
|
187
199
|
const totals =
|
|
188
200
|
database
|
|
@@ -192,7 +204,7 @@ export function readDateOnlyCostDimensionPage(
|
|
|
192
204
|
.get(...args) ?? {};
|
|
193
205
|
const rows = database
|
|
194
206
|
.prepare(
|
|
195
|
-
`${
|
|
207
|
+
`${aggregatedRows} SELECT * FROM visible_rows ORDER BY total_cost_brl DESC,key LIMIT ? OFFSET ?`,
|
|
196
208
|
)
|
|
197
209
|
.all(...args, limit, offset);
|
|
198
210
|
if (dimension === "model") {
|
package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.test.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { prepareDashboardSqliteStartup } from "./sqlite-dashboard-startup.js";
|
|
6
|
+
import { rebuildSqliteReadModel } from "./sqlite-read-model-rebuild.js";
|
|
7
|
+
import { sqliteReadModelPath, validateSqliteReadModelSchema } from "./sqlite-read-model-schema.js";
|
|
8
|
+
import { sourceFingerprint } from "./sqlite-read-model-source-fingerprint.js";
|
|
9
|
+
|
|
10
|
+
vi.mock("./sqlite-read-model-source-fingerprint.js", async (importOriginal) => {
|
|
11
|
+
const actual = await importOriginal<typeof import("./sqlite-read-model-source-fingerprint.js")>();
|
|
12
|
+
return { ...actual, sourceFingerprint: vi.fn(actual.sourceFingerprint) };
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const roots: string[] = [];
|
|
16
|
+
const taskContent = (title: string) =>
|
|
17
|
+
`---\nid: TASK-STARTUP\ntitle: ${title}\nstatus: ready\nrisk: standard\ncreated_at: 2026-08-01T12:00:00.000Z\n---\n`;
|
|
18
|
+
|
|
19
|
+
async function fixture(seed = true) {
|
|
20
|
+
vi.stubEnv("ZAIP_SQLITE_DB_PATH", undefined);
|
|
21
|
+
const fixtures = resolve(import.meta.dirname, "../../../../../.local/sqlite/tests");
|
|
22
|
+
await mkdir(fixtures, { recursive: true });
|
|
23
|
+
const root = await mkdtemp(resolve(fixtures, "startup-"));
|
|
24
|
+
roots.push(root);
|
|
25
|
+
const task = resolve(root, ".agents/records/tasks/task.md");
|
|
26
|
+
await mkdir(dirname(task), { recursive: true });
|
|
27
|
+
await mkdir(resolve(root, ".agents/config"), { recursive: true });
|
|
28
|
+
await writeFile(resolve(root, ".agents/config/project.yaml"), "zaip:\n enabled: true\n");
|
|
29
|
+
await writeFile(task, taskContent("Original seed"));
|
|
30
|
+
if (seed) await rebuildSqliteReadModel(root);
|
|
31
|
+
vi.mocked(sourceFingerprint).mockClear();
|
|
32
|
+
return { root, task, path: sqliteReadModelPath(root) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function editDatabase(path: string, sql: string) {
|
|
36
|
+
const database = new DatabaseSync(path);
|
|
37
|
+
try {
|
|
38
|
+
database.exec(sql);
|
|
39
|
+
} finally {
|
|
40
|
+
database.close();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function persistedTask(path: string): string {
|
|
45
|
+
const database = new DatabaseSync(path, { readOnly: true });
|
|
46
|
+
try {
|
|
47
|
+
return (
|
|
48
|
+
database
|
|
49
|
+
.prepare("SELECT content FROM zaip_vw_files WHERE path=?")
|
|
50
|
+
.get(".agents/records/tasks/task.md") as { content: string }
|
|
51
|
+
).content;
|
|
52
|
+
} finally {
|
|
53
|
+
database.close();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
afterEach(async () => {
|
|
58
|
+
vi.unstubAllEnvs();
|
|
59
|
+
vi.mocked(sourceFingerprint).mockClear();
|
|
60
|
+
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("dashboard SQLite startup", () => {
|
|
64
|
+
it("serves a validated stale seed without source inventory even after canonical changes", async () => {
|
|
65
|
+
const { root, task, path } = await fixture();
|
|
66
|
+
await writeFile(task, taskContent("Changed canonically"));
|
|
67
|
+
const before = await readFile(path);
|
|
68
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
69
|
+
await expect(prepareDashboardSqliteStartup(root)).resolves.toMatchObject({
|
|
70
|
+
path,
|
|
71
|
+
files: 2,
|
|
72
|
+
cache: "stale_seed",
|
|
73
|
+
fingerprint_ms: 0,
|
|
74
|
+
materialize_ms: 0,
|
|
75
|
+
refresh: { processed_sources: 0, reused_sources: 0, canonical_runs_refolded: 0 },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
expect(sourceFingerprint).not.toHaveBeenCalled();
|
|
79
|
+
expect(await readFile(path)).toEqual(before);
|
|
80
|
+
expect(persistedTask(path)).toContain("Original seed");
|
|
81
|
+
// The normal refresh remains canonical and publishes the changed source.
|
|
82
|
+
await rebuildSqliteReadModel(root);
|
|
83
|
+
expect(sourceFingerprint).toHaveBeenCalled();
|
|
84
|
+
expect(persistedTask(path)).toContain("Changed canonically");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it.each([
|
|
88
|
+
[
|
|
89
|
+
"schema",
|
|
90
|
+
"UPDATE zaip_aios_activities SET payload_json=json_set(payload_json,'$.value','old') WHERE payload_type='metadata' AND json_extract(payload_json,'$.key')='schema_version'",
|
|
91
|
+
],
|
|
92
|
+
["index", "DROP INDEX zaip_activities_work_item_identity"],
|
|
93
|
+
[
|
|
94
|
+
"missing first fold",
|
|
95
|
+
"DELETE FROM zaip_aios_activities WHERE payload_type='first_fold_all_time_cache'",
|
|
96
|
+
],
|
|
97
|
+
[
|
|
98
|
+
"truncated board",
|
|
99
|
+
"UPDATE zaip_aios_activities SET payload_json=json_set(payload_json,'$.pages.task',json('[]')) WHERE payload_type='first_fold_all_time_board_cache'",
|
|
100
|
+
],
|
|
101
|
+
[
|
|
102
|
+
"malformed board",
|
|
103
|
+
"UPDATE zaip_aios_activities SET payload_json='invalid' WHERE payload_type='first_fold_all_time_board_cache'",
|
|
104
|
+
],
|
|
105
|
+
[
|
|
106
|
+
"malformed cost summary",
|
|
107
|
+
"UPDATE zaip_aios_activities SET payload_json='invalid' WHERE payload_type='cost_monitor_summary_cache'",
|
|
108
|
+
],
|
|
109
|
+
[
|
|
110
|
+
"invalid file count",
|
|
111
|
+
"UPDATE zaip_aios_activities SET payload_json=json_set(payload_json,'$.value','NaN') WHERE payload_type='metadata' AND json_extract(payload_json,'$.key')='source_file_count'",
|
|
112
|
+
],
|
|
113
|
+
])("rejects %s and rebuilds changed canonical sources", async (_damage, sql) => {
|
|
114
|
+
const { root, task, path } = await fixture();
|
|
115
|
+
editDatabase(path, sql);
|
|
116
|
+
await writeFile(task, taskContent("Recovered canonical source"));
|
|
117
|
+
await expect(prepareDashboardSqliteStartup(root)).resolves.toMatchObject({
|
|
118
|
+
path,
|
|
119
|
+
cache: "rebuilt",
|
|
120
|
+
});
|
|
121
|
+
expect(sourceFingerprint).toHaveBeenCalled();
|
|
122
|
+
expect(() => validateSqliteReadModelSchema(path)).not.toThrow();
|
|
123
|
+
expect(persistedTask(path)).toContain("Recovered canonical source");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("builds canonical data when the seed is absent", async () => {
|
|
127
|
+
const { root, path } = await fixture(false);
|
|
128
|
+
await expect(prepareDashboardSqliteStartup(root)).resolves.toMatchObject({
|
|
129
|
+
path,
|
|
130
|
+
cache: "rebuilt",
|
|
131
|
+
});
|
|
132
|
+
expect(sourceFingerprint).toHaveBeenCalled();
|
|
133
|
+
expect(() => validateSqliteReadModelSchema(path)).not.toThrow();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("accepts a genuinely empty committed model without fabricating source work", async () => {
|
|
137
|
+
const { root, task, path } = await fixture(false);
|
|
138
|
+
await rm(task);
|
|
139
|
+
await rm(resolve(root, ".agents/config/project.yaml"));
|
|
140
|
+
await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ files: 0 });
|
|
141
|
+
vi.mocked(sourceFingerprint).mockClear();
|
|
142
|
+
await expect(prepareDashboardSqliteStartup(root)).resolves.toMatchObject({
|
|
143
|
+
path,
|
|
144
|
+
files: 0,
|
|
145
|
+
cache: "stale_seed",
|
|
146
|
+
fingerprint_ms: 0,
|
|
147
|
+
});
|
|
148
|
+
expect(sourceFingerprint).not.toHaveBeenCalled();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("honors an explicit forced canonical rebuild", async () => {
|
|
152
|
+
const { root, task, path } = await fixture();
|
|
153
|
+
await writeFile(task, taskContent("Forced canonical source"));
|
|
154
|
+
await expect(
|
|
155
|
+
prepareDashboardSqliteStartup(root, { forceCanonical: true }),
|
|
156
|
+
).resolves.toMatchObject({
|
|
157
|
+
path,
|
|
158
|
+
cache: "rebuilt",
|
|
159
|
+
});
|
|
160
|
+
expect(sourceFingerprint).toHaveBeenCalled();
|
|
161
|
+
expect(persistedTask(path)).toContain("Forced canonical source");
|
|
162
|
+
});
|
|
163
|
+
});
|