Skip to content

feat: send a cart as one request for a quote - #8

Open
JasonYv wants to merge 5 commits into
feature/line-quantity-limitfrom
feature/inquiry-cart
Open

JasonYv wants to merge 5 commits into
feature/line-quantity-limitfrom
feature/inquiry-cart

Conversation

@JasonYv

@JasonYv JasonYv commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Stacked on #7 (the line ceiling). Review that one first; this one's diff is against its branch. Merge in order, by fast-forward, so that the commits keep their ids.

What this is

Phase 1's last piece: the cart, sent as one request for a quote. The owner decided it is the shop plugin's to build (design §11, decision 10; what was built and found is §18).

A buyer fills a cart, says on the cart page who to answer — a name and an address; a company, a phone number and a message if they like — and sends it. They land on the cart page again, empty, with the inquiry's number to quote. Nothing is charged and no stock is taken
A seller is told by email, answerable straight to the buyer, and reads every inquiry in the admin with its lines: to mark, export as CSV, or delete
A size with no price can go in the cart now, to be asked about. Without that an inquiry could only ask about what already has a price

How it works, in short

  • POST cart/inquiry never renders: it answers 303 to the cart page with the inquiry's number, or the kind of reason none was made. Nothing a person typed travels in an address.
  • The write is one batch: the inquiry, its lines as a snapshot, the job that owes its emails, the cart emptied. It happens only while the cart is still exactly what is being sent and the visitor is under five inquiries an hour, both asked inside the statement — so two forms at once store one inquiry, and a refused one leaves the cart alone.
  • The emails are a job queued in that batch, not a call made on the strength of the write's answer.
  • The form has no challenge in front of it: Mallok has nowhere to draw one on a plugin's page. What stands in its place, and what that does not stop, is in SECURITY.md. Because of it the email confirming to the buyer is built and off by default.

An independent review, and what it found

The finished work was given to a second reviewer with the properties it must hold and no account of how. It found the guards sound and eleven things wrong around them; each was checked before it was acted on. The ones that mattered most:

  • The tests of the two races could not be relied on. Two requests sent with Promise.all interleave about half the time in this harness; the other half never reaches the guard inside the write. They now hold both requests at the write, and each guard, broken, is red every time.
  • Deleting an inquiry does not delete its email from Mallok's queue, and the documents said that it did. They say what is true now, with what an operator has to do.
  • A semicolon let a formula through the CSV export where it is the list separator.
  • An address could carry a mailto: link's own syntax, which the admin links as stored.

How it was verified

Check Result
lint, type check, build, both smoke runs, the preview check Pass
Tests inside workerd 775 of 775, 137 of them new
Red and green 84 deliberate breakages in two rounds; three showed a test that was not sharp enough, which was made so. The four guards inside the write were each broken three times over and went red every time
The smoke run sends a cart through the page's own form on a real local Worker, in German, reads the inquiry back the way the admin does, and was seen to fail with the confirmation's check taken out
In a browser, by hand the form at ten widths in four languages; the keyboard's order; the fields refusing and accepting what the server does; one cart sent; the admin's panel, an inquiry opened with its lines, the delete action refusing an unticked box

Not verified

  • Nothing was deployed, so no email was ever delivered: the tests end where Mallok's queue takes the message.
  • The form was not tried with a screen reader. Whether a browser's autofill ever fills the field no person sees was not tried.
  • The German, French and Spanish strings and emails were not read by a native speaker. The export was checked in its text, not in a spreadsheet.

An inquiry is quoted by a number of the same shape as an order's: a prefix,
the date and eight characters of Crockford's alphabet. The generator moves
out of the order code so that both use it, with a test of the shape it
makes and of how one is recognised under its own prefix.
Phase 1's last piece, which the owner decided is the shop plugin's to build
(design §11, decision 10).

A form on the cart page takes who to answer and a message, and posts to
cart/inquiry. The route stores the cart as an inquiry with its lines as a
snapshot, queues the job that owes its emails, and empties the cart, in one
batch; it answers with a redirect to the cart page, which shows the
inquiry's number to the browser whose cart it was. A part with no price can
be asked about, so a product page now offers a form for a size without one.

The write is conditional on the cart still having lines and on the visitor
being under five inquiries an hour, both asked inside the statement, so two
forms at once store one inquiry and a refused one leaves the cart alone.

The seller is told by email, answerable to the buyer, and reads inquiries in
a panel of the admin: to mark, export as CSV, or delete. The email
confirming to the buyer is built and off by default, because the form has no
challenge in front of it: Mallok has nowhere to draw one on a plugin's page.
The design gains the owner's decision and §18. The security notes say what
personal data an inquiry holds, what stands in place of a challenge, and what
that does not stop.
…ependent review found

The review found the guards inside the inquiry's write sound, and these
around them:

- A cart that changed between being read and being written lost the change:
  a part added in between was in neither the inquiry nor the cart. The write
  is now conditional on the cart being exactly what is sent, and a cart that
  changed is answered with cart_changed and left alone.
- An address could carry a mailto link's own syntax, which the admin links
  as stored. It is held to a list of what an address may be made of.
- A browser let through what the server refused, and the form came back
  empty: a name of spaces, a tab in a name, a message whose line breaks the
  field counted once. The name has a pattern, white space in a one-line
  field becomes one space, a line break is counted once.
- A semicolon let a formula through the CSV export where it is the list
  separator. Every piece that could begin a cell is made text.
- A request with no mark of its visitor was not limited. They are counted
  together, as Mallok's own rate limit counts them.
- A cart refused for the limit inside the write could be told it was sent.
- The export scanned every line for every inquiry.

And the tests of the two races proved less than they claimed: Promise.all
interleaves two requests about half the time here, and the other half never
reaches the guard. They now hold both requests at the write (holdWrites),
and each guard, broken, is red every time.
An email handed to Mallok keeps what a buyer typed in Mallok's own queue,
where deleting the inquiry does not reach it. The security notes, the
guidance file and the design said otherwise; they say what is true now, with
what an operator has to do about it. The design gains what the independent
review of the inquiry cart found and what changed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant