Skip to main content

Module triage

Module triage 

Source
Expand description

Held-task triage: walking held tasks so a hold left by an accident does not sit unread forever next to one a human placed on purpose.

crate::queue::HoldSource already distinguishes “the daemon or conductor held this during its own recovery” (HoldSource::Machine, documented as recoverable) from “an operator held this on purpose” (HoldSource::Manual). What was missing was anything that actually acts on that distinction: nothing walked the held list and asked whether a machine hold’s cause was still true, and a record written before hold_source existed (schema < 3, None) was silently protected forever by Task::operator_held’s conservative default - never wrong, but also never looked at again by anything.

run_once is that walk. For every held task it finds:

  • HoldSource::Machine: if [machine_cause_resolved] can tell the cause is gone, the task goes straight back to queued - the same effect as magi task release, just automatic. When it cannot tell, a Question is filed once and the task stays held until answered.
  • None (a legacy record, or a hold nobody explained): always a question, exactly once - the whole point being that “protected forever” must not mean “never shown to anyone” either.
  • HoldSource::Manual: never touched automatically. Only once the hold has sat untouched past [MANUAL_STALE_AFTER] does it earn a question of its own, asking whether it is still wanted.

Every question this module files carries NODE and uses the task id as Question::run - the same convention crate::conduct’s own questions use for a task rather than a run (see conduct::apply_one’s own comment on why the dedupe check there also filters on node, not run alone: an ordinary graph question’s run is a real run id, and a coincidental equality with some task’s id must not be read as “about this task”). [latest_triage_question] follows the identical rule.

§Why answers apply here rather than through crate::queue::Task::block

crate::conduct blocks a task on its own question (Task::block(vec![question_id], …)), and crate::daemon::resolve_blockers unblocks it - unconditionally, back to queued - the moment that question is answered, whatever the answer actually said. That is correct for conduct: the content of the answer is meant for whoever reads Task::answers next, not for the resolver.

A triage question’s answer is different: “not yet” and “discard it” do two entirely different, non-resuming things, and only “resume it” may put the task back in line. Reusing the generic blocked/unblock path would resume every answer alike, so this module never calls Task::block and never leaves a triaged task anything but held while its question is open. [interpret_answer] reads Question::resolution itself and run_once acts on it directly: Task::release for an actual “resume it” choice, Queue::remove for “discard it” (捨ててよい really means “you may throw this away”, not “leave it sitting held” - the English wording must say the same thing, not “leave it held”), and Task::hold_manual for anything else - which both keeps the task held and reclassifies it as a hold an operator has now actually seen, one crate::conduct and a later triage pass leave alone.

The choice is read by its position in Question::choices ([Wording::choices3]/[Wording::choices2] always put “resume” first and “discard” third), never by comparing the answer text against [Wording]’s own strings picked from whatever config is in force now - the language a question was filed in and the language a later run_once call happens to read back are not guaranteed to be the same call’s Config, and a text comparison would silently misread a real “resume” answer as “keep held” the moment they disagree.

§Idempotency, without a new field

run_once runs on every daemon idle tick (see crate::daemon::poll) and on every magi task triage, so applying the same answered question twice has to be harmless - and, once a HoldSource::Manual/None question has been answered “not yet”, finding a fresh one for the same task later (once it goes stale again) has to still be possible. Neither crate::queue::Task nor crate::ask::Question has a field for “this answer was already applied”, so [already_applied] reads the same Question::short id back out of Task::hold_reason that [keep_held_note] appended to it - the same trick Question::abandon already uses to fold a fact into a text field that has no dedicated one. Appended, not written wholesale: the reason the hold happened in the first place is still worth reading in magi task show after an operator says “not yet”. A “resume” or “discard” answer needs no marker at all: the task either leaves held entirely or stops existing, and either way it is never looked at by this module again.

Structs§

Report
What one run_once pass did, task ids in each list.

Constants§

NODE
Node recorded on every question this module files - crate::conduct::NODE for the same idea applied to a crate::conduct decision instead.

Functions§

open_question_for
The open triage question about task_id, if any - what magi task show prints so a held task’s card names the question waiting on it, not only its hold reason. None once it is answered or abandoned: nothing is waiting on it anymore.
open_task_ids
Every task id with an open triage question right now - what magi task list uses to mark a held task that is already waiting on an operator decision, rather than have it read identically to one nobody has looked at yet.
run_once
Run one deterministic triage pass over every held task in queue. No model call anywhere in this function - see this module’s own doc for what each HoldSource gets instead.