toolroll 0.9.91 → 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 +22 -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-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/store.js
CHANGED
|
@@ -35,6 +35,7 @@ import { actorLabel, currentActor } from "./actor.js";
|
|
|
35
35
|
import { scanForSecrets } from "./evidence.js";
|
|
36
36
|
import { LIVE_ACTIVITY_KINDS } from "./live.js";
|
|
37
37
|
import { hashSecret, mintLeadToken, parseCredential, secretMatches, tokenPurpose, tokenRotationProblem } from "./api-tokens.js";
|
|
38
|
+
import { admitTrialRun, bumpModelsGeneration, forgetProjectRoutes, liveEarnedRules, liveTrialFor, noteExposureCancelled, noteExposureFiled, noteExposureSealed, releaseTrialPlaces, reserveTrialPlace, ruleHoldOf, settleTrialPlaces, trialSealProblem, writeRunCohort } from "./route-store.js";
|
|
38
39
|
/**
|
|
39
40
|
* The database: a small task store, and the operational overlay beside it.
|
|
40
41
|
*
|
|
@@ -1110,7 +1111,10 @@ const REGISTERED_AT_FILING = "again, as it was filed";
|
|
|
1110
1111
|
// read 1), a browser session's authority (web_session.authority; saved rows read 'full'), its local generation, key
|
|
1111
1112
|
// epoch and key hash (saved rows read NULL: no local binding, and a stale key), and the running console's key epoch
|
|
1112
1113
|
// (console_instance.key_epoch; NULL). V147_ADDED_COLUMNS.
|
|
1113
|
-
|
|
1114
|
+
// v148 adds, and changes no saved row (docs/plans/results-routing.md): the results-routing tables (V148_SCHEMA, new
|
|
1115
|
+
// tables only) and a run's execution class, a revision's origin and a project's "Suggest cheaper models" switch
|
|
1116
|
+
// (V148_ADDED_COLUMNS: saved rows read NULL, NULL and 1).
|
|
1117
|
+
export const SCHEMA_VERSION = 148;
|
|
1114
1118
|
/** v118: the tables merged into `credential`, in the order they move (a person's API tokens keep their ids first). */
|
|
1115
1119
|
export const V118_MERGED_TABLES = Object.freeze({ api_token: "person", coordinator_credential: "coordinator", lead_credential: "lead" });
|
|
1116
1120
|
/** The foreign keys that named a merged table, exactly as their DDL wrote them; each names `credential(id)` instead. */
|
|
@@ -1243,7 +1247,7 @@ const V115_DROPPED_REFERENCES = /\s+REFERENCES (?:contestant|contest|tournament_
|
|
|
1243
1247
|
* unchanged), and adds the nullable column in V125_ADDED_COLUMNS, checked the same way. v126 only adds a new table
|
|
1244
1248
|
* (task_source) and the columns in V126_ADDED_COLUMNS (null or 0), checked the same way.
|
|
1245
1249
|
*/
|
|
1246
|
-
export const UPDATE_SAFE_MIGRATIONS = Object.freeze([108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147]);
|
|
1250
|
+
export const UPDATE_SAFE_MIGRATIONS = Object.freeze([108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148]);
|
|
1247
1251
|
/** v119: the columns it adds, by table, with the value every row saved before it reads (toolroll-update's HISTORY_RULES). */
|
|
1248
1252
|
export const V119_ADDED_COLUMNS = Object.freeze({
|
|
1249
1253
|
task_ref: { planning_generation: { definition: "INTEGER NOT NULL DEFAULT 0", saved: "0" } },
|
|
@@ -1498,6 +1502,154 @@ export const V147_ADDED_COLUMNS = Object.freeze({
|
|
|
1498
1502
|
});
|
|
1499
1503
|
/** The v147 migration's steps after the epoch, in order: a test seam (OpenOptions.migrationStep) may stop after each. */
|
|
1500
1504
|
export const V147_STEPS = Object.freeze(["epoch", "local-mode", "password-set", "web-session", "console-instance"]);
|
|
1505
|
+
/**
|
|
1506
|
+
* v148 (docs/plans/results-routing.md): every table the results-routing arc uses, landed at once because a current
|
|
1507
|
+
* store runs no DDL. Slice 1 writes run_cohort, route_record and models_generation; route_rule, route_exposure* and
|
|
1508
|
+
* route_trial_reservation are written from later slices. src/route-store.ts is the only writer of all of them (the
|
|
1509
|
+
* migration aside), which release-auto.ts's routing scan (route-hooks.ts) holds a release to.
|
|
1510
|
+
*
|
|
1511
|
+
* - run_cohort: one row per code-writing run (builder, repair), written in the run's admission transaction beside its
|
|
1512
|
+
* route stamp and never updated: the sealed facts its eligibility was decided from. cohort_size NULL = not eligible,
|
|
1513
|
+
* with the reason in `ineligible`.
|
|
1514
|
+
* - route_record: a task family's result record (keyed by its root task_ref), recomputed by the reconcile pass.
|
|
1515
|
+
* `settled` = its verdict (state, reason, result, acceptance, decision time) is final; its spending and counts keep
|
|
1516
|
+
* accumulating through later revision and repair runs until `closed` (the family is quiet, or 90 days after its
|
|
1517
|
+
* first run: route-results.ts ACCUMULATE_FOR_MS).
|
|
1518
|
+
* - route_rule: an earned rule or a trial, with its sealed grant, price snapshot and the configuration generations it
|
|
1519
|
+
* depends on; one live winner per project and size (route_rule_one_winner).
|
|
1520
|
+
* - route_exposure / route_exposure_event: one exposure per task and rule, its events append-only.
|
|
1521
|
+
* - route_trial_reservation: a trial's place, reserved, then consumed (once, at admission) or released.
|
|
1522
|
+
* - models_generation: a counter per (scope, phase), moved by every Models row writer in the same transaction.
|
|
1523
|
+
*/
|
|
1524
|
+
export const V148_SCHEMA = `
|
|
1525
|
+
CREATE TABLE IF NOT EXISTS run_cohort (
|
|
1526
|
+
run INTEGER PRIMARY KEY REFERENCES run(id) ON DELETE CASCADE,
|
|
1527
|
+
task_ref INTEGER NOT NULL REFERENCES task_ref(id) ON DELETE CASCADE,
|
|
1528
|
+
root_ref INTEGER NOT NULL REFERENCES task_ref(id) ON DELETE CASCADE,
|
|
1529
|
+
repo TEXT,
|
|
1530
|
+
role TEXT NOT NULL CHECK (role IN ('builder','repair')),
|
|
1531
|
+
size TEXT CHECK (size IS NULL OR size IN ('small','medium','large')),
|
|
1532
|
+
risky INTEGER CHECK (risky IS NULL OR risky IN (0,1)),
|
|
1533
|
+
quality_mode TEXT NOT NULL CHECK (quality_mode IN ('default','strict')),
|
|
1534
|
+
evidence_demands INTEGER NOT NULL CHECK (evidence_demands IN (0,1)),
|
|
1535
|
+
publication TEXT NOT NULL CHECK (publication IN ('none','notify','automerge')),
|
|
1536
|
+
release INTEGER NOT NULL CHECK (release IN (0,1)),
|
|
1537
|
+
protected INTEGER NOT NULL CHECK (protected IN (0,1)),
|
|
1538
|
+
selection_json TEXT NOT NULL,
|
|
1539
|
+
route_digest TEXT NOT NULL,
|
|
1540
|
+
provider TEXT NOT NULL,
|
|
1541
|
+
model TEXT,
|
|
1542
|
+
cohort_size TEXT CHECK (cohort_size IS NULL OR cohort_size IN ('small','medium')),
|
|
1543
|
+
ineligible TEXT,
|
|
1544
|
+
started_at TEXT NOT NULL,
|
|
1545
|
+
CHECK ((cohort_size IS NULL) = (ineligible IS NOT NULL))
|
|
1546
|
+
);
|
|
1547
|
+
CREATE INDEX IF NOT EXISTS run_cohort_by_task ON run_cohort (task_ref);
|
|
1548
|
+
CREATE INDEX IF NOT EXISTS run_cohort_by_root ON run_cohort (repo, root_ref);
|
|
1549
|
+
CREATE TABLE IF NOT EXISTS route_record (
|
|
1550
|
+
task_ref INTEGER PRIMARY KEY REFERENCES task_ref(id) ON DELETE CASCADE,
|
|
1551
|
+
repo TEXT,
|
|
1552
|
+
cohort_size TEXT CHECK (cohort_size IS NULL OR cohort_size IN ('small','medium')),
|
|
1553
|
+
provider TEXT,
|
|
1554
|
+
model TEXT,
|
|
1555
|
+
state TEXT NOT NULL CHECK (state IN ('pending','provisional','mature','rejected','not-counted')),
|
|
1556
|
+
reason TEXT,
|
|
1557
|
+
result_run INTEGER REFERENCES run(id) ON DELETE SET NULL,
|
|
1558
|
+
first_started_at TEXT,
|
|
1559
|
+
accepted_at TEXT,
|
|
1560
|
+
decided_at TEXT,
|
|
1561
|
+
model_failures INTEGER NOT NULL DEFAULT 0 CHECK (model_failures >= 0),
|
|
1562
|
+
infra_failures INTEGER NOT NULL DEFAULT 0 CHECK (infra_failures >= 0),
|
|
1563
|
+
api_microusd INTEGER NOT NULL DEFAULT 0 CHECK (api_microusd >= 0),
|
|
1564
|
+
subscription_runs INTEGER NOT NULL DEFAULT 0 CHECK (subscription_runs >= 0),
|
|
1565
|
+
subscription_tokens INTEGER NOT NULL DEFAULT 0 CHECK (subscription_tokens >= 0),
|
|
1566
|
+
unknown_runs INTEGER NOT NULL DEFAULT 0 CHECK (unknown_runs >= 0),
|
|
1567
|
+
remediation_runs INTEGER NOT NULL DEFAULT 0 CHECK (remediation_runs >= 0),
|
|
1568
|
+
person_revisions INTEGER NOT NULL DEFAULT 0 CHECK (person_revisions >= 0),
|
|
1569
|
+
first_check_passed INTEGER CHECK (first_check_passed IS NULL OR first_check_passed IN (0,1)),
|
|
1570
|
+
agent_turns INTEGER NOT NULL DEFAULT 0 CHECK (agent_turns >= 0),
|
|
1571
|
+
turns_unknown_runs INTEGER NOT NULL DEFAULT 0 CHECK (turns_unknown_runs >= 0),
|
|
1572
|
+
provenance_json TEXT NOT NULL DEFAULT '[]',
|
|
1573
|
+
settled INTEGER NOT NULL DEFAULT 0 CHECK (settled IN (0,1)),
|
|
1574
|
+
closed INTEGER NOT NULL DEFAULT 0 CHECK (closed IN (0,1)),
|
|
1575
|
+
evaluated_at TEXT NOT NULL,
|
|
1576
|
+
CHECK ((state IN ('rejected','not-counted')) = (reason IS NOT NULL)),
|
|
1577
|
+
CHECK (closed = 0 OR settled = 1)
|
|
1578
|
+
);
|
|
1579
|
+
CREATE INDEX IF NOT EXISTS route_record_by_cohort ON route_record (repo, cohort_size, state, first_started_at);
|
|
1580
|
+
CREATE INDEX IF NOT EXISTS route_record_open ON route_record (evaluated_at) WHERE closed = 0;
|
|
1581
|
+
CREATE TABLE IF NOT EXISTS route_rule (
|
|
1582
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1583
|
+
repo TEXT NOT NULL,
|
|
1584
|
+
cohort_size TEXT NOT NULL CHECK (cohort_size IN ('small','medium')),
|
|
1585
|
+
kind TEXT NOT NULL CHECK (kind IN ('rule','trial')),
|
|
1586
|
+
provider TEXT NOT NULL,
|
|
1587
|
+
model TEXT NOT NULL,
|
|
1588
|
+
configured_provider TEXT NOT NULL,
|
|
1589
|
+
configured_model TEXT,
|
|
1590
|
+
state TEXT NOT NULL CHECK (state IN ('active','trialling','paused','switched-off','turned-off','expired','dropped','finished')),
|
|
1591
|
+
generation INTEGER NOT NULL DEFAULT 1 CHECK (generation >= 1),
|
|
1592
|
+
grant_json TEXT NOT NULL,
|
|
1593
|
+
prices_json TEXT NOT NULL,
|
|
1594
|
+
depends_json TEXT NOT NULL,
|
|
1595
|
+
trial_limit INTEGER CHECK (trial_limit IS NULL OR trial_limit >= 1),
|
|
1596
|
+
accepted_by TEXT NOT NULL,
|
|
1597
|
+
accepted_at TEXT NOT NULL,
|
|
1598
|
+
ended_at TEXT,
|
|
1599
|
+
ended_by TEXT,
|
|
1600
|
+
end_reason TEXT,
|
|
1601
|
+
CHECK ((kind = 'trial') = (trial_limit IS NOT NULL))
|
|
1602
|
+
);
|
|
1603
|
+
CREATE UNIQUE INDEX IF NOT EXISTS route_rule_one_winner ON route_rule (repo, cohort_size) WHERE state IN ('active','trialling','paused');
|
|
1604
|
+
CREATE TABLE IF NOT EXISTS route_exposure (
|
|
1605
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1606
|
+
task_ref INTEGER NOT NULL REFERENCES task_ref(id) ON DELETE CASCADE,
|
|
1607
|
+
rule INTEGER NOT NULL REFERENCES route_rule(id) ON DELETE CASCADE,
|
|
1608
|
+
created_at TEXT NOT NULL,
|
|
1609
|
+
closed_at TEXT,
|
|
1610
|
+
UNIQUE (task_ref, rule)
|
|
1611
|
+
);
|
|
1612
|
+
CREATE TABLE IF NOT EXISTS route_exposure_event (
|
|
1613
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1614
|
+
exposure INTEGER NOT NULL REFERENCES route_exposure(id) ON DELETE CASCADE,
|
|
1615
|
+
kind TEXT NOT NULL CHECK (kind IN ('proposed','refiled','approved-as-proposed','approved-with-other','cancelled')),
|
|
1616
|
+
model TEXT,
|
|
1617
|
+
at TEXT NOT NULL,
|
|
1618
|
+
CHECK ((kind = 'approved-with-other') = (model IS NOT NULL))
|
|
1619
|
+
);
|
|
1620
|
+
CREATE INDEX IF NOT EXISTS route_exposure_event_by_exposure ON route_exposure_event (exposure, id);
|
|
1621
|
+
CREATE TABLE IF NOT EXISTS route_trial_reservation (
|
|
1622
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1623
|
+
rule INTEGER NOT NULL REFERENCES route_rule(id) ON DELETE CASCADE,
|
|
1624
|
+
task_ref INTEGER NOT NULL REFERENCES task_ref(id) ON DELETE CASCADE,
|
|
1625
|
+
scope_digest TEXT NOT NULL,
|
|
1626
|
+
state TEXT NOT NULL CHECK (state IN ('reserved','consumed','released')),
|
|
1627
|
+
consumed_run INTEGER REFERENCES run(id) ON DELETE SET NULL,
|
|
1628
|
+
created_at TEXT NOT NULL,
|
|
1629
|
+
updated_at TEXT NOT NULL,
|
|
1630
|
+
UNIQUE (rule, task_ref),
|
|
1631
|
+
CHECK (state = 'consumed' OR consumed_run IS NULL)
|
|
1632
|
+
);
|
|
1633
|
+
CREATE TABLE IF NOT EXISTS models_generation (
|
|
1634
|
+
scope TEXT NOT NULL,
|
|
1635
|
+
phase TEXT NOT NULL,
|
|
1636
|
+
generation INTEGER NOT NULL CHECK (generation >= 1),
|
|
1637
|
+
changed_at TEXT NOT NULL,
|
|
1638
|
+
PRIMARY KEY (scope, phase)
|
|
1639
|
+
);
|
|
1640
|
+
`;
|
|
1641
|
+
/** The tables V148_SCHEMA adds, in the order a test drops them to make the v147 shape. */
|
|
1642
|
+
export const V148_TABLES = Object.freeze(["route_trial_reservation", "route_exposure_event", "route_exposure", "route_rule", "route_record", "run_cohort", "models_generation"]);
|
|
1643
|
+
/** v148: a run's execution class (saved runs read NULL: none kept, which the records count as the model's), a
|
|
1644
|
+
* revision's origin (saved revisions read NULL: not recorded, never a person), and a project's "Suggest cheaper models"
|
|
1645
|
+
* switch (saved projects read 1: on). */
|
|
1646
|
+
export const V148_ADDED_COLUMNS = Object.freeze({
|
|
1647
|
+
run: { exec_class: { definition: "TEXT CHECK (exec_class IS NULL OR exec_class IN ('model','infrastructure'))", saved: "NULL" } },
|
|
1648
|
+
task_ref: { revision_origin: { definition: "TEXT CHECK (revision_origin IS NULL OR revision_origin IN ('person','automatic-review','flow','lead','machine'))", saved: "NULL" } },
|
|
1649
|
+
project: { suggest_models: { definition: "INTEGER NOT NULL DEFAULT 1 CHECK (suggest_models IN (0,1))", saved: "1" } },
|
|
1650
|
+
});
|
|
1651
|
+
/** The v148 migration's steps after v147's, in order (OpenOptions.migrationStep may stop after each). */
|
|
1652
|
+
export const V148_STEPS = Object.freeze(["results-tables", "exec-class", "revision-origin", "suggest-models"]);
|
|
1501
1653
|
/**
|
|
1502
1654
|
* v135: one approval per release (docs/plans/release-once.md). `release_authority` is the single source of truth for a
|
|
1503
1655
|
* release a person approved: one row per generation, changed only by compare-and-swap on `rev`, with a lease whose
|
|
@@ -4776,6 +4928,19 @@ CREATE INDEX IF NOT EXISTS run_checkpoint_by_task ON run_checkpoint (task_ref, i
|
|
|
4776
4928
|
for (const [column, { definition }] of Object.entries(V147_ADDED_COLUMNS["console_instance"]))
|
|
4777
4929
|
addColumn(db, "console_instance", column, definition);
|
|
4778
4930
|
step("console-instance");
|
|
4931
|
+
// v148: the results-routing tables (new tables only) and the execution class, revision origin and suggestion switch
|
|
4932
|
+
// (V148_ADDED_COLUMNS: saved rows read their declared values). Each step is idempotent, as v147's are.
|
|
4933
|
+
db.exec(V148_SCHEMA);
|
|
4934
|
+
step("results-tables");
|
|
4935
|
+
for (const [column, { definition }] of Object.entries(V148_ADDED_COLUMNS["run"]))
|
|
4936
|
+
addColumn(db, "run", column, definition);
|
|
4937
|
+
step("exec-class");
|
|
4938
|
+
for (const [column, { definition }] of Object.entries(V148_ADDED_COLUMNS["task_ref"]))
|
|
4939
|
+
addColumn(db, "task_ref", column, definition);
|
|
4940
|
+
step("revision-origin");
|
|
4941
|
+
for (const [column, { definition }] of Object.entries(V148_ADDED_COLUMNS["project"]))
|
|
4942
|
+
addColumn(db, "project", column, definition);
|
|
4943
|
+
step("suggest-models");
|
|
4779
4944
|
// Keep the epoch until reclamation succeeds, including a retry after compaction already committed. A new file has
|
|
4780
4945
|
// nothing to reclaim.
|
|
4781
4946
|
if (!fresh)
|
|
@@ -6166,6 +6331,12 @@ export class Store {
|
|
|
6166
6331
|
}
|
|
6167
6332
|
if (ref !== undefined && prior?.["state"] !== "cancelled")
|
|
6168
6333
|
this.noteTaskAct(Number(ref["id"]), "cancelled", now);
|
|
6334
|
+
// Results routing: a cancel closes the task's open exposure to an earned rule.
|
|
6335
|
+
if (ref !== undefined)
|
|
6336
|
+
noteExposureCancelled(this, Number(ref["id"]), now);
|
|
6337
|
+
// …and gives up any trial place it holds unconsumed (a consumed place is never released).
|
|
6338
|
+
if (ref !== undefined)
|
|
6339
|
+
releaseTrialPlaces(this, Number(ref["id"]), now);
|
|
6169
6340
|
// A cancelled task's pending steering settles superseded in the SAME
|
|
6170
6341
|
// transaction, whichever road cancelled it — a note silently waiting
|
|
6171
6342
|
// for a build that can no longer happen is wrong on every road, not
|
|
@@ -7293,6 +7464,9 @@ export class Store {
|
|
|
7293
7464
|
const problems = routeProblems(route);
|
|
7294
7465
|
if (problems.length > 0)
|
|
7295
7466
|
unresolvedReason = problems.join("; ");
|
|
7467
|
+
// A trial's place is one task's (results routing slice 3): a frozen route never carries one to another.
|
|
7468
|
+
else if (legOf(route, "build").selection?.kind === "trial")
|
|
7469
|
+
unresolvedReason = "a frozen agent route can't try a cheaper model — a trial place belongs to the task it was reserved for";
|
|
7296
7470
|
}
|
|
7297
7471
|
}
|
|
7298
7472
|
else if (!candidatesResolved.ok) {
|
|
@@ -7308,7 +7482,16 @@ export class Store {
|
|
|
7308
7482
|
unresolvedReason = planPin.problem;
|
|
7309
7483
|
}
|
|
7310
7484
|
else {
|
|
7311
|
-
route
|
|
7485
|
+
// Results routing (route v3): the project's live earned rules and trial, and the two eligibility facts the
|
|
7486
|
+
// route doesn't carry. A release or candidate is read conservatively: terms this filing may still drop count.
|
|
7487
|
+
const earnedBase = ref === null || ref.backend !== BUILT_IN || ref.repo === null ? null : {
|
|
7488
|
+
rules: liveEarnedRules(this, ref.repo),
|
|
7489
|
+
release: (scope.candidate ?? null) !== null || options.releaseTerms != null || options.releaseCheck != null
|
|
7490
|
+
|| prior?.["release_terms_json"] != null || prior?.["release_check_json"] != null || releaseTaskRef(this, ref.id) || nightlyTaskRef(this, ref.id),
|
|
7491
|
+
protected: isProtectedWork(this.approvalRules(ref.repo), scope.touches),
|
|
7492
|
+
};
|
|
7493
|
+
const trial = earnedBase === null || ref === null ? null : liveTrialFor(this, ref.repo, ref.sizing?.size);
|
|
7494
|
+
const routeWith = (reservation) => recommendRoute({
|
|
7312
7495
|
qualityMode,
|
|
7313
7496
|
evidence: [...new Set(scope.acceptance.flatMap(one => one.evidence))],
|
|
7314
7497
|
publication: this.publicationAuthorityOf(ref?.repo ?? null, new Date(scope.proposedAt)),
|
|
@@ -7319,7 +7502,19 @@ export class Store {
|
|
|
7319
7502
|
// with the route like every other input.
|
|
7320
7503
|
size: ref?.sizing ?? null,
|
|
7321
7504
|
headroom: this.planHeadroom(new Date(scope.proposedAt)),
|
|
7505
|
+
earned: earnedBase === null ? null : { ...earnedBase, ...(trial === null ? {} : { trial: { ...trial, reservation } }) },
|
|
7322
7506
|
});
|
|
7507
|
+
// A TRIAL'S PLACE (results routing slice 3): asked first whether this filing would take the trial at all (a
|
|
7508
|
+
// pure probe, nothing written), then its place reserved here, in this write transaction, and the route made
|
|
7509
|
+
// with that place (or with none left: the configured builder runs it). A pin, an override or an ineligible
|
|
7510
|
+
// task never takes a place.
|
|
7511
|
+
route = routeWith(trial === null ? null : Number.MAX_SAFE_INTEGER);
|
|
7512
|
+
if (trial !== null && ref !== null && legOf(route, "build").selection?.kind === "trial") {
|
|
7513
|
+
route = routeWith(reserveTrialPlace(this, trial.id, ref.id, new Date(scope.proposedAt)));
|
|
7514
|
+
}
|
|
7515
|
+
else if (trial !== null) {
|
|
7516
|
+
route = routeWith(null);
|
|
7517
|
+
}
|
|
7323
7518
|
const problems = routeProblems(route);
|
|
7324
7519
|
if (problems.length > 0)
|
|
7325
7520
|
unresolvedReason = problems.join("; ");
|
|
@@ -7463,6 +7658,16 @@ export class Store {
|
|
|
7463
7658
|
// or a remote API run keeps any mark already there and never erases a
|
|
7464
7659
|
// quarantine by refiling, and the lead's own text is marked `lead`.
|
|
7465
7660
|
this.db.prepare("UPDATE task_scope SET proposed_via = ? WHERE task_id = ?").run(provenanceAfter(prior?.["proposed_via"], options.proposedVia ?? null), scope.taskId);
|
|
7661
|
+
// Results routing (exposure, route-store.ts): a build leg under an earned rule opens the task's one exposure to
|
|
7662
|
+
// it; a re-filing adds an event to an open one, never a row.
|
|
7663
|
+
if (ref !== null && route !== null)
|
|
7664
|
+
noteExposureFiled(this, ref.id, legOf(route, "build").selection, new Date(scope.proposedAt));
|
|
7665
|
+
// Results routing (trials, route-store.ts): the place this route seals is bound to the digest it was filed under
|
|
7666
|
+
// (repeated saves reuse it); any other place this task held unconsumed is released.
|
|
7667
|
+
if (ref !== null) {
|
|
7668
|
+
const selection = route === null ? undefined : legOf(route, "build").selection;
|
|
7669
|
+
settleTrialPlaces(this, ref.id, options.route !== undefined ? undefined : selection, boundDigest, new Date(scope.proposedAt));
|
|
7670
|
+
}
|
|
7466
7671
|
return null;
|
|
7467
7672
|
}));
|
|
7468
7673
|
}
|
|
@@ -7584,6 +7789,17 @@ export class Store {
|
|
|
7584
7789
|
return false;
|
|
7585
7790
|
if (routeProblems(authority.route).length > 0)
|
|
7586
7791
|
return false;
|
|
7792
|
+
// RESULTS ROUTING (route v3): a build leg selected by an earned rule that switched itself off since it was
|
|
7793
|
+
// filed seals nothing — re-filing routes it on the configured builder (route-rules.ts holds what was approved).
|
|
7794
|
+
const sealedSelection = legOf(authority.route, "build").selection;
|
|
7795
|
+
if (sealedSelection?.kind === "rule" && this.db.prepare("SELECT state FROM route_rule WHERE id = ?").get(sealedSelection.rule)?.["state"] === "switched-off")
|
|
7796
|
+
return false;
|
|
7797
|
+
// A TRIAL (results routing slice 3): only a person's own yes seals it — the ceremony or their paired-chat tap,
|
|
7798
|
+
// never a mode, a grant, a schedule, an automatic seal, a carry or the lead — and only while its place stands.
|
|
7799
|
+
const trialRef = this.db.prepare("SELECT id FROM task_ref WHERE backend = ? AND external_id = ?").get(BUILT_IN, taskId);
|
|
7800
|
+
if (trialSealProblem(this, { taskRef: trialRef === undefined ? null : Number(trialRef["id"]), selection: sealedSelection, digest: String(working["digest"] ?? ""),
|
|
7801
|
+
basis: basis === undefined ? null : basis.kind, by, machine: machineActing() }) !== null)
|
|
7802
|
+
return false;
|
|
7587
7803
|
// RELEASE TERMS (release-once.md): only a person's own yes (the ceremony or a paired-chat tap) seals them, and
|
|
7588
7804
|
// that yes opens the release's authority record in this same transaction — or the seal doesn't stand.
|
|
7589
7805
|
const terms = storedReleaseTerms(working["release_terms_json"]);
|
|
@@ -7650,6 +7866,10 @@ export class Store {
|
|
|
7650
7866
|
if (basis === undefined || basis.kind === "chat")
|
|
7651
7867
|
this.noteTaskAct(Number(ref["id"]), "approved", now);
|
|
7652
7868
|
this.db.prepare("DELETE FROM hold WHERE owner_kind = 'backoff' AND owner_id = ?").run(`stale:${Number(ref["id"])}`);
|
|
7869
|
+
// Results routing: this yes closes the task's open exposure (as proposed, or with the model it sealed),
|
|
7870
|
+
// and lifts the hold a switched-off rule put on the approval it replaces.
|
|
7871
|
+
noteExposureSealed(this, Number(ref["id"]), { selection: sealedSelection, model: legOf(authority.route, "build").model }, now);
|
|
7872
|
+
this.db.prepare("DELETE FROM hold WHERE owner_kind = 'backoff' AND owner_id = ?").run(`rule-off:${Number(ref["id"])}`);
|
|
7653
7873
|
this.db
|
|
7654
7874
|
.prepare("UPDATE notification SET resolved_at = ? WHERE dedupe_key LIKE ? AND resolved_at IS NULL")
|
|
7655
7875
|
.run(now.toISOString(), `stale-approval:${Number(ref["id"])}:%`);
|
|
@@ -11778,22 +11998,32 @@ export class Store {
|
|
|
11778
11998
|
}
|
|
11779
11999
|
/** Write one complete pair. Audited: who changed spend routing, and when. */
|
|
11780
12000
|
setPhaseConfig(scope, phase, provider, model, by, now) {
|
|
11781
|
-
|
|
11782
|
-
|
|
11783
|
-
this.
|
|
11784
|
-
|
|
11785
|
-
|
|
11786
|
-
|
|
11787
|
-
|
|
11788
|
-
|
|
11789
|
-
|
|
11790
|
-
|
|
12001
|
+
// Results routing (v148): a changed row moves its (scope, phase) generation in the same transaction, which pauses
|
|
12002
|
+
// any rule that depended on it (route-store.ts bumpModelsGeneration).
|
|
12003
|
+
this.transact(() => {
|
|
12004
|
+
const was = this.phaseConfig(scope, phase);
|
|
12005
|
+
const words = (agent) => agent === null ? "not set" : `${agent.provider} · ${agent.model ?? "its default model"}`;
|
|
12006
|
+
this.recordPolicy(by, scope === "installation" ? null : scope, `agent for ${phase} changed`, words(was), words({ provider, model }), now);
|
|
12007
|
+
this.db
|
|
12008
|
+
.prepare(`INSERT INTO phase_config (scope, phase, provider, model, updated_at, updated_by)
|
|
12009
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
12010
|
+
ON CONFLICT (scope, phase) DO UPDATE SET
|
|
12011
|
+
provider = excluded.provider, model = excluded.model,
|
|
12012
|
+
updated_at = excluded.updated_at, updated_by = excluded.updated_by`)
|
|
12013
|
+
.run(scope, phase, provider, model, now.toISOString(), by);
|
|
12014
|
+
if (was === null || was.provider !== provider || was.model !== model)
|
|
12015
|
+
bumpModelsGeneration(this, scope, phase, now);
|
|
12016
|
+
});
|
|
11791
12017
|
}
|
|
11792
12018
|
clearPhaseConfig(scope, phase) {
|
|
11793
|
-
|
|
11794
|
-
|
|
11795
|
-
|
|
11796
|
-
|
|
12019
|
+
return this.transact(() => {
|
|
12020
|
+
const { changes } = this.db
|
|
12021
|
+
.prepare("DELETE FROM phase_config WHERE scope = ? AND phase = ?")
|
|
12022
|
+
.run(scope, phase);
|
|
12023
|
+
if (Number(changes) > 0)
|
|
12024
|
+
bumpModelsGeneration(this, scope, phase, new Date());
|
|
12025
|
+
return Number(changes) > 0;
|
|
12026
|
+
});
|
|
11797
12027
|
}
|
|
11798
12028
|
// ---- phase routing (v47) -----------------------------------------------
|
|
11799
12029
|
/** The operator's named STRONG candidate for a phase at a scope, or null.
|
|
@@ -11827,16 +12057,25 @@ export class Store {
|
|
|
11827
12057
|
// A small change makes no plan and repairs resume the builder: only the build phase has a light agent.
|
|
11828
12058
|
if (tier === "light" && phase !== "build")
|
|
11829
12059
|
throw new Error(`a light agent is for the build phase only, not ${phase}`);
|
|
11830
|
-
this.
|
|
11831
|
-
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
12060
|
+
this.transact(() => {
|
|
12061
|
+
const was = this.phaseTierConfig(scope, phase, tier);
|
|
12062
|
+
this.db
|
|
12063
|
+
.prepare(`INSERT INTO phase_tier_config (scope, phase, tier, provider, model, updated_at, updated_by)
|
|
12064
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
12065
|
+
ON CONFLICT (scope, phase, tier) DO UPDATE SET provider = excluded.provider, model = excluded.model,
|
|
12066
|
+
updated_at = excluded.updated_at, updated_by = excluded.updated_by`)
|
|
12067
|
+
.run(scope, phase, tier, provider, model, now.toISOString(), by);
|
|
12068
|
+
if (was === null || was.provider !== provider || was.model !== model)
|
|
12069
|
+
bumpModelsGeneration(this, scope, phase, now);
|
|
12070
|
+
});
|
|
11836
12071
|
}
|
|
11837
12072
|
clearPhaseTierConfig(scope, phase, tier) {
|
|
11838
|
-
|
|
11839
|
-
|
|
12073
|
+
return this.transact(() => {
|
|
12074
|
+
const { changes } = this.db.prepare("DELETE FROM phase_tier_config WHERE scope = ? AND phase = ? AND tier = ?").run(scope, phase, tier);
|
|
12075
|
+
if (Number(changes) > 0)
|
|
12076
|
+
bumpModelsGeneration(this, scope, phase, new Date());
|
|
12077
|
+
return Number(changes) > 0;
|
|
12078
|
+
});
|
|
11840
12079
|
}
|
|
11841
12080
|
/** A tier's candidates on other providers at a scope, in provider order (sized routing). */
|
|
11842
12081
|
phaseTierAlternates(scope, phase, tier) {
|
|
@@ -11863,19 +12102,28 @@ export class Store {
|
|
|
11863
12102
|
setPhaseTierAlternate(scope, phase, tier, provider, model, by, now) {
|
|
11864
12103
|
if (tier === "light" && phase !== "build")
|
|
11865
12104
|
throw new Error(`a light agent is for the build phase only, not ${phase}`);
|
|
11866
|
-
this.
|
|
11867
|
-
|
|
11868
|
-
|
|
11869
|
-
|
|
11870
|
-
|
|
11871
|
-
|
|
12105
|
+
this.transact(() => {
|
|
12106
|
+
const was = this.phaseTierAlternates(scope, phase, tier).find(one => one.provider === provider) ?? null;
|
|
12107
|
+
this.db
|
|
12108
|
+
.prepare(`INSERT INTO phase_tier_alternate (scope, phase, tier, provider, model, updated_at, updated_by)
|
|
12109
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
12110
|
+
ON CONFLICT (scope, phase, tier, provider) DO UPDATE SET model = excluded.model,
|
|
12111
|
+
updated_at = excluded.updated_at, updated_by = excluded.updated_by`)
|
|
12112
|
+
.run(scope, phase, tier, provider, model, now.toISOString(), by);
|
|
12113
|
+
if (was === null || was.model !== model)
|
|
12114
|
+
bumpModelsGeneration(this, scope, phase, now);
|
|
12115
|
+
});
|
|
11872
12116
|
}
|
|
11873
12117
|
/** Remove one provider's candidate from a tier, or every one when no provider is named; how many went. */
|
|
11874
12118
|
clearPhaseTierAlternates(scope, phase, tier, provider) {
|
|
11875
|
-
|
|
11876
|
-
|
|
11877
|
-
|
|
11878
|
-
|
|
12119
|
+
return this.transact(() => {
|
|
12120
|
+
const { changes } = provider === null
|
|
12121
|
+
? this.db.prepare("DELETE FROM phase_tier_alternate WHERE scope = ? AND phase = ? AND tier = ?").run(scope, phase, tier)
|
|
12122
|
+
: this.db.prepare("DELETE FROM phase_tier_alternate WHERE scope = ? AND phase = ? AND tier = ? AND provider = ?").run(scope, phase, tier, provider);
|
|
12123
|
+
if (Number(changes) > 0)
|
|
12124
|
+
bumpModelsGeneration(this, scope, phase, new Date());
|
|
12125
|
+
return Number(changes);
|
|
12126
|
+
});
|
|
11879
12127
|
}
|
|
11880
12128
|
/** Each provider's plan headroom right now, from the latest readings of
|
|
11881
12129
|
* its usage windows — the routing policy's headroom input at filing. */
|
|
@@ -13288,6 +13536,14 @@ export class Store {
|
|
|
13288
13536
|
const problem = this.routeAdmissionProblem({ taskRef: run.taskRef, role, provider: provider ?? "claude", model }, run.route, run.now);
|
|
13289
13537
|
if (problem !== null)
|
|
13290
13538
|
refuse(problem);
|
|
13539
|
+
// RESULTS ROUTING (route v3): a task approved under an earned rule that then switched itself off on its sealed
|
|
13540
|
+
// trigger is held — its first build never starts on that model (no strike: nothing ran). A rule a person turned
|
|
13541
|
+
// off, or one that expired, keeps the sealed model; a started task's later attempts finish under it.
|
|
13542
|
+
if (role === "builder") {
|
|
13543
|
+
const held = ruleHoldOf(this, taskId);
|
|
13544
|
+
if (held !== null)
|
|
13545
|
+
refuse(`rule-switched-off: results for ${held.model} dropped after this was approved — approve it again or re-file it`);
|
|
13546
|
+
}
|
|
13291
13547
|
const stamp = run.route;
|
|
13292
13548
|
const qualityMode = role === "reviewer" && parent !== null
|
|
13293
13549
|
? parent.qualityMode ?? "default"
|
|
@@ -13319,6 +13575,16 @@ export class Store {
|
|
|
13319
13575
|
// The provenance row lands in the same transaction as the run row —
|
|
13320
13576
|
// proved above, written here; a run and its route are one fact.
|
|
13321
13577
|
this.writeRunRoute(id, stamp, run.now);
|
|
13578
|
+
// Results routing (v148): a code-writing run's cohort snapshot, beside its route in this same transaction and
|
|
13579
|
+
// never updated (route-store.ts writeRunCohort; a snapshot it can't compute is recorded as not eligible).
|
|
13580
|
+
if (role === "builder" || role === "repair") {
|
|
13581
|
+
writeRunCohort(this, { run: id, taskRef: run.taskRef, role, routeDigest: stamp.routeDigest, provider: provider ?? "claude", model, qualityMode, startedAt: run.now.toISOString() });
|
|
13582
|
+
// A TRIAL (results routing slice 3): a person's own approval, and its place consumed exactly once against the
|
|
13583
|
+
// sealed digest (a retry or repair of the same task consumes nothing more). Refused → the row rolls back.
|
|
13584
|
+
const trialProblem = admitTrialRun(this, { taskId, taskRef: run.taskRef, role, run: id, now: run.now });
|
|
13585
|
+
if (trialProblem !== null)
|
|
13586
|
+
refuse(trialProblem);
|
|
13587
|
+
}
|
|
13322
13588
|
if (role === "reviewer" && parent?.role === "reviewer" && parent.watchIncarnation != null) {
|
|
13323
13589
|
this.db.prepare("UPDATE run SET watch_incarnation = ? WHERE id = ?").run(parent.watchIncarnation, id);
|
|
13324
13590
|
}
|
|
@@ -22376,3 +22642,22 @@ export function laptopPortOf(store) {
|
|
|
22376
22642
|
export function recordLaptopPort(store, port, now) {
|
|
22377
22643
|
store.recordInstallationFact(LAPTOP_PORT_FACT, String(port), now);
|
|
22378
22644
|
}
|
|
22645
|
+
// ---- v148: results routing (docs/plans/results-routing.md) ------------------------------------------------------
|
|
22646
|
+
/** The Models rows of a project being deleted (project-delete.ts), through the same counter as every other Models
|
|
22647
|
+
* writer: each phase whose rows go moves its generation (pausing what depended on it), then the project's
|
|
22648
|
+
* results-routing rows go (route-store.ts forgetProjectRoutes). One of the routing scan's named phase_* writers
|
|
22649
|
+
* (route-hooks.ts). How many rows went. */
|
|
22650
|
+
export function forgetProjectModels(store, repo, now) {
|
|
22651
|
+
return store.transact(() => {
|
|
22652
|
+
const db = store.handle;
|
|
22653
|
+
let rows = 0;
|
|
22654
|
+
const phases = new Set(db.prepare(`SELECT phase FROM phase_config WHERE scope = ? UNION SELECT phase FROM phase_tier_config WHERE scope = ?
|
|
22655
|
+
UNION SELECT phase FROM phase_tier_alternate WHERE scope = ?`).all(repo, repo, repo).map(row => String(row["phase"])));
|
|
22656
|
+
rows += Number(db.prepare("DELETE FROM phase_config WHERE scope = ?").run(repo).changes);
|
|
22657
|
+
rows += Number(db.prepare("DELETE FROM phase_tier_config WHERE scope = ?").run(repo).changes);
|
|
22658
|
+
rows += Number(db.prepare("DELETE FROM phase_tier_alternate WHERE scope = ?").run(repo).changes);
|
|
22659
|
+
for (const phase of [...phases].sort())
|
|
22660
|
+
bumpModelsGeneration(store, repo, phase, now);
|
|
22661
|
+
return rows + forgetProjectRoutes(store, repo);
|
|
22662
|
+
});
|
|
22663
|
+
}
|
package/dist/surface.js
CHANGED
|
@@ -116,12 +116,12 @@ const GUIDE_ROWS = [
|
|
|
116
116
|
notableReasons: ["usage", "ambiguous", "drift", "timeout", "check-failed", "ci-failed", "tree-mismatch", "conflict", "mixed-runtime", "gate-held", "after-swap"] },
|
|
117
117
|
operator("demo", "a seeded throwaway sandbox"),
|
|
118
118
|
...KNOWLEDGE_DESCRIPTORS.map(spec => ({ invocation: `knowledge ${spec.action}`, synopsis: spec.synopsis, audience: "audience" in spec ? spec.audience : "agent", agentMayInvoke: !("audience" in spec), mutation: spec.mutation, flags: spec.flags, ...(spec.takesQuery ? { positionals: [{ name: "query", required: true, meaning: "search text or source file for impact" }] } : {}) })),
|
|
119
|
-
...MEMORY_DESCRIPTORS.map(spec => ({ invocation: `memory ${spec.action}`, synopsis: spec.synopsis, audience: "agent", agentMayInvoke:
|
|
119
|
+
...MEMORY_DESCRIPTORS.map(spec => ({ invocation: `memory ${spec.action}`, synopsis: spec.synopsis, audience: "audience" in spec ? spec.audience : "agent", agentMayInvoke: !("audience" in spec), mutation: spec.mutation, flags: spec.flags, ...(spec.takesQuery ? { positionals: [{ name: "query", required: true, meaning: "search text, a decision id, or the decision sentence" }] } : {}) })),
|
|
120
120
|
// Flow writes carry an approver's credential: an agent runs them only as the person asked, after showing the preview.
|
|
121
121
|
// Letting a flow run on its own raises authority: the owner's own password ceremony, never an agent's act.
|
|
122
122
|
...FLOWS_DESCRIPTORS.map(spec => ({ invocation: `flows ${spec.action}`, synopsis: spec.synopsis, audience: "audience" in spec ? spec.audience : "agent", agentMayInvoke: !("audience" in spec), mutation: spec.mutation, flags: spec.flags,
|
|
123
123
|
...("positionals" in spec ? { positionals: spec.positionals.map(name => ({ name, required: true, meaning: name === "flow" ? "a flow id from flows list" : name === "card" ? "a card id from flows show" : name === "on|off" ? "on signs the grant, off revokes it" : "a trigger id from flows show, or for trigger add its settings as JSON, a JSON file, or -" })) } : {}) })),
|
|
124
|
-
...MODELS_DESCRIPTORS.map(spec => ({ invocation: `models ${spec.action}`, synopsis: spec.synopsis, audience: "agent", agentMayInvoke: true, mutation: spec.mutation, flags: spec.flags, ...(spec.takesQuery ? { positionals: [{ name: "target", required: true, meaning: "the CLI to update, or on/off" }] } : {}) })),
|
|
124
|
+
...MODELS_DESCRIPTORS.map(spec => ({ invocation: `models ${spec.action}`, synopsis: spec.synopsis, audience: "agent", agentMayInvoke: true, mutation: spec.mutation, flags: spec.flags, ...(spec.takesQuery ? { positionals: [{ name: "target", required: true, meaning: "positional" in spec ? spec.positional : "the CLI to update, or on/off" }] } : {}) })),
|
|
125
125
|
// ---- the queue (agent surface) ----
|
|
126
126
|
{ invocation: "status", synopsis: "running work, queued reasons, results to review, the latest release check and plan windows", audience: "agent", agentMayInvoke: true, mutation: "none", flags: [jsonFlag, dbFlag] },
|
|
127
127
|
{ invocation: 'health', synopsis: 'recent server latency, event-loop delay, SQLite writes and live streams from the saved connection; instance operators only', audience: 'agent', agentMayInvoke: true, mutation: 'none', flags: [jsonFlag, { name: 'profile', takesValue: true, meaning: 'saved server connection' }] },
|
|
@@ -460,6 +460,11 @@ const REMOTE_NO = [
|
|
|
460
460
|
"serve", "serve check-public", "watch", "up", "onboard", "daemon", "background on", "background off", "background show", "stop", "open", "welcome", "password set", "doctor", "bridge", "setup show", "setup clear",
|
|
461
461
|
// Provider keys and model tools on this machine.
|
|
462
462
|
"providers", "keys status", "keys set", "keys clear", "keys verify", "keys auth", "models check", "models update", "models watch",
|
|
463
|
+
// Result records read with this machine's sign-in: project access, never a token's narrower scope.
|
|
464
|
+
"models results",
|
|
465
|
+
// Earned rules (results routing): the project's own results, and its rules' doors, with this machine's sign-in; a
|
|
466
|
+
// remote token never changes which models run (the rule door refuses it in the write too).
|
|
467
|
+
"models suggestions", "models accept", "models dismiss", "models turn-off", "models keep", "models try", "models stop",
|
|
463
468
|
// Publication and release.
|
|
464
469
|
"publish", "publish status", "publish merge", "publish refire", "publish unblock", "publish rearm", "publish grant", "publish revoke",
|
|
465
470
|
// A repository's authority grants on this machine.
|
|
@@ -474,7 +479,9 @@ const REMOTE_NO = [
|
|
|
474
479
|
const REMOTE_STEP_UP = [
|
|
475
480
|
"proposals", "decide", "task regate", "task approve", "task accept", "task merge", "run settle",
|
|
476
481
|
// Putting tasks away and restoring them: a person's act, in the console or on the server's machine.
|
|
477
|
-
"task archive", "task restore", "task tidy", "knowledge apply",
|
|
482
|
+
"task archive", "task restore", "task tidy", "knowledge apply",
|
|
483
|
+
// Changing project memory: a person's own password, never a token (the lead only proposes it in chat).
|
|
484
|
+
"memory decide", "memory retire", "memory apply",
|
|
478
485
|
"flows card approve", "flows card send-back", "flows script approve", "flows unattended",
|
|
479
486
|
"approver list", "approver add", "people list", "people invite", "people projects", "people revoke",
|
|
480
487
|
// A person's own API tokens: a password step-up on the server's machine, or the console. Never with a token.
|
|
@@ -491,7 +498,7 @@ const REMOTE_YES = [
|
|
|
491
498
|
"config show", "verify show", "intake show", "intake run", "intake preview", "intake pr-comments", "template list", "template show",
|
|
492
499
|
"webhook status", "review show", "chat-approval show", "chat-approval off",
|
|
493
500
|
"knowledge search", "knowledge impact", "knowledge refresh",
|
|
494
|
-
"memory search", "memory decisions", "memory show", "memory
|
|
501
|
+
"memory search", "memory decisions", "memory show", "memory review", "memory status",
|
|
495
502
|
"flows list", "flows show", "flows export", "flows create", "flows edit", "flows trigger add", "flows trigger pause", "flows trigger resume",
|
|
496
503
|
"flows trigger remove", "flows card add", "flows archive",
|
|
497
504
|
"models status", "models list",
|
|
@@ -530,6 +537,11 @@ const LOCAL_ALLOW = {
|
|
|
530
537
|
"task archive": "archive-task",
|
|
531
538
|
"task restore": "archive-task",
|
|
532
539
|
"repos add": "add-project",
|
|
540
|
+
// Results routing: lowering only (route-store.ts proveRuleDoor checks the act is exactly this one).
|
|
541
|
+
"models suggestions": "read",
|
|
542
|
+
"models turn-off": "routing-turn-off",
|
|
543
|
+
"models dismiss": "dismiss-suggestion",
|
|
544
|
+
"models stop": "trial-stop",
|
|
533
545
|
};
|
|
534
546
|
/** Every guide row's local-owner mark, by invocation: `allow` with its act, else `deny`. Exported for the inventory test. */
|
|
535
547
|
export const LOCAL_POLICY = new Map(GUIDE_ROWS.map(row => [row.invocation,
|
package/dist/task-outcome-cli.js
CHANGED
|
@@ -4,7 +4,8 @@ import { assignmentOf, checkAssignmentAsOperator } from './assignment.js';
|
|
|
4
4
|
import { assignmentForCoordinator, coordinatorToken } from './assignment-adapters.js';
|
|
5
5
|
import { authenticateCoordinator } from './coordinator.js';
|
|
6
6
|
import { reproveApprover } from './principal.js';
|
|
7
|
-
import { authenticateApprover, personProof } from './scope.js';
|
|
7
|
+
import { authenticateApprover, machineActing, personProof } from './scope.js';
|
|
8
|
+
import { currentActor } from './actor.js';
|
|
8
9
|
import { requestResultChanges } from './result-actions.js';
|
|
9
10
|
import { commentSourceKey, revisionSourceOf } from './result-review.js';
|
|
10
11
|
import { envelopeJson } from './envelope.js';
|
|
@@ -110,7 +111,9 @@ export async function runTaskOutcomeCommand(action, positional, flags, context)
|
|
|
110
111
|
// The shared action returns exact replays before stale-family checks. An
|
|
111
112
|
// older result cannot create another child once a newer revision is current.
|
|
112
113
|
const revised = requestResultChanges(store, evidenceRoot, { run: run.id, source, actor, repos,
|
|
113
|
-
batch: '', note: feedback, path: '', line: '', request: key, allowMode: operator !== null
|
|
114
|
+
batch: '', note: feedback, path: '', line: '', request: key, allowMode: operator !== null,
|
|
115
|
+
// A verified approver's own words; a scoped credential or the lead's token is the lead; a remote API run a machine.
|
|
116
|
+
origin: coordinator !== null || currentActor()?.lead === true ? 'lead' : machineActing() ? 'machine' : 'person' }, now);
|
|
114
117
|
if (!revised.ok)
|
|
115
118
|
return fail(revised.status === 400 ? 'usage' : 'stale-result', revised.message);
|
|
116
119
|
const scope = store.getScope(revised.id);
|
package/dist/toolroll-update.js
CHANGED
|
@@ -39,7 +39,7 @@ import { installLaunchdService, launchdPlist, stopLaunchdService, writeFileDurab
|
|
|
39
39
|
import { LAPTOP_LABEL_DARWIN, LAPTOP_LABEL_LINUX, unitFlag } from "./laptop-service.js";
|
|
40
40
|
import { processGone, readRunnerProcessRecord, realProcessProbe, recordStandsFor, RUNNER_PROCESS_DIR } from "./runner-process.js";
|
|
41
41
|
import { NAME } from "./names.js";
|
|
42
|
-
import { orphanKey, updateSafeSchema, V117_RENAMED_COLUMNS, V117_RENAMED_TABLES, V118_MERGED_TABLES, V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS, V143_ADDED_COLUMNS, V147_ADDED_COLUMNS } from "./store.js";
|
|
42
|
+
import { orphanKey, updateSafeSchema, V117_RENAMED_COLUMNS, V117_RENAMED_TABLES, V118_MERGED_TABLES, V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS, V143_ADDED_COLUMNS, V147_ADDED_COLUMNS, V148_ADDED_COLUMNS } from "./store.js";
|
|
43
43
|
import { isNewer, REGISTRY } from "./releases.js";
|
|
44
44
|
import { markNeverIndex } from "./never-index.js";
|
|
45
45
|
import { readRuntimeUpdateJournal, RUNTIME_PHASES, RUNTIME_UPDATE_STEPS, stagedStartedAt, updaterStartingOf } from "./contracts/update-journal.js";
|
|
@@ -413,7 +413,8 @@ const HISTORY_RULES = {
|
|
|
413
413
|
// v128: a lead message's pending flag reads waiting exactly for a team queue's unadmitted operator message.
|
|
414
414
|
// v147: an account's password flag reads set (1), a browser session reads full with no local binding or key, and the
|
|
415
415
|
// console record reads no key epoch.
|
|
416
|
-
|
|
416
|
+
// v148: a run's execution class and a revision's origin read none (NULL), and a project's suggestion switch reads on.
|
|
417
|
+
...Object.fromEntries(Object.entries(addedColumns(V119_ADDED_COLUMNS, V120_ADDED_COLUMNS, V121_ADDED_COLUMNS, V122_ADDED_COLUMNS, V123_ADDED_COLUMNS, V125_ADDED_COLUMNS, V126_ADDED_COLUMNS, V128_ADDED_COLUMNS, V129_ADDED_COLUMNS, V134_ADDED_COLUMNS, V135_ADDED_COLUMNS, V138_ADDED_COLUMNS, V143_ADDED_COLUMNS, V147_ADDED_COLUMNS, V148_ADDED_COLUMNS))
|
|
417
418
|
.map(([table, rule]) => [table, { ...EARLIER_RULES[table], ...rule }])),
|
|
418
419
|
};
|
|
419
420
|
/** The tables a migration may remove whole (v115's removed features). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.92",
|
|
4
4
|
"description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
|
|
5
5
|
"directories": {
|
|
6
6
|
"doc": "docs"
|