toolroll 0.9.51 → 0.9.53

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 (164) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +55 -80
  3. package/dist/agentconfig.d.ts +11 -36
  4. package/dist/agentconfig.js +3 -62
  5. package/dist/approval-policy.d.ts +1 -1
  6. package/dist/approval-policy.js +1 -1
  7. package/dist/backup.d.ts +8 -0
  8. package/dist/backup.js +10 -1
  9. package/dist/board.d.ts +3 -19
  10. package/dist/board.js +4 -42
  11. package/dist/browser/workspace.css +1 -1
  12. package/dist/browser/workspace.js +18 -18
  13. package/dist/browser-workspace.d.ts +3 -1
  14. package/dist/builder.d.ts +9 -40
  15. package/dist/builder.js +50 -257
  16. package/dist/chat-actions.js +3 -14
  17. package/dist/chat-controls.d.ts +0 -4
  18. package/dist/chat-controls.js +0 -1
  19. package/dist/chat-decide.js +1 -3
  20. package/dist/chat-delivery.d.ts +0 -2
  21. package/dist/chat-delivery.js +0 -1
  22. package/dist/chat-quiet.js +2 -2
  23. package/dist/chat-task-actions.d.ts +1 -1
  24. package/dist/chat-task-actions.js +0 -2
  25. package/dist/claim.d.ts +79 -244
  26. package/dist/claim.js +688 -1023
  27. package/dist/cli.d.ts +2 -0
  28. package/dist/cli.js +10 -2
  29. package/dist/contracts/cli.d.ts +0 -13
  30. package/dist/contracts/cli.js +0 -2
  31. package/dist/contracts/console-api.d.ts +5 -10
  32. package/dist/contracts/console-api.js +4 -10
  33. package/dist/contracts/flow.d.ts +50 -0
  34. package/dist/contracts/flow.js +26 -1
  35. package/dist/contracts/lead-tools.d.ts +0 -13
  36. package/dist/contracts/lead-tools.js +3 -3
  37. package/dist/contracts/store-json.d.ts +1 -25
  38. package/dist/contracts/store-json.js +3 -13
  39. package/dist/converse.d.ts +1 -11
  40. package/dist/converse.js +6 -38
  41. package/dist/coordinator.d.ts +0 -1
  42. package/dist/coordinator.js +3 -5
  43. package/dist/database-reclaim.d.ts +4 -0
  44. package/dist/database-reclaim.js +13 -0
  45. package/dist/demo.d.ts +1 -1
  46. package/dist/demo.js +17 -27
  47. package/dist/desktop-update-gate.js +0 -1
  48. package/dist/desktop-update.d.ts +1 -1
  49. package/dist/desktop-update.js +1 -3
  50. package/dist/dispatch.d.ts +1 -1
  51. package/dist/dispatch.js +1 -1
  52. package/dist/dispose.d.ts +2 -3
  53. package/dist/dispose.js +17 -92
  54. package/dist/envelope.js +0 -3
  55. package/dist/exec.d.ts +0 -64
  56. package/dist/exec.js +3 -356
  57. package/dist/exhaustion.d.ts +22 -76
  58. package/dist/exhaustion.js +14 -140
  59. package/dist/export.js +4 -4
  60. package/dist/flow-engine.js +1 -1
  61. package/dist/flow-schedule.d.ts +154 -0
  62. package/dist/flow-schedule.js +321 -0
  63. package/dist/flow-share.js +1 -1
  64. package/dist/flow-triggers.d.ts +6 -1
  65. package/dist/flow-triggers.js +65 -10
  66. package/dist/flows-cli.js +8 -4
  67. package/dist/flows-ui.js +2 -0
  68. package/dist/flows.js +1 -1
  69. package/dist/guides.js +9 -29
  70. package/dist/invoke.d.ts +1 -24
  71. package/dist/invoke.js +17 -259
  72. package/dist/lead-status.js +2 -4
  73. package/dist/lead-voice.js +1 -1
  74. package/dist/live-bus.d.ts +3 -3
  75. package/dist/live-bus.js +3 -3
  76. package/dist/mate-doors.d.ts +1 -2
  77. package/dist/mate-doors.js +12 -25
  78. package/dist/mate-tools.d.ts +2 -2
  79. package/dist/mate-tools.js +10 -22
  80. package/dist/modes.d.ts +6 -7
  81. package/dist/modes.js +7 -13
  82. package/dist/monitoring.js +32 -8
  83. package/dist/notification-delivery.d.ts +19 -0
  84. package/dist/notification-delivery.js +37 -0
  85. package/dist/operate-remote-arguments.js +2 -6
  86. package/dist/operate-remote.js +6 -7
  87. package/dist/operate.d.ts +0 -6
  88. package/dist/operate.js +283 -2378
  89. package/dist/phase-routing.d.ts +13 -23
  90. package/dist/phase-routing.js +13 -35
  91. package/dist/plan-auto.js +2 -2
  92. package/dist/planner-source.js +2 -1
  93. package/dist/policy.d.ts +1 -4
  94. package/dist/policy.js +0 -8
  95. package/dist/process-recovery-eligibility.js +2 -3
  96. package/dist/process-recovery-settlement.js +1 -0
  97. package/dist/project-delete.js +3 -15
  98. package/dist/proposal.d.ts +4 -24
  99. package/dist/proposal.js +3 -88
  100. package/dist/provider-auth.d.ts +3 -3
  101. package/dist/provider-auth.js +4 -6
  102. package/dist/provider.d.ts +11 -25
  103. package/dist/provider.js +4 -36
  104. package/dist/push.js +1 -1
  105. package/dist/recipe-ui.js +6 -6
  106. package/dist/recipes.d.ts +3 -3
  107. package/dist/recipes.js +27 -23
  108. package/dist/retention.js +6 -5
  109. package/dist/run-closeout.d.ts +1 -1
  110. package/dist/run-closeout.js +1 -4
  111. package/dist/scope.d.ts +11 -65
  112. package/dist/scope.js +17 -82
  113. package/dist/serve.js +16 -25
  114. package/dist/server/chat.js +7 -30
  115. package/dist/server/flows.js +11 -10
  116. package/dist/server/guards.js +0 -1
  117. package/dist/server/pages-assets.js +1 -1
  118. package/dist/server/pages.js +0 -2
  119. package/dist/server/people-tokens.js +0 -4
  120. package/dist/server/route-table.d.ts +1 -1
  121. package/dist/server/route-table.js +2 -17
  122. package/dist/server/settings.js +4 -9
  123. package/dist/server/shared.d.ts +29 -212
  124. package/dist/server/shared.js +73 -780
  125. package/dist/server/task-views.js +9 -76
  126. package/dist/server/tasks.d.ts +1 -50
  127. package/dist/server/tasks.js +36 -946
  128. package/dist/setup-guide.d.ts +2 -2
  129. package/dist/slack-chat.d.ts +0 -2
  130. package/dist/slack-chat.js +0 -1
  131. package/dist/store.d.ts +142 -1382
  132. package/dist/store.js +900 -4083
  133. package/dist/surface.js +10 -25
  134. package/dist/task-composer.d.ts +1 -1
  135. package/dist/task-composer.js +1 -1
  136. package/dist/task-control.d.ts +3 -10
  137. package/dist/task-control.js +2 -7
  138. package/dist/teammate-desk.js +1 -1
  139. package/dist/teammates-ui.js +1 -1
  140. package/dist/telegram-mate.d.ts +0 -3
  141. package/dist/telegram-mate.js +0 -1
  142. package/dist/templates.d.ts +11 -7
  143. package/dist/templates.js +12 -9
  144. package/dist/toolroll-update.d.ts +24 -3
  145. package/dist/toolroll-update.js +159 -16
  146. package/dist/webhooks.d.ts +10 -22
  147. package/dist/webhooks.js +43 -81
  148. package/dist/work-index.js +3 -7
  149. package/dist/workspace-revision.d.ts +28 -1
  150. package/dist/workspace-revision.js +276 -49
  151. package/dist/workspace-ui.js +1 -1
  152. package/package.json +1 -3
  153. package/scripts/recovery-canary.mjs +3 -3
  154. package/dist/contest.d.ts +0 -318
  155. package/dist/contest.js +0 -753
  156. package/dist/held.d.ts +0 -150
  157. package/dist/held.js +0 -799
  158. package/dist/liveness.d.ts +0 -30
  159. package/dist/liveness.js +0 -42
  160. package/dist/pricing.d.ts +0 -45
  161. package/dist/pricing.js +0 -77
  162. package/dist/routine.d.ts +0 -228
  163. package/dist/routine.js +0 -764
  164. package/dist/supervisor.mjs +0 -297
@@ -23,7 +23,7 @@ import { addCardToFlow, advanceFlows, cancelFlowCard, crossProjectProblem, decid
23
23
  import { chooseFlowCard, flowPersonOf } from "./flow-send.js";
24
24
  import { assignFlowCard, commentOnFlowCard, flowPeople, mentionsIn, watchFlowCard } from "./flow-people.js";
25
25
  import { addFlowTriggerTo, describeTrigger, readLinearKey, removeFlowTrigger, scheduleFromWords, takesDeliveries, triggerConfigOf, validateTriggerConfig } from "./flow-triggers.js";
26
- import { describeSchedule, parseSchedule } from "./routine.js";
26
+ import { describeSchedule, parseSchedule } from "./flow-schedule.js";
27
27
  import { dirname } from "node:path";
28
28
  import { handleOf, parseSoul, SOUL_CHARS, TEAMMATE_TEMPLATES, teammateLabel } from "./teammates.js";
29
29
  import { createTeammateFrom, labelOf, nameOf, renamedSoul, saveSoul, setTeammateState } from "./teammate-admin.js";
@@ -825,8 +825,6 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
825
825
  const ref = store.lookupRef(task), row = store.getTask(task), family = store.taskFamilyOf(task, who.repos, false);
826
826
  if (!row || !family || family.problem || family.current.id !== task)
827
827
  throw Error("Choose the current task.");
828
- if (store.openContestFor(ref.id) !== null)
829
- throw Error("Wait for the agent comparison to finish.");
830
828
  const scope = store.getScope(task);
831
829
  state = {
832
830
  task,
@@ -847,19 +845,13 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
847
845
  if (!authority.ok)
848
846
  throw Error(authority.problem);
849
847
  state["authMode"] = authority.authMode;
850
- const race = store.activeTournamentTerms(ref.id);
851
- state["race"] = race;
852
848
  terms.push(...describeScope(scope)
853
849
  .filter((line) => !/^\s*(reference|approved)\s/.test(line))
854
850
  .map((line) => line
855
851
  .trimStart()
856
852
  .replace(/^goal\s+/, "Work: ")
857
853
  .replace(/^not this\s+/, "Excluded: ")
858
- .replace(/^touches\s+/, "Allowed files: ")), ...(authority.chain ?? [
859
- { profile: authority.profile, authMode: authority.authMode },
860
- ]).map((entry, index) => `${index === 0 ? "Agent permissions and limits" : "Fallback " + index}\n${runtimeTerms(entry.profile)}\n${entry.authMode === "api-key" ? "Uses your API key; spend is charged to that account." : entry.authMode === "subscription" ? "Uses your subscription login." : ""}`));
861
- if (race)
862
- terms.push(`Agent comparison terms:\n${JSON.stringify(race, null, 2)}`);
854
+ .replace(/^touches\s+/, "Allowed files: ")), `Agent permissions and limits\n${runtimeTerms(authority.profile)}\n${authority.authMode === "api-key" ? "Uses your API key; spend is charged to that account." : authority.authMode === "subscription" ? "Uses your subscription login." : ""}`);
863
855
  const plan = store.latestPlanArtifact(ref.id), revision = ref.revisionBriefArtifact === null
864
856
  ? null
865
857
  : store.getArtifact(ref.revisionBriefArtifact);
@@ -1111,7 +1103,7 @@ export function executeSharedAction(store, who, id, payload, now, options) {
1111
1103
  : { restore: Number(req["restore"]) }),
1112
1104
  }, now);
1113
1105
  else if (payload.operation === "scope_approve") {
1114
- const scope = store.getScope(task), race = store.activeTournamentTerms(store.lookupRef(task).id);
1106
+ const scope = store.getScope(task);
1115
1107
  const result = approve(store, task, actor, now, scope.digest, options.review.password);
1116
1108
  if (!result.ok) {
1117
1109
  // v102: the project's approval rules speak for themselves.
@@ -1123,9 +1115,6 @@ export function executeSharedAction(store, who, id, payload, now, options) {
1123
1115
  throw Error(result.message);
1124
1116
  throw Error(`Approval refused: ${result.reason}.`);
1125
1117
  }
1126
- if (race &&
1127
- !store.approveTournamentTerms(race.id, actor, race.raceDigest, now))
1128
- throw Error("The comparison terms changed. Nothing was approved.");
1129
1118
  }
1130
1119
  else if (payload.operation === "result_accept") {
1131
1120
  // Accept and finish, as on the result page: whether a check only the person makes is accepted
@@ -42,10 +42,6 @@ export declare const CHAT_CONTROLS: {
42
42
  readonly label: "Open coding workspace";
43
43
  readonly href: "/code";
44
44
  };
45
- readonly routines: {
46
- readonly label: "Manage routines";
47
- readonly href: "/routines";
48
- };
49
45
  readonly flows: {
50
46
  readonly label: "Open flows";
51
47
  readonly href: "/flows";
@@ -12,7 +12,6 @@ export const CHAT_CONTROLS = {
12
12
  publish: { label: "Review publication", target: "task" },
13
13
  projects: { label: "Manage projects", href: "/projects" },
14
14
  code: { label: "Open coding workspace", href: "/code" },
15
- routines: { label: "Manage routines", href: "/routines" },
16
15
  flows: { label: "Open flows", href: "/flows" },
17
16
  recipes: { label: "Browse workflows", href: "/recipes" },
18
17
  workers: { label: "Manage workers", href: "/fleet" },
@@ -104,11 +104,9 @@ export function planInChat(store, taskId, approver, now, root) {
104
104
  const family = store.taskFamilyOf(taskId, [ref.repo], false);
105
105
  if (family === null || family.problem !== null || family.current.id !== taskId)
106
106
  return { ok: false, why: "A newer version of this task is current." };
107
- // What the mode's seal refuses anyway, said first: model- or coordinator-written terms, a race, an unreadable route.
107
+ // What the mode's seal refuses anyway, said first: model- or coordinator-written terms, an unreadable route.
108
108
  if (ref.coordinatorCid !== null || (scope.proposedVia ?? null) !== null)
109
109
  return { ok: false, why: "This plan was written for you, so you approve it in Toolroll." };
110
- if (store.activeTournamentTerms(ref.id) !== null)
111
- return { ok: false, why: "Agents compete on this task, so you approve it in Toolroll." };
112
110
  if (scope.profileState === "unresolved" || scope.routeEra == null || (scope.termsProblem ?? null) !== null)
113
111
  return { ok: false, why: "This plan can't say exactly which agents run, so you approve it in Toolroll." };
114
112
  const yours = mode.source === "setting" ? "your chat approval setting" : "your mode";
@@ -1,5 +1,4 @@
1
1
  import { type WarmHooks } from "./chat-warmth.js";
2
- import { type DoorOptions } from "./mate-doors.js";
3
2
  import { ChatState, type ChatBinding, type ChatEvent, type ChatIdentity } from "./chat-delivery-state.js";
4
3
  import { type Store } from "./store.js";
5
4
  import type { SubscriptionMateRunner } from "./subscription-chat.js";
@@ -16,7 +15,6 @@ export type ChatDeliveryOptions = {
16
15
  current: () => boolean;
17
16
  origin: () => string | null;
18
17
  subscriptionRunner?: SubscriptionMateRunner;
19
- held?: DoorOptions["held"];
20
18
  canNotify?: () => boolean;
21
19
  clock?: () => Date;
22
20
  partSize?: number;
@@ -547,7 +547,6 @@ export function applyChatAction(options, event, binding, repos) {
547
547
  via: options.state.channel,
548
548
  evidenceRoot: options.evidenceRoot,
549
549
  confirm: phase === "yes",
550
- ...(options.held ? { held: options.held } : {}),
551
550
  deferSignal: (signal) => signals.push(signal),
552
551
  });
553
552
  if (!outcome.ok && outcome.reason === "needs-confirm") {
@@ -36,9 +36,9 @@ export function needsPerson(row) {
36
36
  export function isTaskFact(row) {
37
37
  return row.taskRef !== null && row.taskId !== null && row.project !== null;
38
38
  }
39
- /** The attempt a task's card follows: its newest builder attempt, outside any contest. */
39
+ /** The attempt a task's card follows: its newest builder attempt. */
40
40
  function cardRun(store, taskRef) {
41
- return store.runsFor(taskRef).find(run => run.role === "builder" && run.contestant === null) ?? null;
41
+ return store.runsFor(taskRef).find(run => run.role === "builder") ?? null;
42
42
  }
43
43
  /** Before any attempt starts, the card says where the task stands in the words of its last lifecycle fact. */
44
44
  const before = (headline) => ({ icon: headlineEmoji(headline), status: headline });
@@ -44,7 +44,7 @@ export declare function isChatTaskAction(value: unknown): value is keyof typeof
44
44
  export declare function chatTaskStamp(store: Store, who: VerifiedApprover, task: string): string | null;
45
45
  /** Only current, eligible controls can become cards; retain the exact run the reader saw. */
46
46
  export declare function chatTaskRun(store: Store, task: string, operation: "stop" | "resume", now: Date): number | null;
47
- export declare function applyChatTaskAction(store: Store, who: VerifiedApprover, payload: Record<string, unknown>, now: Date, web: boolean, controls?: Pick<StopRequest, "held" | "deferSignal"> & {
47
+ export declare function applyChatTaskAction(store: Store, who: VerifiedApprover, payload: Record<string, unknown>, now: Date, web: boolean, controls?: Pick<StopRequest, "deferSignal"> & {
48
48
  via?: StopRequest["via"];
49
49
  evidenceRoot?: string;
50
50
  }): {
@@ -46,8 +46,6 @@ export function applyChatTaskAction(store, who, payload, now, web, controls = {}
46
46
  if (stamp === null || stamp !== payload["stamp"])
47
47
  return { ok: false, message: "This task changed. Review it again before confirming." };
48
48
  const ref = store.lookupRef(task);
49
- if (store.openContestFor(ref.id) !== null)
50
- return { ok: false, message: "Wait for the agent comparison to finish." };
51
49
  if (operation === "stop" || operation === "resume") {
52
50
  if (store.isDemo())
53
51
  return { ok: false, message: "The demo authorizes nothing." };
package/dist/claim.d.ts CHANGED
@@ -63,21 +63,6 @@ export type AcquireResult = {
63
63
  reason: "external";
64
64
  detail: "stale-mirror" | "external-closed" | "dispatch-revoked" | "plane-blocked";
65
65
  }
66
- /** An attended authorization holds this task for its named runner (v25).
67
- * Its OWN reason token — never the `reserved` tag, whose reservedFor field
68
- * is a documented public contract meaning task_ref.assigned_runner. */
69
- | {
70
- ok: false;
71
- reason: "attended-held";
72
- runner: string;
73
- }
74
- /** The task's only authority is an attended authorization, and the
75
- * operator is not watching (or the one attempt is spent). Expiry never
76
- * converts attended work into unattended work. */
77
- | {
78
- ok: false;
79
- reason: "attended-only";
80
- }
81
66
  /** A mode-sealed approval whose signature no longer stands (v29): the
82
67
  * approval falls back to a person; nothing dispatches on a dead mode. */
83
68
  | {
@@ -209,26 +194,6 @@ export declare function acquireIfReady(store: Store, taskRef: number, runner: st
209
194
  * caller's survey is not trusted (Codex planning review, finding 2). */
210
195
  dispatchRole?: "builder" | "planner" | "scout";
211
196
  }): AcquireResult | NotReady;
212
- /**
213
- * The FALLBACK-ADMISSION claim (E3d, review finding 4): the one gate a
214
- * pending-admission chain cycle dispatches through. It enforces everything
215
- * `acquireIfReady` enforces — task state, holds, blockers, the approved
216
- * scope + live-mode belt, capability, capacity, and quota — with EXACTLY
217
- * ONE deliberate difference: a BACKOFF hold does not gate, because the
218
- * pending admission IS the system's answer to the predecessor's failure
219
- * (waiting out the exhausted entry's backoff to run a different credential
220
- * would be waiting for nothing). Quota is keyed by the PINNED entry's
221
- * provider, model, and auth mode — the credential that would actually
222
- * spend — so an exhausted fallback credential refuses here, before any
223
- * claim exists.
224
- */
225
- export declare function acquireFallback(store: Store, taskRef: number, runner: string, options: AcquireOptions & {
226
- repo?: string;
227
- provider: string;
228
- model: string;
229
- authMode: "subscription" | "api-key";
230
- maxOpenDecisions?: number;
231
- }): AcquireResult | NotReady;
232
197
  /**
233
198
  * "I am still here." Extends the lease, and tells a superseded runner that it
234
199
  * has been superseded — which is the cheapest moment for it to find out, well
@@ -246,73 +211,67 @@ export declare function heartbeat(store: Store, leaseId: string, now: Date, ttlM
246
211
  */
247
212
  export declare function release(store: Store, leaseId: string, now: Date): FenceResult;
248
213
  /**
249
- * Release the lease and write the task's terminal state, as one step.
250
- *
251
- * Release-then-mark is a window: between the two, the freed task is back in
252
- * the ready set, and another pass can claim it before the terminal state
253
- * lands — two builders for one task, the second dispatched by our own
254
- * bookkeeping. Here the state is written inside the same transaction as the
255
- * fenced release, so a task is never simultaneously free and unfinished.
256
- *
257
- * A fenced or unknown lease writes nothing: a runner the world moved past
258
- * does not get to say how the task ended. A duplicate release reports
259
- * `duplicate` and also writes nothing — the first completion already said,
260
- * and a retry changing the answer would make "done" negotiable.
261
- */
262
- export declare function completeFenced(store: Store, leaseId: string, state: Extract<TaskState, "done" | "failed">, now: Date, mutation?: Mutation): CompleteResult;
263
- /**
264
- * Seal a park, fenced at the write (§7).
265
- *
266
- * The builder proved its lease after the agent ran; this transaction proves
267
- * it again *in the same statement that releases it*, because everything a
268
- * park creates — the decision, its hold, the outbox row — is only true if
269
- * this lease still spoke for the task at the moment of sealing. A superseded
270
- * lease creates none of it: no decision, no hold, no notification, and the
271
- * run is finalized as the fenced refusal it was. A crash anywhere inside
272
- * rolls all of it back together; there is no instant at which a decision
273
- * exists without its hold.
274
- *
275
- * `released_by = 'parked'` so a late retry from this lease is answered with
276
- * the fence, not "duplicate": a park hands the task to a person, and nothing
277
- * the runner says afterwards is that person's answer.
278
- */
279
- /**
280
- * Continuation admission (Phase 2E, A4 / v3 R7): the AUTHORIZATION is the
281
- * claimable unit — the finished parent task is never re-queued, its state,
282
- * strikes, holds, and dependents untouched. Every shared gate still runs
283
- * (acquireLocked: reservation, external mirror, the attended gates, the
284
- * lease CAS); the queued-state readiness gate alone is replaced by
285
- * construction. Liveness, the named runner, and the unspent attempt are
286
- * re-proved HERE at claim time and again at the final dispatch proof.
214
+ * How an attempt ended, one kind per ending, each with the facts its seal needs. `finalize` seals every one.
287
215
  */
288
- export declare function acquireContinuation(store: Store, authorization: import("./store.js").AttendedAuthorization, runner: string, options: AcquireOptions): AcquireResult | {
289
- ok: false;
290
- reason: "attended-only" | "contest-open" | "continuation-blocked";
291
- message: string;
216
+ export type Outcome =
217
+ /** The task's terminal state, written with the release (no run: the lease's own completion). */
218
+ ({
219
+ kind: "complete";
220
+ } & CompleteSeal) | ({
221
+ kind: "park";
222
+ } & ParkSeal) | ({
223
+ kind: "malformed";
224
+ } & MalformedSeal) | ({
225
+ kind: "interrupted";
226
+ } & InterruptedSeal) | ({
227
+ kind: "hand-back";
228
+ } & HandBackSeal) | ({
229
+ kind: "failure";
230
+ } & FailureSeal) | ({
231
+ kind: "plan";
232
+ } & PlanSeal) | ({
233
+ kind: "revision";
234
+ } & RevisionSeal) | ({
235
+ kind: "plan-failure";
236
+ } & PlanFailureSeal) | ({
237
+ kind: "scout";
238
+ } & ScoutSeal) | ({
239
+ kind: "scout-failure";
240
+ } & ScoutFailureSeal);
241
+ /** What sealing each kind answers. */
242
+ export type Finalized = {
243
+ complete: CompleteResult;
244
+ park: ParkFinalize;
245
+ malformed: MalformedFinalize;
246
+ interrupted: InterruptSeal;
247
+ "hand-back": HandBackFinalize;
248
+ failure: FailureDisposition;
249
+ plan: PlanFinalize;
250
+ revision: RevisionFinalize;
251
+ "plan-failure": PlanFailureDisposition;
252
+ scout: ScoutFinalize;
253
+ "scout-failure": ScoutFailureDisposition;
292
254
  };
293
255
  /**
294
- * The HELD park (Phase 2E, v2 S1f): the decision is recorded, paged, and
295
- * causally linked to the session turn that produced it — and NOTHING else
296
- * ends. The lease stays; the run stays open; the process stays held. The
297
- * conversation continues when the answer is injected as the next turn.
298
- * The one-unresolved-per-run partial unique refuses a second open park.
256
+ * THE ONE WAY AN ATTEMPT ENDS: seal `outcome` for `leaseId` in one fenced transaction.
257
+ *
258
+ * Every ending passes the same fence, in the same order: the run is this lease's open attempt of the right role (a
259
+ * caller defect otherwise, thrown); a stop that applies to it wins — the attempt is sealed as interrupted instead
260
+ * (except the endings that ARE a stop or a service hand-back); then the claim is released by a compare-and-swap only the
261
+ * live, unsuperseded lease wins. A lease the world moved past writes nothing but its own run's fenced refusal (the
262
+ * stop and hand-back endings still end their run, touching no task state), and a retry of an ending the same lease
263
+ * already made answers as a duplicate where that ending allows it. Only then does the ending's own effect run —
264
+ * decision and hold, incident, strike and backoff, scope, report, revision, task state, the disowned mirror — inside
265
+ * the same transaction, so nothing of it exists without the release, and a crash rolls all of it back together.
299
266
  */
300
- export declare function finalizeParkHeld(store: Store, args: {
301
- runId: number;
302
- taskId: string;
303
- decision: ParsedDecision;
304
- artifactIds: readonly number[];
305
- sessionTurn: number;
267
+ export declare function finalize<O extends Outcome>(store: Store, leaseId: string, outcome: O): Finalized[O["kind"]];
268
+ /** The task's terminal state, written with the release (no run: the lease's own completion). */
269
+ export type CompleteSeal = {
270
+ state: Extract<TaskState, "done" | "failed">;
306
271
  now: Date;
307
- }): {
308
- ok: true;
309
- decisionId: number;
310
- } | {
311
- ok: false;
312
- reason: "decision-open" | "not-held";
272
+ mutation?: Mutation;
313
273
  };
314
- export declare function finalizeParkFenced(store: Store, args: {
315
- leaseId: string;
274
+ export type ParkSeal = {
316
275
  runId: number;
317
276
  taskId: string;
318
277
  decision: ParsedDecision;
@@ -321,28 +280,21 @@ export declare function finalizeParkFenced(store: Store, args: {
321
280
  * settled in this same fence, never earlier. */
322
281
  repairRunId?: number | null;
323
282
  now: Date;
324
- }): {
283
+ };
284
+ export type ParkFinalize = {
325
285
  ok: true;
326
286
  decisionId: number;
327
287
  } | {
328
288
  ok: false;
329
289
  reason: "fenced" | "unknown" | "stopped";
330
290
  };
331
- /**
332
- * Seal a park whose payload never became a decision (§6's bounded repair ran
333
- * out). Same fence, same atomicity, different record: an incident that stays
334
- * in every brief until a person resolves it, holding the task so the next
335
- * pass does not spend the same tokens hitting the same wall nightly. The
336
- * malformed payload is preserved as evidence — a person may still be able to
337
- * read what the agent meant.
338
- */
339
- export declare function finalizeMalformedFenced(store: Store, args: {
340
- leaseId: string;
291
+ export type MalformedSeal = {
341
292
  runId: number;
342
293
  taskId: string;
343
294
  problems: readonly Problem[];
344
295
  now: Date;
345
- }): {
296
+ };
297
+ export type MalformedFinalize = {
346
298
  ok: true;
347
299
  incidentId: number;
348
300
  } | {
@@ -387,48 +339,21 @@ export type InterruptSeal = {
387
339
  requeued: false;
388
340
  fenced: true;
389
341
  };
390
- /**
391
- * Seal an operator-stopped attempt (v52): ONE fenced transaction for the
392
- * claim's release as `interrupted`, the run's ending as `failed` /
393
- * `interrupted` (the same words dead-runner recovery writes, so the
394
- * recovered-draft road inherits the preserved work exactly as after a
395
- * crash), every open run the attempt owns, the task's return to the
396
- * queue under the stop's own hold, and the stop's settlement. Nothing
397
- * here is a failure: no strike, no backoff, no incident, no automatic
398
- * repair task, no notification page. A commit the attempt already made
399
- * is kept as a reviewable artifact — `committed` records that it exists.
400
- *
401
- * A lease the world moved past (reaped, recovered, superseded) still
402
- * ends the run as interrupted — that IS the honest ending — but touches
403
- * no task state: the task belongs to whoever holds it now.
404
- */
405
- export declare function finalizeInterruptedFenced(store: Store, args: {
406
- leaseId: string;
342
+ export type InterruptedSeal = {
407
343
  runId: number;
408
344
  taskId: string;
409
345
  stopRun: number;
410
346
  message?: string;
411
347
  committed?: boolean;
412
348
  now: Date;
413
- }): InterruptSeal;
414
- /**
415
- * Hand an attempt back because the SERVICE stopped (its builder is
416
- * stopping or restarting), not because anyone stopped the task: one fenced
417
- * transaction releases the claim as `interrupted`, ends the run (and every
418
- * open run it owns) as `failed` / `interrupted` — the words dead-runner
419
- * recovery writes, so the next attempt inherits the preserved work in the
420
- * same checkout exactly as after a crash — and returns the task to the
421
- * queue with no strike, no backoff and no hold, so the next builder pass
422
- * resumes it. A lease the world moved past still ends the run, touching no
423
- * task state.
424
- */
425
- export declare function finalizeHandBackFenced(store: Store, args: {
426
- leaseId: string;
349
+ };
350
+ export type HandBackSeal = {
427
351
  runId: number;
428
352
  taskId: string;
429
353
  message: string;
430
354
  now: Date;
431
- }): {
355
+ };
356
+ export type HandBackFinalize = {
432
357
  ok: true;
433
358
  requeued: boolean;
434
359
  fenced: boolean;
@@ -449,30 +374,14 @@ export declare function interruptIfStopped(store: Store, args: {
449
374
  committed?: boolean;
450
375
  now: Date;
451
376
  }): InterruptSeal | null;
452
- /**
453
- * Seal a failed attempt: one fenced transaction for the release, the run,
454
- * the strike, the hold, and the page (§6, gnhf adopted rather than
455
- * reinvented).
456
- *
457
- * Strikes count only top-level builder attempts; built, no-change, and
458
- * parked reset them elsewhere; refusals, fenced attempts, and repair
459
- * children never reach this function. Under three strikes the task
460
- * requeues behind a doubling backoff hold the failure owns; at three it
461
- * stalls — an 'attempts-exhausted' incident, the task marked failed, held,
462
- * and paged once — because a fourth identical attempt at 3am is a token
463
- * bonfire, not persistence. A commit-stage failure takes neither road: the
464
- * worktree holds unpreserved work, ordinary re-pooling would refuse it as
465
- * dirty forever, so it is an immediate incident naming the checkout.
466
- */
467
- export declare function finalizeFailureFenced(store: Store, args: {
468
- leaseId: string;
377
+ export type FailureSeal = {
469
378
  runId: number;
470
379
  taskId: string;
471
380
  failureClass: FailureClass;
472
381
  message: string;
473
382
  worktree: string;
474
383
  now: Date;
475
- }): FailureDisposition;
384
+ };
476
385
  /** The live claim on a task, if there is one. */
477
386
  export declare const MAX_PLAN_STRIKES = 3;
478
387
  export type PlanFinalize = {
@@ -492,16 +401,7 @@ export type PlanFinalize = {
492
401
  reason: "stale-source" | "source-invalid";
493
402
  detail: string;
494
403
  };
495
- /**
496
- * Seal a successful planning run: the proposed scope, the plan document
497
- * artifact, the drafted state, the run outcome, and the page to the
498
- * operator exist together or — if the lease was superseded — not at all.
499
- * The scope proposal is authority-bearing (it is what the approve card
500
- * will restate), which is why this is the park discipline, not the DONE
501
- * one (Codex planning review, question 2).
502
- */
503
- export declare function finalizePlanFenced(store: Store, args: {
504
- leaseId: string;
404
+ export type PlanSeal = {
505
405
  runId: number;
506
406
  taskId: string;
507
407
  plan: ParsedPlan;
@@ -529,7 +429,7 @@ export declare function finalizePlanFenced(store: Store, args: {
529
429
  /** Where the plan-contract ingestion record is written. */
530
430
  evidenceRoot?: string;
531
431
  now: Date;
532
- }): PlanFinalize;
432
+ };
533
433
  export type RevisionFinalize = {
534
434
  ok: true;
535
435
  revisionId: number;
@@ -538,42 +438,7 @@ export type RevisionFinalize = {
538
438
  ok: false;
539
439
  reason: "fenced" | "unknown" | "stopped";
540
440
  };
541
- /**
542
- * Seal a running build's plan-revision proposal (adaptive execution plans).
543
- *
544
- * A builder that finds repository evidence invalidating a named dependency,
545
- * risk, or assumption in the plan it was given files ONE proposal and stops
546
- * without committing — the park discipline, applied to the road rather than
547
- * to a question. The ledger row, the hold when one is owed, the run's
548
- * outcome, and the page exist together or, if the lease was superseded,
549
- * not at all. Structurally `finalizePlanFenced`'s twin: the same
550
- * open-attempt assertion, the same fenced release, the same all-or-nothing
551
- * transaction.
552
- *
553
- * Two dispositions, decided by the caller's classification and recorded
554
- * here:
555
- *
556
- * plan-only the signed scope and publication authority are
557
- * byte-identical to what this build started under, so
558
- * nothing authority-bearing moved and the revision is
559
- * APPLIED. The claim releases plainly, the task returns
560
- * to the ready set, and the next attempt reads the new
561
- * plan — that is the "resume" the subject line promises.
562
- *
563
- * authority-change something else moved the signed scope or the
564
- * publication authority WHILE this build ran. Whatever
565
- * the revision proposes, it is never auto-applied: the
566
- * row lands 'blocked', a `revision` hold keeps the task
567
- * out of every ready set until a person accepts or
568
- * rejects it, and the page names exactly which fields
569
- * changed.
570
- *
571
- * The lease is released either way — the attempt is over — and the run is
572
- * finished as `refused`, reusing the existing outcome vocabulary rather
573
- * than growing it: no work was committed, and nothing failed.
574
- */
575
- export declare function finalizeRevisionFenced(store: Store, args: {
576
- leaseId: string;
441
+ export type RevisionSeal = {
577
442
  runId: number;
578
443
  taskId: string;
579
444
  taskRef: number;
@@ -594,7 +459,7 @@ export declare function finalizeRevisionFenced(store: Store, args: {
594
459
  changedFields: readonly AuthorityChangeField[];
595
460
  };
596
461
  now: Date;
597
- }): RevisionFinalize;
462
+ };
598
463
  export type PlanFailureDisposition = {
599
464
  ok: true;
600
465
  disposition: "malformed-incident";
@@ -616,44 +481,22 @@ export type PlanFailureDisposition = {
616
481
  ok: false;
617
482
  reason: "fenced" | "unknown" | "stopped";
618
483
  };
619
- /**
620
- * The planner's own fenced failure finalizer (Codex planning review,
621
- * finding 6): releases exactly the planner claim, finishes the run,
622
- * counts a SEPARATE planning strike — a planner that cannot finish must
623
- * never spend the builder's three attempts — and eventually leaves a
624
- * durable incident + hold + page. It never marks the task done, never
625
- * touches builder strikes, never commits, never publishes.
626
- *
627
- * A malformed payload goes straight to its incident with no strike: the
628
- * protocol failed, not the weather, and retrying the same session buys
629
- * nothing without the repair machinery (deliberately not wired for
630
- * planners in v1).
631
- */
632
- export declare function finalizePlanFailureFenced(store: Store, args: {
633
- leaseId: string;
484
+ export type PlanFailureSeal = {
634
485
  runId: number;
635
486
  taskId: string;
636
- kind: "malformed" | "failure";
487
+ failure: "malformed" | "failure";
637
488
  /** Which payload was malformed (v4 review, finding 12): the mailbox is a `malformed-decision`. */
638
489
  malformed?: "decision" | "plan";
639
490
  message: string;
640
491
  now: Date;
641
- }): PlanFailureDisposition;
492
+ };
642
493
  export type ScoutFinalize = {
643
494
  ok: true;
644
495
  } | {
645
496
  ok: false;
646
497
  reason: "fenced" | "unknown" | "stopped";
647
498
  };
648
- /**
649
- * Seal a successful scouting run (mate arc §10): the report artifact, the
650
- * run outcome, the task's terminal state, and the page to the operator
651
- * exist together or — if the lease was superseded — not at all. Nothing
652
- * here touches a branch, a publication, or a scope: the deliverable is
653
- * the artifact, and the task is done the moment it verifies.
654
- */
655
- export declare function finalizeScoutFenced(store: Store, args: {
656
- leaseId: string;
499
+ export type ScoutSeal = {
657
500
  runId: number;
658
501
  taskId: string;
659
502
  report: ParsedReport;
@@ -670,7 +513,7 @@ export declare function finalizeScoutFenced(store: Store, args: {
670
513
  redacted: boolean;
671
514
  };
672
515
  now: Date;
673
- }): ScoutFinalize;
516
+ };
674
517
  export type ScoutFailureDisposition = {
675
518
  ok: true;
676
519
  disposition: "malformed-incident";
@@ -692,25 +535,17 @@ export type ScoutFailureDisposition = {
692
535
  ok: false;
693
536
  reason: "fenced" | "unknown" | "stopped";
694
537
  };
695
- /**
696
- * The scout's fenced failure finalizer: the BUILDER's discipline, not the
697
- * planner's — a scout task has no builder attempts, so its strikes are the
698
- * task's own (three stall it, `task requeue` clears them), and a malformed
699
- * report is a straight incident with no strike. Never a branch, never a
700
- * repair session, never a commit.
701
- */
702
- export declare function finalizeScoutFailureFenced(store: Store, args: {
703
- leaseId: string;
538
+ export type ScoutFailureSeal = {
704
539
  runId: number;
705
540
  taskId: string;
706
- kind: "malformed" | "failure";
541
+ failure: "malformed" | "failure";
707
542
  /** Which payload was malformed (v4 review, finding 12): the park
708
543
  * mailbox is a `malformed-decision` incident, the handoff a
709
544
  * `malformed-report` one — the taxonomy says what actually broke. */
710
545
  malformed?: "decision" | "report";
711
546
  message: string;
712
547
  now: Date;
713
- }): ScoutFailureDisposition;
548
+ };
714
549
  export declare function currentClaim(store: Store, taskRef: number, now: Date): Claim | null;
715
550
  /**
716
551
  * Release every lease that has run out.