toolroll 0.9.91 → 0.9.93
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/CHANGELOG.md +41 -0
- package/dist/approval-authority.d.ts +9 -1
- package/dist/approval-authority.js +15 -2
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +2 -2
- package/dist/assignment.js +11 -3
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +30 -30
- package/dist/browser-crew.js +2 -2
- package/dist/browser-workspace.d.ts +3 -5
- package/dist/build-review.js +2 -0
- package/dist/builder.js +21 -2
- package/dist/chat-actions.d.ts +11 -3
- package/dist/chat-actions.js +47 -2
- package/dist/chat-decide.js +2 -0
- package/dist/chat-review.d.ts +2 -1
- package/dist/chat-review.js +2 -2
- package/dist/check-facts.d.ts +124 -0
- package/dist/check-facts.js +272 -0
- package/dist/check-report.d.ts +5 -0
- package/dist/check-report.js +4 -3
- package/dist/cli.js +5 -0
- package/dist/contracts/chat-actions.d.ts +10 -0
- package/dist/contracts/chat-actions.js +4 -0
- package/dist/contracts/console-api.d.ts +3 -2
- package/dist/contracts/console-api.js +5 -2
- package/dist/contracts/release-check.d.ts +25 -1
- package/dist/contracts/release-check.js +19 -0
- package/dist/contracts/route-results.d.ts +121 -0
- package/dist/contracts/route-results.js +71 -0
- package/dist/contracts/route.d.ts +343 -5
- package/dist/contracts/route.js +36 -7
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/desktop-update.d.ts +2 -2
- package/dist/desktop-update.js +13 -9
- package/dist/dispose.js +6 -0
- package/dist/eval-hooks.d.ts +55 -0
- package/dist/eval-hooks.js +227 -4
- package/dist/flaky.d.ts +17 -0
- package/dist/flaky.js +9 -0
- package/dist/flow-engine.js +1 -1
- package/dist/flow-pull-request.js +1 -1
- package/dist/lead-doors.js +4 -2
- package/dist/lead-tools.js +10 -2
- package/dist/local-owner.d.ts +1 -1
- package/dist/local-owner.js +11 -0
- package/dist/memory-cli.d.ts +3 -0
- package/dist/memory-cli.js +3 -3
- package/dist/memory-pass.js +5 -1
- package/dist/model-choices.d.ts +7 -3
- package/dist/model-choices.js +26 -11
- package/dist/models-cli.d.ts +205 -0
- package/dist/models-cli.js +143 -4
- package/dist/models-ui.d.ts +57 -0
- package/dist/models-ui.js +44 -1
- package/dist/needs-you.d.ts +7 -7
- package/dist/needs-you.js +12 -10
- package/dist/operate-remote-arguments.js +4 -4
- package/dist/operate-remote.js +1 -1
- package/dist/operate.js +191 -23
- package/dist/phase-routing.d.ts +82 -2
- package/dist/phase-routing.js +132 -18
- package/dist/project-delete.d.ts +4 -0
- package/dist/project-delete.js +16 -4
- package/dist/project-memory.d.ts +8 -0
- package/dist/project-memory.js +20 -1
- package/dist/pull-request-flow.js +1 -1
- package/dist/push.d.ts +10 -3
- package/dist/push.js +22 -6
- package/dist/release-adapters.js +11 -1
- package/dist/release-approval.d.ts +191 -0
- package/dist/release-approval.js +382 -0
- package/dist/release-auto.js +22 -4
- package/dist/release-card.d.ts +73 -4
- package/dist/release-card.js +168 -23
- package/dist/release-cli.js +9 -1
- package/dist/release.d.ts +30 -0
- package/dist/release.js +42 -3
- package/dist/result-actions.d.ts +4 -0
- package/dist/result-actions.js +3 -0
- package/dist/result-acts.d.ts +3 -3
- package/dist/result-acts.js +1 -1
- package/dist/route-hooks.d.ts +54 -0
- package/dist/route-hooks.js +179 -0
- package/dist/route-results.d.ts +70 -0
- package/dist/route-results.js +340 -0
- package/dist/route-rules.d.ts +324 -0
- package/dist/route-rules.js +781 -0
- package/dist/route-savings.d.ts +136 -0
- package/dist/route-savings.js +193 -0
- package/dist/route-store.d.ts +300 -0
- package/dist/route-store.js +598 -0
- package/dist/scope.d.ts +3 -1
- package/dist/scope.js +4 -1
- package/dist/server/guards.js +1 -0
- package/dist/server/pages-assets.js +1 -1
- package/dist/server/render-chat.js +2 -2
- package/dist/server/render-release.d.ts +17 -0
- package/dist/server/render-release.js +84 -0
- package/dist/server/render-results.d.ts +0 -2
- package/dist/server/render-results.js +4 -6
- package/dist/server/render-tasks.d.ts +7 -0
- package/dist/server/render-tasks.js +16 -3
- package/dist/server/route-table.d.ts +1 -1
- package/dist/server/route-table.js +19 -1
- package/dist/server/settings.d.ts +2 -1
- package/dist/server/settings.js +87 -1
- package/dist/server/task-views.js +6 -0
- package/dist/server/tasks.d.ts +15 -0
- package/dist/server/tasks.js +105 -20
- package/dist/settings-proofs.js +2 -0
- package/dist/spend-ui.d.ts +11 -0
- package/dist/spend-ui.js +49 -2
- package/dist/stop-proof.d.ts +67 -0
- package/dist/stop-proof.js +293 -0
- package/dist/store.d.ts +106 -31
- package/dist/store.js +653 -128
- package/dist/surface.js +17 -5
- package/dist/task-outcome-cli.js +24 -2
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +4 -2
- package/dist/work-index.js +7 -9
- package/dist/work-summary.d.ts +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check facts (docs/plans/learning-loops.md, "Foundation: check facts"): what each settled check ran under and what it
|
|
3
|
+
* reported, recorded at disposition, never trusted for authority. The only writer of `check_fact` and `check_result`
|
|
4
|
+
* (the v149 migration aside); release-auto's learning scan (eval-hooks.ts learningWriteProblem) needs a person for
|
|
5
|
+
* SQL that writes them from anywhere else. A policy path (release-auto.ts EVAL_POLICY): any change needs a person.
|
|
6
|
+
*
|
|
7
|
+
* - Conditions are captured when the check's command starts (captureCheckConditions, called from the builder's
|
|
8
|
+
* runVerification), held in memory and passed here: the commit's tree read through verified objects (never the
|
|
9
|
+
* checkout), the approved command's digest, the check's access, the harness digest and lockfile, and the values of
|
|
10
|
+
* every environment variable the check inherits (the setup allowlist), hashed with checkContextOf's fields as
|
|
11
|
+
* `conditions_digest`. Not re-read later.
|
|
12
|
+
* - Results are tagged by source: `receipt` (the plane's own whole-check result: trusted for "the check passed or
|
|
13
|
+
* failed", nothing finer), `report` (the candidate-written report's lines), `log` (Vitest's FAIL lines in the sealed
|
|
14
|
+
* log, failures only, file level) and `learn` (the advisory learning line). Every failure is recorded; the check's
|
|
15
|
+
* blocking failure is the check's and is never changed here.
|
|
16
|
+
* - Explicit execution evidence is a `report` or `learn` line naming an identity. No line is no evidence: never
|
|
17
|
+
* skipped, removed or passed.
|
|
18
|
+
* - Fail closed: an unreadable tree, or conditions never captured (the command never started), records the fact row
|
|
19
|
+
* with no conditions and no results at all.
|
|
20
|
+
*
|
|
21
|
+
* Nothing here changes a check's outcome, a verdict, an approval or a rerun: every reader of a check's outcome reads
|
|
22
|
+
* the receipt and run_check, which this module never writes.
|
|
23
|
+
*/
|
|
24
|
+
import { createHash } from "node:crypto";
|
|
25
|
+
import { failingTestsOf } from "./gate-failure.js";
|
|
26
|
+
import { checkContextOf, projectGitDir, treeFactsOf } from "./check-report.js";
|
|
27
|
+
import { readVerifiedArtifact } from "./evidence.js";
|
|
28
|
+
import { CHECK_REPORT_PREFIX, isExactCase, learnLineSchema, learnRequest, LEARN_MAX_BYTES, LEARN_PREFIX } from "./contracts/release-check.js";
|
|
29
|
+
import { nightlyTaskRef, releaseTaskRef } from "./store.js";
|
|
30
|
+
/** The sources a result can come from, and the states it can have. */
|
|
31
|
+
export const CHECK_RESULT_SOURCES = Object.freeze(["receipt", "report", "log", "learn"]);
|
|
32
|
+
/** The receipt's whole-check identity, and the one a failed check with nothing else named records. */
|
|
33
|
+
export const WHOLE_CHECK = "check";
|
|
34
|
+
export const UNEXPLAINED = "unexplained";
|
|
35
|
+
/** How many result rows one check records at most (a report's own bounds are far above any real check). */
|
|
36
|
+
export const MAX_RESULTS_PER_CHECK = 20_000;
|
|
37
|
+
/** How old a tracked observation may be and still be asked about (docs/plans/learning-loops.md, 90-day window). */
|
|
38
|
+
export const OBSERVATION_WATCH_MS = 90 * 24 * 60 * 60 * 1000;
|
|
39
|
+
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
40
|
+
/**
|
|
41
|
+
* The conditions a check's command starts under: its commit's verified tree facts, the approved command's digest, its
|
|
42
|
+
* access, and the values of `envNames` (the variables the check inherits, builder.ts SETUP_ENV_ALLOWLIST) as this
|
|
43
|
+
* process holds them now — what the command is given. Hashed with checkContextOf's fields; the class reads
|
|
44
|
+
* `<platform>-<arch>-node<major>`. Values are hashed, never stored.
|
|
45
|
+
*/
|
|
46
|
+
export function captureCheckConditions(input) {
|
|
47
|
+
const none = (problem) => ({ head: input.head, tree: null, commandDigest: input.command.digest, access: input.access, harnessDigest: null, lockfile: null, conditionsDigest: null, conditionsClass: null, problem });
|
|
48
|
+
if (input.repo === null)
|
|
49
|
+
return none("the check's project isn't recorded");
|
|
50
|
+
const gitDir = projectGitDir(input.repo);
|
|
51
|
+
if (gitDir === null)
|
|
52
|
+
return none("the project's Git directory can't be read");
|
|
53
|
+
let read;
|
|
54
|
+
// Advisory: whatever happens reading the tree, the check itself is never stopped by it.
|
|
55
|
+
try {
|
|
56
|
+
read = treeFactsOf(gitDir, input.head);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return none(`the checked commit can't be read from verified objects (${error instanceof Error ? error.message : String(error)})`);
|
|
60
|
+
}
|
|
61
|
+
if (!read.ok)
|
|
62
|
+
return none(`the checked commit can't be read from verified objects (${read.problem})`);
|
|
63
|
+
const context = checkContextOf(read.facts, input.command, input.access);
|
|
64
|
+
const env = input.env ?? process.env;
|
|
65
|
+
const environment = Object.fromEntries([...new Set(input.envNames)].sort().map(name => [name, env[name] ?? null]));
|
|
66
|
+
const digest = sha256(JSON.stringify({ version: 1, context, environment }));
|
|
67
|
+
const major = /^v?(\d+)/.exec(context.node)?.[1] ?? "unknown";
|
|
68
|
+
return { head: input.head, tree: read.facts.tree, commandDigest: input.command.digest, access: input.access, harnessDigest: read.facts.harnessDigest, lockfile: read.facts.lockfile,
|
|
69
|
+
conditionsDigest: digest, conditionsClass: `${context.platform}-${context.arch}-node${major}`, problem: null };
|
|
70
|
+
}
|
|
71
|
+
/** Why the task's check ran: a release's or the nightly's purpose, read from the plane's own records. */
|
|
72
|
+
export function checkPurposeOf(store, taskRef) {
|
|
73
|
+
if (nightlyTaskRef(store, taskRef))
|
|
74
|
+
return "nightly";
|
|
75
|
+
if (releaseTaskRef(store, taskRef))
|
|
76
|
+
return "release";
|
|
77
|
+
const terms = store.handle.prepare(`SELECT s.release_terms_json, s.release_check_json FROM task_scope s JOIN task_ref r ON r.external_id = s.task_id AND r.backend = 'built-in' WHERE r.id = ?`).get(taskRef);
|
|
78
|
+
return terms !== undefined && (terms["release_terms_json"] != null || terms["release_check_json"] != null) ? "release" : "ordinary";
|
|
79
|
+
}
|
|
80
|
+
/** The advisory learning line of a check's output, read strictly: exactly one line carrying this run's nonce, at most
|
|
81
|
+
* 64 KiB, before the report, matching its contract, naming only requested exact cases, each once. Anything else is
|
|
82
|
+
* not recorded, with why; that loses learning evidence only. */
|
|
83
|
+
export function learningLinesOf(stdout, nonce, asked) {
|
|
84
|
+
const lines = stdout.split("\n");
|
|
85
|
+
let reportAt = -1;
|
|
86
|
+
for (let at = lines.length - 1; at >= 0; at--)
|
|
87
|
+
if (lines[at].startsWith(CHECK_REPORT_PREFIX)) {
|
|
88
|
+
reportAt = at;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
const ours = [];
|
|
92
|
+
let oversized = false, others = false, unreadable = false;
|
|
93
|
+
lines.forEach((line, at) => {
|
|
94
|
+
if (!line.startsWith(LEARN_PREFIX))
|
|
95
|
+
return;
|
|
96
|
+
if (Buffer.byteLength(line) > LEARN_MAX_BYTES) {
|
|
97
|
+
oversized = true;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
let value;
|
|
101
|
+
try {
|
|
102
|
+
value = JSON.parse(line.slice(LEARN_PREFIX.length));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
unreadable = true;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (value === null || typeof value !== "object" || value["nonce"] !== nonce) {
|
|
109
|
+
others = true;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
ours.push({ at, value });
|
|
113
|
+
});
|
|
114
|
+
if (ours.length === 0)
|
|
115
|
+
return { ok: false, why: oversized ? "the learning line was over 64 KiB" : unreadable ? "the learning line couldn't be read" : others ? "no learning line carried this run's nonce" : "the check printed no learning line" };
|
|
116
|
+
if (ours.length > 1)
|
|
117
|
+
return { ok: false, why: "the check printed more than one learning line" };
|
|
118
|
+
if (oversized)
|
|
119
|
+
return { ok: false, why: "a learning line was over 64 KiB" };
|
|
120
|
+
const only = ours[0];
|
|
121
|
+
if (reportAt >= 0 && only.at > reportAt)
|
|
122
|
+
return { ok: false, why: "the learning line came after the report" };
|
|
123
|
+
const read = learnLineSchema.safeParse(only.value);
|
|
124
|
+
if (!read.success)
|
|
125
|
+
return { ok: false, why: "the learning line doesn't match its contract" };
|
|
126
|
+
const wanted = new Set(asked);
|
|
127
|
+
const seen = new Set();
|
|
128
|
+
for (const one of read.data.results) {
|
|
129
|
+
if (!wanted.has(one.identity) || !isExactCase(one.identity))
|
|
130
|
+
return { ok: false, why: "the learning line named an identity it wasn't asked about" };
|
|
131
|
+
if (seen.has(one.identity))
|
|
132
|
+
return { ok: false, why: "the learning line named an identity twice" };
|
|
133
|
+
seen.add(one.identity);
|
|
134
|
+
}
|
|
135
|
+
return { ok: true, results: read.data.results };
|
|
136
|
+
}
|
|
137
|
+
const RANK = { passed: 0, skipped: 1, "passed-on-retry": 2, failed: 3 };
|
|
138
|
+
/** The report's lines as results: every case it names; a file, group, version or part only when it didn't simply pass. */
|
|
139
|
+
export function reportResults(report) {
|
|
140
|
+
const rows = [];
|
|
141
|
+
const add = (identity, state) => rows.push({ identity, state, source: "report" });
|
|
142
|
+
for (const one of report.units?.identities ?? [])
|
|
143
|
+
add(`unit:${one.file}::${one.name}`, one.state === "passed" && one.flaky ? "passed-on-retry" : one.state);
|
|
144
|
+
for (const one of report.units?.files ?? []) {
|
|
145
|
+
if (one.state !== "passed")
|
|
146
|
+
add(`file:${one.file}`, one.state);
|
|
147
|
+
else if (one.flaky)
|
|
148
|
+
add(`file:${one.file}`, "passed-on-retry");
|
|
149
|
+
}
|
|
150
|
+
for (const run of report.journeys)
|
|
151
|
+
for (const group of run.groups) {
|
|
152
|
+
if (group.state === "failed")
|
|
153
|
+
add(`group:${run.script}:${run.kind}:${group.group}`, "failed");
|
|
154
|
+
else if (group.state === "passed" && group.retried)
|
|
155
|
+
add(`group:${run.script}:${run.kind}:${group.group}`, "passed-on-retry");
|
|
156
|
+
if (group.state === "not-run")
|
|
157
|
+
continue;
|
|
158
|
+
for (const journey of group.journeys)
|
|
159
|
+
add(`journey:${run.script}:${run.kind}:${group.group}::${journey.name}`, journey.state === "passed" && group.retried ? "passed-on-retry" : journey.state);
|
|
160
|
+
}
|
|
161
|
+
for (const one of report.upgrades?.versions ?? [])
|
|
162
|
+
if (one.state === "failed")
|
|
163
|
+
add(`upgrade:${one.version}`, "failed");
|
|
164
|
+
for (const part of report.parts)
|
|
165
|
+
if (part.selected && part.ran && (part.code !== 0 || part.timedOut))
|
|
166
|
+
add(`part:${part.part}`, "failed");
|
|
167
|
+
return rows;
|
|
168
|
+
}
|
|
169
|
+
/** The sealed log's FAIL lines (Vitest), failures only, file level; nothing when the log's own count disagrees. */
|
|
170
|
+
function logResults(store, root, runId) {
|
|
171
|
+
const logs = store.artifactsFor(runId).filter(one => one.kind === "check-log");
|
|
172
|
+
if (logs.length !== 1)
|
|
173
|
+
return [];
|
|
174
|
+
const read = readVerifiedArtifact(root, logs[0]);
|
|
175
|
+
if (!read.ok)
|
|
176
|
+
return [];
|
|
177
|
+
const failing = failingTestsOf(read.content.toString("utf8")) ?? [];
|
|
178
|
+
return failing.filter(one => /^(?!\/)(?!.*(?:^|\/)\.\.?(?:\/|$))[^\u0000-\u001f\\]{1,512}$/.test(one.file)).map(one => ({ identity: `file:${one.file}`, state: "failed", source: "log" }));
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Record what a settled check ran under and reported, once (first write wins), in one transaction: its fact row and
|
|
182
|
+
* every result row. Called at disposition by the builder right after it records the check's own outcome. Advisory:
|
|
183
|
+
* nothing reads these for a verdict, a rerun or an approval.
|
|
184
|
+
*/
|
|
185
|
+
export function recordCheckFacts(store, root, input) {
|
|
186
|
+
const conditions = input.conditions;
|
|
187
|
+
const comparable = conditions !== null && conditions.tree !== null && conditions.conditionsDigest !== null;
|
|
188
|
+
const asked = input.attempt === null ? [] : learnRequest(input.attempt.learn);
|
|
189
|
+
let learnState = asked.length === 0 ? "not-asked" : "not-recorded";
|
|
190
|
+
let learnWhy = null;
|
|
191
|
+
const rows = [];
|
|
192
|
+
if (comparable) {
|
|
193
|
+
if (input.receipt.status !== "not-run")
|
|
194
|
+
rows.push({ identity: WHOLE_CHECK, state: input.receipt.status, source: "receipt" });
|
|
195
|
+
if (input.report !== null)
|
|
196
|
+
rows.push(...reportResults(input.report));
|
|
197
|
+
rows.push(...logResults(store, root, input.runId));
|
|
198
|
+
}
|
|
199
|
+
if (asked.length > 0) {
|
|
200
|
+
if (!comparable)
|
|
201
|
+
learnWhy = conditions === null ? "the check's command never started" : conditions.problem ?? "the check's conditions weren't recorded";
|
|
202
|
+
else if (input.stdout === null || input.attempt === null)
|
|
203
|
+
learnWhy = "the check's output wasn't kept";
|
|
204
|
+
else {
|
|
205
|
+
const learned = learningLinesOf(input.stdout, input.attempt.nonce, asked);
|
|
206
|
+
if (!learned.ok)
|
|
207
|
+
learnWhy = learned.why;
|
|
208
|
+
else {
|
|
209
|
+
learnState = "recorded";
|
|
210
|
+
for (const one of learned.results)
|
|
211
|
+
rows.push({ identity: one.identity, state: one.state, source: "learn" });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (comparable && input.receipt.status === "failed" && !rows.some(one => one.source !== "receipt" && one.state === "failed"))
|
|
216
|
+
rows.push({ identity: UNEXPLAINED, state: "failed", source: "receipt" });
|
|
217
|
+
// One row per (identity, source), its worst state; bounded.
|
|
218
|
+
const kept = new Map();
|
|
219
|
+
for (const row of rows) {
|
|
220
|
+
if (row.identity.length < 1 || row.identity.length > 2600)
|
|
221
|
+
continue;
|
|
222
|
+
const key = `${row.source}\u0000${row.identity}`;
|
|
223
|
+
const before = kept.get(key);
|
|
224
|
+
if (before === undefined || RANK[row.state] > RANK[before.state])
|
|
225
|
+
kept.set(key, row);
|
|
226
|
+
}
|
|
227
|
+
const at = input.now.toISOString();
|
|
228
|
+
store.transact(() => {
|
|
229
|
+
const inserted = store.handle.prepare(`INSERT INTO check_fact (run, repo, task_ref, head, tree, command_digest, access, harness_digest, lockfile, conditions_digest,
|
|
230
|
+
conditions_class, purpose, learn_state, learn_why, recorded_at) SELECT id, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? FROM run WHERE id = ?
|
|
231
|
+
ON CONFLICT (run) DO NOTHING`).run(input.repo, input.taskRef, conditions?.head ?? null, comparable ? conditions.tree : null, conditions?.commandDigest ?? null, conditions?.access ?? null, comparable ? conditions.harnessDigest : null, comparable ? conditions.lockfile : null, comparable ? conditions.conditionsDigest : null, comparable ? conditions.conditionsClass : null, checkPurposeOf(store, input.taskRef), learnState, learnState === "not-recorded" ? learnWhy ?? "not recorded" : null, at, input.runId);
|
|
232
|
+
if (Number(inserted.changes) !== 1)
|
|
233
|
+
return;
|
|
234
|
+
const insert = store.handle.prepare("INSERT INTO check_result (run, identity, state, source, recorded_at) VALUES (?, ?, ?, ?, ?)");
|
|
235
|
+
for (const row of [...kept.values()].slice(0, MAX_RESULTS_PER_CHECK))
|
|
236
|
+
insert.run(input.runId, row.identity, row.state, row.source, at);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
export function checkFactOf(store, runId) {
|
|
240
|
+
const row = store.handle.prepare("SELECT * FROM check_fact WHERE run = ?").get(runId);
|
|
241
|
+
if (row === undefined)
|
|
242
|
+
return null;
|
|
243
|
+
const text = (key) => row[key] === null ? null : String(row[key]);
|
|
244
|
+
return { run: Number(row["run"]), repo: text("repo"), taskRef: Number(row["task_ref"]), head: text("head"), tree: text("tree"), commandDigest: text("command_digest"), access: text("access"),
|
|
245
|
+
harnessDigest: text("harness_digest"), lockfile: text("lockfile"), conditionsDigest: text("conditions_digest"), conditionsClass: text("conditions_class"),
|
|
246
|
+
purpose: String(row["purpose"]), learnState: String(row["learn_state"]), learnWhy: text("learn_why"), recordedAt: String(row["recorded_at"]) };
|
|
247
|
+
}
|
|
248
|
+
/** A check's recorded results, by source then identity. */
|
|
249
|
+
export function checkResultsOf(store, runId) {
|
|
250
|
+
return store.handle.prepare("SELECT identity, state, source FROM check_result WHERE run = ? ORDER BY source, identity").all(runId)
|
|
251
|
+
.map(row => ({ identity: String(row["identity"]), state: String(row["state"]), source: String(row["source"]) }));
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* What a project's Full checks are asked about for learning (`learn`): its tracked identities (learning_watch) — an
|
|
255
|
+
* accepted note's until it ends, an observation's for 90 days — exact cases only, the most recently tracked first, at
|
|
256
|
+
* most 200. Those left out are counted (`leftOut`): a coverage gap, said, never hidden.
|
|
257
|
+
*/
|
|
258
|
+
export function learnRequestFor(store, repo, now) {
|
|
259
|
+
const since = new Date(now.getTime() - OBSERVATION_WATCH_MS).toISOString();
|
|
260
|
+
let tracked;
|
|
261
|
+
// Advisory: a read that fails asks about nothing, and the check runs as it would have.
|
|
262
|
+
try {
|
|
263
|
+
tracked = store.handle.prepare(`SELECT identity, MAX(since) AS latest FROM learning_watch
|
|
264
|
+
WHERE repo = ? AND ((reason = 'note' AND until IS NULL) OR (reason = 'observation' AND since >= ?))
|
|
265
|
+
GROUP BY identity ORDER BY latest DESC, identity`).all(repo, since).map(row => String(row["identity"])).filter(isExactCase);
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return { learn: [], leftOut: 0 };
|
|
269
|
+
}
|
|
270
|
+
const learn = learnRequest(tracked);
|
|
271
|
+
return { learn, leftOut: tracked.length - learn.length };
|
|
272
|
+
}
|
package/dist/check-report.d.ts
CHANGED
|
@@ -88,6 +88,8 @@ export type PreparedAttempt = {
|
|
|
88
88
|
snapshot: ObligationSnapshot | null;
|
|
89
89
|
/** The identities the check was asked to report one by one (required ones). */
|
|
90
90
|
watch: string[];
|
|
91
|
+
/** The exact cases the check was asked about for learning only (docs/plans/learning-loops.md): never gate evidence. */
|
|
92
|
+
learn: string[];
|
|
91
93
|
};
|
|
92
94
|
/** A Full check's preparation refused (eval-harness review 2, finding 4): the plane recorded a release whose commit it
|
|
93
95
|
* can't verify now, so the check's obligations can't be decided. The check isn't run (builder.ts: `baseline-unverifiable`). */
|
|
@@ -112,6 +114,9 @@ export declare function prepareCheckAttempt(store: Store, root: string, input: {
|
|
|
112
114
|
/** A release's check (signed release terms or a release authority): CI never stands in for its unit suite. Sealed in
|
|
113
115
|
* the inventory (whose digest the attempt row holds); only an explicit false lets CI stand in. */
|
|
114
116
|
release?: boolean;
|
|
117
|
+
/** Exact cases asked about for learning only (docs/plans/learning-loops.md): a separate handshake field, at most 200,
|
|
118
|
+
* that never joins `watch`, the inventory or anything the check owes. */
|
|
119
|
+
learn?: readonly string[];
|
|
115
120
|
}, now: Date): PreparedAttempt | null;
|
|
116
121
|
/** Seal one JSON payload as a structured-output artifact under its capture label. */
|
|
117
122
|
export declare function sealJson(store: Store, root: string, runId: number, name: string, value: unknown, capture: string, now: Date): number;
|
package/dist/check-report.js
CHANGED
|
@@ -21,7 +21,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
21
21
|
import { lstatSync, realpathSync } from "node:fs";
|
|
22
22
|
import { join } from "node:path";
|
|
23
23
|
import { readVerifiedCommit, verifiedBlobs, verifiedTreeEntries } from "./commit-extract.js";
|
|
24
|
-
import { CHECK_PARTS, CHECK_REPORT_MAX_BYTES, CHECK_REPORT_PREFIX, readCheckReport, } from "./contracts/release-check.js";
|
|
24
|
+
import { CHECK_PARTS, CHECK_REPORT_MAX_BYTES, CHECK_REPORT_PREFIX, learnRequest, readCheckReport, } from "./contracts/release-check.js";
|
|
25
25
|
import { readVerifiedArtifact, storeEvidence } from "./evidence.js";
|
|
26
26
|
import { releaseBaselineRecord } from "./release-card.js";
|
|
27
27
|
import { checkAttemptOf, recordCheckAttempt } from "./store.js";
|
|
@@ -219,8 +219,9 @@ export function prepareCheckAttempt(store, root, input, now) {
|
|
|
219
219
|
// The command's own limit rides along, so the check ends itself (logs and report printed) before the plane stops it.
|
|
220
220
|
// `reportBytes`: the largest report this plane reads back once sealed. A plane that doesn't say (0.9.77 and older) reads
|
|
221
221
|
// only 64 KiB, so the check gives it no larger report rather than one that poisons the receipt (scripts/release-check.mjs).
|
|
222
|
-
const
|
|
223
|
-
|
|
222
|
+
const learn = learnRequest(input.learn ?? []);
|
|
223
|
+
const stdin = JSON.stringify({ nonce, mode, plan, watch: [...(input.watch ?? [])], limitMs: input.command.timeoutMs, reportBytes: CHECK_ARTIFACT_CAP, ...(plan === null ? { owed, ciSubstitute } : {}), ...(learn.length > 0 ? { learn } : {}) });
|
|
224
|
+
return { runId: input.runId, nonce, stdin, mode, inventory, context, plan, snapshot, watch: [...(input.watch ?? [])], learn };
|
|
224
225
|
}
|
|
225
226
|
/** Seal one JSON payload as a structured-output artifact under its capture label. */
|
|
226
227
|
export function sealJson(store, root, runId, name, value, capture, now) {
|
package/dist/cli.js
CHANGED
|
@@ -113,6 +113,8 @@ export const OPERATE_COMMANDS = new Set([
|
|
|
113
113
|
"evals",
|
|
114
114
|
// Only `lead token` reaches here; the central service's lead verbs answer first.
|
|
115
115
|
"lead",
|
|
116
|
+
// Only `release approve` reaches here: every other `release` form is release-cli.ts, routed before this set.
|
|
117
|
+
"release",
|
|
116
118
|
"onboard",
|
|
117
119
|
"status",
|
|
118
120
|
"ready",
|
|
@@ -344,6 +346,9 @@ async function dispatch(argv, write, mainOptions) {
|
|
|
344
346
|
}
|
|
345
347
|
if (first === "update")
|
|
346
348
|
return (await import("./toolroll-update-cli.js")).runUpdateCommand(rest, write, mainOptions.update);
|
|
349
|
+
// `toolroll release approve`: a person's approval, with the operate commands' own credential prompt and context.
|
|
350
|
+
if (first === "release" && rest[0] === "approve")
|
|
351
|
+
return runOperate("release", rest, write, mainOptions.operate ?? {});
|
|
347
352
|
if (first === "release") {
|
|
348
353
|
const { runReleaseCommand } = await import("./release-cli.js");
|
|
349
354
|
return runReleaseCommand(rest, write, { releaseLease: lease => runOperate("worker", ["release", ...lease], write, mainOptions.operate ?? {}), ...mainOptions.release });
|
|
@@ -252,6 +252,14 @@ export declare const chatActionRequestSchemas: {
|
|
|
252
252
|
decision: z.ZodInt;
|
|
253
253
|
reason: z.ZodString;
|
|
254
254
|
}, z.core.$strict>;
|
|
255
|
+
readonly suggestion_not_now: z.ZodObject<{
|
|
256
|
+
repo: z.ZodString;
|
|
257
|
+
suggestion: z.ZodString;
|
|
258
|
+
}, z.core.$strict>;
|
|
259
|
+
readonly trial_stop: z.ZodObject<{
|
|
260
|
+
repo: z.ZodString;
|
|
261
|
+
trial: z.ZodInt;
|
|
262
|
+
}, z.core.$strict>;
|
|
255
263
|
/** The line, and (from 1) the line it replaces; 0 or none adds it. */
|
|
256
264
|
readonly lead_about_you: z.ZodObject<{
|
|
257
265
|
line: z.ZodString;
|
|
@@ -331,12 +339,14 @@ export declare const sharedActionSchema: z.ZodObject<{
|
|
|
331
339
|
subagent_state: "subagent_state";
|
|
332
340
|
subagent_tools: "subagent_tools";
|
|
333
341
|
subagent_undo: "subagent_undo";
|
|
342
|
+
suggestion_not_now: "suggestion_not_now";
|
|
334
343
|
sweep_cancel_rest: "sweep_cancel_rest";
|
|
335
344
|
sweep_file: "sweep_file";
|
|
336
345
|
task_cancel: "task_cancel";
|
|
337
346
|
task_resume: "task_resume";
|
|
338
347
|
tool_add: "tool_add";
|
|
339
348
|
tool_remove: "tool_remove";
|
|
349
|
+
trial_stop: "trial_stop";
|
|
340
350
|
}>;
|
|
341
351
|
request: z.ZodObject<{}, z.core.$catchall<z.ZodUnknown>>;
|
|
342
352
|
repo: z.ZodString;
|
|
@@ -81,6 +81,10 @@ export const chatActionRequestSchemas = {
|
|
|
81
81
|
kit_setup: z.strictObject({ repo: words, kit: words }),
|
|
82
82
|
decision_record: z.strictObject({ repo: words, claim: words, why: words, supersedes: id.optional(), source: words.optional() }),
|
|
83
83
|
decision_retire: z.strictObject({ repo: words, decision: id, reason: words }),
|
|
84
|
+
// Results routing: Not now on a suggested cheaper model (accepting stays in Settings → Models, behind a step-up).
|
|
85
|
+
suggestion_not_now: z.strictObject({ repo: words, suggestion: words }),
|
|
86
|
+
// Slice 3: stopping a trial (starting one stays in Settings → Models, behind a step-up).
|
|
87
|
+
trial_stop: z.strictObject({ repo: words, trial: id }),
|
|
84
88
|
/** The line, and (from 1) the line it replaces; 0 or none adds it. */
|
|
85
89
|
lead_about_you: z.strictObject({ line: words, replaces: z.int().min(0).optional() }),
|
|
86
90
|
scope_approve: z.strictObject({ task: words }),
|
|
@@ -112,6 +112,7 @@ export declare const CONSOLE_FORMS: {
|
|
|
112
112
|
readonly appearance: FormContract<"accent" | "quiet" | "return" | "theme">;
|
|
113
113
|
readonly models: FormContract<"enabled" | "tool">;
|
|
114
114
|
readonly modelsChange: FormContract<"build" | "build-light" | "build-strong" | "change" | "digest" | "password" | "plan" | "plan-strong" | "preset" | "repair" | "review" | "review-strong" | "scope" | "switch" | "to">;
|
|
115
|
+
readonly modelsRules: FormContract<"id" | "password" | "rule" | "scope">;
|
|
115
116
|
readonly knowledgeRefresh: FormContract<"repo">;
|
|
116
117
|
readonly knowledgeProposal: FormContract<"decision" | "proposal" | "repo">;
|
|
117
118
|
readonly knowledgeDecision: FormContract<"action" | "claim" | "decision" | "reason" | "repo" | "why">;
|
|
@@ -149,7 +150,7 @@ export declare const CONSOLE_FORMS: {
|
|
|
149
150
|
readonly peopleRevoke: FormContract<"name" | "token">;
|
|
150
151
|
readonly runnerRetire: FormContract<"name" | "token">;
|
|
151
152
|
readonly decisionAnswer: FormContract<"choice" | "confirm" | "note" | "return">;
|
|
152
|
-
readonly
|
|
153
|
+
readonly recheckStopped: FormContract<"return" | "run">;
|
|
153
154
|
readonly merge: FormContract<"anyway" | "run" | "token">;
|
|
154
155
|
readonly onboard: FormContract<"big-ok" | "nonce" | "repo" | "root" | "token">;
|
|
155
156
|
readonly pushSubscribe: FormContract<"auth" | "endpoint" | "p256dh" | "token">;
|
|
@@ -198,7 +199,7 @@ export declare const CONSOLE_FORMS: {
|
|
|
198
199
|
readonly taskCancel: FormContract<"reason">;
|
|
199
200
|
readonly taskRoute: FormContract<"agent" | "clear-phase" | "model" | "phase" | "provider" | "return" | "risky" | "sawDigest" | "size">;
|
|
200
201
|
readonly taskScope: FormContract<"acceptance" | "budget-microusd" | "budget-usd" | "goal" | "goal-brief" | "not" | "permission-mode" | "quality-mode" | "requirement" | "requirement-new" | "sawDigest" | "touches">;
|
|
201
|
-
readonly taskApprove: FormContract<"digest" | "nonce" | "rebuild" | "return" | "token">;
|
|
202
|
+
readonly taskApprove: FormContract<"digest" | "nonce" | "rebuild" | "return" | "sheet" | "token">;
|
|
202
203
|
readonly taskAcceptProof: FormContract<"note" | "return" | "run">;
|
|
203
204
|
readonly taskComplete: FormContract<"accept" | "note" | "publish" | "receipt" | "return" | "run">;
|
|
204
205
|
readonly taskStop: FormContract<"return" | "run">;
|
|
@@ -124,6 +124,9 @@ export const CONSOLE_FORMS = {
|
|
|
124
124
|
// preset or switch; the digest is what the page showed, and the change digest what the preview showed.
|
|
125
125
|
modelsChange: formContract("POST /settings/models/agent, /settings/models/apply", ["password", "scope", "digest", "change", "preset", "switch", "to",
|
|
126
126
|
"plan", "plan-strong", "build-light", "build", "build-strong", "repair", "review", "review-strong"]),
|
|
127
|
+
// Settings → Models → Suggested (results routing): the project, a suggestion's id or a rule's number, and the step-up
|
|
128
|
+
// password for accepting, keeping and switching suggestions on.
|
|
129
|
+
modelsRules: formContract("POST /settings/models/accept, /settings/models/keep, /settings/models/suggest-on, /settings/models/turn-off, /settings/models/not-now, /settings/models/suggest-off, /settings/models/try, /settings/models/stop", ["password", "scope", "id", "rule"]),
|
|
127
130
|
knowledgeRefresh: formContract("POST /settings/knowledge/refresh", ["repo"]),
|
|
128
131
|
knowledgeProposal: formContract("POST /settings/knowledge/proposal", ["repo", "decision", "proposal"]),
|
|
129
132
|
knowledgeDecision: formContract("POST /settings/knowledge/decision", ["repo", "action", "claim", "why", "decision", "reason"]),
|
|
@@ -162,7 +165,7 @@ export const CONSOLE_FORMS = {
|
|
|
162
165
|
peopleRevoke: formContract("POST /people/revoke", ["token", "name"]),
|
|
163
166
|
runnerRetire: formContract("POST /fleet/runner/retire", ["token", "name"]),
|
|
164
167
|
decisionAnswer: formContract("POST /d/<id>/answer", ["return", "choice", "confirm", "note"]),
|
|
165
|
-
|
|
168
|
+
recheckStopped: formContract("POST /t/<task>/recheck-stopped", ["run", "return"]),
|
|
166
169
|
merge: formContract("POST /t/<task>/merge", ["run", "token", "anyway"]),
|
|
167
170
|
onboard: formContract("POST /projects/onboard-preview, /projects/onboard-confirm", ["repo", "root", "token", "nonce", "big-ok"]),
|
|
168
171
|
pushSubscribe: formContract("POST /push/subscribe", ["token", "endpoint", "p256dh", "auth"]),
|
|
@@ -214,7 +217,7 @@ export const CONSOLE_FORMS = {
|
|
|
214
217
|
taskCancel: formContract("POST /t/<task>/cancel", ["reason"]),
|
|
215
218
|
taskRoute: formContract("POST /t/<task>/route", ["size", "risky", "phase", "clear-phase", "agent", "provider", "model", "sawDigest", "return"]),
|
|
216
219
|
taskScope: formContract("POST /t/<task>/scope", ["sawDigest", "permission-mode", "quality-mode", "budget-usd", "budget-microusd", "requirement", "requirement-new", "goal-brief", "goal", "not", "touches", "acceptance"]),
|
|
217
|
-
taskApprove: formContract("POST /t/<task>/approve", ["return", "digest", "token", "nonce", "rebuild"]),
|
|
220
|
+
taskApprove: formContract("POST /t/<task>/approve", ["return", "digest", "token", "nonce", "rebuild", "sheet"]),
|
|
218
221
|
taskAcceptProof: formContract("POST /t/<task>/accept-proof", ["run", "note", "return"]),
|
|
219
222
|
taskComplete: formContract("POST /t/<task>/complete", ["receipt", "run", "accept", "note", "publish", "return"]),
|
|
220
223
|
taskStop: formContract("POST /t/<task>/stop", ["run", "return"]),
|
|
@@ -233,7 +233,9 @@ export declare const obligationSnapshotSchema: z.ZodObject<{
|
|
|
233
233
|
}, z.core.$strict>>;
|
|
234
234
|
}, z.core.$strict>;
|
|
235
235
|
export type ObligationSnapshot = z.infer<typeof obligationSnapshotSchema>;
|
|
236
|
-
/** What the plane writes on the check's stdin before closing it.
|
|
236
|
+
/** What the plane writes on the check's stdin before closing it. `learn` (docs/plans/learning-loops.md) is advisory and
|
|
237
|
+
* apart from everything else: exact case identities the check is asked to say it ran, on their own line
|
|
238
|
+
* (LEARN_PREFIX), never in the report and never part of what the check owes. */
|
|
237
239
|
export type CheckHandshake = {
|
|
238
240
|
nonce: string;
|
|
239
241
|
mode: "full" | "targeted";
|
|
@@ -241,7 +243,29 @@ export type CheckHandshake = {
|
|
|
241
243
|
watch: string[];
|
|
242
244
|
owed?: CheckPart[];
|
|
243
245
|
ciSubstitute?: boolean;
|
|
246
|
+
learn?: string[];
|
|
244
247
|
};
|
|
248
|
+
/** The advisory learning line a check prints before its report: `TOOLROLL-LEARN <json>`, at most 64 KiB, holding only
|
|
249
|
+
* the requested identities that ran. Never part of the report: buildReport, reportLine and fullCheckProblem don't read
|
|
250
|
+
* it, and a missing, malformed or oversized one loses learning evidence only. */
|
|
251
|
+
export declare const LEARN_PREFIX = "TOOLROLL-LEARN ";
|
|
252
|
+
export declare const LEARN_MAX_BYTES: number;
|
|
253
|
+
export declare const LEARN_MAX_IDENTITIES = 200;
|
|
254
|
+
export declare const isExactCase: (identity: unknown) => identity is string;
|
|
255
|
+
/** What the plane asks a check to report for learning: exact cases only, first seen first, at most 200. */
|
|
256
|
+
export declare function learnRequest(identities: readonly unknown[]): string[];
|
|
257
|
+
export declare const learnLineSchema: z.ZodObject<{
|
|
258
|
+
nonce: z.ZodString;
|
|
259
|
+
results: z.ZodArray<z.ZodObject<{
|
|
260
|
+
identity: z.ZodString;
|
|
261
|
+
state: z.ZodEnum<{
|
|
262
|
+
failed: "failed";
|
|
263
|
+
passed: "passed";
|
|
264
|
+
skipped: "skipped";
|
|
265
|
+
}>;
|
|
266
|
+
}, z.core.$strict>>;
|
|
267
|
+
}, z.core.$strict>;
|
|
268
|
+
export type LearnLine = z.infer<typeof learnLineSchema>;
|
|
245
269
|
/** A unit test: its file and full name. */
|
|
246
270
|
export declare const unitIdentity: (file: string, testName: string) => string;
|
|
247
271
|
/** A unit file. */
|
|
@@ -146,6 +146,25 @@ export const obligationSnapshotSchema = versioned(1, {
|
|
|
146
146
|
base: z.int().min(1),
|
|
147
147
|
earlier: z.array(z.strictObject({ run: z.int().min(1), reportDigest: z.string().max(64).nullable(), failed: z.array(z.string().min(1).max(2600)).max(5000) })).max(200),
|
|
148
148
|
});
|
|
149
|
+
// ------------------------------------------------------------------ advisory learning lines (docs/plans/learning-loops.md)
|
|
150
|
+
/** The advisory learning line a check prints before its report: `TOOLROLL-LEARN <json>`, at most 64 KiB, holding only
|
|
151
|
+
* the requested identities that ran. Never part of the report: buildReport, reportLine and fullCheckProblem don't read
|
|
152
|
+
* it, and a missing, malformed or oversized one loses learning evidence only. */
|
|
153
|
+
export const LEARN_PREFIX = "TOOLROLL-LEARN ";
|
|
154
|
+
export const LEARN_MAX_BYTES = 64 * 1024;
|
|
155
|
+
export const LEARN_MAX_IDENTITIES = 200;
|
|
156
|
+
/** An exact case: a unit test (`unit:<file>::<name>`) or a journey (`journey:<script>:<kind>:<group>::<name>`), never a
|
|
157
|
+
* file, group, part or version; one line, no control characters, at most 2600 characters. */
|
|
158
|
+
const EXACT_CASE = /^(?:unit:[^\u0000-\u001f\\:][^\u0000-\u001f\\]*?::[^\u0000-\u001f]+|journey:(?:app|flows):(?:scripted|real):[A-Za-z0-9._/-]{1,200}::[^\u0000-\u001f]+)$/;
|
|
159
|
+
export const isExactCase = (identity) => typeof identity === "string" && identity.length <= 2600 && EXACT_CASE.test(identity);
|
|
160
|
+
/** What the plane asks a check to report for learning: exact cases only, first seen first, at most 200. */
|
|
161
|
+
export function learnRequest(identities) {
|
|
162
|
+
return [...new Set(identities.filter(isExactCase))].slice(0, LEARN_MAX_IDENTITIES);
|
|
163
|
+
}
|
|
164
|
+
export const learnLineSchema = z.strictObject({
|
|
165
|
+
nonce: hex64,
|
|
166
|
+
results: z.array(z.strictObject({ identity: z.string().min(1).max(2600), state: z.enum(["passed", "failed", "skipped"]) })).max(LEARN_MAX_IDENTITIES),
|
|
167
|
+
});
|
|
149
168
|
// ------------------------------------------------------------------ identities
|
|
150
169
|
/** A unit test: its file and full name. */
|
|
151
170
|
export const unitIdentity = (file, testName) => `unit:${file}::${testName}`;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result records (docs/plans/results-routing.md, slice 1): the words and shapes a task family's result record is kept
|
|
3
|
+
* and shown in. A policy path (release-auto.ts EVAL_POLICY): the states, reasons and the execution-class table decide
|
|
4
|
+
* what a cheaper model's evidence is, so a change to them needs a person.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
/** A record's lifecycle. Only mature and rejected records are decided; pending and provisional never count anywhere. */
|
|
8
|
+
export declare const RECORD_STATES: readonly ["pending", "provisional", "mature", "rejected", "not-counted"];
|
|
9
|
+
export type RecordState = (typeof RECORD_STATES)[number];
|
|
10
|
+
/** Why a record was rejected, in the order they are checked. */
|
|
11
|
+
export declare const REJECT_REASONS: readonly ["model-failures", "task-failed", "cancelled-after-result", "high-finding", "person-revision", "proof-refuted", "check-failed"];
|
|
12
|
+
/** Why a record is not counted. Never counted as good, never as 0. */
|
|
13
|
+
export declare const NOT_COUNTED_REASONS: readonly ["before-records", "not-eligible", "mixed-cohort", "mixed-models", "model-not-reported", "infrastructure-only", "cancelled-before-result", "no-check", "check-unavailable", "touched-protected", "changed-files-unknown", "unanswered"];
|
|
14
|
+
export type RejectReason = (typeof REJECT_REASONS)[number];
|
|
15
|
+
export type NotCountedReason = (typeof NOT_COUNTED_REASONS)[number];
|
|
16
|
+
export type RecordReason = RejectReason | NotCountedReason;
|
|
17
|
+
/** The plain words for each reason. */
|
|
18
|
+
export declare const REASON_WORDS: Readonly<Record<RecordReason, string>>;
|
|
19
|
+
/** Why a run was refused a cohort before dispatch: any of these makes its record not counted ("not eligible"). */
|
|
20
|
+
export declare const INELIGIBLE_REASONS: readonly ["no-sealed-route", "not-built-in", "large", "risky", "unsized", "strict", "evidence-demands", "automerge", "release", "protected"];
|
|
21
|
+
export type IneligibleReason = (typeof INELIGIBLE_REASONS)[number];
|
|
22
|
+
/** Who a revision came from (task_ref.revision_origin). Only `person` rejects a record. */
|
|
23
|
+
export declare const REVISION_ORIGINS: readonly ["person", "automatic-review", "flow", "lead", "machine"];
|
|
24
|
+
export type RevisionOrigin = (typeof REVISION_ORIGINS)[number];
|
|
25
|
+
/** A failed run's execution class, kept before the tick normalizes its reason (run.exec_class). */
|
|
26
|
+
export declare const EXEC_CLASSES: readonly ["model", "infrastructure"];
|
|
27
|
+
export type ExecClass = (typeof EXEC_CLASSES)[number];
|
|
28
|
+
/** The build leg's sealed selection, as a run's cohort keeps it (route v3 `selection`; earlier routes by `chosen`). */
|
|
29
|
+
export declare const selectionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
30
|
+
kind: z.ZodLiteral<"rule">;
|
|
31
|
+
rule: z.ZodNumber;
|
|
32
|
+
ruleGeneration: z.ZodNumber;
|
|
33
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
34
|
+
kind: z.ZodLiteral<"trial">;
|
|
35
|
+
rule: z.ZodNumber;
|
|
36
|
+
reservation: z.ZodNumber;
|
|
37
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
38
|
+
kind: z.ZodEnum<{
|
|
39
|
+
configured: "configured";
|
|
40
|
+
override: "override";
|
|
41
|
+
pin: "pin";
|
|
42
|
+
}>;
|
|
43
|
+
}, z.core.$strict>], "kind">;
|
|
44
|
+
export type Selection = z.infer<typeof selectionSchema>;
|
|
45
|
+
/** One record as `toolroll models results --json` returns it. */
|
|
46
|
+
export declare const resultRecordSchema: z.ZodObject<{
|
|
47
|
+
task: z.ZodString;
|
|
48
|
+
repo: z.ZodNullable<z.ZodString>;
|
|
49
|
+
size: z.ZodNullable<z.ZodEnum<{
|
|
50
|
+
medium: "medium";
|
|
51
|
+
small: "small";
|
|
52
|
+
}>>;
|
|
53
|
+
provider: z.ZodNullable<z.ZodString>;
|
|
54
|
+
model: z.ZodNullable<z.ZodString>;
|
|
55
|
+
state: z.ZodEnum<{
|
|
56
|
+
mature: "mature";
|
|
57
|
+
"not-counted": "not-counted";
|
|
58
|
+
pending: "pending";
|
|
59
|
+
provisional: "provisional";
|
|
60
|
+
rejected: "rejected";
|
|
61
|
+
}>;
|
|
62
|
+
reason: z.ZodNullable<z.ZodEnum<{
|
|
63
|
+
"before-records": "before-records";
|
|
64
|
+
"cancelled-after-result": "cancelled-after-result";
|
|
65
|
+
"cancelled-before-result": "cancelled-before-result";
|
|
66
|
+
"changed-files-unknown": "changed-files-unknown";
|
|
67
|
+
"check-failed": "check-failed";
|
|
68
|
+
"check-unavailable": "check-unavailable";
|
|
69
|
+
"high-finding": "high-finding";
|
|
70
|
+
"infrastructure-only": "infrastructure-only";
|
|
71
|
+
"mixed-cohort": "mixed-cohort";
|
|
72
|
+
"mixed-models": "mixed-models";
|
|
73
|
+
"model-failures": "model-failures";
|
|
74
|
+
"model-not-reported": "model-not-reported";
|
|
75
|
+
"no-check": "no-check";
|
|
76
|
+
"not-eligible": "not-eligible";
|
|
77
|
+
"person-revision": "person-revision";
|
|
78
|
+
"proof-refuted": "proof-refuted";
|
|
79
|
+
"task-failed": "task-failed";
|
|
80
|
+
"touched-protected": "touched-protected";
|
|
81
|
+
unanswered: "unanswered";
|
|
82
|
+
}>>;
|
|
83
|
+
words: z.ZodString;
|
|
84
|
+
resultRun: z.ZodNullable<z.ZodNumber>;
|
|
85
|
+
acceptedAt: z.ZodNullable<z.ZodString>;
|
|
86
|
+
decidedAt: z.ZodNullable<z.ZodString>;
|
|
87
|
+
failures: z.ZodObject<{
|
|
88
|
+
model: z.ZodNumber;
|
|
89
|
+
infrastructure: z.ZodNumber;
|
|
90
|
+
}, z.core.$strict>;
|
|
91
|
+
spend: z.ZodObject<{
|
|
92
|
+
apiMicrousd: z.ZodNumber;
|
|
93
|
+
subscriptionRuns: z.ZodNumber;
|
|
94
|
+
subscriptionTokens: z.ZodNumber;
|
|
95
|
+
unknownRuns: z.ZodNumber;
|
|
96
|
+
remediationRuns: z.ZodNumber;
|
|
97
|
+
}, z.core.$strict>;
|
|
98
|
+
personRevisions: z.ZodNumber;
|
|
99
|
+
firstCheckPassed: z.ZodNullable<z.ZodBoolean>;
|
|
100
|
+
turns: z.ZodObject<{
|
|
101
|
+
known: z.ZodNumber;
|
|
102
|
+
unknownRuns: z.ZodNumber;
|
|
103
|
+
}, z.core.$strict>;
|
|
104
|
+
closed: z.ZodBoolean;
|
|
105
|
+
selections: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
106
|
+
kind: z.ZodLiteral<"rule">;
|
|
107
|
+
rule: z.ZodNumber;
|
|
108
|
+
ruleGeneration: z.ZodNumber;
|
|
109
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
110
|
+
kind: z.ZodLiteral<"trial">;
|
|
111
|
+
rule: z.ZodNumber;
|
|
112
|
+
reservation: z.ZodNumber;
|
|
113
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
114
|
+
kind: z.ZodEnum<{
|
|
115
|
+
configured: "configured";
|
|
116
|
+
override: "override";
|
|
117
|
+
pin: "pin";
|
|
118
|
+
}>;
|
|
119
|
+
}, z.core.$strict>], "kind">>;
|
|
120
|
+
}, z.core.$strict>;
|
|
121
|
+
export type ResultRecordView = z.infer<typeof resultRecordSchema>;
|