toolroll 0.9.71 → 0.9.72
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 +79 -0
- package/dist/agent-fence.d.ts +5 -2
- package/dist/agent-fence.js +5 -2
- package/dist/approval-authority.d.ts +20 -0
- package/dist/approval-authority.js +67 -0
- package/dist/assignment.js +26 -4
- package/dist/browser/workspace.css +1 -1
- package/dist/builder.js +48 -6
- package/dist/chat-decide.js +17 -2
- package/dist/check-access.js +2 -2
- package/dist/check-report.d.ts +109 -0
- package/dist/check-report.js +204 -0
- package/dist/claim.js +1 -1
- package/dist/commit-extract.d.ts +94 -0
- package/dist/commit-extract.js +436 -0
- package/dist/contracts/console-api.d.ts +1 -1
- package/dist/contracts/console-api.js +1 -0
- package/dist/contracts/flow.d.ts +16 -0
- package/dist/contracts/flow.js +3 -0
- package/dist/contracts/release-check.d.ts +335 -0
- package/dist/contracts/release-check.js +159 -0
- package/dist/contracts/release-terms.d.ts +60 -0
- package/dist/contracts/release-terms.js +70 -0
- package/dist/contracts/verification-receipt.d.ts +42 -8
- package/dist/contracts/verification-receipt.js +24 -7
- package/dist/contracts/verification-view.d.ts +1 -0
- package/dist/deploy-gate.d.ts +17 -0
- package/dist/deploy-gate.js +68 -0
- package/dist/dispatch.js +3 -10
- package/dist/dispose.js +4 -1
- package/dist/evidence-pack.d.ts +1 -1
- package/dist/evidence-pack.js +20 -2
- package/dist/evidence.d.ts +1 -0
- package/dist/evidence.js +2 -1
- package/dist/flow-engine.js +5 -1
- package/dist/flow-pull-request.d.ts +5 -2
- package/dist/flow-pull-request.js +14 -5
- package/dist/flow-schedule.js +25 -6
- package/dist/flows-ui.js +7 -1
- package/dist/invoke.d.ts +17 -6
- package/dist/invoke.js +48 -18
- package/dist/lead-status.js +5 -2
- package/dist/live-terminal.d.ts +10 -6
- package/dist/live-terminal.js +55 -14
- package/dist/nightly-check.d.ts +104 -0
- package/dist/nightly-check.js +362 -0
- package/dist/operate.js +39 -2
- package/dist/person-proof.d.ts +2 -0
- package/dist/person-proof.js +14 -0
- package/dist/plan-auto.js +4 -4
- package/dist/planner-source.d.ts +1 -1
- package/dist/project-delete-ui.js +1 -1
- package/dist/proof.js +2 -0
- package/dist/publish.js +156 -144
- package/dist/pull-request-flow.d.ts +2 -0
- package/dist/pull-request-flow.js +24 -2
- package/dist/release-adapters.d.ts +8 -0
- package/dist/release-adapters.js +212 -15
- package/dist/release-authority.d.ts +112 -0
- package/dist/release-authority.js +126 -0
- package/dist/release-carry.d.ts +233 -0
- package/dist/release-carry.js +645 -0
- package/dist/release-cli.d.ts +3 -0
- package/dist/release-cli.js +124 -5
- package/dist/release-coverage.js +3 -0
- package/dist/release-recheck.d.ts +146 -0
- package/dist/release-recheck.js +657 -0
- package/dist/release-view.d.ts +34 -0
- package/dist/release-view.js +27 -0
- package/dist/release.d.ts +131 -1
- package/dist/release.js +562 -244
- package/dist/remote-main.d.ts +2 -0
- package/dist/remote-main.js +1 -1
- package/dist/result-completion.d.ts +6 -0
- package/dist/result-completion.js +15 -1
- package/dist/result-follow-ups.js +29 -1
- package/dist/sandbox-runtime.d.ts +16 -5
- package/dist/sandbox-runtime.js +19 -6
- package/dist/scope.d.ts +115 -5
- package/dist/scope.js +219 -7
- package/dist/server/render-tasks.d.ts +8 -0
- package/dist/server/render-tasks.js +50 -9
- package/dist/server/route-table.js +1 -0
- package/dist/server/task-views.js +4 -0
- package/dist/server/tasks.js +12 -0
- package/dist/store.d.ts +227 -5
- package/dist/store.js +727 -37
- package/dist/task-outcome-cli.js +7 -3
- package/dist/toolroll-update.js +2 -2
- package/dist/verification-evidence.d.ts +25 -1
- package/dist/verification-evidence.js +57 -2
- package/dist/work-index.js +3 -6
- package/package.json +1 -1
package/dist/task-outcome-cli.js
CHANGED
|
@@ -4,11 +4,11 @@ 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 } from './scope.js';
|
|
7
|
+
import { authenticateApprover, personProof } from './scope.js';
|
|
8
8
|
import { requestResultChanges } from './result-actions.js';
|
|
9
9
|
import { commentSourceKey, revisionSourceOf } from './result-review.js';
|
|
10
10
|
import { envelopeJson } from './envelope.js';
|
|
11
|
-
import { completeAndOpenPullRequest, mergeAsPerson, newestPullRequestOf, publishingOf, pullRequestViewOf } from './pull-request-flow.js';
|
|
11
|
+
import { completeAndOpenPullRequest, mergeAsPerson, MERGE_NEEDS_PERSON, newestPullRequestOf, publishingOf, pullRequestViewOf } from './pull-request-flow.js';
|
|
12
12
|
export async function runTaskOutcomeCommand(action, positional, flags, context) {
|
|
13
13
|
const command = `task ${action}`;
|
|
14
14
|
const emit = (result, lines) => {
|
|
@@ -141,8 +141,12 @@ export async function runTaskMergeCommand(positional, flags, context) {
|
|
|
141
141
|
if (acting === null)
|
|
142
142
|
return fail('unauthenticated', 'Merging takes your password: --as <you> --token <password>.');
|
|
143
143
|
const { store } = context;
|
|
144
|
-
|
|
144
|
+
const proved = authenticateApprover(store, acting.name, acting.token);
|
|
145
|
+
if (!proved.ok)
|
|
145
146
|
return fail('not-an-approver', 'That password didn\'t work, or you can\'t merge.');
|
|
147
|
+
// A merge by hand is a person's (approval policy §1.7): refused before anything is read, --anyway included.
|
|
148
|
+
if (!personProof(proved.method))
|
|
149
|
+
return fail('person-required', MERGE_NEEDS_PERSON);
|
|
146
150
|
const family = store.taskFamilyOf(task, store.knownRepos().filter(repo => store.accountCanAccess(acting.name, repo)), false);
|
|
147
151
|
if (family === null)
|
|
148
152
|
return fail('not-found', 'That task is unavailable in your projects.');
|
package/dist/toolroll-update.js
CHANGED
|
@@ -36,7 +36,7 @@ import { removeTerminalUpdateGate } from "./terminal-catalog.js";
|
|
|
36
36
|
import { assertCodingUpdateStopped, backupCodingCatalog, codingCatalogExists, releaseStaleCodingOwner, removeCodingUpdateGate } from "./coding-update.js";
|
|
37
37
|
import { installLaunchdService, launchdPlist, stopLaunchdService, writeFileDurably } from "./daemon.js";
|
|
38
38
|
import { NAME } from "./names.js";
|
|
39
|
-
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 } from "./store.js";
|
|
39
|
+
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 } from "./store.js";
|
|
40
40
|
import { isNewer, REGISTRY } from "./releases.js";
|
|
41
41
|
import { markNeverIndex } from "./never-index.js";
|
|
42
42
|
import { readRuntimeUpdateJournal, RUNTIME_PHASES, RUNTIME_UPDATE_STEPS, stagedStartedAt, updaterStartingOf } from "./contracts/update-journal.js";
|
|
@@ -408,7 +408,7 @@ const HISTORY_RULES = {
|
|
|
408
408
|
// v126: a receipt's state, acceptance time and held item, a trigger's credential generation and turned-away count,
|
|
409
409
|
// and who made a card read none, 0, or none. A table with an earlier rule (flow_trigger's v115 move) keeps it too.
|
|
410
410
|
// v128: a lead message's pending flag reads waiting exactly for a team queue's unadmitted operator message.
|
|
411
|
-
...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))
|
|
411
|
+
...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))
|
|
412
412
|
.map(([table, rule]) => [table, { ...EARLIER_RULES[table], ...rule }])),
|
|
413
413
|
};
|
|
414
414
|
/** The tables a migration may remove whole (v115's removed features). */
|
|
@@ -1,12 +1,36 @@
|
|
|
1
1
|
import type { Artifact, Store, VerifyCommand } from "./store.js";
|
|
2
2
|
import type { VerifyCommandFacts } from "./proof.js";
|
|
3
3
|
import { type AdjudicateResult } from "./proof.js";
|
|
4
|
+
import { type ComposedLinks, type Coverage } from "./release-recheck.js";
|
|
4
5
|
export declare const VERIFICATION_RECEIPT_CAPTURE = "machine verification receipt v1";
|
|
5
6
|
export declare const isVerificationReceipt: (a: Artifact) => boolean;
|
|
7
|
+
/** A release check's own facts for a version 3 receipt (release-once.md): its coverage, its sealed check artifacts, and
|
|
8
|
+
* for a composed check the links its reader proves again. */
|
|
9
|
+
export type ReleaseCheckFacts = {
|
|
10
|
+
coverage: Coverage;
|
|
11
|
+
check: {
|
|
12
|
+
attempt: "full" | "targeted";
|
|
13
|
+
report: string | null;
|
|
14
|
+
inventory: string | null;
|
|
15
|
+
exitCode?: number | null;
|
|
16
|
+
};
|
|
17
|
+
composed: ComposedLinks | null;
|
|
18
|
+
};
|
|
19
|
+
/** Whether a coverage lets a check pass: only full or composed (absent: a check that isn't a release check). */
|
|
20
|
+
export declare const coveragePasses: (coverage: unknown) => boolean;
|
|
21
|
+
/**
|
|
22
|
+
* THE one place a release check's coverage meets its outcome (security review): a check whose coverage is partial or
|
|
23
|
+
* identity-removed is a failed attempt for every reader — the verdict, the run's check, the receipt's `result`, reuse,
|
|
24
|
+
* follow-ups, the deploy gates — whatever its command exited with. The raw exit is kept in the receipt's `check`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function effectiveCheckFacts(raw: VerifyCommandFacts, facts: ReleaseCheckFacts | null): {
|
|
27
|
+
result: VerifyCommandFacts;
|
|
28
|
+
facts: ReleaseCheckFacts | null;
|
|
29
|
+
};
|
|
6
30
|
export declare function sealVerificationReceipt(store: Store, root: string, runId: number, head: string, command: VerifyCommand, result: VerifyCommandFacts, now: Date, reusedFrom?: {
|
|
7
31
|
run: number;
|
|
8
32
|
digest: string;
|
|
9
|
-
}): void;
|
|
33
|
+
}, release?: ReleaseCheckFacts): void;
|
|
10
34
|
/** Read and bind existing evidence, never backfill historical receipts. A legacy
|
|
11
35
|
* single-attempt machine header is sufficient only with its exact approved grant
|
|
12
36
|
* and sealed candidate endpoints. Recovery logs without a receipt stay explicit. */
|
|
@@ -11,19 +11,36 @@ import { readVerificationReceipt } from "./contracts/verification-receipt.js";
|
|
|
11
11
|
import { LEGACY_GATE_SOURCE, readVerificationView } from "./contracts/verification-view.js";
|
|
12
12
|
import { readCandidateEndpoints, savedInventoryPaths } from "./contracts/diff-stat.js";
|
|
13
13
|
import { liveQuickCommand, quickVerifyKey, runCheckLevel } from "./check-levels.js";
|
|
14
|
+
import { CHECK_INVENTORY_CAPTURE, CHECK_REPORT_CAPTURE } from "./check-report.js";
|
|
15
|
+
import { provedComposition } from "./release-recheck.js";
|
|
16
|
+
import { checkAttemptOf } from "./store.js";
|
|
14
17
|
export const VERIFICATION_RECEIPT_CAPTURE = "machine verification receipt v1";
|
|
15
18
|
const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
16
19
|
const binding = (a) => ({ artifactId: a.id, sha256: a.sha256, bytesStored: a.bytesStored, bytesOriginal: a.bytesOriginal, truncated: a.truncated, redacted: a.redacted, captureStatus: a.captureStatus });
|
|
17
20
|
export const isVerificationReceipt = (a) => a.kind === "structured-output" && (a.capture === VERIFICATION_RECEIPT_CAPTURE || a.capture === "machine verification reuse v1");
|
|
18
|
-
|
|
21
|
+
/** Whether a coverage lets a check pass: only full or composed (absent: a check that isn't a release check). */
|
|
22
|
+
export const coveragePasses = (coverage) => coverage === undefined || coverage === "full" || coverage === "composed";
|
|
23
|
+
/**
|
|
24
|
+
* THE one place a release check's coverage meets its outcome (security review): a check whose coverage is partial or
|
|
25
|
+
* identity-removed is a failed attempt for every reader — the verdict, the run's check, the receipt's `result`, reuse,
|
|
26
|
+
* follow-ups, the deploy gates — whatever its command exited with. The raw exit is kept in the receipt's `check`.
|
|
27
|
+
*/
|
|
28
|
+
export function effectiveCheckFacts(raw, facts) {
|
|
29
|
+
if (facts === null || coveragePasses(facts.coverage))
|
|
30
|
+
return { result: raw, facts };
|
|
31
|
+
const exitCode = raw.configured && raw.ran ? raw.exitCode : null;
|
|
32
|
+
return { result: { configured: true, ran: false, attemptFailed: true, failure: "coverage-incomplete" }, facts: { ...facts, check: { ...facts.check, exitCode } } };
|
|
33
|
+
}
|
|
34
|
+
export function sealVerificationReceipt(store, root, runId, head, command, result, now, reusedFrom, release) {
|
|
19
35
|
const source = store.getRun(runId);
|
|
20
36
|
const log = store.artifactsFor(runId).find(a => a.kind === "check-log");
|
|
21
37
|
if (!log)
|
|
22
38
|
throw new Error("Verification receipt requires its retained log");
|
|
23
39
|
const receipt = {
|
|
24
|
-
version: reusedFrom
|
|
40
|
+
version: reusedFrom !== undefined ? 2 : release !== undefined ? 3 : 1, run: runId, head, base: source.baseRevision, scopeDigest: source.scopeDigest,
|
|
25
41
|
...(reusedFrom === undefined ? {} : { reusedFrom, executedHere: false }),
|
|
26
42
|
command, result, log: binding(log),
|
|
43
|
+
...(release === undefined || reusedFrom !== undefined ? {} : { coverage: release.coverage, check: release.check, ...(release.composed === null ? {} : { composed: release.composed }) }),
|
|
27
44
|
};
|
|
28
45
|
const checked = readVerificationReceipt(receipt);
|
|
29
46
|
if (!checked.ok)
|
|
@@ -114,6 +131,44 @@ export function verificationEvidence(store, root, runId) {
|
|
|
114
131
|
}
|
|
115
132
|
else if (checked.reusedFrom !== undefined || checked.executedHere !== undefined)
|
|
116
133
|
return fail("The gate reuse receipt has an unsupported version.");
|
|
134
|
+
// Version 3, a release check (release-once.md): its attempt, sealed before it ran, and its sealed artifacts must be
|
|
135
|
+
// the ones it names; a composed one has every link proved again from sealed artifacts and the store.
|
|
136
|
+
if (receipt.version === 3) {
|
|
137
|
+
if (checked.coverage === undefined || checked.check === undefined)
|
|
138
|
+
return fail("The release check receipt names no coverage.");
|
|
139
|
+
const attempt = checkAttemptOf(store, runId);
|
|
140
|
+
if (attempt === null || attempt.mode !== checked.check.attempt)
|
|
141
|
+
return fail("The release check's sealed attempt doesn't match its receipt.");
|
|
142
|
+
const sha = (capture) => { const found = artifacts.filter(a => a.kind === "structured-output" && a.capture === capture); return found.length === 1 ? found[0].sha256 : null; };
|
|
143
|
+
if (sha(CHECK_REPORT_CAPTURE) !== checked.check.report || sha(CHECK_INVENTORY_CAPTURE) !== checked.check.inventory)
|
|
144
|
+
return fail("The release check's sealed report or inventory changed.");
|
|
145
|
+
if ((checked.coverage === "composed") !== (checked.composed !== undefined))
|
|
146
|
+
return fail("The release check's coverage and its links disagree.");
|
|
147
|
+
// A check that didn't cover what it owed can never read as a pass.
|
|
148
|
+
if (!coveragePasses(checked.coverage) && checked.result.configured && checked.result.ran && checked.result.exitCode === 0)
|
|
149
|
+
return fail("The release check reads as passed, but its coverage fell short.");
|
|
150
|
+
if (checked.composed !== undefined) {
|
|
151
|
+
const gateOf = (run) => { const one = verificationEvidence(store, root, run); return one.ok ? one.digest : null; };
|
|
152
|
+
const grantOf = (run) => {
|
|
153
|
+
if (run === runId)
|
|
154
|
+
return checked.command.digest;
|
|
155
|
+
const one = verificationEvidence(store, root, run);
|
|
156
|
+
if (!one.ok || one.bytes === null)
|
|
157
|
+
return null;
|
|
158
|
+
try {
|
|
159
|
+
return String(JSON.parse(one.bytes).command?.digest ?? "") || null;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const problem = provedComposition(store, root, runId, checked.composed, gateOf, grantOf);
|
|
166
|
+
if (problem !== null)
|
|
167
|
+
return fail(problem);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else if (checked.coverage !== undefined || checked.check !== undefined || checked.composed !== undefined)
|
|
171
|
+
return fail("The receipt names release check facts its version doesn't have.");
|
|
117
172
|
const result = checked.result;
|
|
118
173
|
if (!result.configured)
|
|
119
174
|
return fail("The machine verification result is malformed.");
|
package/dist/work-index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/** Paginated, permission-bound summaries of saved task facts. These reads do
|
|
2
2
|
* not verify artifacts or authorize task operations; a build Toolroll can't
|
|
3
3
|
* yet confirm stopped is read through the store's own read-only probe. */
|
|
4
|
+
import { authorityStandsSql } from "./approval-authority.js";
|
|
4
5
|
import { leadNameOf } from './lead-identity.js';
|
|
5
6
|
import { createHash } from 'node:crypto';
|
|
6
7
|
import { existsSync } from 'node:fs';
|
|
7
8
|
import { join } from 'node:path';
|
|
8
|
-
import {
|
|
9
|
+
import { readProjectAccess } from './project-access.js';
|
|
9
10
|
import { pauseOnTaskProviders, providerName, signInReason } from './provider-auth.js';
|
|
10
11
|
import { scopeTermsProblem, stopFactOf } from './store.js';
|
|
11
12
|
import { assignmentChecksForRun } from './assignment.js';
|
|
@@ -19,7 +20,6 @@ import { withFollowUps } from './result-follow-ups.js';
|
|
|
19
20
|
import { LANDED_ACTOR, landedReasonOf } from './result-completion.js';
|
|
20
21
|
import { ASKS, NEED_ASK, NEEDS, askChipOf, failedAttemptSentence, processNeedOf, resultHoldUpSentence } from './needs-you.js';
|
|
21
22
|
import { LEAD_CLAIM_SQL, LEAD_IDLE_MS } from './lead-voice.js';
|
|
22
|
-
import { grantStandsSql } from './flow-unattended.js';
|
|
23
23
|
export const WORK_INDEX_PAGE_LIMIT = 40;
|
|
24
24
|
export const WORK_INDEX_MAX_LIMIT = 100;
|
|
25
25
|
const EMPTY = { all: 0, 'needs-you': 0, running: 0, completed: 0 };
|
|
@@ -135,10 +135,7 @@ const PROJECTION = `WITH RECURSIVE admitted AS MATERIALIZED (
|
|
|
135
135
|
UNION ALL SELECT json_extract(leg.value,'$.provider') FROM json_each(CASE WHEN json_valid(sc.approved_route_json) THEN sc.approved_route_json ELSE '{}' END,'$.legs') leg
|
|
136
136
|
WHERE json_extract(leg.value,'$.phase')='build'))) signed_out,
|
|
137
137
|
(sc.approved_at IS NOT NULL AND sc.approved_by IS NOT NULL AND sc.approved_digest=sc.digest) approved,
|
|
138
|
-
(
|
|
139
|
-
WHERE om.repo=c.repo AND om.digest=sc.mode_digest AND om.revoked_at IS NULL AND om.absolute_expiry>$now
|
|
140
|
-
AND signer.role='approver' AND signer.revoked_at IS NULL AND ${accountReachesSql("signer", "c.repo")}))
|
|
141
|
-
OR (sc.approval_basis='grant' AND ${grantStandsSql("sc.mode_digest", "$now", "c.repo")})) dispatch_approval,
|
|
138
|
+
${authorityStandsSql("sc", "c.repo", "$now")} dispatch_approval,
|
|
142
139
|
CASE WHEN sc.task_id IS NULL THEN 0 ELSE so_work_scope_valid(json_object(
|
|
143
140
|
'touches',sc.touches,'acceptance_json',sc.acceptance_json,'budget_microusd',sc.budget_microusd,
|
|
144
141
|
'goal',CASE WHEN typeof(sc.goal)='text' THEN '' ELSE sc.goal END,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.72",
|
|
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"
|