@zaip-ai/cli 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +11 -5
  2. package/dist/commands/init.d.ts.map +1 -1
  3. package/dist/commands/init.js +16 -6
  4. package/dist/commands/init.js.map +1 -1
  5. package/dist/services/adapters.d.ts +1 -1
  6. package/dist/services/adapters.js +1 -1
  7. package/dist/services/github-setup-status.d.ts +12 -0
  8. package/dist/services/github-setup-status.d.ts.map +1 -0
  9. package/dist/services/github-setup-status.js +84 -0
  10. package/dist/services/github-setup-status.js.map +1 -0
  11. package/dist/services/github-setup-status.test.d.ts +2 -0
  12. package/dist/services/github-setup-status.test.d.ts.map +1 -0
  13. package/dist/services/github-setup-status.test.js +57 -0
  14. package/dist/services/github-setup-status.test.js.map +1 -0
  15. package/dist/services/logger.d.ts +10 -1
  16. package/dist/services/logger.d.ts.map +1 -1
  17. package/dist/services/logger.js +23 -6
  18. package/dist/services/logger.js.map +1 -1
  19. package/dist/services/logger.test.js +33 -5
  20. package/dist/services/logger.test.js.map +1 -1
  21. package/dist/types.d.ts +3 -0
  22. package/dist/types.d.ts.map +1 -1
  23. package/package.json +1 -1
  24. package/templates/.agents/README-ENG.md +5 -1
  25. package/templates/.agents/README.md +5 -1
  26. package/templates/.agents/config/agents/discovery.json +1 -1
  27. package/templates/.agents/dashboard/.zaip-managed.json +65 -41
  28. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-TnrL1ZyZ.js → DashboardHelpDialog-BzaUTZDx.js} +27 -12
  29. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-DjQpBqGU.js → PricingSettingsDialog-21cCJik-.js} +1 -1
  30. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-BQZXUnQu.js → TraceabilityWorkspace-wqW52uCx.js} +1 -1
  31. package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-Basaw1NR.js → dashboard-localization-BkAm8-h4.js} +5 -1
  32. package/templates/.agents/dashboard/apps/frontend/dist/assets/{index-BQe3zTvL.js → index-DqZKJS8y.js} +2 -2
  33. package/templates/.agents/dashboard/apps/frontend/dist/index.html +1 -1
  34. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  35. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  36. package/templates/.agents/dashboard/modules/operations/package.json +1 -1
  37. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  38. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  39. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  40. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  41. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +182 -160
  42. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +174 -152
  43. package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
  44. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +182 -160
  45. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-calendar.ts +17 -0
  46. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cold-parse.test.ts +78 -0
  47. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.test.ts +94 -0
  48. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.ts +5 -5
  49. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-duration-lookup.test.ts +59 -0
  50. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-entity-label-views.test.ts +72 -0
  51. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-materialize-runs.ts +3 -4
  52. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-entity-group.test.ts +171 -0
  53. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-entity-group.ts +36 -16
  54. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical-parsing.ts +7 -0
  55. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +5 -15
  56. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-keeps-orphan-telemetry-out-kanban-without.test.ts +1 -1
  57. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-rebuild.ts +2 -0
  58. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-schema.ts +3 -2
  59. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +1 -1
  60. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-base-views.ts +22 -14
  61. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  62. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  63. package/templates/.agents/design/.zaip-managed.json +3 -3
  64. package/templates/.agents/design/apps/catalog/package.json +1 -1
  65. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  66. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  67. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
  68. package/templates/.agents/requirements/comparative-audit.md +1 -1
  69. package/templates/.agents/requirements/context-engine.md +1 -1
  70. package/templates/.agents/requirements/cost-observability.md +1 -1
  71. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  72. package/templates/.agents/requirements/data-export.md +1 -1
  73. package/templates/.agents/requirements/design-system.md +1 -1
  74. package/templates/.agents/requirements/execution-engine.md +1 -1
  75. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  76. package/templates/.agents/requirements/model-orchestration.md +1 -1
  77. package/templates/.agents/requirements/public-distribution.md +9 -2
  78. package/templates/.agents/requirements/software-architecture.md +1 -1
  79. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  80. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  81. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  82. package/templates/.agents/runtime/VERSION +1 -1
  83. package/templates/.agents/runtime/bin/zaip.mjs +5 -5
  84. package/templates/.agents/runtime/checksums.sha256 +5 -5
  85. package/templates/.agents/runtime/manifest.json +1 -1
  86. package/templates/.agents/runtime/provenance.json +4 -4
  87. package/templates/.agents/runtime/sbom.cdx.json +1 -1
  88. package/templates/.agents/skills/zaip-skill-config/SKILL.md +12 -0
  89. package/templates/.agents/skills/zaip-skill-github-deploy/references/release-identity.md +17 -12
  90. package/templates/.agents/skills/zaip-skill-github-setup/SKILL.md +7 -1
  91. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +12 -0
  92. package/templates/.agents/zaip-aios/README.md +4 -3
  93. package/templates/.agents/zaip-aios/VERSION +1 -1
  94. package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +22 -1
  95. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +20 -9
  96. package/templates/template-manifest.json +105 -81
@@ -0,0 +1,17 @@
1
+ // Intl's immutable formatter is reusable; timestamps and results are never cached.
2
+ const dateFormatter = new Intl.DateTimeFormat("en", {
3
+ timeZone: "America/Sao_Paulo",
4
+ year: "numeric",
5
+ month: "2-digit",
6
+ day: "2-digit",
7
+ });
8
+
9
+ export function saoPauloDay(timestamp: string): string {
10
+ const values = Object.fromEntries(
11
+ dateFormatter
12
+ .formatToParts(new Date(timestamp))
13
+ .filter((part) => part.type !== "literal")
14
+ .map((part) => [part.type, part.value]),
15
+ ) as { year: string; month: string; day: string };
16
+ return `${values.year}-${values.month}-${values.day}`;
17
+ }
@@ -0,0 +1,78 @@
1
+ import { expect, it, vi } from "vitest";
2
+ import { saoPauloDay } from "./sqlite-calendar.js";
3
+ import { readCanonicalLifecycleRecords } from "./sqlite-read-model-canonical.js";
4
+ import { isCanonicalLifecyclePath } from "./sqlite-read-model-canonical-parsing.js";
5
+
6
+ it("reuses only the immutable formatter and preserves calendar, DST and invalid input behavior", () => {
7
+ const cases = [
8
+ ["2024-03-01T02:59:59Z", "2024-02-29"],
9
+ ["2026-01-01T02:59:59Z", "2025-12-31"],
10
+ ["2026-01-01T03:00:00Z", "2026-01-01"],
11
+ ["2018-11-04T02:59:59Z", "2018-11-03"],
12
+ ["2018-11-04T03:00:00Z", "2018-11-04"],
13
+ ["2019-02-17T02:00:00Z", "2019-02-16"],
14
+ ] as const;
15
+ const formatterConstructor = vi.spyOn(Intl, "DateTimeFormat");
16
+ try {
17
+ for (let index = 0; index < 100; index++)
18
+ for (const [timestamp, expected] of cases) expect(saoPauloDay(timestamp)).toBe(expected);
19
+ expect(() => saoPauloDay("invalid")).toThrow(RangeError);
20
+ expect(formatterConstructor).not.toHaveBeenCalled();
21
+ } finally {
22
+ formatterConstructor.mockRestore();
23
+ }
24
+ });
25
+
26
+ it("ignores irrelevant source content before YAML parsing and keeps both lifecycle layouts", () => {
27
+ const ignored = [
28
+ ".agents/records/tasks/2026/09/task.md",
29
+ ".agents/records/changes/2026/09/C/artifacts/tests/test.md",
30
+ ".agents/records/runs/run.jsonl",
31
+ ".agents/records/executions/E/other.md",
32
+ ".agents/records/changes/C/decisions/other.txt",
33
+ "elsewhere/executions/E/execution.md",
34
+ ].map((path) => ({
35
+ path,
36
+ get content(): string {
37
+ throw new Error("Irrelevant content must never be inspected");
38
+ },
39
+ }));
40
+ expect(readCanonicalLifecycleRecords(ignored)).toEqual({
41
+ executions: [],
42
+ changes: [],
43
+ decisions: [],
44
+ });
45
+ for (const layout of ["data", "records"]) {
46
+ const rows = [
47
+ {
48
+ path: `.agents/${layout}/executions/2026/09/E/execution.md`,
49
+ content:
50
+ "---\nid: E\ncreated_at: 2026-09-15T00:00:00Z\nstatus: blocked\norigin:\n type: interaction\n conversation_id: C\n---\n",
51
+ },
52
+ {
53
+ path: `.agents/${layout}/changes/2026/09/H/change.md`,
54
+ content:
55
+ "---\nid: H\ncreated_at: 2026-09-15T00:00:00Z\nsource:\n type: task\n task_id: T\n---\n",
56
+ },
57
+ ...["changes", "executions"].map((kind) => ({
58
+ path: `.agents/${layout}/${kind}/E/artifacts/decisions/D.md`,
59
+ content: `---\nid: D-${kind}\ncreated_at: 2026-09-15T00:00:00Z\norigin: human\nscope_type: execution\nscope_id: E\n---\n`,
60
+ })),
61
+ ];
62
+ expect(rows.every((row) => isCanonicalLifecyclePath(row.path))).toBe(true);
63
+ const result = readCanonicalLifecycleRecords([...ignored, ...rows]);
64
+ expect(result.executions).toHaveLength(1);
65
+ expect(result.executions[0]).toMatchObject({ id: "E", originId: "C", status: "blocked" });
66
+ expect(result.changes).toHaveLength(1);
67
+ expect(result.changes[0]).toMatchObject({ id: "H", sourceId: "T" });
68
+ expect(result.decisions).toHaveLength(2);
69
+ expect(
70
+ readCanonicalLifecycleRecords([
71
+ {
72
+ path: `.agents/${layout}/executions/E/execution.md`,
73
+ content: "---\nid: [invalid\n---\n",
74
+ },
75
+ ]).executions,
76
+ ).toEqual([]);
77
+ }
78
+ });
@@ -0,0 +1,94 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { expect, it } from "vitest";
3
+ import { materializeCostGroupDailyProjection } from "./sqlite-cost-group-projection.js";
4
+ import { baseViewSql } from "./sqlite-schema-base-views.js";
5
+
6
+ it("evaluates the invocation population once across all eight daily groups with legacy parity", () => {
7
+ const database = new DatabaseSync(":memory:");
8
+ let costReads = 0;
9
+ database.function("observed_cost", (id) => {
10
+ costReads++;
11
+ return id === 1 ? 0 : null;
12
+ });
13
+ try {
14
+ database.exec(`CREATE TABLE inputs(id INTEGER); INSERT INTO inputs VALUES (1),(2);
15
+ CREATE VIEW invocations AS SELECT '2026-09-15' AS occurred_day_sp,
16
+ 'e' AS execution_id, 'a' AS zaip_agent, 'p' AS provider, 'm' AS model,
17
+ 't' AS task_id, 's' AS spec_id, 'r' AS prd_id, 'c' AS conversation_id,
18
+ 1 AS llm_call_count, NULL AS duration_ms, 2 AS input_tokens,
19
+ NULL AS cached_input_tokens, 3 AS output_tokens, NULL AS reasoning_tokens,
20
+ 5 AS total_tokens, observed_cost(id) AS cost_usd, NULL AS cost_brl FROM inputs;`);
21
+ const definition = /CREATE VIEW cost_monitor_group_daily_cache AS ([^;]+);/.exec(
22
+ baseViewSql,
23
+ )?.[1];
24
+ if (!definition) throw new Error("Canonical daily group SQL absent");
25
+ database.exec(`CREATE VIEW measured AS ${definition};`);
26
+ const actual = database.prepare("SELECT * FROM measured ORDER BY group_kind").all();
27
+ expect(actual).toHaveLength(8);
28
+ expect(costReads).toBe(2);
29
+ const reference = definition
30
+ .replace(/WITH group_invocations AS MATERIALIZED \([^)]*\) /, "")
31
+ .replaceAll("FROM group_invocations", "FROM invocations");
32
+ costReads = 0;
33
+ const expected = database.prepare(`SELECT * FROM (${reference}) ORDER BY group_kind`).all();
34
+ expect(costReads).toBe(16);
35
+ expect(actual).toEqual(expected);
36
+ for (const row of actual) {
37
+ expect(row.invocation_count).toBe(2);
38
+ expect(row.total_cost_usd).toBe(0);
39
+ expect(row.total_cost_brl).toBeNull();
40
+ expect(row.total_duration).toBeNull();
41
+ }
42
+ } finally {
43
+ database.close();
44
+ }
45
+ });
46
+
47
+ it("materializes exact labels without evaluating unrelated entity metrics", () => {
48
+ const database = new DatabaseSync(":memory:");
49
+ let irrelevantReads = 0;
50
+ database.function("unrelated_metric", () => {
51
+ irrelevantReads++;
52
+ throw new Error("Label projection must not evaluate unrelated metrics");
53
+ });
54
+ try {
55
+ database.exec(
56
+ `CREATE TABLE zaip_vw_cost_monitor_group_daily_cache(group_kind TEXT, day TEXT, group_value TEXT, cost REAL);`,
57
+ );
58
+ const insert = database.prepare(
59
+ "INSERT INTO zaip_vw_cost_monitor_group_daily_cache VALUES (?, '2026-09-15', ?, 2)",
60
+ );
61
+ for (const kind of ["task", "spec", "prd", "execution", "conversation"]) {
62
+ insert.run(kind, kind);
63
+ insert.run(kind, "missing");
64
+ insert.run(kind, null);
65
+ database.exec(
66
+ `CREATE VIEW zaip_vw_${kind}s AS SELECT '${kind}' AS id, 'Title' AS title, 'blocked' AS status, 'standard' AS risk, unrelated_metric() AS expensive_unused;`,
67
+ );
68
+ }
69
+ materializeCostGroupDailyProjection(database);
70
+ const rows = database
71
+ .prepare("SELECT * FROM cost_group_entity_projection ORDER BY group_kind, group_value")
72
+ .all();
73
+ expect(rows).toHaveLength(10);
74
+ for (const row of rows) {
75
+ expect(row.status).toBe(row.group_value === "missing" ? null : "blocked");
76
+ const titled =
77
+ row.group_value !== "missing" && ["task", "spec", "prd"].includes(String(row.group_kind));
78
+ expect(row.title).toBe(titled ? "Title" : null);
79
+ expect(row.risk).toBe(titled ? "standard" : null);
80
+ }
81
+ expect(irrelevantReads).toBe(0);
82
+ materializeCostGroupDailyProjection(database);
83
+ expect(
84
+ database
85
+ .prepare("SELECT * FROM cost_group_entity_projection ORDER BY group_kind, group_value")
86
+ .all(),
87
+ ).toEqual(rows);
88
+ expect(database.prepare("SELECT COUNT(*) AS n FROM cost_group_daily_projection").get()?.n).toBe(
89
+ 15,
90
+ );
91
+ } finally {
92
+ database.close();
93
+ }
94
+ });
@@ -15,11 +15,11 @@ export function materializeCostGroupDailyProjection(database: DatabaseSync): voi
15
15
  CREATE TABLE IF NOT EXISTS cost_group_entity_projection(group_kind TEXT, group_value TEXT, title TEXT, status TEXT, risk TEXT);
16
16
  DELETE FROM cost_group_entity_projection;
17
17
  INSERT INTO cost_group_entity_projection
18
- WITH tasks AS MATERIALIZED (SELECT * FROM zaip_vw_tasks),
19
- specs AS MATERIALIZED (SELECT * FROM zaip_vw_specs),
20
- prds AS MATERIALIZED (SELECT * FROM zaip_vw_prds),
21
- executions AS MATERIALIZED (SELECT * FROM zaip_vw_executions),
22
- conversations AS MATERIALIZED (SELECT * FROM zaip_vw_conversations)
18
+ WITH tasks AS MATERIALIZED (SELECT id, title, status, risk FROM zaip_vw_tasks),
19
+ specs AS MATERIALIZED (SELECT id, title, status, risk FROM zaip_vw_specs),
20
+ prds AS MATERIALIZED (SELECT id, title, status, risk FROM zaip_vw_prds),
21
+ executions AS MATERIALIZED (SELECT id, status FROM zaip_vw_executions),
22
+ conversations AS MATERIALIZED (SELECT id, status FROM zaip_vw_conversations)
23
23
  SELECT 'task' AS group_kind, groups.group_value, entity.title, entity.status, entity.risk
24
24
  FROM (SELECT DISTINCT group_value FROM cost_group_daily_projection
25
25
  WHERE group_kind = 'task' AND group_value IS NOT NULL) groups
@@ -0,0 +1,59 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { expect, it } from "vitest";
3
+ import { baseViewSql } from "./sqlite-schema-base-views.js";
4
+
5
+ it("uses indexed duration lookup with exact legacy sums and nulls and no duplicate Run rows", () => {
6
+ const db = new DatabaseSync(":memory:");
7
+ try {
8
+ db.exec(
9
+ "CREATE TABLE zaip_aios_activities(activity_row_id TEXT, payload_type TEXT, payload_json TEXT);",
10
+ );
11
+ const insert = db.prepare("INSERT INTO zaip_aios_activities VALUES(?,?,?)");
12
+ for (const id of ["known", "zero", "unknown", "missing"]) insert.run(id, null, null);
13
+ for (const [id, values] of [
14
+ ["known", [3, 5, null]],
15
+ ["zero", [0]],
16
+ ["unknown", [null]],
17
+ ["unrelated", [900]],
18
+ ] as const)
19
+ for (const duration of values)
20
+ insert.run(
21
+ `fact-${id}`,
22
+ "duration_allocation",
23
+ JSON.stringify({ activity_row_id: id, attributed_token_active_ms: duration }),
24
+ );
25
+ insert.run(
26
+ "technical",
27
+ "other",
28
+ JSON.stringify({ activity_row_id: "known", attributed_token_active_ms: 999 }),
29
+ );
30
+ const index = /CREATE INDEX zaip_activities_duration_activity[^;]+;/.exec(baseViewSql)?.[0];
31
+ const view = /CREATE VIEW activities AS\s+([^;]+);/.exec(baseViewSql)?.[1];
32
+ if (!index || !view) throw new Error("Canonical indexed duration SQL missing");
33
+ db.exec(`${index} CREATE VIEW activities AS ${view};`);
34
+ const query =
35
+ "SELECT activity_row_id,attributed_token_active_ms FROM activities ORDER BY activity_row_id";
36
+ const actual = db.prepare(query).all();
37
+ const expected = db
38
+ .prepare(`SELECT a.activity_row_id,f.duration AS attributed_token_active_ms
39
+ FROM zaip_aios_activities a LEFT JOIN (
40
+ SELECT json_extract(payload_json,'$.activity_row_id') AS activity_row_id,
41
+ SUM(json_extract(payload_json,'$.attributed_token_active_ms')) AS duration
42
+ FROM zaip_aios_activities WHERE payload_type='duration_allocation' GROUP BY activity_row_id
43
+ ) f USING(activity_row_id) WHERE a.payload_type IS NULL ORDER BY a.activity_row_id`)
44
+ .all();
45
+ expect(actual).toEqual(expected);
46
+ expect(actual).toHaveLength(4);
47
+ expect(actual.map((row) => row.attributed_token_active_ms)).toEqual([8, null, null, 0]);
48
+ const plan = db.prepare(`EXPLAIN QUERY PLAN ${query}`).all();
49
+ expect(
50
+ plan.some((row) =>
51
+ String(row.detail).includes("zaip_activities_duration_activity (<expr>=?)"),
52
+ ),
53
+ ).toBe(true);
54
+ const labelPlan = db.prepare("EXPLAIN QUERY PLAN SELECT activity_row_id FROM activities").all();
55
+ expect(labelPlan.some((row) => String(row.detail).includes("CORRELATED"))).toBe(false);
56
+ } finally {
57
+ db.close();
58
+ }
59
+ });
@@ -0,0 +1,72 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { expect, it } from "vitest";
3
+ import { baseViewSql } from "./sqlite-schema-base-views.js";
4
+
5
+ it("preserves canonical precedence and legacy labels without repeated payload or metric expansion", () => {
6
+ const db = new DatabaseSync(":memory:");
7
+ let payloadReads = 0;
8
+ let metricReads = 0;
9
+ db.function("payload_origin", (value) => {
10
+ payloadReads++;
11
+ return value;
12
+ });
13
+ db.function("unused_metric", () => {
14
+ metricReads++;
15
+ return 42;
16
+ });
17
+ try {
18
+ db.exec(`
19
+ CREATE TABLE inputs(id TEXT, status TEXT, created_at TEXT, occurred_day_sp TEXT, origin_type TEXT, origin_id TEXT);
20
+ INSERT INTO inputs VALUES
21
+ ('e1','blocked','2026-09-01','2026-09-01','interaction','c1'),
22
+ ('e2','completed','2026-09-02','2026-09-02','interaction','c1'),
23
+ ('e3',NULL,'2026-09-03','2026-09-03','task','not-conversation'),
24
+ ('e4','failed','2026-09-04','2026-09-04','interaction',NULL);
25
+ CREATE VIEW canonical_executions AS SELECT id,status,created_at,occurred_day_sp,origin_type,
26
+ payload_origin(origin_id) AS origin_id FROM inputs;
27
+ CREATE TABLE zaip_aios_activities(payload_type TEXT, execution_id TEXT, conversation_id TEXT,
28
+ status TEXT, occurred_at TEXT, occurred_day_sp TEXT);
29
+ INSERT INTO zaip_aios_activities VALUES
30
+ (NULL,'e1','c1','failed','2026-08-01','2026-08-01'),
31
+ (NULL,'legacy','legacy-c','blocked','2026-09-05','2026-09-05'),
32
+ (NULL,'legacy','legacy-c','completed','2026-09-06','2026-09-06'),
33
+ (NULL,'unknown','unknown-c',NULL,NULL,NULL),
34
+ ('technical','fake','fake-c','completed','2026-01-01','2026-01-01'),
35
+ (NULL,NULL,NULL,'completed','2026-01-01','2026-01-01');
36
+ CREATE VIEW activities AS SELECT a.*, f.metric FROM zaip_aios_activities a
37
+ LEFT JOIN (SELECT execution_id, SUM(unused_metric()) AS metric FROM zaip_aios_activities GROUP BY execution_id) f
38
+ USING(execution_id) WHERE a.payload_type IS NULL;
39
+ `);
40
+ const reference = {
41
+ executions: `SELECT id,status,created_at AS occurred_at,occurred_day_sp FROM canonical_executions
42
+ UNION ALL SELECT execution_id,MAX(status),MIN(occurred_at),MIN(occurred_day_sp)
43
+ FROM activities WHERE execution_id IS NOT NULL AND NOT EXISTS
44
+ (SELECT 1 FROM canonical_executions c WHERE c.id=activities.execution_id) GROUP BY execution_id`,
45
+ conversations: `SELECT origin_id AS id,MAX(status) AS status,MIN(created_at) AS occurred_at,MIN(occurred_day_sp) AS occurred_day_sp
46
+ FROM canonical_executions WHERE origin_type='interaction' AND origin_id IS NOT NULL GROUP BY origin_id
47
+ UNION ALL SELECT conversation_id,MAX(status),MIN(occurred_at),MIN(occurred_day_sp)
48
+ FROM activities WHERE conversation_id IS NOT NULL AND NOT EXISTS
49
+ (SELECT 1 FROM canonical_executions c WHERE c.origin_type='interaction' AND c.origin_id=activities.conversation_id) GROUP BY conversation_id`,
50
+ };
51
+ for (const name of ["executions", "conversations"] as const) {
52
+ const expected = db.prepare(`SELECT * FROM (${reference[name]}) ORDER BY id`).all();
53
+ const repeatedReads = payloadReads;
54
+ payloadReads = 0;
55
+ metricReads = 0;
56
+ const definition = new RegExp(`CREATE VIEW ${name} AS\\s+([^;]+);`).exec(baseViewSql)?.[1];
57
+ if (!definition) throw new Error(`Missing canonical view ${name}`);
58
+ const actual = db.prepare(`SELECT * FROM (${definition}) ORDER BY id`).all();
59
+ expect(actual).toEqual(expected);
60
+ expect(actual.some((row) => String(row.id).startsWith("fake"))).toBe(false);
61
+ expect(metricReads).toBe(0);
62
+ if (name === "conversations") {
63
+ expect(payloadReads).toBe(4);
64
+ expect(repeatedReads).toBeGreaterThan(payloadReads);
65
+ expect(actual.find((row) => row.id === "c1")?.status).toBe("completed");
66
+ expect(actual.find((row) => row.id === "unknown-c")?.status).toBeNull();
67
+ }
68
+ }
69
+ } finally {
70
+ db.close();
71
+ }
72
+ });
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import type { DatabaseSync } from "node:sqlite";
3
- import { pricingCatalogSchema, resolveHistoricalDeveloperAttribution } from "@zaip-ai/costs";
3
+ import { prepareHistoricalDeveloperAttribution, pricingCatalogSchema } from "@zaip-ai/costs";
4
4
  import {
5
5
  foldRunV2Records,
6
6
  parseRunV2JsonLines,
@@ -97,7 +97,7 @@ export function materializeCanonicalRuns(input: RunMaterializationInput) {
97
97
  .all() as Array<{ content: string }>
98
98
  ).flatMap((row) => {
99
99
  try {
100
- return [JSON.parse(row.content)];
100
+ return [prepareHistoricalDeveloperAttribution(JSON.parse(row.content))];
101
101
  } catch {
102
102
  return [];
103
103
  }
@@ -117,8 +117,7 @@ export function materializeCanonicalRuns(input: RunMaterializationInput) {
117
117
  };
118
118
  for (const candidate of historicalDeveloperManifests) {
119
119
  try {
120
- const resolved = resolveHistoricalDeveloperAttribution({
121
- manifest: candidate,
120
+ const resolved = candidate({
122
121
  run_id: String(JSON.parse(row.content.split(/\r?\n/u)[0] ?? "{}").run_id ?? ""),
123
122
  path: row.path,
124
123
  content: row.content,
@@ -0,0 +1,171 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import type { SqliteViewInput } from "./sqlite-query-context.js";
4
+ import { readEntityGroup } from "./sqlite-query-entity-group.js";
5
+
6
+ const databases: DatabaseSync[] = [];
7
+ afterEach(() => {
8
+ vi.restoreAllMocks();
9
+ for (const database of databases.splice(0)) database.close();
10
+ });
11
+
12
+ function fixture(kind: "spec" | "prd") {
13
+ const database = new DatabaseSync(":memory:");
14
+ databases.push(database);
15
+ database.exec(`
16
+ CREATE TABLE ${kind}s (id TEXT PRIMARY KEY, title TEXT, status TEXT, risk TEXT,
17
+ occurred_at TEXT, occurred_day_sp TEXT);
18
+ CREATE TABLE invocations (agent_invocation_id TEXT PRIMARY KEY, ${kind}_id TEXT,
19
+ occurred_at TEXT, occurred_day_sp TEXT, llm_call_count INTEGER, duration_ms REAL,
20
+ input_tokens REAL, cached_input_tokens REAL, output_tokens REAL, reasoning_tokens REAL,
21
+ total_tokens REAL, cost_usd REAL, cost_brl REAL);
22
+ `);
23
+ const entity = (id: string, day = "2026-09-10", status = "completed", risk = "standard") =>
24
+ database
25
+ .prepare(`INSERT INTO ${kind}s VALUES (?, ?, ?, ?, ?, ?)`)
26
+ .run(id, `Title ${id}`, status, risk, `${day}T12:00:00Z`, day);
27
+ const invocation = (id: string, parent: string, day: string, cost: number | null) =>
28
+ database
29
+ .prepare("INSERT INTO invocations VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
30
+ .run(
31
+ id,
32
+ parent,
33
+ `${day}T13:00:00Z`,
34
+ day,
35
+ cost === null ? null : 1,
36
+ cost,
37
+ cost,
38
+ cost,
39
+ cost,
40
+ cost,
41
+ cost,
42
+ cost,
43
+ cost,
44
+ );
45
+ const read = (input: SqliteViewInput = {}) =>
46
+ readEntityGroup(
47
+ { database },
48
+ kind === "spec" ? "cost_monitor_specs" : "cost_monitor_prds",
49
+ input,
50
+ kind,
51
+ );
52
+ return { database, entity, invocation, read };
53
+ }
54
+
55
+ describe.each(["spec", "prd"] as const)("canonical %s single grouped query", (kind) => {
56
+ it("computes full totals and deterministic pages with one SQL statement", () => {
57
+ const { database, entity, invocation, read } = fixture(kind);
58
+ for (let i = 0; i < 12; i++) {
59
+ const id = `E${String(i).padStart(2, "0")}`;
60
+ entity(id);
61
+ invocation(`I${i}`, id, "2026-09-10", 0.12345678901234566);
62
+ }
63
+ const prepare = vi.spyOn(database, "prepare");
64
+ const first = read({ limit: 10 });
65
+ expect(prepare).toHaveBeenCalledTimes(1);
66
+ prepare.mockClear();
67
+ const second = read({ limit: 10, offset: 10 });
68
+ expect(prepare).toHaveBeenCalledTimes(1);
69
+ expect(first.total_count).toBe(12);
70
+ expect(first.has_more).toBe(true);
71
+ expect(second.has_more).toBe(false);
72
+ expect(second.rows.map((row) => row.id)).toEqual(["E10", "E11"]);
73
+ expect(first.totals).toEqual(second.totals);
74
+ expect(first.totals).toMatchObject({
75
+ entity_count: 12,
76
+ invocation_count: 12,
77
+ total_duration: 1,
78
+ });
79
+ expect(first.rows[0]).toMatchObject({
80
+ id: "E00",
81
+ key: "E00",
82
+ total_cost_brl: 0.12345678901234566,
83
+ });
84
+ expect(Object.keys(first.rows[0] ?? {}).some((key) => key.startsWith("__"))).toBe(false);
85
+ const beyond = read({ limit: 10, offset: 99 });
86
+ expect(beyond).toMatchObject({
87
+ total_count: 12,
88
+ rows: [],
89
+ totals: first.totals,
90
+ has_more: false,
91
+ });
92
+ });
93
+
94
+ it("retains canonical entity dates, no-run unknowns and observed zero with filters", () => {
95
+ const { entity, invocation, read } = fixture(kind);
96
+ entity("CURRENT");
97
+ invocation("old-run", "CURRENT", "2026-08-01", 100);
98
+ entity("ZERO");
99
+ invocation("zero", "ZERO", "2026-09-10", 0);
100
+ entity("UNKNOWN");
101
+ invocation("unknown", "UNKNOWN", "2026-09-10", null);
102
+ entity("NO-RUN");
103
+ entity("OLD", "2026-08-01");
104
+ invocation("new-run", "OLD", "2026-09-10", 999);
105
+ entity("BLOCKED", "2026-09-10", "blocked", "critical");
106
+ const page = read({
107
+ from: "2026-09-01",
108
+ to: "2026-09-30",
109
+ status: "completed",
110
+ risk: "standard",
111
+ });
112
+ expect(page.total_count).toBe(4);
113
+ expect(page.totals).toEqual({
114
+ entity_count: 4,
115
+ invocation_count: 2,
116
+ total_duration: 0,
117
+ total_tokens: 0,
118
+ total_cost_usd: 0,
119
+ total_cost_brl: 0,
120
+ });
121
+ expect(page.rows.find((row) => row.id === "CURRENT")).toMatchObject({
122
+ invocation_count: 0,
123
+ total_cost_brl: null,
124
+ first_day: "2026-09-10",
125
+ occurred_at: "2026-09-10T12:00:00Z",
126
+ });
127
+ expect(page.rows.find((row) => row.id === "ZERO")).toMatchObject({
128
+ total_cost_brl: 0,
129
+ invocation_count: 1,
130
+ });
131
+ expect(page.rows.find((row) => row.id === "UNKNOWN")).toMatchObject({
132
+ total_cost_brl: null,
133
+ invocation_count: 1,
134
+ });
135
+ expect(page.rows.find((row) => row.id === "NO-RUN")).toMatchObject({
136
+ total_cost_brl: null,
137
+ invocation_count: 0,
138
+ });
139
+ expect(read({ query: "Title BLOCKED", risk: "critical" }).rows.map((row) => row.id)).toEqual([
140
+ "BLOCKED",
141
+ ]);
142
+ expect(read({ query: "CURRENT" }).totals.total_cost_brl).toBe(100);
143
+ });
144
+
145
+ it("returns nullable empty totals and clamps page boundaries without losing counts", () => {
146
+ const { entity, read } = fixture(kind);
147
+ expect(read({ limit: 0, offset: -2 })).toMatchObject({
148
+ rows: [],
149
+ total_count: 0,
150
+ limit: 1,
151
+ offset: 0,
152
+ has_more: false,
153
+ totals: {
154
+ entity_count: 0,
155
+ invocation_count: 0,
156
+ total_duration: null,
157
+ total_tokens: null,
158
+ total_cost_usd: null,
159
+ total_cost_brl: null,
160
+ },
161
+ });
162
+ entity("ONLY");
163
+ expect(read({ limit: 1000, offset: 1 })).toMatchObject({
164
+ rows: [],
165
+ total_count: 1,
166
+ limit: 200,
167
+ has_more: false,
168
+ totals: { entity_count: 1, invocation_count: 0, total_cost_brl: null },
169
+ });
170
+ });
171
+ });
@@ -2,7 +2,7 @@ import { type CostGroupKind, readCostGroupPage } from "./sqlite-cost-group-page.
2
2
  import type { SqliteQueryContext, SqliteViewInput } from "./sqlite-query-context.js";
3
3
  import type { SqliteViewName } from "./sqlite-read-model-types.js";
4
4
  export function readEntityGroup(
5
- context: SqliteQueryContext,
5
+ context: Pick<SqliteQueryContext, "database">,
6
6
  view: SqliteViewName,
7
7
  input: SqliteViewInput,
8
8
  groupKind: string,
@@ -55,7 +55,7 @@ export function readEntityGroup(
55
55
  const entityWhere = entityClauses.length === 0 ? "1" : entityClauses.join(" AND ");
56
56
  const invocationJoin =
57
57
  invocationClauses.length === 0 ? "" : ` AND ${invocationClauses.join(" AND ")}`;
58
- const ctes = `WITH grouped AS (
58
+ const ctes = `WITH grouped AS MATERIALIZED (
59
59
  SELECT entity.id AS group_value, entity.title, entity.status, entity.risk,
60
60
  COALESCE(MIN(i.occurred_day_sp), entity.occurred_day_sp) AS first_day,
61
61
  COALESCE(MAX(i.occurred_day_sp), entity.occurred_day_sp) AS last_day,
@@ -76,23 +76,43 @@ export function readEntityGroup(
76
76
  const limit = Math.min(200, Math.max(1, input.limit ?? 50));
77
77
  const offset = Math.max(0, input.offset ?? 0);
78
78
  const values = [...invocationValues, ...entityValues];
79
- const total_count = Number(
80
- (
81
- context.database
82
- .prepare(`${ctes} SELECT COUNT(*) AS count FROM grouped`)
83
- .get(...values) as { count: number }
84
- ).count,
85
- );
86
- const totals = context.database
79
+ // Materialize this population once for count, totals and the requested page.
80
+ // A totals-first LEFT JOIN retains totals even for an empty/out-of-range page.
81
+ const result = context.database
87
82
  .prepare(
88
- `${ctes} SELECT COUNT(*) AS entity_count, COALESCE(SUM(invocation_count), 0) AS invocation_count, ROUND(SUM(total_duration)) AS total_duration, SUM(total_tokens) AS total_tokens, SUM(total_cost_usd) AS total_cost_usd, SUM(total_cost_brl) AS total_cost_brl FROM grouped`,
89
- )
90
- .get(...values) as Record<string, unknown>;
91
- const rows = context.database
92
- .prepare(
93
- `${ctes} SELECT group_value AS id, group_value AS key, title, status, risk, first_day, last_day, occurred_at, invocation_count, llm_call_count, total_duration, total_input_tokens, total_cached_input_tokens, total_output_tokens, total_reasoning_tokens, total_tokens, total_cost_usd, total_cost_brl FROM grouped ORDER BY total_cost_brl DESC, group_value LIMIT ? OFFSET ?`,
83
+ `${ctes}, totals AS (
84
+ SELECT COUNT(*) AS __entity_count,
85
+ COALESCE(SUM(invocation_count), 0) AS __invocation_count,
86
+ ROUND(SUM(total_duration)) AS __total_duration,
87
+ SUM(total_tokens) AS __total_tokens,
88
+ SUM(total_cost_usd) AS __total_cost_usd,
89
+ SUM(total_cost_brl) AS __total_cost_brl FROM grouped
90
+ ), paged AS (
91
+ SELECT 1 AS __row_present, group_value AS id, group_value AS key,
92
+ title, status, risk, first_day, last_day, occurred_at, invocation_count,
93
+ llm_call_count, total_duration, total_input_tokens, total_cached_input_tokens,
94
+ total_output_tokens, total_reasoning_tokens, total_tokens, total_cost_usd,
95
+ total_cost_brl FROM grouped ORDER BY total_cost_brl DESC, group_value LIMIT ? OFFSET ?
96
+ ) SELECT totals.*, paged.* FROM totals LEFT JOIN paged ON 1
97
+ ORDER BY paged.total_cost_brl DESC, paged.id`,
94
98
  )
95
99
  .all(...values, limit, offset) as Array<Record<string, unknown>>;
100
+ const aggregate = result[0];
101
+ if (!aggregate) throw new Error("Canonical macro query did not return its aggregate row.");
102
+ const total_count = Number(aggregate.__entity_count);
103
+ const totals = {
104
+ entity_count: aggregate.__entity_count,
105
+ invocation_count: aggregate.__invocation_count,
106
+ total_duration: aggregate.__total_duration,
107
+ total_tokens: aggregate.__total_tokens,
108
+ total_cost_usd: aggregate.__total_cost_usd,
109
+ total_cost_brl: aggregate.__total_cost_brl,
110
+ };
111
+ const rows = result
112
+ .filter((row) => row.__row_present === 1)
113
+ .map((row) =>
114
+ Object.fromEntries(Object.entries(row).filter(([key]) => !key.startsWith("__"))),
115
+ );
96
116
  return {
97
117
  data_source: "sqlite_read_model" as const,
98
118
  view,
@@ -1,5 +1,12 @@
1
1
  import { parseYaml } from "@zaip-ai/core";
2
2
 
3
+ /** Same accepted lifecycle paths as the parser, before any YAML or cache work. */
4
+ export function isCanonicalLifecyclePath(path: string): boolean {
5
+ return /^\.agents\/(?:data|records)\/(?:executions\/.*\/execution\.md|changes\/.*\/change\.md|(?:changes|executions)\/.*\/decisions\/.*\.md)$/.test(
6
+ path,
7
+ );
8
+ }
9
+
3
10
  export function frontMatterRecord(content: string): Record<string, unknown> | null {
4
11
  const source = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content)?.[1];
5
12
  if (source === undefined) return null;