fix(spec): os migrate meta guidance for the datasource-*, filter-*, action-*, data-* and element-* migration entries states each lesson in words, not tracker numbers (stage 4) - #20509
Conversation
…s, not tracker numbers (#20233 stage 4, wip) Rewrite the reason / replacement / acceptanceCriteria text of the datasource-* ADR-0087 semantic entries in form D: each cited ruling, measurement or fix is stated in words; no tracker number remains. Text only: no id, surface, from/to or matcher moves. registry.ts is regenerated in a later commit. Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH Co-authored-by: Claude <noreply@anthropic.com>
…ot tracker numbers (wip) Rewrite the reason / replacement / acceptanceCriteria text of the filter-* ADR-0087 semantic entries in form D. Text only: no id, surface, from/to or matcher moves. Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH Co-authored-by: Claude <noreply@anthropic.com>
…ot tracker numbers (wip) Rewrite the reason / replacement / acceptanceCriteria text of the action-* ADR-0087 semantic entries in form D. Text only: no id, surface, from/to or matcher moves. Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH Co-authored-by: Claude <noreply@anthropic.com>
… tracker numbers (wip) Rewrite the reason / replacement / acceptanceCriteria text of the data-* ADR-0087 semantic entries in form D, the downstream-repo citation included. Text only: no id, surface, from/to or matcher moves. Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH Co-authored-by: Claude <noreply@anthropic.com>
…nges and upgrade guide; widen the pin Rewrite the element-* ADR-0087 semantic entries in form D, regenerate registry.ts, spec-changes.json and docs/protocol-upgrade-guide.md with their generators, widen the os migrate meta guidance pin to the datasource-, filter-, action-, data- and element- families (REWRITTEN floor 55 -> 88), re-pin the one test that matched a removed tracker number on the sentence, and add the patch changeset. Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH Co-authored-by: Claude <noreply@anthropic.com>
…moved read check:org-identifier refuses the literal removed read in author-facing source; the action-session entry now names the alias in words. Registry, spec-changes and upgrade guide regenerated. Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 137 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 4fbc989d1ba0994ffe99282e0138dd28d1134f95 && git checkout 4fbc989d1ba0994ffe99282e0138dd28d1134f95
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4a1df19656bf5b12258ef20e5aa9e8d99c3461bd d8bcc46ad4eb6c25fc83c7487e4e06730441ec57 && git checkout -B drift-repro 4a1df19656bf5b12258ef20e5aa9e8d99c3461bd && git merge --no-ff d8bcc46ad4eb6c25fc83c7487e4e06730441ec57
node scripts/docs-audit/affected-docs.mjs --json 4a1df19656bf5b12258ef20e5aa9e8d99c3461bd |
Contract reviewServed-tier: Head unmoved across the review (re-read at the end: same sha, ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Part of #20233
Stage 4: the datasource-, filter-, action-, data- and element- families.
Clause-②: no
Stage 4 of a staged card. The card stays open for later stages; this PR carries no closing keyword. Text only: no entry id,
surface,from/to, conversion or matching logic moves, and the chain rewrites exactly what it rewrote before.What this does
os migrate metaprints every ADR-0087 semantic entry it crosses as one block:⚠ [protocol N] SURFACE → REPLACEMENT, thenwhy:(the entry'sreason) andverify:(itsacceptanceCriteria). AGENTS.md's runtime-string rule applies to all of it: 「Runtime strings — refusal prose, prescriptions, anything an author is shown — carry no tracker number (pnpm check:doc-authoring): the lesson goes into the text.」 Form D of ruling C+D on #19123 (5749154545) sets the shape: the lesson in words, and no number, dead or alive; a cross-repo number (cloud#N,objectui#N) is still a tracker number.This stage covers the next five families,
datasource-,filter-,action-,data-andelement-: 150 sites → 0 in the three prose fields, across 33 entry files. None of the 37 entries in these families carries a tracker id insurface(ruling A of the stage-1 ACCEPT,5858839916, is checked and has nothing to do here). Each site now says what the cited ruling, measurement or fix decided. ADR ids stay.registry.ts,spec-changes.jsonanddocs/protocol-upgrade-guide.mdare regenerated from the entries (gen:migration-registry,gen:spec-changes,gen:upgrade-guide), never hand-edited. The pin now holds eleven families.Census — tracker ids in the author-shown fields
Instrument. The stage-2 AST instrument, re-written to its published description (the stage-2 and stage-3 copies lived in removed scratchpads): a TypeScript-AST walk over every
packages/spec/src/migrations/entries/**/*.ts. For each entry object literal it evaluates the string value ofreplacement,reason,acceptanceCriteriaand (counted separately)surface, joining string literals with+, then counts#followed by 4 or 5 digits at a word boundary. Validated first by reproducing earlier readings on their own trees (extracted withgit archive): on443b2f4fdc(stage 1)engine-67,datasource-43,filter-30,action-26,element-25,data-24, whole tree 266 entries / 1,016 sites / 9surface; on0d7ed5a378(stage 3 landed) whole tree 711 sites / 7surface— every figure equal to the stage-1 census and to stage 3's after-count. Unevaluable fields: 0.Controls, same run.
17.aggregation-node-distinct-retired.tsreads 7 sites (replacement 1, reason 6), before and after./// docblock lines in entry files carry a tracker id, and none is counted; 823 before and after. Comment lines are the sibling card packages/spec/src: 1,277 comment lines still cite 170 deleted tracker numbers (1,295 sites) — the staged remainder of ruling C+D on #19123, measured by PR #20226 #20234's surface, and this PR touches none (proved below).surfacesites left in the tree (other families) count 0 in the three-field total.Base
fc0db22b:datasource-9 entries, 43 sites (3 / 38 / 2);filter-11, 32 (1 / 30 / 1);action-6, 26 (0 / 23 / 3);element-5, 25 (2 / 19 / 4);data-6, 24 (0 / 24 / 0). 150 sites (6 / 134 / 10) in 33 of the 37 entries; 88 distinct ids (80 bare, 8objectui#, 2cloud#). Whole tree: 310 entries, 721 sites (711 after stage 3, plus entriesmainadded since in other families), 7surface.After this PR: all five families 0; the six earlier families still 0; whole tree 721 → 571;
surface7 (other families). The PM's rough line count (156 sites, 33 files) is a wider instrument; the AST reading is 150 in the same 33 files.#NN17.action-descriptor-is-async-retired17.action-descriptor-resume-authority-default-flip17.action-session-roles-to-positions17.data-driver-find-stream-retired17.data-driver-query-omit-object17.data-engine-batch-retired17.data-field-changed-event-retired17.datasource-config-inline-credential-refused17.datasource-config-placeholder-refused17.datasource-config-url-userinfo-refused17.filter-regex-options-retired18.action-bulk-dispatch-contract-undeclared18.action-engine-facade-find-query-envelope18.data-file-value-duration-unit-in-key18.data-nosql-query-options-timeout-unit-in-key18.datasource-config-mongo-options-credential-refused18.datasource-config-postgres-url-unparseable-refused18.datasource-config-url-query-credential-refused18.datasource-credentialsref-mongo-composed-no-username-refused18.datasource-credentialsref-mongo-url-no-user-refused18.element-data-source-and-object-block-filter-rule-array18.element-number-filter-rule-array18.element-record-picker-filter-rule-array18.filter-between-blank-endpoint-refused18.filter-between-field-reference-endpoint-refused18.filter-comparand-types-and-widget-nested-slots-refused-at-save18.filter-equality-array-comparand-refused18.filter-equality-array-comparand-refused-at-save18.filter-icontains-comparand-refused-at-parse18.filter-ne-array-comparand-refused18.filter-preset-ordering-comparand-refused18.filter-query-face-comparands-refused-at-save18.filter-text-operator-declared-type-refusedText only — proved by a base-vs-head AST comparison
For every entry file this PR changes, both versions (
fc0db22band the head) are parsed and three things are compared token for token: every import declaration, every property other than the three prose fields (soid,surface,from/toand any matcher, by evaluated value), and every comment token in the file. 33 files compared, 0 with a non-prose change. The instrument is shown able to fail first: on an in-memory copy it reports DETECTED for a mutatedid, a mutated comment and a mutatedsurface. So none of #20234's comment lines moved, and no entry's identity or matching moved.Every citation read, and what the text now says
Each cited id was read with a single-card REST read (body plus the ruling, measurement or landing comments), resolved against the repository its sentence names. Ids are in code spans so this body posts no cross-references. 10 bare ids answer 404 on both the issues and the pulls endpoint (re-probed with a 200 control,
14478), and 2cloud#ids are unreadable from this session (403); those sentences are rewritten from whatmainrecords, listed in Acceptance notes.datasource-(14 ids)7990sys_metadatawrite guard parked.8078(PR)${…}placeholders reach the client verbatim and thatconfig.urlstill took the secret.80828336${…}at publish; implementing resolution rejected.833788768696new URL()rejects the multi-host form outright, and the mongo arm hands the authored URL to its client untouched"; "the defect class closed when each DSN branch was made to inject the bound secret its composed branch already used"9041datasource-credentialsref-mongo-url-no-user-refused"; "this URL-branch refusal"9147url+ nousernamerefused, inheriting the URL-branch ruling.7314,7385,8152,88758875, 404) the mysql DSN branch.8873filter-(24 ids)4706$regexloudly and add$icontains; five-backend true regex (A) excluded (turso remote cannot, no business pull).$regexloudly and add$icontains, rather than make five backends agree on one regex dialect)"5701,5702$icontainsdeclaration, …)"; "the driver half"614818012$betweenendpoint is refused at the authoring door, and the refusal names the blank side."13495objectui#96957596{ $field }endpoint in either$betweenunion.7713(PR)5222$fieldcompiled to a column-to-column comparison on the SQL faces.19377201167872string/number/bigint/boolean/null/Dateand refuses everything else.198891975719514,objectui#905018113FILTER_TEXT_CASES.19886$newith an array refused at the shared face and the schema door.86908808(PR)15661,1577314079$notContains.$notContains)"action-(19 ids)6748isAsync: zero readers on a fresh three-repo measurement.6667supportsPauseat runtime.38013823waitpause is service-owned but inherited'any'.4484,5540,60115561resumeAuthoritydefaulted to'any'— fail-open by omission.5703supportsPause/isAsyncwere zero-reader declarations.56135697,5779positionsis now the canonical key …"5050rolesremoved outright.3280,3290ctx.session.tenantIdalias: deprecated, then removed in the next major.ctx.session.tenantIdalias: deprecated first, removed in the next major"4579,4657openApi31andactivationEventsretirements.1731914175findthe filter alone.data-(15 ids)4484,4618,46734639data.records.*events.3196cloud#1053,cloud#1030$likethe type layer would have caught reached runtime there through exactly that hole"63505181DriverQuerynarrowing itself.6321,60836083404;mainrecords it as ADR-0122 phase 2).1866918122DurationMs/DurationSecondsbesideEpochMs, the unit set derived from six genuine duration rows.EpochMsas a closed duration type"; "one of the six genuine durations the closed types' unit set was derived from"14478,15680element-(17 ids)objectui#6206ViewFilterRulearray, not a record-shaped exception; measurement-first binding.15442,15449object-*doors family-wide, one entry; exception and mixed rejected.7751object-*block props enterComponentPropsMap.object-*blocks first got props schemas in the map"objectui#6948$and/$or/$not.15828POST /analytics/queryrefused the arraywherethe adapter sent.16626objectui#7754,objectui#7752,objectui#6828aggregate()runstranslateFilterArray.ObjectStackAdapter.aggregate()runs the sametranslateFilterArray"; "The adapter-side lowering lands in the console's own repository."5158FilterArrayis input-only sugar with one lowering seam (parseFilterAST).5334wherewas silently dropped.wherewas found silently dropped on the analytics path)"1732114406element:record_picker; its census pin asks whether anyfilterdoor refuses the array.filterdoor still refuses the array"; the 2026-08-25 Option-A ordering ruling stated as "measure the consumer's read path before the contract moves"12039element:numberconverged.element:numberconverged"objectui#7663inputs.filterflip for the picker.15829filterlocation, a finding of its own.Pin —
packages/cli/test/migrate-meta-engine-guidance.test.ts, widenedCOVERED_PREFIXESis nowengine-,ui-,plugin-,driver-,kernel-,system-,datasource-,filter-,action-,data-,element-(data-does not selectdatasource-ordataset-: the match isstartsWith('data-')). TheREWRITTENfloor rises from 55 to 88 ids: the 33 entries of this stage that carried a tracker id. The threeitblocks are textually unchanged: the detector is exercised on both sides, every covered prefix must select an entry, and each covered block is found verbatim in the real CLI's stdout before it is asserted clean. The file keeps its stage-1 name; the header lists the eleven covered families.Ablation — the widened pin can fail on a new-family block
From committed state, HEAD
d8bcc46a, in one lock turn, withscripts/ablation-replace.mjsin wrap mode (it owns the mutation's restore trap; the leg script adds its owntrap … EXIT INT TERMthat restoresregistry.tsfromHEADby absolute path and checks the blob) andscripts/ablation-dist-preflight.mjsgating each leg. The bundle is built from the generatedregistry.ts, so that is the file mutated.registry.ts, theacceptanceCriteriaofdatasource-credentialsref-mongo-url-no-user-refused: anchorparse reports this URL-branch refusal.→parse reports the #9041 refusal.The tool read anchor 1 → 0 and replacement 0 → 1, blob7001c102→6f3f81ff.1 failed | 2 passed—datasource-credentialsref-mongo-url-no-user-refused: the printed guidance cites a tracker id: expected '#9041' to be undefined.7001c102== HEAD,git diff HEADempty.--absentpreflight found the marker in none of 224 built files, with the working tree clean against HEAD. Pin: green,3 passed. Whole tree afterwards: 0 dirty paths.Verification
Final head
d8bcc46a. Every heavy run went throughscripts/pm/os-verify-lock.sh(VERDICT command-exit 0on each turn; per-step exit codes recorded separately). Where a line was taken at95fb97eait says so: the one commit after it (d8bcc46a) changes one entry sentence and the three generated projections, and every reader of that text was re-run atd8bcc46a.pnpm exec turbo run build --concurrency=2 --filter='@objectstack/cli^...'givesTasks: 58 successful, 58 total(at95fb97ea); the spec package was rebuilt atd8bcc46ain both ablation legs (exit 0).d8bcc46a, the ablation's restore leg:test/migrate-meta-engine-guidance.test.ts3 passed. With its neighbour at95fb97ea:pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 test/migrate-meta-engine-guidance.test.ts test/migrate-meta-default-range.test.tsgivesTest Files 2 passed,Tests 10 passed | 1 skipped(the skip is the default-range file's ownskipIf). Re-run with its neighbour atd8bcc46a:Test Files 2 passed,Tests 10 passed | 1 skipped. One earlier run at this head is void and is not counted: it spawned the CLI while a concurrent lock-free gate was rebuilding the packagedist/trees (MODULE_NOT_FOUND, both files); the re-run is the reading.localproject (at95fb97ea):pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2givesTest Files 572 passed (572),Tests 16789 passed | 1 todo. Therepoproject (atd8bcc46a):Test Files 38 passed,Tests 690 passed. The readers of the changed sentence (atd8bcc46a):src/migrations,src/ui/action-params.test.ts,src/ui/filter-rule-array-guidance.test.tsgiveTest Files 5 passed,Tests 229 passed.95fb97ea, no CLI file changed after):test/vitest-tiers-partition.test.tsandsrc/utils/spec-release-changes.test.tsgiveTest Files 2 passed,Tests 28 passed.registry.ts:pnpm --filter @objectstack/driver-sql exec vitest run --maxWorkers=2 src/sql-driver-query-signature.test.tsgives 15 passed.95fb97ea; the last commit edits string literals only):pnpm --filter @objectstack/spec typecheckexits 0 (test layer: 53 files / 251 errors held in its ledger);pnpm --filter @objectstack/cli typecheckexits 0 (3 files / 28 errors held).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 90 families atd8bcc46a(the same set it derived at95fb97ea).--ranover the recorded exit codes reads 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0. They includecheck:doc-authoring("16735 customer-facing string(s) across 1167 spec sources clean"),check:issue-citations,check:migration-registry("registry.ts is current (310 semantic, 230 retired-key, 206 retired-def)"),check:spec-changes,check:upgrade-guide,check:generated("All 15 generated artifacts are up to date"),check:org-identifier,check:nul-bytes,check:dual-build-cjs-loads(104 require entry points across 66 packages load),check:type-check-debt,check:adr-0087-registrationandcheck:changeset-no-major.d8bcc46aafter 30 families; the other 60 were run afterwards on the same head, andcheck:type-check-debt, whose first resumed run lost a package build to an OOM kill (exit 3,PREREQUISITE NOT MET), was re-run alone and exited 0 ("4 ledger entr(ies) re-measured … none above its recorded number").95fb97earead 88 exit 0,check:dual-build-cjs-loadsexit 3 (dists not yet built) andcheck:org-identifierexit 1: my rewrite of the action-session entry had spelled the removed hook read literally.d8bcc46anames that alias in words instead, and the gate reads OK there.eslint --no-inline-config --format jsonover the 36 changed.tsfiles atd8bcc46areports 36 files, 0 errors, 0 warnings.eslint.config.mjs:**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}minusNEVER_LINTED, and all 36 are in it (no file-ignored warning).parserOptions.project, no typed rules), so a text edit cannot move the verdict on a file it does not touch.mainmoved 10 commits past the base, to4a1df196; the only ones underpackages/spec/src/migrations/are the landed20488(two entries in thefield-andui-families, plusregistry.ts). A driver-free bare-clonemerge-tree --write-treeofd8bcc46aagainst4a1df196exits 0 with no conflicted path, and the census over that merged tree reads 0 sites in all eleven covered families, somainwas not merged in; CI's merge ref runs the registry gates on the merged tree.Acceptance notes
main. Each answers 404 on both the issues and the pulls endpoint, with a 200 control (14478).8696/8875/8873(the bound secret reaching the mongo, mysql and postgres clients):service-datasource'sdefault-datasource-driver-factory.ts, its CHANGELOG (the mongo URL-branch entry says it "closes the last arm of the family" the other cards closed one driver at a time; the mysql DSN fix is{ uri, password }) andbound-secret-dsn-branches.test.ts, which pins the bound secret outrankingoptions.auth.common.zod.tsandpg-url-grammar.server.tsrecord why the shared helpers must not parse (mongo's multi-host form, whichnew URL()rejects).8876:common.zod.ts(urlUserinfoUsername, the username half of the same grammar; a username is not credential material).9041:datasource.zod.ts(CREDENTIALS_REF_MONGO_URL_NO_USER_REFUSEDand its docblock); the text now names the sibling entry by id.6148:scripts/check-adr-0087-registration.mjs's header (a declared-breaking changeset must state its ADR-0087 disposition in writing).18012:.changeset/18012-between-blank-endpoint-refused.mdandfilter.zod.ts(ruling A of 2026-09-17: a blank bound refused at the authoring door, the blank side named).19377:.changeset/19377-between-field-endpoint-runtime-door.mdandfilter-comparand-shape.ts(the runtime door brought under the 2026-08-11 removal).16626:.changeset/filter-orthography-binding-and-object-blocks.mdonmainrecords it as the objectui pin bump the element convergence was parked behind; the text now says "a bump of that pin", and the pin sha it names is unchanged.6083:mainrecords it as ADR-0122 phase 2 (the spec CHANGELOG); the sentence keeps its claim ("two later, smaller driver call-parameter changes both registered") without the numbers.cloud#1053andcloud#1030answer 403 (objectstack-ai/cloudis not reachable here, and attaching it was refused). The sentence was rewritten from what this repository records:packages/spec/CHANGELOG.md(the publishedDriverQueryentry: 20 measuredas anysites downstream, and a$likethat reached runtime through that hole) and the5181card body ("cloud 实测 20 处 cast … cloud#1030 的$like本可在类型层拦住"). Nothing the cloud cards add beyond that is claimed.#43×2,#55,#123,#146,#151), three ruling-record comment ids and onebatch 217 item 3spelling are numbers an author is shown and cannot follow, so each is dropped. So are the maintainer's batch acknowledgements 「146 同意」 and 「217 同意」 and the bare 「同意」 besideobjectui#6206(×3),15442and14175: they record only that a batch was approved, and each sentence now states the ruling's date and content instead. The one verbatim quote that carries a lesson, 「the differences are the protocol's to close」, is kept untranslated.issue/card/PR/batch/record/summonplus a number now finds only HTTP statuses, ports and example values. The twoPR #…citations were#-spelled, so the instrument saw them.packages/spec/src/ui/action-params.test.tsassertedreasonmatches the5613number; it is re-pinned on the sentence that carries the ruling (/ruled contract-first/). No other test reads these entries' prose for a tracker id (a grep of test files for the 88 cited numbers finds only comment lines and assertions on other runtime strings).17076skipped both times), none carrying amigrations/entries/semantic/NN.(datasource|filter|action|data|element)-*file. PRs20504,20503,20460and20458add or edit entries in other families (turso-,view-,stack-,cube-) and regenerateregistry.ts: ordinary concurrency, regenerate on merge. Widening the pin reds no sibling.spec-changes.json,docs/protocol-upgrade-guide.md) are regenerated, as in stages 1–3; only the protocol-17 entries appear in them.#NNsites, 7surfacesites, and threecloud#sites (storage-service-list-retired,cloud-subpath-retired,cluster-driver-dangling-values-removed; the fourth,data-driver-query-omit-object, is done here).AutomationEngine's boot warning for a pausing node type that never declaresresumeAuthority(packages/services/service-automation/src/engine.ts, the line that readsso the #3801 resume gate REFUSES every pause it creates) is printed to a plugin author, andresume-authority-declaration.test.tsasserts the3801and3823numbers are in it. That is the runtime-string rule this card applies, on a producer outside its surface; it is reported to the seat, not fixed here.Line budget
Entry files: 367 changed lines (+214 / −153) across 33 files, against the stage-1 ≈400 budget. The whole diff is 871 lines (+527 / −344) in 39 files. Of the rest,
registry.tsis 367, the two projections are 68 (spec-changes.json44, the upgrade guide 24), the widened pin is 42, the re-pinned test 2 and the changeset 25.Generated by Claude Code