Skip to content

The kitchen queue watches, and prints nothing - #787

Merged
sridharkalaibala merged 1 commit into
developfrom
feat/the-kitchen-queue-watches-in-shadow
Sep 15, 2026
Merged

sridharkalaibala merged 1 commit into
developfrom
feat/the-kitchen-queue-watches-in-shadow

Conversation

@sridharkalaibala

Copy link
Copy Markdown
Contributor

Step 1 of Stage 2 in the print roadmap, and the step usually skipped:

"Write print_jobs rows and drain nothing. Compare, for a week, what the queue
says should print against what actually printed. Any disagreement is a bug
found before it can cost anything."

Why it's worth a whole change to print nothing

Ninety shops feed their kitchens through multiKitchenPrint. The failure mode of
a bad cutover is not an error message — it is a dish nobody made, or two dishes
where one was ordered.

"printing dont bring me new issues. keep changes safely"

So the queue records what it believes should print, the old path keeps printing
exactly as it does, and the two are compared. Nobody can currently say whether
duplicates happen four times a week or forty.
This can answer that without
touching a printer.

How it cannot print

Rows are written with status shadow, which nothing claims — claimPrintJobs
matches queued only, and the till asks for bills. A test drives the real
claim
and fails if that filter ever widens:

await claimPrintJobs({ branchId, tillId, kind: 'kot' }, { Model });
expect(filter.status).toBe('queued');
expect(JSON.stringify(filter)).not.toContain('shadow');

They carry no payload either, so there is nothing to print even by accident.

What it records

Tickets named the way the till names them — both schemes. A sale with no
print_jobs array is named by the fallback rather than skipped, because skipping
it would make the shadow blind to a whole class of ticket and then report
perfect agreement, which is the worst outcome a measurement can have.

One row per ticket, enforced by a partial unique index rather than a
read-then-write: two tills polling at once would both read "absent" and both
insert. An amended order is a second row, because it is a second ticket.

Closing a row, and the build that doesn't know about it

The till now sends printedKeys — the names of what actually reached paper, and
only when paper actually came out. A failed ticket reported as printed would
close a row that should be showing up as a disagreement.

But ticket names are new on the wire. Closing only by name would leave every row
from every older till open for ever, and the first report would say the whole
estate is failing — worthless exactly when it's meant to establish a baseline.
So an older till still closes its rows by sale. Less precise, and it
understates disagreement rather than inventing it, which is the safe direction
for a number nobody has yet.

It is a bystander

Every failure is swallowed inside, and a test drives all three functions against
a database that throws on every call:

await expect(shadow.recordExpected(...)).resolves.toMatchObject({ status: true });

A shadow that breaks a service is worse than no shadow at all.

Tests

17 new cases. API 10715 pass / 0 fail. Desktop 2622 pass / 0 fail.

What this makes possible

After a week of real data, disagreements() answers two questions that have
never had an answer:

  • queued, never reported — the server expected a ticket the till never said
    it printed
  • reported, never queued — the till printed something the server did not
    expect, which is the shape a duplicate has

That baseline is what Stage 2's cutover decision should rest on.

Step 1 of Stage 2 in the print roadmap, and the step usually skipped:

  "Write print_jobs rows and drain nothing. Compare, for a week, what the queue
  says should print against what actually printed. Any disagreement is a bug
  found before it can cost anything."

WHY IT IS WORTH A WHOLE CHANGE TO PRINT NOTHING

Ninety shops feed their kitchens through multiKitchenPrint. The failure mode of
a bad cutover is not an error message - it is a dish nobody made, or two dishes
where one was ordered. Owner: "printing dont bring me new issues. keep changes
safely."

So the queue records what it believes should print, the old path keeps printing
exactly as it does, and the two are compared. Nobody can currently say whether
duplicates happen four times a week or forty; this can answer that without
touching a printer.

HOW IT CANNOT PRINT

Rows are written with status `shadow`, which nothing claims - claimPrintJobs
matches `queued` only, and the till asks for bills. A test drives the real claim
and fails if that filter ever widens. They carry no payload either, so there is
nothing to print even by accident.

WHAT IT RECORDS

Tickets named the way the TILL names them, both schemes - a sale with no
print_jobs array is named by the fallback rather than skipped, because skipping
it would make the shadow blind to a whole class of ticket and then report
perfect agreement, which is the worst outcome a measurement can have.

One row per ticket, enforced by a partial unique index rather than by a
read-then-write: two tills polling at once would both read "absent" and both
insert. An amended order is a second row, because it is a second ticket.

CLOSING A ROW, AND THE BUILD THAT DOES NOT KNOW ABOUT IT

The till now sends printedKeys - the names of what actually reached paper, only
when paper actually came out. A failed ticket reported as printed would close a
row that should be showing up as a disagreement.

But ticket names are new on the wire, and closing only by name would leave every
row from every older till open for ever - the first report would say the whole
estate is failing, which is worthless exactly when it is meant to establish a
baseline. So an older till still closes its rows by sale. That is less precise
and understates disagreement rather than inventing it, which is the safe
direction for a number nobody has yet.

It is a bystander throughout: every failure is swallowed inside, and a test
drives it against a database that throws on every call to prove a broken shadow
cannot fail a service.

API 10715 pass, desktop 2541 pass.
@sridharkalaibala
sridharkalaibala merged commit 3106519 into develop Sep 15, 2026
9 checks passed
@github-actions github-actions Bot added the ready for QA Merged to develop and live on develop.posnic.io - anyone can test it label Sep 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Merged to develop. Anyone can test this - you do not need write access.

Try it at https://develop.posnic.io, or run it yourself:

git fetch origin develop && git checkout develop
npm install && npm --prefix api install
npm run dev   # then http://localhost:3000

When you have tested it, say what you did and what happened, and set
QA passed or QA failed. If you cannot set labels, just comment -
a maintainer will.

Reporting that something is broken is as useful as fixing it. It is
better found here than by a shopkeeper.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for QA Merged to develop and live on develop.posnic.io - anyone can test it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant