fix(spec): os migrate meta guidance for the ui-* and plugin-* migration entries states each lesson in words, not tracker numbers (stage 2) - #20324
Conversation
…on entries states each lesson in words, not tracker numbers (stage 2) The ADR-0087 semantic entries of the ui- and plugin- families carried 111 tracker-id sites in reason / replacement / acceptanceCriteria, plus one in the surface of two entries. Each is rewritten to say what the cited ruling, measurement or fix decided; ADR ids stay. Text only: no id, from / to or matcher moves. The os migrate meta output pin now holds the engine-, ui- and plugin- families. registry.ts, spec-changes.json and the protocol upgrade guide are regenerated by their generators. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
…y tracker number The ui-notification-action-embed-config-retired guard in migrations.test.ts matched the two tracker numbers the rewritten reason no longer carries. It now matches the sentences that state the dual-source orphaning and the objectui correction. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 136 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 563ad541d569e8f02ad373a28f363c81e5bbea74 && git checkout 563ad541d569e8f02ad373a28f363c81e5bbea74
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin de091b50e67aec12764eccc18a87b1b4259573e3 071dc7fc1d156b8af281c9276af6519178b10110 && git checkout -B drift-repro de091b50e67aec12764eccc18a87b1b4259573e3 && git merge --no-ff 071dc7fc1d156b8af281c9276af6519178b10110
node scripts/docs-audit/affected-docs.mjs --json de091b50e67aec12764eccc18a87b1b4259573e3
|
Part of #20233
Clause-②: no
Stage 2 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.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 the parent card sets the shape: the lesson in words, and no number, dead or alive.This stage covers the next two families by site count,
ui-andplugin-: 111 sites → 0 in the three prose fields, plus thesurfacefield of the two entries that carried an id there (ruling A in the stage-1 ACCEPT,5858839916). 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 stage-1 pin is widened to holdengine-,ui-andplugin-.Census — tracker ids in the author-shown fields
Instrument. The stage-1 instrument, re-implemented: a TypeScript-AST walk over every
packages/spec/src/migrations/entries/**/*.ts. For eachentryobject 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. Tree:objectstack-ai/objectstackat2aa25efb4e(this branch's base, the stage-1 merge). Unevaluable fields: 0.Controls, same run.
17.aggregation-node-distinct-retired.tsreads 7 sites (replacement 1, reason 6), the same reading stage 1 took.//lines in entry files carry a tracker id, and none is counted — for example18.client-envelope-convergence-analytics-automation.tshas 5 such lines and counts 0. Comment lines belong to the sibling card, and ⛔ this PR touches none (the count is 734 before and after).17.authoring-schemas-strict-unknown-keys.tscarries one id insurface; it counts 0 in the three-field total and 1 in thesurfacecolumn.Re-measured on the base, matching the stage-1 census:
ui-17 entries, 65 sites (replacement 6 / reason 58 / acceptanceCriteria 1), 42 distinct ids;plugin-11 entries, 46 sites (1 / 39 / 6), 31 distinct ids.surface: 1 site each.engine-: 0 (stage 1). Whole tree: 267 entries, 950 sites, 9surfacesites.After this PR:
ui-0,plugin-0,engine-0; whole tree 950 → 839 sites andsurface9 → 7. The next family by site count isdriver-/kernel-/system-(44 each).surface17.plugin-activation-events-retired18.plugin-auto-restart-never-reinitialised18.plugin-manifest-contributes-dead-members-retired18.plugin-manifest-contributes-routes-retired18.plugin-manifest-dead-containers-retired18.plugin-manifest-kind-globs-retired17.plugin-manifest-loading-retired17.plugin-runtime-family-retired18.plugin-security-scan-result-surface-retired18.plugin-security-scanner-retired18.ui-cloud-connection-widgets-unknown-keys-refused18.ui-form-field-length-malformed-refused18.ui-form-field-precision-scale-integer-refused18.ui-form-view-predicate-features-root-refused17.ui-interaction-config-family-retired18.ui-list-view-groupbyfield-padded-refused18.ui-list-view-grouping-field-padded-refused18.ui-mcp-connect-agent-unknown-keys-refused17.ui-notification-action-embed-config-retired18.ui-object-grid-page-size-positive-integer-refused18.ui-react-list-view-binding-aliases-retired18.ui-record-blocks-unknown-keys-refused18.ui-reference-rail-unknown-keys-refused17.ui-widget-i18n-family-retiredplugin-version-semver-2-0-0,ui-action-undoable-unfulfillable-refused,ui-bulk-action-param-unknown-keys-refused,ui-report-joined-container-selection-refusedEvery citation read, and what the text now says
I read each cited issue or PR myself with single-card REST reads: the body, and the comments where a ruling or a measurement lives. Ids are in code spans so this body posts no cross-references.
objectui#Nids were read fromobjectstack-ai/objectui; bare ids from this repository.#3733cachedfield key: the parse succeeded and the removed key was dropped without a word; the orphan schema was deleted.#3950#4001#4115#4484findStreamremoved with no tombstone: a TS/API surface nothing parses, so tsc at the call site carries the ban.contracts.IDataDriver.findStream(removed with no tombstone, because nothing parses a driver object)"#4535./uicopies …"#4583readOnlywas precisely validated and inert, and the CRM example called a datasource a read replica while writes went through. The strictness ledger records the "more convincing lie" lesson there.readOnlywas precisely validated and read by nothing, while a shipped example called a datasource a read replica and wrote through it)"#4610./uiNotification/NotificationConfigcopies (zero import sites measured); the./apiinbox row stayed live../uicopies ofNotificationSchema/NotificationConfigSchema(the same names declared differently on other entry points)"#4653activationEventson the kernel's structured{ type, pattern }shape, re-exported from studio.{ type, pattern }shape"#4657activationEventskeys (REMOVE, v17 window): kernel tombstone, studio strict refusal, the orphan schema deleted.plugin-activation-events-retired#4834plugin-runtime-family-retiredby id elsewhere#4875unref'd (an unref'd guard can swallow the timeout).#4910server:key that carries only the keys its executor consumes.#4914manifest.loading, with a hard precondition of a clean cloud and objectui bare-name sweep.#4938HttpServerConfig's seven unreachable keys with their container.HttpServerConfigretirement (seven keys no runtime read and no authoring door reached, retired with their container)"#4988ui-interaction-config-family-retiredby id in the widget entry#5015NotificationActionSchema/EmbedConfigSchema, 2026-08-04: a no-door dead surface retires implementation-first, as three same-shape rulings that week had decided.#5021#5040apis:becomes execution.apis:became execution"#5055FieldWidgetPropskept).#5068propertiesagainst itsComponentPropsMaprow bytype, at publish and lint; a type with no row is skipped because the type union is open.propertiesagainst the type's row at publish and lint time, and skips a type with no row because the type union is open)"#5781./uinotification names; the removal stands.#6011ctx.userrolesalias now, no window.actor-user-roles-to-positions(thectx.userrolesalias, closed at once on the maintainer's word rather than given a window)"#7751object-*blocks getComponentPropsMaprows, key sets from renderer read points.object-*blocks first gotComponentPropsMaprows measured from their read points"#8321Field.scale/precisionrefuse non-integer and negative values (int().min(0)).z.number().int().min(0)"#8691record:reference_railrow, key set from the renderer's read points.record:reference_rail…"; "after the rail was given its strict row"#8744record:alert/record:quick_actions/record:history.record:alert/record:quick_actions/record:history, each by declaring a strictComponentPropsMaprow"#11168id; thekindbucket is reachable throughGET /metadata/:type;globshad zero readers.id(which also found thekindbucket itself reachable …)"#11169globsthrough the full ADR-0049 ceremony.#11327http.servermount.#11566maxLength→z.number().int().min(1).maxLengthby the maintainer's 2026-08-24 ruling"; "tightened first, to a positive integer, by maintainer rulings"#11575cloud-connection:panel/marketplace:installed-list.cloud-connection:panel/marketplace:installed-listrows closed the previous two"#11825AdvancedPluginLifecycleConfigcontainer; the classes stay a host-driven library.#11852#11949minLength→int().min(1), zero refused.minLengthby the 2026-08-25 one, which refused zero too"#11955successThresholdbinds from every status that records a failure.successThresholdbind from every status that records a failure made that MORE convincing"#12174#12269packages/mcpgets its own canonical-envelope gate.@objectstack/mcpwas given for its shipped page"#12340'disk'/'distributed'state strategies (silent memory fallbacks) anddistributedConfig; "a vocabulary of nothing is not a vocabulary".'disk'/'distributed'state strategies that fell back to memory in silence"; "(ruled 2026-08-26: a vocabulary of nothing is not a vocabulary)"#12400capabilities/configuration/extensionsmeasured clean at cloud15f55df.15f55df)#12428startWatchingthrows instead of logging success;watchPatternsis tombstoned because a key leaving a surviving def has no route-3 exit.startWatchinglogged success while watching nothing"; "for the reason the file-watching retirement recorded"#12665features.*in a predicate; refused at authoring.features.*in a predicate, and the authoring door refuses it loudly)"#14791objectName/viewTypealiases now, no window.#15513#15930PluginSecurityScanner; no replacement, repair refused.#15932securityScan.#16526#17360#17499groupByFieldon kanban, gantt and timeline.#19046objectui#3161#4115rule (renames).#4115sentenceobjectui#3169objectui#3289(PR)@object-ui/fields' validation slot renamed onto the spec'serrorand connected to its producer.objectui#5595maxLengthoverride.objectui#5898objectui#6262features.*asymmetry, and the ruling record.#12665sentenceobjectui#7347#17360sentenceobjectui#9853pagination.pageSize: 0reachingObjectGrid.objectui#9896(PR)pageSizeis refused at all three grid read points.The eight dead ids, and the two ids whose page does not say what the text claimed (
#2561,#3896), are in Acceptance notes. No call-shaped token moves: aname(census overregistry.tsis identical before and after, so textual call-spelling ratchets read the same.Pin —
packages/cli/test/migrate-meta-engine-guidance.test.ts, widenedThe stage-1 pin now selects every entry whose id starts with a covered prefix:
engine-,ui-orplugin-. It spawns the real CLI (os migrate meta --from 16 --to 18) once, locates each covered block verbatim in stdout, and asserts the printed block carries no#plus 4 or 5 digits. That block includessurface. Anti-vacuity:engine-, 10plugin-, 14ui-), and every covered prefix must select at least one entry;ADR-0112).The chain reports every semantic entry of every crossed hop, whatever the stack authors (
applyMetaMigrationsmapsstep.semanticstraight to TODOs), so the fixture is kept as-is and the header now says so. The file keeps its stage-1 name; a rename is left to the stage that covers the last family. An entry added later to a covered family is held on arrival — see Acceptance notes for the one known in-flight case.Ablation — the widened pin can fail on a
ui-block and onsurfaceFrom committed state, HEAD
8a1076b0a6, withscripts/ablation-replace.mjsin wrap mode andscripts/ablation-dist-preflight.mjsgating each leg. The bundle is built from the generatedregistry.ts, so that is the file mutated (stage 1's attempt 2 records why the entry file is the wrong target).registry.ts, thesurfaceofui-react-list-view-binding-aliases-retired: anchor(the react-tier overlay aliases published as deprecated→(the react-tier overlay aliases #11284 published as deprecated. The tool read anchor 1 → 0 and replacement 0 → 1, blobab26922f→9a200491.1 failed | 2 passed—ui-react-list-view-binding-aliases-retired: the printed guidance cites a tracker id: expected '#11284' to be undefined.ab26922f== HEAD,git diff HEADempty.--absentpreflight found the marker in none of 222 built files, with the working tree clean against HEAD. Pin: green,3 passed.Verification
Final head
071dc7fc1dunless a line says otherwise.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 own pre-existingskipIf).pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 src/migrationsplus the 14 other spec test files that readMIGRATIONS_BY_MAJORor one of these entries' text (plugin-runtime-tier-truthful-text,interaction-config-retirement,widget-i18n-retirement, …) andscripts/build-schemas-check-mode.test.ts:Test Files 15 passed,Tests 438 passed.src/migrationsrun, at8a1076b0a6, went red, 2 failed:migrations.test.tspinned the two tracker numbers inui-notification-action-embed-config-retired'sreason. The second commit re-pins the same guard on the sentences that now carry the lesson (see Acceptance notes).src/utils/spec-release-changes.test.ts6 passed;test/vitest-tiers-partition.test.ts22 passed (at8a1076b0a6; no CLI file changed after it).registry.ts:pnpm --filter @objectstack/driver-sql exec vitest run --maxWorkers=2 src/sql-driver-query-signature.test.tsgives 15 passed.pnpm --filter @objectstack/spec typecheckexits 0.pnpm --filter @objectstack/cli typecheckexits 0 (at8a1076b0a6), and its test layer holds the recorded 3 files / 28 errors, unchanged.pnpm exec turbo run build --filter="@objectstack/cli^..." --concurrency=2gives 55/55 (at8a1076b0a6);@objectstack/specrebuilt at the final head, command-exit 0.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderives 88 families at071dc7fc1d(the same set as at8a1076b0a6).--ranover the recorded exit codes reads 88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0. They includecheck:migration-registry,check:spec-changes,check:upgrade-guide,check:generated(15 artifacts current),check:doc-authoring,check:issue-citations,check:nul-bytes,check:adr-0087-registration,check:changeset-no-majorandcheck:dual-build-cjs-loads(104 require entry points across 66 packages load).diststamp made stale by that edit (5 × exit 3,check:generatedexit 1 namingapi-surfacestale by stamp,check:dual-build-cjs-loadsexit 3) were re-run after rebuilding spec at the final head. The reconciled list takes each family's latest run.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 warning).parserOptions.project, no typed rules), so a text edit cannot move the verdict on a file it does not touch.merge-tree --write-treeof this head againstorigin/main7b1e4a4871(six commits past the base, one of them adding 25 semantic entries) exits 0 with no conflict. The census over that merged tree readsengine-0,plugin-0 andui-0 across 18 entries: main's newui-report-joined-chart-retiredcarries tracker ids only in comment lines.Acceptance notes
main. Eight cited numbers answer 404 on both the issues and the pulls endpoint, re-probed with a 200 control (#11327):#10627,#10724,#10726,#10812,#11284,#11328,#11332,#14919. Each sentence was rewritten from whatmainrecords, and anything it could not confirm was dropped:#10627/#10724/#10726/#10812/#11328(contributes routes and dead members):packages/spec/src/kernel/manifest.zod.tscarries all tenretiredKey()tombstones and the census comment;packages/objectql/src/engine.ts(manifest.contributes?.kinds) is re-measured as the only non-spec read;plugin-rest-api.zod.tsandmetadata-plugin.zod.tspoint at the imperativehttp.servermount. Dropped: "triage graded 2026-08-21" and the 2026-08-24 date on the routes entry's cloud sentence (the cloud sha5b5925ais kept:#12400's body corroborates it). The routes ruling's 「接受所有」 and Option B are kept:#11327's body records them.#11332(dead containers): the threemanifest.capabilities/configuration/extensionstombstones onmain. Dropped: "triage graded 2026-08-23".#11284(react-tier convergence):packages/spec/src/ui/react-blocks.tsrecords the 2026-08-23 maintainer ruling that the react tier converges on the metadata-tier vocabulary, deprecating first.#14919(scanner):packages/core/src/security/index.ts's tombstone andsecurity-scanner-retirement.pin.test.tsrecord the 2026-09-05 ruling, the removed class and types, and repair refused. Dropped: "ruled A: retire in three surfaces" and the batch numbers, whichmaindoes not state.#2561)" resolves here to an unrelated security-lifecycle umbrella. The claim ("objectui holds TYPE re-exports … never validators, and says so") is objectui's own decision onobjectui#2561: keep the@objectstack/spec/uire-exports type-only. It was rewritten from objectui'spackages/types/src/__tests__/p2-spec-exports.test.tsat objectuimain, which records that decision.#3896, cited as "follow-up" and "close-out".#3896itself is the sharing-rulecriteriaREST bypass and says neither. The "follow-up" is PR#3950(its title says so); the "close-out" is the inert-key sweep recorded onmainindocs/protocol-upgrade-guide.mdand the strictness ledger. Both sentences now say what those decided.objectui#N(nine) or "objectui PR " plus a number (one). The stage-1 census counted them by number with the rest; they were read fromobjectstack-ai/objectui(all 200), not from this repository, where the same numbers name unrelated cards.#. The instrument cannot see it, but it is a tracker number shown to the author, and it sat in a rewritten sentence.layoutaccepts only vertical | horizontal — theinlineandgridarms retired (#20221) #20262 adds18.ui-form-layout-inline-grid-retired.tswith one tracker id in its entry text (read from the PR's file list: one#plus five digits). It is not onmain, so it is untouched here. Once this lands,ui-is covered: feat(spec)!: formlayoutaccepts only vertical | horizontal — theinlineandgridarms retired (#20221) #20262 must rewrite that site before it lands, or the pin goes red on its merge ref.18.ui-list-view-groupbyfield-padded-refused.tskeeps its//comment citing a number; comment and docblock lines are the sibling card's surface.spec-changes.json,docs/protocol-upgrade-guide.md) are regenerated, as in stage 1; their--checklegs are green.packages/spec/src/migrations/migrations.test.ts. Its guard onui-notification-action-embed-config-retired(the entry must keep explaining its orphaning and name the objectui correction) matched the two tracker numbers by regex, so the rewrite turned it red. The same two assertions now match the sentences that carry the lesson (dual-source cleanup removed the ./ui copies,objectui, which re-exported both names); the negative assertion beside them is unchanged. No other test pins a covered entry's text. Agit grepof test files for the 24 ids finds four besides the pin and this one: three (plugin-runtime-tier-truthful-text,interaction-config-retirement,widget-i18n-retirement) were run above and pass, and the fourth (packages/core'sgranted-permissions-not-enforced.pin.test.ts) names the loading entry's file only inside a failure message.Line budget
Entry files: 333 changed lines (+210 / −123) across 24 files, against the stage-1 ≈400 budget. The whole diff is 830 lines (+533 / −297) in 30 files. Of the rest,
registry.tsis 333, the two projections are 52 (spec-changes.json32, the upgrade guide 20), the widened pin is 85,migrations.test.tsis 6 and the changeset is 21.Generated by Claude Code