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
package/dist/result-acts.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* report and changes disagree,
|
|
12
12
|
* or the proof is refuted → Request changes, then Accept (as allowed);
|
|
13
13
|
* never Accept as ink, even with no form here
|
|
14
|
-
* a Needs you form resolves it → that form (Build again,
|
|
14
|
+
* a Needs you form resolves it → that form (Build again, Check again)
|
|
15
15
|
* saved notes or a failing PR → Revise / Draft a repair task
|
|
16
16
|
* a failed build → Retry (its note starts with what to change), then Run checks when they
|
|
17
17
|
* didn't run; never Accept: a failed build is not a result to accept
|
|
@@ -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;
|