fix(spec): os migrate meta guidance for the field-*, export-*, api-*, dataset-*, hook-* and metadata-* migration entries states each lesson in words, not tracker numbers (stage 5) - #20522
Conversation
…igration entries state each lesson in words, not tracker numbers Stage 5 of the staged sweep: the reason / replacement / acceptanceCriteria text (and one surface) of 25 ADR-0087 semantic entries in these six families no longer cites a tracker id. Each site now says what the cited ruling, measurement or fix decided; ADR ids stay. Text only. Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH Co-authored-by: Claude <noreply@anthropic.com>
…pi-, dataset-, hook- and metadata- families; regenerate the migration registry and its projections COVERED_PREFIXES gains the six families and the REWRITTEN floor rises from 88 to 113 (the 25 entries this stage rewrote). The three assertions are unchanged. registry.ts, spec-changes.json and the protocol upgrade guide are regenerated by their generators; a patch changeset records the text change. Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 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 — 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 0a2f5204d77f747d04b739fc213b9dc5f1fe2c54 && git checkout 0a2f5204d77f747d04b739fc213b9dc5f1fe2c54
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0bbe4005e82fce2058238720cac7ba5cd2f182d5 d24a253bd04582f05875143dee32077168bdd193 && git checkout -B drift-repro 0bbe4005e82fce2058238720cac7ba5cd2f182d5 && git merge --no-ff d24a253bd04582f05875143dee32077168bdd193
node scripts/docs-audit/affected-docs.mjs --json 0bbe4005e82fce2058238720cac7ba5cd2f182d5
|
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 5: the field-, export-, api-, dataset-, hook- and metadata- families.
Clause-②: no
Stage 5 of a staged card. The card stays open for later stages; this PR carries no closing keyword. Text only: no entry id,
from/to, conversion or matching logic moves, and the chain rewrites exactly what it rewrote before. Onesurfacemoves, under ruling A of the stage-1 ACCEPT (5858839916): it carried two tracker numbers.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 is still a tracker number.This stage covers the next six families,
field-,export-,api-,dataset-,hook-andmetadata-: 110 sites → 0 in the three prose fields and 2 → 0 insurface, across 25 entry files. 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 seventeen families.Census — tracker ids in the author-shown fields
Instrument. Stage 4's TypeScript-AST census, the same script: for each entry object literal under
packages/spec/src/migrations/entries/**it evaluatesreplacement,reason,acceptanceCriteriaand (separately)surface, joining string literals with+, and counts#followed by 4 or 5 digits at a word boundary. On base9e9bb464it reads the whole tree at 571 sites / 7surface/ 61 short, which is stage 4's recorded 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 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).Base
9e9bb464:field-10 entries, 28 sites (5 / 20 / 3);export-3, 21 (0 / 21 / 0);hook-4, 17 (0 / 17 / 0);api-5, 16 (0 / 14 / 2);metadata-7, 16 (1 / 15 / 0);dataset-3, 12 (2 / 8 / 2), plus 2 insurface. 110 sites (8 / 95 / 7) in 24 of the 32 entries; 75 distinct ids (71 bare, 1 spelledframework#, 3objectui#). Short numbers: 14.After this PR: all six families 0,
surface0; the eleven earlier families still 0; whole tree 571 → 461,surface7 → 5, short 61 → 50. The PM's rough line count (126 sites, 25 files) is a wider instrument; the AST reading is 110 in 24 files, and the 25th file carries only short decision-batch numbers and ruling-record ids.17.api-runtime-create-withdrawn17.export-axis-opt-in17.export-field-meta-constraints-retired17.field-runtime-create-withdrawn17.hook-context-session-roles-retired17.hook-register-empty-object-target-refused18.api-assembled-entry-split18.api-error-retry-after-unit-in-key18.api-runtime-config-durations-unit-in-key18.dataset-filter-nested-relation-equality-array-refused-at-save18.dataset-measure-aggregate-field-type-refused18.dataset-measure-selecting-aggregate-field-type-refused18.export-job-family-retired18.field-currency-scale-refused18.field-max-length-malformed-or-misplaced-refused18.field-min-length-malformed-or-misplaced-refused18.field-multiple-non-capable-type-refused18.field-predicate-reference-traversal-refused18.field-scale-precision-integer-refused18.hook-register-undispatched-lifecycle-event-refused18.metadata-customization-protocol-retired18.metadata-endpoints-switch-radius-repartitioned18.metadata-manager-config-cache-ttl-unit-in-key18.metadata-manager-config-inert-cache-keys-retired18.metadata-plugin-additional-types-retiredThe seven entries of these families that carried no number are untouched:
api-endpoint-cache-ttl-unit-in-key,field-inline-and-related-list-columns-closed,field-master-detail-set-null-refused,field-reference-to-spelling-retired,hook-timeout-unit-in-key,metadata-changed-event-payload-retired,metadata-item-name-grammar-enforced.Text only — proved by a base-vs-head AST comparison
For every entry file this PR changes, both versions (
9e9bb464and the head) are parsed and compared: every import declaration; every property other than the three prose fields, by evaluated value (soid,from/toand any matcher); every comment token in the file; and the code skeleton, token by token with each run of joined string literals collapsed to one.surfaceis allowed to differ only where the base value carried a tracker id and the head value carries none. 25 files compared, 0 with a non-prose change; one note, the ruling-Asurfaceof18.dataset-measure-aggregate-field-type-refused. The instrument is shown able to fail first: on an in-memory copy it reports DETECTED for a mutatedid, a mutated comment, a mutatedsurfacewhose base carried no tracker id, a mutated code token and a mutated import, and stays dark on a prose-only mutation. 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: 72 in this repository (one spelled
framework#, the repository's old directory name) and 3 inobjectstack-ai/objectui. Ids are in code spans so this body posts no cross-references. 4 ids answer 404 on both the issues and the pulls endpoint (re-probed with a 200 control,14478); those sentences are rewritten from whatmainrecords, listed in Acceptance notes.api-(11 ids)5488apitoallowRuntimeCreate: falseand refuse at the write inlet (remove, not converge the read path); re-entry only with a real consumption path.50404052BatchOptions.validateOnly, retired the same way (a runtime verdict, no D2).5279(PR),5189,5203(PR)apidrafts;publishPackageand load-timebuildEndpointIndexrunning the endpoint gate.2657apisamong them) become registered types.apisto a registered type WITH A REAL CONSUMPTION PATH"5311saveMetaItemwrite was a third path past the namespace and duplicate gates; closed as subsumed by the5488ruling.18576./apientry, rather than add a bundle-weight rule (A) or accept the weight (C).14478,15677api/stack of that ruling.export-(15 ids)63503544,371061483956(spelledframework#)4633,6532(PR)4484,5540,6011findStream,IStorageService.listandactor-user-roles-to-positionsretirements.653617158IExportServiceandScheduleExportInput;ScheduleStatewith it unless a live consumer is measured. Landing route A, 2026-09-24; scope note, 2026-09-25.objectui#1024719543limit/cursorand never read them; the export-job list's door folded into this retirement.16320field-(20 ids)7893fieldwrite channel rather than build a read path.5488,4052apiandvalidateOnlywithdrawals.apiwithdrawal's rationale reused (api-runtime-create-withdrawn)"; named by key7743,7894NOT_OVERRIDABLE); the plural/meta/fields/spelling folded onto the singular.8169_diagnosticsenvelope asserts well-formedness only; it has no "in effect" axis.11566,11989(PR),11950maxLength(value shape and applicable types); shipped on 17.x; registered in a follow-up.11875maxLengthforsignature/qrcode, then both join the bounded-string set.11431varchar(0)plan).8321scale/precisionrefused as non-integer or negative — the house pattern.precision/scaleinteger refusal set"11949minLengthisint().min(1), zero refused, themaxLengthtemplate in full.maxLength"17469,11437multiple: truerefused outside the multi-capable types — the earlierradiorule generalised; the driver derives its JSON column from the spec predicate.radiowithmultiple: true, generalised)"objectui#8886,objectui#893720078186827501scaleenforced at write time: an over-scale write refused, never rounded.scalewas made enforced at write time (an over-scale write refused, never rounded)"; "the write-timescaleenforcement"hook-(12 ids)4839,5049(PR)session.rolesadmin exemptions removed; the record lock and the delegation guard back on the one permission vocabulary.4579,4657openApi31andactivationEventsretirements.373350504281(PR)5928excludeObjectsface (global except named objects); it declined to change the matcher's read in passing.excludeObjectsface (a hook global except for the objects it names)"; "theexcludeObjectschange declined to do it in passing"65734001319517713metadata-(11 ids)12057mainrecords it).131351151315542,15854endpoints.*switch gates exactly the face its name states; the whole-store family gets its own key.15543RestServerConfig.15624144788586,8421additionalTypes, and refuse unknown/metatypes by the static registry./metaboundary by the static registry"4212onInstallamong them.onInstall(a documented hook with no invocation site)"dataset-(9 ids)19889$key stays undescended.2008016737,16099AVG()over a datetime is an average year on SQLite and an error on Postgres.1635316099(insurfaceandacceptanceCriteria)sum/avgover every field class, registerednot-requiredagainst this entry.1756015768,16236measureResultTypetypingmin/maxover strings and over aformulareturn type.17513Pin —
packages/cli/test/migrate-meta-engine-guidance.test.ts, widenedCOVERED_PREFIXESgainsfield-,export-,api-,dataset-,hook-andmetadata-(17 prefixes;data-still selects neitherdatasource-nordataset-, andapi-does not selectapimethod-: the match isstartsWith). TheREWRITTENfloor rises from 88 to 113 ids: the 25 entries this stage rewrote. The threeitblocks are textually unchanged. The file keeps its stage-1 name; the header lists the seventeen covered families.Ablation — the widened pin can fail on a new-family block
From committed state, HEAD
d24a253bd0, in one lock turn, withscripts/ablation-replace.mjsin wrap mode (it owns the mutation's restore; 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, thereasonofhook-register-undispatched-lifecycle-event-refused: anchormade this branch a warn —→made this branch a warn (#3195) —. The tool read anchor 1 → 0 and replacement 0 → 1, blob94bc4938→b045dbfd.1 failed | 2 passed—hook-register-undispatched-lifecycle-event-refused: the printed guidance cites a tracker id: expected '#3195' to be undefined.94bc4938== 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
d24a253bd0; every reading below was taken there. Every heavy run went throughscripts/pm/os-verify-lock.sh, with per-step exit codes recorded separately.pnpm exec turbo run build --concurrency=2 --filter='@objectstack/cli^...'givesTasks: 58 successful, 58 total; the ten packages outside that closure (forcheck:dual-build-cjs-loads) giveTasks: 68 successful, 68 total.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).test/vitest-tiers-partition.test.tsandsrc/utils/spec-release-changes.test.tsgiveTest Files 2 passed,Tests 28 passed.localproject:pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2givesTest Files 573 passed (573),Tests 16801 passed | 1 todo. Therepoproject:Test Files 38 passed,Tests 690 passed.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 89 families.--ranover the recorded exit codes reads 89 derived, 89 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0. They includecheck:doc-authoring("16735 customer-facing string(s) across 1168 spec sources clean"),check:issue-citations,check:migration-registry("registry.ts is current (311 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("no removed session.tenantId alias"),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.check:dual-build-cjs-loadsfirst exited 3 (PREREQUISITE NOT MET: ten packages had nodist/); after the ten were built it exits 0, and that is the reading recorded.eslint --no-inline-config --format jsonover the 27 changed.tsfiles reports 27 files, 0 errors, 0 warnings.eslint.config.mjs:**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}minusNEVER_LINTED, and all 27 are in it (no file-ignored notice).parserOptions.project, no typed rules), so a text edit cannot move the verdict on a file it does not touch.mainmoved one commit past the base, to0bbe4005: the landed20511(aqa-entry, a retired key andregistry.ts). A driver-free bare-clonemerge-tree --write-treeofd24a253bd0against0bbe4005exits 0 with no conflicted path, and the census over that merged tree reads 0 sites in all six families (whole tree 461), somainwas not merged in; CI's merge ref runs the registry gates on the merged tree.Acceptance notes
mainrecords. Each answers 404 on both the issues and the pulls endpoint, with a 200 control (14478).6148:scripts/check-adr-0087-registration.mjs's header (a declared-breaking changeset must state its ADR-0087 disposition in writing) — the same reading stage 4 used.6573: the objectql CHANGELOG entry "engine.registerHookrefuses an emptyobjecttarget and a scope whose two faces cancel out", andengine.ts's refusal docblocks. It is this entry's own change, so the trailing id is dropped; the entry already states the change.13135:packages/metadata/CHANGELOG.md("retire the paper metadata-customization protocol with its full coupling set … re-charter of" the12057ruling, "the maintainer adopted retirement … 2026-08-29 … and" it "charters the full coupling set the fork report measured").11513: ADR-0126 names it the lock-and-clone ruling of 2026-08-24 (Salesforce-style: lock the packaged base, customize a clone), and its section 9 records the per-field overlay layer as explicitly not chartered.main, not from its card.12057answers 200 but carries only its triage comment; the 2026-08-29 ruling the entry names is recorded inpackages/metadata/CHANGELOG.md, and the sentence says only what that record says.#43×2,#59×2,#122,#127,#128,#145,#215,#218,#221) and five ruling-record comment ids (infield-currency-scale-refused,field-predicate-reference-traversal-refusedanddataset-filter-nested-relation-equality-array-refused-at-save) are numbers an author is shown and cannot follow, so each is dropped. So are five maintainer acknowledgements (「同意,其他也同意」 inapi-assembled-entry-split, 「同意」 ×3 inexport-job-family-retired, 「同意」 inmetadata-customization-protocol-retired): they record only that a batch was approved, and each sentence now states the ruling's date and content instead. The two quotes that carry the lesson stay verbatim: "both legs, table in spec" and 「min/maxnumeric plusdate/datetime; everything else refused」.Prime Directive #12/PD #12spellings are kept. They name a rule in this repository's AGENTS.md, not a tracker item, like the ADR ids; the earlier stages kept the same spellings in theui-,plugin-andsystem-families.surface, per ruling A.18.dataset-measure-aggregate-field-type-refusedwas the only entry of these families whosesurfacecarried tracker ids (two). Its header now names the later widening and the sibling entry in words, and the AST comparison shows nothing else in it moved. No test or tool reads thatsurface: outside the migration tree, the pin and the generated projections, the id appears only in three earlier changesets' ADR-0087 disposition markers (and this PR's changeset).issue/card/PR/batch/record/summon/itemplus a number now finds only HTTP statuses, ports, byte counts, durations, dates, commit shas, SQLSTATE and TS error codes, ADR ids and example values.export-job-family-retirement.test.ts(it assertsnot a D2 conversionand the backtick-freesurface, both unchanged) and comment lines; a search for the 75 cited numbers intoMatch/toContainassertions finds only unrelated digit runs and runtime strings outside this card (api-endpoint-step.test.tsandendpoint-executor.test.tsassert a5040hint, which is [finding] runtime warnings outside the migration ledger print tracker numbers to authors and operators: theAutomationEngineresumeAuthority boot warning (#3801/#5561/#3823) and two objectql data-event warnings (#4639/#4626) #20513's surface).17076included both times). None carries amigrations/entries/semantic/NN.(field|export|api|dataset|hook|metadata)-*file. PR20512adds a retired-key file18.api__RestApiConfig__documentation.version.ts; a retired key has no id and is not instep.semantic, so the widenedapi-prefix does not select it. PRs20512,20504,20460and20458add entries in other families (rest-,turso-,stack-,cube-) and regenerateregistry.ts: ordinary concurrency, regenerate on merge.spec-changes.json,docs/protocol-upgrade-guide.md) are regenerated, as in stages 1–4; only the protocol-17 entries appear in them.surfacesites.Line budget
Entry files: 265 changed lines (+147 / −118) across 25 files, against the stage-1 ≈400 budget. The whole diff is 631 lines (+372 / −259) in 30 files. Of the rest,
registry.tsis 265, the two projections are 44 (spec-changes.json24, the upgrade guide 20), the widened pin is 29 and the changeset 28.Generated by Claude Code