@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
package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-dashboard-startup.mjs
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { once } from "node:events";
|
|
4
|
+
import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { createServer } from "node:net";
|
|
7
|
+
import { dirname, resolve } from "node:path";
|
|
8
|
+
import { DatabaseSync } from "node:sqlite";
|
|
9
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
10
|
+
import { pathToFileURL } from "node:url";
|
|
11
|
+
|
|
12
|
+
// Exercises both built formats and the exact portable server entrypoint.
|
|
13
|
+
// All fixtures and their single central database are cleaned after the test.
|
|
14
|
+
const packageRoot = resolve(import.meta.dirname, "..");
|
|
15
|
+
const checkout = resolve(packageRoot, "../../../..");
|
|
16
|
+
const fixtures = resolve(checkout, ".local/sqlite/tests");
|
|
17
|
+
const runtime = await import(pathToFileURL(resolve(packageRoot, "server.mjs")).href);
|
|
18
|
+
const cjs = createRequire(import.meta.url)(resolve(packageRoot, "dist/server.cjs"));
|
|
19
|
+
const budgetMs = 10_000;
|
|
20
|
+
const taskContent = (title) =>
|
|
21
|
+
`---\nid: TASK-PORTABLE-STARTUP\ntitle: ${title}\nstatus: ready\nrisk: standard\ncreated_at: 2026-08-01T12:00:00.000Z\n---\n`;
|
|
22
|
+
const previousDbPath = process.env.ZAIP_SQLITE_DB_PATH;
|
|
23
|
+
delete process.env.ZAIP_SQLITE_DB_PATH;
|
|
24
|
+
await mkdir(fixtures, { recursive: true });
|
|
25
|
+
const workspace = await mkdtemp(resolve(fixtures, "portable-startup-"));
|
|
26
|
+
const task = resolve(workspace, ".agents/records/tasks/task.md");
|
|
27
|
+
const expectedPath = resolve(workspace, ".local/sqlite/zaip-aios-dashboard.sqlite");
|
|
28
|
+
let child;
|
|
29
|
+
|
|
30
|
+
function persistedTitle() {
|
|
31
|
+
const database = new DatabaseSync(expectedPath, { readOnly: true });
|
|
32
|
+
try {
|
|
33
|
+
return database
|
|
34
|
+
.prepare("SELECT content FROM zaip_vw_files WHERE path=?")
|
|
35
|
+
.get(".agents/records/tasks/task.md").content;
|
|
36
|
+
} finally {
|
|
37
|
+
database.close();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function availablePort() {
|
|
42
|
+
const reservation = createServer();
|
|
43
|
+
reservation.listen(0, "127.0.0.1");
|
|
44
|
+
await once(reservation, "listening");
|
|
45
|
+
const port = reservation.address().port;
|
|
46
|
+
await new Promise((done, reject) =>
|
|
47
|
+
reservation.close((error) => (error ? reject(error) : done())),
|
|
48
|
+
);
|
|
49
|
+
return port;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function stop() {
|
|
53
|
+
if (!child || child.exitCode !== null) return;
|
|
54
|
+
const exited = once(child, "exit");
|
|
55
|
+
child.kill();
|
|
56
|
+
await exited;
|
|
57
|
+
child = undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function launch(expectedCache, expectedTitle) {
|
|
61
|
+
const port = await availablePort();
|
|
62
|
+
const started = performance.now();
|
|
63
|
+
let stderr = "";
|
|
64
|
+
let spawnError;
|
|
65
|
+
child = spawn(process.execPath, [resolve(packageRoot, "server.mjs")], {
|
|
66
|
+
cwd: packageRoot,
|
|
67
|
+
env: {
|
|
68
|
+
...process.env,
|
|
69
|
+
ZAIP_WORKSPACE: workspace,
|
|
70
|
+
ZAIP_SQLITE_DB_PATH: expectedPath,
|
|
71
|
+
ZAIP_SERVER_PORT: String(port),
|
|
72
|
+
ZAIP_UI_TOKEN: "portable-startup-fixture-token",
|
|
73
|
+
ZAIP_UI_ROOT: resolve(workspace, "ui"),
|
|
74
|
+
ZAIP_DASHBOARD_WATCH_SOURCES: "0",
|
|
75
|
+
ZAIP_DASHBOARD_LAUNCH_PROGRESS: "1",
|
|
76
|
+
},
|
|
77
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
78
|
+
windowsHide: true,
|
|
79
|
+
});
|
|
80
|
+
child.on("error", (error) => {
|
|
81
|
+
spawnError = error;
|
|
82
|
+
});
|
|
83
|
+
child.stderr.setEncoding("utf8");
|
|
84
|
+
child.stderr.on("data", (chunk) => {
|
|
85
|
+
stderr = `${stderr}${chunk}`.slice(-100_000);
|
|
86
|
+
});
|
|
87
|
+
const events = () =>
|
|
88
|
+
stderr.split(/\r?\n/).flatMap((line) => {
|
|
89
|
+
try {
|
|
90
|
+
return [JSON.parse(line)];
|
|
91
|
+
} catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
while (!events().some((event) => event.phase === "listening")) {
|
|
96
|
+
if (spawnError) throw spawnError;
|
|
97
|
+
assert.equal(child.exitCode, null, `Portable runtime exited before listening: ${stderr}`);
|
|
98
|
+
assert.ok(
|
|
99
|
+
performance.now() - started < budgetMs,
|
|
100
|
+
`Portable startup exceeded ${budgetMs}ms: ${stderr}`,
|
|
101
|
+
);
|
|
102
|
+
await delay(20);
|
|
103
|
+
}
|
|
104
|
+
const readModel = events().find((event) => event.event === "dashboard_sqlite_read_model");
|
|
105
|
+
assert.equal(readModel.cache, expectedCache);
|
|
106
|
+
if (expectedCache === "stale_seed") {
|
|
107
|
+
assert.equal(readModel.fingerprint_ms, 0);
|
|
108
|
+
assert.ok(!events().some((event) => event.phase === "source_fingerprint_started"));
|
|
109
|
+
}
|
|
110
|
+
const listeningMs = Math.round(performance.now() - started);
|
|
111
|
+
const url = `http://127.0.0.1:${port}/api/v2/health`;
|
|
112
|
+
const headers = { "x-zaip-session": "portable-startup-fixture-token" };
|
|
113
|
+
const response = await fetch(url, { headers, signal: AbortSignal.timeout(5_000) });
|
|
114
|
+
assert.equal(response.status, 200);
|
|
115
|
+
const health = await response.json();
|
|
116
|
+
assert.equal(health.status, "ok");
|
|
117
|
+
assert.ok(["updating", "current"].includes(health.snapshot_state));
|
|
118
|
+
const refreshStarted = performance.now();
|
|
119
|
+
let current = health;
|
|
120
|
+
while (current.snapshot_state !== "current") {
|
|
121
|
+
assert.ok(performance.now() - refreshStarted < 30_000, `Refresh did not complete: ${stderr}`);
|
|
122
|
+
await delay(30);
|
|
123
|
+
current = await (await fetch(url, { headers, signal: AbortSignal.timeout(5_000) })).json();
|
|
124
|
+
}
|
|
125
|
+
assert.equal(current.projection_refresh_failure, null);
|
|
126
|
+
assert.ok(persistedTitle().includes(expectedTitle));
|
|
127
|
+
const ready = await fetch(`http://127.0.0.1:${port}/api/v2/ready`, { headers });
|
|
128
|
+
assert.equal(ready.status, 200);
|
|
129
|
+
assert.equal((await ready.json()).storage_mode, "sqlite_read_model");
|
|
130
|
+
await stop();
|
|
131
|
+
return {
|
|
132
|
+
cache: readModel.cache,
|
|
133
|
+
fingerprint_ms: readModel.fingerprint_ms,
|
|
134
|
+
listening_ms: listeningMs,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
await mkdir(dirname(task), { recursive: true });
|
|
140
|
+
await mkdir(resolve(workspace, ".agents/config"), { recursive: true });
|
|
141
|
+
for (const filename of ["project.yaml", "framework.lock.yaml"]) {
|
|
142
|
+
await writeFile(
|
|
143
|
+
resolve(workspace, ".agents/config", filename),
|
|
144
|
+
await readFile(resolve(checkout, ".agents/config", filename)),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
// Synthetic fixture manifests make lifecycle health meaningful for this
|
|
148
|
+
// isolated workspace; no real lifecycle records are modified by the harness.
|
|
149
|
+
const lifecycle = resolve(workspace, ".agents/records/lifecycle");
|
|
150
|
+
await mkdir(lifecycle, { recursive: true });
|
|
151
|
+
const updated_at = "2026-08-01T12:00:00.000Z";
|
|
152
|
+
await writeFile(
|
|
153
|
+
resolve(lifecycle, "manifest.json"),
|
|
154
|
+
JSON.stringify({
|
|
155
|
+
schema_version: 1,
|
|
156
|
+
updated_at,
|
|
157
|
+
offsets: {},
|
|
158
|
+
}),
|
|
159
|
+
);
|
|
160
|
+
await writeFile(
|
|
161
|
+
resolve(lifecycle, "summary.json"),
|
|
162
|
+
JSON.stringify({
|
|
163
|
+
schema_version: 1,
|
|
164
|
+
updated_at,
|
|
165
|
+
counts: { total: 1, by_kind: { task: 1 }, by_status: { ready: 1 } },
|
|
166
|
+
freshness: {},
|
|
167
|
+
usage: {},
|
|
168
|
+
periods: {},
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
171
|
+
await mkdir(resolve(workspace, "ui"));
|
|
172
|
+
await writeFile(
|
|
173
|
+
resolve(workspace, "ui/index.html"),
|
|
174
|
+
"<!doctype html><title>Startup fixture</title>",
|
|
175
|
+
);
|
|
176
|
+
await writeFile(task, taskContent("Original portable seed"));
|
|
177
|
+
const built = await runtime.rebuildSqliteReadModel(workspace);
|
|
178
|
+
assert.equal(built.path, expectedPath);
|
|
179
|
+
await writeFile(task, taskContent("Changed before portable launch"));
|
|
180
|
+
for (const implementation of [runtime, cjs]) {
|
|
181
|
+
const seed = await implementation.prepareDashboardSqliteStartup(workspace);
|
|
182
|
+
assert.equal(seed.cache, "stale_seed");
|
|
183
|
+
assert.equal(seed.fingerprint_ms, 0);
|
|
184
|
+
assert.equal(seed.path, expectedPath);
|
|
185
|
+
assert.ok(persistedTitle().includes("Original portable seed"));
|
|
186
|
+
}
|
|
187
|
+
// Deterministically hold the refresh to prove the served seed remains updating.
|
|
188
|
+
let release;
|
|
189
|
+
const pending = new Promise((done) => {
|
|
190
|
+
release = done;
|
|
191
|
+
});
|
|
192
|
+
const filesystem = new runtime.SqliteReadModelFileSystem(workspace);
|
|
193
|
+
try {
|
|
194
|
+
const app = runtime.createDashboardApp(workspace, "fixture", resolve(workspace, "ui"), {
|
|
195
|
+
filesystem,
|
|
196
|
+
snapshotState: "updating",
|
|
197
|
+
refreshReadModel: () => pending,
|
|
198
|
+
});
|
|
199
|
+
const health = await app.request("/api/v2/health", {
|
|
200
|
+
headers: { "x-zaip-session": "fixture" },
|
|
201
|
+
});
|
|
202
|
+
assert.equal((await health.json()).snapshot_state, "updating");
|
|
203
|
+
release();
|
|
204
|
+
await pending;
|
|
205
|
+
await delay(0);
|
|
206
|
+
} finally {
|
|
207
|
+
filesystem.close();
|
|
208
|
+
}
|
|
209
|
+
const launches = [await launch("stale_seed", "Changed before portable launch")];
|
|
210
|
+
await writeFile(task, taskContent("Changed again before repeated launch"));
|
|
211
|
+
launches.push(await launch("stale_seed", "Changed again before repeated launch"));
|
|
212
|
+
const database = new DatabaseSync(expectedPath);
|
|
213
|
+
try {
|
|
214
|
+
database.exec("DROP INDEX zaip_activities_work_item_identity");
|
|
215
|
+
} finally {
|
|
216
|
+
database.close();
|
|
217
|
+
}
|
|
218
|
+
await writeFile(task, taskContent("Recovered invalid portable seed"));
|
|
219
|
+
launches.push(await launch("rebuilt", "Recovered invalid portable seed"));
|
|
220
|
+
const files = await readdir(dirname(expectedPath));
|
|
221
|
+
assert.deepEqual(
|
|
222
|
+
files.filter((file) => file.endsWith(".sqlite")),
|
|
223
|
+
["zaip-aios-dashboard.sqlite"],
|
|
224
|
+
);
|
|
225
|
+
process.stdout.write(
|
|
226
|
+
`${JSON.stringify({ status: "passed", formats: ["portable-esm", "cjs"], launches })}\n`,
|
|
227
|
+
);
|
|
228
|
+
} finally {
|
|
229
|
+
await stop();
|
|
230
|
+
await rm(workspace, { recursive: true, force: true });
|
|
231
|
+
if (previousDbPath === undefined) delete process.env.ZAIP_SQLITE_DB_PATH;
|
|
232
|
+
else process.env.ZAIP_SQLITE_DB_PATH = previousDbPath;
|
|
233
|
+
}
|
package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-provider-nonzero.mjs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, realpathSync, symlinkSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
const root = resolve(import.meta.dirname, "../../../../..");
|
|
7
|
+
// Exact-candidate validation can borrow installed dependencies while loading
|
|
8
|
+
// the provider implementation and tests exclusively from this candidate.
|
|
9
|
+
const dependencyRoot =
|
|
10
|
+
process.argv[2] ??
|
|
11
|
+
dirname(
|
|
12
|
+
resolve(
|
|
13
|
+
root,
|
|
14
|
+
execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
15
|
+
cwd: root,
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
windowsHide: true,
|
|
18
|
+
}).trim(),
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
if (dependencyRoot) {
|
|
22
|
+
const installed = realpathSync(dependencyRoot);
|
|
23
|
+
for (const relative of [
|
|
24
|
+
"node_modules",
|
|
25
|
+
".agents/dashboard/packages/local-runtime/node_modules",
|
|
26
|
+
]) {
|
|
27
|
+
const destination = resolve(root, relative);
|
|
28
|
+
const source = resolve(installed, relative);
|
|
29
|
+
if (!existsSync(source)) throw new Error(`Missing installed dependencies: ${relative}`);
|
|
30
|
+
if (!existsSync(destination)) symlinkSync(source, destination, "junction");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const require = createRequire(resolve(root, "package.json"));
|
|
34
|
+
const result = spawnSync(
|
|
35
|
+
process.execPath,
|
|
36
|
+
[
|
|
37
|
+
resolve(dirname(require.resolve("vitest/package.json")), "vitest.mjs"),
|
|
38
|
+
"run",
|
|
39
|
+
".agents/dashboard/packages/local-runtime/src/sqlite-provider-nonzero.test.ts",
|
|
40
|
+
"--no-file-parallelism",
|
|
41
|
+
],
|
|
42
|
+
{ cwd: root, stdio: "inherit", shell: false, windowsHide: true },
|
|
43
|
+
);
|
|
44
|
+
if (result.error) throw result.error;
|
|
45
|
+
process.exitCode = result.status ?? 1;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
6
|
+
import { dashboardUrl, readDashboardSession } from "../start-dashboard.mjs";
|
|
7
|
+
import { launcherLogPath, workspaceRoot } from "../start-dashboard-paths.mjs";
|
|
8
|
+
|
|
9
|
+
assert.equal(
|
|
10
|
+
process.platform,
|
|
11
|
+
"win32",
|
|
12
|
+
"This verification exercises the actual Windows executable",
|
|
13
|
+
);
|
|
14
|
+
const executable = resolve(workspaceRoot, ".agents/dashboard/zaip-dashboard-windows.exe");
|
|
15
|
+
const results = [];
|
|
16
|
+
for (let click = 1; click <= 3; click += 1) {
|
|
17
|
+
const previous = await readDashboardSession();
|
|
18
|
+
const startedAt = Date.now();
|
|
19
|
+
const child = spawn(executable, [], { cwd: workspaceRoot, windowsHide: true, stdio: "ignore" });
|
|
20
|
+
let launchError = null;
|
|
21
|
+
child.on("error", (error) => {
|
|
22
|
+
launchError = error;
|
|
23
|
+
});
|
|
24
|
+
let session = null;
|
|
25
|
+
let health = null;
|
|
26
|
+
while (Date.now() - startedAt < 30_000) {
|
|
27
|
+
if (launchError) throw launchError;
|
|
28
|
+
const candidate = await readDashboardSession();
|
|
29
|
+
if (candidate && candidate.serverPid !== previous?.serverPid) {
|
|
30
|
+
const url = dashboardUrl(candidate.port, candidate.token);
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(new URL("/api/v2/health", url), {
|
|
33
|
+
signal: AbortSignal.timeout(1_000),
|
|
34
|
+
});
|
|
35
|
+
if (response.ok) {
|
|
36
|
+
health = await response.json();
|
|
37
|
+
const page = await fetch(url, { signal: AbortSignal.timeout(1_000) });
|
|
38
|
+
if (page.ok && (await page.text()).includes("<html")) {
|
|
39
|
+
session = candidate;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
/* Poll only this workspace's newly created runtime. */
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
await delay(100);
|
|
48
|
+
}
|
|
49
|
+
assert.ok(session, `Click ${click} did not serve a new healthy dashboard within 30s`);
|
|
50
|
+
const elapsedMs = Date.now() - startedAt;
|
|
51
|
+
const log = await readFile(launcherLogPath, "utf8");
|
|
52
|
+
const phases = log
|
|
53
|
+
.split(/\r?\n/)
|
|
54
|
+
.flatMap((line) => {
|
|
55
|
+
const offset = line.indexOf('{"event":"dashboard_sqlite_phase"');
|
|
56
|
+
if (offset < 0) return [];
|
|
57
|
+
try {
|
|
58
|
+
return [JSON.parse(line.slice(offset))];
|
|
59
|
+
} catch {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
.filter((phase) => phase.launcher_pid === session.rootPid);
|
|
64
|
+
assert.equal(health.workspace, workspaceRoot);
|
|
65
|
+
assert.equal(health.server_pid, session.serverPid);
|
|
66
|
+
assert.equal(health.storage.storage_mode, "sqlite_read_model");
|
|
67
|
+
assert.equal(health.projection_refresh_failure, null);
|
|
68
|
+
// Capture diagnostics are preserved in the evidence. Serving availability
|
|
69
|
+
// is independently verified against the exact new executable session.
|
|
70
|
+
const readModelEvent = log
|
|
71
|
+
.split(/\r?\n/)
|
|
72
|
+
.flatMap((line) => {
|
|
73
|
+
const offset = line.indexOf('{"event":"dashboard_sqlite_read_model"');
|
|
74
|
+
if (offset < 0) return [];
|
|
75
|
+
try {
|
|
76
|
+
return [JSON.parse(line.slice(offset))];
|
|
77
|
+
} catch {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
.at(-1);
|
|
82
|
+
assert.equal(readModelEvent?.cache, "stale_seed");
|
|
83
|
+
assert.equal(readModelEvent?.fingerprint_ms, 0);
|
|
84
|
+
assert.ok(phases.some((phase) => phase.phase === "handoff_complete"));
|
|
85
|
+
const result = {
|
|
86
|
+
click,
|
|
87
|
+
elapsed_ms: elapsedMs,
|
|
88
|
+
pid: session.serverPid,
|
|
89
|
+
port: session.port,
|
|
90
|
+
health,
|
|
91
|
+
read_model: readModelEvent,
|
|
92
|
+
phases,
|
|
93
|
+
};
|
|
94
|
+
results.push(result);
|
|
95
|
+
console.log(JSON.stringify(result));
|
|
96
|
+
assert.ok(elapsedMs < 10_000, `Click ${click}: ${elapsedMs}ms exceeds the 10s warm-start budget`);
|
|
97
|
+
await delay(500);
|
|
98
|
+
}
|
|
99
|
+
const evidenceDirectory = resolve(workspaceRoot, ".local/evidences/dashboard-startup");
|
|
100
|
+
await mkdir(evidenceDirectory, { recursive: true });
|
|
101
|
+
await writeFile(
|
|
102
|
+
resolve(evidenceDirectory, "executable-startup.json"),
|
|
103
|
+
JSON.stringify({ executable, observed_at: new Date().toISOString(), results }, null, 2) + "\n",
|
|
104
|
+
);
|
|
105
|
+
console.log(
|
|
106
|
+
"PASS: three actual executable launches served the validated SQLite model and HTML within 10s each.",
|
|
107
|
+
);
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { cp, mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
8
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
9
|
+
import { stopProcessTree } from "../start-dashboard-processes.mjs";
|
|
10
|
+
|
|
11
|
+
if (process.platform !== "win32") {
|
|
12
|
+
console.log("Windows executable proof unavailable on this host; browser regression is separate.");
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
const checkout = resolve(import.meta.dirname, "../../../../..");
|
|
16
|
+
const packageRoot = resolve(import.meta.dirname, "..");
|
|
17
|
+
const common = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
18
|
+
cwd: checkout,
|
|
19
|
+
encoding: "utf8",
|
|
20
|
+
windowsHide: true,
|
|
21
|
+
}).trim();
|
|
22
|
+
const canonicalRoot = dirname(resolve(checkout, common));
|
|
23
|
+
const evidence = resolve(
|
|
24
|
+
process.env.ZAIP_DASHBOARD_PROOF_DIR ??
|
|
25
|
+
join(checkout, ".local/evidences/dashboard-reconciliation"),
|
|
26
|
+
);
|
|
27
|
+
assert.ok(
|
|
28
|
+
[checkout, canonicalRoot].some((root) => {
|
|
29
|
+
const local = relative(join(root, ".local"), evidence);
|
|
30
|
+
return (
|
|
31
|
+
!isAbsolute(local) &&
|
|
32
|
+
local !== ".." &&
|
|
33
|
+
!local.startsWith(".." + (process.platform === "win32" ? "\\" : "/"))
|
|
34
|
+
);
|
|
35
|
+
}),
|
|
36
|
+
"Proof destination must stay inside this checkout or canonical repository .local",
|
|
37
|
+
);
|
|
38
|
+
const compile = spawnSync(
|
|
39
|
+
"powershell",
|
|
40
|
+
[
|
|
41
|
+
"-NoProfile",
|
|
42
|
+
"-ExecutionPolicy",
|
|
43
|
+
"Bypass",
|
|
44
|
+
"-File",
|
|
45
|
+
resolve(checkout, ".agents/dashboard/packages/windows-launcher/build-dashboard-launcher.ps1"),
|
|
46
|
+
"-SkipReleaseSync",
|
|
47
|
+
],
|
|
48
|
+
{ cwd: checkout, stdio: "inherit", windowsHide: true },
|
|
49
|
+
);
|
|
50
|
+
assert.equal(compile.status, 0);
|
|
51
|
+
const base = join(checkout, ".tmp", "reconcile-exe");
|
|
52
|
+
await mkdir(base, { recursive: true });
|
|
53
|
+
const fixture = await mkdtemp(join(base, "f-"));
|
|
54
|
+
const fixtureRuntime = join(fixture, ".agents/dashboard/packages/local-runtime");
|
|
55
|
+
const key = createHash("sha256")
|
|
56
|
+
.update(fixture + ":dashboard-sqlite")
|
|
57
|
+
.digest("hex")
|
|
58
|
+
.slice(0, 16);
|
|
59
|
+
const sessionPath = join(
|
|
60
|
+
tmpdir(),
|
|
61
|
+
"zaip-aios",
|
|
62
|
+
"dashboard-sqlite-" + key,
|
|
63
|
+
"dashboard-sqlite-session.json",
|
|
64
|
+
);
|
|
65
|
+
const require = createRequire(resolve(checkout, "package.json"));
|
|
66
|
+
const { chromium } = require("@playwright/test");
|
|
67
|
+
let session;
|
|
68
|
+
let native;
|
|
69
|
+
let browser;
|
|
70
|
+
let verifiedFixtureSession = false;
|
|
71
|
+
try {
|
|
72
|
+
await mkdir(fixtureRuntime, { recursive: true });
|
|
73
|
+
for (const file of (await readdir(packageRoot)).filter((name) => name.endsWith(".mjs")))
|
|
74
|
+
await cp(join(packageRoot, file), join(fixtureRuntime, file));
|
|
75
|
+
await mkdir(join(fixtureRuntime, "scripts"), { recursive: true });
|
|
76
|
+
await cp(
|
|
77
|
+
join(packageRoot, "scripts/rebuild-sqlite-read-model.mjs"),
|
|
78
|
+
join(fixtureRuntime, "scripts/rebuild-sqlite-read-model.mjs"),
|
|
79
|
+
);
|
|
80
|
+
await cp(
|
|
81
|
+
resolve(checkout, ".agents/dashboard/zaip-dashboard-windows.exe"),
|
|
82
|
+
join(fixture, ".agents/dashboard/zaip-dashboard-windows.exe"),
|
|
83
|
+
);
|
|
84
|
+
await cp(
|
|
85
|
+
resolve(checkout, ".agents/dashboard/apps/frontend/dist"),
|
|
86
|
+
join(fixture, ".agents/dashboard/apps/frontend/dist"),
|
|
87
|
+
{ recursive: true },
|
|
88
|
+
);
|
|
89
|
+
await mkdir(join(fixture, ".agents/config"), { recursive: true });
|
|
90
|
+
for (const file of ["project.yaml", "framework.lock.yaml"])
|
|
91
|
+
await cp(join(checkout, ".agents/config", file), join(fixture, ".agents/config", file));
|
|
92
|
+
const lifecycle = join(fixture, ".agents/records/lifecycle");
|
|
93
|
+
await mkdir(lifecycle, { recursive: true });
|
|
94
|
+
const updated_at = "2026-09-29T10:00:00Z";
|
|
95
|
+
await writeFile(
|
|
96
|
+
join(lifecycle, "manifest.json"),
|
|
97
|
+
JSON.stringify({ schema_version: 1, updated_at, offsets: {} }),
|
|
98
|
+
);
|
|
99
|
+
await writeFile(
|
|
100
|
+
join(lifecycle, "summary.json"),
|
|
101
|
+
JSON.stringify({
|
|
102
|
+
schema_version: 1,
|
|
103
|
+
updated_at,
|
|
104
|
+
counts: { total: 0, by_kind: {}, by_status: {} },
|
|
105
|
+
freshness: {},
|
|
106
|
+
usage: {},
|
|
107
|
+
periods: {},
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
await mkdir(join(fixture, ".agents/runtime/bin"), { recursive: true });
|
|
111
|
+
// Production subprocess boundary and RunV2Fold shape; only fixture records change.
|
|
112
|
+
await writeFile(
|
|
113
|
+
join(fixture, ".agents/runtime/bin/zaip.mjs"),
|
|
114
|
+
[
|
|
115
|
+
'import { mkdirSync, writeFileSync } from "node:fs";',
|
|
116
|
+
'mkdirSync(".agents/records/tasks", {recursive:true});',
|
|
117
|
+
'writeFileSync(".agents/records/tasks/TASK-FIXTURE.md", "---\\nschema_version: 2\\nid: TASK-FIXTURE\\ntitle: Fixture orphan\\nstatus: blocked\\ncreated_at: 2026-09-29T10:00:00Z\\n---\\n");',
|
|
118
|
+
'const reason = "Explicit orphan reconciliation found no active host invocation or terminal work outcome; repair is required before resuming (Run RUN-FIXTURE).";',
|
|
119
|
+
'process.stderr.write("fixture CLI diagnostic\\n");',
|
|
120
|
+
'process.stdout.write(JSON.stringify({ runs:[{run:{run_id:"RUN-FIXTURE"},status:"failed",finished:{status:"failed",summary:"Host stopped"}}],',
|
|
121
|
+
'reconciliations:[{status:"closed",tasks:[{id:"TASK-FIXTURE",status:"blocked",status_reason:reason}], changes:[{id:"CHG-FIXTURE",status:"blocked",status_reason:reason}], executions:[{id:"EXEC-FIXTURE",status:"blocked",status_reason:reason}]}] }));',
|
|
122
|
+
].join("\n"),
|
|
123
|
+
);
|
|
124
|
+
native = spawn(join(fixture, ".agents/dashboard/zaip-dashboard-windows.exe"), [], {
|
|
125
|
+
cwd: fixture,
|
|
126
|
+
windowsHide: true,
|
|
127
|
+
stdio: "ignore",
|
|
128
|
+
env: { ...process.env, ZAIP_UI_NO_BROWSER: "1", ZAIP_DASHBOARD_WATCH_SOURCES: "0" },
|
|
129
|
+
});
|
|
130
|
+
const started = Date.now();
|
|
131
|
+
while (Date.now() - started < 45_000) {
|
|
132
|
+
try {
|
|
133
|
+
session = JSON.parse(await readFile(sessionPath, "utf8"));
|
|
134
|
+
if (session?.serverPid) {
|
|
135
|
+
const response = await fetch("http://127.0.0.1:" + session.port + "/api/v2/health");
|
|
136
|
+
if (response.ok) {
|
|
137
|
+
const health = await response.json();
|
|
138
|
+
assert.equal(health.workspace, fixture);
|
|
139
|
+
assert.equal(health.workspaceKey, key);
|
|
140
|
+
assert.equal(health.server_pid, session.serverPid);
|
|
141
|
+
verifiedFixtureSession = true;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
/* Wait for this fixture's executable session only. */
|
|
147
|
+
}
|
|
148
|
+
await delay(100);
|
|
149
|
+
}
|
|
150
|
+
assert.ok(session?.serverPid, "Executable did not publish a healthy fixture session");
|
|
151
|
+
assert.ok(verifiedFixtureSession, "Fixture process ownership was not verified");
|
|
152
|
+
browser = await chromium.launch({ headless: true });
|
|
153
|
+
const page = await browser.newPage();
|
|
154
|
+
const errors = [];
|
|
155
|
+
page.on("pageerror", (error) => errors.push(error.message));
|
|
156
|
+
await page.goto(
|
|
157
|
+
"http://127.0.0.1:" + session.port + "/?token=" + encodeURIComponent(session.token),
|
|
158
|
+
);
|
|
159
|
+
await page
|
|
160
|
+
.locator(".dashboard-header-brand small")
|
|
161
|
+
.filter({ hasText: "Última Atualização" })
|
|
162
|
+
.waitFor();
|
|
163
|
+
assert.equal(await page.locator(".dashboard-header-brand small").count(), 1);
|
|
164
|
+
await page.getByRole("button", { name: "Atualizar", exact: true }).first().click();
|
|
165
|
+
const responsePromise = page.waitForResponse(
|
|
166
|
+
(response) =>
|
|
167
|
+
response.url().includes("/api/v2/reconcile-open-work") &&
|
|
168
|
+
response.request().method() === "POST",
|
|
169
|
+
);
|
|
170
|
+
await page.getByRole("button", { name: "Reconciliar itens abertos", exact: true }).click();
|
|
171
|
+
const response = await responsePromise;
|
|
172
|
+
assert.equal(response.status(), 200);
|
|
173
|
+
const receipt = await response.json();
|
|
174
|
+
assert.equal(receipt.reconciliation.reconciled_ids.length, 4);
|
|
175
|
+
assert.equal(receipt.reconciliation.blocked.length, 3);
|
|
176
|
+
await page.getByRole("button", { name: "Veja mais" }).click();
|
|
177
|
+
const dialog = page.getByRole("dialog", { name: "Resultado da reconciliação manual" });
|
|
178
|
+
await dialog.waitFor();
|
|
179
|
+
assert.ok((await dialog.textContent()).includes("Falha técnica"));
|
|
180
|
+
assert.ok((await dialog.textContent()).includes("Outros estados registrados (1)"));
|
|
181
|
+
assert.ok((await dialog.textContent()).includes("Desfechos bloqueados (3)"));
|
|
182
|
+
assert.equal(await dialog.locator("code").filter({ hasText: "TASK-FIXTURE" }).count(), 1);
|
|
183
|
+
assert.ok((await dialog.textContent()).includes("sem novos tokens não altera esse horário"));
|
|
184
|
+
assert.deepEqual(errors, []);
|
|
185
|
+
const directory = join(fixture, ".local/dashboard-logs");
|
|
186
|
+
let events = [];
|
|
187
|
+
for (let attempt = 0; attempt < 100; attempt++) {
|
|
188
|
+
events = (
|
|
189
|
+
await Promise.all(
|
|
190
|
+
(
|
|
191
|
+
await readdir(directory)
|
|
192
|
+
)
|
|
193
|
+
.filter((name) => name.endsWith(".jsonl"))
|
|
194
|
+
.map((name) => readFile(join(directory, name), "utf8")),
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
.join("")
|
|
198
|
+
.trim()
|
|
199
|
+
.split("\n")
|
|
200
|
+
.map((line) => JSON.parse(line));
|
|
201
|
+
if (
|
|
202
|
+
events.some(
|
|
203
|
+
(event) =>
|
|
204
|
+
event.event === "projection_refresh_completed" &&
|
|
205
|
+
event.job_id === receipt.reconciliation.diagnostics.job_id,
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
break;
|
|
209
|
+
await delay(50);
|
|
210
|
+
}
|
|
211
|
+
for (const event of [
|
|
212
|
+
"executable_clicked",
|
|
213
|
+
"node_started",
|
|
214
|
+
"launcher_entered",
|
|
215
|
+
"runtime_app_started",
|
|
216
|
+
"reconciliation_button_received",
|
|
217
|
+
"cli_output",
|
|
218
|
+
"reconciliation_completed",
|
|
219
|
+
"projection_refresh_completed",
|
|
220
|
+
])
|
|
221
|
+
assert.ok(
|
|
222
|
+
events.some((item) => item.event === event),
|
|
223
|
+
"Missing persisted event " + event,
|
|
224
|
+
);
|
|
225
|
+
const launchId = events.find((event) => event.event === "executable_clicked").correlation_id;
|
|
226
|
+
for (const component of ["native", "launcher", "runtime"])
|
|
227
|
+
assert.ok(
|
|
228
|
+
events.some((event) => event.component === component && event.correlation_id === launchId),
|
|
229
|
+
);
|
|
230
|
+
assert.ok(!JSON.stringify(events).includes(session.token));
|
|
231
|
+
await mkdir(evidence, { recursive: true });
|
|
232
|
+
await page.screenshot({ path: join(evidence, "reconciliation-executable.png"), fullPage: true });
|
|
233
|
+
await writeFile(
|
|
234
|
+
join(evidence, "executable-browser.json"),
|
|
235
|
+
JSON.stringify(
|
|
236
|
+
{
|
|
237
|
+
observed_at: new Date().toISOString(),
|
|
238
|
+
fixture_only: true,
|
|
239
|
+
native_exit_code: native.exitCode,
|
|
240
|
+
receipt,
|
|
241
|
+
events,
|
|
242
|
+
browser_errors: errors,
|
|
243
|
+
},
|
|
244
|
+
null,
|
|
245
|
+
2,
|
|
246
|
+
),
|
|
247
|
+
);
|
|
248
|
+
console.log(
|
|
249
|
+
JSON.stringify({
|
|
250
|
+
status: "passed",
|
|
251
|
+
executable: "actual Windows EXE",
|
|
252
|
+
fixture_only: true,
|
|
253
|
+
persisted_events: events.length,
|
|
254
|
+
job_id: receipt.reconciliation.diagnostics.job_id,
|
|
255
|
+
proof_directory: evidence,
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
258
|
+
} finally {
|
|
259
|
+
await browser?.close();
|
|
260
|
+
if (verifiedFixtureSession && session?.rootPid) await stopProcessTree(session.rootPid);
|
|
261
|
+
if (native && native.exitCode === null) await stopProcessTree(native.pid);
|
|
262
|
+
const fixtureRelative = relative(resolve(base), resolve(fixture));
|
|
263
|
+
assert.ok(
|
|
264
|
+
fixtureRelative &&
|
|
265
|
+
!isAbsolute(fixtureRelative) &&
|
|
266
|
+
fixtureRelative !== ".." &&
|
|
267
|
+
!fixtureRelative.startsWith(".." + (process.platform === "win32" ? "\\" : "/")),
|
|
268
|
+
"Recursive cleanup must stay below the resolved fixture base",
|
|
269
|
+
);
|
|
270
|
+
await rm(resolve(fixture), { recursive: true, force: true });
|
|
271
|
+
}
|