docs(cli): state the measured platform-id width at four citation sites - #16414
Conversation
All four sites cited driver-sql's `case 'lookup': case 'user':` arm BY NAME for "a platform id is 26 characters", two of them spelling out a ULID literal. #15522 corrected that origin sentence, so the citation chain pointed at text that no longer exists -- the arm now carries an explicit "do not restore the number this sentence used to carry" guard naming the same four copies. Measured on this branch by driving the real SqlDriver against SQLite rather than reading its constants: - the driver mints a 16-character nanoid when the caller supplies no id (12 samples, exactly one distinct width; the alphabet carries `-` and lowercase, so a Crockford-base32 ULID check over them returns false); - a SUPPLIED id is stored verbatim at whatever width the caller chose -- 10, 17, 18, 26, 40 and 200 all landed and read back unaltered, with a non-vacuity control: a query for an id never written returned 0 rows while the table held 6, so the filter really filters; - width is driver-owned, not a platform constant: driver-sql, mongodb and turso each spell DEFAULT_ID_LENGTH = 16, while driver-memory mints objectName-timestamp-counter, a variable-width shape. So the replacement does not swap 26 for 16. An id's width is not a fixed number at all, and each site now says which half it states -- what the driver MINTS versus what a supplied id is stored at -- because naming a new fixed number would only re-arm the same trap. Every site's conclusion is unchanged: a reference column takes `table.string(...)` / VARCHAR(255) rather than `table.uuid`, because Postgres refuses a platform id in a `uuid` column with 22P02 -- it refuses a 16-character nanoid exactly as it refused a ULID. Comment and failure-message text only: no behaviour, no asserted value and no minted width changes. The two CHANGELOG copies stay untouched as historical records. A fifth live-source site carrying the same sentence in its strongest "minted" form, in packages/platform-objects, is filed as #16410 rather than edited here -- a different package, a different verification surface. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
Commit f933d1e ("docs(cli): state the measured platform-id width at four citation sites") left the aligned doc-comment block in `generate.ts` reading: * Postgres refuses either in a `uuid` column with `22P02`. The * width half * is the same rule for the whole REFERENCE_VALUE_TYPES class: "width half" was stranded on a line of its own -- an artifact of where that commit's replacement text ended, not something the surrounding block ever said. Rewrapped to close the orphan; the sentence is unchanged word for word. Presentation only, and no line is being bought: nothing is added here, the same words occupy one line fewer, and the paragraph's content is identical. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 70910382e5a2d40b15f19b021afcb5f65b11e619 && git checkout 70910382e5a2d40b15f19b021afcb5f65b11e619
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin fd75728bc65156b9b5e6361d54011bd72c835932 e58829a25638f72ad1513ece36c53c48b8ef2d32 && git checkout -B drift-repro fd75728bc65156b9b5e6361d54011bd72c835932 && git merge --no-ff e58829a25638f72ad1513ece36c53c48b8ef2d32
node scripts/docs-audit/affected-docs.mjs --json fd75728bc65156b9b5e6361d54011bd72c835932
|
…of copying it `automation-runs-query-validation.test.ts` asserted "forwards every declared ExecutionStatus member" over a list of eight members typed into the test body. It was identical to the enum the day it was written and short by one the day `refused` was appended (#14945) -- so the row stayed green while covering eight of nine, under a name that promises every member. A pin that copies the vocabulary it claims to cover stops measuring that claim the moment the vocabulary moves, and says nothing when it does. The rows now come from `ExecutionStatus.options`, which is the discipline both neighbours already apply: the boundary hands `parseEnumParam` that same array (`domains/automation.ts`), and `automation-api.zod.test.ts` turned its own inline copy into the same read under #7359. Iterating `.options` only reads it -- nothing here sorts or re-seats it, because `execution.zod.ts` reserves those positions for readers that index them ("Appended last so every reader that indexes `.options` keeps its positions"). Two prose citations of the same set were counting it rather than naming it: - `query-param.ts` said `ListRunsRequestSchema` "bounds it to the eight `ExecutionStatus` members"; - `domains/automation.ts` said the schema declares "`z.enum([...8 ExecutionStatus members])`". Both now say what the set is defined BY -- the enum itself, never a copy of its members -- rather than how many are in it. Deliberately NOT "nine": a fresh literal re-arms the identical trap and reads authoritative the whole time, which is the correction #16414 landed hours ago and the reason it refused to mint a replacement number of its own. Population, swept rather than assumed: every tracked non-dist file was scanned for windows carrying four or more of the nine members, and separately for count words next to `ExecutionStatus`. The matcher's positive control is this file's own pre-change text, which it reports at eight of nine members with `refused` missing. Outside the three sites above it finds no live stale copy: `execution.test.ts`'s eight-member list is the deliberate PREFIX pin for the appended-last invariant, `AUTOMATION_RESULT_STATUSES` is a different, five member union held exhaustive by a type-level `Eq`, plugin-approvals' `TERMINAL_RUN_STATUSES` is a documented narrower subset, and the generated reference docs carry all nine. One stale copy does survive, in `docs/qa/platform-checklist/areas/automation.json` -- filed separately rather than edited here: a JSON checklist has no enum to read, so its repair is an authoring decision and a different gate family. Test-only plus comment text. No behaviour, no asserted value and no minted count changes; the CHANGELOG copies stay untouched as historical records. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
Round 2 on #15890, after review. Comment prose only — no executable line moves, the pin and its ablation are untouched and settled. 1. HISTORY. The previous commit on this branch wrote, of `ListRunsRequestSchema`, in `domains/automation.ts`: "has always declared it — as `ExecutionStatus.optional()`, the enum itself rather than a copy of its members" and in `query-param.ts`: "bounds it to `ExecutionStatus` itself — the enum, never a copy of its members" Both are FALSE of history, and — the whole lesson — the sentence the first one replaced was accurate. `ListRunsRequestSchema` declared `status` as an inline `z.enum(['pending', 'running', 'paused', 'completed', 'failed', 'cancelled', 'timed_out', 'retrying']).optional()` from the schema's introduction (`5e685a7d6f7`, 2026-02-21) until #7359 replaced that copy with `ExecutionStatus.optional()` — landed as `cf7c69421a7` (2026-08-11), in the same commit that made this boundary read the parameter. So "always" and "never" are both wrong: it was a copy of eight members for most of its life. Verified by `git show 5e685a7:packages/spec/src/api/automation-api.zod.ts`, `git show cf7c694^:` on the same path, and `git log -S` on that literal — which names exactly two commits, the introduction and #7359. Repairing the stale COUNT had introduced a falsehood about HISTORY, in a card whose entire subject is untrue claims about this vocabulary. Both sites now say what the line declares today AND when it started saying it. No count of the LIVE enum is minted; "eight" is a property of a deleted literal, frozen by `cf7c69421a7`, and it is what makes the sentence explain itself. 2. `TERMINAL_RUN_STATUSES`. The previous commit called it "a documented narrower subset" Measured in `plugin-approvals/src/approval-service.ts`, it is `{completed, failed, cancelled, timed_out}` — a set its own header calls "A CLOSED set, deliberately", whose dead-run sweep treats every other answer, an unknown status included, as ALIVE so an unrecognised state can never cost someone a live approval. That is a fail-safe default, not a narrowing anyone documented relative to `ExecutionStatus`. It was closed before `refused` existed, and `refused` — "Terminal, never resumed" — is not in it, so once lane 2 (#15788) makes a run reach that status, its still-pending approval reads as alive to that sweep. A reader of this vocabulary that has not learned the new member, owned by the `refused` lanes, not by this card. Corrected in the PR body; no tree text carried the wrong wording. 3. "#16414 landed hours ago" (previous commit and PR body). #16414 landed `0ea5f9d9f79` at 2026-09-06T21:22:02Z; the commit that said "hours" was authored 22:50:48Z the same day — 89 minutes by those two stamps, 66 by the review's anchor. Two anchors, two numbers, and the elapsed time was never load-bearing: the citation of #16414 stays, the time reference is dropped rather than replaced by a third number. Also corrected in the PR body, not in the tree: "No `dist` is on the resolved path" was true of the two ablation legs and false of the suite. Measured on a fresh worktree with nothing built — `pnpm --filter @objectstack/runtime exec vitest run src/domains/automation-runs-query-validation.test.ts` cannot collect the file at all: "Failed to resolve entry for package @objectstack/observability". `http-dispatcher.ts`, which the suite imports, imports that package; `packages/runtime/vitest.config.ts` has no alias entry for it; its `exports` map serves `./dist/index.js`. The two legs stay source-resolved and `ablation-dist-preflight` still does not apply to them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
Fixes #16114
Four
packages/clisites cited driver-sql'scase 'lookup': case 'user':arm by name for the sentence "a platform id is 26 characters", two of them spelling out a ULID literal. #15522 corrected that origin sentence, so the citation chain pointed at text that no longer exists — the arm today carries an explicit guard, "⛔ Do not restore the number this sentence used to carry", which names these four surviving copies and hands them to this card.This PR makes the four citations true. It changes comment and failure-message text only — no behaviour, no asserted value, no minted width.
The card's premise was re-derived here, not inherited
The dispatch was explicit that the count and the numbers were the card's and unverified by the dispatching seat. Both were re-measured on this branch.
The premise holds. Every claim reproduced.
What the platform actually does — driven, not read
The oracle enters the chain at the top: a real
SqlDriveragainst SQLite, throughcreate()andfind(), rather than readingDEFAULT_ID_LENGTHor callingnanoid()directly.m3rD4WR6sc6XqF-O,cevYV0RMKDHMMTbk,F6uD2Cz19oQO167i-and lowercase; a Crockford-base32 check over the samples returnsfalseulidcase-insensitive over tracked source matches exactly 1 file, and that file is driver-sql's own "do not restore" guard naming the removed literalnanoidmatches 26 files ⇒ the instrument fires, so the 1 is a real readingDEFAULT_ID_LENGTH = 16;driver-memorymintsobjectName-timestamp-counter, a variable-width shapeThe repair therefore does not swap 26 for 16
An id's width is not a fixed number at all. Naming a new fixed number would re-arm the same trap, so each site now says which half it states — what the driver MINTS versus what a supplied id is stored at.
Every site's conclusion is unchanged, which is why this is
documentationand not a defect: a reference column takestable.string(...)/VARCHAR(255)rather thantable.uuid, because Postgres refuses a platform id in auuidcolumn with22P02— it refuses a 16-character nanoid exactly as it refused a ULID.The population was swept, and four was a floor
The dispatch asked for the real number rather than the card's. Sweeping
26 char/26-charactervariants, the ULID literal,platform id,DEFAULT_ID_LENGTHand id-width phrasings across code, comments, docblocks, tests and docs:Four in
packages/cli— all corrected here (located by text; the triage anchors had rotted, one by 491 lines):packages/cli/src/commands/generate.ts— doc comment,id is 26 characters (...says so andpackages/cli/src/commands/generate.ts— inline comment,this card's five rows — a platform id is 26 characterspackages/cli/src/commands/generate-field-type-vocabulary.pin.test.ts—expect(value, message)failure stringpackages/cli/src/commands/generate-multiple-json-column.pin.test.ts— plain commentA fifth live-source site, outside the card's population — filed as #16410, not edited here:
packages/platform-objects/src/audit/sys-import-job.object.tscarries "a minted platform id is 26 characters" — the strongest form, since "minted" is the one reading measurement flatly contradicts. The card listed theplatform-objectsCHANGELOG copy but not the live source it records. It is filed rather than ridden along on this card's own stated precedent — "deliberately not fixed there — a different package, so a different verification surface" — which measurement confirms: adding that file took the derived gate set from 48 to 49 families, pulling incheck:i18n-stale-fillandpackages/platform-objects' i18n extractor.Correctly excluded, verified individually: the two CHANGELOG copies (historical records); driver-sql's arm at
sql-driver.ts(it is the correction, and says the opposite);like-pattern.ts,text-match-sql.tsandsql-driver.ts:2761,2767(the 26 letters ofA-Z);validate-rule-schema-formats.ts(format names ≤ 26 chars);notification-keyed-text-bounds.test.ts(uses 255, the physical column width).⛔
packages/rest/src/rest-server.tsis a reserved single-writer path and was not edited. The sweep found no stale citation in it.Verification
Everything below was run on the final commit,
e58829a2563, with the workspace built.Two commits, and the second corrects the first:
f933d1e3c7dmade the four corrections, ande58829a2563reflows one line that commit left orphaned (width halfstranded on a line of its own), quoting the three lines it corrects and naming the commit. Nothing is added there and no line is bought — the same words occupy one line fewer.Suites
pnpm --filter @objectstack/cli test— 272 test files passed (272); 3263 passed, 6 expected fail.pnpm --filter @objectstack/cli typecheck— exit 0. ⭐ Not taken on trust:tsc --noEmit --listFileswas checked to actually contain all three edited files, including the two.test.ts, so "typecheck is clean" is a statement about this diff rather than about a program that excluded it.@objectstack/cliitself, and those seventest/files assert against the package's owndist/. Each failure said so in its own words ("packages/cli is not built... CI declares the build (turbo:@objectstack/cli#testdependsOn build); a direct vitest run does not"). Afterpnpm build, all seven pass. No diagnosis was assumed: the tests named the cause and the rebuild confirmed it.Gates — derived, not recalled.
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackattests the answer comes from this repo ate58829a2563; the plain form'sReconciliation — 48 famil(ies)and the 48 lines of--commandsagree, so the union is reconciled across both spellings.check:type-check-debt, whose--re-measurehalf exhausted tsc's heap at the 4 GB ceiling this shared box runs under, and which then refused to record a number rather than report one ("refusing to record 0"; its own text: "⛔ This is NOT a pass and NOT a finding"). Its coverage half printedOK — 75/79 workspace packages type-checked. That is a resource limit, not a verdict on this diff.Declared WIDE populationblock — all 10 run, all green. These sit outside the 48 and no derivation can say whether they bite, so their absence from the matched block is not a clearance. They walk every non-test source underpackages/, which is exactly what this diff edits, so they were run rather than read as silence.Artifact rosterblock — all run, one NOT MEASURED.check:react-declaration-parityneeds an SDUI manifest that only a browser build of objectui produces and that this repo's build deliberately does not; unrelated to this diff, which touches no spec or React surface.check:partof-closing-keywordandcheck:single-claim-pathsboth refuse withoutPR_NUMBER/PR_BODY("a wiring or usage failure, NOT a verdict"). The first is the one that reads this body's closing keyword. This body carries exactly one, on its opening line, and it names this card alone; every other card referenced here is mentioned with no keyword standing before it — checked mechanically over the body and over both commit messages.⭐ The four gates that needed the build were measured, not waved through.
check:i18n,check:i18n-coverage,check:i18n-walk-parityandcheck:dual-build-cjs-loadsall reportedPREREQUISITE NOT METon an unbuilt tree — which reads as neither pass nor fail.check:i18nis genuinely implicated here, because it re-extracts translation bundles frompackages/cli/src, which this diff edits. After the build all four are green on their own verdict lines,check:i18nreportingOK (9 package(s) — all bundles in sync, no undeclared authoring keys).⭐ No ablation leg, stated rather than invented. The diff changes comment and message text only, so there is nothing whose removal could turn a test red — a mutation here could not fire, and a leg that cannot fire proves nothing.
⭐ No mirror pair. Nothing here mirrors a second declaration, so there is no "mutate the side you mirror" leg to run.
⛔ No changeset, and
skip-changesetapplied: this publishes nothing from any released package. Comments and one test failure-message string are the entire diff.🤖 Generated with Claude Code
https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
Generated by Claude Code