Skip to content

Baby steps from 11 onwards (learner builds, my_11 …) - #6

Merged
Wania-Kazmi merged 431 commits into
stagingfrom
wania/dev-baby-steps-from-11
Oct 14, 2026
Merged

Wania-Kazmi merged 431 commits into
stagingfrom
wania/dev-baby-steps-from-11

Conversation

@Wania-Kazmi

@Wania-Kazmi Wania-Kazmi commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

This branch holds every learner step from 11 onwards. Each step lands on it in its own commits as it is built, and the branch is merged into staging when the human says so. Step 11 is the first; the sections below grow with each step.

Step 16 from decision 124 on, and steps 17 to 25, were built on 2026-10-11 and 2026-10-12 on the learner's standing instructions, "go with your recommended settings" and "choose your recommended options". Every decision from 123 on was taken with the recommended option, recorded with its alternatives in my_notes/decisions.md, and is open to reversal. Each step's database tier, from 16 to 25, now passes on Neon, each on a database of its own (decision 158, below). Decision 158's two main choices were confirmed by the learner on 2026-10-15.

Decision 158: on a database far away

Problem. The database tiers of steps 16 to 25 ran against Neon for the first time on 2026-10-14, 127 ms from this machine. Until then they had run on a PostgreSQL on the same machine. Steps 20 to 25 each failed one to three tests, every one a test of twenty or fifty requests at once. Fifty requests with one key made one payment, but the forty-nine others stopped waiting after two seconds, and 47 or 48 of their refusals then lost their place in the audit log. From step 22, fifty requests from one company raced for the end of its log, and 36 to 39 of fifty reads were refused after ten tries; in steps 24 and 25 a raw audit_pkey error came out of propose with no envelope. Both settings had been measured where a round trip takes a fraction of a millisecond.

Solution. From step 20, a request waits up to thirty seconds, not two, for the same request still being carried out; the wait ends as soon as the answer is there. A look that fails after the first request was seen running, or after its key was found taken, is OUTCOME_UNKNOWN, never "nothing was done". From step 22, each company's log takes one writer at a time: a PostgreSQL advisory lock, taken with the company in the turn's first statement, at READ COMMITTED, ended by PostgreSQL after ten seconds idle, behind a line in the program that waits without a connection and lets the next request go once a turn is taken. Steps 20 and 21 keep step 09's honest refusal of a lost place. Two hostile reviews of the fix found problems, all fixed and each broken on purpose to show its test catches it, except three written down: open question 130 (a busy agent can delay a person's brake past §44), 131 (a waiting retry whose refusal cannot be recorded), and how refusals arrive during an outage.

Behaviour. Every database tier from step 16 to 25 passes on Neon, none skipped: 39, 51, 51, 51, 52, 53, 60, 60, 62 and 75 tests. "Most of twenty, on this machine" became "all twenty, on any server". New tests pin the turn on a stub pool and on a real server, show another company is not kept waiting, and that a quiet turn ends. Unit tests: 735, 754, 840, 881, 923 and 1021. All twenty-two Break it exercises of steps 20 to 25 were measured again, twice; only step 20's Breaks 1, 3 and 4 moved, each explained.

The second review of steps 15 to 25

Problem. On 2026-10-12 the learner asked for a review of steps 15 to 25. Every step passed its own pnpm check in CI and its database tier on PostgreSQL, but a hostile review, one reviewer for each step, three more across the steps, a skeptic for every serious finding and a critic, found 119 problems that held: 5 high, 40 medium and 74 low. Twelve were the code doing the wrong thing. The worst: in step 20, a retry that decision 132 had added let one idempotency key make two payments when a release's reply was lost (measured, 9 runs of 12); and from step 22, no proposal move was written to the audit log, though DSOR-AUD-01 asks for it and step 08 had handed that job to step 22. Most of the rest were tests that still passed with their guarantee removed, and sentences that said more than the code.

Solution. The learner chose to fix everything, with four recommendations: build audit records for proposal moves; let an approver revoke a slip they signed; write down four start-up gaps that need an administrator's mistake, for step 26; and reword step 25's revocation sentence rather than add a second slip check (decision 145). Each finding was fixed in the step where it began and carried into every later folder, red first or broken on purpose (decisions 146 to 156). Decision 157 writes every proposal move with its own proposal_transition record in one statement; the database refuses a move without one; and a record that loses its place in its company's log tries the next place after a short random pause, up to ten. An independent review of decision 157 found the crowd test likely to fail on Neon, two overclaiming sentences and two untested guarantees, all fixed, and a revocation limit that step 29 must handle, written down. The specification's questions are open questions 124 to 129.

Behavior. Every step passes pnpm check, and its database tier on a local PostgreSQL 17 set up like Neon, with every demo running against it. All 65 README breaks were measured again on the final code, twice, and the passes agreed.

Step Tests Database tier
15 534 39
16 557 39
17 623 51
18 663 51
19 696 51
20 732 52
21 751 53
22 833 57
23 874 57
24 916 59
25 1014 72

On a PostgreSQL 17 on the same machine, twenty payments sent at once now all go through with every move recorded, where 3 of 20 did before. Each step's own section below gives its counts as it was built.

CI: one job per step folder

Problem. CI checked the baby steps in one job per system, one folder after another. From my_09 on, each my_ folder runs its test files one at a time, and each step takes longer than the one before, so the job grew with every step. With 40 to 45 folders it took from 35 minutes to more than 60, and from 41 folders it was cut off at 60 on Windows, so the newest steps went unchecked: Windows had never reached my_23, my_24 or my_25.

Solution. A first job lists every folder with a package.json. Every folder then gets a job of its own on Ubuntu and on Windows, at most 12 at once, because the organization's Free plan has 20 runners for all of its repositories. A job named "Baby steps" passes only when every one of them did. A new step is checked without editing ci.yml, up to 128 folders. A hostile review of the change, five reviewers with a skeptic for each finding, found what the folders would meet on Windows for the first time. The ten-stage ordering walk in my_22 to my_24 would run past its 30-second limit, so it now has two minutes of its own, with the proof unchanged (decision 144). And a fresh Windows machine once took 33 seconds over its first TypeScript run, so each Windows job now runs node on TypeScript once, timed, before the tests. The review also corrected the comment's numbers and my_01's README, which said CI does not run its tests.

Behavior. The first run passed: 95 jobs, 19.5 minutes from the first job to the last (run 38256321861). A folder took 15 seconds to 7.2 minutes on Ubuntu, and 51 seconds to 8.9 minutes on Windows, where my_25 ran for the first time. The ordering walk took 43.2, 33.8 and 33.6 seconds on Windows in my_22, my_23 and my_24: without its own limit all three would have failed. The warm-up took 0.1 to 2.7 seconds on all 45 Windows machines, so there was no stall this time. The test closest to its limit is my_21's start-up check, at 22.5 of 30 seconds on Windows. Not changed: every job still runs the repository's own install, and every push checks every folder; and every job checks its lockfiles against the npm registry again, about 180 checks a push where the old job made 12.

Step 25: revocation and the emergency brake

Problem. When the agent misbehaved at 3 a.m. there was no switch: user_123 could not tear up del_100, nothing stopped one agent or every agent at once, and asking the agent to stop is asking the thing that misbehaves to behave.

Solution. The brake is a record in DSoR's own store (migration 024; decision 142): control.suspend stops one agent in a company, or every agent in it, and releases it. A new stage, §21.4, refuses a stopped agent's commands AGENT_SUSPENDED and lets its reads through; the move to EXECUTING, and a prepared command's to READY, ask the brake again in the same statement. delegation.revoke marks a slip revoked with who and why, and in one statement cancels what waited under it for approval, releasing its reservations (migration 025). The review found a refusal recorded as ALLOW, the brake optional on the way into EXECUTING, and a prepared command not stopped, all fixed: who may revoke and who may release are decided at a new §21.9 stage and recorded (decision 143). The stage-order test became a proof by its parts, since walking all orderings of eleven stages took 65 seconds. Written down: a question for the specification about DSOR-SRC-02b and a revocation's target; the tenant administrator and the expiry half of DSOR-DEL-04c, not built.

Behavior. pnpm check: 876 passed. Database tier: 69 passed on a local PostgreSQL 17 set up like Neon. Three README breaks measured twice, agreeing (5, 1, 2 failing); decision 143's six broken once each, all as predicted. Stage 3 of the learning path is complete.

Step 24: limits with reservations

Problem. del_100 limited each payment to 50,000.00 and nothing else, so the agent could make any number of them in a day; and a daily limit is a running total with a race, where fifty payments at once each read the total and find room.

Solution. The slip gets a daily limit, 200,000.00 USD for del_100 (migrations 022, 023; decision 140). §21.10 books each payment against the slip's total for the UTC day in one INSERT ... ON CONFLICT DO UPDATE ... WHERE used + amount <= limit, writing a reservation keyed by the proposal from the same statement. The door settles it as the proposal ends: committed, released, or held while unknown or prepared, and only when the history holds the move. A dry run books nothing. The review found the settle running without the move, a race test that could not fail, and reserve() taking any currency, all fixed; and that the application's own code could still make room, written down with why: a payment is not tied to its reservation (decision 141).

Behavior. pnpm check: 806 passed. Database tier: 57 passed on a local PostgreSQL 17 set up like Neon, five runs in a row; fifty reservations at once book exactly forty, and with room for one, fifty payments through the door make at most one, while a read-then-write booking made three. Three README breaks measured twice, agreeing (1, 4, 4 failing).

Step 23: three ways to call

Problem. The agent could only find out whether a payment would be allowed by sending it, and every allowed one was made at once; nothing could be prepared now and released later.

Solution. A command is called execute, propose_only or validate_only, said beside its arguments as the key is, read once, its own field only (decision 138). A dry run skips the claim and the proposal, as §21 says, still asks the limit, is recorded as VALIDATED, and answers VALIDATED, ALLOW, with nothing changed. A prepared command walks the same checklist and stops at READY. The claim keeps the mode (migration 021), so the same key in another mode is IDEMPOTENCY_CONFLICT. The review found one request able to leave two READY proposals, and a mode on the request's prototype executing, both fixed (decision 139). A dry run skips the handlers' preconditions until step 32, written down and pinned by a test.

Behavior. pnpm check: 779 passed. Database tier: 55 passed on a local PostgreSQL 17 set up like Neon. Three README breaks measured twice, agreeing (8, 1, 6 failing); decision 139's three broken once each, all as predicted.

Step 22: proposals and their states

Problem. In the morning nobody could say what became of the agent's 2 a.m. request: the answer was spread across the audit log, an idempotency claim and a payment row, if there was one, and nothing kept a request's state from moving backwards.

Solution. Every command past its key's claim becomes a proposal at §21.8 (migration 020, decision 136): its payload as the hashed text, who asked, its key, and a history of moves, each with an actor and a cause; its state is its last move. The §26.2 map is a table and every move must be on it; the history is one line, a state entered once and left once, so a finished proposal cannot move and one cannot run twice, kept by the database and asked about at start-up. The door moves it: DENIED, READY and EXECUTING together, then COMMITTED, FAILED or OUTCOME_UNKNOWN; the receipt names the real proposal. The review found a proposal that could be left READY for good and a careless handler's failure believed, both fixed (decision 137).

Behavior. pnpm check: 753 passed. Database tier: 55 passed on a local PostgreSQL 17 set up like Neon, including twenty moves out of READY at once over a pool, one written. Three README breaks measured twice, agreeing (4, 1, 9 failing); decision 137's three broken once each, all as predicted.

Step 21: optimistic concurrency

Problem. The agent reads a payment, decides to cancel it, and meanwhile a person changes it. The agent's command ran anyway, on a record that was no longer the one it decided about, and nothing in the request said which version that was.

Solution. Invoices and payments carry a version, raised by one in the same UPDATE as every change (migration 019). Every command's contract declares its strategy: invoice.issue and payment.cancel are optimistic, payment.create is none, and the registry refuses any other (decision 133). An optimistic command must say expected_version, a whole number from 1 to 2147483647, asked at §21.6 so that a missing one is recorded as a DENY. The check is part of the change, ... AND version = $3, and a record that moved on is STALE_STATE, retry after_state_refresh, before any business rule, with no new version in the message. The review found a version too large for the column that made the door throw, a declared strategy nothing read, and tests the status alone could pass, all fixed (decision 134). Breaking the fixes on purpose found two more weak tests, both fixed (decision 135).

Behavior. pnpm check: 718 passed. Database tier: 53 passed on a local PostgreSQL 17 set up like Neon, including twenty cancels of one payment on one version over a pool, one made. Three README breaks measured twice, agreeing (2, 6, 1 failing).

Step 20: idempotency keys

Problem. A payment whose answer is lost is sent again by the agent, and DSoR made a second payment: nothing in a request said DSoR had seen it before.

Solution. Every command carries an idempotency key beside its arguments, never inside them, and DSoR claims it at §21.7 with one INSERT into dsor.idempotency_keys, under a primary key on the company, the caller who logged in, the operation and the key (decision 131). The same key and request get the recorded answer word for word, with nothing run and no second decision recorded; the same key with a different request is IDEMPOTENCY_CONFLICT; the same request still running is waited for. The review found three broken guarantees, all fixed (decision 132): only a refusal from the walk releases a claim, so a handler's "safe to send again" after a payment no longer reopens it; a restrictive policy keeps an answer once written, start-up asks for it, and a replay is checked against its caller; and the claim's messages say only what is known.

Behavior. pnpm check: 702 passed. Database tier: 52 passed on a local PostgreSQL 17 set up like Neon, including fifty requests with one key over a pool of twenty connections, one payment, run eight more times. Five README breaks measured twice, agreeing; decision 132's six broken once each, all as predicted.

Step 19: unattended mode and the role source

Problem. At 2 a.m. the agent's payment under del_100 was recorded as the agent acting for itself, direct, with no slip, and what user_123 holds came from a list that never changes while the program runs: when user_123 changes jobs, the agent keeps paying under their name.

Solution. An agent's command under a slip is unattended: the slip must allow that mode, the record's subject is the slip's signer, taken from the slip and never from the request, the agent is in the actor chain, and the slip is named on the record, inside the hash (decision 129). What the signer holds is asked of the company's directory, a fake one per company, at every decision; a directory that does not answer, or none at all, refuses with DEPENDENCY_TIMEOUT. The review found three broken guarantees, all fixed (decision 130): a future-dated or zone-less directory answer counted as fresh, so now an answer counts only with its zone and at most 24 hours old, and a stale or unusable one is FRESHNESS_UNSATISFIABLE; refusals after DSoR found the slip were recorded as the agent acting for itself, and are recorded under the slip now; and a column-level UPDATE on the log passed start-up. A slip allows unattended only when it says so, and none is signed by its own agent.

Behavior. pnpm check: 673 passed. Database tier: 51 passed on a local PostgreSQL 17 set up like Neon, seventeen migrations. Six README breaks measured twice, agreeing; decision 130's nine broken once each, all as predicted.

Step 18: delegations

Problem. The agent held power of its own: its role granted it the commands outright, nobody had signed for what it did, and when user_123 lost a permission the agent kept it.

Solution. The permission slip, dsor.delegations, in DSoR's own store under the same lock. Every command the agent sends runs under the one active slip DSoR finds for it; its power is computed at each decision as the slip's permissions cut down to what the signer holds right then and to the login's scopes, up to the slip's limit per payment (decision 127). The review found four ways past an expiry or a limit, all fixed (decision 128): an expiry the program cannot read is not in force, a slip or a signer that cannot be read is a refusal that is recorded, DSoR never chooses between two slips, and a helper function's owner is asked what the application is asked. A payment over the limit is now LIMIT_EXCEEDED, the specification's code.

Behavior. pnpm check: 645 passed. Database tier: 51 passed on a local PostgreSQL 17 set up like Neon. Eight README breaks measured twice, agreeing; decision 128's eleven broken once each, all as predicted.

Step 17: vendors and payments

Problem. No command said whether its effect could be undone, and there was nothing to undo: the running example's PAY-901 had nowhere to live.

Solution. Vendors and payments, under the same two locks as the invoices. payment.create makes a draft and is compensatable, undone by payment.cancel, which is atomic; every command's contract declares its semantics, the door writes them on every receipt from the contract, and the registry refuses an undo that is not a command the program has (decision 125). The review's eight findings were all fixed (decision 126): a payment's vendor is its invoice's by the database too, the payment numbers are nobody's to choose, a command that answers like a query is refused, a payment is in its invoice's currency, and the handler reads exactly what the payload hash describes.

Behavior. pnpm check: 608 passed. Database tier: 51 passed on a local PostgreSQL 17 set up like Neon. Ten README breaks measured twice, agreeing; decision 126's seven fixes broken once each, six as predicted and one that failed three tests instead of one because the break changed two things.

Step 16: the control-plane store

Problem. DSoR's log, its only paperwork in the database, was public.audit, filed beside the business's invoices in the schema the business's own tools treat as theirs. A cleanup of public would have taken DSoR's evidence with it.

Solution. A second schema, dsor, in the same database, and the log moved into it as it is: records, hash chain, grants and row-level security together (decision 122). The application may use dsor and create nothing there. Two hostile reviews followed (decisions 123 and 124), and start-up now also refuses an application that owns the database or either schema, or may create in either, by itself, one SET ROLE away, or through a helper function's owner.

Behavior. pnpm check: 548 passed. Database tier: 39 passed on a local PostgreSQL 17 set up like Neon; on Neon, dsor_step16 waits for the folder's .env. Nine README breaks measured twice, agreeing.

Step 15: freshness labels

my_15_freshness_labels. Every answer says how old its data is: the mode, when it was read, and from where (DSOR-FRS-01a), and the door refuses a current label stamped before the request began (DSOR-FRS-01b). The problem first, on step 14's demo, run on the local database so step 14's Neon database was not touched: the agent's INV-1008 says issued, with no time and no source, so an agent's hour-old memory and a fresh read look the same. Four decisions taken by the learner before any code (decision 120): the modes in lowercase, as the schemas spell them, against §27's capitals (open question 51); the code that reads writes the label, and the door insists on one; an old value labelled current is refused, not relabelled; no resource_version until step 21. Four pieces, each red first and committed alone: the label, in src/freshness.ts, with the two reads handing back their rows with it; the door insisting on a well-formed label; never an old current; and the demo printing the label under every read.

The hostile review, told this time to assume a careless handler as decision 119 says, found no way past the door and no wrong label on a real read, and three fixes the learner chose (decision 121): a query that answers with a receipt is refused, since a receipt carries no label; the label's time is taken just before the query, so data is at least as fresh as it says, and only an exact ISO time not in the future is accepted; and four guards got a test, with leaveTheDoor keeping only a label's three parts. Written down and not fixed: the door checks when a label was stamped, not whether the code that stamped it read the database, so the cache a later step adds must label its own answers; and a clock moved backwards refuses an honest read with retry never.

The step's own database, dsor_step15, was made on Neon with one CREATE DATABASE through the owner login in the copied .env. The settings refuse Claude any command that names .env, so the learner changed the database name in it.

531 tests, 39 in the database tier on Neon, and ten breaks measured twice with the runs agreeing. Learning-path stage 2 is complete.

Step 14: classification and masking

my_14_classification_and_masking. Every field has a label, the agent has a clearance, and a field above it is taken out of the answer before the answer leaves, and listed (DSOR-CLS-01, 02a, 02b, 03, 05). The problem first, on step 13's running demo: the supervisor's and the agent's reads of INV-1008 print the same line, 31400.00 USD, and the agent's line goes to a model provider. Three decisions taken by the learner before any code (decision 105): one file for all labels, with a field missing from it confidential; a field above the clearance left out and listed, not masked with a placeholder; agents only filtered, the agent cleared for internal. Four pieces, each red first and committed alone: the labels and the clearance; the filter at the door, for one invoice, a page and a command's receipt, with the answer labelled by the highest label among what is left; the record of a confidential read — a second audit record after the decision, with every address returned and the row count, written before the answer leaves and refusing the rows if it cannot be written; and the demo's two lines that are no longer the same.

What the build found (decision 106): the record of a read has to be a second record, because the decision is written before the handler runs and the log is never amended; and a column-level GRANT does not grow with the table — migration 006's first version added two columns and every INSERT into the log was refused.

The hostile review, six agents and three verifiers. Five reviewers found ten guarantees no test pinned, each now a test that fails under the review's own mutant: a page filtered row by row, a row with no address staying locked, the human's answer whole, the fourth label reaching the door, the ceiling measuring what leaves, the rebuilt receipt validated again, a page's cursor going with its addresses, a row with no address refused as the program's own error, a store down for the look-back too, and an empty page's label. The critic then found two things that changed the design (decision 107): a label describes a value it can see the whole of, so a value with parts inside is confidential whatever its field is called — a reviewer got the amount out inside a vendor that was an object — and an answer the door cannot filter is an envelope with a code and a retry class of never, where it had thrown a raw TypeError after the side effect. A receipt with no data, legal in the schema and what step 17's first PENDING_APPROVAL receipt will be, leaves as it came. The break measurement then caught the first of those fixes weakening the suite: with every object treated as unseeable, the table's own amount: confidential stopped mattering, so money is the one compound value the rule accepts and that break fails thirty-three tests again instead of two.

One finding is recorded rather than fixed (decision 108): five endings leave one ALLOW record and nothing else — an agent's answered read, a bad limit, an answer over step 13's ceiling, a row with no address, and the evidence store failing — so the log cannot tell a read that was answered from one refused after its decision. Measured against the rules nothing is missing, because DSOR-AUD-01 asks for command decisions, proposal transitions and DSOR-CLS-05 reads and all of those are written; "and then what happened" is a proposal's final outcome, two stages away; and the ending where the evidence store fails cannot be recorded by anything.

After the step, decision 109, the learner's choice: the record of a read kept its row count under extensions, because the build believed audit-record.schema.json had no field for it. It has one, row_count. Migration 007 adds the column with its column-level GRANT, and the count moved there, red first. The tests changed and did not grow. Broken on purpose: no count written fails 3, a read-back that drops it fails 7, and 007 without its GRANT fails 185 (187 once decisions 110 and 111 added two tests), because every INSERT names the column. Also corrected after the step: two citations of §4 that should say §6, a comment in boundary.ts that described a rule the filter now enforces, and the counts in docs/status.md and the step note.

After the step, decisions 110 and 111, both the learner's choice after a run showed the leak. Money, { value, currency }, kept its field's label wherever it appeared, so a handler that put the amount in vendor sent it to the agent labelled internal, with nothing in the list. It now keeps its field's label only in a field declared to hold money, today invoice.amount, as the specification's entity schema declares each field's type; anywhere else it is a value with parts inside, and at least confidential (decision 110). And labelOf walked the prototype chain: labelOf("invoice", "toString") returned a function, which is never above a clearance, so fields named toString and valueOf left for the agent with the amount in them. It asks for the table's own names now, the fix decision 36 made for roles, in a file written after it (decision 111). Each red first, in commits of its own, and each guard broken alone. Break 13 had been measuring another guard: the function it breaks was also the door's "is this a row?" check, so all 65 of its failures were the door refusing every row. Those are two functions now, and Break 13 fails 4, all about the label. A hostile review of the two found no new way past the filter through a row. It found two parts of an answer the filter never looks at, askedBy and a page's next — a gap from the step's first build — two guards no test pins, and sentences that said more than was measured. The sentences are corrected; the rest is decisions 112 to 114.

After the step, decisions 112 to 119, the learner's choices on what three more hostile reviews found. The door writes askedBy itself on every way out, and refuses a page whose next is not the address of its last row. Before, a handler that put the row in either sent the amount to the agent, with amount listed as withheld beside it (112). The money rule is a function a test can reach, and the door's row check is tested with money, as the agent too (113). The door copies a handler's answer once, and the check, the filter, the ceiling and the record of the read all work from that copy: a cursor that answered the check and the filter differently got the row out, and a record of a read named INV-1009 while INV-1008 was returned (116). A receipt is built from the parts its schema closes and is always checked; a row under requires or extensions had left beside a filtered data (117). Three guards the reviews found removable got a test (118). Written down and not fixed: two checks that fail open where nothing in this step reaches (114); an error's envelope, whose open parts take anything while its message is free text (115); and four ways past the copy that each need code written on purpose to trick the door, which could write the amount as text into a field anyway (119). The reviews had been told to assume the handler was trying to get the amount out, a stronger threat than this step is about, and decision 119 says so. Each fix red first, in commits of its own, with each guard broken alone.

512 tests, 39 in the database tier on Neon with migrations 006 and 007, and the fifteen breaks measured twice after decision 118, the two runs agreeing. The limits are in the README: no egress policy, no tokens, no budgets, humans unfiltered, error answers unfiltered and unlabelled, an unknown entity blank for an agent.

Step 13: bounded queries

my_13_bounded_queries. invoice.list, the first operation that returns many rows, with the size of the answer the server's (DSOR-QRY-01). Measured first on a copy of step 12 with a list written the obvious way and fifty thousand invoices seeded: { limit: 1,000,000 } returned all 50,002 rows, three megabytes, in a tenth of a second, and nothing said no. Three decisions taken by the learner before any code: the next page comes after the last invoice on this one, as a cursor; the cursor is an address, so the §21.6 scan refuses one from another company and step 12's suite tests the list with no edit; two maxima and a default in one file, with the handler's SQL carrying LIMIT and the door measuring every answer against both maxima and refusing an oversize one as the program's own error. Three pieces, each red first and committed alone. The test that matters is a connection that counts what every statement returned: a list that fetched the whole table and cut the page afterwards passes every other test.

A hostile review (four reviewers, a mutation pass of thirty-two one-line changes, a critic, three verifiers) found eight holes, each closed with a test that failed first: the counting connection was blind to an extra unordered read; error answers were exempt from the ceiling; the byte ceiling was proven with ASCII only, so a copy that counted characters passed; the list's own WHERE could be deleted with every test green because RLS filtered anyway; the walk trusted JavaScript's sort over the database's order; the demo pin carried a rule id it could not prove and a label that did not come from its request; NaN was refused as "null". One design suggestion declined and recorded (decision 104): the page is not cut by bytes; a row wider than the ceiling refuses, and the README says so. 468 tests, 36 in the database tier on Neon, eleven breaks measured twice with the runs agreeing.

Step 12: cross-tenant test suite

my_12_cross_tenant_test_suite. One generated test that calls every operation in the registry with another company's address: each contract carries an example request under extensions["com.panaversity.tutorial"], the suite moves every address in it to org_789 and asks six questions of every operation — refused with TENANT_MISMATCH; the same refusal, whole, for a real and a fake company and nothing about yours; the other company holds every number the example names, in the same state for a command; its rows untouched; exactly one record for the request in the caller's log, the DENY; the example works for its own company and carries nothing of the other. An operation without an example, or with an example that names no address, fails by name. Built explain → ask → build: the problem measured first (a careless third operation leaked org_789's invoice while step 11's hand-written suite stayed green), two decisions taken by the learner, three pieces red first.

A hostile review (four reviewers, a mutation pass, a critic) found the first version could be lied to — a refusal-shaped envelope carrying the row with a cosmetic DENY appended, a success answer carrying the other company's row, assertions deletable without a failure, a seed row that made a question hollow — and every finding is closed with a test: the six questions are plain functions and cross-tenant-suite-itself.test.ts feeds each the lie it exists to catch. 444 tests, 30 in the database tier on Neon, sixteen breaks measured twice with the runs agreeing. The map's "fresh Neon branch" is recorded as a divergence (decision 99).

Problem

Step 10 keeps the two companies apart with a WHERE tenant_id = $1 in every query. That is one lock, held by the program alone. Measured on step 10's own database as dsor_runtime, a query that forgot the company returned both companies' INV-1008 — and all 376 tests were green, because they only check the queries that exist today. The leak is the query somebody writes next year.

Solution

my_11_row_level_security, a learner build of step 11, copied from my_10_tenants and built explain → ask → build: the problem shown from the running program first, three decisions put to the learner one at a time before any code (both tables get the lock; the company is said before each statement, in its own transaction; the demo runs the forgotten query), then five pieces, each red first, committed on its own, and broken on purpose.

  • Migration 005 enables and forces row-level security on public.invoices and public.audit, with one policy each: a row is visible, and may be written, only when its company is the one the transaction said. A CHECK ties a record's chain to its company.
  • The stores say the company through the one handle they all use, theDatabase(tenant), which refuses an empty company. Two adapters run each statement inside its own transaction after set_config('dsor.tenant_id', $1, true), for PGlite and for a pg pool — where a per-connection setting would be met again by a stranger's statement. The stores' SQL is unchanged.
  • The start-up check asks what no privilege check answers: BYPASSRLS; membership of a role that holds it (Neon's neon_superuser, refused by name); ownership of a tenant table or membership of its owner; whether the lock is on at all, and is exactly the one policy the migration wrote; a SECURITY DEFINER helper whose owner skips the lock; and any relation beside the two tenant tables the account may read.
  • The test support runs the stores as dsor_runtime. PGlite's connection is a superuser, and a superuser skips every policy: with a store that forgets the company, the tests as the superuser fail 28 of 400 and none is a store's own test; as the application, 131. Sixteen tests that ran raw SQL as the application without a company went red when the lock arrived and now say it, or run as the owner.
  • The database tier (13 tests, a local PostgreSQL 17 with a database of the step's own) demonstrates the pool trap on a real pooled connection and then the adapter not doing it; the real login with no company gets no rows.

Rules: DSOR-TEN-01b, DSOR-RP-01a, DSOR-RP-01b, DSOR-RP-01c, DSOR-RP-01d, each with its honest scope in the README. 412 tests; runs from a clean copy outside the repository.

Behaviour

pnpm start prints the forgotten query run through the program's own connection: one row for org_456, no rows with no company said. Everything step 10 printed still prints.

Two measurements corrected the build. BYPASSRLS is a role attribute, and PostgreSQL never passes attributes through membership, so a Console-made Neon user is one SET ROLE neon_superuser away from skipping the lock rather than skipping it outright; the map's wording says "altogether", recorded as a divergence with the map untouched. And the audit policy binds a record's tenant, not its chain, which the migration's comment had claimed — a CHECK now says it.

The hostile review measured three windows past the lock that the first start-up check could not see — a second wide-open policy (policies are OR'd), a SECURITY DEFINER helper owned by a BYPASSRLS role, a view the owner made — each as both companies coming back while the check said the lock was on. All closed, each red first, each guard removed again to see its test fail. Thirteen breaks in the README, every count measured twice at 400 with both runs agreeing.

Tested again, on request. An independent evaluation — four reviewers, a mutation pass on a copy, a critic — graded the folder C and found: three more ways round the helper question (an aggregate, an INHERIT FALSE membership, a trigger on the invoices fired by the application's own UPDATE), the demo's two lines pinned by no test, the pool adapter invisible to pnpm test, two step 09 test files running the stores as the superuser, and singleFork — an option Vitest 4 does not have, ignored since step 09, so the test files had run in parallel all along and the configs were never typechecked. All fixed, each guard removed again to see its test fail; the configs are typechecked now. Two faults were injected for real (a COMMIT that dies on a real server; PGlite's one connection) and the cost of a transaction per statement measured. 412 tests, 16 in the database tier, seventeen breaks measured twice with the runs agreeing. Steps 09 and 10 carry the same singleFork lines and are reported here, not edited. Also fixed on this PR: step 08's demo test, which built its path in a way Windows could not open.

And Neon itself. A project, a database and dsor_runtime made with SQL; the five migrations, the sixteen database-tier tests twice, and the demo through the real login all pass on Neon. Measured there: Neon's owner and every Console- or API-made role hold BYPASSRLS directly and Neon forbids SET ROLE neon_superuser, so the map's "ignores row-level security altogether" is right for Neon, and the folder's earlier "one SET ROLE away" was PostgreSQL's rule, not Neon's — corrected. Over the network a statement with a company is four round trips (117 ms plain, 484 ms through the adapter), the measured price of "before each statement" that step 36's one transaction per request removes.

The four files pull request #4 changed in my_10 (comments only, the drift markers) are carried into my_11 so the new guard holds. docs/status.md and my_notes (decisions 94 to 96, lessons 36 to 38, the step note) are updated in the same change.

🤖 Generated with Claude Code

Wania-Kazmi and others added 29 commits October 11, 2026 16:14
…ps did not hold

The demo said step 20 stops the duplicate INV-1008 payment; it is a new
request with a new key, and in-flight exclusivity, §25.1, is what would
stop it. A pipeline test's comment put the claim after 6 and listed the
tenant and the delegation as missing. Migration 018 said a claim is
released "because nothing ran", which only the walk can know, and that
the grants keep an answer, which the second policy does. Start-up's
comment now says where rewriting is refused.

Comments only. Migration 018 is edited in place: no database that is
kept has applied it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README tells the step in plain words: every command carries an
idempotency key beside its arguments, DSoR claims it with one INSERT,
the same request sent twice gets the same answer and runs once, a
different request under the key is refused, and fifty at once make one
payment. Its five breaks were measured twice, in a copy, on the whole
suite, and the two runs agreed: 2, 6, 15, 13, 3. The database tier, 52
tests, passed on a local PostgreSQL 17 set up like Neon with eighteen
migrations, and the race passed eight more times on its own.

Decision 132 records the review's three broken guarantees and the rest,
what was done about each, and six breaks measured once each, all as
predicted.

docs/status.md lists my_20 at 702 tests and 132 decisions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The job runs pnpm check in every step folder, one after another, and
was cancelled at its 30-minute limit on the last two runs: on Ubuntu it
reached the middle of my_17, so steps 18 to 20 were never checked in CI.
No step had failed before the cut-off. About three minutes a step; 60
leaves room for ten more, after which the job wants splitting by track.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copied without node_modules, .env or a local database, so this step's
migrations can never run against step 20's Neon database. Only the
package's name and description change. Everything else is byte for byte
step 20's committed code, which passed 702 under pnpm check; the README
is rewritten with this step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments in src and test only. Migration 018 keeps its NEW IN STEP 20:
an applied migration is never edited. pnpm check: 702 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Migration 019 adds a version to invoices and payments: a whole number
from 1, never null, raised by one in the same UPDATE as every change.
The application may raise it and nothing else. A read says it, the
resource_version DSOR-FRS-01a names, and it is labelled internal, so the
agent that decides on a record can read the version it decided on.

The tests that pin a record's fields exactly gain the version.

Red first: no read said a version, and issuing left none to raise.
pnpm check: 705 passed. DSOR-CON-01b, DSOR-FRS-01a.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ded on, and a record that moved on is STALE_STATE

invoice.issue and payment.cancel declare the optimistic strategy;
payment.create, which makes a record and has nothing that could have
moved on, says none. An optimistic command needs expected_version, the
version the decision was made on, a whole number from 1, as an ordinary
argument inside the payload hash; without one, or with anything else,
it is VALIDATION_FAILED.

The check is part of the change: one UPDATE with the status and the
version in its WHERE, which raises the version. When it changes nothing,
DSoR looks, the version first: a record that moved on is STALE_STATE,
retry after_state_refresh, whatever its state now; on the same version,
the business rule says CONFLICT, as before. Two changes on one version
sent together: one is made, the other finds it stale.

Every existing call to the two commands, in the tests, the contracts'
example requests and the demo, says the version it decided on. Tests
that retry a changed record say the version they would have read again.
The demo reads INV-1009 before the agent issues it, and cancels the
mistaken payment on the version its receipt says, so a second run is
still CONFLICT and not STALE_STATE.

Red first: five tests. pnpm check: 710 passed. DSOR-CON-01a, DSOR-CON-01b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tic command, and within the column's range

An expected_version above 2147483647, PostgreSQL's largest INTEGER,
failed inside the statement: a raw driver error, a log saying ALLOWED
for something that never ran, and an idempotency claim left with no
answer. And the handlers asked for a version by hand, after §21.11 had
recorded ALLOW, so a missing one was refused VALIDATION_FAILED with
ALLOW on the record; and nothing read the contract's declared strategy.

Now §21.6 asks for expected_version, 1 to 2147483647, of every command
whose contract says optimistic, so the refusal is recorded as the DENY
it is; and the registry refuses a strategy no code carries out,
pessimistic and connector_managed.

Red first: 2**31 and 1e300 threw, the missing version was recorded
ALLOW, and the registry took pessimistic. DSOR-CON-01a, DSOR-CON-01b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n a real server, and a refusal that hands over no number

With the version taken out of the UPDATE, every step-21 test still
passed, because the status alone refused each case (measured on a
mutant). Now a draft at version 1 decided on as 7, and a draft an owner
reopened at version 3 decided on as 1, are both stale and untouched;
cancelling a payment raises its version, and a new payment starts at 1.
test/versions.db.test.ts races twenty cancels of one payment on one
version over a pool of twenty connections to a real PostgreSQL: one is
made, nineteen are stale (database tier, local PostgreSQL 17: 53 passed,
and the race three more times on its own).

And STALE_STATE no longer says the record's new version, so a caller
reads the record again instead of retrying on a number it was handed.

Red first: the message named version 2. pnpm check: 716 passed.
DSOR-CON-01b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…never asked, and comments say what is true

Start-up refuses an application that may insert a payment's version,
which is the table's to give, and one that may change an invoice's
vendor or amount, which it has never asked since step 10. Its messages
say the application changes a status and its version.

Migration 019 said the application may only raise the version; the
grant allows any value from 1, and only the code raises it by one, which
the comment now says, with what an owner's hand edit and a reset do.
freshness.ts and a step-15 test said no invoice has a version until
step 21; the row has one now, and the label does not repeat it, so
DSOR-FRS-01a is met in part. The new helpers sat between idFrom and its
doc comment; they sit above it.

Red first: start-up passed the four new grants. pnpm check: 716 passed.
DSOR-TEN-01a.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd the second told it is stale

Decision 133 said the demo shows a stale version; it did not. A section
after the keys': the agent makes a payment, user_123 cancels it on the
version it was made at, and the agent, deciding on that same version,
is refused STALE_STATE, retry after_state_refresh. The demo's tests pin
it, and the log's counts grow by its three decisions.

Red first: four demo tests. pnpm check: 717 passed. DSOR-CON-01b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e, and the refusal is checked word for word

Breaking the step on purpose found two gaps. A cancel with the version taken
out of its UPDATE passed every test, because the status alone refused each
case, the 20-way race included. And STALE_STATE could name the new version
in other words than "version 2". Now a draft payment decided on as version 7
is STALE_STATE and stays a draft, the message is compared whole, and the
race test says it proves one change, not the version. DSOR-CON-01b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tatus row

The README says what a version is, where a caller gets one, why STALE_STATE
holds no number, the demo's two decisions on one payment, the database tier,
three breaks measured twice on the final code (2, 6 and 1 failing, both runs
the same), and what the step leaves. The note records the build, the review
and what breaking it found. docs/status.md lists my_21 at 718 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copied without node_modules, .env or a local database, so this step's
migrations can never run against step 21's Neon database. Only the
package's name and description change. Everything else is byte for byte
step 21's committed code, which passed 718 under pnpm check; the README
is rewritten with this step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments in src and test only. Migration 019 keeps its NEW IN STEP 21:
an applied migration is never edited. pnpm check: 718 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…26.2 kept by the database

Migration 020 adds dsor.proposals, written once, and dsor.proposal_moves,
its history, where a proposal's state is its last move. The map of §26.2
is a table, and every move must be on it; because the machine has no
loops, a state is entered and left at most once, and a move out of a
state needs the move into it. So a move off the map, out of a state the
proposal is not in, or out of a finished state is refused by the
database, and two moves out of READY cannot both be written. The
application may add rows and nothing else; start-up knows eight tenant
tables and refuses an application that may change a proposal, a move or
the map. Nothing calls this yet. DSOR-APR-01a, DSOR-APR-01b,
DSOR-APR-01c, DSOR-IDM-04. pnpm check: 735 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the door moves it

A new stage, after the claim, writes the proposal: the payload as the
text §21.6 wrote down, its hash, who asked as the decision record says
it, and the key. §21.11 refuses a command with none. The door moves it:
DENIED when a later stage refuses, READY and EXECUTING just before the
handler, then COMMITTED on a receipt, FAILED on a refusal, and
OUTCOME_UNKNOWN when the handler throws, answers OUTCOME_UNKNOWN, or
answers with a row. A move before the handler that cannot be written
means nothing runs, and the claim is released. The receipt names the
real proposal; success() no longer counts its own, and refuses one from
another company. The orderings test walks all 3,628,800 orderings one at
a time. DSOR-APR-01a, DSOR-APR-01b, DSOR-IDM-04. pnpm check: 747 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…finished proposal refusing to move

After the two decisions on one payment, the demo lists this run's
proposals with their states, found by the run's keys because the store
keeps every run's; prints the stale cancel move by move, with who made
each move and why; and asks, with the application's own rights, to move
a COMMITTED proposal back to EXECUTING, which the database refuses. A
store read, proposalsOf, lists a company's proposals in number order.
DSOR-APR-01a, DSOR-APR-01c. pnpm check: 748 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…al server; and a state is left once

The database tier races twenty moves out of READY over a pool of twenty
connections: exactly one is written. Twenty moves out of a COMMITTED
proposal at once: none is. 55 passed on a local PostgreSQL 17 set up
like Neon. And a unit test for the rule nothing else reached: two
different moves out of one state, approved and rejected at once, of which
one is written; with "left once" taken out of migration 020, it fails.
DSOR-IDM-04, DSOR-APR-01a, DSOR-APR-01c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ost reply is read back

A review measured a proposal left READY for good when the move to
EXECUTING failed, and one left EXECUTING with its handler never run when
a reply was lost; each time the retry made a second proposal. Now both
moves are one INSERT, all or none, so a failure leaves PROPOSED or
EXECUTING. When the reply is lost, the door reads the proposal back:
EXECUTING is its own move, and the handler runs; PROPOSED is DENIED and
the claim released. And a test for the branch nothing tested: a door
that finds its proposal already finished never calls the handler.
DSOR-APR-01a, DSOR-IDM-04. pnpm check: 752 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…AILED only when it says nothing was done

A review measured a careless handler that made the payment and then
answered TRANSACTION_FAILED leaving its proposal FAILED, finished. The
door already distrusted that answer for the claim. Now FAILED only for
CONFLICT, STALE_STATE, RESOURCE_NOT_FOUND and VALIDATION_FAILED, the four
this program's handlers give; any other code is OUTCOME_UNKNOWN.
DSOR-UNK-01b, DSOR-APR-01a. pnpm check: 753 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…status row

The README says what a proposal is, how its state is its last move, the
two rules the database keeps, the demo's ten proposals and the stale
cancel move by move, the database tier, three breaks measured twice (4, 1
and 9 failing, both runs the same), and what the step leaves. The note
records the build, the review and what breaking it found. docs/status.md
lists my_22 at 753 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copied without node_modules, .env or a local database, so this step's
migrations can never run against step 22's Neon database. Only the
package's name and description change. Everything else is byte for byte
step 22's committed code, which passed 753 under pnpm check; the README
is rewritten with this step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments in src and test only. Migration 020 keeps its NEW IN STEP 22:
an applied migration is never edited. pnpm check: 753 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uments, and the claim keeps it

The door reads a mode beside the key, once, its own field only; §21.6
checks it: none is execute, anything but execute, propose_only or
validate_only is VALIDATION_FAILED and recorded, and a query has only
execute. Migration 021 keeps the mode on the claim, so the same key in
another mode is IDEMPOTENCY_CONFLICT, and start-up refuses an application
that may change it. Nothing behaves differently by mode yet.
DSOR-OPR-05, DSOR-IDM-01d. pnpm check: 761 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and nothing else

A dry run skips the claim at §21.7 and the proposal at §21.8, as §21
says, so it needs no key and leaves no claim and no proposal. The limit
is still asked and the decision still recorded, with the result
VALIDATED, so the log never shows a dry run as something allowed to run.
The door answers VALIDATED with the decision, ALLOW, and carries nothing
out: no change, no payment, no payment number taken. A refusal is the
one execute would get. The result envelope type now has room for READY
and VALIDATED, which name no data. DSOR-OPR-06, DSOR-OPR-05.
pnpm check: 766 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…EADY

A command called propose_only walks the same checklist, its key
claimed, its proposal made with mode propose_only, the limit asked, the
decision recorded, and then its proposal moves to READY, "waiting to be
released", and nothing runs. The answer is READY with the proposal, the
fingerprint and the semantics, and no data. A refusal is DENIED, as for
execute; a move to READY that cannot be written is read back as decision
137 does, and refused with the claim released. Releasing a prepared
proposal is proposal.execute, step 31's. DSOR-OPR-05. pnpm check: 771
passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y the third makes it

A dry run is VALIDATED, "the decision would be ALLOW; nothing was done";
a prepared one is READY, with its proposal waiting to be released; the
third is COMMITTED. The line under them counts the payments: none by the
first two, one by the third. The demo's log has three more decisions, the
dry run's among them, and the counts in main.test.ts say so. DSOR-OPR-05.
pnpm check: 772 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…back is OUTCOME_UNKNOWN, and the claim is kept

A review measured one propose_only request leaving two READY proposals:
the move to READY committed, its reply was lost, the read-back failed
too, and the caller was told "safe to send again" with the claim
released. Now, when the read-back fails, the answer is OUTCOME_UNKNOWN,
the claim keeps it, and the proposal waits for reconciliation; the same
before a handler, where a proposal EXECUTING that never ran sat beside a
second that did. Tests for a READY reply lost and read back, and both
unknown cases; and prepare, like the door, leaves a proposal that was not
PROPOSED alone. DSOR-IDM-01c, DSOR-IDM-04, DSOR-UNK-01b.
pnpm check: 775 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wania-Kazmi and others added 27 commits October 13, 2026 20:08
…at leaves (decision 157)

The problem. The READMEs of steps 22 to 25 described a log of decisions
and reads only, pasted demo output without the moves' records, and in
steps 24 and 25 said that requests at once in one company mostly lose
the race for its log and are refused: no longer true once a record that
loses its place tries the next one.

The change. Step 22: the new idea in plain words (each move written
with its record by one statement, the creation's record found through
its move, two requests at the end of the log), the problem it solves
with its measured numbers, the demo's move-by-move block with each
move's record and a printed-log excerpt, the What changed table, the
database tier's four racing tests and the migration edited in place,
Breaks 4 and 5, three more Check yourself questions, DSOR-AUD-01 among
the rules, and the limits it leaves. Step 23: the log excerpt of the
three calls, with the moves' records. Step 24: why the demo pays one
after another, Check yourself 5 and its answer (the limit refuses
forty-nine of fifty, measured), and the limit's last paragraph. Step 25:
each cancellation's record, the revocation's "still what was read",
the brake's two locks writing no record, the revocation's CONFLICT and
its snapshot moment, and the agent's traffic against the brake
(measured on PostgreSQL 17: the person's control.suspend landed on its
first try in twenty runs, with one and with five agents sending without
a pause). Test totals and the other Break-it counts are left for the
lead; they are listed in the report.

How it was proven. Every pasted line is from a demo run of this code.
Breaks 4 and 5 were measured on step 22's full suite in a copy outside
the repository, twice each, and the runs agreed: 16 failed | 811 passed
| 1 skipped (828), and 9 failed | 818 passed | 1 skipped (828). pnpm
guard passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and what was chosen

145 is the review and the learner's four choices; 146 to 156 are each step's findings, what was
fixed, what was rejected, what is written down, and how each fix was proven by breaking it; 157 is
the audit records of proposal moves, with the six questions it asked and the answers taken on the
learner's standing instruction. Open question 81 asks where a transition record should say what
moved, since the audit record has no field for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The my_ table gives the totals measured on the final code (829, 868, 910 and 1007), says that from
step 22 every proposal move is written with its own audit record (decision 157), that only a payment
books against the day (decision 155), and that a slip's signer, supervisor or approver, may revoke it
whether it is active or suspended (decision 156). The notes hold 157 decisions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments in steps 20 to 25, and step 20's README, sent a dead key to reconciliation at step 37.
The map makes step 37 outcome_unknown and step 38 reconciliation; decision 153 corrected the same
number in steps 22 to 25's other words. Words only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt measured again

After decisions 153 to 157, all fourteen breaks of steps 22 to 25 were run again on the final code,
on the whole suite, twice, in a copy outside the repository; the passes agreed, and each target was
found once. Each pasted Tests line is the measured one, and each break's words name the tests that
fail now. The totals: 829, 868, 910 and 1007; the database tiers, run on a local PostgreSQL 17 set up
like Neon: 57, 57, 59 and 72. The change tables of steps 22, 24 and 25 list the start-up test file,
and steps 24 and 25 the modes test file, which each changed. Step 24's two races measured before
decision 157 now say when they were measured. The step notes' headers give the same counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sion 157

On PostgreSQL 17, with step 24's booking broken on purpose to read the total and then write it,
fifty reservations at once still booked all fifty, three runs of three; but fifty payments through
the door made one, where on 2026-10-11 they made three. Since decision 157 the fifty take turns at
the company's log, and the turns keep the broken bookings apart, so only the store's test sees that
break now. Step 24's README and note say so, and the note no longer says that fifty payments mostly
lose the log's race. Step 24's change table lists the slip test that now asks the daily limit too.
Step 25's note says a suspended slip can be revoked since decision 156.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…de (decision 157)

A review of decision 157 found that "a request that loses ten places is refused, with nothing done"
is false for a proposal's last move: there the handler has run, the caller is told the payment
stands, and the proposal stays EXECUTING in its history and in the log, for reconciliation, which
step 22's own test pins. And "the log cannot be emptied with TRUNCATE" holds only for a TRUNCATE of
the log by itself: the log and the moves together, or CASCADE, empty both. Words only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…his machine (decision 157)

The problem. test/transitions.db.test.ts asked that at least 15 of twenty
payments sent at once be carried out, on every server. The READMEs tell
the learner to run the database tier on Neon, where every statement holds
its place at the end of the log for several round trips, and more requests
lose ten places in a row. Measured on PostgreSQL 17 with a pool of twenty,
on this machine: 20 of 20, every run. With up to 30 ms before and after
each statement, 12 to 15. With up to 10 ms on each round trip inside a
statement's transaction, 8 to 11; up to 100 ms, 4 to 8. Nothing was wrong,
and the test failed. A crowd also took 22 to 25 seconds at 100 ms a round
trip, and 43 to 57 at 200 ms, against the tier's 30-second limit.

The change, in each of the four folders:
- "most of twenty are carried out" is asked only when DSOR_DB_URL's host
  is 127.0.0.1, localhost or ::1 (it.skipIf), and its comment says why,
  with the numbers. Elsewhere the report shows it skipped.
- the test that runs on every server keeps every move with its record,
  every record with its move, the chain verifying, each proposal's records
  one request's, and now also asks that every refusal left its proposal
  DENIED or made none. It has two minutes instead of thirty seconds.

The proof. On my own PostgreSQL 17: the database tier passes in all four
folders (22: 57, 23: 57, 24: 59, 25: 72 passed), and with the host given
as "localhost." it reports 1 passed | 1 skipped for this file. Over a
simulated 30 ms link, unbroken: the every-server test passes, the bar
fails with 8 of 20. With the door's move to DENIED removed (a mutant): the
every-server test fails, "expected [ 'DENIED', undefined ] to include
'PROPOSED'". On this machine with no delay that mutant passes, because
nothing is refused there. pnpm check: 829, 868, 910 and 1007 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pared with their request's decision (decision 157)

The problem. Decision 157's Q5: a move's record carries the identity of
the request that made it, the same as that request's decision record,
its slip included, and never dsor's. Only the four moves of a payment
carried out were compared with their decision. A review wrote the record
of the move to DENIED in dsor's name (the door, src/operations.ts) and
the whole suite passed; the same done to step 23's prepare, the record
of READY, passed too.

The change, in test/transitions.test.ts of each folder: a helper finds
the one decision in the log and the record the move names, and compares
the request, the login, the slip and the identity. A person's authority
is as of each record's own time, so that one field is the move's own; an
agent's is the directory's, the same in both. Two tests for DENIED: the
agent under del_100, refused LIMIT_EXCEEDED (unattended, subject
user_123, actor chain the agent), and user_123, whose move to READY and
EXECUTING the server refused (direct). In steps 23 to 25, two more for
prepare's READY: the agent's payment and user_123's invoice.issue, each
called propose_only.

The proof. Red first was not possible: the code is right. Broken on
purpose, the whole suite each time, then put back:
- DENIED in dsor's name ({ ...by, subject: "dsor" } in the door):
  22: 2 failed | 829 passed (831); 23: 2 failed | 870 passed (872);
  24: 2 failed | 912 passed (914); 25: 2 failed | 1009 passed (1011).
  The two DENIED tests, and nothing else.
- DENIED without its slip ({ ...by, underSlip: undefined }), step 22:
  1 failed | 830 passed (831), the agent's.
- prepare's READY in dsor's name: 23: 2 failed | 870 passed (872);
  24: 2 failed | 912 passed (914); 25: 2 failed | 1009 passed (1011).
  The two READY tests, and nothing else.
pnpm check afterwards: 831, 872, 914 and 1011 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a revocation, tested (decision 157)

The problem. When the statement that revokes a slip commits and its reply
is lost, recordTogether finds the first record of a cancel written and
hands back no rows, and revokeSlip answers "revoked", with what it read as
waiting (src/delegation.ts, `if (rows === undefined || row?.revoked ===
1)`). No test reached that half of the condition. A review changed it to
`if (row?.revoked === 1)` and the whole suite passed, while the revocation
that had committed answered AUTHORIZATION_DENIED ("only the person who
signed del_100 may revoke it here, and user_123 did not"), its own
proposal was recorded FAILED, and 40,000.00 USD stayed held against the
day.

The change, in test/revoke.test.ts: a test with one proposal waiting for
approval and one approved under del_100, 40,000.00 USD reserved for the
approved one. The store runs the statement that revokes and then throws
ECONNRESET, as a lost reply. The test expects the true answer, revoked
with 2 cancelled; the slip revoked; both proposals CANCELLED; nothing
held against the day; and the revocation's own proposal COMMITTED.

The proof. Red first was not possible: the code is right. With the
review's mutant, the whole suite: 1 failed | 1011 passed (1012), the new
test, at the answer. Measured on a copy with the mutant: the answer
AUTHORIZATION_DENIED, the proposal FAILED, usedToday 40000.00, the slip
revoked and both proposals CANCELLED. Put back: pnpm check, 1012 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The problem. Two comments in src/audit.ts said more than the code does.
- recordTogether's said it returns "the records". In step 25 a record can
  wait on a condition (recordInsertIf): a brake that stops an agent's
  moves, or a revocation that revokes nothing, leaves a statement that
  succeeds and writes none of the records it was handed. The records
  returned were sealed, not written.
- isItMine's said it asks "on the connection that just failed". Through
  a pool, each statement takes whichever connection is free, and one
  that is gone is dropped (database.ts), so the look-up runs on another,
  a new one if it must. Only PGlite's one connection is the same.

The change. Step 25's recordTogether comment says the records are as
they were sealed, and that only the rows, `moved` or `revoked`, say
whether they were written; steps 22 to 24 write no record on a
condition, and their comment stays. isItMine's comment, in all four
folders, says what the store does, and keeps why a failed look-up is
OUTCOME_UNKNOWN. Comments only; no code changed.

The proof. pnpm check: 831, 872, 914 and 1012 passed. oxfmt --check
passes on the four files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… every receipt (decision 157)

A hostile review of 2f85d2c found three things in test/transitions.db.test.ts.

The problem.
- "Every refusal leaves its proposal DENIED or made none" asks a little
  more than the door promises: it gives DENIED two tries of ten places,
  and a proposal it could not deny stays PROPOSED (decision 153). The
  comment said nothing of that.
- A refusal whose request matched no record passed as "made none", and
  nothing showed the matching worked.
- "Up to 100 ms on each round trip" did not say the pause came before and
  after each one, and nobody had measured Neon.

The change, in each of the four folders: the comment says what the door
promises and that no run measured has come to it; every receipt's request
must be found among the proposals, so a refusal found by none really made
none; the latency comments say "before and after each round trip", and
that Neon itself was not measured. The assertion stays DENIED or none, as
the task asked; accepting PROPOSED would let a door that never writes
DENIED pass.

The proof. My own PostgreSQL 17: the database tier, 57, 57, 59 and 72
passed. On a copy, with up to 30 ms and 100 ms before and after each round
trip: the every-server test passed, in 7.9 and 25.9 seconds. The unit tier
does not run this file, and pnpm check's typecheck does: it passed in all
four folders, with 833, 874, 916 and 1014 tests passed once the two
commits after this one were in place too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…th its request's decision too (decision 157)

A hostile review of dcc3c06 found a third place that writes a move with
the request's words: the door's catch around a handler that throws, which
moves the proposal to OUTCOME_UNKNOWN. Its record in dsor's name left
every test green. Measured, step 22's whole suite with that mutant:
831 passed (831). The review also found the helpers trusting which record
a move names: a move that named its decision's record would have the
decision compared with itself.

The change, in test/transitions.test.ts of each folder: two tests, the
agent's payment under del_100 and user_123's invoice.issue, each through a
door whose handler throws, comparing the record of OUTCOME_UNKNOWN with
the request's decision. The helper that finds a move's record now asks
that it is a proposal_transition into that state, and the comparison
asks for the same operation and payload hash too.

The proof. With the review's mutant ({ ...by, subject: "dsor" } in the
door's catch), the whole suite: 22: 2 failed | 831 passed (833);
23: 2 failed | 872 passed (874); 24: 2 failed | 914 passed (916);
25: 2 failed | 1012 passed (1014). The two new tests, and nothing else.
Put back: pnpm check 833, 874, 916 and 1014 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…157)

A hostile review of 0257c3c found two wordings in test/revoke.test.ts that
said something else. "With that test deleted" meant half of an `if` in
revokeSlip, and read like a test case. "The log says it was done" sat over
an assertion that reads the proposal's state, not the log.

The change: "with that half of its condition deleted", and "COMMITTED, not
FAILED: it was done". Comments only.

The proof. pnpm check in step 25: 1014 passed. oxfmt --check passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the build

The crowd test asks "most of twenty" only of a server on this machine now, so step 22's README says
why: over a link with delays added on purpose, as Neon's would add them, 8 to 18 of twenty got
through. Step 23's change table lists the transitions test that compares a prepared READY's record
with its decision. Decision 157 says its race was measured on this machine, that a last move which
loses its places leaves the proposal EXECUTING after the payment stands, and writes down what the
review found and left for later: the revocation's parameter limit for step 29, a move that can name
another company's record, step 24's broken booking now hidden through the door, and two comments in
older steps. The status page gives the totals after the review's tests: 833, 874, 916 and 1014.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…view of decision 157

The review's tests moved every total: 833, 874, 916 and 1014. All fourteen breaks of steps 22 to
25 were run again on the final code, twice, in a copy outside the repository, and the passes agreed.
Three breaks now fail more tests, and say which: step 22's Break 3 fails forty, three of them the
records of a refused or unknown move; step 23's Break 3 nineteen, two of them a prepared READY's
record; and step 25's Break 3 eight, one of them the revocation whose reply was lost. The step notes
give the same counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch added six open questions numbered from 76 while staging, through pull request #7,
numbered its own from 76 to 123. Ids are never reused, so this branch's six follow staging's: 124 to
129, and the seven references to them in the decisions log follow too. No question's words change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… again

Staging gained pull request #7, the mj_ track's steps through 30, and pull request #6 could no
longer be merged into it, so GitHub ran no CI for its last push. One file conflicted:
research/open-questions.md, where both branches had added questions after 75. Staging's 76 to 123
come first, and this branch's, renumbered in the commit before this one, follow as 124 to 129.
docs/status.md merged by itself: staging's mj_ rows, and this branch's my_ rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…for its twin, and a company's log takes one writer at a time

The database tiers of steps 16 to 25 ran against Neon for the first time, 127 ms from this
machine. Steps 20 to 25 failed their tests of twenty and fifty requests at once: two settings had
been measured on a PostgreSQL on this machine. Decision 158 records the problem, the two changes,
what two hostile reviews found and what was chosen, and notes on decisions 131 and 157, whose
numbers it changes. Open questions 130 (a person's brake waits behind the agent's turns, beside
DSOR-OPS-01b and DSOR-DEL-04b) and 131 (a waiting retry whose own refusal cannot be recorded, §28)
are left for the specification.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing carried out (decision 158)

On Neon, 127 ms away, fifty requests with one key made one payment, but the forty-nine others
stopped waiting after two seconds, and 47 or 48 of their refusals then lost their place in the log.
A request now waits up to thirty seconds, and the wait ends as soon as the answer is there. A look
that fails after the first request was seen running, or after its key was found taken, is told
OUTCOME_UNKNOWN, never that nothing was done.

Tests: the thirty seconds shown by moving the clock at the first look; a failed second look and a
failed first one; the file of the key waits two seconds, so no test leaves a request running into
the next; the database test of fifty gets three minutes. Breaks 1, 3 and 4 measured again: more
tests notice them now, and the README says why.

DSOR-IDM-01b, DSOR-IDM-01c, DSOR-UNK-01b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n 158)

The wait for the same request still being carried out, the answers to a look that fails, their
tests, and the database test's three minutes, as step 20 has them. Break lines measured again on
the new total; none moved.

DSOR-IDM-01c, DSOR-UNK-01b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Neon, 127 ms away, fifty reads sent at once by one company lost their place at the end of the
log so often that 36 to 39 were refused after ten tries. Now a writer waits for its company's turn,
reads the end, writes after it and commits. The turn is an advisory lock taken with the company in
the turn's first statement, at READ COMMITTED, and ended by PostgreSQL after ten seconds idle;
inside the program each log has a line that waits without a connection and lets the next request go
once a turn is taken. The ten places stay, for a writer that takes no turns. A statement is built
before its records are said to be sent.

Tests: the pool's turn pinned on a stub pool (READ COMMITTED, ROLLBACK, the line); on a real server,
a writer waits for the turn and reads the end only then, another company is not kept waiting, and a
quiet turn ends; "most of twenty, on this machine" becomes "all twenty, on any server"; two race
tests race again through a database that takes no turns. Carried from step 20: the wait.

DSOR-AUD-01, DSOR-TEN-02a, DSOR-IDM-01c, DSOR-UNK-01b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ard (decision 158)

As steps 20 and 22 have them: the company's turn at the log, the program's line, the isolation
level and the idle limit, their tests, and the wait for the same request. Break lines measured
again on the new total; none moved.

DSOR-AUD-01, DSOR-TEN-02a, DSOR-IDM-01c, DSOR-UNK-01b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ard (decision 158)

As steps 20 and 22 have them, and the limits' tests of fifty at once get three minutes for a
database far away. A request that books and cannot write its
decision is said to have failed to write it, not to have lost ten places. Break lines measured
again on the new total; none moved.

DSOR-AUD-01, DSOR-TEN-02a, DSOR-DEL-06a, DSOR-IDM-01c, DSOR-UNK-01b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ard (decision 158)

As steps 20, 22 and 24 have them. The agent's traffic can delay a person's brake now, not make it
lose its place: the README says so, beside DSOR-OPS-01b and DSOR-DEL-04b (open question 130), and
the revocation's comment says the turns close the moment a move into waiting could slip past it.
Break lines measured again on the new total; none moved.

DSOR-AUD-01, DSOR-TEN-02a, DSOR-OPS-01b, DSOR-IDM-01c, DSOR-UNK-01b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n 158

The my_ track's counts for steps 20 to 25, a word on each of the two changes in the rows of steps 20
and 22, a note on the first run against Neon, 158 decisions, and the date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Asked in plain words with the trade-offs: a company's log takes one writer at a time, and a repeat
of a request waits up to thirty seconds for the first. Both were chosen, and both are what is built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Wania-Kazmi
Wania-Kazmi marked this pull request as ready for review October 14, 2026 22:43
@Wania-Kazmi
Wania-Kazmi merged commit c9daad6 into staging Oct 14, 2026
125 checks passed
mjunaidca added a commit that referenced this pull request Oct 16, 2026
… 158

Staging gained pull request #6, whose open questions 124 to 131 took the numbers step 31 had
used. Step 31's three questions become 132 to 134, and its README, the status row, and the
notes now cite those numbers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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