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.
Files changed (93) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/agent-fence.d.ts +5 -2
  3. package/dist/agent-fence.js +5 -2
  4. package/dist/approval-authority.d.ts +20 -0
  5. package/dist/approval-authority.js +67 -0
  6. package/dist/assignment.js +26 -4
  7. package/dist/browser/workspace.css +1 -1
  8. package/dist/builder.js +48 -6
  9. package/dist/chat-decide.js +17 -2
  10. package/dist/check-access.js +2 -2
  11. package/dist/check-report.d.ts +109 -0
  12. package/dist/check-report.js +204 -0
  13. package/dist/claim.js +1 -1
  14. package/dist/commit-extract.d.ts +94 -0
  15. package/dist/commit-extract.js +436 -0
  16. package/dist/contracts/console-api.d.ts +1 -1
  17. package/dist/contracts/console-api.js +1 -0
  18. package/dist/contracts/flow.d.ts +16 -0
  19. package/dist/contracts/flow.js +3 -0
  20. package/dist/contracts/release-check.d.ts +335 -0
  21. package/dist/contracts/release-check.js +159 -0
  22. package/dist/contracts/release-terms.d.ts +60 -0
  23. package/dist/contracts/release-terms.js +70 -0
  24. package/dist/contracts/verification-receipt.d.ts +42 -8
  25. package/dist/contracts/verification-receipt.js +24 -7
  26. package/dist/contracts/verification-view.d.ts +1 -0
  27. package/dist/deploy-gate.d.ts +17 -0
  28. package/dist/deploy-gate.js +68 -0
  29. package/dist/dispatch.js +3 -10
  30. package/dist/dispose.js +4 -1
  31. package/dist/evidence-pack.d.ts +1 -1
  32. package/dist/evidence-pack.js +20 -2
  33. package/dist/evidence.d.ts +1 -0
  34. package/dist/evidence.js +2 -1
  35. package/dist/flow-engine.js +5 -1
  36. package/dist/flow-pull-request.d.ts +5 -2
  37. package/dist/flow-pull-request.js +14 -5
  38. package/dist/flow-schedule.js +25 -6
  39. package/dist/flows-ui.js +7 -1
  40. package/dist/invoke.d.ts +17 -6
  41. package/dist/invoke.js +48 -18
  42. package/dist/lead-status.js +5 -2
  43. package/dist/live-terminal.d.ts +10 -6
  44. package/dist/live-terminal.js +55 -14
  45. package/dist/nightly-check.d.ts +104 -0
  46. package/dist/nightly-check.js +362 -0
  47. package/dist/operate.js +39 -2
  48. package/dist/person-proof.d.ts +2 -0
  49. package/dist/person-proof.js +14 -0
  50. package/dist/plan-auto.js +4 -4
  51. package/dist/planner-source.d.ts +1 -1
  52. package/dist/project-delete-ui.js +1 -1
  53. package/dist/proof.js +2 -0
  54. package/dist/publish.js +156 -144
  55. package/dist/pull-request-flow.d.ts +2 -0
  56. package/dist/pull-request-flow.js +24 -2
  57. package/dist/release-adapters.d.ts +8 -0
  58. package/dist/release-adapters.js +212 -15
  59. package/dist/release-authority.d.ts +112 -0
  60. package/dist/release-authority.js +126 -0
  61. package/dist/release-carry.d.ts +233 -0
  62. package/dist/release-carry.js +645 -0
  63. package/dist/release-cli.d.ts +3 -0
  64. package/dist/release-cli.js +124 -5
  65. package/dist/release-coverage.js +3 -0
  66. package/dist/release-recheck.d.ts +146 -0
  67. package/dist/release-recheck.js +657 -0
  68. package/dist/release-view.d.ts +34 -0
  69. package/dist/release-view.js +27 -0
  70. package/dist/release.d.ts +131 -1
  71. package/dist/release.js +562 -244
  72. package/dist/remote-main.d.ts +2 -0
  73. package/dist/remote-main.js +1 -1
  74. package/dist/result-completion.d.ts +6 -0
  75. package/dist/result-completion.js +15 -1
  76. package/dist/result-follow-ups.js +29 -1
  77. package/dist/sandbox-runtime.d.ts +16 -5
  78. package/dist/sandbox-runtime.js +19 -6
  79. package/dist/scope.d.ts +115 -5
  80. package/dist/scope.js +219 -7
  81. package/dist/server/render-tasks.d.ts +8 -0
  82. package/dist/server/render-tasks.js +50 -9
  83. package/dist/server/route-table.js +1 -0
  84. package/dist/server/task-views.js +4 -0
  85. package/dist/server/tasks.js +12 -0
  86. package/dist/store.d.ts +227 -5
  87. package/dist/store.js +727 -37
  88. package/dist/task-outcome-cli.js +7 -3
  89. package/dist/toolroll-update.js +2 -2
  90. package/dist/verification-evidence.d.ts +25 -1
  91. package/dist/verification-evidence.js +57 -2
  92. package/dist/work-index.js +3 -6
  93. package/package.json +1 -1
@@ -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
- if (!authenticateApprover(store, acting.name, acting.token).ok)
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.');
@@ -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
- export function sealVerificationReceipt(store, root, runId, head, command, result, now, reusedFrom) {
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 === undefined ? 1 : 2, run: runId, head, base: source.baseRevision, scopeDigest: source.scopeDigest,
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.");
@@ -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 { accountReachesSql, readProjectAccess } from './project-access.js';
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
- ((sc.approval_basis IS NOT 'mode' AND sc.approval_basis IS NOT 'grant') OR (sc.approval_basis='mode' AND EXISTS(SELECT 1 FROM operating_mode om JOIN approver signer ON signer.name=om.signed_by
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.71",
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"