Skip to content

feat(contract): give every op and every app-raised code a home in the registry - #18

Merged
frahlg merged 1 commit into
mainfrom
claude/admiring-pike-1cd1cc
Aug 9, 2026
Merged

feat(contract): give every op and every app-raised code a home in the registry#18
frahlg merged 1 commit into
mainfrom
claude/admiring-pike-1cd1cc

Conversation

@frahlg

@frahlg frahlg commented Aug 9, 2026

Copy link
Copy Markdown
Member

Contract-pair: srcfl/ftw@agent/registry-ops-client-errors

Pairs with srcfl/ftw#868 — the two copies of contract/registry.yaml are one file and must merge together.

What

An ops block in the registry. site.mode.set and battery.hold crossed the wire as hand-written strings on both sides — OP_SET_MODE and OP_BATTERY_HOLD here, OpSetMode beside the box's dispatcher — and the scope each op demands was written twice more: in this app's simulator (OP_SCOPES in src/lib/sim/box.ts) and in the box's defaultOps(). Nothing compared any of the four. The block now names each op and the scope its grant must carry.

tests/registry-contract.test.ts reads it all back: the OP_ constants must be the registry's ops exactly, both directions; every op's scope must be a declared scope; and the simulator's authorisation table must demand the registry's scope for every op. The box's real table is checked on its own side, so the two tables can now disagree with each other only by first disagreeing with the registry — which CI compares byte for byte.

battery.hold stays declared-but-unimplemented on the box: an app may say it, the box answers E_UNKNOWN_OP, and a box test pins that rejection.

Homes in client_errors. The block's header says every app-raised code has a home there. Three did not: E_NO_ACK (session.ts's ack deadline), E_NO_ANSWER and E_BAD_BODY (box-api.ts, plus one E_BAD_BODY site in access.svelte.ts). They follow the path E_RESPONSE_TOO_LARGE took — an entry with a retry answer, and a row in the RETRYABLE table, which the existing set-equality test now pins in both directions.

The retry answers: E_NO_ACK is retryable because no ack means the box never took the intent, so asking again cannot act twice. E_NO_ANSWER is retryable because the wire went away and the session reconnects on its own. E_BAD_BODY is not — the box and the app disagree about a route, and asking again gets the same answer.

Tests

  • New command operations describe block; each check was verified red under mutation before landing green.
  • npm run verify passes (types, 728 tests, production build).
  • check-contract-drift.mjs against the box branch: 12110 bytes, byte for byte.

🤖 Generated with Claude Code

… registry

The registry gains an ops block naming what a cmd frame may carry —
site.mode.set and battery.hold — and the scope each demands. Both sides
hand-wrote that pair and nothing compared them. The OP_ constants and the
simulator's authorisation table are now read back against the block.

client_errors takes in E_NO_ACK, E_NO_ANSWER and E_BAD_BODY, the three
app-raised codes that sat outside the file whose header claims every such
code has a home there. They join the RETRYABLE table, so the existing
set-equality check now pins their retry answers too.

Contract-pair: srcfl/ftw@agent/registry-ops-client-errors

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
ftw-webapp 64edf1d Aug 09 2026, 02:52 PM

@frahlg
frahlg merged commit 7c86877 into main Aug 9, 2026
3 checks passed

@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: 64edf1db63

ℹ️ 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".

// went away — the session reconnects on its own and the same ask can go
// again. A bad body is the box and the app disagreeing about a route, and
// asking again gets the same answer.
E_NO_ACK: true,

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 assume a missing acknowledgement means no execution

When a command reaches the box but its acknowledgement is lost or delayed beyond the five-second deadline, the box may already have acted even though the client raises E_NO_ACK. Session.command() generates a new UUID for every invocation and discards the original pending entry on timeout, so a retry does not reuse the box's idempotency key and may submit the intent twice. Preserve and resend the original command ID before classifying this as retryable, or avoid presenting an ordinary retry.

Useful? React with 👍 / 👎.

Comment on lines +258 to +262
expect(entries.length, 'no ops were found in the simulator to check').toBe(declared.size)
for (const [, constant, scope] of entries) {
const wire = ops[constant!]
expect(wire, `the simulator's ${constant} is not a constant in messages.ts`).toBeDefined()
expect(scope, `the simulator demands ${scope} for ${wire}`).toBe(declared.get(wire!))

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 Compare simulator operation names as a set

This count plus per-entry check does not prove that every registry operation occurs in OP_SCOPES: if the simulator accidentally contains [OP_SET_MODE] twice and omits [OP_BATTERY_HOLD], the count remains two and both loop iterations validate the same correct mapping. The resulting runtime object lacks the omitted authorization entry, yet this contract test passes; resolve each constant to its wire name and compare the complete wire-name/scope map while rejecting duplicates.

Useful? React with 👍 / 👎.

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