toolroll 0.9.71 → 0.9.73
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 +155 -0
- package/dist/agent-fence.d.ts +10 -2
- package/dist/agent-fence.js +47 -9
- 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/browser/workspace.js +28 -28
- package/dist/builder.js +48 -6
- package/dist/chat-actions.js +13 -8
- package/dist/chat-decide.d.ts +47 -1
- package/dist/chat-decide.js +186 -5
- package/dist/check-access.js +2 -2
- package/dist/check-report.d.ts +109 -0
- package/dist/check-report.js +205 -0
- package/dist/claim.js +1 -1
- package/dist/coding-provider.js +4 -2
- package/dist/commit-extract.d.ts +111 -0
- package/dist/commit-extract.js +514 -0
- package/dist/contracts/console-api.d.ts +4 -4
- package/dist/contracts/console-api.js +5 -4
- 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 +66 -0
- package/dist/contracts/release-terms.js +74 -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/exec.d.ts +1 -1
- package/dist/exec.js +7 -3
- package/dist/flow-engine.js +5 -1
- package/dist/flow-gallery-ui.js +4 -2
- package/dist/flow-gallery.d.ts +3 -0
- package/dist/flow-gallery.js +6 -2
- 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/flow-triggers.d.ts +8 -0
- package/dist/flow-triggers.js +15 -0
- package/dist/flows-cli.js +13 -3
- package/dist/flows-ui.js +7 -1
- package/dist/guides.js +10 -1
- package/dist/invoke.d.ts +17 -6
- package/dist/invoke.js +48 -18
- package/dist/kits-ui.js +4 -2
- package/dist/kits.d.ts +6 -4
- package/dist/kits.js +9 -11
- package/dist/lead-status.js +5 -2
- package/dist/live-terminal.d.ts +10 -6
- package/dist/live-terminal.js +55 -14
- package/dist/mcp-connect.d.ts +10 -0
- package/dist/mcp-connect.js +2 -0
- 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/project-tools.js +3 -1
- package/dist/proof.js +2 -0
- package/dist/protected-storage.d.ts +67 -0
- package/dist/protected-storage.js +398 -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 +16 -1
- package/dist/release-adapters.js +292 -29
- package/dist/release-authority.d.ts +118 -0
- package/dist/release-authority.js +135 -0
- package/dist/release-card.d.ts +76 -0
- package/dist/release-card.js +252 -0
- package/dist/release-carry.d.ts +236 -0
- package/dist/release-carry.js +664 -0
- package/dist/release-cli.d.ts +3 -0
- package/dist/release-cli.js +158 -6
- package/dist/release-coverage.js +3 -0
- package/dist/release-git.d.ts +43 -0
- package/dist/release-git.js +106 -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 +139 -3
- package/dist/release.js +641 -250
- 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 +122 -5
- package/dist/scope.js +236 -7
- package/dist/server/flows.js +40 -10
- package/dist/server/render-settings.js +1 -1
- 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/settings.js +26 -5
- package/dist/server/task-views.js +4 -0
- package/dist/server/tasks.js +12 -0
- package/dist/setup-guide.d.ts +2 -2
- package/dist/store.d.ts +230 -5
- package/dist/store.js +733 -37
- package/dist/subagent-memory.d.ts +1 -0
- package/dist/subagent-memory.js +1 -1
- package/dist/subagent-tools.d.ts +22 -6
- package/dist/subagent-tools.js +44 -8
- package/dist/subagent-work.d.ts +2 -0
- package/dist/subagent-work.js +5 -2
- package/dist/subagents-ui.js +6 -4
- package/dist/task-outcome-cli.js +7 -3
- package/dist/telegram.d.ts +6 -1
- package/dist/telegram.js +58 -6
- 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/remote-main.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export declare function remoteUrlProblem(url: string): string | null;
|
|
|
25
25
|
export type RemoteGit = (args: readonly string[]) => Promise<ExecResult>;
|
|
26
26
|
export type RemoteMain = {
|
|
27
27
|
url: string;
|
|
28
|
+
/** The private Git directory the read fetched into (plane-owned; gone at `close`). */
|
|
29
|
+
gitDir: string;
|
|
28
30
|
/** The commit the recorded remote's main is at, as fetched just now. */
|
|
29
31
|
main: string;
|
|
30
32
|
/** Git in the private directory, hardened as above. */
|
package/dist/remote-main.js
CHANGED
|
@@ -165,7 +165,7 @@ export class RemoteMains {
|
|
|
165
165
|
const got = await this.exec(dir, ["fetch", "--quiet", "--no-tags", "--no-write-fetch-head", "--", from, `+${head}:refs/toolroll/results/${head}`], "always");
|
|
166
166
|
return got.code === 0 && this.clean(dir) && (await git(["cat-file", "-e", `${head}^{commit}`])).code === 0;
|
|
167
167
|
};
|
|
168
|
-
return { ok: true, remote: { url, main, git, bring } };
|
|
168
|
+
return { ok: true, remote: { url, gitDir: dir, main, git, bring } };
|
|
169
169
|
}
|
|
170
170
|
close() {
|
|
171
171
|
for (const dir of this.dirs.values()) {
|
|
@@ -27,3 +27,9 @@ export declare function familyChangedFiles(store: Store, taskId: string, runId:
|
|
|
27
27
|
* revises) changed protected files on a one-person approval, and nobody but its approver and requesters
|
|
28
28
|
* has marked it complete. */
|
|
29
29
|
export declare function protectedPublicationHold(store: Store, runId: number, root: string): string | null;
|
|
30
|
+
/** A completion's mark (v134, approval policy §1.7a): whether a person or a machine credential made it. */
|
|
31
|
+
export declare const COMPLETION_BY_PERSON = "proof person";
|
|
32
|
+
export declare const COMPLETION_BY_MACHINE = "proof machine";
|
|
33
|
+
/** Whether this person completing an already-complete result would release a protected publication hold: protected
|
|
34
|
+
* work needs someone other than its approver, no person's completion on record provides it, and this person can. */
|
|
35
|
+
export declare function personCompletionOwed(store: Store, taskId: string, runId: number, receiptDigest: string, person: string, root: string): boolean;
|
|
@@ -78,9 +78,23 @@ export function protectedPublicationHold(store, runId, root) {
|
|
|
78
78
|
const changed = familyChangedFiles(store, taskId, runId, root);
|
|
79
79
|
if (store.protectedResultProblem(taskId, changed, null) === null)
|
|
80
80
|
return null;
|
|
81
|
-
|
|
81
|
+
// Only a person's completion counts here (approval policy §1.7a): one a machine credential made, or one from before
|
|
82
|
+
// completions said who made them, never stands as the second person.
|
|
83
|
+
const completers = store.handle.prepare("SELECT DISTINCT actor FROM action_ledger WHERE run_id = ? AND action = ? AND detail = ?").all(runId, COMPLETION_ACTION, COMPLETION_BY_PERSON)
|
|
82
84
|
.map(row => String(row["actor"])).filter(actor => actor.startsWith("operator:")).map(actor => actor.slice("operator:".length));
|
|
83
85
|
if (completers.some(name => store.protectedResultProblem(taskId, changed, name) === null))
|
|
84
86
|
return null;
|
|
85
87
|
return "held: it changed protected files on a one-person approval, so it publishes once someone other than its approver marks it complete";
|
|
86
88
|
}
|
|
89
|
+
/** A completion's mark (v134, approval policy §1.7a): whether a person or a machine credential made it. */
|
|
90
|
+
export const COMPLETION_BY_PERSON = "proof person";
|
|
91
|
+
export const COMPLETION_BY_MACHINE = "proof machine";
|
|
92
|
+
/** Whether this person completing an already-complete result would release a protected publication hold: protected
|
|
93
|
+
* work needs someone other than its approver, no person's completion on record provides it, and this person can. */
|
|
94
|
+
export function personCompletionOwed(store, taskId, runId, receiptDigest, person, root) {
|
|
95
|
+
const changed = familyChangedFiles(store, taskId, runId, root);
|
|
96
|
+
if (store.protectedResultProblem(taskId, changed, null) === null || store.protectedResultProblem(taskId, changed, person) !== null)
|
|
97
|
+
return false;
|
|
98
|
+
return store.handle.prepare("SELECT 1 FROM action_ledger WHERE run_id = ? AND action = ? AND outcome = ? AND actor = ? AND detail = ?")
|
|
99
|
+
.get(runId, COMPLETION_ACTION, receiptDigest, `operator:${person}`, COMPLETION_BY_PERSON) === undefined;
|
|
100
|
+
}
|
|
@@ -2,6 +2,7 @@ import { checkoutRunner, ranAtLine, widestRan } from "./invoke.js";
|
|
|
2
2
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { belongsToRelease } from "./release-authority.js";
|
|
5
6
|
import { run as execRun } from "./exec.js";
|
|
6
7
|
import { approvedCommandShell, SETUP_ENV_ALLOWLIST, SETUP_ENV_DENYLIST } from "./builder.js";
|
|
7
8
|
import { runWithIsolatedDatabase } from "./child-database.js";
|
|
@@ -288,7 +289,34 @@ export function fullCheckGate(store, runId, now) {
|
|
|
288
289
|
const fulls = followUpChecksOf(store, runId, now).filter(one => one.level === "full" && one.head === run.headRevision);
|
|
289
290
|
// A Full build that waited for a batch check has had no check of its own: Merge waits for the batch, like Quick does.
|
|
290
291
|
const batched = level === "full" && fulls.some(one => one.why === "batch");
|
|
291
|
-
|
|
292
|
+
// A recorded Full level is not a pass (review round 1): the build's own check must have passed, and a release
|
|
293
|
+
// check that re-ran only what failed is proved by its release alone, never cleared here.
|
|
294
|
+
if (level === "full" && !batched) {
|
|
295
|
+
const targeted = store.handle.prepare("SELECT 1 AS hit FROM release_check_attempt WHERE run = ? AND mode = 'targeted'").get(runId) !== undefined;
|
|
296
|
+
if (targeted)
|
|
297
|
+
return { state: "failed", message: "This result's check re-ran only what failed; it merges only through its release." };
|
|
298
|
+
const own = store.handle.prepare("SELECT status FROM run_check WHERE run = ?").get(runId);
|
|
299
|
+
if (own !== undefined && own["status"] === "passed")
|
|
300
|
+
return { state: "clear" };
|
|
301
|
+
// An ordinary task's own check didn't pass (or has no row): a Full check run again on this same commit decides
|
|
302
|
+
// (review round 2). A release's task never: it merges only through its release.
|
|
303
|
+
const taskId = store.refById(run.taskRef)?.externalId ?? null;
|
|
304
|
+
const last = fulls.at(-1);
|
|
305
|
+
if (last !== undefined && (taskId === null || !belongsToRelease(store, taskId))) {
|
|
306
|
+
if (last.state === "passed")
|
|
307
|
+
return { state: "clear" };
|
|
308
|
+
if (last.state === "failed")
|
|
309
|
+
return { state: "failed", message: `The full check failed on this commit${last.exitCode === null ? "" : ` (exit ${last.exitCode})`}. Fix it, or merge anyway.` };
|
|
310
|
+
if (last.state === "not-run")
|
|
311
|
+
return { state: "not-run", message: `The full check didn't finish on this commit${last.note === null ? "" : `: ${last.note}`} Run it again, or merge anyway.` };
|
|
312
|
+
return { state: "waiting", message: "The full check is still running on this commit. Merge once it passes, or merge anyway." };
|
|
313
|
+
}
|
|
314
|
+
// No row is a check pending or never requested, never a pass (security review).
|
|
315
|
+
if (own === undefined)
|
|
316
|
+
return { state: "not-requested", message: "The full check hasn't run on this commit yet. Merge once it passes, or merge anyway." };
|
|
317
|
+
return { state: "failed", message: "The full check didn't pass on this commit. Fix it, or merge anyway." };
|
|
318
|
+
}
|
|
319
|
+
if (!batched && store.liveVerifyCommand(repo) === null)
|
|
292
320
|
return { state: "clear" };
|
|
293
321
|
const last = fulls.at(-1);
|
|
294
322
|
if (last === undefined)
|
|
@@ -15,9 +15,11 @@ export type SrtTerms = {
|
|
|
15
15
|
readConfig: FsReadRestrictionConfig;
|
|
16
16
|
writeConfig: FsWriteRestrictionConfig;
|
|
17
17
|
allowMachLookup: string[];
|
|
18
|
-
/** macOS: the launch may reach the keychain's services (securityd). Only a build's agent
|
|
19
|
-
*
|
|
18
|
+
/** macOS: the launch may reach the keychain's services (securityd). Only a build's agent: every other launch is
|
|
19
|
+
* denied them (SEATBELT_NO_KEYCHAIN). */
|
|
20
20
|
keychain?: boolean;
|
|
21
|
+
/** macOS: a project check's launch, whose Chromium may register and reach its own service (SEATBELT_CHROMIUM). */
|
|
22
|
+
browser?: boolean;
|
|
21
23
|
network: {
|
|
22
24
|
kind: "open";
|
|
23
25
|
} | {
|
|
@@ -56,13 +58,22 @@ export declare const SEATBELT_PTY: string;
|
|
|
56
58
|
* _IOW('t', 114, char)), its own controlling terminal included. */
|
|
57
59
|
export declare const SEATBELT_NO_OTHER_TERMINAL: string;
|
|
58
60
|
/**
|
|
59
|
-
* Last in every launch's profile but a build agent's
|
|
61
|
+
* Last in every launch's profile but a build agent's: the keychain's services. The runtime's
|
|
60
62
|
* baseline lets every sandboxed process ask securityd, so it reads the login keychain's items whose access list lets
|
|
61
63
|
* any program read them (anthropics/sandbox-runtime#755). Build agents keep it (Claude Code signs in from the keychain).
|
|
62
64
|
*/
|
|
63
65
|
export declare const SEATBELT_NO_KEYCHAIN: string;
|
|
64
|
-
/**
|
|
65
|
-
*
|
|
66
|
+
/**
|
|
67
|
+
* Chromium, a project check's launch only: Playwright's default browser (its Chromium headless shell) registers its
|
|
68
|
+
* port rendezvous service as org.chromium.Chromium.MachPortRendezvousServer.<its pid> at start, and its helpers look
|
|
69
|
+
* it up. Exactly that name. Chromium takes the prefix from its bundle id with no switch or variable to change it, and
|
|
70
|
+
* Seatbelt can't tie the pid to the caller, so the residual (another pid's name registered or looked up) is written
|
|
71
|
+
* up in SECURITY.md. Chrome for Testing (`channel: "chromium"`, headed runs) gets nothing: such a check runs Not
|
|
72
|
+
* sandboxed.
|
|
73
|
+
*/
|
|
74
|
+
export declare const SEATBELT_CHROMIUM: string;
|
|
75
|
+
/** Toolroll's rules for a launch's terms, in order: its own terminal (unless its read denies cover the devices), a
|
|
76
|
+
* check's Chromium service, then the denies that must come last. */
|
|
66
77
|
export declare function seatbeltRulesFor(terms: SrtTerms): string;
|
|
67
78
|
/** The runtime's own quoting (its utils/shell-quote.js): a word bare when nothing in it is special, otherwise in single
|
|
68
79
|
* quotes with a quote inside as '"'"'. */
|
package/dist/sandbox-runtime.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* its own code, per launch, from a small static config (read denies with exact re-allows, write roots with denies
|
|
5
5
|
* inside them, mach services, the run's proxy). Toolroll adds only what that config says (agent-fence.ts srtTermsOf),
|
|
6
6
|
* plus, on macOS, a few static rules of its own appended to the end of the profile (seatbeltRulesFor: a terminal of the
|
|
7
|
-
* launch's own and no other, no keystrokes pushed into a terminal,
|
|
8
|
-
* of the rules, their baseline and their limits are the runtime's.
|
|
7
|
+
* launch's own and no other, no keystrokes pushed into a terminal, a check's Chromium service, the keychain's services
|
|
8
|
+
* only for builds); the rest of the rules, their baseline and their limits are the runtime's.
|
|
9
9
|
*
|
|
10
10
|
* macOS: its profile builder runs in this process (it is synchronous), with this process's working folder set to the
|
|
11
11
|
* launch's for the call, because the runtime anchors its mandatory write denies (.git/hooks, .vscode, shell profiles)
|
|
@@ -116,7 +116,7 @@ export const SEATBELT_NO_OTHER_TERMINAL = [
|
|
|
116
116
|
"(deny file-ioctl (ioctl-command 2147578994))",
|
|
117
117
|
].join("\n");
|
|
118
118
|
/**
|
|
119
|
-
* Last in every launch's profile but a build agent's
|
|
119
|
+
* Last in every launch's profile but a build agent's: the keychain's services. The runtime's
|
|
120
120
|
* baseline lets every sandboxed process ask securityd, so it reads the login keychain's items whose access list lets
|
|
121
121
|
* any program read them (anthropics/sandbox-runtime#755). Build agents keep it (Claude Code signs in from the keychain).
|
|
122
122
|
*/
|
|
@@ -124,6 +124,18 @@ export const SEATBELT_NO_KEYCHAIN = [
|
|
|
124
124
|
"; Toolroll: no keychain",
|
|
125
125
|
"(deny mach-lookup (global-name \"com.apple.SecurityServer\") (global-name \"com.apple.securityd.xpc\"))",
|
|
126
126
|
].join("\n");
|
|
127
|
+
/**
|
|
128
|
+
* Chromium, a project check's launch only: Playwright's default browser (its Chromium headless shell) registers its
|
|
129
|
+
* port rendezvous service as org.chromium.Chromium.MachPortRendezvousServer.<its pid> at start, and its helpers look
|
|
130
|
+
* it up. Exactly that name. Chromium takes the prefix from its bundle id with no switch or variable to change it, and
|
|
131
|
+
* Seatbelt can't tie the pid to the caller, so the residual (another pid's name registered or looked up) is written
|
|
132
|
+
* up in SECURITY.md. Chrome for Testing (`channel: "chromium"`, headed runs) gets nothing: such a check runs Not
|
|
133
|
+
* sandboxed.
|
|
134
|
+
*/
|
|
135
|
+
export const SEATBELT_CHROMIUM = [
|
|
136
|
+
"; Toolroll: the Chromium headless shell port rendezvous service",
|
|
137
|
+
"(allow mach-register mach-lookup (global-name-regex #\"^org\\.chromium\\.Chromium\\.MachPortRendezvousServer\\.[0-9]+$\"))",
|
|
138
|
+
].join("\n");
|
|
127
139
|
/** Whether a read deny covers the launch's own terminal devices (/dev/ptmx, /dev/ttys*): then it gets none. */
|
|
128
140
|
function deniesTerminals(deny) {
|
|
129
141
|
const glob = deny.search(/[*?[]/);
|
|
@@ -133,11 +145,12 @@ function deniesTerminals(deny) {
|
|
|
133
145
|
return devices.some(device => device.startsWith(path));
|
|
134
146
|
return path === "" || path === "/dev" || devices.includes(path) || /^\/dev\/ttys[0-9]*$/.test(path);
|
|
135
147
|
}
|
|
136
|
-
/** Toolroll's rules for a launch's terms, in order: its own terminal (unless its read denies cover the devices),
|
|
137
|
-
* the denies that must come last. */
|
|
148
|
+
/** Toolroll's rules for a launch's terms, in order: its own terminal (unless its read denies cover the devices), a
|
|
149
|
+
* check's Chromium service, then the denies that must come last. */
|
|
138
150
|
export function seatbeltRulesFor(terms) {
|
|
139
151
|
const denied = [...terms.readConfig.denyOnly, ...terms.readConfig.literalDenyOnly ?? []].some(one => deniesTerminals(typeof one === "string" ? one : String(one)));
|
|
140
|
-
return [...denied ? [] : [SEATBELT_PTY],
|
|
152
|
+
return [...denied ? [] : [SEATBELT_PTY], ...terms.browser === true ? [SEATBELT_CHROMIUM] : [], SEATBELT_NO_OTHER_TERMINAL,
|
|
153
|
+
...terms.keychain === true ? [] : [SEATBELT_NO_KEYCHAIN]].join("\n");
|
|
141
154
|
}
|
|
142
155
|
const BARE_WORD = /^[A-Za-z0-9_./:@+,-][A-Za-z0-9_./:=@+,-]*$/;
|
|
143
156
|
/** The runtime's own quoting (its utils/shell-quote.js): a word bare when nothing in it is special, otherwise in single
|
package/dist/scope.d.ts
CHANGED
|
@@ -353,8 +353,10 @@ export type Scope = SavedScopeTerms & {
|
|
|
353
353
|
/** How the approval happened (v29): 'password' = the ceremony;
|
|
354
354
|
* 'mode' = sealed by the signer's live mode (modeDigest names it);
|
|
355
355
|
* 'chat' = the owner's own two taps in their paired chat, under their chat-approval setting or a signed term
|
|
356
|
-
* (modeDigest names that authority; the yes is the person's and doesn't lapse with it).
|
|
357
|
-
|
|
356
|
+
* (modeDigest names that authority; the yes is the person's and doesn't lapse with it).
|
|
357
|
+
* 'schedule' = a standing order's firing (modeDigest names the order); 'lead' = the owner's lead (never a person);
|
|
358
|
+
* 'unknown' = a stored word this code doesn't write, which no reader treats as authority (approval-policy plan). */
|
|
359
|
+
approvalBasis?: ApprovalBasis | "unknown" | null;
|
|
358
360
|
modeDigest?: string | null;
|
|
359
361
|
/** v24 (optional so hand-built scopes in tests stay valid): the working
|
|
360
362
|
* execution profile, its resolution state, and the immutable snapshot
|
|
@@ -403,9 +405,21 @@ export type Scope = SavedScopeTerms & {
|
|
|
403
405
|
/** Who wrote the text (mate arc, ruling 2): a confirmed lead proposal,
|
|
404
406
|
* the coordinator, a scout, or a person (null). Read raw and proved by
|
|
405
407
|
* `termsProblem` — a word outside these is a stated problem, never a
|
|
406
|
-
* value that slips past the lead quarantine at a mode seal.
|
|
407
|
-
|
|
408
|
+
* value that slips past the lead quarantine at a mode seal. `lead`: the lead wrote it through its token (CLI); it is
|
|
409
|
+
* the lead's own text, not another machine's, so it isn't quarantined (`quarantinedAuthor`). */
|
|
410
|
+
proposedVia?: ScopeAuthor | null;
|
|
411
|
+
/** v135 (docs/plans/release-once.md): a release scope's terms — repository, version, pull request, approved commit,
|
|
412
|
+
* carry policy, grant, check access, expiry. Part of the digest when present; a person's seal of them opens the
|
|
413
|
+
* release's authority record. */
|
|
414
|
+
releaseTerms?: ReleaseTerms | null;
|
|
408
415
|
};
|
|
416
|
+
/** Who wrote a scope's text, when not a person (approval-policy plan §1.4). */
|
|
417
|
+
export type ScopeAuthor = "mate" | "coordinator" | "scout" | "lead";
|
|
418
|
+
/** The authorities a seal can stand on (approval-policy plan §1.5). Anything else stands on nothing. */
|
|
419
|
+
/** `carried`: a later commit of a release carried under a person's release approval (release-once.md); only the carry
|
|
420
|
+
* transaction ever writes it. */
|
|
421
|
+
export type ApprovalBasis = "password" | "mode" | "chat" | "grant" | "schedule" | "lead" | "carried";
|
|
422
|
+
export declare const APPROVAL_BASES: readonly ApprovalBasis[];
|
|
409
423
|
export type Approval = {
|
|
410
424
|
approved: true;
|
|
411
425
|
at: string;
|
|
@@ -439,11 +453,14 @@ export type ScopeInput = {
|
|
|
439
453
|
posture?: "escalated";
|
|
440
454
|
/** Who wrote this text (mate arc, ruling 2): `mate` for a confirmed mate
|
|
441
455
|
* proposal — mode coverage then never seals it; a human rewrite clears it. */
|
|
442
|
-
proposedVia?:
|
|
456
|
+
proposedVia?: ScopeAuthor | null;
|
|
443
457
|
/** v39: the signed acceptance rubric. Absent digests exactly like `[]` —
|
|
444
458
|
* see `Scope.acceptance`. `propose` itself never requires this to be
|
|
445
459
|
* non-empty; the roads that call it do. */
|
|
446
460
|
acceptance?: readonly AcceptanceCriterion[];
|
|
461
|
+
/** v135: release terms (release-carry.ts releaseTermsFor), from the plane's own records. Undefined keeps any terms
|
|
462
|
+
* the scope already carries; null removes them. */
|
|
463
|
+
releaseTerms?: ReleaseTerms | null;
|
|
447
464
|
now: Date;
|
|
448
465
|
mutation?: Mutation;
|
|
449
466
|
};
|
|
@@ -466,6 +483,8 @@ export declare function digestOf(scope: Pick<Scope, "goal" | "outOfScope" | "tou
|
|
|
466
483
|
/** v69: a prepared commit, folded in only when present so every earlier
|
|
467
484
|
* digest is untouched. */
|
|
468
485
|
candidate?: string | null;
|
|
486
|
+
/** v135: release terms, folded in only when present. */
|
|
487
|
+
releaseTerms?: ReleaseTerms | null;
|
|
469
488
|
}, target?: ExecutionProfile | null | {
|
|
470
489
|
chain: readonly ChainEntry[];
|
|
471
490
|
}, route?: PhaseRoute | null): string;
|
|
@@ -562,6 +581,11 @@ export type ApproveResult = {
|
|
|
562
581
|
| {
|
|
563
582
|
ok: false;
|
|
564
583
|
reason: "password-now";
|
|
584
|
+
}
|
|
585
|
+
/** Approval policy, phase 1: a machine credential (the lead's token, a remote API token) is not a person. */
|
|
586
|
+
| {
|
|
587
|
+
ok: false;
|
|
588
|
+
reason: "machine-proof";
|
|
565
589
|
};
|
|
566
590
|
/**
|
|
567
591
|
* Whether this name-and-token pair is a person the store knows. Shared by
|
|
@@ -835,3 +859,96 @@ export declare function acceptanceWords(criteria: readonly AcceptanceCriterion[]
|
|
|
835
859
|
export declare function scopeRouteWords(scope: Pick<Scope, "proposedRouteJson">, readiness?: ReadinessLookup): string[];
|
|
836
860
|
/** The scope, in the words an operator has to be able to agree or disagree with. */
|
|
837
861
|
export declare function describeScope(scope: Scope, readiness?: ReadinessLookup): string[];
|
|
862
|
+
/** The approval sheet's release lines (release-once.md): the release in one sentence, then what asks again. */
|
|
863
|
+
export declare function releaseTermsSentence(terms: ReleaseTerms): string;
|
|
864
|
+
/** What asks again, in full: the terms behind the sentence. */
|
|
865
|
+
export declare function releaseAsksAgainWords(terms: ReleaseTerms): string[];
|
|
866
|
+
export { personProof } from "./person-proof.js";
|
|
867
|
+
import { type ReleaseTerms } from "./contracts/release-terms.js";
|
|
868
|
+
import { type ReleaseAuthority, type ReleaseCarry } from "./release-authority.js";
|
|
869
|
+
/** Whether the command running now is a machine's: the lead (its token) or a remote API run. Trusted context only. */
|
|
870
|
+
export declare const machineActing: () => boolean;
|
|
871
|
+
/** A proof row's detail, either form: the legacy text (`<at> · generation <n> · digest <d>`) or the versioned record
|
|
872
|
+
* (`{"v":1,"at":…,"digest":…}`, more fields allowed). Any other shape is no proof. */
|
|
873
|
+
export declare function proofDetailOf(detail: unknown): {
|
|
874
|
+
at: string;
|
|
875
|
+
digest: string;
|
|
876
|
+
} | null;
|
|
877
|
+
/**
|
|
878
|
+
* A person's approval of this task as it stands: approved, on the current digest, by the ceremony (`password`) or a
|
|
879
|
+
* paired-chat tap (`chat`), with a person's proof recorded for that approver and those exact bytes. Release, deploy
|
|
880
|
+
* and the protected-work counters ask this; a lead, a mode, a grant, a schedule or an approval with no proof is not
|
|
881
|
+
* a person's.
|
|
882
|
+
*/
|
|
883
|
+
export declare function personApprovalOf(store: Store, taskId: string): {
|
|
884
|
+
ok: true;
|
|
885
|
+
by: string;
|
|
886
|
+
at: string;
|
|
887
|
+
method: string;
|
|
888
|
+
} | {
|
|
889
|
+
ok: false;
|
|
890
|
+
why: string;
|
|
891
|
+
};
|
|
892
|
+
/**
|
|
893
|
+
* Who made a seal that says it came through a person's road (`password`, `chat`), from the acts recorded AT THAT SEAL
|
|
894
|
+
* (approval policy §1.5): the proof rows its approver left at the seal's own instant, and the lead's task act then.
|
|
895
|
+
* - `machine`: any proof there that isn't a person's (a lead or remote token, or a word this code doesn't know), or the
|
|
896
|
+
* lead's act — fail closed, whatever its digest says;
|
|
897
|
+
* - `person`: a person's proof there for these exact bytes (parsed, either form);
|
|
898
|
+
* - `automation`: a standing order's firing that no person's proof stands behind;
|
|
899
|
+
* - `unproven`: none of these (a seal from before proof rows): it dispatches as before, and is never a person's yes.
|
|
900
|
+
* An older proof for the same bytes never vouches for a later seal. The dispatch belts ask the same in SQL
|
|
901
|
+
* (approval-authority.ts).
|
|
902
|
+
*/
|
|
903
|
+
export declare function sealOriginOf(store: Store, taskId: string, by: string, digest: string, at: string): {
|
|
904
|
+
origin: "machine";
|
|
905
|
+
lead: boolean;
|
|
906
|
+
} | {
|
|
907
|
+
origin: "person";
|
|
908
|
+
method: string;
|
|
909
|
+
} | {
|
|
910
|
+
origin: "automation";
|
|
911
|
+
} | {
|
|
912
|
+
origin: "unproven";
|
|
913
|
+
};
|
|
914
|
+
/** Text another machine wrote (a confirmed chat proposal, a coordinator, a scout): no mode, grant, remembered password
|
|
915
|
+
* or chat tap seals it. The lead's own CLI text (`lead`) is not quarantined. */
|
|
916
|
+
export declare const quarantinedAuthor: (via: string | null | undefined) => boolean;
|
|
917
|
+
/** What a release task is about to do. */
|
|
918
|
+
export type ReleaseAction = "dispatch" | "deploy" | "merge" | "complete";
|
|
919
|
+
export type LiveRelease = {
|
|
920
|
+
applies: false;
|
|
921
|
+
} | {
|
|
922
|
+
applies: true;
|
|
923
|
+
ok: true;
|
|
924
|
+
authority: ReleaseAuthority;
|
|
925
|
+
carry: ReleaseCarry | null;
|
|
926
|
+
} | {
|
|
927
|
+
applies: true;
|
|
928
|
+
ok: false;
|
|
929
|
+
why: string;
|
|
930
|
+
authority: ReleaseAuthority | null;
|
|
931
|
+
};
|
|
932
|
+
/**
|
|
933
|
+
* Whether a task that belongs to a release authority may do `action` now. It applies to EVERY such task, whether its
|
|
934
|
+
* seal is the person's own or `carried`, and requires all of: the authority's current generation (never superseded,
|
|
935
|
+
* revoked or expired before it started); a state that fits the action; the task is the authority's current tip; the
|
|
936
|
+
* anchor's seal is a person's (personApprovalOf, unchanged) on the exact sealed anchor digest; the anchor's approver is
|
|
937
|
+
* still an active approver with access to the project; for a carried task, a carry row for exactly this task, scope
|
|
938
|
+
* digest, candidate and generation; the release check grant and check access as sealed (access only ever the fence);
|
|
939
|
+
* a sealed policy this runtime knows; and, for deploy and merge, no open main-check failure blocking a composed check.
|
|
940
|
+
* A task that belongs to no release reads `applies: false` — the ordinary rules decide it.
|
|
941
|
+
*/
|
|
942
|
+
export declare function liveReleaseOf(store: Store, taskId: string, action: ReleaseAction, now?: Date): LiveRelease;
|
|
943
|
+
/**
|
|
944
|
+
* Open main-check failures (the nightly full check, part 3) block the deploy and the merge of a release whose gate
|
|
945
|
+
* check was composed from a targeted run: a full pass is needed first. Null when nothing blocks.
|
|
946
|
+
*/
|
|
947
|
+
export declare function mainDebtProblem(store: Store, repo: string, taskId: string): string | null;
|
|
948
|
+
/**
|
|
949
|
+
* A deploy gate (docs/plans/chat-management.md §0a.5): a scope whose signed terms name a prepared candidate or carry
|
|
950
|
+
* release terms. Both are in the scope digest and written at filing, before any release authority exists, so this is
|
|
951
|
+
* read from the scope alone: a first approval and a fallback gate filed without --release are deploy gates too. From
|
|
952
|
+
* chat it is approved only through the release card.
|
|
953
|
+
*/
|
|
954
|
+
export declare function deployGateScope(scope: Pick<Scope, "candidate" | "releaseTerms">): boolean;
|