Skip to content

Company fixtures: small_co (Wrenfield Labs) and big_co (Ostrander Systems) - #16

Merged
scosman merged 15 commits into
mainfrom
claude/confident-mccarthy-jia798
Oct 15, 2026
Merged

scosman merged 15 commits into
mainfrom
claude/confident-mccarthy-jia798

Conversation

@scosman

@scosman scosman commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Adds two company fixtures in test mode, small_co and big_co, and the account table they need.

Summary

Fixture What it is Size
empty The schema plus the sandbox account, in live mode. Its rows are unchanged; only its description is updated. 0.6 MB
small_co Wrenfield Labs: a seed-stage SaaS company with 45 hand-written customers, from its 2025-08 beta to NOW. 1.8 MB
big_co Ostrander Systems: field-service software with 379 simulated HVAC, plumbing and electrical businesses, over 18 months, plus scripted loose ends. 23.7 MB

All three are frozen at 2026-09-01T14:00:00.000Z. Each company fixture is built by replaying its history through the world's own dispatch and the four engine functions billing_engine.md §7 provides for fixture generators. The build never writes SQL straight to the tables. python fixtures_src/generate.py rebuilds every fixture byte-for-byte.

The only world change: an account table

  • What it is: schema/006_account.sql is a one-row account table. It replaces the hardcoded account in startup.py, so a fixture carries its own account identity and mode.
  • The mode rule: a row with no mode is stamped with the mode it is first opened in. A stored mode that differs from an explicit livemode raises WorldBug.
  • Version and tests: the world version goes from 0.1.0 to 0.2.0. tests/test_account_table.py covers the table.
  • Nothing else changes: no other file under src/ is touched. Running git diff main -- src/ shows only schema/006_account.sql, startup.py and the version string in world.py.

Fixture generator (fixtures_src/)

  • replay/: the clock swap, the timeline, the dispatch client (one savepoint per call), the subscription and payout drivers, the finishing writes, and check_fixture. check_fixture checks the consistency invariants and every expectation, and each check has its own planted-violation test.
  • small_co/: the catalog, the 45-member cast and the story.
  • big_co/: the shape, the population draw, the simulation and the scripted loose ends.
  • guide.py checks the sql landmark= blocks in docs/fixtures.md against the committed fixtures. There are 27 landmarks, and none differ.

Deviations from the spec, decided along the way

Test plan

  • uv run ruff format --check && uv run ruff check
  • uv run ty check
  • uv run seahaven check
  • uv run pytest. The full suite passes, including:
    • the byte-identical rebuild of every fixture;
    • the expectations and loose ends for small_co and big_co;
    • the planted violations;
    • the fork cost;
    • the guide.
  • python -m fixtures_src.guide reports 27 landmarks and 0 differences.
  • CI is green on this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN


Generated by Claude Code

scosman and others added 15 commits October 13, 2026 18:17
Proposes two company fixtures to replace the deferred small/large:
Wrenfield Labs (small prosumer SaaS) and Ostrander Systems (B2B
vertical SaaS at scale).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN
…ld specs left as-is

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN
…se; mark complete

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN
…king unchanged

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN
…gh the world's code, declare gaps

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN
…workarounds

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN
…o charge count

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN
…and implementation plan

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN
…x account

- schema/006_account.sql: one-row STRICT account table (id, livemode, object, settings),
  seeded with the sandbox account and no mode; the first open stamps the creation-time mode.
- startup.open_account: validates the row, applies the mode rule (an explicit livemode that
  differs from a stored mode raises WorldBug), merges ledger/dunning settings.
- tests/golden/sandbox_account.json and tests/test_account_table.py; the empty fixture is
  rebuilt (its row is stamped live); version 0.1.0 -> 0.2.0; table count 24 -> 25 in docs.
- Deviations: the seed is written via json('<indented text>') (same stored bytes); the
  account object now comes back from the DB with sorted keys.

Committed without the code-review round at the user's request (demo branch).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN
fixtures_src/ becomes a package. replay/ holds the clock swap, the timeline, the dispatch
client (each call in a savepoint, as the HTTP handler does), the four engine wrappers,
the test-card catalogue, the people pools, the subscription and payout drivers, the
finishing writes (balance-status refresh, 30-day event trim) and check_fixture.
accounts.py defines WRENFIELD and OSTRANDER and install_account; generate.py wires
STARTUP and builds small_co and big_co lazily. docs/fixtures.md skeleton. 17 tests in
tests/replay/. Nothing under src/ changed.

Simplified: smaller name pools; retries follow only the latest invoice; trimmed tests.
Benchmark: 200 customers over 12 months builds in 4.9s at 9.1 MB; projected for
big_co's 3,000 customers that is far above the 25 MB budget (decision pending).

Committed without the code-review round at the user's request (demo branch).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN
fixtures/small_co: a 45-customer seed-stage SaaS in test mode, replayed through the
world's dispatcher from the 2025-08-25 beta to NOW (2026-09-01T14:00Z). 42 subscriptions
(29 active, 3 trialing, 1 past_due, 1 paused, 7 canceled, 1 incomplete_expired), 109
charges, 119 invoices, 2 refunds, 1 open dispute, 19 payouts, 145 events; 1.74 MB.
fixtures_src/small_co/ (catalog, cast, story, build), tests/test_small_co.py (expectations,
invariants, named characters, byte-identical rebuild), docs/fixtures.md small_co section.

Deviations: the dispute is opened with disputes.maybe_create_dispute (subscription
payments open none); setup fees are billed on their own invoice (pending items are
never swept); replay/checks.py skips the trial-end invoice in the overlap check; the
LAUNCH coupon's 3-month limit is not enforced; guide tooling and the web name check
skipped; payouts expected 15-40. test_empty_fixture/test_fixtures no longer assert that
empty is the only fixture.

Committed without the code-review round at the user's request (demo branch).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN
fixtures/big_co: the test-mode account of Ostrander Systems, a Denver field-service SaaS company,
replayed through the world's dispatcher and engine functions from the 2025-03-03 migration week to
NOW (2026-09-01T14:00Z). 379 customers (167 migrated), 378 subscriptions (all eight statuses),
2,917 invoices, 2,650 charges, 19 disputes (all six stages), 81 credit notes (SLA batch included),
378 payouts (one pending); 23.0 MB; builds in about 23 s and rebuilds byte-identical.

- fixtures_src/big_co/: shape.py (knobs, catalog, tax rates, one-off services), population.py
  (pure draw: migration cohort, organic and sales-led signups, exempt public buyers, CS notes,
  variant-name duplicates), simulate.py (catalog steps, Lives hooks: boundary draws, ramps,
  scheduled downgrades, concessions, SLA batch, invoice payment), loose_ends.py (the scripted
  loose ends of big_co.md §5), __init__.py (build, EXPECTATIONS).
- fixtures_src/guide.py: `python -m fixtures_src.guide [--write]` checks or rewrites the
  landmark rows in docs/fixtures.md; the big_co half of the guide has 13 checked landmarks.
- Tests: test_population.py, test_big_co.py (identity, live refused, expectations, every loose
  end, tools carry on), the big_co determinism case in test_fixtures.py, test_fork_cost.py,
  test_guide.py. conftest's api_* helpers take stripe_context/livemode keywords.
- Docs: docs/fixtures.md big_co section; AGENTS.md fixture line; README ready-made fixtures
  line; docs/technical.md fixtures section and layout.
- Web check: no real company named "Ostrander Systems" or "Ostrander FieldOps", and nothing at
  ostranderfieldops.com (searches found only Ostrander, Ohio and unrelated Ostrander-named
  businesses).
- Fork cost (30 opens): small_co median 7 ms, max 14 ms; big_co median 15 ms, max 80 ms (the
  checksum-verifying first open). Ceilings: 70 ms / 150 ms median, 1 s max.

Deviations the user decided:
- Population: the spec said about 3,000 customers; the build has about 380 (SCALE 0.115 on every
  count knob, rounded up; every rate as specified), because the Phase 2 benchmark put 3,000 at
  135-250 MB and the file must be 25 MB or less. Draws at 0.12 and 0.125 came to 25.9 and 25.1 MB.
- Disputes: the spec said a dispute card is swapped in for a renewal; the code opens every
  dispute on a one-off services payment (training day, data import, report build, app rollout)
  confirmed through POST /v1/payment_intents on a dispute test card, because the world opens a
  card's dispute only on that confirm path and the build calls no world function beyond dispatch
  and the four engine functions. Same rate per card-paid period, mix and course by age; the
  scripted queue, inquiries and review are made at the spec's August renewal instants, so their
  due dates are unchanged.
- Expectation ranges: the spec's §8 ranges scaled, except active subscriptions 190-260 (the
  specified rates leave under 60% of customers active, not 70%) and disputes 10-22 (the spec's
  110 at SCALE plus the nine scripted; 0.3% of a few thousand periods is single digits).

Other deviations:
- Ramps: the spec said the schedule creates the subscription; the code creates the send_invoice
  subscription, then a schedule from_subscription updated to the three phases, because the
  world's schedule routes declare no days_until_due.
- SLA credit notes: the spec said 10% of the invoice total; the code caps it at the invoice's
  remaining creditable amount, because the world refuses more (a balance applied to the invoice).
- Added: ordinary one-off services payments (3% per card-paid period) so disputed ones are not
  the account's only PaymentIntents; two scripted settled disputes (one won, one lost) so every
  stage is present at this population; scheduled downgrades picked by a 5% draw at an eligible
  last boundary, exactly two; concessions a 2% draw per boundary; schedule-governed
  subscriptions skip random changes; immediate downgrades wait for a good card.
- Tax-exempt buyers use invented place names (the pool's generic geography includes real places).
- Guide tooling (Phase 3's, skipped there) lands here; the small_co section is not converted to
  landmarks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN
… 4 items

Phases 1-3 were committed without code review. This commit does every item the triage of the
three reviews marked DO. Under src/, only startup.py changes, and it belongs to the account-table
change. All three fixtures are rebuilt: empty is still 0.6 MB, small_co is 1.8 MB and big_co is
23.7 MB.

The user's decisions
- World bugs are filed, not fixed. #12 to #15 are avoided or declared in a
  new "Known gaps" section of docs/fixtures.md.
  - #12, a renewal bills the period that just ended. The spec says no two cycle invoices
    overlap. The overlap check now allows one exception only: an identical span on a trial-end
    invoice and the cycle invoice right after it. Before, it left every trial-end invoice out of
    the comparison. The exception carries a comment linking the issue.
  - #13, repeating coupons never end and times_redeemed stays 0. Only once and forever coupons
    are used.
    - The spec has LAUNCH at 50% off for 3 months; it is now 50% off once, the first invoice.
    - The spec has SWITCH25 at 25% off for 3 months; it is now 25% off once.
    - The guide no longer claims redemption counts (the spec said LAUNCH 10, TEACHWELL25 5) or
      that discounts have ended.
  - #14, pending invoice items are never swept. Anika's setup fee is still billed on its own
    invoice (the BillPending beat, not in small_co.md's beat vocabulary). Elena's $49 item stays
    pending, and the guide no longer says her 09-10 renewal will pick it up.
  - #15, canceled_at is null on a cancel-at-period-end request. Declared in the guide.
- Disputes come only from one-off payments confirmed through POST /v1/payment_intents on a
  dispute test card. The spec (small_co.md row 45) has Grace buy Studio annual on the dispute
  card. Now she buys it with her own card, then pays $49 for "Done-for-you setup" at 15:25 on a
  dispute card added for that payment. That payment is disputed as fraudulent, evidence due
  2026-09-07T23:59:59Z. The story no longer calls disputes.maybe_create_dispute.
  OneOffPayment gains an optional card.
- The Wrenfield name stays, and the collision is accepted. Functional spec §3.2 asks for no real
  company. The web check found:
  - wrenfield.app is a live "Wrenfield" puzzle and word games site;
  - Wrenfield Finance, Wrenfield Capital and Wrenfield Media exist;
  - no "Wrenfield Labs".
  The user kept the name, domain, emails and account id.

Other deviations from the spec (all recorded in phase_plans/catchup.md)
- Accounts: architecture §2.3 has CompanyAccount(id, object, settings). It now holds only the
  company's fields, settings and bank suffix. install_account sets them over the seeded row's
  object, so the SQL seed is the only copy of the sandbox account; the hand-kept _SANDBOX is
  gone. It refuses a field the sandbox lacks, and refuses a row other than the seeded sandbox.
- "Nothing after NOW" (§3.4, replay.md §7):
  - It covers every timestamp-shaped column: canonical ISO text, and time-named Unix integers
    inside JSON. Before, it checked 8 column names.
  - The forward fields are listed in one _FORWARD set. Beyond the spec's list it adds
    billing_cycle_anchor, cancel_at, period ends (an invoice's, an invoice item's, a line's or a
    discount's), end_date, and every date inside a schedule's phases.
  - It reports offenders by id, and skips only the FTS virtual and shadow tables.
- Retries (replay.md §5): a scheduled retry is tied to its invoice. If that invoice is still open
  but no longer its subscription's latest, the build raises FixtureBuildError instead of
  silently dropping the remaining retries.
- n (replay.md §5) counts only boundaries that ran. The Hooks.before_boundary docstring says so.
- open_account raises a WorldBug naming the field when the object lacks
  settings.dashboard.display_name, country or default_currency. Before, it raised a bare
  KeyError.
- empty's description says what it holds: the schema and the sandbox account, in live mode.
- big_co:
  - TRADE_WORDS keeps only HVAC, plumbing, electrical, drain and sewer, and mechanical (§5).
    Roofing, pest control, garage doors and home services are gone.
  - _trade_coupon matches whole words, so "Prairie" is no longer "air".
  - CRM ids are 006 plus 12 digits everywhere; the scripted loose ends used 10.
- People pools grow to the spec's sizes: 210 first names, 200 last names, 151 real cities, and
  the state weights of replay.md §4.
- Corrections to components/small_co.md, recorded in the phase plan rather than the spec:
  - row 13: Anika's setup fee is billed on its own invoice;
  - row 34: Victor expires at 15:40, 23 hours after 16:40 the day before, not at 16:40;
  - row 45: Grace's dispute, as above;
  - §6: 5 products and 8 prices, not 3 and 6, because each setup item mints a one-off price and
    product;
  - §6: payouts 15 to 40, not 20 to 40, because a quiet Monday makes no payout.
- Dropped by the triage:
  - test_benchmark;
  - renaming the replay test files to architecture §3.3's names;
  - rewriting small_co.md's tables;
  - the three "Done-for-you setup" products;
  - big_co's _card_step/_swap duplication;
  - the exclusive refund and credit-note draws (big_co.md §4.1 makes them independent).

Guide
- docs/fixtures.md gives small_co 14 checked landmarks: counts, status counts, the cast, and
  one per loose end of small_co.md §7. Before, small_co had one unchecked query.
- Coupon text is corrected in both sections.
- `python -m fixtures_src.guide` reports 27 landmarks, 0 differences.

Tests
- One planted violation per invariant and guard, each required to trip only its own check. The
  two the schema enforces (net = amount - fee, refunded <= captured) are tested as refused by
  the schema.
- Tests for the narrowed overlap rule (including a trial-end span repeated by a renewal that is
  not the next one), forward fields, reporting by id, the test cards' behaviour, and a pi_ id
  carrying its instant.
- Driver tests:
  - no payout overdraws;
  - a draft finalized automatically carries no request;
  - the instant a trial pauses;
  - mark_unpaid;
  - a superseded retry stops the build;
  - an abandoned boundary uses no number.
- tests/replay/test_accounts.py: a company differs from the seeded sandbox only in its own
  fields.
- test_small_co.py:
  - opens the fixture with the row's own mode;
  - checks the account identity exactly;
  - adds test_opening_live_is_refused, test_new_ids_carry_wrenfield and test_tools_carry_on
    (a payout of available + 1 is refused);
  - has one test per named character of §4.4.
- A test requires small_co's landmarks.
- test_fixtures checks that the imported world holds exactly its three fixtures.
- Population tests: the market's trades, whole-word coupons, one CRM id format, the pool sizes.
- The account-table quick fixes are in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4ry4bFLC4jJqmCF5aowCN
@scosman
scosman merged commit 850221e into main Oct 15, 2026
1 check passed
@scosman
scosman deleted the claude/confident-mccarthy-jia798 branch October 15, 2026 22:09
scosman added a commit to scosman/OpenEnv that referenced this pull request Oct 17, 2026
Stripe World now ships three fixtures (Kiln-AI/stripe_world#16). Add a
Fixtures section listing empty, small_co (Wrenfield Labs, 45 customers)
and big_co (Ostrander Systems, about 380 customers), how to select one
with reset(fixture=...), and that a fixture keeps the mode it was saved
in. Scope the livemode note to resets without a fixture, link the
fixtures guide, and update the table count to 25.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uxf3k6Fcyfm5Jmt8d4HDfA
scosman added a commit to scosman/OpenEnv that referenced this pull request Oct 17, 2026
The main README isn't the right place for a Seahaven pointer, so restore
README.md to match main. The tutorial and its toctree entry stay.

Stripe World added an account table (Kiln-AI/stripe_world#16), so the
tutorial's Stripe World paragraph now says 25 tables instead of 24.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uxf3k6Fcyfm5Jmt8d4HDfA
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