@zaip-ai/cli 1.3.2 → 1.3.4

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 (81) hide show
  1. package/LICENSE +6 -3
  2. package/README.md +8 -2
  3. package/dist/services/codex-pre-tool-hook-template.js +1 -1
  4. package/dist/services/runtime-template.test.js +9 -8
  5. package/dist/services/runtime-template.test.js.map +1 -1
  6. package/package.json +2 -2
  7. package/templates/.agents/dashboard/.zaip-managed.json +401 -381
  8. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-ovxurkIk.js → CostCalculationGuidePage-BZWjXQB-.js} +1 -1
  9. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-BXy6nti1.js → DashboardHelpDialog-D8qqeIPE.js} +30 -1
  10. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-vSvvOHQp.js → PricingSettingsDialog-Cm12_A0v.js} +1 -1
  11. package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-zH758vfs.js → ProjectSettingsPage-DxO9F8B4.js} +1 -1
  12. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-CTH2wDs5.js → TraceabilityWorkspace-_x0twOcS.js} +1 -1
  13. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Cpp8xFij.js +68 -0
  14. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-B_lDDHn1.js → icons-vendor-CR4Fbf3g.js} +1 -1
  15. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-ncC1rxap.js +2 -0
  16. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-B3TJrD4O.js → react-vendor-DRJQbKnp.js} +1 -1
  17. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-7f1j5wx3.js → traceability-CMrDYqjR.js} +1 -1
  18. package/templates/.agents/dashboard/apps/frontend/dist/index.html +5 -5
  19. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +48 -3
  20. package/templates/.agents/dashboard/apps/frontend/src/AppRequestGuards.tsx +44 -0
  21. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +92 -1
  22. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +2 -2
  23. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperAttributionPanel.test.ts +60 -0
  24. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperAttributionPanel.tsx +26 -9
  25. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorNavigation.tsx +189 -0
  26. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +122 -2
  27. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +70 -155
  28. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +59 -0
  29. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +55 -2
  30. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +274 -246
  31. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +255 -227
  32. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +263 -235
  33. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-dimension-facts.test.ts +282 -0
  34. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-dimension-facts.ts +71 -0
  35. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-launch-progress.ts +18 -0
  36. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-stale-seed.ts +50 -0
  37. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +9 -6
  38. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +45 -73
  39. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-seed.test.ts +79 -2
  40. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-work-item-index.test.ts +56 -0
  41. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.mjs +26 -8
  42. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.test.mjs +45 -4
  43. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.mjs +58 -0
  44. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.test.mjs +81 -0
  45. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +171 -71
  46. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +152 -11
  47. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +4 -9
  48. package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +9 -1
  49. package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-period-transition.spec.ts +232 -0
  50. package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +26 -5
  51. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  52. package/templates/.agents/requirements/comparative-audit.md +1 -1
  53. package/templates/.agents/requirements/context-engine.md +1 -1
  54. package/templates/.agents/requirements/cost-observability.md +1 -1
  55. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  56. package/templates/.agents/requirements/data-export.md +1 -1
  57. package/templates/.agents/requirements/design-system.md +1 -1
  58. package/templates/.agents/requirements/execution-engine.md +1 -1
  59. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  60. package/templates/.agents/requirements/model-orchestration.md +1 -1
  61. package/templates/.agents/requirements/public-distribution.md +1 -1
  62. package/templates/.agents/requirements/software-architecture.md +1 -1
  63. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  64. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  65. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  66. package/templates/.agents/runtime/bin/zaip.mjs +7 -7
  67. package/templates/.agents/runtime/checksums.sha256 +3 -3
  68. package/templates/.agents/runtime/provenance.json +3 -3
  69. package/templates/.agents/runtime/sbom.cdx.json +1595 -3491
  70. package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +16 -0
  71. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +19 -20
  72. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.test.mjs +143 -0
  73. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +14 -0
  74. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +3 -1
  75. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +21 -0
  76. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/source-size-telemetry/model-identity-reconciliation.mjs +3 -0
  77. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +29 -0
  78. package/templates/template-manifest.json +536 -932
  79. package/templates/.agents/dashboard/.tmp-run-list.json +0 -254
  80. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-DKwP1stV.js +0 -68
  81. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-CuMcoUni.js +0 -2
@@ -22,6 +22,22 @@ the supplied ID and canonical record; never scan ordinary history to guess a tar
22
22
  5. Persist observed evidence before terminal Task, Change, and Execution state; refresh
23
23
  manifests before the root Run outcome. Report technical success and formal closure
24
24
  separately.
25
+ 6. Inventory every affected ID, including prerequisites, failed preparations and retries.
26
+ Before the last finalizer, verify their canonical states, not only the current root.
27
+ After failed creation or missing manifest entries, rebuild the manifest and compare
28
+ it with the canonical records. A finished Run is not proof that its Task, Change,
29
+ Execution, or sibling work has closed.
30
+ 7. Explicit recovery of confirmed inactive legacy work uses `run reconcile --open`
31
+ with `--orphan-reason inactive_terminal_without_host_identity` and
32
+ `--orphan-scope` containing the reviewed Run/Task/Execution IDs. Preserve the current
33
+ delivery and other active work. Do not widen an ID list to `all`, infer inactivity
34
+ from missing metadata alone, or invent host identity. Persist the reason for blocking
35
+ work without completion evidence. Re-run the same scope to verify idempotence.
36
+ 8. Check source, generated files, framework lock, and evidence scopes before starting
37
+ an implementation. Reuse the current delivery for its own recovery; do not create
38
+ a chain of cleanup Tasks to compensate for an incomplete scope or preparation error.
39
+ Await every delegated result, make the finalizer the last child, and only report
40
+ closure after all owned work is terminal. A progress handoff is not completion.
25
41
 
26
42
  Use `$zaip-skill-reconcile-telemetry` only for missing observed usage after lifecycle
27
43
  identity is correct; it is not a substitute for closure.
@@ -20,6 +20,7 @@ import {
20
20
  recordSequence,
21
21
  recordType,
22
22
  sameCost,
23
+ selectedRunIdsFromArgs,
23
24
  supersedableUsageBeforeTerminal,
24
25
  usageStatus,
25
26
  usageView,
@@ -42,17 +43,7 @@ const explicitRecovery = process.argv.includes("--explicit-turn-recovery");
42
43
  if (timingOnly && !remeasure) {
43
44
  throw new Error("--timing-only requires --remeasure");
44
45
  }
45
- const selectedRunIds = new Set(
46
- process.argv.flatMap((argument, index, values) =>
47
- argument === "--run"
48
- ? String(values[index + 1] ?? "")
49
- .split(",")
50
- .filter(Boolean)
51
- : argument.startsWith("--run=")
52
- ? argument.slice("--run=".length).split(",").filter(Boolean)
53
- : [],
54
- ),
55
- );
46
+ const selectedRunIds = selectedRunIdsFromArgs(process.argv);
56
47
  const selectedSessionFiles = process.argv
57
48
  .flatMap((argument, index, values) =>
58
49
  argument === "--session-file"
@@ -75,6 +66,7 @@ if (apply && deduplicate && selectedRunIds.size === 0) {
75
66
  throw new Error("--apply --deduplicate requires at least one explicit --run <RUN-ID>");
76
67
  }
77
68
  const applyBackfill = apply && !deduplicate && !remeasure && !explicitRecovery;
69
+ const appliesToRun = (runId) => selectedRunIds.size === 0 || selectedRunIds.has(runId);
78
70
 
79
71
  const runFiles = await filesUnder(join(workspace, ".agents", "records", "runs"), ".jsonl");
80
72
  const runs = [];
@@ -82,6 +74,8 @@ const usedKeys = new Set();
82
74
  let danglingSupersessionRepairs = 0;
83
75
  for (const file of runFiles) {
84
76
  let records = jsonLines(await readFile(file, "utf8"));
77
+ const started = records.find((record) => recordType(record) === "run.started");
78
+ const applyToCurrentRun = appliesToRun(started?.run_id);
85
79
  const finishedRecord = records.find((record) => recordType(record) === "run.finished");
86
80
  const canonicalCorrectionTime = finishedRecord
87
81
  ? new Date(Date.parse(recordOccurredAt(finishedRecord)) - 1).toISOString()
@@ -93,7 +87,7 @@ for (const file of runFiles) {
93
87
  usageView(record)?.activity_kind === "retrospective_local_reconciliation" &&
94
88
  recordOccurredAt(record) !== canonicalCorrectionTime,
95
89
  );
96
- if (applyBackfill && staleCorrectionTime) {
90
+ if (applyBackfill && applyToCurrentRun && staleCorrectionTime) {
97
91
  records = records.map((record) =>
98
92
  usageView(record)?.activity_kind === "retrospective_local_reconciliation"
99
93
  ? { ...record, occurred_at: canonicalCorrectionTime }
@@ -116,7 +110,7 @@ for (const file of runFiles) {
116
110
  .map((usage) => usage.supersedes_event_id)
117
111
  .filter(Boolean),
118
112
  );
119
- if (applyBackfill && nativeMeasured)
113
+ if (applyBackfill && applyToCurrentRun && nativeMeasured)
120
114
  records = records.filter((record) => {
121
115
  const usage = usageView(record);
122
116
  return (
@@ -132,7 +126,7 @@ for (const file of runFiles) {
132
126
  let next = record;
133
127
  if (usage.supersedes_event_id !== null && !priorUsageIds.includes(usage.supersedes_event_id)) {
134
128
  danglingSupersessionRepairs += 1;
135
- if (applyBackfill) {
129
+ if (applyBackfill && applyToCurrentRun) {
136
130
  repairedDangling = true;
137
131
  const replacement = priorUsageIds.at(-1) ?? null;
138
132
  next = Array.isArray(record)
@@ -147,7 +141,7 @@ for (const file of runFiles) {
147
141
  priorUsageIds.push(usage.event_id);
148
142
  return next;
149
143
  });
150
- if (applyBackfill && repairedDangling) {
144
+ if (applyBackfill && applyToCurrentRun && repairedDangling) {
151
145
  const temporary = `${file}.supersession.tmp`;
152
146
  await writeFile(
153
147
  temporary,
@@ -163,7 +157,7 @@ for (const file of runFiles) {
163
157
  index > finishIndex &&
164
158
  finishIndex >= 0,
165
159
  );
166
- if (applyBackfill && (misplaced.length || staleCorrectionTime)) {
160
+ if (applyBackfill && applyToCurrentRun && (misplaced.length || staleCorrectionTime)) {
167
161
  const retained = records.filter((record) => !misplaced.includes(record));
168
162
  const insertAt = retained.findIndex((record) => recordType(record) === "run.finished");
169
163
  records = [...retained.slice(0, insertAt), ...misplaced, ...retained.slice(insertAt)];
@@ -180,6 +174,7 @@ for (const file of runFiles) {
180
174
  );
181
175
  if (
182
176
  applyBackfill &&
177
+ applyToCurrentRun &&
183
178
  (hasCorrection || nativeMeasured) &&
184
179
  records.some((record, index) => recordSequence(record) !== index + 1)
185
180
  ) {
@@ -192,7 +187,6 @@ for (const file of runFiles) {
192
187
  );
193
188
  await rename(temporary, file);
194
189
  }
195
- const started = records.find((record) => recordType(record) === "run.started");
196
190
  const usage = records.filter((record) => recordType(record) === "usage.recorded").map(usageView);
197
191
  for (const event of usage)
198
192
  if (event.source_segment_key && event.tokens?.total !== null)
@@ -264,7 +258,9 @@ const unfinishedMatches = unfinished.map((run) => ({
264
258
  const finishEligible = unfinishedMatches.filter(({ segment }) => segment !== null);
265
259
  const finishAmbiguous = unfinishedMatches.filter(({ ambiguous }) => ambiguous);
266
260
  if (applyBackfill) {
267
- for (const { run, segment } of finishEligible) {
261
+ for (const { run, segment } of finishEligible.filter(({ run }) =>
262
+ appliesToRun(run.started.run_id),
263
+ )) {
268
264
  const sequence = Math.max(...run.records.map((record) => recordSequence(record) ?? 0)) + 1;
269
265
  const finished = {
270
266
  schema_version: 2,
@@ -300,6 +296,7 @@ let repricedEvents = 0;
300
296
  let repricedRuns = 0;
301
297
  if (reprice) {
302
298
  for (const run of runs) {
299
+ if (applyBackfill && !appliesToRun(run.started.run_id)) continue;
303
300
  const superseded = new Set(run.usage.map((usage) => usage.supersedes_event_id).filter(Boolean));
304
301
  const effective = run.usage.filter((usage) => !superseded.has(usage.event_id));
305
302
  const corrections = [];
@@ -361,6 +358,7 @@ if (reprice) {
361
358
 
362
359
  const modelIdentityCorrections = await reconcileGenericModelIdentities({
363
360
  applyBackfill,
361
+ selectedRunIds,
364
362
  pricingFor,
365
363
  runs,
366
364
  segments: allSegments,
@@ -703,6 +701,7 @@ const ambiguousRunIds = [
703
701
  ]),
704
702
  ];
705
703
  const matchedIds = new Set(eligible.map(({ run }) => run.started.run_id));
704
+ const scopedFinishEligible = finishEligible.filter(({ run }) => appliesToRun(run.started.run_id));
706
705
  const corrections = runs.flatMap((run) =>
707
706
  run.usage.filter((usage) => usage.activity_kind === "retrospective_local_reconciliation"),
708
707
  );
@@ -749,7 +748,7 @@ console.log(
749
748
  missing_before: missing.length,
750
749
  unfinished_before: unfinished.length,
751
750
  unfinished_finish_eligible: finishEligible.length,
752
- unfinished_finish_applied: applyBackfill ? finishEligible.length : 0,
751
+ unfinished_finish_applied: applyBackfill ? scopedFinishEligible.length : 0,
753
752
  unfinished_finish_ambiguous: finishAmbiguous.length,
754
753
  eligible: eligible.length,
755
754
  applied: applyBackfill ? eligible.length : 0,
@@ -789,7 +788,7 @@ console.log(
789
788
  model_identity_corrections: modelIdentityCorrections,
790
789
  dangling_supersession_repairs: danglingSupersessionRepairs,
791
790
  eligible_run_ids: eligible.map(({ run }) => run.started.run_id),
792
- finish_eligible_run_ids: finishEligible.map(({ run }) => run.started.run_id),
791
+ finish_eligible_run_ids: scopedFinishEligible.map(({ run }) => run.started.run_id),
793
792
  confidence: Object.fromEntries(
794
793
  eligible.map(({ run, confidence }) => [run.started.run_id, confidence]),
795
794
  ),
@@ -0,0 +1,143 @@
1
+ import assert from "node:assert/strict";
2
+ import { execFile } from "node:child_process";
3
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { after, test } from "node:test";
7
+ import { fileURLToPath } from "node:url";
8
+ import { promisify } from "node:util";
9
+
10
+ const roots = [];
11
+ const run = promisify(execFile);
12
+ const script = fileURLToPath(new URL("./reconcile-local-telemetry.mjs", import.meta.url));
13
+
14
+ after(async () => Promise.all(roots.map((root) => rm(root, { recursive: true, force: true }))));
15
+
16
+ test("apply with --run leaves unselected historical corrections byte-identical", async () => {
17
+ const root = await mkdtemp(join(tmpdir(), "zaip-reconcile-scope-"));
18
+ roots.push(root);
19
+ const records = join(root, ".agents", "records", "runs", "2026", "08");
20
+ const selectedFile = join(records, "RUN-SELECTED.jsonl");
21
+ const unselectedFile = join(records, "RUN-UNSELECTED.jsonl");
22
+ const sessionFile = join(root, "selected-session.jsonl");
23
+
24
+ await mkdir(records, { recursive: true });
25
+
26
+ await writeFile(
27
+ selectedFile,
28
+ `${[
29
+ started("RUN-SELECTED", "turn-selected"),
30
+ finished("RUN-SELECTED", "2026-08-31T12:01:00.000Z"),
31
+ ]
32
+ .map(JSON.stringify)
33
+ .join("\n")}\n`,
34
+ );
35
+ const unselected = `${[
36
+ started("RUN-UNSELECTED", "turn-unselected"),
37
+ usage("RUN-UNSELECTED:000002", "2026-08-31T12:00:20.000Z", null),
38
+ usage("RUN-UNSELECTED:reconcile:000003", "2026-08-31T12:00:30.000Z", "RUN-UNSELECTED:000002"),
39
+ finished("RUN-UNSELECTED", "2026-08-31T12:01:00.000Z"),
40
+ ]
41
+ .map(JSON.stringify)
42
+ .join("\n")}\n`;
43
+ await writeFile(unselectedFile, unselected);
44
+ await writeFile(
45
+ sessionFile,
46
+ `${[
47
+ {
48
+ timestamp: "2026-08-31T12:00:00.000Z",
49
+ type: "session_meta",
50
+ payload: { id: "session", cwd: root },
51
+ },
52
+ {
53
+ timestamp: "2026-08-31T12:00:01.000Z",
54
+ type: "event_msg",
55
+ payload: { type: "task_started", turn_id: "turn-selected" },
56
+ },
57
+ {
58
+ timestamp: "2026-08-31T12:00:20.000Z",
59
+ type: "event_msg",
60
+ payload: {
61
+ type: "token_count",
62
+ info: {
63
+ last_token_usage: {
64
+ input_tokens: 10,
65
+ cached_input_tokens: 2,
66
+ output_tokens: 4,
67
+ reasoning_output_tokens: 1,
68
+ total_tokens: 14,
69
+ },
70
+ },
71
+ },
72
+ },
73
+ {
74
+ timestamp: "2026-08-31T12:00:30.000Z",
75
+ type: "event_msg",
76
+ payload: { type: "task_complete", turn_id: "turn-selected" },
77
+ },
78
+ ]
79
+ .map(JSON.stringify)
80
+ .join("\n")}\n`,
81
+ );
82
+
83
+ await run(
84
+ process.execPath,
85
+ [script, "--apply", "--run", "RUN-SELECTED", "--session-file", sessionFile],
86
+ {
87
+ cwd: root,
88
+ },
89
+ );
90
+
91
+ assert.equal(await readFile(unselectedFile, "utf8"), unselected);
92
+ const selected = await readFile(selectedFile, "utf8");
93
+ assert.match(selected, /retrospective_local_reconciliation/);
94
+ assert.match(selected, /"total":14/);
95
+ });
96
+
97
+ function started(runId, turnId) {
98
+ return {
99
+ schema_version: 2,
100
+ event_id: `${runId}:000001`,
101
+ run_id: runId,
102
+ sequence: 1,
103
+ occurred_at: "2026-08-31T12:00:00.000Z",
104
+ type: "run.started",
105
+ phase: "execution",
106
+ client_turn_id: turnId,
107
+ model: "gpt-5.6-terra",
108
+ };
109
+ }
110
+
111
+ function usage(eventId, occurredAt, supersedes) {
112
+ return {
113
+ schema_version: 2,
114
+ event_id: eventId,
115
+ run_id: "RUN-UNSELECTED",
116
+ sequence: eventId.endsWith("000002") ? 2 : 3,
117
+ occurred_at: occurredAt,
118
+ type: "usage.recorded",
119
+ phase: "execution",
120
+ activity_kind: eventId.includes("reconcile")
121
+ ? "retrospective_local_reconciliation"
122
+ : "implementation",
123
+ tokens: { input: 1, cached_input: 0, output: 1, reasoning: 0, total: 2 },
124
+ cost: { usd: 0.001, brl: 0.005 },
125
+ provider: "openai",
126
+ model: "gpt-5.6-terra",
127
+ source_segment_key: null,
128
+ supersedes_event_id: supersedes,
129
+ };
130
+ }
131
+
132
+ function finished(runId, occurredAt) {
133
+ return {
134
+ schema_version: 2,
135
+ event_id: `${runId}:000004`,
136
+ run_id: runId,
137
+ sequence: runId === "RUN-SELECTED" ? 2 : 4,
138
+ occurred_at: occurredAt,
139
+ type: "run.finished",
140
+ phase: "execution",
141
+ status: "completed",
142
+ };
143
+ }
@@ -48,6 +48,20 @@ export function jsonLines(text) {
48
48
  });
49
49
  }
50
50
 
51
+ export function selectedRunIdsFromArgs(args) {
52
+ return new Set(
53
+ args.flatMap((argument, index, values) =>
54
+ argument === "--run"
55
+ ? String(values[index + 1] ?? "")
56
+ .split(",")
57
+ .filter(Boolean)
58
+ : argument.startsWith("--run=")
59
+ ? argument.slice("--run=".length).split(",").filter(Boolean)
60
+ : [],
61
+ ),
62
+ );
63
+ }
64
+
51
65
  export function recordType(record) {
52
66
  if (!Array.isArray(record)) return record.type;
53
67
  return record[1] === "u"
@@ -146,7 +146,9 @@ export function timingEnrichmentRunIds({ remeasure, applyBackfill, selectedRunId
146
146
  runs
147
147
  .filter(
148
148
  (run) =>
149
- run.nativeMeasured && run.records.some((record) => recordType(record) === "run.finished"),
149
+ (selectedRunIds.size === 0 || selectedRunIds.has(run.started.run_id)) &&
150
+ run.nativeMeasured &&
151
+ run.records.some((record) => recordType(record) === "run.finished"),
150
152
  )
151
153
  .map((run) => run.started.run_id),
152
154
  );
@@ -37,6 +37,27 @@ test("selects completed native Runs for default timing-only backfill", () => {
37
37
  assert.deepEqual([...selected], ["RUN-NATIVE"]);
38
38
  });
39
39
 
40
+ test("limits automatic timing enrichment to explicit Run selection", () => {
41
+ const selected = timingEnrichmentRunIds({
42
+ remeasure: false,
43
+ applyBackfill: true,
44
+ selectedRunIds: new Set(["RUN-SELECTED"]),
45
+ runs: [
46
+ {
47
+ nativeMeasured: true,
48
+ started: { run_id: "RUN-SELECTED" },
49
+ records: [{ type: "run.started" }, { type: "run.finished" }],
50
+ },
51
+ {
52
+ nativeMeasured: true,
53
+ started: { run_id: "RUN-UNSELECTED" },
54
+ records: [{ type: "run.started" }, { type: "run.finished" }],
55
+ },
56
+ ],
57
+ });
58
+ assert.deepEqual([...selected], ["RUN-SELECTED"]);
59
+ });
60
+
40
61
  test("explicit recovery requires exact session identity, unused turns, model/effort, and SPEC text", () => {
41
62
  const run = {
42
63
  started: {
@@ -2,6 +2,7 @@ import { rename, writeFile } from "node:fs/promises";
2
2
 
3
3
  export async function reconcileGenericModelIdentities({
4
4
  applyBackfill,
5
+ selectedRunIds = new Set(),
5
6
  pricingFor,
6
7
  runs,
7
8
  segments,
@@ -13,6 +14,8 @@ export async function reconcileGenericModelIdentities({
13
14
  const genericModel = (model) => typeof model === "string" && /^gpt-5$/i.test(model.trim());
14
15
  let correctionsCount = 0;
15
16
  for (const run of runs) {
17
+ if (applyBackfill && selectedRunIds.size > 0 && !selectedRunIds.has(run.started.run_id))
18
+ continue;
16
19
  const superseded = new Set(run.usage.map((usage) => usage.supersedes_event_id).filter(Boolean));
17
20
  const effective = run.usage.filter((usage) => !superseded.has(usage.event_id));
18
21
  const corrections = effective.flatMap((usage) => {
@@ -113,6 +113,35 @@ O guard não remove, move, ignora ou adiciona arquivos ao Git; a limpeza deve
113
113
  ser resolvida pelo responsável autorizado, preservando os artefatos anteriores.
114
114
  Use o checkout limpo do candidato exato e não publique TGZs de diagnóstico.
115
115
 
116
+ ## Controles críticos pendentes de prova
117
+
118
+ Para uma Change que altere diagnóstico de pacote, encerramento/recovery,
119
+ filtragem de conteúdo, SBOM, política de empacotamento ou proteção remota,
120
+ documentar e executar cenários negativos no candidato exato. O contrato deve
121
+ incluir, quando aplicável: dual-doctor de TGZ e corrupção controlada; paridade
122
+ entre `finish` e recovery com Evidence de reviewer filho e rejeição cross-root
123
+ e cross-task; variantes de segredo/binário; identidade do SBOM do artefato
124
+ real; e temporário interno rastreado que não deve entrar no TGZ. A projeção
125
+ exclui `.tmp-run-list.json`; se esse arquivo aparecer no inventário empacotado,
126
+ o guard deve bloquear o pacote, sem apagar o arquivo local.
127
+
128
+ Um script local de consulta de proteção não demonstra que a branch está
129
+ protegida. Para `main`, o guard implementado é
130
+ `node scripts/validate-github-protection.mjs --repository zaip-ai/zaip-aios --branch main --json`,
131
+ com alias `validate:github-protection`. Se a consulta retornar 403, registre a
132
+ limitação de acesso e mantenha o controle remoto pendente; não contorne a API
133
+ nem declare proteção aplicada. Configurar/aplicar a proteção depende de duas
134
+ pessoas/contas autorizadas e de decisão humana separada.
135
+
136
+ O alias live é um check manual, separado de CI: restrição de plano ou permissão
137
+ do provedor não deve tornar o CI indiscriminadamente vermelho. Testes locais do
138
+ guard comprovam somente a proposta local; o JSON retornado pela consulta não é
139
+ proteção ativa.
140
+
141
+ Documentação de contrato, presença de testes ou snapshot não substituem output
142
+ executado, SHA candidato, ambiente, horário real e locator sanitizado. Não
143
+ alegar aprovação final, suporte, SLA ou canal comercial a partir desses gates.
144
+
116
145
  ## Evidência e aprovação
117
146
 
118
147
  Testes passando comprovam validação técnica. Maturidade formal máxima exige ainda: