Skip to content

Harden the approval machine - #2

Merged
mcheemaa merged 7 commits into
mainfrom
hardening/approval-machine
Aug 11, 2026
Merged

mcheemaa merged 7 commits into
mainfrom
hardening/approval-machine

Conversation

@mcheemaa

Copy link
Copy Markdown
Owner

The approval machine shipped in #1 with a public triage of Codex's review; this PR lands the queued stack, plus one bug found live on production. Six commits, most severe first:

  • Money races cannot double-spend. The daily order budget is reserved atomically (advisory lock around count+insert), blank or junk cap env values mean the defaults instead of a zero-dollar ceiling, an approval whose code email fails voids itself while still counting toward rate caps, and cancel reopens the cart only when the void actually happened.
  • The cart seals the moment its approval is spent. Every mutating op refuses sealed carts, a refresh in flight cannot stomp the seal, shrinking a line demands the menu id before removing (a grocery line without one used to vanish), and pickup approvals now check pickup availability.
  • Approval routes demand the card's own capability. Sequential ids stop being another gate holder's lever: verify, cancel, and status require an HMAC capability from the tool output. The wake retries three times and the card says plainly when nobody answered.
  • Cart cards adopt the durable truth. Cards hydrate from a snapshot GET on mount so reloads and older copies converge; a sealed cart renders sealed; a timed-out edit cancels its run instead of mutating later; a submit retry refreshes pending outcomes against live status.
  • Hidden is not empty. DD_HIDE_PERSONAL renders as "the owner keeps that private," never as an empty account. Missing stories added (CartToolCard, ZoomableImage, the private face).
  • Widget stage directions are not addressed to us. dd-cli's address-picker widget instructions leaked into a card as data on production; search results now translate widget-speak into the honest fact and the agent knows it has no widgets.

Not in this PR, with reasoning on #1: cart-state readback into the next agent turn (both money steps re-read the live cart, so staleness cannot reach a charge) and address selection among saved addresses (its own research and PR).

Gates green per commit: lint, typecheck, 296 tests, build.

Codex hardening, first slice. The daily order budget is reserved
atomically: count and insert serialize under an advisory lock, so
several verified approvals racing to submit cannot spend the last slot
twice. Blank or junk cap variables now mean the defaults instead of a
zero-dollar ceiling (Number of an empty string is 0, the template trap).
An approval whose code email fails to send voids itself while still
counting toward the rate caps, since excluding voided rows would let
re-requests reset the meter. And cancel reopens the cart only when the
void actually happened; a consumed approval answers 409.
Codex hardening, second slice. Submission now seals the cart right
after consuming the approval, and every mutating op (add, quantity,
remove, tip, delete, fulfillment flip) refuses sealed carts with a
plain answer, so nothing can drift between the final revalidation and
the charge. A snapshot refresh in flight during that window can no
longer stomp the seal back to open. Shrinking a line now demands the
menu id BEFORE removing, so a grocery line without one decrements
safely instead of vanishing. And pickup approvals check pickup
availability the way delivery always did; the preview schema carried
the flag, normalization just dropped it.
Codex hardening, third slice. Approval ids are sequential, and on a
shared-gate instance that made them another guest's lever: five wrong
guesses could lock a stranger's approval, cancel could void it. The
request tool now returns an HMAC capability bound to the approval id;
verify, cancel, and status all demand it before touching the row. The
inbox code remains the only key that moves money. The wake is durable
too: the bell rings up to three times, the card says plainly when
nobody answered, and the agent knows a manual submit is safe because
consumption refuses anything unverified.
Codex hardening, fourth slice. Transcripts freeze tool output forever,
but the cart keeps living: cards now hydrate from a snapshot GET on
mount, so reloads and older copies of the same cart converge on what
is actually true, and a sealed cart renders sealed, controls frozen,
header saying why. The ops route cancels its run when the deadline
fires, so a timed-out edit can no longer mutate the cart after the
card was told it failed. And a submit retry that lands on an
already-used approval refreshes pending or unknown outcomes against
live order status instead of repeating stale news.
Codex hardening, fifth slice. With DD_HIDE_PERSONAL on, the browse
card used to render the owner's addresses as zero saved and their
history as zero orders, presenting privacy as an empty account; the
private answer now says what it is. CartToolCard, ZoomableImage, and
the private face all gained the stories the constitution requires,
and AGENTS.md records that data backfills legitimately ride
pnpm db:generate --custom, which the review misread as handwritten
migration SQL.
Found live on production: dd-cli is built for a renderer with widgets,
and when its backend wants an address choice, the widget's stage
directions arrive as message text. Our card repeated them as data and
the agent pointed the user at a picker that only exists in DoorDash's
own app. Search results now translate widget-speak into the honest
fact (saved addresses are the only delivery targets), keyed on the
structural needs_address flag as well as the text, and the agent knows
it has no widgets and that only the owner can save a new address, in
the DoorDash app itself.
@vercel

vercel Bot commented Aug 11, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
rendi Ready Ready Preview Aug 11, 2026 6:07pm

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b3a9bdaf71

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/rendi/doordash-ops.ts
}): Promise<CartOpResult> {
const snapshot = await getCartSnapshot(input.cartUuid);
if (!snapshot) throw new Error("unknown cart");
assertUnsealed(snapshot.status);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make the seal atomic with cart mutations

When an edit has already read an open status at this check, submission can change the row to placing while the edit continues toward its DoorDash mutation. If submission revalidates the cart before that mutation but calls orderSubmit afterward, the approved hash passes and the altered cart can be charged. Preserving placing during snapshot refresh only protects the local row, so the mutation and submission seal need serialization or versioned conflict detection.

Useful? React with 👍 / 👎.

Comment thread trigger/tools/doordash-submit.ts Outdated
Comment on lines 121 to 123
await setCartStatus(approval.cartUuid, "placing");
const snapshot = await getCartSnapshot(approval.cartUuid);
if (!snapshot) return { refused: "the cart is gone" };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Recover the cart when pre-submit validation throws

If cartShow, orderPreview, or the database reservation throws after this status update but before an order row is created, the approval is already consumed and the cart remains placing. A retry reaches the alreadyConsumed branch with no ddOrders row and returns a refusal, while every cart mutation rejects the lingering placing state, permanently stranding the cart even though orderSubmit was never called. Pre-submit failures need to record a recoverable outcome and transition the cart out of the sealed state.

Useful? React with 👍 / 👎.

Comment thread components/doordash/cart-card.tsx Outdated
Comment on lines +85 to +93
setCart((current) => ({
...current,
...(durable.items ? { items: durable.items } : {}),
...(durable.quote !== undefined ? { quote: durable.quote } : {}),
...(durable.tipCents !== undefined
? { tipCents: durable.tipCents }
: {}),
...(durable.fulfillment ? { fulfillment: durable.fulfillment } : {}),
}));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve hydrated state when an edit fails

After this effect adopts a newer durable snapshot, any subsequent edit rejection reaches the existing catch path and calls setCart(data), where data is the frozen transcript prop. A card hydrated to current items, pricing, or tip therefore snaps back to stale generated values after a failed or timed-out edit even though durable state did not change. Retain the last durable pre-edit snapshot or rehydrate on failure instead.

AGENTS.md reference: AGENTS.md:L15-L18

Useful? React with 👍 / 👎.

Comment on lines +73 to 74
await runs.cancel(handle.id).catch(() => {});
return Response.json({ error: "edit timed out" }, { status: 504 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not report failure when cancellation fails

When the Trigger cancellation request rejects, this catch discards the only evidence that cancellation did not happen and still returns a definitive 504. The worker may remain queued or running and apply the edit after the card has rolled back and told the user it failed, leaving the displayed cart inconsistent with durable state. Propagate an uncertain result or keep polling and verify the final snapshot before reporting failure.

AGENTS.md reference: AGENTS.md:L15-L18

Useful? React with 👍 / 👎.

Comment on lines 99 to +100
const approvalId = output?.approvalId;
const token = output?.token ?? "";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve approval cards created before deployment

Persisted tool outputs created before this commit contain an approvalId but no token, so this fallback sends an empty capability to the newly mandatory status, verify, and cancel routes. The status client maps the resulting 403 to voided, which prevents still-live pre-deployment codes from being entered or cancelled and makes historical verified or consumed cards claim they are no longer live. Add versioned handling or an authenticated capability-upgrade path for legacy outputs.

AGENTS.md reference: AGENTS.md:L15-L18

Useful? React with 👍 / 👎.

Comment on lines +51 to +58
for (let attempt = 0; attempt < 3 && !woke; attempt++) {
try {
await sendSessionText(
row.conversationId,
`[order approved, approval ${approvalId}] The owner entered the code. Place the order with doordash-submit.`,
);
woke = true;
} catch {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reuse one wake identity across retries

If a session send commits but its response is lost, the call throws and this loop invokes sendSessionText again. That wrapper creates a fresh crypto.randomUUID() for every invocation, so the retries are distinct durable messages rather than idempotent attempts and can wake multiple agent turns for one verification, producing duplicate submission outcomes and transcript entries even though approval consumption prevents a second charge. Reuse a single message identity across all three attempts.

Useful? React with 👍 / 👎.

Codex round two, the declared final pass. Pre-flight failures after
consumption (the CLI, the database) no longer strand a sealed cart
with a spent approval and no order row; the cart reopens and the tool
says plainly that the approval is spent. Right before the charge, the
seal stamp is checked one last time, so a mutation that slipped past
the seal while pre-flight CLI calls were in the air aborts the order
instead of charging a cart nobody approved; the residual window is
the submit call itself, which nothing outside DoorDash can close. A
failed edit now rolls the card back to the freshest settled truth
instead of the frozen transcript numbers, a timed-out edit looks once
more after cancelling in case the run finished anyway, and wake
retries reuse one message id so a lost response cannot wake the agent
twice.
@mcheemaa

Copy link
Copy Markdown
Owner Author

Round-two triage, five of six landed in 414845c:

  • Pre-submit failures stranding the cart (P1): real and introduced by the seal itself. The pre-flight (snapshot, tip check, revalidation, reservation) now runs under a recovery boundary: any throw reopens the cart and reports the approval as spent, so no retry ever meets a sealed cart with no order row.
  • Seal not atomic with mutations (P1): accepted in substance. Full serialization would mean holding locks across multi-second CLI calls, so the fix is a version check instead: every cart write bumps updatedAt, the seal's own write stamps it, and submission re-reads the stamp immediately before the charge, aborting (nothing charged, cart reopened) if anything moved. The residual window is the order submit call itself, which no caller outside DoorDash can make atomic; that residual is documented in code.
  • Failed edits snapping back to transcript numbers (P2): fixed with a last-settled-truth rollback, regression story included.
  • 504 despite a run that may still land (P2): after cancelling, the route looks once more and returns success if the run completed anyway.
  • Wake retries minting duplicate messages (P2): retries now reuse one message id end to end.

Rejected: legacy approval cards. Pre-deployment tool outputs carry no capability token, so their cards will read as no-longer-live. By inspection there are zero live pre-deployment approvals (codes die in fifteen minutes; every historical row is expired, voided, or consumed), and for dead approvals the legacy face is truthful. Versioned capability upgrades would be complexity in the money path for an empty set.

Per the repo's review-loop convention this was the declared final round; anything further that is not money-path lands in a follow-up.

@mcheemaa
mcheemaa merged commit c53f444 into main Aug 11, 2026
7 checks passed

This branch was successfully deployed

1 active deployment
Preview — 414845c9 Deployed Aug 11, 2026 by vercel[bot]
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