Skip to content

fix(spec): os migrate meta guidance for the ui-* and plugin-* migration entries states each lesson in words, not tracker numbers (stage 2) - #20324

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20233-migrate-meta-tracker-free-stage-2
Sep 27, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20233-migrate-meta-tracker-free-stage-2

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 meta prints every ADR-0087 semantic entry it crosses as one block: ⚠ [protocol N] SURFACE → REPLACEMENT, then why: (the entry's reason) and verify: (its acceptanceCriteria). 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- and plugin-: 111 sites → 0 in the three prose fields, plus the surface field 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.json and docs/protocol-upgrade-guide.md are regenerated from the entries (gen:migration-registry, gen:spec-changes, gen:upgrade-guide), never hand-edited. The stage-1 pin is widened to hold engine-, ui- and plugin-.

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 each entry object literal it evaluates the string value of replacement, reason, acceptanceCriteria and (counted separately) surface, joining string literals with +, then counts # followed by 4 or 5 digits at a word boundary. Tree: objectstack-ai/objectstack at 2aa25efb4e (this branch's base, the stage-1 merge). Unevaluable fields: 0.

Controls, same run.

  • Lit: 17.aggregation-node-distinct-retired.ts reads 7 sites (replacement 1, reason 6), the same reading stage 1 took.
  • Dark (comment lines): 734 // lines in entry files carry a tracker id, and none is counted — for example 18.client-envelope-convergence-analytics-automation.ts has 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).
  • Dark (field boundary): 17.authoring-schemas-strict-unknown-keys.ts carries one id in surface; it counts 0 in the three-field total and 1 in the surface column.

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, 9 surface sites.

After this PR: ui- 0, plugin- 0, engine- 0; whole tree 950 → 839 sites and surface 9 → 7. The next family by site count is driver- / kernel- / system- (44 each).

entry sites (replacement / reason / acceptanceCriteria) surface
17.plugin-activation-events-retired 5 (0 / 4 / 1) 0
18.plugin-auto-restart-never-reinitialised 11 (0 / 7 / 4) 0
18.plugin-manifest-contributes-dead-members-retired 3 (0 / 2 / 1) 0
18.plugin-manifest-contributes-routes-retired 6 (1 / 5 / 0) 1
18.plugin-manifest-dead-containers-retired 3 (0 / 3 / 0) 0
18.plugin-manifest-kind-globs-retired 2 (0 / 2 / 0) 0
17.plugin-manifest-loading-retired 2 (0 / 2 / 0) 0
17.plugin-runtime-family-retired 5 (0 / 5 / 0) 0
18.plugin-security-scan-result-surface-retired 6 (0 / 6 / 0) 0
18.plugin-security-scanner-retired 3 (0 / 3 / 0) 0
18.ui-cloud-connection-widgets-unknown-keys-refused 3 (0 / 3 / 0) 0
18.ui-form-field-length-malformed-refused 8 (2 / 6 / 0) 0
18.ui-form-field-precision-scale-integer-refused 4 (1 / 3 / 0) 0
18.ui-form-view-predicate-features-root-refused 2 (0 / 2 / 0) 0
17.ui-interaction-config-family-retired 7 (1 / 6 / 0) 0
18.ui-list-view-groupbyfield-padded-refused 1 (0 / 1 / 0) 0
18.ui-list-view-grouping-field-padded-refused 2 (0 / 2 / 0) 0
18.ui-mcp-connect-agent-unknown-keys-refused 5 (0 / 5 / 0) 0
17.ui-notification-action-embed-config-retired 8 (0 / 8 / 0) 0
18.ui-object-grid-page-size-positive-integer-refused 4 (0 / 4 / 0) 0
18.ui-react-list-view-binding-aliases-retired 2 (0 / 2 / 0) 1
18.ui-record-blocks-unknown-keys-refused 3 (0 / 3 / 0) 0
18.ui-reference-rail-unknown-keys-refused 2 (0 / 2 / 0) 0
17.ui-widget-i18n-family-retired 14 (2 / 11 / 1) 0
four entries with no site: plugin-version-semver-2-0-0, ui-action-undoable-unfulfillable-refused, ui-bulk-action-param-unknown-keys-refused, ui-report-joined-container-selection-refused 0 0
total, 28 entries 111 (7 / 97 / 7) 2

Every 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#N ids were read from objectstack-ai/objectui; bare ids from this repository.

cited what it decided (read) how the text now carries it
#3733 The pruned cached field key: the parse succeeded and the removed key was dropped without a word; the orphan schema was deleted. "an earlier field-key prune measured exactly that — the parse succeeded and the removed key was dropped without a word"
#3950 Removed the plugin sandboxing / integrity / approval config nothing read: an exported schema with no consumer is read as a capability. "the lesson of the unwired plugin sandboxing / integrity / approval config …: an exported schema with no consumer is read as a capability", and the plugin-runtime "earlier removal of this module's discovery/sandbox config island"
#4001 Maintainer, 2026-08-03: every authorable surface refuses an unknown key (strict), in the v17 window, measured file by file for an authoring door. "the component-props unknown-key gate (an authorable surface refuses a key it does not declare …)"; "the v17 unknown-key strictness sweep (its ui/ batch 14)"; "the batch of the v17 unknown-key strictness sweep that measured this file as having no authoring door"
#4115 Ruling A: an objectui symbol named like a spec export must import it, or take a name of its own (or an allowlist row), enforced by a CI guard. "renamed off the spec's names under objectui's rule that a symbol named like a spec export must import it or take a name of its own"
#4484 findStream removed 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 The dual-source cleanup: 52 names declared twice across entry points, taken to 0. "the dual-source cleanup removed the ./ui copies …"
#4583 The datasource capability flags were dead; readOnly was 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. "(the lesson of the datasource capability flags: readOnly was precisely validated and read by nothing, while a shipped example called a datasource a read replica and wrote through it)"
#4610 Removed the ./ui Notification / NotificationConfig copies (zero import sites measured); the ./api inbox row stayed live. "the dual-source cleanup removed the ./ui copies of NotificationSchema / NotificationConfigSchema (the same names declared differently on other entry points)"
#4653 Maintainer ruling A, 2026-08-02: converge activationEvents on the kernel's structured { type, pattern } shape, re-exported from studio. "once the kernel and studio copies had converged on the kernel's structured { type, pattern } shape"
#4657 Retire both activationEvents keys (REMOVE, v17 window): kernel tombstone, studio strict refusal, the orphan schema deleted. "Both keys took ADR-0049's REMOVE answer, not ENFORCE, while protocol 17 was still unreleased"; its id sentence in plugin-runtime is covered by the entry id plugin-activation-events-retired
#4834 Maintainer, 2026-08-03: REMOVE the rest of the plugin-runtime family; hot loading returns with its implementation, if ever. "The maintainer's ruling of 2026-08-03 is that decision, answered REMOVE: …"; "the maintainer's REMOVE ruling on the rest of the plugin-runtime family"; plugin-runtime-family-retired by id elsewhere
#4875 The health-check timeout guard is cleared when the race settles, and deliberately not unref'd (an unref'd guard can swallow the timeout). "its guard timer (kept ref'd while the race is undecided, cleared the moment it settles)"
#4910 Inbound rate limiting was built from a new seam: a server: key that carries only the keys its executor consumes. "the way inbound rate limiting came back, as a new key carrying only what its executor consumes"
#4914 Maintainer, 2026-08-04: REMOVE manifest.loading, with a hard precondition of a clean cloud and objectui bare-name sweep. "the maintainer ruled REMOVE on 2026-08-04, on condition that a bare-name sweep of cloud and objectui came back clean first"
#4938 Maintainer, 2026-08-04: retire HttpServerConfig's seven unreachable keys with their container. "the HttpServerConfig retirement (seven keys no runtime read and no authoring door reached, retired with their container)"
#4988 Maintainer, 2026-08-04: retire the five ui interaction files; touch, dnd, keyboard and motion are renderer built-in behaviour, offline belongs to a sync engine. "The 2026-08-04 ruling retired the family — touch, drag-and-drop, keyboard and motion are renderer built-in behaviour and offline belongs to a sync engine …"; ui-interaction-config-family-retired by id in the widget entry
#5015 REMOVE NotificationActionSchema / EmbedConfigSchema, 2026-08-04: a no-door dead surface retires implementation-first, as three same-shape rulings that week had decided. "left the disposition to ADR-0049's enforce-or-remove, which came back REMOVE on 2026-08-04: a dead surface with no authoring door retires implementation-first, …"
#5021 Maintainer, 2026-08-04: retire all nine unconsumed theme token groups; theme-driven typography is not near-term. "the theme-token retirement (theme-driven typography is not a near-term capability, so nine token groups nothing consumed were retired)"
#5040 Build the declarative ApiEndpoint executor in 17.x; the v17 loud refusal of a non-empty apis: becomes execution. "live since protocol 17, once the declarative endpoint executor was built and the loud refusal of a non-empty apis: became execution"
#5055 Maintainer, 2026-08-06: retire the doorless widget and i18n shapes (8 of 9 widget sites; FieldWidgetProps kept). already stated by the entry ("The 2026-08-06 ruling weighed …"); the trailing id is dropped
#5068 Maintainer, 2026-08-05, direction A: parse a component's properties against its ComponentPropsMap row by type, at publish and lint; a type with no row is skipped because the type union is open. "the props gate's dispatch (it parses properties against the type's row at publish and lint time, and skips a type with no row because the type union is open)"
#5781 Correction: objectui did re-export the ./ui notification names; the removal stands. "falsified for objectui, which re-exported both names, … the removal itself stands"
#6011 Maintainer: close the ctx.user roles alias now, no window. "actor-user-roles-to-positions (the ctx.user roles alias, closed at once on the maintainer's word rather than given a window)"
#7751 Maintainer, 2026-08-12, direction A: the object-* blocks get ComponentPropsMap rows, key sets from renderer read points. "the shape it was given when the object-* blocks first got ComponentPropsMap rows measured from their read points"
#8321 Field.scale / precision refuse non-integer and negative values (int().min(0)). "(that surface tightened first, to a non-negative integer)"; "after that surface converged on z.number().int().min(0)"
#8691 A strict record:reference_rail row, key set from the renderer's read points. "the class already closed for record:reference_rail …"; "after the rail was given its strict row"
#8744 Strict rows for record:alert / record:quick_actions / record:history. "… and then for record:alert / record:quick_actions / record:history, each by declaring a strict ComponentPropsMap row"
#11168 The kind-registration log names the declared id; the kind bucket is reachable through GET /metadata/:type; globs had zero readers. "Measured by the engine-lane fix that made kind registration log its declared id (which also found the kind bucket itself reachable …)"
#11169 Maintainer, 2026-08-24 (「接受你的建议。」): remove globs through the full ADR-0049 ceremony. "maintainer ruling 2026-08-24 (「接受你的建议。」) …: remove, through the full ADR-0049 ceremony"
#11327 The doc-correction half of the routes ruling (2026-08-22, 「接受所有」, Option B): four author-facing sites redirected to the imperative http.server mount. "the author-facing corrections landed FIRST (the skill's decision table, the dispatcher protocol doc, ADR-0088:40 and app.mdx, each redirected to the imperative mount)"; the ruling sentence states Option B's content
#11566 Maintainer, 2026-08-24: maxLength → z.number().int().min(1). "maxLength by the maintainer's 2026-08-24 ruling"; "tightened first, to a positive integer, by maintainer rulings"
#11575 Strict, empty rows for cloud-connection:panel / marketplace:installed-list. "the strict, empty cloud-connection:panel / marketplace:installed-list rows closed the previous two"
#11825 Maintainer, 2026-08-25: retire the declarative AdvancedPluginLifecycleConfig container; the classes stay a host-driven library. "which is why the maintainer retired its declarative config container on 2026-08-25 and kept the classes as a host-driven library"; "The maintainer's 2026-08-25 keep of the host-driven library still stands"
#11852 Both failure routes (returned, thrown or timed out) funnel into one failure step: one counter, one threshold comparison. "the two failure routes — a returned failure and a thrown or timed-out check — sharing one failure counter and one threshold comparison"
#11949 Maintainer, 2026-08-25, option B: minLength → int().min(1), zero refused. "minLength by the 2026-08-25 one, which refused zero too"
#11955 successThreshold binds from every status that records a failure. "The fix that made successThreshold bind from every status that records a failure made that MORE convincing"
#12174 The form-field row keys are live, so they were shape-tightened in place on the object-field templates. "The form-field row still carried the object field's old shape … The row keys are LIVE … The schema now refuses …" (unchanged tail)
#12269 Closure A: packages/mcp gets its own canonical-envelope gate. "door 3 of the canonical-envelope gate @objectstack/mcp was given for its shipped page"
#12340 Maintainer, 2026-08-26: retire the 'disk' / 'distributed' state strategies (silent memory fallbacks) and distributedConfig; "a vocabulary of nothing is not a vocabulary". "the 'disk' / 'distributed' state strategies that fell back to memory in silence"; "(ruled 2026-08-26: a vocabulary of nothing is not a vocabulary)"
#12400 The cloud leg for capabilities / configuration / extensions measured clean at cloud 15f55df. "dispatched once the cloud half of the census below came back clean" (the census clause names 15f55df)
#12428 Refuse/retire: startWatching throws instead of logging success; watchPatterns is tombstoned because a key leaving a surviving def has no route-3 exit. "the file-watching placeholder whose startWatching logged success while watching nothing"; "for the reason the file-watching retirement recorded"
#12665 Implement the maintainer's 2026-08-27 option B: a form view may not name features.* in a predicate; refused at authoring. "Ruled by the maintainer on 2026-08-27 (option B — vocabulary narrowing: a form view may not name features.* in a predicate, and the authoring door refuses it loudly)"
#14791 Maintainer, 2026-09-07: retire the objectName / viewType aliases now, no window. "(2026-09-07)" in the sentence that states the retirement
#15513 Maintainer, 2026-09-05: retire the incident-response, training and change-management families whole; not roadmapped. "retired whole, with the training and change-management families (maintainer ruling 2026-09-05: not roadmapped, so retired rather than marked experimental …)"
#15930 Retired PluginSecurityScanner; no replacement, repair refused. "the scanner retirement recorded as plugin-security-scanner-retired. That retirement removed PluginSecurityScanner …"
#15932 Maintainer, 2026-09-07 (「同意」): retire the scan-result family and securityScan. "maintainer ruling 2026-09-07 (adopted verbatim 「同意」): retire the scan-result family and its securityScan sibling, because once the scanner was gone nothing so much as imported their types"
#16526 Maintainer, 2026-09-07, option A: cloud does not re-host the consumer-less control-plane files; they are deleted. "(ruled 2026-09-07: cloud does not re-host the control-plane files it never consumed)"
#17360 Ruling C: refuse a padded grouping field name at the producer (not a trim). "Ruled by the maintainer on 2026-09-10 (「其他同意」): refuse at the producer."
#17499 Refuse a padded groupByField on kanban, gantt and timeline. "The same padded-name defect the grouping-level narrowing … refused, on the axis that one scoped out by name, and given the same refusal."
#19046 Bound the grid component arm's page sizes to positive integers. the sentence now opens "This door still carried the shape …"; the declaration half is stated in the entry
objectui#3161 Batch 7/8 of the objectui burn-down under the #4115 rule (renames). folded into the #4115 sentence
objectui#3169 objectui stopped declaring symbols under names the spec owns; its rename tripwire fails both ways. "the rename tripwire objectui added when it stopped declaring symbols under names the spec owns"
objectui#3289 (PR) @object-ui/fields' validation slot renamed onto the spec's error and connected to its producer. "an objectui fix of 2026-08-03, made to follow the spec, renamed …"
objectui#5595 The console FormPage honours the form's own maxLength override. "(fixed so that a form's own bound wins over the object's, as its docstring promised)"
objectui#5898 The form-view bridge maps every spec key or explains why not. "mapField, which maps every spec key or explains why it does not"
objectui#6262 The measured features.* asymmetry, and the ruling record. folded into the #12665 sentence
objectui#7347 The measured padded-grouping failure, and ruling C's record. folded into the #17360 sentence
objectui#9853 Measured pagination.pageSize: 0 reaching ObjectGrid. "an objectui grid measurement found that …"
objectui#9896 (PR) A non-positive pageSize is refused at all three grid read points. "objectui's grid plugin repaired the consumer half — it now refuses a non-positive page size at all three 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: a name( census over registry.ts is identical before and after, so textual call-spelling ratchets read the same.

Pin — packages/cli/test/migrate-meta-engine-guidance.test.ts, widened

The stage-1 pin now selects every entry whose id starts with a covered prefix: engine-, ui- or plugin-. 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 includes surface. Anti-vacuity:

  • the derived set must contain all 29 rewritten entries (5 engine-, 10 plugin-, 14 ui-), and every covered prefix must select at least one entry;
  • presence in stdout is asserted before cleanliness;
  • the detector is exercised on both sides first (lit on 4 and 5 digits, dark on 3, 6 and ADR-0112).

The chain reports every semantic entry of every crossed hop, whatever the stack authors (applyMetaMigrations maps step.semantic straight 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 on surface

From committed state, HEAD 8a1076b0a6, with scripts/ablation-replace.mjs in wrap mode and scripts/ablation-dist-preflight.mjs gating each leg. The bundle is built from the generated registry.ts, so that is the file mutated (stage 1's attempt 2 records why the entry file is the wrong target).

  • Mutation. In registry.ts, the surface of ui-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, blob ab26922f → 9a200491.
  • Mutate leg. Spec build under the lock: command-exit 0. Preflight: marker present in 4 built files. Pin: red, 1 failed | 2 passed — ui-react-list-view-binding-aliases-retired: the printed guidance cites a tracker id: expected '#11284' to be undefined.
  • Restore. Tool-proven: blob ab26922f == HEAD, git diff HEAD empty.
  • Restore leg. Spec build under the lock: command-exit 0. The --absent preflight found the marker in none of 222 built files, with the working tree clean against HEAD. Pin: green, 3 passed.

Verification

Final head 071dc7fc1d unless a line says otherwise.

  • Pin and its neighbour: pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 test/migrate-meta-engine-guidance.test.ts test/migrate-meta-default-range.test.ts gives Test Files 2 passed, Tests 10 passed | 1 skipped (the skip is the default-range file's own pre-existing skipIf).
  • Spec tests that read these entries or the registry: pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 src/migrations plus the 14 other spec test files that read MIGRATIONS_BY_MAJOR or one of these entries' text (plugin-runtime-tier-truthful-text, interaction-config-retirement, widget-i18n-retirement, …) and scripts/build-schemas-check-mode.test.ts: Test Files 15 passed, Tests 438 passed.
    • ⚠️ The first src/migrations run, at 8a1076b0a6, went red, 2 failed: migrations.test.ts pinned the two tracker numbers in ui-notification-action-embed-config-retired's reason. The second commit re-pins the same guard on the sentences that now carry the lesson (see Acceptance notes).
  • CLI unit: src/utils/spec-release-changes.test.ts 6 passed; test/vitest-tiers-partition.test.ts 22 passed (at 8a1076b0a6; no CLI file changed after it).
  • Call-spelling census that reads registry.ts: pnpm --filter @objectstack/driver-sql exec vitest run --maxWorkers=2 src/sql-driver-query-signature.test.ts gives 15 passed.
  • Typecheck: pnpm --filter @objectstack/spec typecheck exits 0. pnpm --filter @objectstack/cli typecheck exits 0 (at 8a1076b0a6), and its test layer holds the recorded 3 files / 28 errors, unchanged.
  • Build: pnpm exec turbo run build --filter="@objectstack/cli^..." --concurrency=2 gives 55/55 (at 8a1076b0a6); @objectstack/spec rebuilt at the final head, command-exit 0.
  • Gate families: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derives 88 families at 071dc7fc1d (the same set as at 8a1076b0a6). --ran over the recorded exit codes reads 88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0. They include check: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-major and check:dual-build-cjs-loads (104 require entry points across 66 packages load).
    • Union discipline: the first pass started before the second commit, so the 21 families that started before that edit were re-run at the final head, and the 6 that refused on a spec dist stamp made stale by that edit (5 × exit 3, check:generated exit 1 naming api-surface stale by stamp, check:dual-build-cjs-loads exit 3) were re-run after rebuilding spec at the final head. The reconciled list takes each family's latest run.
  • Lint (a proven narrowing, not the repo-wide run, which is CI's): eslint --no-inline-config --format json over the 27 changed .ts files reports 27 files, 0 errors, 0 warnings.
    • The population is read from eslint.config.mjs: **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus NEVER_LINTED, and all 27 are in it (no file-ignored warning).
    • Invariance: the config enables no type-aware linting (no parserOptions.project, no typed rules), so a text edit cannot move the verdict on a file it does not touch.
  • Mergeability: a driver-free bare clone's merge-tree --write-tree of this head against origin/main 7b1e4a4871 (six commits past the base, one of them adding 25 semantic entries) exits 0 with no conflict. The census over that merged tree reads engine- 0, plugin- 0 and ui- 0 across 18 entries: main's new ui-report-joined-chart-retired carries tracker ids only in comment lines.

Acceptance notes

  • Dead ids, rewritten from the code on 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 what main records, and anything it could not confirm was dropped:
    • #10627 / #10724 / #10726 / #10812 / #11328 (contributes routes and dead members): packages/spec/src/kernel/manifest.zod.ts carries all ten retiredKey() 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.ts and metadata-plugin.zod.ts point at the imperative http.server mount. Dropped: "triage graded 2026-08-21" and the 2026-08-24 date on the routes entry's cloud sentence (the cloud sha 5b5925a is kept: #12400's body corroborates it). The routes ruling's 「接受所有」 and Option B are kept: #11327's body records them.
    • #11332 (dead containers): the three manifest.capabilities / configuration / extensions tombstones on main. Dropped: "triage graded 2026-08-23".
    • #11284 (react-tier convergence): packages/spec/src/ui/react-blocks.ts records 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 and security-scanner-retirement.pin.test.ts record the 2026-09-05 ruling, the removed class and types, and repair refused. Dropped: "ruled A: retire in three surfaces" and the batch numbers, which main does not state.
  • A bare id that names the wrong card. The widget / interaction entries' "(#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 on objectui#2561: keep the @objectstack/spec/ui re-exports type-only. It was rewritten from objectui's packages/types/src/__tests__/p2-spec-exports.test.ts at objectui main, which records that decision.
  • #3896, cited as "follow-up" and "close-out". #3896 itself is the sharing-rule criteria REST bypass and says neither. The "follow-up" is PR #3950 (its title says so); the "close-out" is the inert-key sweep recorded on main in docs/protocol-upgrade-guide.md and the strictness ledger. Both sentences now say what those decided.
  • Cross-repo ids. Ten sites are spelled objectui#N (nine) or "objectui PR " plus a number (one). The stage-1 census counted them by number with the rest; they were read from objectstack-ai/objectui (all 200), not from this repository, where the same numbers name unrelated cards.
  • One bare-number spelling went too. The scan-result entry spelled a deleted card as "issue" plus its number, twice, without #. The instrument cannot see it, but it is a tracker number shown to the author, and it sat in a rewritten sentence.
  • In-flight entry the widened pin will hold. Open PR feat(spec)!: form layout accepts only vertical | horizontal — the inline and grid arms retired (#20221) #20262 adds 18.ui-form-layout-inline-grid-retired.ts with one tracker id in its entry text (read from the PR's file list: one # plus five digits). It is not on main, so it is untouched here. Once this lands, ui- is covered: feat(spec)!: form layout accepts only vertical | horizontal — the inline and grid arms retired (#20221) #20262 must rewrite that site before it lands, or the pin goes red on its merge ref.
  • Comment lines are untouched. 18.ui-list-view-groupbyfield-padded-refused.ts keeps its // comment citing a number; comment and docblock lines are the sibling card's surface.
  • Generated projections (spec-changes.json, docs/protocol-upgrade-guide.md) are regenerated, as in stage 1; their --check legs are green.
  • One file beyond the claim's surface: packages/spec/src/migrations/migrations.test.ts. Its guard on ui-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. A git grep of 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's granted-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.ts is 333, the two projections are 52 (spec-changes.json 32, the upgrade guide 20), the widened pin is 85, migrations.test.ts is 6 and the changeset is 21.


Generated by Claude Code

…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
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 2 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/spec-changes.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/plugins/packages.mdx (via /metadata/:type (route, a path literal in reason; a path literal in semantic))
  • content/docs/protocol/kernel/metadata-service.mdx (via /metadata/:type (route, a path literal in reason; a path literal in semantic))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-3.mdx (via /metadata/:type (route, a path literal in reason; a path literal in semantic))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/spec-changes.json) — pages documenting those are invisible to this run
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json de091b50e67aec12764eccc18a87b1b4259573e3 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 563ad541d569e8f02ad373a28f363c81e5bbea74 — the merge of head 071dc7fc1d156b8af281c9276af6519178b10110 into base de091b50e67aec12764eccc18a87b1b4259573e3, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs de091b50e67aec12764eccc18a87b1b4259573e3 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Sep 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: 105/105 CONTRACT_REVIEW_TIER
Head-sha: 071dc7fc1d156b8af281c9276af6519178b10110

① Derived judgments

  1. Faithfulness — PASS. PR re-read: head still 071dc7fc1d (2 commits on base 2aa25efb4e). All 70 distinct removed ids fetched by single REST reads (60 objectstack, 10 objectui; body + comments). Every replacement (6), acceptanceCriteria (7) and surface (2) site judged; every reason site judged by id against its source. Rulings match: spec 双源清账 C5:ActivationEventSchema(./kernel ≠ ./studio)—— 1 条 #4653 A { type, pattern } 2026-08-02; ADR-0049:两份 activationEvents 声明四仓零 reader —— declared-but-unenforced,且 studio 侧 z.string() 零校验 #4657 v17 pre-mode → remove; ADR-0049:plugin-runtime.zod.ts 剩余家族(DynamicLoadRequest / DynamicUnloadRequest / DynamicPluginResult / PluginSource / DynamicPluginOperation)四仓零 runtime consumer —— enforce-or-remove 悬置待裁 #4834 2026-08-03 REMOVE with the "nothing built, nothing pulls, vocabulary enters with the implementation" wording; docs/spec: PLUGIN_STANDARDS.md §5.1/§5.2/§5.4 把 Hot Reload 与 Plugin Isolation 标为 ✅,但 PluginHotReloadSchema / PluginSandboxingSchema 全仓零 runtime reader(ADR-0049) #4914 2026-08-04 remove on the cloud/objectui bare-name precondition; system/HttpServerConfigSchema 九个键全仓零 reader,且没有任何作者面入口(defineStack 无 server:,不在 config-schema) #4938 seven keys + container (two of nine activated via server:); ui/ 五个交互配置文件(22 个 z.object 站点)没有任何承载键:实测「无授权门」,按 ADR-0049 定去留 #4988/主题引擎发出的 9 组 CSS 变量零消费方(--font-size-* / --z-* / --duration-* …):ADR-0049 该判去留 #5021/ADR-0049 定去留:NotificationActionSchema / EmbedConfigSchema 实测没有授权门(#4001 批 14 改判的两个站点) #5015/ADR-0049 enforce-or-remove: ui/widget.zod.ts 全文件 + ui/i18n.zod.ts 五个形状实测无门(#4001 批 16 改判) #5055 dates and content; 「v17」入站 rateLimit 接执行:ApiEndpoint / HttpServer 的 RateLimitConfig 推导为 runtime token bucket 配置,dispatcher 生效(#4686 拆向之一) #4910 server: carries only consumed keys; spec: maxLength is authorable on every field type and validated as no more than a number — maxLength: 0 and maxLength: 12.5 parse cleanly #11566/spec: minLength still has the pre-#11566 defect pair — validated as no more than a number, authorable on every field type #11949/spec: Field.scale accepts meaningless declarations (2.5, -1) — now that scale is enforced, malformed declarations should be refused at authoring time #8321 shapes and dates; contributes.kinds.globs is declared-but-unenforced: nothing reads it, and the file-type discovery it advertises globs filePatterns off a registry contributes.kinds does not extend #11169 「接受你的建议。」; docs(spec): redirect the contributes.routes recommendation to the imperative http.server mount #11327 「接受所有」 Option B, four sites; spec: form-view predicates must loudly reject the features.* root — ruled vocabulary narrowing (from objectui#6262) #12665 Option B 2026-08-27; [finding] The #11284 convergence shipped only its producer half: the react-blocks contract deprecates objectName / viewType on ListView in favour of data={{ provider: 'object', object }} / type, and the lint blesses that spelling — but objectui's ListView reads neither, so the canonical spelling validates green and renders an empty list #14791 2026-09-07 no window; spec: the rest of the incident-response, training and change-management families — every remaining key and all fifteen defs — has zero readers; whole-def enforce-or-remove is the open question left after #14477 #15513 "none of the three is roadmapped" is verbatim in the ruling; spec: the plugin-security-advanced scan-result surface has ZERO consumers after #14919 — 22 published authorable rows with no author and no parser #15932 「同意」; [Decision] Does cloud re-create the four consumer-less control-plane schemas, or does step 3 just delete them? (follow-on to the #16325 ruling) #16526 A; spec: GroupingFieldSchema.field accepts a padded field name that three objectui readers bucket wrong — refuse non-trimmed names at the producer (ruling C on objectui#7347) #17360 C 「其他同意」 2026-09-10; [finding] groupByField accepts a padded field name on Kanban (required), Timeline and Gantt — the sibling axis #17360 scoped out #17499 kanban/timeline/gantt; [runtime] ctx.user 的 roles 别名(值是 positions)没有关闭日期 —— #5613 给 ctx.session 装了迁移窗口,同名同值的 ctx.user 面仍是无限期别名(observation) #6011 「现在就关闭」; IDataDriver.findStream 没有任何调用方,两个 driver 的实现还正好做了它承诺要避免的事(ADR-0049 enforce-or-remove) #4484 no tombstone because nothing parses a driver; [finding] packages/mcp ships a kernel-reaching Page with no canonical-envelope gate — now discoverable, still unaudited #12269 closure A door 3; SDUI 组件 props 没有解析闸门:PageComponent.properties 是开放 record,ComponentPropsMap 的 29 个站点从不被 parse(#4001 批 17 的 no gate 判定) #5068 A "publish / lint, unregistered type skipped, open union"; 未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001 batches measured each file's door before tightening; objectui#3169/chore(lint): org-identifier authoring guard for deprecated session.tenantId (#3280 follow-up) #3289/fix(service-automation): 从未 seal 的引擎首次执行 flow 时告警一次 (#4792) #5595/build-schemas.ts 检查 (c) 的「墓碑已满 2 个 major」证明仍用叶名匹配 —— 无关簇的登记可以替一次退休提前起算 #5898/docs-audit: a PARTIAL ledger parse is silent — one backtick-quoted route: drops a row from the bridge population with no verdict and exit 0 #9896/[umbrella][security] 授权全生命周期缺口地图 — 包开发 → 生产上线(P0–P3) #2561 as stated. No invented, strengthened or weakened decision. One non-blocking soft spot: [finding] HotReloadConfig.stateStrategy 'disk'/'distributed' are silent memory fallbacks, and distributedConfig has zero readers — declared≠enforced inside the KEPT host-driven library #12340's "a vocabulary of nothing is not a vocabulary" was the dev's escalation argument the maintainer accepted (「接受反转」), now phrased as "ruled".
  2. Code-derived sentences — PASS. All eight ids 404 on both issues/ and pulls/ (I probed all eight; 200 control docs(spec): redirect the contributes.routes recommendation to the imperative http.server mount #11327). Sole non-spec manifest.contributes read: packages/objectql/src/engine.ts:6355-6357; tombstones packages/spec/src/kernel/manifest.zod.ts:566-834 (routes :717); http.server redirect packages/spec/src/api/plugin-rest-api.zod.ts:32-45, kernel/metadata-plugin.zod.ts:118-126; [seam→cloud] Measure the cloud leg of the remaining plugin-manifest keys (runtime, integrity, capabilities/configuration/extensions, structured permissions) — unblocks #11330 #11331 #11332 #11333 #12400 body corroborates cloud 5b5925a and 15f55df; [Decision] Does react-blocks.ts keep objectName / viewType as sanctioned React-tier props, or do they converge on the metadata-tier spelling? — the carrier objectui#2890's last phase has never had #11284 → packages/spec/src/ui/react-blocks.ts:40-42,109,295; packages/core/examples/phase2-integration.ts is the sole composer of PluginSecurityScanner, which FOLLOW-UPS.md already records as exported dead code with 3 of 5 scan methods empty stubs — repair it, or retire it as #4939 did to its neighbour? #14919 → packages/core/src/security/index.ts:80-92 and security-scanner-retirement.pin.test.ts:10-16 (2026-09-05, no replacement, repair refused); objectui#2561 → objectui packages/types/src/__tests__/p2-spec-exports.test.ts:27-34 (decision (a), type-only); POST /data/sharing/rules 绕过 SharingRuleSchema:criteria 缺失或拼错静默变成"共享该对象全部记录",与 ADR-0049 "never seeded as a permissive match-all" 直接冲突 #3896 → PR refactor(spec)!: remove the plugin sandboxing / integrity / approval config that never existed (#3896 follow-up) #3950's title "(POST /data/sharing/rules 绕过 SharingRuleSchema:criteria 缺失或拼错静默变成"共享该对象全部记录",与 ADR-0049 "never seeded as a permissive match-all" 直接冲突 #3896 follow-up)" and docs/protocol-upgrade-guide.md:172 "widget 'performance' removed (POST /data/sharing/rules 绕过 SharingRuleSchema:criteria 缺失或拼错静默变成"共享该对象全部记录",与 ADR-0049 "never seeded as a permissive match-all" 直接冲突 #3896 close-out)". Nothing an author needs was dropped; the dropped batch/summon numbers are provenance, not prescription.
  3. Nothing else moved — PASS. Evaluated all 24 entries at base and head: only the three prose fields differ, plus surface on exactly the two named entries; id identical; these entries carry no from/to/matcher keys. Head scan of the four author-shown fields: no #NNNN, no issue NNNN / PR NNNN; a bare 4–5-digit sweep finds only "4742 nodes". Comment lines untouched.
  4. Generated projections — PASS. registry.ts evaluated under type-stripping: 267 semantic entries load; the 24 entries' four fields are byte-equal to the entry files; no engine-/ui-/plugin- entry (5/17/11) carries a tracker id. spec-changes.json (268 rows, protocol 17 only) and the guide carry the six protocol-17 entries' text exactly; protocol-18 entries are not projected by design. CI: check:migration-registry/check:generated (Lint & Repo Gates) success; check:spec-changes/check:upgrade-guide (Type Check · source gates) success.
  5. Widened pin — PASS. COVERED_PREFIXES engine-/ui-/plugin-; printedBlock matches meta.ts:524-525 and includes surface; presence asserted before cleanliness; REWRITTEN floor of 29 (5+10+14) plus per-prefix non-emptiness; detector lit on 4/5 digits, dark on 3/6 and ADR-0112. Queue tier: no .e2e/.live name (vitest-tiers.ts:89-116), integration project by spawn. Note: TRACKER_ID cannot see a bare "issue NNNN" spelling; the one such site was removed by hand.
  6. migrations.test.ts — PASS. The two regexes now pin the lesson sentences (dual-source cleanup removed the ./ui copies, objectui, which re-exported both names), both present in the head reason; /NotificationConfigSchema/, /falsified/, /removal itself stands/ unchanged, so the anti-vacuity and no-un-retirement guards stand.
  7. Changeset .changeset/20233-ui-plugin-migration-guidance-tracker-free.md — PASS. Every sentence true; @objectstack/spec: patch right for prose-only text; Clause-②: no on its own line, no arm.

② Semver level

patch for @objectstack/spec, Clause-②: no — no accept set widens or narrows and no public symbol moves; the line is in clause2-line.mjs's fixed spelling in both body and changeset.

③ Boundary flags

Implemented-by: claude/issue-20233-migrate-meta-tracker-free-stage-2
Reviewed-by: session_01QcAS3qiYYZNezaxZxaUdMV

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 27, 2026 22:11
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit dfd8e39 Sep 27, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20233-migrate-meta-tracker-free-stage-2 branch September 27, 2026 22:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants