Repository navigation
fix(mt#5137): Gate the unattended supervisor's child admission on autonomy class - #3762
Conversation
…onomy class
The supervisor tick now classifies the frontier's dispatchable children with the
same `classifyTasks` the routing service uses and spawns only servable classes
(`pull-only`, `contained`). A `principal-gated` or `unknown` child is recorded
rather than silently dropped: per tick on `SupervisionAdvance.excludedByClass`,
durably on the new `task_supervisions.last_excluded_by_class` jsonb column
(migration 0121), and in `tasks.supervision-status` under
`Withheld on autonomy class (N)` with the classifier's reason strings.
When every dispatchable child is gated the hold reason is
`all-candidates-gated-by-autonomy-class`, distinct from `all-children-blocked`
so an umbrella whose remaining work is the principal's does not read as a
dependency stall.
The check lives in the tick, not in `computeUmbrellaFrontier`: `tasks.orchestrate`
shares the frontier and answers an agent's own pull, where the default-deny
lands on the agent.
- `classifyTasks` extracted from `TaskRoutingService.classify` into
`autonomy-class-store.ts` so both consumers run one classifier.
- `SupervisionTickDeps.classifyCandidates` is required; the sweep wires it
through `createDbAutonomySignalSource(db)`.
- Tests: AT1 (READY pull-only + READY rfc-tagged + TODO child → one spawn, one
recorded exclusion, TODO filtered by status), the all-gated hold, exclusion
clearing on a clean tick, and AT2 on `formatSupervisionStatus`.
- Docs: `docs/autonomy-class.md` consumer list, `docs/architecture/cockpit.md`
§Unattended task-supervision sweeper.
- `transcript-sweep-backstop.test.ts`: all 11 starts now pass
`schemaReadiness: false` (the case the option's docblock names). Without it
the pre-commit fast gate fails on any schema-ahead branch: an earlier file
initializes config, Phase 0 reaches the live DB, finds 0121 pending, skips
every tick, and 14 tests time out. Recorded on mt#3575.
Execution evidence:
- `validate_typecheck` (session): pass, 0 errors, 8 workspaces
- `validate_lint` (session): pass, 0 errors, 0 warnings
- `bun test packages/domain/src/supervision/`: 24 pass, 0 fail
- `bun test src/adapters/shared/commands/tasks/supervision-commands.test.ts`: 10 pass, 0 fail
- `bun test packages/domain/src/tasks/{autonomy-class,task-routing-service,pointings-store}.test.ts`: 32 pass, 0 fail
- `bun test src/cockpit/task-supervision-sweep.test.ts`: 8 pass, 0 fail
- `bun test container.test.ts domain-repository-backend.test.ts transcript-sweep-backstop.test.ts`: 71 pass, 0 fail (was 14 fail before the schemaReadiness fix)
Negative control: with `isServableClass(verdict.class)` replaced by `true` in
`supervision-tick.ts`, `bun test ... -t "mt#5137"` → 0 pass, 3 fail (AT1
`Expected length: 0 / Received length: 2`; all-gated hold; clearing test).
Restored byte-identical (diff against backup: IDENTICAL).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0147JJ3SVgqRz9JyUD4NZBz9
Minsky Reviewer StatusVerdict: APPROVED — no blocking findings Commands
|
There was a problem hiding this comment.
Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2
Solid, spec-aligned implementation of autonomy-class gating for the unattended supervisor: classification is injected via a required dep, shared through the new classifyTasks to avoid drift, withheld children are durably recorded and surfaced, and cockpit logs carry per-class counts. Migration/schema wiring and tests (including mixed and all-gated cases, and clearing behavior) are present and meaningful. I raised a few non-blocking notes: storage type for the persisted class is a plain string (could narrow/validate), the UNCLASSIFIED reason text may leak internal phrasing to operators, and mixed-result logs only include counts without sample ids. None of these block merge. Overall, this meets the task’s success criteria and improves observability and consistency.
Findings
- [NON-BLOCKING] packages/domain/src/storage/schemas/task-supervisions-schema.ts:158 — Type of
lastExcludedByClass.classis a plainstringat the storage boundary while domain types expectTaskAutonomyClass
taskSupervisionsTable.lastExcludedByClassis typed with$type<Array<{ taskId: string; class: string; reasons: string[] }>>(), and downstream code (packages/domain/src/supervision/types.ts) casts it toExcludedCandidate[]whereclass: TaskAutonomyClass. This compiles (runtime JSONB is untyped), but widens the surface for accidental values not in the union and shifts validation to later. Consider tightening the schema’s$typeto the union, or validating/coercing on read to avoid persisting arbitrary strings. - [NON-BLOCKING] packages/domain/src/supervision/supervision-tick.ts:104 —
UNCLASSIFIEDreason string can leak into user surfaces if a classifier omission occurs
When a candidate id is absent fromclassifyCandidates’s map, it resolves toUNCLASSIFIEDwith reason"classifier returned no verdict for this task". That reason will flow tolast_excluded_by_class, CLI status, and cockpit logs. If an upstream bug or transient omission occurs, operators will see this internal diagnostic phrasing. Consider using a more operator-oriented wording (e.g.,"insufficient signals to determine autonomy class") and/or distinguishing between hard-unknown vs. plumbing/omission cases for clearer actionability. - [NON-BLOCKING] src/cockpit/task-supervision-sweep.ts:256 — When a tick both dispatches/settles and withholds, only per-class counts are logged — no sample identifiers
The new logs includeexcludedByClassaggregate counts on advancement lines and the explicit task-id list only when nothing dispatched. This meets SC2 (counts), but operators triaging mixed-result ticks may benefit from seeing a short sample (e.g., first N ids/classes) alongside counts to accelerate investigation without reaching for DB/CLI. Not blocking; raising as observability enhancement for operational clarity.
Spec verification
| Criterion | Status | Evidence |
|---|---|---|
| Admission via computeAutonomyClass through the registered AutonomySignalSource, only pull-only/contained admitted, exclusions recorded with class + reason — supervision-tick.ts step 3b + last_excluded_by_class; AT1 below. | Met | packages/domain/src/supervision/supervision-tick.ts:209-245 implements step 3b — calls deps.classifyCandidates and filters with isServableClass; withheld candidates are appended to advance.excludedByClass with {taskId,class,reasons}. Persisted each tick at packages/domain/src/supervision/supervision-tick.ts:336-342 to task_supervisions.last_excluded_by_class (migration adds column at packages/domain/src/storage/migrations/pg/0121_freezing_blonde_phantom.sql:1; schema wired at packages/domain/src/storage/schemas/task-supervisions-schema.ts:146-160). AT1 test covers spawn vs exclusion at packages/domain/src/supervision/supervision-tick.test.ts:88-129. |
| Per-tick log names excludedByClass counts — task-supervision-sweep.ts:263-278. | Met | src/cockpit/task-supervision-sweep.ts:256-279 aggregates per-class counts into excludedByClass and logs them on both advancement and withheld-only paths. The withheld-only branch logs explicit excluded ids as well. |
| Fake-signal-source test: one pull-only READY child + one rfc-tagged READY child → one spawn, other recorded principal-gated — AT1 below. | Met | packages/domain/src/supervision/supervision-tick.test.ts:101-129 builds a fake DAG, wires classifyCandidates using real classifyTasks with a fake signal source, asserts exactly one dispatch (mt#911) and one excludedByClass entry { taskId: 'mt#912', class: 'principal-gated', reasons: ['tag rfc'] }, and that the persisted lastExcludedByClass matches. |
Adoption sweep
| Symbol | Kind | Consumers found | Classification | Notes |
|---|---|---|---|---|
| classifyTasks | function | packages/domain/src/tasks/task-routing-service.ts:88 — private classify delegates to classifyTasks, packages/domain/src/supervision/supervision-tick.test.ts:68 — deps.classifyCandidates uses classifyTasks over fake graph, scripts/verify-task-supervision.ts:335 — deps.classifyCandidates wires classifyTasks with DB source, src/cockpit/task-supervision-sweep.ts:196-205 — classifyCandidates wired via classifyTasks with DB source | Adopted | Extracted from TaskRoutingService to a shared store and consumed by both routing and supervision paths to prevent drift. |
Documentation impact
- updated-in-pr — Docs updated in this PR to describe new behavior: docs/architecture/cockpit.md adds last_excluded_by_class column and new step 4 'Admit on autonomy class' (lines around added section); docs/autonomy-class.md adds the unattended supervisor consumer wiring details. These reflect the added persistence field and gating behavior. No existing doc statements read as contradicted post-change within the touched docs.
Affected: docs/architecture/cockpit.md, docs/autonomy-class.md
Cockpit PreviewPreview URL: https://cockpit-preview-production.up.railway.app Deployed from commit Updated by Cockpit Preview. |
…g line too A tick that dispatched one child and withheld another logged only per-class counts for the withheld one; the ids now ride on both the "advanced" and the "withheld" lines. Execution evidence: - `bun test src/cockpit/task-supervision-sweep.test.ts`: 8 pass, 0 fail Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0147JJ3SVgqRz9JyUD4NZBz9
There was a problem hiding this comment.
Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2
Verified the fix commit 909f85e addresses prior R1 feedback: the cockpit sweep now includes the withheld child ids on the "advanced" log line as well as per-class counts. I re-checked the supervision tick’s admission gating and durability against the current spec — SC1–SC3 are met, and tests exercise the new paths. No correctness regressions surfaced in the updated code; the only note is a non-blocking reminder to ensure any log consumers tolerate the added excludedByClass and excluded fields. With that, this iteration is ready to merge.
Findings
- [NON-BLOCKING] src/cockpit/task-supervision-sweep.ts:169 — Logging shape changed — ensure log consumers tolerate new
excludedByClassandexcludedfields
The tick log for both the "advanced" and "withheld" cases now includesexcludedByClass(an object of per-class counts) andexcluded(an array of"<taskId> (<class>)"). If any downstream parser relies on the previous exact shape, it may need an update. Suggest confirming no strict JSON-schema consumer exists or making the addition explicitly backward-compatible in dashboards.
Spec verification
| Criterion | Status | Evidence |
|---|---|---|
The supervisor's child admission computes each candidate's class with computeAutonomyClass (via the same AutonomySignalSource mt#5130 registers) and admits only pull-only / contained; excluded children are recorded on the supervision run with their class and reason, not silently skipped. |
Met | packages/domain/src/supervision/supervision-tick.ts:103-157 — Step 3b calls deps.classifyCandidates(...), uses isServableClass(verdict.class) to admit only servable classes, and pushes non-servable candidates into advance.excludedByClass with { taskId, class, reasons }. HOLD_ALL_GATED is set when every dispatchable child is withheld. The row update writes lastExcludedByClass each tick (lines 199-212). |
The sweep's own log line per tick names excludedByClass counts, so a supervision that spawns nothing says why. |
Met | src/cockpit/task-supervision-sweep.ts:150-187 — For each advance, the code aggregates excludedByClass counts and includes them in both the "advanced" and the "withheld" log lines; it also logs the excluded id list to make withheld tasks explicit. |
A test over a fake signal source: an umbrella with one pull-only READY child and one rfc-tagged READY child spawns exactly one child and records the other as excluded principal-gated. |
Met | packages/domain/src/supervision/supervision-tick.test.ts:93-141 — Test "spawns the pull-only child, withholds the principal-gated one, and records why (AT1)" builds a fake source, asserts one dispatch (mt#911) and one exclusion for mt#912 with class principal-gated and reasons ["tag rfc"], and verifies persistence on the supervision row. |
Documentation impact
- no-update-needed — This fix commit only augments the cockpit sweep log payload to include
excludedByClasscounts and theexcludedid list on the "advanced" line (src/cockpit/task-supervision-sweep.ts:165-187). It does not change any user-facing commands, APIs, or documented behavior; the additional keys are internal log fields. Existing supervision and autonomy-class docs remain accurate.
There was a problem hiding this comment.
Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2
Re-verified the fixes from the latest commit against the current spec. The supervisor tick now gates frontier candidates by autonomy class via the shared classifyTasks, records withheld children, and persists last_excluded_by_class; the cockpit sweep logs per-class counts and the id list. Tests cover the admission, all-gated hold, and clearing cases; the new shared classifier is adopted by both the routing service and the supervisor wiring. I find no new blocking defects. One non-blocking note: ensure any log consumers tolerate the added excludedByClass/excluded fields. With docs updated, this iteration is ready to merge.
Findings
- [NON-BLOCKING] src/cockpit/task-supervision-sweep.ts:268 — Log-payload additions: verify downstream tolerance for
excludedByClassandexcludedkeys
The sweep’s per-tick logs now include two additional fields on the "advanced" and new "withheld" lines:excludedByClass(per-class counts) andexcluded(array of"<taskId> (<class>)"). See src/cockpit/task-supervision-sweep.ts:268-286. If any log consumer parses these records with a strict schema, it may need an update. If consumers treat the log metadata as an open map, this is safe. Recommend a quick check on any dashboards or alerts that read these lines.
Spec verification
| Criterion | Status | Evidence |
|---|---|---|
Admission via computeAutonomyClass through the registered AutonomySignalSource, only pull-only/contained admitted, exclusions recorded with class + reason — supervision-tick.ts step 3b + last_excluded_by_class; AT1 below. |
Met | packages/domain/src/supervision/supervision-tick.ts:209-247 — Step 3b classifies undispatched via deps.classifyCandidates(...), admits only when isServableClass(verdict.class); non-servable candidates are pushed into advance.excludedByClass with { taskId, class, reasons }. packages/domain/src/storage/schemas/task-supervisions-schema.ts:146-162 defines last_excluded_by_class jsonb; supervision-tick.ts:332-338 persists it each tick (null when none). Tests: packages/domain/src/supervision/supervision-tick.test.ts:86-119 wires the REAL classifier; 134-172 asserts one spawn and one principal-gated exclusion with reasons. |
Per-tick log names excludedByClass counts — task-supervision-sweep.ts advance/withheld lines. |
Met | src/cockpit/task-supervision-sweep.ts:256-286 — Aggregates per-class counts into excludedByClass and lists excluded ids; includes both in the "advanced" line and emits a dedicated "withheld on autonomy class" line when nothing spawned but some were excluded. |
Fake-signal-source test: one pull-only READY child + one rfc-tagged READY child → one spawn, other recorded principal-gated — AT1 below. |
Met | packages/domain/src/supervision/supervision-tick.test.ts:134-172 — "spawns the pull-only child, withholds the principal-gated one, and records why (AT1)"; asserts exactly one dispatch (mt#911) and one exclusion for mt#912 { class: "principal-gated", reasons: ["tag rfc"] }, plus durability on the supervision row. |
Adoption sweep
| Symbol | Kind | Consumers found | Classification | Notes |
|---|---|---|---|---|
| packages/domain/src/tasks/autonomy-class-store.classifyTasks | function | packages/domain/src/tasks/task-routing-service.ts:88 — delegates private classify() to classifyTasks(tasks, this.signalSource), packages/domain/src/supervision/supervision-tick.test.ts:66 — deps.classifyCandidates uses classifyTasks over fake graph, scripts/verify-task-supervision.ts:335 — classifyCandidates wires classifyTasks with DB-backed signal source, src/cockpit/task-supervision-sweep.ts:198 — classifyCandidates wires classifyTasks with DB-backed signal source |
Adopted | Shared classifier extracted to prevent drift between tasks_available and the unattended supervisor (mt#5137). |
Documentation impact
- updated-in-pr — Docs updated alongside the code: docs/architecture/cockpit.md adds
last_excluded_by_classand a new Step 4 explaining autonomy-class admission; docs/autonomy-class.md now lists the unattended supervisor as a wired consumer with durability and status-surface details.
Affected: docs/architecture/cockpit.md, docs/autonomy-class.md
There was a problem hiding this comment.
Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2
Re-verified the latest changes against the current spec. The supervisor tick now gates undispatched frontier children via the shared classifyTasks, records withheld candidates, persists last_excluded_by_class, and distinguishes the all-gated hold. Cockpit sweep logs include per-class counts and ids; status formatting surfaces withheld children with reasons. All call sites supply the required classifyCandidates dep; migrations and schema wiring are consistent. Tests cover admission, all-gated holds, and clearing. I see no new blocking defects introduced by this iteration — approving.
Spec verification
| Criterion | Status | Evidence |
|---|---|---|
The supervisor's child admission computes each candidate's class with computeAutonomyClass (via the same AutonomySignalSource mt#5130 registers) and admits only pull-only / contained; excluded children are recorded on the supervision run with their class and reason, not silently skipped. |
Met | packages/domain/src/supervision/supervision-tick.ts: step 3b adds autonomy-class admission — calls deps.classifyCandidates(...), keeps only isServableClass(verdict.class), and pushes withheld candidates into advance.excludedByClass; later persists lastExcludedByClass on the row via store.updateSupervision. |
The sweep's own log line per tick names excludedByClass counts — task-supervision-sweep.ts advance/withheld lines. |
Met | src/cockpit/task-supervision-sweep.ts:256-286 — computes excludedByClass per-class counts and excluded id list for each advance; includes them on the "advanced" log line and emits a dedicated "withheld on autonomy class" log when nothing spawned but exclusions exist. |
A test over a fake signal source: an umbrella with one pull-only READY child and one rfc-tagged READY child spawns exactly one child and records the other as excluded principal-gated. |
Met | packages/domain/src/supervision/supervision-tick.test.ts:134-172 — test "spawns the pull-only child, withholds the principal-gated one, and records why (AT1)" builds a fake source over real classifier, asserts one dispatch and one principal-gated exclusion with reasons, and verifies lastExcludedByClass was written. |
Adoption sweep
| Symbol | Kind | Consumers found | Classification | Notes |
|---|---|---|---|---|
| packages/domain/src/tasks/autonomy-class-store.classifyTasks | function | packages/domain/src/tasks/task-routing-service.ts — private method classify now delegates to classifyTasks(tasks, this.signalSource), packages/domain/src/supervision/supervision-tick.test.ts — deps.classifyCandidates uses classifyTasks over fake graph, scripts/verify-task-supervision.ts — wires classifyTasks with DB-backed signal source for verify script, src/cockpit/task-supervision-sweep.ts — wires classifyTasks with DB-backed signal source in tick deps |
Adopted | Shared classifier extracted to prevent drift; both the routing service and the unattended supervisor now share it per mt#5137. |
Documentation impact
- updated-in-pr — Docs were updated alongside the code to describe the new autonomy-class admission and durable record: docs/architecture/cockpit.md adds
last_excluded_by_classto thetask_supervisionsrecord and a new Step 4 detailing admission; docs/autonomy-class.md now lists the unattended supervisor as a wired consumer and explains per-tick/row/status surfaces.
Affected: docs/architecture/cockpit.md, docs/autonomy-class.md
Summary
The unattended supervisor (mt#4571) dispatched every unblocked child in its frontier with no regard for the child's autonomy class (RFC 3ae937f0 / mt#5130) — an
rfc-tagged orstate-opschild would be spawned unattended. Now the tick classifies the not-yet-dispatched frontier with the sameclassifyTasksthattasks_availableuses, spawns only servable classes (pull-only,contained), and records every withheld child rather than silently shrinking the frontier.Had Claude implement this against the mt#5137 spec.
What changed
packages/domain/src/supervision/supervision-tick.ts, step 3b):principal-gatedandunknownchildren go toSupervisionAdvance.excludedByClasswith the classifier's reason strings. When every dispatchable child is withheld the hold reason isall-candidates-gated-by-autonomy-class(HOLD_ALL_GATED), distinct fromall-children-blocked, so an umbrella whose remaining work is the principal's does not read as a dependency stall.task_supervisions.last_excluded_by_classjsonb column (migration0121_freezing_blonde_phantom.sql), written each tick (nullwhen nothing was excluded, so a stale exclusion cannot persist).src/cockpit/task-supervision-sweep.ts): the per-tick advance line carriesexcludedByClassper-class counts plus the<taskId> (<class>)list; a tick that spawned nothing but withheld something logscockpit: supervision withheld on autonomy class <umbrella>with the same fields.tasks.supervision-statusrendersWithheld on autonomy class (N):/<taskId> <class>: <reasons>.classifyTasks(tasks, signalSource)extracted fromTaskRoutingService's privateclassifyintoautonomy-class-store.ts; the routing service now calls it, so the two auto-selecting consumers cannot disagree.SupervisionTickDeps.classifyCandidatesis REQUIRED (not optional — a consumer that forgets it must fail to typecheck rather than dispatch ungated). The cockpit sweep supplies it viacreateDbAutonomySignalSource(db);scripts/verify-task-supervision.tslikewise.computeUmbrellaFrontier—tasks.orchestrateshares the frontier and answers an agent's own pull, where the default-deny lands on the agent (docs/autonomy-class.md).docs/autonomy-class.mdconsumer list (replaces the "Not yet wired" line),docs/architecture/cockpit.md §Unattended task-supervision sweeper(new column, new step 4).Success criteria
computeAutonomyClassthrough the registeredAutonomySignalSource, onlypull-only/containedadmitted, exclusions recorded with class + reason —supervision-tick.tsstep 3b +last_excluded_by_class; AT1 below.excludedByClasscounts —task-supervision-sweep.tsadvance/withheld lines.pull-onlyREADY child + onerfc-tagged READY child → one spawn, other recordedprincipal-gated— AT1 below.Tests
supervision-tick.test.ts): umbrella with a READY untagged child, a READYrfc-tagged child, and a TODO child, filter["READY"]→ exactly one spawn (the untagged child), oneexcludedByClassentry{ mt#912, principal-gated, ["tag rfc"] }, the TODO child neither dispatched nor listed (status-filtered before classification),lastExcludedByClasspersisted on the row, hold reasonnull. Uses the REAL classifier over the fake graph with a fake signal source — no stubbed verdicts.RFC:-titled child + astate-opschild → 0 spawns,HOLD_ALL_GATED, both recorded.lastExcludedByClass: null.supervision-commands.test.ts):formatSupervisionStatuslists the withheld child with its reason string; omits the section when empty.Negative control: with
isServableClass(verdict.class)replaced bytrueinsupervision-tick.ts,bun test ... -t "mt#5137"→ 0 pass / 3 fail (AT1:Expected length: 0 / Received length: 2). Restored byte-identical (diff against backup: IDENTICAL).Execution evidence:
validate_typecheck(session): pass, 0 errors, 8 workspacesvalidate_lint(session): pass, 0 errors, 0 warningsbun test packages/domain/src/supervision/: 24 pass, 0 failbun test src/adapters/shared/commands/tasks/supervision-commands.test.ts: 10 pass, 0 failbun test packages/domain/src/tasks/{autonomy-class,task-routing-service,pointings-store}.test.ts: 32 pass, 0 failbun test src/cockpit/task-supervision-sweep.test.ts: 8 pass, 0 fail (re-run after R1: 8 pass, 0 fail)run-related-tests.ts, 134 files): passed on the committed tree; pre-push gated runner passed (pushed without a hatch)Reviewer R1 (APPROVED, 3 non-blocking)
909f85e6c): the<taskId> (<class>)list now rides on both lines.lastExcludedByClass.classisstringat the storage boundary,TaskAutonomyClassin the domain — deliberate. The column is jsonb, so a row written by an earlier build can carry a class name a later build has retired; keeping the storage type honest (string) and narrowing inDrizzleSupervisionStore's row mapper keeps the cast at the one seam that owns it, rather than letting the schema assert a guarantee the database does not give.UNCLASSIFIEDreason string can reach user surfaces — intended. The reason (classifier returned no verdict for this task) exists to be read: a status report that saidunknownwith no reason would send the operator to the logs for exactly this sentence. It is a default-deny made visible, not a leak.Incidental fix:
transcript-sweep-backstop.test.tsorder-dependenceThe pre-commit fast gate failed 14 tests in
src/cockpit/transcript-sweep-backstop.test.tson this branch and passes on main. Cause:packages/domain/src/composition/container.test.tsruns earlier in the same process and initializes configuration, so the sweep's Phase 0refreshSchemaReadinessFromDbreached the operator's live DB, found migration 0121 pending, skipped every tick, andsweepsRun >= 1never held. The file's 11startTranscriptSweepBackstopstarts injecteddepsbut never passedschemaReadiness: false— the exact case the option's docblock names, and which the siblingtranscript-backfill-sweep.test.tsalready handles. All 11 now pass it. Reproduced with 3 files, fixed at the source; recorded on mt#3575 as a second same-day instance of the schema-ahead-branch hazard.Deploy verification
Deploy surface:
minsky-mcp(migration 0121 applied bydeploy-minsky-mcp.ymlbefore the image push; the supervisor sweep runs in the cockpit daemon, which picks up the code on restart). Post-merge: confirmtask_supervisions.last_excluded_by_classexists in prod viainformation_schema.columns, correlate theDeploy MCPrun head_sha with the merge commit, and read/healthservice identity.Consumer account
No signal-producing call removed.
TaskRoutingService.classifywas replaced by a delegation toclassifyTaskswith identical inputs and outputs (task-routing-service.test.ts+tests/domain/tasks/task-routing-service.test.tsunchanged and green).Spec: mt#5137. Parent handoff: mt#5144.
🤖 Generated with Claude Code
https://claude.ai/code/session_0147JJ3SVgqRz9JyUD4NZBz9