toolroll 0.9.90 → 0.9.92
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/build-review.js +2 -0
- 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/contracts/chat-actions.d.ts +10 -0
- package/dist/contracts/chat-actions.js +4 -0
- package/dist/contracts/console-api.d.ts +1 -0
- package/dist/contracts/console-api.js +3 -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/dispose.js +6 -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/operate-remote-arguments.js +4 -4
- package/dist/operate-remote.js +1 -1
- package/dist/operate.js +53 -5
- 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 +12 -4
- package/dist/project-knowledge.d.ts +6 -6
- 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/release-adapters.d.ts +2 -0
- package/dist/release-adapters.js +30 -1
- package/dist/release-auto.js +12 -3
- package/dist/result-actions.d.ts +4 -0
- package/dist/result-actions.js +3 -0
- 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/render-chat.js +2 -2
- package/dist/server/render-tasks.d.ts +7 -0
- package/dist/server/render-tasks.js +13 -0
- package/dist/server/route-table.js +12 -0
- 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.js +3 -0
- package/dist/settings-proofs.js +2 -0
- package/dist/setup-guide.d.ts +2 -2
- package/dist/spend-ui.d.ts +11 -0
- package/dist/spend-ui.js +49 -2
- package/dist/store.d.ts +38 -2
- package/dist/store.js +320 -35
- package/dist/surface.js +16 -4
- package/dist/task-outcome-cli.js +5 -2
- package/dist/toolroll-update.js +3 -2
- package/package.json +1 -1
package/dist/project-memory.d.ts
CHANGED
|
@@ -7,6 +7,14 @@ export declare function listDecisions(store: Store, repo: string, actor: string,
|
|
|
7
7
|
limit?: number;
|
|
8
8
|
}): Decision[];
|
|
9
9
|
export declare function getDecision(store: Store, repo: string, actor: string, id: number): Decision | null;
|
|
10
|
+
/**
|
|
11
|
+
* Project memory is changed by a person (like `knowledge apply`, project-knowledge.ts applySavedKnowledge): never in the
|
|
12
|
+
* lead's chain and never inside a remote run (an API token), whatever credential reached here. The lead proposes (chat
|
|
13
|
+
* cards decision_record/decision_retire) and a person confirms. Every writer here and in memory-pass.ts calls this first,
|
|
14
|
+
* and again inside its transaction beside the person's standing (`admission`), so a revocation made since the caller
|
|
15
|
+
* proved them is seen by the write.
|
|
16
|
+
*/
|
|
17
|
+
export declare function refuseLeadMemoryWrite(): void;
|
|
10
18
|
/** Record one settled choice. A superseding decision retires the older one in the same transaction. */
|
|
11
19
|
export declare function recordDecision(store: Store, args: {
|
|
12
20
|
repo: string;
|
package/dist/project-memory.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* model; the backward pass that proposes edits lives in memory-pass.ts.
|
|
9
9
|
*/
|
|
10
10
|
import { createHash } from 'node:crypto';
|
|
11
|
+
import { currentActor } from './actor.js';
|
|
12
|
+
import { activeRemote } from './remote-run.js';
|
|
11
13
|
import { scanForSecrets } from './evidence.js';
|
|
12
14
|
import { learningIdentity } from './project-learning.js';
|
|
13
15
|
import { TEXT_LIMITS } from './text-limits.js';
|
|
@@ -112,8 +114,22 @@ export function getDecision(store, repo, actor, id) {
|
|
|
112
114
|
throw Error('This decision could not be verified.');
|
|
113
115
|
return d;
|
|
114
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Project memory is changed by a person (like `knowledge apply`, project-knowledge.ts applySavedKnowledge): never in the
|
|
119
|
+
* lead's chain and never inside a remote run (an API token), whatever credential reached here. The lead proposes (chat
|
|
120
|
+
* cards decision_record/decision_retire) and a person confirms. Every writer here and in memory-pass.ts calls this first,
|
|
121
|
+
* and again inside its transaction beside the person's standing (`admission`), so a revocation made since the caller
|
|
122
|
+
* proved them is seen by the write.
|
|
123
|
+
*/
|
|
124
|
+
export function refuseLeadMemoryWrite() {
|
|
125
|
+
if (currentActor()?.lead === true)
|
|
126
|
+
throw Error('Project memory is changed by a person, not the lead.');
|
|
127
|
+
if (activeRemote() !== null)
|
|
128
|
+
throw Error('Project memory is changed by a person with their own password, not an API token.');
|
|
129
|
+
}
|
|
115
130
|
/** Record one settled choice. A superseding decision retires the older one in the same transaction. */
|
|
116
131
|
export function recordDecision(store, args, now = new Date()) {
|
|
132
|
+
refuseLeadMemoryWrite();
|
|
117
133
|
const identity = admission(store, args.repo, args.actor, true);
|
|
118
134
|
const claim = clean(args.draft.claim, TEXT_LIMITS.decisionClaimBytes, 'The decision'), why = clean(args.draft.why, TEXT_LIMITS.decisionWhyBytes, 'The reason');
|
|
119
135
|
const decidedBy = args.draft.decidedBy === undefined ? args.actor : clean(args.draft.decidedBy, TEXT_LIMITS.decisionByBytes, 'Who decided');
|
|
@@ -123,6 +139,8 @@ export function recordDecision(store, args, now = new Date()) {
|
|
|
123
139
|
const sourceKind = args.draft.sourceKind ?? 'manual';
|
|
124
140
|
const sourceRef = args.draft.sourceRef === undefined || args.draft.sourceRef === null ? null : clean(String(args.draft.sourceRef), TEXT_LIMITS.decisionSourceBytes, 'The source');
|
|
125
141
|
return store.transact(() => {
|
|
142
|
+
refuseLeadMemoryWrite();
|
|
143
|
+
admission(store, args.repo, args.actor, true);
|
|
126
144
|
let supersedes = null;
|
|
127
145
|
if (args.draft.supersedes !== undefined && args.draft.supersedes !== null) {
|
|
128
146
|
const older = getDecision(store, args.repo, args.actor, Number(args.draft.supersedes));
|
|
@@ -155,9 +173,10 @@ function changeStatus(store, repo, actor, id, status, reason, now) {
|
|
|
155
173
|
}
|
|
156
174
|
/** Retire a decision that no longer holds; history keeps it. */
|
|
157
175
|
export function retireDecision(store, args, now = new Date()) {
|
|
176
|
+
refuseLeadMemoryWrite();
|
|
158
177
|
admission(store, args.repo, args.actor, true);
|
|
159
178
|
const reason = clean(args.reason, TEXT_LIMITS.decisionRetireBytes, 'The reason');
|
|
160
|
-
return store.transact(() => { const d = changeStatus(store, args.repo, args.actor, args.id, 'retired', reason, now); reindexProjectMemory(store, args.repo); return d; });
|
|
179
|
+
return store.transact(() => { refuseLeadMemoryWrite(); admission(store, args.repo, args.actor, true); const d = changeStatus(store, args.repo, args.actor, args.id, 'retired', reason, now); reindexProjectMemory(store, args.repo); return d; });
|
|
161
180
|
}
|
|
162
181
|
export function decisionHistory(store, repo, actor, id) {
|
|
163
182
|
admission(store, repo, actor);
|
|
@@ -319,7 +319,7 @@ export async function followPullRequests(store, seen, options) {
|
|
|
319
319
|
const filed = store.transact(() => {
|
|
320
320
|
const revised = requestResultChanges(store, options.evidenceRoot, {
|
|
321
321
|
run: publication.run, batch: "", source: revisionSourceOf(scope?.digest ?? null), actor: CI_ACTOR, repos: null, includeUnplaced: true,
|
|
322
|
-
allowMode: false, note, path: "", line: "", request,
|
|
322
|
+
allowMode: false, note, path: "", line: "", request, origin: "flow",
|
|
323
323
|
}, now);
|
|
324
324
|
if (!revised.ok)
|
|
325
325
|
return revised;
|
|
@@ -62,6 +62,8 @@ export type AdapterOptions = {
|
|
|
62
62
|
formula?: string;
|
|
63
63
|
/** How long the deployment itself may run, in minutes. */
|
|
64
64
|
deployMinutes?: number;
|
|
65
|
+
/** The Toolroll CLI the release calls, as [program, ...leading args]; by default this runtime's own bin.js. */
|
|
66
|
+
cli?: readonly string[];
|
|
65
67
|
run?: Exec;
|
|
66
68
|
fetch?: typeof fetch;
|
|
67
69
|
say?: (line: string) => void;
|
package/dist/release-adapters.js
CHANGED
|
@@ -10,6 +10,7 @@ import { existsSync, lstatSync, mkdtempSync, readdirSync, readFileSync, realpath
|
|
|
10
10
|
import { homedir, tmpdir } from "node:os";
|
|
11
11
|
import { basename, delimiter, dirname, join } from "node:path";
|
|
12
12
|
import { createRequire } from "node:module";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
13
14
|
import { parse as parseYaml } from "yaml";
|
|
14
15
|
import { sleep } from "./sleep.js";
|
|
15
16
|
import { callLabel, readOnlyCall, retryCall, retryingExec, writeWithRecheck } from "./release-net.js";
|
|
@@ -50,6 +51,13 @@ export const exec = (command, args, options = {}) => new Promise(done => {
|
|
|
50
51
|
child.stdin?.end();
|
|
51
52
|
});
|
|
52
53
|
/** Without NODE_OPTIONS, which a test runner or debugger may have left for this process. */
|
|
54
|
+
/** This runtime's own CLI (dist/bin.js beside this module) when it has one; from source, `toolroll` as installed. */
|
|
55
|
+
const ownCli = () => {
|
|
56
|
+
const bin = join(dirname(fileURLToPath(import.meta.url)), "bin.js");
|
|
57
|
+
return existsSync(bin) ? [process.execPath, bin] : ["toolroll"];
|
|
58
|
+
};
|
|
59
|
+
/** Journal phases the deployment's finish accepts (scripts/deploy-phases.mjs finish): the new service runs, unproven. */
|
|
60
|
+
const FINISHES_FORWARD = Object.freeze(["started", "healthy"]);
|
|
53
61
|
const cleanEnv = (extra = {}) => {
|
|
54
62
|
const env = { ...process.env, ...extra };
|
|
55
63
|
delete env["NODE_OPTIONS"];
|
|
@@ -244,8 +252,11 @@ export function releaseAdapters(options) {
|
|
|
244
252
|
/** A write by gh, never retried by itself. */
|
|
245
253
|
const ghWrite = (remainingMs, ...args) => run(tools.gh, args, { cwd: "/", env: gitEnv, timeoutMs: Math.min(120_000, remainingMs) });
|
|
246
254
|
const ghJson = async (what, ...args) => JSON.parse(await must(gh(...args), what));
|
|
255
|
+
// The build running this release, never a PATH lookup: until the link step, `toolroll` on PATH is still the previous
|
|
256
|
+
// build, which refuses a database the deployment migrated, so a release resumed after its deploy couldn't read its task.
|
|
257
|
+
const cli = options.cli ?? ownCli();
|
|
247
258
|
const toolroll = async (...args) => {
|
|
248
|
-
const result = await run(
|
|
259
|
+
const result = await run(cli[0], [...cli.slice(1), ...args, "--json"], { env: cleanEnv(), timeoutMs: 120_000 });
|
|
249
260
|
try {
|
|
250
261
|
return object(JSON.parse(result.stdout));
|
|
251
262
|
}
|
|
@@ -562,6 +573,24 @@ export function releaseAdapters(options) {
|
|
|
562
573
|
const running = (options.processes ?? processes)().filter(one => one.command.includes("deploy-browser.mjs") && one.command.includes(stage));
|
|
563
574
|
if (running.length > 0)
|
|
564
575
|
return { ok: false, message: `the deployment is still running (process ${running.map(one => one.pid).join(", ")}); let it finish, then rerun` };
|
|
576
|
+
// The new service started (deploy-rescue.ts FORWARD_ONLY): recovery refuses to roll it back, so the release
|
|
577
|
+
// resumes it forward with the deployment's own finish (health, both consoles, leases, then the pause lifted).
|
|
578
|
+
// Finishing deploys, so the candidate is proven first, as for a deploy.
|
|
579
|
+
const phase = (() => { try {
|
|
580
|
+
return object(JSON.parse(readFileSync(join(stage, "deployment.json"), "utf8")))["phase"];
|
|
581
|
+
}
|
|
582
|
+
catch {
|
|
583
|
+
return undefined;
|
|
584
|
+
} })();
|
|
585
|
+
if (FINISHES_FORWARD.includes(String(phase))) {
|
|
586
|
+
const proven = proveCandidate(runId);
|
|
587
|
+
if (!proven.ok)
|
|
588
|
+
return stopped(stage, proven.message);
|
|
589
|
+
const finished = await deployScript(worktree, stage, runId, proven, ["--yes", "--phase", "finish"]);
|
|
590
|
+
if (finished.code === 0)
|
|
591
|
+
return { ok: true };
|
|
592
|
+
return stopped(stage, `${tail(finished)} (the deployment journal is at ${String(phase)}; finishing it forward stopped; ${join(stage, "deployment.json")})`);
|
|
593
|
+
}
|
|
565
594
|
// Recovery needs no approval, but it runs only the recorded commit's own script, extracted by this runtime.
|
|
566
595
|
const recorded = recordedCommit(runId);
|
|
567
596
|
if (recorded === null)
|
package/dist/release-auto.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* Git directory. No command or console road reaches these functions: `toolroll release` calls them through its store
|
|
13
13
|
* adapter. Any refusal unseals `auto-check`, so the task asks a person, its passing check kept.
|
|
14
14
|
*/
|
|
15
|
+
import { ROUTING_HOOK_FILES, routingHookProblem } from "./route-hooks.js";
|
|
15
16
|
import { assignmentOf } from "./assignment.js";
|
|
16
17
|
import { fullCheckProblem, projectGitDir } from "./check-report.js";
|
|
17
18
|
import { sealedRun } from "./release-recheck.js";
|
|
@@ -182,8 +183,10 @@ const TEST_FILE = /^(?!.*(^|\/)fixtures?\/)[^]*\.test\.ts$/;
|
|
|
182
183
|
/** The eval set's and GitHub nightly's policy code (docs/plans/evals-nightly.md, part C, finding 10): the cases and
|
|
183
184
|
* oracles, the evaluator, its gate, hooks and world, reviewer scoring, nightly ingestion, their contracts, and the
|
|
184
185
|
* accounting readings evals trust (reported-usage.ts, run-efficiency.ts). Where they are called from, and the store's
|
|
185
|
-
* accounting reads, are held by eval-hooks.ts instead (evalHookProblem).
|
|
186
|
-
|
|
186
|
+
* accounting reads, are held by eval-hooks.ts instead (evalHookProblem). Results routing's policy is here too
|
|
187
|
+
* (docs/plans/results-routing.md, Protection 1): the record evaluator, the rules, the one writer module, this scan's
|
|
188
|
+
* manifest and the record contract; their callers and pinned bodies are route-hooks.ts's (routingHookProblem). */
|
|
189
|
+
const EVAL_POLICY = /^(evals\/|src\/evals[^/]*\.ts$|src\/eval-[^/]*\.ts$|src\/reviewer-evals\.ts$|src\/nightly-github\.ts$|src\/contracts\/evals[^/]*\.ts$|src\/reported-usage\.ts$|src\/run-efficiency\.ts$|src\/route-(?:results|rules|store|hooks)\.ts$|src\/contracts\/route-results\.ts$)/;
|
|
187
190
|
/** Build or type configuration anywhere: a person approves it. */
|
|
188
191
|
const BUILD_CONFIG = /(^|\/)(tsconfig[^/]*\.json|[^/]*\.config\.[cm]?[jt]s|\.npmrc|\.nvmrc|\.node-version)$/;
|
|
189
192
|
/**
|
|
@@ -209,7 +212,7 @@ export function autoPathProblem(path) {
|
|
|
209
212
|
// Test files under test/ carry the same trust as src's (Alex: test authors are trusted) — only the `.test.ts` files
|
|
210
213
|
// themselves, never a helper, fixture, setup or config file beside them, nor one inside a fixtures folder.
|
|
211
214
|
if (EVAL_POLICY.test(normal))
|
|
212
|
-
return "it's eval or
|
|
215
|
+
return "it's eval, nightly or model routing policy code";
|
|
213
216
|
if (testUnderTest)
|
|
214
217
|
return null;
|
|
215
218
|
if (BUILD_CONFIG.test(normal))
|
|
@@ -334,6 +337,12 @@ function changeProblem(repo, base, candidate) {
|
|
|
334
337
|
const rewrites = storeRewriteProblem(changed, changedAt(after.value.tree), changedAt(before.value.tree));
|
|
335
338
|
if (rewrites !== null)
|
|
336
339
|
return `${rewrites}: a person decides`;
|
|
340
|
+
// Results routing (route-hooks.ts): its required calls and pinned bodies the base holds, and no changed file writing
|
|
341
|
+
// its rows (or the Models rows) from anywhere but their writers.
|
|
342
|
+
const routingAt = (tree) => { const blobs = verifiedBlobs(gitDir, tree, ROUTING_HOOK_FILES); return (file) => blobs.ok ? blobs.value.get(file)?.bytes ?? null : null; };
|
|
343
|
+
const routing = routingHookProblem({ candidate: routingAt(after.value.tree), base: routingAt(before.value.tree) }, { paths: changed, candidate: changedAt(after.value.tree), base: changedAt(before.value.tree) });
|
|
344
|
+
if (routing !== null)
|
|
345
|
+
return `${routing}: a person decides`;
|
|
337
346
|
for (const change of diff.value) {
|
|
338
347
|
const modes = [change.before?.mode, change.after?.mode].filter((one) => one !== undefined);
|
|
339
348
|
if (modes.includes("120000") || modes.includes("160000"))
|
package/dist/result-actions.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Store } from "./store.js";
|
|
2
2
|
import { type FeedbackImage } from "./result-review.js";
|
|
3
|
+
import type { RevisionOrigin } from "./contracts/route-results.js";
|
|
3
4
|
export type RevisionResult = {
|
|
4
5
|
ok: true;
|
|
5
6
|
id: string;
|
|
@@ -28,5 +29,8 @@ export declare function createResultRevision(store: Store, evidenceRoot: string,
|
|
|
28
29
|
repos: readonly string[] | null;
|
|
29
30
|
includeUnplaced?: boolean;
|
|
30
31
|
allowMode?: boolean;
|
|
32
|
+
/** Who this revision comes from (results routing): each caller says it from its own trusted context, never from
|
|
33
|
+
* `allowMode` or the actor's name. Only `person` counts against the model that built the result. */
|
|
34
|
+
origin: RevisionOrigin;
|
|
31
35
|
}, now: Date): RevisionResult;
|
|
32
36
|
export {};
|
package/dist/result-actions.js
CHANGED
|
@@ -9,6 +9,7 @@ import { parseFeedbackImage, sameFeedbackImage, isRevisionFeedback, parseRevisio
|
|
|
9
9
|
import { validateNote, hasForbiddenControls } from "./decision.js";
|
|
10
10
|
import { TASK_TEXT_LIMITS, validateTaskText } from "./task-text.js";
|
|
11
11
|
import { modeFilingCoverage } from "./scope.js";
|
|
12
|
+
import { recordRevisionOrigin } from "./route-store.js";
|
|
12
13
|
/** Re-prove image custody and current result at each write. No file paths supplied by callers. */
|
|
13
14
|
export function feedbackImageProblem(store, root, runId, image) {
|
|
14
15
|
if (image == null)
|
|
@@ -257,6 +258,8 @@ export function createResultRevision(store, evidenceRoot, input, now) {
|
|
|
257
258
|
// and collision handling. Set only this new row's display title,
|
|
258
259
|
// atomically with its seal; retries returned earlier, untouched.
|
|
259
260
|
store.raw().prepare("UPDATE task SET title = ? WHERE id = ?").run(title, result.id);
|
|
261
|
+
// Who it comes from, with its seal (route-store.ts: written once, on a revision only).
|
|
262
|
+
recordRevisionOrigin(store, result.id, input.origin);
|
|
260
263
|
}
|
|
261
264
|
// Planned afresh: the planner reads the copied terms and these notes, and a note asking for
|
|
262
265
|
// more than the old plan allows becomes an amendment the person approves before anything
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Results routing's protection (docs/plans/results-routing.md, "Protection", items 2 and 4): what a release can't
|
|
3
|
+
* change without a person, checked by the INSTALLED plane against a candidate (release-auto.ts changeProblem), so a
|
|
4
|
+
* candidate can't edit the check that judges it. This file is itself a policy path.
|
|
5
|
+
*
|
|
6
|
+
* 1. Required calls and pinned bodies (eval-hooks.ts's machinery, its own manifest): the calls that feed the records
|
|
7
|
+
* (the raw execution class, every revision's origin, the re-evaluation pass, the project deletion's Models
|
|
8
|
+
* counter) and the functions whose bodies decide what a record says or what a route is — a pinned body can't gain
|
|
9
|
+
* an `if (false)` without changing. Each revision caller's arguments are pinned too, so the origin it passes can't
|
|
10
|
+
* change by itself.
|
|
11
|
+
* 2. The SQL scan: a changed file whose string or template text writes a results-routing table, run.exec_class,
|
|
12
|
+
* task_ref.revision_origin or a Models (phase_*) table, from anywhere but route-store.ts or the named store.ts
|
|
13
|
+
* writers, needs a person — unless the base's copy of that file already had exactly that text. It reads text the
|
|
14
|
+
* candidate wrote, never runs it; SQL assembled from pieces (a table name in a variable) isn't seen, which the
|
|
15
|
+
* pinned writers and a person's review of these files cover.
|
|
16
|
+
*/
|
|
17
|
+
import type ts from "typescript-parser";
|
|
18
|
+
import { type EvalHook, type EvalPinned } from "./eval-hooks.js";
|
|
19
|
+
export declare const ROUTING_HOOKS: readonly EvalHook[];
|
|
20
|
+
export declare const ROUTING_PINNED: readonly EvalPinned[];
|
|
21
|
+
/** Every file the manifest reads, for one verified read of each tree. */
|
|
22
|
+
export declare const ROUTING_HOOK_FILES: readonly string[];
|
|
23
|
+
/** The tables only route-store.ts writes, and the Models tables only the named store.ts writers write. */
|
|
24
|
+
export declare const ROUTING_TABLES: readonly ["route_record", "route_rule", "route_exposure", "route_exposure_event", "route_trial_reservation", "run_cohort", "models_generation"];
|
|
25
|
+
export declare const MODELS_TABLES: readonly ["phase_config", "phase_tier_config", "phase_tier_alternate"];
|
|
26
|
+
/** The one writer module, and the store.ts functions (`Class#method` or top-level) that may write the Models tables:
|
|
27
|
+
* the six row writers, a deleted project's rows, and the migration that rebuilt one. */
|
|
28
|
+
export declare const ROUTING_WRITER = "src/route-store.ts";
|
|
29
|
+
export declare const MODELS_WRITERS: Readonly<Record<string, readonly string[]>>;
|
|
30
|
+
/** Each string or template text in the file that writes a protected row, with the function it's in ("" at the top
|
|
31
|
+
* level): `Class#method`, or the top-level function. */
|
|
32
|
+
export declare function protectedWrites(file: string, source: ts.SourceFile): {
|
|
33
|
+
text: string;
|
|
34
|
+
inside: string;
|
|
35
|
+
models: boolean;
|
|
36
|
+
}[];
|
|
37
|
+
/** Why a changed file writes a protected row from a place that may not, or null. A construct the base's copy of the
|
|
38
|
+
* file already had (the same text in the same function, counted) is kept; a file that can't be read or parsed needs a
|
|
39
|
+
* person. Test files are not scanned: they never ship as the plane. */
|
|
40
|
+
export declare function sqlWriteProblem(paths: readonly string[], candidate: ((file: string) => Buffer | null) | null, base: ((file: string) => Buffer | null) | null): string | null;
|
|
41
|
+
/**
|
|
42
|
+
* Why a candidate needs a person for results routing, or null: a required call dropped, a pinned body changed (its
|
|
43
|
+
* manifest files read through `manifest`), or a changed file that writes a protected row outside its writers
|
|
44
|
+
* (`changed`). With `manifest.base` (the plane's own record of where the release started), only what the base holds
|
|
45
|
+
* is required, as eval-hooks.ts does.
|
|
46
|
+
*/
|
|
47
|
+
export declare function routingHookProblem(manifest: {
|
|
48
|
+
candidate: (file: string) => Buffer | null;
|
|
49
|
+
base?: (file: string) => Buffer | null;
|
|
50
|
+
}, changed: {
|
|
51
|
+
paths: readonly string[];
|
|
52
|
+
candidate: ((file: string) => Buffer | null) | null;
|
|
53
|
+
base: ((file: string) => Buffer | null) | null;
|
|
54
|
+
}): string | null;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { evalHookProblem } from "./eval-hooks.js";
|
|
3
|
+
let compiler;
|
|
4
|
+
const typescript = () => compiler ??= createRequire(import.meta.url)("typescript-parser");
|
|
5
|
+
/** Where a revision is created from: each caller's arguments (and so the origin it passes) are held as the base has them. */
|
|
6
|
+
const REVISION_CALLERS = [
|
|
7
|
+
{ file: "src/flow-engine.ts", inside: "revisionWithNote", calls: "requestResultChanges", from: "./result-actions.js", args: true, why: "a flow's revision says it came from a flow" },
|
|
8
|
+
{ file: "src/build-review.ts", inside: "sendBack", calls: "requestResultChanges", from: "./result-actions.js", args: true, why: "the automatic review's revision is never a person's" },
|
|
9
|
+
{ file: "src/flow-pull-request.ts", inside: "pullRequestStep", calls: "requestResultChanges", from: "./result-actions.js", args: true, why: "a flow's CI revision says it came from a flow" },
|
|
10
|
+
{ file: "src/pull-request-flow.ts", inside: "followPullRequests", calls: "requestResultChanges", from: "./result-actions.js", args: true, why: "a CI revision says it came from a flow" },
|
|
11
|
+
{ file: "src/chat-decide.ts", inside: "applyDecideFeedback", calls: "requestResultChanges", from: "./result-actions.js", args: true, why: "a chat's verified person asks for changes in their own words" },
|
|
12
|
+
{ file: "src/chat-review.ts", inside: "applyChatReview", calls: "createResultRevision", from: "./result-actions.js", args: true, why: "a confirmed review card passes the origin it was given" },
|
|
13
|
+
{ file: "src/lead-doors.ts", inside: "executeProposal", calls: "applyChatReview", from: "./chat-review.js", args: true, why: "a review card a person confirms is the person's; under a lead chain or remote run, the lead's" },
|
|
14
|
+
{ file: "src/task-outcome-cli.ts", inside: "runTaskOutcomeCommand", calls: "requestResultChanges", from: "./result-actions.js", args: true, why: "the command line tells a person from a lead or a machine" },
|
|
15
|
+
{ file: "src/server/tasks.ts", inside: "createTasksHandlers", calls: "requestResultChanges", from: "../result-actions.js", args: true, why: "a browser session is a person; a bearer credential a machine" },
|
|
16
|
+
{ file: "src/server/tasks.ts", inside: "createTasksHandlers", calls: "createResultRevision", from: "../result-actions.js", args: true, why: "a browser session is a person; a bearer credential a machine" },
|
|
17
|
+
];
|
|
18
|
+
export const ROUTING_HOOKS = Object.freeze([
|
|
19
|
+
{ file: "src/dispose.ts", inside: "disposeBuildOutcomeLocked", calls: "recordExecClass", from: "./route-store.js", args: true, why: "a failed run's class is kept from its raw reason" },
|
|
20
|
+
{ file: "src/result-actions.ts", inside: "createResultRevision", calls: "recordRevisionOrigin", from: "./route-store.js", args: true, why: "every revision says who it came from" },
|
|
21
|
+
{ file: "src/operate.ts", inside: "reconcileCommand", calls: "reevaluateResultRecords", from: "./route-results.js", why: "records are evaluated again as their inputs change" },
|
|
22
|
+
{ file: "src/project-delete.ts", inside: "deleteRows", calls: "forgetProjectModels", from: "./store.js", why: "a deleted project's Models rows go through the counter" },
|
|
23
|
+
{ file: "src/release-auto.ts", inside: "changeProblem", calls: "routingHookProblem", from: "./route-hooks.js", why: "this check itself" },
|
|
24
|
+
// Slice 2: each live rule is checked against its sealed terms every pass, and a held task never starts.
|
|
25
|
+
{ file: "src/operate.ts", inside: "reconcileCommand", calls: "routeRulesPass", from: "./route-rules.js", why: "a rule switches itself off on its sealed terms" },
|
|
26
|
+
{ file: "src/operate.ts", inside: "tickCommand", calls: "ruleHoldOf", from: "./route-store.js", why: "a task approved under a rule that switched itself off never starts on it" },
|
|
27
|
+
...REVISION_CALLERS,
|
|
28
|
+
]);
|
|
29
|
+
export const ROUTING_PINNED = Object.freeze([
|
|
30
|
+
// What a route is and how it's read.
|
|
31
|
+
{ file: "src/phase-routing.ts", fn: "recommendRoute", why: "which agent a task is routed to" },
|
|
32
|
+
// Slice 2: the one precedence order (pin > override > trial > earned rule > configured > headroom) and who is eligible.
|
|
33
|
+
{ file: "src/phase-routing.ts", fn: "selectBuildLeg", why: "the build leg's one precedence order" },
|
|
34
|
+
{ file: "src/phase-routing.ts", fn: "earnedRuleFor", why: "which tasks an earned rule may build" },
|
|
35
|
+
// Slice 3: which tasks a trial may build, and that only a person's own yes seals or dispatches one.
|
|
36
|
+
{ file: "src/phase-routing.ts", fn: "trialFor", why: "which tasks a trial may build" },
|
|
37
|
+
{ file: "src/approval-authority.ts", fn: "authorityStandsSql", why: "which approvals dispatch, a trial task's only on a person's yes" },
|
|
38
|
+
{ file: "src/approval-authority.ts", fn: "trialSealStandsSql", why: "a trial task stands only on a person's own approval" },
|
|
39
|
+
{ file: "src/phase-routing.ts", fn: "canonicalRouteJson", why: "a sealed route's bytes, and so its digest" },
|
|
40
|
+
{ file: "src/phase-routing.ts", fn: "routeFromJson", why: "how a sealed route reads back" },
|
|
41
|
+
{ file: "src/contracts/route.ts", fn: "readSealedRoute", why: "every route version's rules, the build leg's selection included" },
|
|
42
|
+
{ file: "src/store.ts", fn: "Store#sealedRouteOf", why: "the sealed route a run's cohort is decided from" },
|
|
43
|
+
{ file: "src/model-choices.ts", fn: "checkScope", why: "what a Models setting may say" },
|
|
44
|
+
{ file: "src/model-choices.ts", fn: "effectiveRoutes", why: "the routes the Models page previews" },
|
|
45
|
+
// The evidence a record reads.
|
|
46
|
+
{ file: "src/assignment.ts", fn: "assignmentChecksForRun", why: "a result's effective check" },
|
|
47
|
+
{ file: "src/result-completion.ts", fn: "changedFilesOf", why: "what a result changed" },
|
|
48
|
+
{ file: "src/assignment.ts", fn: "acknowledgeCurrent", why: "a completion's person or machine mark" },
|
|
49
|
+
// Its inputs.
|
|
50
|
+
{ file: "src/store.ts", fn: "Store#finishRun", why: "how a run ends" },
|
|
51
|
+
{ file: "src/store.ts", fn: "Store#recordRunCheck", why: "a run's own check" },
|
|
52
|
+
{ file: "src/store.ts", fn: "Store#writeRunRoute", why: "a run's route stamp" },
|
|
53
|
+
{ file: "src/result-actions.ts", fn: "createResultRevision", why: "every revision and its origin" },
|
|
54
|
+
// Its consumers and the Models writers, which move the configuration generations.
|
|
55
|
+
{ file: "src/store.ts", fn: "Store#saveScope", why: "filing a task's route" },
|
|
56
|
+
{ file: "src/store.ts", fn: "Store#sealScopeApproval", why: "approving a task's route" },
|
|
57
|
+
{ file: "src/store.ts", fn: "Store#admitRun", why: "a run's admission, its cohort snapshot, and the switched-off rule's hold" },
|
|
58
|
+
{ file: "src/store.ts", fn: "Store#applyCancellationLocked", why: "a cancel closes a task's exposure to an earned rule" },
|
|
59
|
+
{ file: "src/store.ts", fn: "Store#setPhaseConfig", why: "a Models row write moves its generation" },
|
|
60
|
+
{ file: "src/store.ts", fn: "Store#clearPhaseConfig", why: "a Models row write moves its generation" },
|
|
61
|
+
{ file: "src/store.ts", fn: "Store#setPhaseTierConfig", why: "a Models row write moves its generation" },
|
|
62
|
+
{ file: "src/store.ts", fn: "Store#clearPhaseTierConfig", why: "a Models row write moves its generation" },
|
|
63
|
+
{ file: "src/store.ts", fn: "Store#setPhaseTierAlternate", why: "a Models row write moves its generation" },
|
|
64
|
+
{ file: "src/store.ts", fn: "Store#clearPhaseTierAlternates", why: "a Models row write moves its generation" },
|
|
65
|
+
{ file: "src/store.ts", fn: "forgetProjectModels", why: "a deleted project's Models rows move their generations" },
|
|
66
|
+
]);
|
|
67
|
+
/** Every file the manifest reads, for one verified read of each tree. */
|
|
68
|
+
export const ROUTING_HOOK_FILES = Object.freeze([...new Set([...ROUTING_HOOKS.map(hook => hook.file), ...ROUTING_PINNED.map(pin => pin.file)])]);
|
|
69
|
+
/** The tables only route-store.ts writes, and the Models tables only the named store.ts writers write. */
|
|
70
|
+
export const ROUTING_TABLES = Object.freeze(["route_record", "route_rule", "route_exposure", "route_exposure_event", "route_trial_reservation", "run_cohort", "models_generation"]);
|
|
71
|
+
export const MODELS_TABLES = Object.freeze(["phase_config", "phase_tier_config", "phase_tier_alternate"]);
|
|
72
|
+
/** The one writer module, and the store.ts functions (`Class#method` or top-level) that may write the Models tables:
|
|
73
|
+
* the six row writers, a deleted project's rows, and the migration that rebuilt one. */
|
|
74
|
+
export const ROUTING_WRITER = "src/route-store.ts";
|
|
75
|
+
export const MODELS_WRITERS = Object.freeze({
|
|
76
|
+
"src/store.ts": Object.freeze(["Store#setPhaseConfig", "Store#clearPhaseConfig", "Store#setPhaseTierConfig", "Store#clearPhaseTierConfig",
|
|
77
|
+
"Store#setPhaseTierAlternate", "Store#clearPhaseTierAlternates", "forgetProjectModels", "widenPhaseTierSchema"]),
|
|
78
|
+
});
|
|
79
|
+
const VERB = String.raw `(?:INSERT(?:\s+OR\s+\w+)?\s+INTO|REPLACE\s+INTO|UPDATE(?:\s+OR\s+\w+)?|DELETE\s+FROM|DROP\s+TABLE(?:\s+IF\s+EXISTS)?|ALTER\s+TABLE|DROP\s+INDEX(?:\s+IF\s+EXISTS)?)`;
|
|
80
|
+
const writes = (tables) => new RegExp(String.raw `\b${VERB}\s+(?:main\s*\.\s*)?["\`\[]?(?:${tables.join("|")})["\`\]]?(?![\w])`, "i");
|
|
81
|
+
const ROUTING_WRITE = writes([...ROUTING_TABLES, "route_rule_one_winner"]);
|
|
82
|
+
const MODELS_WRITE = writes(MODELS_TABLES);
|
|
83
|
+
/** The two columns only route-store.ts writes: any write statement that names one. */
|
|
84
|
+
const COLUMN_WRITE = /\b(?:INSERT|REPLACE|UPDATE)\b[^]*\b(?:exec_class|revision_origin)\b/i;
|
|
85
|
+
/** Each string or template text in the file that writes a protected row, with the function it's in ("" at the top
|
|
86
|
+
* level): `Class#method`, or the top-level function. */
|
|
87
|
+
export function protectedWrites(file, source) {
|
|
88
|
+
const compiler = typescript();
|
|
89
|
+
const found = [];
|
|
90
|
+
const owner = (node) => {
|
|
91
|
+
let name = "";
|
|
92
|
+
for (let at = node.parent; at !== undefined; at = at.parent) {
|
|
93
|
+
if (compiler.isMethodDeclaration(at) && at.name !== undefined && compiler.isIdentifier(at.name) && compiler.isClassDeclaration(at.parent) && at.parent.name !== undefined)
|
|
94
|
+
name = `${at.parent.name.text}#${at.name.text}`;
|
|
95
|
+
else if (compiler.isFunctionDeclaration(at) && at.name !== undefined && compiler.isSourceFile(at.parent))
|
|
96
|
+
name = at.name.text;
|
|
97
|
+
}
|
|
98
|
+
return name;
|
|
99
|
+
};
|
|
100
|
+
const visit = (node) => {
|
|
101
|
+
let text = null;
|
|
102
|
+
if (compiler.isStringLiteralLike(node))
|
|
103
|
+
text = node.text;
|
|
104
|
+
else if (compiler.isTemplateExpression(node))
|
|
105
|
+
text = [node.head.text, ...node.templateSpans.map(span => span.literal.text)].join("${}");
|
|
106
|
+
if (text !== null) {
|
|
107
|
+
const models = MODELS_WRITE.test(text);
|
|
108
|
+
if (models || ROUTING_WRITE.test(text) || COLUMN_WRITE.test(text))
|
|
109
|
+
found.push({ text: text.replace(/\s+/g, " ").trim(), inside: owner(node), models: models && !ROUTING_WRITE.test(text) && !COLUMN_WRITE.test(text) });
|
|
110
|
+
if (compiler.isTemplateExpression(node)) {
|
|
111
|
+
for (const span of node.templateSpans)
|
|
112
|
+
visit(span.expression);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
compiler.forEachChild(node, visit);
|
|
117
|
+
};
|
|
118
|
+
visit(source);
|
|
119
|
+
void file;
|
|
120
|
+
return found;
|
|
121
|
+
}
|
|
122
|
+
/** Whether this write may stand where it is: route-store.ts writes anything; the named store.ts writers write the
|
|
123
|
+
* Models tables only. */
|
|
124
|
+
function allowed(file, write) {
|
|
125
|
+
if (file === ROUTING_WRITER)
|
|
126
|
+
return true;
|
|
127
|
+
return write.models && (MODELS_WRITERS[file] ?? []).includes(write.inside);
|
|
128
|
+
}
|
|
129
|
+
/** Why a changed file writes a protected row from a place that may not, or null. A construct the base's copy of the
|
|
130
|
+
* file already had (the same text in the same function, counted) is kept; a file that can't be read or parsed needs a
|
|
131
|
+
* person. Test files are not scanned: they never ship as the plane. */
|
|
132
|
+
export function sqlWriteProblem(paths, candidate, base) {
|
|
133
|
+
const scanned = paths.filter(path => /\.(?:[cm]?[jt]sx?)$/.test(path) && !/\.test\.[cm]?[jt]sx?$/.test(path) && !path.startsWith("test/")
|
|
134
|
+
&& !path.startsWith("node_modules/") && !path.includes("/node_modules/"));
|
|
135
|
+
if (scanned.length === 0)
|
|
136
|
+
return null;
|
|
137
|
+
if (candidate === null || base === null)
|
|
138
|
+
return "the changed files can't be read";
|
|
139
|
+
const compiler = typescript();
|
|
140
|
+
const parse = (path, bytes) => {
|
|
141
|
+
const source = compiler.createSourceFile(path, bytes.toString("utf8"), compiler.ScriptTarget.Latest, true, compiler.ScriptKind.TS);
|
|
142
|
+
return (source.parseDiagnostics ?? []).length === 0 ? source : null;
|
|
143
|
+
};
|
|
144
|
+
for (const path of scanned) {
|
|
145
|
+
const bytes = candidate(path);
|
|
146
|
+
if (bytes === null)
|
|
147
|
+
return `${path} can't be read`;
|
|
148
|
+
const now = parse(path, bytes);
|
|
149
|
+
if (now === null)
|
|
150
|
+
return `${path} doesn't parse`;
|
|
151
|
+
const before = base(path);
|
|
152
|
+
const was = before === null ? null : parse(path, before);
|
|
153
|
+
const kept = new Map();
|
|
154
|
+
const key = (one) => `${one.inside}\u0000${one.text}`;
|
|
155
|
+
if (was !== null)
|
|
156
|
+
for (const one of protectedWrites(path, was))
|
|
157
|
+
kept.set(key(one), (kept.get(key(one)) ?? 0) + 1);
|
|
158
|
+
for (const one of protectedWrites(path, now)) {
|
|
159
|
+
if (allowed(path, one))
|
|
160
|
+
continue;
|
|
161
|
+
const left = kept.get(key(one)) ?? 0;
|
|
162
|
+
if (left > 0) {
|
|
163
|
+
kept.set(key(one), left - 1);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
return `${path} writes results-routing or Models rows outside their writers (\`${one.text.slice(0, 80)}\`)`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Why a candidate needs a person for results routing, or null: a required call dropped, a pinned body changed (its
|
|
173
|
+
* manifest files read through `manifest`), or a changed file that writes a protected row outside its writers
|
|
174
|
+
* (`changed`). With `manifest.base` (the plane's own record of where the release started), only what the base holds
|
|
175
|
+
* is required, as eval-hooks.ts does.
|
|
176
|
+
*/
|
|
177
|
+
export function routingHookProblem(manifest, changed) {
|
|
178
|
+
return evalHookProblem(manifest.candidate, manifest.base, ROUTING_HOOKS, ROUTING_PINNED) ?? sqlWriteProblem(changed.paths, changed.candidate, changed.base);
|
|
179
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { INELIGIBLE_REASONS, type RecordReason, type RecordState, type ResultRecordView, type Selection } from "./contracts/route-results.js";
|
|
2
|
+
import { type Store } from "./store.js";
|
|
3
|
+
export { cohortOf, EXEC_CLASS_OF_REASON, execClassOf, type CohortInput, type CohortRow } from "./route-store.js";
|
|
4
|
+
/** How long an accepted result waits before it counts, and how long a result may wait for an answer. */
|
|
5
|
+
export declare const MATURE_AFTER_MS: number;
|
|
6
|
+
export declare const UNANSWERED_AFTER_MS: number;
|
|
7
|
+
/** The explicit cutoff for a family's spending and counts: they keep accumulating through later revision and repair
|
|
8
|
+
* runs (a verdict, once final, never moves) until the family is quiet — every version ended and no run open — or
|
|
9
|
+
* this long after its first run, whichever is first. */
|
|
10
|
+
export declare const ACCUMULATE_FOR_MS: number;
|
|
11
|
+
export type RecordValue = {
|
|
12
|
+
taskRef: number;
|
|
13
|
+
task: string;
|
|
14
|
+
repo: string | null;
|
|
15
|
+
cohortSize: "small" | "medium" | null;
|
|
16
|
+
provider: string | null;
|
|
17
|
+
model: string | null;
|
|
18
|
+
state: RecordState;
|
|
19
|
+
reason: RecordReason | null;
|
|
20
|
+
resultRun: number | null;
|
|
21
|
+
firstStartedAt: string | null;
|
|
22
|
+
acceptedAt: string | null;
|
|
23
|
+
decidedAt: string | null;
|
|
24
|
+
modelFailures: number;
|
|
25
|
+
infraFailures: number;
|
|
26
|
+
apiMicrousd: number;
|
|
27
|
+
subscriptionRuns: number;
|
|
28
|
+
subscriptionTokens: number;
|
|
29
|
+
unknownRuns: number;
|
|
30
|
+
remediationRuns: number;
|
|
31
|
+
selections: Selection[];
|
|
32
|
+
settled: boolean;
|
|
33
|
+
/** Accumulating counts: person revisions, whether the family's first built run's own check passed (null: none
|
|
34
|
+
* recorded), and agent turns (runs whose provider reports none counted apart). */
|
|
35
|
+
personRevisions: number;
|
|
36
|
+
firstCheckPassed: boolean | null;
|
|
37
|
+
agentTurns: number;
|
|
38
|
+
turnsUnknownRuns: number;
|
|
39
|
+
/** The family is quiet, or past ACCUMULATE_FOR_MS: with a final verdict, the record closes. */
|
|
40
|
+
accumulationEnded: boolean;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* A task family's record, evaluated now from its rows (null: no code-writing run, or a family whose lineage can't be
|
|
44
|
+
* proved). The order is the plan's: what can't be attributed to one model and one cohort first, then a live attempt
|
|
45
|
+
* (pending), then every rejection, then what isn't counted, then acceptance and maturity.
|
|
46
|
+
*/
|
|
47
|
+
export declare function evaluateFamily(store: Store, rootTaskId: string, evidenceRoot: string | undefined, now: Date): RecordValue | null;
|
|
48
|
+
/** The record in plain words, as the task page and `models results` say it. */
|
|
49
|
+
export declare function recordWords(record: Pick<RecordValue, "state" | "reason" | "model" | "resultRun" | "acceptedAt">): string;
|
|
50
|
+
/** A record row as saved, in the shape `models results --json` and the task page read. */
|
|
51
|
+
export declare function recordView(row: Record<string, unknown>, task: string): ResultRecordView;
|
|
52
|
+
/** The value as a view, for a record evaluated now and not (yet) saved. */
|
|
53
|
+
export declare function valueView(value: RecordValue): ResultRecordView;
|
|
54
|
+
/** The saved records of these projects, newest first (by their family's first run). */
|
|
55
|
+
export declare function resultRecords(store: Store, repos: readonly string[], limit?: number): ResultRecordView[];
|
|
56
|
+
/** A task's record: its family root's saved record, or, before the first pass saves one, the record evaluated now. */
|
|
57
|
+
export declare function resultRecordOf(store: Store, taskId: string, evidenceRoot: string | undefined, now: Date): ResultRecordView | null;
|
|
58
|
+
/** The task page's "Result record" line: the record's words, or null when the task has none (or it can't be read:
|
|
59
|
+
* a record never fails the page). */
|
|
60
|
+
export declare function resultRecordWordsOf(store: Store, taskId: string, evidenceRoot: string | undefined, now: Date): string | null;
|
|
61
|
+
/** The families the re-evaluation pass looks at: a root task with a code-writing run that has a cohort snapshot, whose
|
|
62
|
+
* record is missing or not closed (a final verdict still gathers its family's later spending until it closes); the
|
|
63
|
+
* least recently evaluated first. */
|
|
64
|
+
export declare function familiesToEvaluate(store: Store, repo: string, limit: number): string[];
|
|
65
|
+
export { INELIGIBLE_REASONS };
|
|
66
|
+
/** The re-evaluation pass (the reconcile pass of each project, operate.ts): every family with a cohort snapshot whose
|
|
67
|
+
* record is missing or not closed is evaluated again from its rows, oldest evaluation first, at most `limit` a pass,
|
|
68
|
+
* and saved through route-store.ts. Each family is its own transaction, so one unreadable family never holds the others
|
|
69
|
+
* back. */
|
|
70
|
+
export declare function reevaluateResultRecords(store: Store, repo: string, evidenceRoot: string | undefined, now: Date, limit?: number): number;
|