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.
Files changed (131) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/dist/agent-fence.d.ts +10 -2
  3. package/dist/agent-fence.js +47 -9
  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/browser/workspace.js +28 -28
  9. package/dist/builder.js +48 -6
  10. package/dist/chat-actions.js +13 -8
  11. package/dist/chat-decide.d.ts +47 -1
  12. package/dist/chat-decide.js +186 -5
  13. package/dist/check-access.js +2 -2
  14. package/dist/check-report.d.ts +109 -0
  15. package/dist/check-report.js +205 -0
  16. package/dist/claim.js +1 -1
  17. package/dist/coding-provider.js +4 -2
  18. package/dist/commit-extract.d.ts +111 -0
  19. package/dist/commit-extract.js +514 -0
  20. package/dist/contracts/console-api.d.ts +4 -4
  21. package/dist/contracts/console-api.js +5 -4
  22. package/dist/contracts/flow.d.ts +16 -0
  23. package/dist/contracts/flow.js +3 -0
  24. package/dist/contracts/release-check.d.ts +335 -0
  25. package/dist/contracts/release-check.js +159 -0
  26. package/dist/contracts/release-terms.d.ts +66 -0
  27. package/dist/contracts/release-terms.js +74 -0
  28. package/dist/contracts/verification-receipt.d.ts +42 -8
  29. package/dist/contracts/verification-receipt.js +24 -7
  30. package/dist/contracts/verification-view.d.ts +1 -0
  31. package/dist/deploy-gate.d.ts +17 -0
  32. package/dist/deploy-gate.js +68 -0
  33. package/dist/dispatch.js +3 -10
  34. package/dist/dispose.js +4 -1
  35. package/dist/evidence-pack.d.ts +1 -1
  36. package/dist/evidence-pack.js +20 -2
  37. package/dist/evidence.d.ts +1 -0
  38. package/dist/evidence.js +2 -1
  39. package/dist/exec.d.ts +1 -1
  40. package/dist/exec.js +7 -3
  41. package/dist/flow-engine.js +5 -1
  42. package/dist/flow-gallery-ui.js +4 -2
  43. package/dist/flow-gallery.d.ts +3 -0
  44. package/dist/flow-gallery.js +6 -2
  45. package/dist/flow-pull-request.d.ts +5 -2
  46. package/dist/flow-pull-request.js +14 -5
  47. package/dist/flow-schedule.js +25 -6
  48. package/dist/flow-triggers.d.ts +8 -0
  49. package/dist/flow-triggers.js +15 -0
  50. package/dist/flows-cli.js +13 -3
  51. package/dist/flows-ui.js +7 -1
  52. package/dist/guides.js +10 -1
  53. package/dist/invoke.d.ts +17 -6
  54. package/dist/invoke.js +48 -18
  55. package/dist/kits-ui.js +4 -2
  56. package/dist/kits.d.ts +6 -4
  57. package/dist/kits.js +9 -11
  58. package/dist/lead-status.js +5 -2
  59. package/dist/live-terminal.d.ts +10 -6
  60. package/dist/live-terminal.js +55 -14
  61. package/dist/mcp-connect.d.ts +10 -0
  62. package/dist/mcp-connect.js +2 -0
  63. package/dist/nightly-check.d.ts +104 -0
  64. package/dist/nightly-check.js +362 -0
  65. package/dist/operate.js +39 -2
  66. package/dist/person-proof.d.ts +2 -0
  67. package/dist/person-proof.js +14 -0
  68. package/dist/plan-auto.js +4 -4
  69. package/dist/planner-source.d.ts +1 -1
  70. package/dist/project-delete-ui.js +1 -1
  71. package/dist/project-tools.js +3 -1
  72. package/dist/proof.js +2 -0
  73. package/dist/protected-storage.d.ts +67 -0
  74. package/dist/protected-storage.js +398 -0
  75. package/dist/publish.js +156 -144
  76. package/dist/pull-request-flow.d.ts +2 -0
  77. package/dist/pull-request-flow.js +24 -2
  78. package/dist/release-adapters.d.ts +16 -1
  79. package/dist/release-adapters.js +292 -29
  80. package/dist/release-authority.d.ts +118 -0
  81. package/dist/release-authority.js +135 -0
  82. package/dist/release-card.d.ts +76 -0
  83. package/dist/release-card.js +252 -0
  84. package/dist/release-carry.d.ts +236 -0
  85. package/dist/release-carry.js +664 -0
  86. package/dist/release-cli.d.ts +3 -0
  87. package/dist/release-cli.js +158 -6
  88. package/dist/release-coverage.js +3 -0
  89. package/dist/release-git.d.ts +43 -0
  90. package/dist/release-git.js +106 -0
  91. package/dist/release-recheck.d.ts +146 -0
  92. package/dist/release-recheck.js +657 -0
  93. package/dist/release-view.d.ts +34 -0
  94. package/dist/release-view.js +27 -0
  95. package/dist/release.d.ts +139 -3
  96. package/dist/release.js +641 -250
  97. package/dist/remote-main.d.ts +2 -0
  98. package/dist/remote-main.js +1 -1
  99. package/dist/result-completion.d.ts +6 -0
  100. package/dist/result-completion.js +15 -1
  101. package/dist/result-follow-ups.js +29 -1
  102. package/dist/sandbox-runtime.d.ts +16 -5
  103. package/dist/sandbox-runtime.js +19 -6
  104. package/dist/scope.d.ts +122 -5
  105. package/dist/scope.js +236 -7
  106. package/dist/server/flows.js +40 -10
  107. package/dist/server/render-settings.js +1 -1
  108. package/dist/server/render-tasks.d.ts +8 -0
  109. package/dist/server/render-tasks.js +50 -9
  110. package/dist/server/route-table.js +1 -0
  111. package/dist/server/settings.js +26 -5
  112. package/dist/server/task-views.js +4 -0
  113. package/dist/server/tasks.js +12 -0
  114. package/dist/setup-guide.d.ts +2 -2
  115. package/dist/store.d.ts +230 -5
  116. package/dist/store.js +733 -37
  117. package/dist/subagent-memory.d.ts +1 -0
  118. package/dist/subagent-memory.js +1 -1
  119. package/dist/subagent-tools.d.ts +22 -6
  120. package/dist/subagent-tools.js +44 -8
  121. package/dist/subagent-work.d.ts +2 -0
  122. package/dist/subagent-work.js +5 -2
  123. package/dist/subagents-ui.js +6 -4
  124. package/dist/task-outcome-cli.js +7 -3
  125. package/dist/telegram.d.ts +6 -1
  126. package/dist/telegram.js +58 -6
  127. package/dist/toolroll-update.js +2 -2
  128. package/dist/verification-evidence.d.ts +25 -1
  129. package/dist/verification-evidence.js +57 -2
  130. package/dist/work-index.js +3 -6
  131. package/package.json +1 -1
@@ -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. */
@@ -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
- const completers = store.handle.prepare("SELECT DISTINCT actor FROM action_ledger WHERE run_id = ? AND action = ?").all(runId, COMPLETION_ACTION)
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
- if ((level === "full" && !batched) || (!batched && store.liveVerifyCommand(repo) === null))
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 and a check with sign-ins:
19
- * every other launch is denied them (SEATBELT_NO_KEYCHAIN). */
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 and a check with sign-ins: the keychain's services. The runtime'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
- /** Toolroll's rules for a launch's terms, in order: its own terminal (unless its read denies cover the devices), then
65
- * the denies that must come last. */
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 '"'"'. */
@@ -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, the keychain's services only for builds); the rest
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 and a check with sign-ins: the keychain's services. The runtime'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), then
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], SEATBELT_NO_OTHER_TERMINAL, ...terms.keychain === true ? [] : [SEATBELT_NO_KEYCHAIN]].join("\n");
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
- approvalBasis?: "password" | "mode" | "chat" | "grant" | null;
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
- proposedVia?: "mate" | "coordinator" | "scout" | null;
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?: "mate" | "coordinator" | null;
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;