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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.53 — 2026-10-08
4
+
5
+ - **Fewer features to learn.** Contests, held sessions and fallback chains
6
+ are gone. Routines are now scheduled flows; existing routines migrate,
7
+ paused, and `toolroll routine` points you to flows.
8
+ - **Schedule limits hold.** Moving a flow card can't reset its spending or
9
+ single-flight limits.
10
+
11
+ ## 0.9.52 — 2026-10-07
12
+
13
+ - **Smaller database.** Process logs are compacted after each run, so the
14
+ database file shrinks (one install went from about 116 MB to 26 MB), and
15
+ backups shrink with it. The action ledger is checkpointed automatically.
16
+ - **Lighter under the hood.** Update checks read history with bounded memory,
17
+ and live views update without database triggers.
18
+ - **Honest release check.** It runs every unit test on 8 workers, and its
19
+ tests check behaviour at runtime instead of reading source text.
20
+ - **Webhook alerts keep working.** Legacy Slack and Discord webhooks still
21
+ send this release, with a notice pointing to Chat settings.
22
+
3
23
  ## 0.9.51 — 2026-10-07
4
24
 
5
25
  - **No lockout by username.** Wrong passwords slow the source that sent them,
package/README.md CHANGED
@@ -163,7 +163,10 @@ failed when a result is marked complete.
163
163
  - **Triggers start cards on their own.** A button with a few questions, a
164
164
  schedule, GitHub (new issues, a label being added, new pull requests,
165
165
  failed checks), Linear (a team, a state, a label), or another flow's cards
166
- reaching a zone. GitHub is checked through your `gh` login and Linear
166
+ reaching a zone. A schedule can carry a standing order — the exact task to
167
+ file each time, with its own per-run budget and a rolling 7-day cost
168
+ ceiling — and runs one at a time, skipping while the last task is
169
+ unfinished. GitHub is checked through your `gh` login and Linear
167
170
  with an API key kept on this computer; neither spends model tokens. Each
168
171
  issue or run makes at most one card; text from outside the repository's
169
172
  team is left out unless you say anyone. With a public address, GitHub,
@@ -361,7 +364,7 @@ for people splitting those parts across machines.
361
364
  mandatory review step. Older results retain their own links.
362
365
 
363
366
  Specialized views remain in **Tools** and **Settings**: the activity ledger, the review cockpit,
364
- routines (tasks that file themselves on a schedule), the fleet,
367
+ the fleet,
365
368
  people (invite a second approver), the operating mode (a signed, expiring
366
369
  envelope that pre-approves your own filings), and **chat** — the mate, one
367
370
  conversation across every project, which only ever proposes.
@@ -597,16 +600,17 @@ deployment require their own authority and checks.
597
600
 
598
601
  Which agent plans, builds, and repairs a task is decided once, from
599
602
  signed facts, and written down with its reasons. The route reads the task's
600
- declared **risk** (routine, elevated, high), its quality mode, what the
603
+ **size** (small, medium, large, risky or not), its quality mode, what the
601
604
  acceptance rubric demands (screenshots, manual review), how far a live
602
- operating mode may carry the result unattended, and the agents you configured for each phase. Strength is never
605
+ operating mode may carry the result unattended, each provider's plan room, and
606
+ the agents you configured for each phase. Strength is never
603
607
  inferred from a model's name: the ordinary phase row is the routine tier, and
604
608
  `config set <phase> --tier strong --provider <p> --model <m>` names the agent
605
- high-risk, strict, screenshot-proof, and automerge routes reach for. With no
609
+ large or risky, strict, screenshot-proof, and automerge routes reach for. With no
606
610
  strong row, a demanding task keeps the default and says so.
607
611
 
608
612
  ```
609
- toolroll task scope <id> --goal … --acceptance … --risk high
613
+ toolroll task route <id> --size large --as you --token <t>
610
614
  toolroll task route <id> # every leg, its reason, its readiness
611
615
  toolroll task route <id> --phase build --provider codex --model gpt-5-codex --as you --token <t>
612
616
  toolroll task route <id> --clear-phase build --as you --token <t>
@@ -625,8 +629,8 @@ an exact model too, and a plan override becomes the planner pin.
625
629
  Every override is recorded under the approver's name, in one transaction that
626
630
  checks the scope you were reading is still the one on file (`--digest`, or the
627
631
  form's own field). Approval seals the route — routine-shaped routes included —
628
- together with any configured fallback chain, exactly as it seals the execution
629
- profile; a later risk change or override re-files the scope and the old
632
+ exactly as it seals the execution profile; a later size change or override
633
+ re-files the scope and the old
630
634
  approval reads stale, while a global configuration change can never rewrite a
631
635
  sealed route. A row filed before routing existed is recognised by a durable
632
636
  marker and stays governed by its sealed profile; a routed row whose route data
@@ -636,44 +640,35 @@ readiness without spending — at startup and with `providers --report`, never
636
640
  on a timer — and the task page, the chat, and `task show` say **ready**,
637
641
  **unavailable** (with the runner's own words), or **unknown** beside the
638
642
  agents, outside what the approval signs; an unavailable provider halts before
639
- any claim and is never substituted, except that an unavailable primary under
640
- an approved fallback chain moves to the exact approved next entry (and only
641
- under a live mode that allows paid fallback). Fallback admission re-check readiness and the exact leg inside their transactions.
642
- Every run is stamped with its route and the actual provider and model at
643
+ any claim and is never substituted. When a provider's plan runs out mid-build,
644
+ the attempt fails with its reason like any other failure and the task waits
645
+ for a retry or for you; nothing switches to another agent or account on its
646
+ own. Every run is stamped with its route and the actual provider and model at
643
647
  admission — set once; a run that would spend as anything else refuses.
644
648
 
645
649
  Admission proves that stamp before any run row exists: its shape (a known
646
650
  phase, provenance word, and provider; a digest; an exact, argv-safe model id),
647
651
  its phase against the run's role, its provider and model against what the run
648
652
  would spend as, and its provenance against the authority the task actually
649
- holds — the sealed route's digest and that phase's exact leg, the one approved
650
- fallback-chain entry the run is bound to for a `fallback` run (two entries
651
- that share a provider and model but differ in auth mode or repair model are
652
- different authorities, and an ambiguous pair proves nothing), or a proven
653
+ holds — the sealed route's digest and that phase's exact leg, or a proven
653
654
  pre-routing row for `legacy`, whose stamp must name the very sealed profile
654
655
  (and, for a build or repair, its exact pair) — the bare word `legacy` belongs
655
656
  only to a task with no scope. A task filed under routing never opens an
656
657
  unstamped run, and the store dictates nothing: every planner, builder, scout,
657
- reviewer, repair turn, and fallback **presents** the exact authority it holds
658
+ reviewer, and repair turn **presents** the exact authority it holds
658
659
  (`routeAuthorityFor` puts it in the caller's hands, in words), and a missing,
659
660
  forged, stale, or inexact stamp opens no row — the refusal names what would
660
- have had to be presented. Chain custody is proved and written in the same
661
- insert: base custody opens the task's fallback cycle with the row, a
662
- parked-resume takes the parked tail's custody through the proven transfer,
663
- a repair turn inherits exactly its same-task parent's binding, and a fallback
664
- entry is admitted only when everything it is told — the task, the live cycle,
665
- the approved chain, the index, the entry digest, provider, model, auth mode,
666
- repair model, and `fallback` provenance — re-proves against durable state; a
667
- binding that cannot be proved rolls the insert back, and a mismatch creates no
668
- run and consumes no edge. A reviewer after a fallback spawns under the review
669
- leg and takes no custody. A run admitted under a route the scope has since
670
- re-sealed away refuses to spend. Malformed authority — corrupt route, profile,
671
- chain, or fallback JSON, a model id that is not one, a turn bound or clock
672
- that is not a positive whole number, a run row whose chain index or auth mode
673
- does not read — fails closed in words and never shrinks: a fallback row that
674
- cannot be read files the scope unresolved rather than sealing a single profile
675
- nobody configured, and an unreadable binding is no binding, never the base
676
- entry or the subscription credential.
661
+ have had to be presented. A repair turn mends exactly its same-task parent
662
+ under the claim that holds the task now. A run admitted under a route the scope
663
+ has since re-sealed away refuses to spend. Malformed authority — corrupt route
664
+ or profile JSON, a model id that is not one, a turn bound or clock that is not
665
+ a positive whole number — fails closed in words and never shrinks.
666
+
667
+ Fallback chains and the declared risk level were removed (schema v115). An
668
+ unfinished task that was filed or approved with fallback agents asks again
669
+ after the update — its scope says so, and saving it once chooses its
670
+ agents; finished tasks and routes sealed at elevated or high risk keep their
671
+ exact bytes and still verify.
677
672
 
678
673
  Codex resumes carry their sandbox as a configuration override (`-c
679
674
  sandbox_mode=…`): `codex exec resume` has no `--sandbox` flag, so a structured
@@ -683,49 +678,37 @@ the planning attempt with its typed reason — the recorded session is not
683
678
  resumed twice — and the next attempt, after the planning backoff, is a fresh
684
679
  planner root in a fresh session.
685
680
 
686
- A **routine** freezes its agents too. Filing a standing order resolves its
687
- four-role route from the configuration of that moment and binds it into the
688
- digest you sign; approval seals the snapshot, and every firing re-hashes that
689
- snapshot against the approval, holds the build and repair legs to the sealed
690
- profile's exact provider and model, copies it onto the instance verbatim, and
691
- rolls the whole firing back unless the instance seals under it — a later
692
- `config set` cannot re-route a firing. One integrity projection answers
693
- every question about a standing order's agents — what the page says, whether
694
- a password may be minted, whether a yes may land, whether a firing may proceed
695
- — read before any write: an approval is *live* only when its frozen snapshot
696
- reads back, hashes with the stored terms to the digest the approver signed,
697
- states no leg problem, and agrees with the sealed profile's build and repair
698
- pairs. A routine approved before agents were frozen (an upgrade from before
699
- v48), one whose snapshot cannot be read, or one whose snapshot no longer
700
- verifies fires nothing and pages once; its page and `routine show` say so and
701
- offer the one road: `toolroll routine refresh <name>` (or the page's
702
- **Refresh agents** button) re-resolves the agents from today's configuration,
703
- withdraws an approval that is not live even when the working agents are
704
- unchanged, and approves nothing — you read the exact agents it now names and
705
- approve it again with your password, and only that yes fires the new
706
- snapshot. An authentic v47 database, or one whose v47→v48 upgrade was
707
- interrupted, upgrades without re-running older data passes, changing ids,
708
- backfilling a route, or approving anything.
681
+ A **scheduled flow** with a standing order files an ordinary task each time
682
+ it fires: the order's terms (goal, exclusions, touches, requirements, success
683
+ checks, per-run budget) are copied into the task's scope unchanged, and the
684
+ task waits for approval under the project's approval rules like any other
685
+ proposal. A standing order moved over from an old routine keeps that routine's
686
+ approval and its frozen agents while they still verify: each firing re-hashes
687
+ the frozen snapshot against the approval, holds the build and repair legs to
688
+ the sealed profile's exact provider and model, and rolls the whole firing back
689
+ unless the task seals under it — a later `config set` cannot re-route it. Once
690
+ that approval no longer verifies, each firing says why and waits for approval
691
+ as an ordinary proposal. `toolroll flows show` prints a standing order's
692
+ terms; `toolroll flows trigger pause|resume` (or **Resume** on the flow canvas)
693
+ stops and starts its schedule.
709
694
 
710
695
  Every approval surface — the task page, the focused chat, and `/next` — shows
711
- the same concise line of exact agents above the password, with the declared
712
- risk explained in plain words and the runtime limits one tap away; changing
696
+ the same concise line of exact agents above the password, with why stronger
697
+ agents run explained in plain words and the runtime limits one tap away; changing
713
698
  any agent invalidates the approval every surface signed under. Where no yes
714
- could bind — an unreadable route, a route that cannot run, a standing order
715
- without frozen agents, or a pre-routing row whose old approval no longer
716
- stands — those surfaces mint no nonce and show no password or approve button,
717
- only the reason and the act that opens it (re-file the scope, change the
718
- agents, refresh the routine) — the inbox row reads *needs attention* rather
719
- than *review & approve*, and the routine's recovery is one labelled, described
720
- button with nothing to type; an old approval already on a pre-routing row is
721
- grandfathered, but no new yes lands on it. In the demo sandbox, Chat is a
699
+ could bind — an unreadable route, a route that cannot run, or a pre-routing
700
+ row whose old approval no longer stands — those surfaces mint no nonce and
701
+ show no password or approve button, only the reason and the act that opens it
702
+ (re-file the scope or change the agents) — the inbox row reads *needs
703
+ attention* rather than *review & approve*; an old approval already on a
704
+ pre-routing row is grandfathered, but no new yes lands on it. In the demo sandbox, Chat is a
722
705
  scripted lead that never calls a model; real chat evidence is a subscription-backed plane. The task page's controls offer,
723
706
  per role, only the agents you configured *for that role* (gemini never
724
707
  reviews; repairs stay on the build provider), a current agent the
725
708
  configuration no longer names is shown for what runs today and never offered
726
- again, and each risk level says what it does — truthfully under strict
727
- quality and screenshot proof too. Chat reads the same route (`get_agents`)
728
- and proposes one confirmation-gated change (`propose_agents`) — a risk, one
709
+ again, and each size says what it does — truthfully under strict quality and
710
+ screenshot proof too. Chat reads the same route (`get_agents`)
711
+ and proposes one confirmation-gated change (`propose_agents`) — a size, one
729
712
  role switched to a listed agent, or a hand-picked role cleared — which lands,
730
713
  when you confirm the card, through the same authenticated route edit the page
731
714
  uses; the chosen agent is re-proved against the role's configured choices
@@ -947,9 +930,6 @@ Everything below ships in 0.4.0:
947
930
  dependencies (cycles refused), "starts after" on the filing form,
948
931
  `task next` moves work to the front of its own queue. Scheduling,
949
932
  never authority: approvals are untouched by any of it.
950
- - **Tournaments** — race 2–4 agents on one task under native dollar
951
- caps, compare their verified diffs side by side, and pick one through
952
- a password ceremony; the losers' branches and evidence are kept.
953
933
  - **The live peek** — a running build's page shows what is changing in
954
934
  its checkout right now (names and counts, never contents), through a
955
935
  native reader that executes nothing — no git command ever runs
@@ -967,8 +947,7 @@ Everything below ships in 0.4.0:
967
947
  into one new approval-bound task.
968
948
  - **Operating modes** — a per-repository, password-signed, expiring
969
949
  envelope that pre-authorizes the SIGNER'S OWN future acts: your
970
- filings approve themselves the moment you file them, watched sessions
971
- start without retyping your password, merges fire themselves when CI
950
+ filings approve themselves the moment you file them, merges fire themselves when CI
972
951
  is seen green on the exact authorized commit (only through a merge
973
952
  grant, never around one), and daily run/dollar rails bound the spend.
974
953
  Every term renders in words at the signing ceremony — including the
@@ -981,7 +960,7 @@ Everything below ships in 0.4.0:
981
960
  re-verified against its recorded hash, comments are proven
982
961
  patch-local, and they land beside your own for YOU to prune and seal
983
962
  into a revision task. A revision keeps the source's contract — its
984
- goal, exclusions, touches, exact rubric, declared risk, quality,
963
+ goal, exclusions, touches, exact rubric, quality,
985
964
  permission posture, and budget ceiling — however the installation's
986
965
  defaults have changed since, re-resolves its agents for a fresh
987
966
  approval, and inherits no approval, session, publication, or merge
@@ -1004,10 +983,6 @@ Everything below ships in 0.4.0:
1004
983
  doing what, and remove access with one ceremony that actually severs:
1005
984
  sessions, invites, and every mode they signed end together, while
1006
985
  history stays attributed forever.
1007
- - **Watched sessions, plural** — run several attended sessions per
1008
- worker under one signed ceiling, each with its own model and
1009
- permission posture chosen at mint, the whole execution profile under
1010
- the signature.
1011
986
  - **Scout tasks** — `task add … --report` (or the "scout" checkbox, or
1012
987
  the mate's `propose_task` with `report: true`) files a task whose
1013
988
  deliverable is a report, never a branch: once its scope is approved,
@@ -20,8 +20,8 @@
20
20
  * the caller snapshots it into the run row.
21
21
  */
22
22
  import { type AgentSpec, type Phase } from "./provider.js";
23
- import { type Store, type TaskRef } from "./store.js";
24
- import { type ChainEntry, type ExecutionProfile, type UnattendedPermissionMode } from "./scope.js";
23
+ import type { Store, TaskRef } from "./store.js";
24
+ import { type ExecutionProfile, type UnattendedPermissionMode } from "./scope.js";
25
25
  import { type ExactSpec, type PhaseRoute, type RouteCandidates } from "./phase-routing.js";
26
26
  import type { AcceptanceCriterion } from "./scope.js";
27
27
  export declare const INSTALLATION_SCOPE = "installation";
@@ -66,30 +66,6 @@ export declare function resolveScopeProfile(store: Store, repo: string | null, r
66
66
  repairModel?: string | undefined;
67
67
  permissionMode?: UnattendedPermissionMode | undefined;
68
68
  }): ProfileResolution;
69
- export type ChainResolution = {
70
- ok: true;
71
- chain: ChainEntry[];
72
- kind: "profile" | "chain";
73
- } | {
74
- ok: false;
75
- reason: "base-unresolved" | "bad-fallback" | "duplicate";
76
- problem: string;
77
- };
78
- /**
79
- * Resolve the full EXECUTION CHAIN a scope files under (v30): the base
80
- * (entry 0) is the ordinary single-profile resolution wearing the base
81
- * provider's auth mode; the fallbacks are the scope's configured entries,
82
- * each resolved to a WHOLE ExecutionProfile (repair per entry). With no
83
- * fallbacks configured, the result is `kind: "profile"` — a single-profile
84
- * approval, byte-identical to today (NOT an explicit chain). With
85
- * fallbacks, it is `kind: "chain"` and the approval binds the whole thing.
86
- * The base auth mode is passed in (the caller reads it from the managed
87
- * key store) so resolution stays pure and testable.
88
- */
89
- export declare function resolveScopeChain(store: Store, repo: string | null, ref: Pick<TaskRef, "agentProvider" | "agentModel"> | undefined, flags: PhaseFlags & {
90
- repairModel?: string | undefined;
91
- permissionMode?: UnattendedPermissionMode | undefined;
92
- }, baseAuthMode: "subscription" | "api-key"): ChainResolution;
93
69
  /**
94
70
  * The two candidate tiers the routing policy chooses between, per phase,
95
71
  * read from configuration ONLY — never inferred from a model's name:
@@ -166,16 +142,15 @@ export type TaskRoute = {
166
142
  };
167
143
  export declare function routeOfTask(store: Store, taskId: string, ref: TaskRef | null, now: Date): TaskRoute | null;
168
144
  /**
169
- * THE ROUTINE'S FROZEN AUTHORITY, resolved once at filing: the four-role
170
- * route a routine's firings will run under, and the execution profile that
171
- * restates its build and repair legs exactly. Routine tasks declare routine
172
- * risk; the rubric's evidence needs, the installation's quality default,
173
- * and the repository's publication authority are the other signed inputs.
174
- * A configuration that cannot make an exact, runnable route answers with
175
- * the words — the routine files unresolved and cannot be approved until
176
- * it is filed again under a configuration that can.
145
+ * THE EXACT AUTHORITY a project's configuration names today: the four-role
146
+ * route work would run under, and the execution profile that restates its
147
+ * build and repair legs exactly. The rubric's evidence needs, the
148
+ * installation's quality default, and the repository's publication
149
+ * authority are the other inputs. A configuration that cannot make an
150
+ * exact, runnable route answers with the words (the recipes preview shows
151
+ * them before any work is created).
177
152
  */
178
- export type RoutineAuthority = {
153
+ export type AgentAuthority = {
179
154
  ok: true;
180
155
  route: PhaseRoute;
181
156
  profile: ExecutionProfile;
@@ -183,7 +158,7 @@ export type RoutineAuthority = {
183
158
  ok: false;
184
159
  problem: string;
185
160
  };
186
- export declare function resolveRoutineAuthority(store: Store, repo: string, acceptance: readonly AcceptanceCriterion[], now: Date): RoutineAuthority;
161
+ export declare function resolveAgentAuthority(store: Store, repo: string, acceptance: readonly AcceptanceCriterion[], now: Date): AgentAuthority;
187
162
  /** One agent a surface shows for one role: an exact pair, where it comes
188
163
  * from, whether it is the role's current leg, and whether it can be
189
164
  * CHOSEN — only a pair the active configuration names for this role is
@@ -20,9 +20,7 @@
20
20
  * the caller snapshots it into the run row.
21
21
  */
22
22
  import { isProviderId, validateSpec } from "./provider.js";
23
- import { contestantProfileOf } from "./store.js";
24
- import { CLAUDE_LIMITS, CODEX_SHAPED_LIMITS, GEMINI_LIMITS, chainFromJson, canonicalChainJson } from "./scope.js";
25
- import { SUBSCRIPTION_CAPABLE } from "./keys.js";
23
+ import { CLAUDE_LIMITS, CODEX_SHAPED_LIMITS, GEMINI_LIMITS } from "./scope.js";
26
24
  import { exactModelId, legOf, PHASES, recommendRoute, routeFromJson, routeProblems, sameSpec } from "./phase-routing.js";
27
25
  export const INSTALLATION_SCOPE = "installation";
28
26
  /**
@@ -177,61 +175,6 @@ export function resolveScopeProfile(store, repo, ref, flags) {
177
175
  };
178
176
  return { ok: true, profile, provenance: { resolvedFrom: build.source, repairFrom } };
179
177
  }
180
- /**
181
- * Resolve the full EXECUTION CHAIN a scope files under (v30): the base
182
- * (entry 0) is the ordinary single-profile resolution wearing the base
183
- * provider's auth mode; the fallbacks are the scope's configured entries,
184
- * each resolved to a WHOLE ExecutionProfile (repair per entry). With no
185
- * fallbacks configured, the result is `kind: "profile"` — a single-profile
186
- * approval, byte-identical to today (NOT an explicit chain). With
187
- * fallbacks, it is `kind: "chain"` and the approval binds the whole thing.
188
- * The base auth mode is passed in (the caller reads it from the managed
189
- * key store) so resolution stays pure and testable.
190
- */
191
- export function resolveScopeChain(store, repo, ref, flags, baseAuthMode) {
192
- const base = resolveScopeProfile(store, repo, ref, flags);
193
- if (!base.ok)
194
- return { ok: false, reason: "base-unresolved", problem: base.problem };
195
- const unreadable = repo === null ? null : store.fallbackConfigProblem(repo);
196
- if (unreadable !== null)
197
- return { ok: false, reason: "bad-fallback", problem: unreadable };
198
- const fallbacks = repo === null ? [] : store.fallbackConfig(repo);
199
- if (fallbacks.length === 0) {
200
- // No fallbacks: a legacy single-profile approval, unchanged.
201
- return { ok: true, chain: [{ profile: base.profile, authMode: baseAuthMode }], kind: "profile" };
202
- }
203
- const entries = [{ profile: base.profile, authMode: baseAuthMode }];
204
- for (const one of fallbacks) {
205
- if (!isProviderId(one.provider) || one.model === undefined || one.model === "") {
206
- return { ok: false, reason: "bad-fallback", problem: `a fallback entry names an unknown provider or empty model (${one.provider}:${one.model})` };
207
- }
208
- if (one.authMode !== "subscription" && one.authMode !== "api-key") {
209
- return { ok: false, reason: "bad-fallback", problem: `a fallback entry has an unknown auth mode` };
210
- }
211
- // An entry can only pin an auth mode its provider can actually take:
212
- // "subscription" on a provider with no login (openrouter) would seal an
213
- // entry that can never authenticate (E3d, gateway pin).
214
- if (one.authMode === "subscription" && !SUBSCRIPTION_CAPABLE[one.provider]) {
215
- return { ok: false, reason: "bad-fallback", problem: `${one.provider} has no subscription login — this entry must use an API key` };
216
- }
217
- // The model rides provider argv (F+G review, finding 1): the SAME
218
- // argv-safety validation every other sealed model passes — never a
219
- // leading dash, control bytes, or whitespace into a harness's argv.
220
- const argvSafe = validateSpec({ provider: one.provider, model: one.model });
221
- if (!argvSafe.ok) {
222
- return { ok: false, reason: "bad-fallback", problem: argvSafe.problem };
223
- }
224
- entries.push({ profile: contestantProfileOf(one.provider, one.model, one.repairModel ?? "inherit", flags.permissionMode ?? store.permissionDefault().mode), authMode: one.authMode });
225
- }
226
- // Re-prove the whole chain through the strict rehydrator: it rejects
227
- // exact-duplicate entries and any malformed shape, so what the approval
228
- // seals is exactly what dispatch will re-derive.
229
- const proven = chainFromJson(canonicalChainJson(entries));
230
- if (proven === null) {
231
- return { ok: false, reason: "duplicate", problem: "the fallback chain has a duplicate entry (same profile and auth mode) or is malformed" };
232
- }
233
- return { ok: true, chain: proven, kind: "chain" };
234
- }
235
178
  const PHASE_NOUN = { plan: "planner", build: "builder", repair: "repair", review: "reviewer" };
236
179
  export function resolveRouteCandidates(store, repo, pins = {}) {
237
180
  // The LIGHT tier (v2): the operator's named fast builder, project over
@@ -289,7 +232,7 @@ export function resolveRouteCandidates(store, repo, pins = {}) {
289
232
  if (!routine.ok)
290
233
  return { ok: false, problem: routine.problem };
291
234
  if (routine.spec.model === null || routine.spec.model === "") {
292
- // A PINNED exact pair (a routine firing's approved profile, a plan
235
+ // A PINNED exact pair (an approved standing order's frozen profile, a plan
293
236
  // request's pin) is the phase's agent whatever the configuration
294
237
  // says — it stands in as the candidate when nothing exact is
295
238
  // configured, so a firing never depends on today's config.
@@ -417,7 +360,6 @@ export function routeOfTask(store, taskId, ref, now) {
417
360
  if (!candidates.ok)
418
361
  return { kind: "unreadable", problem: candidates.problem };
419
362
  const route = recommendRoute({
420
- risk: ref?.riskLevel ?? "routine",
421
363
  qualityMode: ref?.qualityMode ?? store.qualityDefault().mode,
422
364
  evidence: [],
423
365
  publication: store.publicationAuthorityOf(repo, now),
@@ -429,12 +371,11 @@ export function routeOfTask(store, taskId, ref, now) {
429
371
  });
430
372
  return { kind: "route", route, source: "live" };
431
373
  }
432
- export function resolveRoutineAuthority(store, repo, acceptance, now) {
374
+ export function resolveAgentAuthority(store, repo, acceptance, now) {
433
375
  const candidates = resolveRouteCandidates(store, repo);
434
376
  if (!candidates.ok)
435
377
  return { ok: false, problem: candidates.problem };
436
378
  const route = recommendRoute({
437
- risk: "routine",
438
379
  qualityMode: store.qualityDefault().mode,
439
380
  evidence: [...new Set(acceptance.flatMap(one => one.evidence))],
440
381
  publication: store.publicationAuthorityOf(repo, now),
@@ -12,7 +12,7 @@
12
12
  */
13
13
  /** Who filed a task, as far as anyone can tell: a person (browser, chat, a
14
14
  * revision), a coordinator acting for the person who made it, an AI
15
- * teammate, or automation that names no person (a routine, the CLI, a flow
15
+ * teammate, or automation that names no person (a schedule, the CLI, a flow
16
16
  * with no owner). The requester rule binds only a named person. */
17
17
  export type FilerKind = "person" | "coordinator" | "teammate" | "automation";
18
18
  export type Filer = {
@@ -133,5 +133,5 @@ export function gateWords(gate) {
133
133
  : `Your approval is recorded (${gate.have} of ${gate.need}). This work is protected, so a second person needs to approve it.`;
134
134
  return gate.reason === "requester"
135
135
  ? "You filed this task, and this project needs someone else to approve it."
136
- : "This work is protected: a person has to approve it, not an operating mode, a routine or an AI teammate.";
136
+ : "This work is protected: a person has to approve it, not an operating mode, a schedule or an AI teammate.";
137
137
  }
package/dist/backup.d.ts CHANGED
@@ -31,12 +31,20 @@ export declare function backupFiles(folder: string, owner: BackupOwner): {
31
31
  }[];
32
32
  /** Remove all but this database's newest `keep` backups. Never touches another database's, or anything else in the folder. */
33
33
  export declare function pruneBackups(folder: string, keep: number, owner: BackupOwner): number;
34
+ /** The checkpoint made before a backup: the head it records, a broken chain's first break (none recorded), or null (nothing new since the last). */
35
+ export type BackupCheckpoint = {
36
+ through: number;
37
+ hash: string;
38
+ } | {
39
+ problem: string;
40
+ } | null;
34
41
  export type BackupOutcome = {
35
42
  ok: true;
36
43
  id: number;
37
44
  file: string;
38
45
  bytes: number;
39
46
  removed: number;
47
+ checkpoint: BackupCheckpoint;
40
48
  } | {
41
49
  ok: false;
42
50
  id: number;
package/dist/backup.js CHANGED
@@ -164,6 +164,15 @@ export async function backupNow(store, databaseFile, trigger, clock = () => new
164
164
  let name = `standing-orders-${stamp}.db`;
165
165
  for (let n = 2; existsSync(join(folder, name)); n++)
166
166
  name = `standing-orders-${stamp}-${n}.db`;
167
+ // The whole chain walked and its head checkpointed first, so the copy carries the checkpoint. A broken chain gets
168
+ // none (the walk raises the console's chain alarm, as any whole walk does); the backup goes ahead either way.
169
+ let checkpoint;
170
+ try {
171
+ checkpoint = store.ledgerCheckpoint("system", clock(), {});
172
+ }
173
+ catch (error) {
174
+ checkpoint = { problem: message(error) };
175
+ }
167
176
  partial = join(folder, `.${name}.${randomBytes(4).toString("hex")}.partial`);
168
177
  const made = await onlineCopy(databaseFile, partial);
169
178
  const file = join(folder, name);
@@ -171,7 +180,7 @@ export async function backupNow(store, databaseFile, trigger, clock = () => new
171
180
  partial = null;
172
181
  const removed = pruneBackups(folder, settings.keep, backupOwner(store, databaseFile));
173
182
  store.finishBackupRun(id, { ok: true, file, bytes: made.bytes, schemaVersion: made.schemaVersion, removed }, clock());
174
- return { ok: true, id, file, bytes: made.bytes, removed };
183
+ return { ok: true, id, file, bytes: made.bytes, removed, checkpoint };
175
184
  }
176
185
  catch (error) {
177
186
  if (partial !== null)
package/dist/board.d.ts CHANGED
@@ -57,17 +57,9 @@ export type BoardFacts = {
57
57
  liveRunId?: number | null;
58
58
  /** The top-precedence live hold: operator > backoff > decision > incident. */
59
59
  hold: {
60
- ownerKind: "operator" | "decision" | "incident" | "backoff" | "contest";
60
+ ownerKind: "operator" | "decision" | "incident" | "backoff";
61
61
  until: string | null;
62
62
  } | null;
63
- /** The task's open tournament, when agents raced (v14). Optional so
64
- * callers that predate tournaments stay valid; absent reads as none. */
65
- contest?: {
66
- id: number;
67
- state: string;
68
- agents: number;
69
- kind?: "race" | "comparison";
70
- } | null;
71
63
  /** The first blocker that is not done, when one exists. */
72
64
  unmetDependency: string | null;
73
65
  /** That blocker's state — pre-redacted by the caller to null when the
@@ -76,11 +68,6 @@ export type BoardFacts = {
76
68
  blockerState: string | null;
77
69
  /** The first required capability not currently verified, as "kind:name". */
78
70
  missingRequirement: string | null;
79
- /** The standing order this task is an instance of, when it is one. The
80
- * board keeps instances in their track row — they enter the main lanes
81
- * only when they need a person, wearing the routine's name. */
82
- routineId: number | null;
83
- routineName: string | null;
84
71
  /** Queue rank — 0 is filing order. Scheduling only; ranks compare
85
72
  * within one column, never across columns. */
86
73
  priority?: number;
@@ -102,8 +89,6 @@ export type BoardCard = {
102
89
  /** The live attempt's ordinal when earlier ones failed — building only. */
103
90
  attempt: number | null;
104
91
  overdue: boolean;
105
- /** The routine this card is an instance of — the chip it wears in lanes. */
106
- routineName: string | null;
107
92
  /** The task's queue rank, carried for the queued lane's sort and badge. */
108
93
  priority: number;
109
94
  /** The worker this card is reserved for; null = the shared queue. */
@@ -111,9 +96,8 @@ export type BoardCard = {
111
96
  };
112
97
  /**
113
98
  * Plain words for every hold owner — shared with the task page so "who is
114
- * holding this" reads the same everywhere. The internal owner tokens
115
- * ("contest") never reach a page; an unknown future owner degrades to the
116
- * generic word, never to its raw name.
99
+ * holding this" reads the same everywhere. An unknown or retired owner
100
+ * degrades to the generic word, never to its raw name.
117
101
  */
118
102
  export declare const HOLD_OWNER_WORDS: Record<string, string>;
119
103
  export declare function holdOwnerWords(ownerKind: string): string;