fix(core): check the printer once per job, leave a job in hand to its lane, and always let go of a job #44

Merged
james.maes merged 8 commits from fix/2.0-worker-recheck-and-in-hand-20261110 into release/2.0 2026-11-11 02:36:59 +00:00
Owner

Core halves of two audit findings on the print worker, plus an older bug found while reviewing them.

What changes

  • One printer check per job, where it was. The printer is checked once, after the download and before SUBMITTING is recorded. A printer that is unshared, or whose media default changes, while the job waits is refused with FAILED and Windows is never asked.
  • A job a lane already holds is recognised when it is offered again. The lane that holds it carries on; the second offer ends that pass and records nothing. Before, the second offer could stop a print lane with "same key".
  • A lane always lets go of a job. If recording a failed receipt itself failed, the lane's hold on the job was never released; later offers of that job were told a lane still held it, and an update waiting for the lanes to empty could wait forever. The hold is now released whatever the recording does.

What does not change

  • SUBMITTING is recorded before Windows is asked; FAILED means Windows was never asked; an interrupted submission is OUTCOME_UNKNOWN and is never reprinted automatically. The reviewer confirmed the recording points are untouched.
  • A queue that is removed while a job waits still ends as OUTCOME_UNKNOWN, because the helper has no error code for it (recorded in the audit).

Verification

  • Head 69ffc19 on release/2.0 at db6ee01.
  • build/Verify.ps1 passed on that head: 782 Core, 1,523 Windows runtime and command line, 352 presentation tests, none failed; Core 100% of 2,148 lines; coverage gate passed (Windows 87.37%).

Review (independent reviewer, two passes)

  • 871764e: one should-fix (the runtime test did not exercise the case the branch fixes), four nits.
  • 99b947c: Verdict on 99b947c: 0 blockers, 0 should-fix, 0 nits. The reviewer's own mutation (the in-hand check wired to the wrong set) now fails the test; removing the release fails its test.
  • The head merged is 99b947c replayed onto db6ee01; git range-diff e2ca086..99b947c db6ee01..69ffc19 reports all eight patches identical.

🤖 Generated with Claude Code

Core halves of two audit findings on the print worker, plus an older bug found while reviewing them. **What changes** - **One printer check per job, where it was.** The printer is checked once, after the download and before SUBMITTING is recorded. A printer that is unshared, or whose media default changes, while the job waits is refused with FAILED and Windows is never asked. - **A job a lane already holds is recognised when it is offered again.** The lane that holds it carries on; the second offer ends that pass and records nothing. Before, the second offer could stop a print lane with "same key". - **A lane always lets go of a job.** If recording a failed receipt itself failed, the lane's hold on the job was never released; later offers of that job were told a lane still held it, and an update waiting for the lanes to empty could wait forever. The hold is now released whatever the recording does. **What does not change** - SUBMITTING is recorded before Windows is asked; FAILED means Windows was never asked; an interrupted submission is OUTCOME_UNKNOWN and is never reprinted automatically. The reviewer confirmed the recording points are untouched. - A queue that is removed while a job waits still ends as OUTCOME_UNKNOWN, because the helper has no error code for it (recorded in the audit). **Verification** - Head `69ffc19` on `release/2.0` at `db6ee01`. - `build/Verify.ps1` passed on that head: 782 Core, 1,523 Windows runtime and command line, 352 presentation tests, none failed; Core 100% of 2,148 lines; coverage gate passed (Windows 87.37%). **Review (independent reviewer, two passes)** - `871764e`: one should-fix (the runtime test did not exercise the case the branch fixes), four nits. - `99b947c`: `Verdict on 99b947c: 0 blockers, 0 should-fix, 0 nits.` The reviewer's own mutation (the in-hand check wired to the wrong set) now fails the test; removing the release fails its test. - The head merged is `99b947c` replayed onto `db6ee01`; `git range-diff e2ca086..99b947c db6ee01..69ffc19` reports all eight patches identical. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
james.maes added 8 commits 2026-11-11 02:36:43 +00:00
Two cases reached Windows-facing code that should have been settled
before it.

A printer was checked once, after the download, and then the job waited
for the printer's turn. A printer unshared during that wait was refused
only by the exposure filter, after SUBMITTING was recorded, and the job
became OUTCOME_UNKNOWN although Windows was never asked. The worker now
checks the printer again once the turn is held and before SUBMITTING: a
printer no longer shared, a queue that is gone or a changed media default
is FAILED. The filter keeps its own check for the instant in between, and
its refusal is now PrinterNotExposedException, which only the filter can
create and creates before it calls the backend behind it. The worker
records that refusal as FAILED too, as it does the native layer's media
mismatch, and its reason says no bytes were submitted.

A replay of a job another lane holds was stopped only by the runtime's
ticket registry refusing a duplicate key. PrintWorkerOptions gains a
required IsInHand hook; a claimed job that is in hand is logged once,
records nothing and ends the pass. The runtime answers from its tickets.

The timing line's revalidate figure now covers both printer checks.

Signed-off-by: James Maes <james@kof22.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
REALTIME gains three entries under the claimed jobs the worker does not
print, and the changelog says what an operator sees.

Signed-off-by: James Maes <james@kof22.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Review of this branch asked for the second printer discovery, made when the
printer's turn is held, to be removed. It started the print helper once more
for every job while the job held the printer's turn and a print slot, and
doubled the exposure to a slow or hung driver on an unrelated queue. The
typed refusal of the exposure filter already gives a true FAILED when a
printer is unshared while the job waits, and the native layer already fails
a media change.

The printer is discovered again once, after the download, as before this
branch. The timing line's revalidate figure is one check again.

What a change during the wait for the turn becomes:
- no longer shared: refused by the exposure filter after SUBMITTING, FAILED,
  nothing sent (RECEIVED, SUBMITTING, FAILED)
- selected media default changed: refused by the native layer before it
  prints, FAILED after SUBMITTING
- queue removed from Windows: the native layer fails without a type the
  worker can rely on, so OUTCOME_UNKNOWN. An error code for a missing queue
  across the helper boundary would make this FAILED; it is not part of this
  change.

The in-hand log line said the computer "is already working on" the job. A
lane lets go of a job only after its final state was recorded and delivered,
so the line can describe a job that has just finished. It now says a lane
"still holds it, working on it or just finished with it", and the test has a
row for a job in hand that is already SPOOLED.

Signed-off-by: James Maes <james@kof22.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
One runtime test for the in-hand function, on the realtime fixture's existing
public members. One lane is inside the printer with a job; the server offers
the same job to a second lane, whose claim is held at the server until then.
The second lane logs the in-hand line once, records and reports nothing, does
not register a second ticket (which stopped the lane with "An item with the
same key has already been added") and does not print. The job is then
SPOOLED and was printed once.

Signed-off-by: James Maes <james@kof22.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
REALTIME no longer describes a second check when the printer's turn is held.
It says what settles each change that happens while a job waits for its turn
(the exposure filter, the print helper's media check, and the missing queue
that is still OUTCOME_UNKNOWN), and that a job in hand may be one that has
just finished. The changelog entry is rewritten to match.

Signed-off-by: James Maes <james@kof22.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The runtime test for a job in hand held the first lane inside the printer,
where SUBMITTING is already recorded. A build without the in-hand check
stops that offer through the journal, so only the wording assertion could
fail there, and a check wired to the submitting set instead of the ticket
registry passed the whole test.

The test is now a theory with two rows. In the first the lane is held in
its download, so the journal says RECEIVED and only the ticket shows the
job is in hand; in the second it is inside the printer, as before. Both
the in-hand line and the absence of a stopped lane discriminate in the
first row: against release/2.0 without the check, and against the check
wired to the submitting set.

It is also deterministic now. One download is allowed at first, so no
second lane can start a pass until the first lane is held; the test then
puts the job back on the server's queue and has the server allow a second
download. Before, the second lane could read the journal in the instant
between the first lane's RECEIVED being recorded and acknowledged, end
the wave, and leave the test waiting for a claim that never came.

Signed-off-by: James Maes <james@kof22.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
When the receipt for a newly claimed job could not be delivered, the
worker recorded FAILED and then told the runtime the job was no longer
active. If the journal failed on that FAILED, the second step was skipped
and the job's ticket stayed in the runtime's registry until the service
restarted. Every later offer of that job was then logged as one a lane
still holds, which was false. Before the in-hand check the same leak
showed as "An item with the same key has already been added".

The lane now lets go in a finally around the recording. Nothing changes
in when RECEIVED or FAILED are recorded, or in which failure reaches the
caller: the journal's, as before, when recording fails.

The new test fails without the change: the runtime is told the job is
active and never told it stopped.

Signed-off-by: James Maes <james@kof22.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The worker's comments, the option's summary and REALTIME.md said a lane
lets go of a job "after its final state was recorded and delivered". That
is the normal case, not a rule: delivery is skipped when the pass is
cancelled and can fail, and the lane lets go all the same. They now say
the lane holds a job until its pass for that job ends, and what normally
comes before that.

The summary of a pass also lists a job a lane already holds among the
reasons it returns false.

Comments and documentation only.

Signed-off-by: James Maes <james@kof22.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
james.maes merged commit 6ad3001d1e into release/2.0 2026-11-11 02:36:59 +00:00
Sign in to join this conversation.