The tree carries every migration the database ever applied
04:03 pm AWST · 7158512
The drift check was broken in the way that matters most: silently. It
numbered files by the database's apply order, but sixteen migrations
from the mid-August build were applied without ever being filed, so the
positions disagreed and a --write renumbered two hundred files out from
under every comment that cites them.
Reconciled, both directions:
- The sixteen unfiled migrations are backfilled beside the work each
belongs with (0137b..0204b, the letter convention 0177b already used),
content taken verbatim from the database's own record. That includes
the first variant of a check applied twice, and the probe-targets
function whose create was never filed even though its drop was.
- The one applied-but-unrecorded change is now recorded: the
probe-targets drop ran ad hoc, so its file content was re-applied as a
proper migration row (drop if exists — a no-op against the schema, a
real row in the history).
- The check itself now matches BY SLUG, not position: the tree's
numbering is its own logical order, the database's timestamps are the
replay order of record, and neither is rewritten to flatter the other.
Content compares after normalising what the storage never kept —
statement separators, comment preambles an older apply path stripped,
string-literal line-wrapping — so real SQL drift still fails.
- Eleven build-era files whose rows hold older in-place-edited variants
are named in a KNOWN_VARIANCES list with the reason, verified against
the live schema rather than waved through. Nothing joins that list
without the same verification.
241 applied, 241 filed, check green. Every future unfiled migration
fails CI the day it happens instead of years later.
The yard learns its branches, its live position, and its floor plan
03:52 pm AWST · 7e03b08
Four things Taj asked for after opening the Lexus group account, and the
two data faults underneath them.
The faults first. Every yard panel read live_snapshot, which refreshes
twice a day and does not carry dealer_name — blind on day one of a
watch, blind to branches forever. And the statement engine read the raw
days_old field, which Lexus's platform never publishes; the
advertised_days() helper has known the createdAt shape since 0104, so a
yard with 40 dateable cars reported zero dated stock. 0214 gives the
yard a live engine (dealer_live_position_scoped, scoped like the
statement by sources AND dealer_names) and teaches the statement to ask
the helper; 0215 rounds the live reads so every past-90 comparison is
between whole days and the page agrees with itself.
On top of that:
- /yard grows the All/branch toggle, live header stats (including total
advertised value), a live standing panel, a branches-side-by-side
table, and a branch-scoped makes list. Single dealers see no change
beyond the floor plan panel.
- The floor plan: a dealer's own bailment rate and cost basis
(dealer_floorplan_pref, scope-keyed so groups are one facility),
multiplied against observed asks — assumptions printed inside the
headline, undated stock a named row, POA stock named as carrying
nothing. Supersedes StandingMoney's "never multiplied by a floorplan
rate" only where the dealer supplies the rate themselves.
- Back from the statement is now always "Your yard", branch toggles
replace history instead of filling it, month links keep the branch,
and the branch survives the trip between /yard and the statement.
- ShelfCurve no longer draws a flat 0% line for a yard with no dated
stock; a group's past-month PDF button no longer points at a 404.
Adversarially reviewed (12 agents, 7 confirmed findings, all fixed):
the boundary disagreement, the undated floor-plan gap, step attributes
that blocked 8.25%, drill-in links that erased history, and the copy
that credited POA cars with asks they never published.
A temporary password opens nothing until it is replaced
12:58 pm AWST · 7255362
An account issued a temporary password carries must_change_password in
app_metadata — which only the service role can write, so the holder
cannot wave away their own obligation. Until it clears, the yard is out
of reach.
Enforced in two places, because one is not enough. proxy.ts redirects
every /yard page load to /set-password so the person is walked into
choosing a password. But a Next server action is a globally addressable
POST that never passes through the proxy, so the obligation also lives
at the authorisation layer: currentMembership() fails closed while the
flag is set, and every dealer-scoped read and action already routes
through it. Path-matching is the convenience; the membership gate is the
boundary.
The password itself still goes browser->auth-server directly and never
touches us. The browser stamps user_metadata.pw_rotated in the same call
that sets it; the finish route lifts the obligation only when that stamp
is present, and consumes it in the same write so a later re-issued
temporary password demands a fresh rotation rather than trusting an old
one. A forced user whose finish failed once can resubmit the same new
password and still complete; typing the temporary password back is
refused.
Verified by two rounds of adversarial review: no bypass via off-path
server actions, no redirect loop, no strand for a cooperative user.
The issuer learns to preview a group, without issuing one
11:59 am AWST · db07126
A dryRun with a groupId renders the whole group or one branch through
the same document every dealer gets, and returns the bytes. It refuses
to write an archive row: group issuance is a separate concern this
route does not yet own, so the preview path stays a preview.
A group holds its branches, and the statement toggles between them
11:55 am AWST · 3e4401b
Lexus of Perth is one account with two rooftops — Osborne Park and
Melville (Myaree) — sharing a single crawl source but reported as
distinct dealers. The statement engine learns to be scoped by a set of
sources AND a set of dealer_names at once, so both group shapes fit:
one site with many branch names, or many sites under one banner.
- 0213 adds dealer_group / dealer_group_branch, a group_id on
membership, and dealer_monthly_statement_scoped(source_ids[],
dealer_names[], month). The single-source function becomes a thin
wrapper over it, so every existing dealer is untouched (westside
still reads 540). dealer_group_statement resolves a branch to its
scope, or the whole group when no branch is named.
- Auth resolves a group membership to its branches; the statement page
and PDF grow an All / per-branch toggle that re-scopes both the
numbers and the document. Verified: group 40, Osborne 21, Melville 19.
The rotation feeds freshness and discovery evenly, and the lapse keeps pace
09:16 am AWST · 4044420
Root-cause pass on why health has emailed red every ~5 hours since Aug 25.
Every pg_cron job is 100% green (2,216 runs, 5 days) and CI is green; the
only failure is integrity check 17, and its residue decomposed into four
causes:
1. The daily lapse raced the continuous 14-day check — marketplace cars
cross the line all day and sat stale until 12:35. lapse-unwatchable now
runs six-hourly (schedule change, 2-second function).
2. Detail-rotation starvation: never-seen URLs went ahead of every stale
re-read, so a source whose sitemap enumeration outruns its inventory
starves its own freshness — pattersoncheney lists 3,780 URLs for 591
cars, the ~3,200 non-inventory pages parse to nothing, stay "never
seen" forever, and claimed the whole budget twice a day while its
stalest prices went 15 days unread. tonywhitegroup (8,700 cars) hit
the same wall by size. The rotation now interleaves one unseen with
one stale, halving the budget between discovery and freshness whatever
the mix.
3. Probe-cycle latency: yesterday's sighting-stamps fix is working (847 ->
685 in a day) and drains the remainder as queues cycle — vonbibra's
pages verified parsing fine, its stale set is queue position, not rot.
4. A redundant probe-budget change was written and reverted: store.ts
already scales the check budget at candidates/4 up to 400 (the
carsguide lesson, learned before this session knew it).