Skip to content

feat(spec)!: retirement kits whose conversion retired at or below 16.x age out; the strict unknown-key refusal names the protocol upgrade guide - #26599

Merged
os-zhuang merged 32 commits into
mainfrom
claude/project-thread-q3olv3
Oct 27, 2026
Merged

os-zhuang merged 32 commits into
mainfrom
claude/project-thread-q3olv3

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #25767
Supersedes #26138

Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: follow-up #26303
Release: 18.3

维护者速读(草稿)

  • 改了什么: 按 spec: retire tombstones, conversions and migration entries older than one protocol major; a strict unknown-key refusal with an upgrade-guide link takes over (maintainer ruling D2 = A) #25767 的裁决(6434038284):≤16.x 的退役套件(50 个 D2 conversion 和它们的 fixture、strict 容器里的 55 个墓碑、authorable-surface 上的 61 行 [RETIRED])从代码里删掉。被删的键由 strict schema 当未知键拒绝,错误里多一句话,指向协议升级指南。迁移链不动:支持底线仍是 16,第 17 步和它的 159 个条目文件都在,os migrate meta --from 16 照常从 16 重放到 18。
  • 为什么改: 每个 major 都在累积墓碑和 conversion,每张 spec 卡都要为它们买单。升级者需要的处方放在升级指南和当时的 release notes 里就够了。链的底线是发版策略,要另行裁决,不随套件老化一起动(feat(spec,cli)!: retire the ≤16.x retirement kits; the strict unknown-key refusal names the protocol upgrade guide #26138 原先连链一起砍,已按裁决撤回)。
  • 风险与回滚: ① 仍写着 ≤16.x 键的源码不再拿到逐键处方,只拿到未知键拒绝和链接。② sys_metadata 里仍是这种形状的行,读取时不再被改写,要在 18.2 或更早版本上用 os migrate meta --stored --apply 规范化一次(裁决把这条一次性提示放进 18.2 的 release notes)。③ 第 17 步的 graduated 列表少了 45 个已删 conversion 的 id,所以 --from 16 不再替你改这 45 类写法;步本身和它的语义条目都还在。回滚就是 revert 本 PR,不写数据,没有不可逆动作。
  • 先看: ① packages/spec/src/migrations/registry.ts.template 第 17 步的 conversionIds(删掉 45 个 id 的那一段);② docs/adr/0087-metadata-protocol-upgrade-contract.md 末尾的 2026-10-26 addendum 和 D2 窗口那条修订;③ packages/spec/scripts/build-schemas.ts 的 proof 1(一个 major 的时钟和 release-tag 定日期)。
  • 两分钟验证: cd packages/spec && pnpm exec vitest run src/migrations/ src/shared/retired-key-migrate-sentence.test.ts,预期全部通过:链的完整性钉子(每个 id 都是真 conversion)、从底线 16 的重放,以及新旧两种处方句式。
  • 需要你做的: 以 GOVERNED_APPROVERS 身份批准本 PR(Tier H:docs/adr/** 原地修订)。

What changes

Maintainer ruling 6434038284 on #25767 (2026-10-27, 「同意」) scopes the D2 = A ruling of 2026-10-25. It keeps kit aging and withdraws the chain cut, and the release target is 18.3. This PR is #26138's branch reworked to that scope. #26138 is closed as superseded (6434144688).

Kept from #26138 (the kit ages out):

  • The 50 D2 conversions with retiredAfter at or below 16.x (5 at 15.0.0, 45 at 16.1.0) are removed, with their fixtures.
  • The 55 strict-container tombstones already [RETIRED] at @objectstack/spec@17.0.0 are removed, along with 61 [RETIRED] lines of the authorable surface and their liveness rows.
  • The 58 strip-container tombstones of the same age stay, because deleting one would turn a refusal into a silent strip. The four inline-credential refusals are not retirements and are untouched.
  • The strict unknown-key refusal carries RETIRED_KEY_UPGRADE_GUIDE_SENTENCE (shared/protocol-upgrade-guide.ts) after its fix channels, keeping the strictObject 的 history 句夹在「哪个键错了」与「该写什么」之间,在单行 error 显示位上把修法推到 222 字符之后(#5762 实测) #5955 order.
  • The deletion gate ages a tombstone one major. Proof 1 dates a deleted line by an exact RETIRED_KEYS_BY_MAJOR row or by the previous major's release tag.
  • The console's own refusals in ui/packages/types cover the keys it used to inherit as tombstones: the five ListView keys and the three flat public-block spellings.
  • The lint, metadata-core, runtime, metadata-protocol, objectql, services and ui/** test follow-ups are kept.

Restored to origin/main (the step stays):

  • packages/spec/src/migrations/entries/**, including the 159 major-17 files, the README and the conversionIds links of the three protocol-18 entries.
  • registry.ts.template with MIGRATION_SUPPORT_FLOOR = 16, chain.ts, migrations.test.ts and semantic-relevance.test.ts.
  • packages/cli/src/commands/migrate/meta.ts and its twelve tests, and docs/adr/0142.
  • The floor prose in docs/protocol-upgrade-guide.md and content/docs/upgrading.mdx. The guide pointer gains one sentence saying where an aged-out kit's prescription lives.
  • ADR-0087's "permanent chain", "forever artifact" and 2026-08-01 amendments are withdrawn, and the D3 text stands.

Reworded:

  • ADR-0087: the status line, the D2 window bullet, the 2026-07-31 stored-row note and the addendum now say the kit ages out and the step stays.
  • The changeset: minor for spec, patch for lint and metadata-core. The cli is no longer touched in published code.
  • Retirement house sentences, which follow the conversion rather than the floor:
    • An edit a surviving step-17 conversion still applies (the twelve toMajor: 17, retiredAfter: 17.7.0 ones: the flow-node aliases and lifts, the mongo alias, the retry default, the sharing-rule full → edit rewrite, the header subtitle alias and the inline action params) goes back to main's "Run os migrate meta --from 16 …" sentence.
    • An edit whose 16.x conversion left says "This edit left os migrate meta with the protocol-16.x conversions: apply it by hand, or run the tool on a release that still carries the conversion", followed by the guide link.
    • No sentence names a floor of 17.

Acceptance notes

  1. Step 17's graduated list named the aged-out conversions. The ruling's split reading checked that no semantic entry links a removed conversion, and that holds. Step 17's own conversionIds list in registry.ts.template, however, named the 45 conversions retired at 16.1.0, so restoring it verbatim failed the chain's integrity pin ("every step references only real conversion ids").
    • The 45 ids leave that list, with a comment naming the ruling. The step, its 77 semantic entries and its retired-key and retired-def rows are unchanged.
    • --from 16 still replays 16 → 18, but it no longer applies those 45 edits. Each one's tombstone or guide entry names the hand edit.
    • The ADR addendum and the changeset say so.
  2. Pins re-pointed by hand, because each replayed an aged-out conversion:
    • The floor replay in migrations.test.ts now uses sharing-rule-access-level-full-to-edit and flow-node-crud-object-alias.
    • The cli cross-hop pairing pin in meta.report-order.test.ts now uses the sharing-rule conversion.
    • The --from 16 shape in migrate-meta-protocol-range.test.ts is now a sharing rule.
    • migrate-meta-write.test.ts takes feat(spec,cli)!: retire the ≤16.x retirement kits; the strict unknown-key refusal names the protocol upgrade guide #26138's protocol-18 fixtures at --from 16. Its aged-out compareTo control now says the conversion left, not the floor.
    • The three repo-project pins on RETIRED_KEYS_BY_MAJOR[17] go back to main.
  3. The optional simplification is not taken. The ruling allowed dropping the release-tag dating source if proof 1 could date every deleted line by its registry row. It cannot: 46 of the 61 deleted [RETIRED] lines predate RETIRED_KEYS_BY_MAJOR, and no row names them. The tag source and its four SKILL.md lines stay, at net zero lines (337 → 337).
  4. Stored rows. A sys_metadata row carrying a spelling whose conversion aged out is no longer rewritten at read. Canonicalize it with --stored --apply on 18.2 or earlier. app-hidden-to-unpublished is among the removed conversions, so the artifact door's refusal of it by id is removed as moot. The 18.2 release-notes notice is the ruling's part 3 and is not in this diff: content/docs/releases/ is release-owned.
  5. Residuals unchanged by this rework, as listed on the card:

Verification

All readings are at head 393c768582, which carries a merge of origin/main at add806c5a6.

Suites (local, under the verify lock)
  • pnpm --filter @objectstack/spec test: 728 files and 21889 tests passed, 1 todo.
  • pnpm --filter @objectstack/spec test:repo: 80 files and 1295 tests passed.
  • pnpm --filter @objectstack/cli exec vitest run --project unit: 337 files and 4904 tests passed. The integration tier is declared to CI.
  • metadata-protocol: 21569 passed and 19 skipped. runtime: 7423 passed and 19 skipped. objectql: 8789 passed. service-automation: 2708 passed. service-datasource: 975 passed. These ran at the tree of 76dad1ceeb. lint (7389 passed) and metadata-core (823 passed) ran at 002710c1dc. The later commits touch none of their files.
  • pnpm --filter @object-ui/types test: 393 files and 10696 tests passed.
  • cd ui && pnpm exec vitest run scripts/: 177 files passed and 1 failed. The failure is check-i18n-dead-keys "keeps every key it makes reachable OUT of both tiers", a 15 s timeout on the real-tree scan under full-suite load, the same on three runs. It passes when its file runs alone (check-i18n-dead-keys plus check-vi-mock-inherit: 217 tests passed), and the diff touches no i18n source.
  • Type-check of types, react, plugin-list, plugin-view, components, core, app-shell and console: all exit 0.
  • pnpm --filter @objectstack/dogfood test, the full battery at the final head: 300 files passed, 3 skipped. 2494 tests passed, 18 skipped. Lock verdict command-exit 0.
Generated artifacts and gates
  • pnpm --filter @objectstack/spec check:generated: all 14 artifacts current. node scripts/check-regen-pending.mjs: exit 0.
  • node scripts/gen-sdui-manifest-node.mjs then check-sdui-manifest: sdui.manifest.json byte-identical (sha256 1f6e1ccb925a…, 106 components).
  • ui check:spec-symbols: exit 0.
  • The derived gate sweep (dispatch-gates.mjs --commands): 150 derived, 150 run, 0 NOT-MEASURED. 149 exit 0, and 1 exit 1, which is pnpm --dir ui exec vitest run scripts/ with the timeout named above. Reconciled with --ran.
  • Merges of origin/main went through scripts/pm/os-regen-merge.sh. Each time the driver took main's side of a generated surface, the branch's own aged-out [RETIRED] lines were deleted again before gen:schema: three in data.json, one in system.json and five in api.json.

Generated by Claude Code

claude added 30 commits October 26, 2026 09:19
…the upgrade guide (#25767) [wip]

Work in progress, paused on maintainer instruction (token budget); not yet
gate-green. Done so far:

- conversions/registry.ts: the 50 retiredAfter-≤16.x conversions and their
  retired-key / retired-def tables are removed; chain floor rises 16 → 17
  (MIGRATION_SUPPORT_FLOOR), MigrationFloorError names the upgrade guide.
- 55 ≤16.x tombstones on strict containers deleted; retiredKey() tombstones
  on non-strict containers kept (a delete would strip silently).
- shared/protocol-upgrade-guide.ts: RETIRED_KEY_UPGRADE_GUIDE_SENTENCE,
  emitted by the strict unknown-key refusal after its fix channels and
  ahead of the history sentence (#5955 order).
- build-schemas.ts tombstone-age gate (registry row or previous-major tag
  proof); migrations registry.ts.template permanence docblock.
- ADR-0087 amendment, upgrade guide page, changeset; cli migrate meta
  support floor.
- Test pins rewritten for ai, automation, conversions, data, kernel,
  migrations, shared, security and most of ui.

Left: ui/view*, ui/dashboard*, scripts liveness pins; full spec test run;
cli floor-17 pins; dispatch gates; PR body and report.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
…-key refusal names the protocol upgrade guide

Maintainer ruling on #25767 (D2 = A): a retirement kit lives through the
protocol major after the one it retired in and leaves when the next major
is cut. At 18 that removes everything retired at or below 16.x — 50 D2
conversions and their fixtures, 55 tombstones on strict containers, the
159 major-17 migration entries and the chain's step 17 — and keeps the
17.x and 18.x kits for 19 to repeat the move.

- A removed key meets the strict unknown-key refusal, which now carries
  one more sentence after its fix channels and before the history
  sentence: keys retired before the previous major are no longer
  prescribed here — see the protocol upgrade guide
  (`RETIRED_KEY_UPGRADE_GUIDE_SENTENCE`, `PROTOCOL_UPGRADE_GUIDE_URL`).
- `MIGRATION_SUPPORT_FLOOR` is 17: `os migrate meta --from 16` and below
  answer `MigrationFloorError`, naming the floor and the guide; the chain
  is one hop (18). The 17.7.0 load-path conversions stay registered for
  the data-at-rest seams but have no replayable step; the three
  protocol-18 entries that linked one drop the link and name the hand
  edit (the F4/F5 shape).
- `check:authorable-surface` ages a tombstone one major, dating a deleted
  `[RETIRED]` line by a registry row or by the previous major's release
  tag; no gate is added.
- ADR-0087 amended in place; the template, the upgrade guide, the
  retirement skill and one changeset follow. Spec, cli, lint and
  metadata-core tests re-pinned to the refusal or to surviving kits.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
Brings in the 84 commits main gained since the branch base. Source files
merged without a text conflict; the merge driver deferred eleven
generator-owned artifacts (authorable-surface ui/security, the permission
liveness state-counts page, eight reference pages), regenerated here from
the merged tree with the spec generators, not by hand.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
…e 17.x kits, not the retired ≤16.x ones

The ≤16.x retirement kits left the registry in the previous commit, and
two suites still rode them as fixtures:

- packages/metadata-protocol's stored-row seams (migrateStoredMetadata,
  getMetaItem(s), loadMetaFromDb, the residue re-save and the
  non-canonical-type control) used the `conditionalRequired` →
  `requiredWhen` field rename and the action `execute` → `target` alias as
  the legacy shape the stored seam keeps lowering. Those conversions are
  gone, so the rows now carry the 17.2.0 `reference_to` → `reference`
  rename (`field-reference-to-alias`) and the 17.4.0 `aria` removal
  (`action-aria-removed`), both still registered for the data-at-rest
  seams; the pins read the same notices, rewrites, checksums and report
  lines as before, on the surviving kit.
- packages/lint's object-door field-rule test listed `conditionalRequired`
  among the slots the build's expression walk judges; the walk no longer
  reads it (its tombstone aged out of `FieldSchema`, so the key is a strict
  unknown key), and the row is dropped with the docblock saying why.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
…ns on the retired ≤16.x kits ride 17.x kits or the strict refusal

Running every package whose sources read the conversion registry in full
found the rest of the pins that rode a kit the previous commits removed:

- packages/metadata-protocol: the served-schema property counts (#17501)
  move down by exactly the top-level tombstones that left each shape
  (action 3, agent 2, app 8, dashboard 2, field 1, flow 2, skill 1); the
  #17502 repeater-row pin moves from the dashboard widget row, which has no
  tombstone left, to the page component row (`visibility`, `responsive`,
  17.x kits), with the dashboard keeping its depth pin through
  `refreshInterval` and the widget `chartConfig`; the aged-out widget
  column is pinned as the strict refusal it is now; the #21982 fallback
  pin rides `errorHandling.retryDelayMs` (`retry-policy-converged`, 17.7.0)
  instead of `flow.active`, and `flow.active` is pinned refused on both
  hosts, naming the upgrade guide.
- packages/services/service-automation: the script `actionType` stubs are
  refused at registration as unknown keys naming the guide (nothing strips
  them on rehydration any more), the fault-edge guard rides the bare
  contract, and the wait-node round trip holds the `timeout` guidance
  channel, with the aged-out `timeoutMs` pinned as the strict refusal.
- packages/objectql: the rule validator pin shows the chain no longer
  lowers the aged-out `conditionalRequired` alias and enforces the rule on
  the canonical `requiredWhen` only.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
…-seam pins ride the 17.x kits; the aged-out ≤16.x spellings are pinned as strict refusals

The two remaining consumer suites of the conversion registry carried
fixtures in the ≤16.x dialect the previous commits retired:

- packages/runtime: the #12892 two-reader harness and the two real-boot
  files (the security registrar, the #14491 seeder declaration copy)
  wrote the stack's `roles`, an RLS `priority` and a `sharedWith.type:
  'role'` recipient, each governed by a kit that left the registry. The
  fixtures now write `positions`, no `priority` and a `position`
  recipient; the conversion axis is measured on the 17.x kits that still
  reach the security collections (the `allowRestore` / `allowPurge`
  grant bits, `accessLevel: 'full'`); and a new harness pin shows the
  door refusing each aged-out spelling outright — a strict unknown key
  naming the upgrade guide, an invalid recipient option. The #3878
  analytics-entry pins expect `query` refused as a strict unknown key
  naming the guide instead of the envelope's per-key prescription.
- packages/services/service-datasource: the #4456 restore-seam pin shows
  what the seam replays now — the 17.7.0 `mongo` → `mongodb` driver
  alias — and that a row still in the ≤16.x config spelling (`file`,
  `connectionString` / `user`, `uri`) is served as stored: the row the
  ruling sends through `os migrate meta --stored --apply` on 18.1 or
  earlier.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
…6 or earlier" — one major at a time, stored rows before the runtime moves

The maintainer's ruling relayed on the PR (B, ships in 18.2): make the
path a ≤16.x project takes prominent, text only. One block, in the two
places the ruling names — the changeset, because the 18.2.0 release
notes are cut from it, and the top of docs/protocol-upgrade-guide.md:

- the default, one major at a time: `@objectstack/cli@17.7.0 migrate
  meta --from 16 --apply`, its `--stored --apply` for the sys_metadata
  rows, then the move to 18.2 and `os migrate meta --from 17 --apply`,
  `os validate`, `os build` — and the rule it follows, each major's CLI
  in turn, which holds at 19;
- the one-shot alternative on `@objectstack/cli@18.1.0`, the last
  release whose floor is 16;
- stored rows are migrated before the deployment's image or
  dependencies move to 18.2, since 18.2 no longer rewrites a ≤16.x row
  at read;
- what 18.2 does when the first hop is skipped: `--from 16` refuses with
  MigrationFloorError and rewrites nothing, and a ≤16.x key meets the
  strict unknown-key refusal with the guide link.

No code and no generated file changes; content/docs/upgrading.mdx keeps
its floor callout, which names the 18.1 path as the alternative does.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
Brings in the 40 commits main gained since the previous merge. One text
conflict, in the #17501 served-schema property-count pin: main moved
`object` 47 → 48 for the `sharing` block (#25760) while this branch had
re-derived every count without the top-level tombstones that left each
shape; resolved as the branch's counts with `object` at 48. The merge
driver deferred four generator-owned artifacts (the data authorable
surface and three reference pages), regenerated here from the merged tree
with the spec generators, not by hand.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
…a site route into the generated directory

CI's Check Documentation Links (lychee, offline, --root-dir content) went
red on the merged head: content/docs/upgrading.mdx linked the guide as
`/docs/protocol-upgrade`, a site route into content/docs/protocol-upgrade/,
which is generated by `gen:upgrade-guide` and git-ignored, so a checkout
has no file for it. The link now carries the guide's address, the spelling
the backward-compatibility page and the refusal text already use, and the
one the offline lane excludes by scheme. No other link-checked page on the
branch targets an untracked path (resolved against `git ls-files`).

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
…bulkEnabled` action keys

CI's Build Core went red on the merged head in ui/packages/core: `ActionDef`
declared `shortcut?: SpecActionInput['shortcut']` and `bulkEnabled?:
SpecActionInput['bulkEnabled']`, two mirrors of spec-17 `retiredKey()`
tombstones that resolved to `undefined` for as long as the tombstones
stood. The ≤16.x kits left the schema one major on, so the indexed access
has nothing to name and tsc refuses it. The two members leave `ActionDef`
with their rows in `ACTION_DEF_KEYS` and `SPEC_ACTION_KEYS` (the key
inventories the pin tests hold against the spec's `ActionSchema` shape),
and `SPEC_ACTION_KEYS` drops its `execute` row for the same reason: the
pin that walks the spec shape reported it stale once that tombstone aged
out. `RETIRED_ACTION_KEYS` keeps the console's own `execute` rename
prescription (a stored row can reach the runner unparsed), and
`ActionEngine.registerAction(action, { shortcut, bulkEnabled })` keeps
both as host-passed options, as before; the closed-surface pin's comment
and the tombstone test's title say what refuses the key now.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
…ys on their own; the kits' readers follow the one-major rule

The contract review of 93505c9 found the readers of the removed 16.x
kits that the removal had not been shipped with: the spec package's own
repo tests, the console's derived twins and their pins, and a react test
pin on the ActionDef mirror.

Spec repo tests (`test:repo`, Test Core 1/8):
- Three surviving-kit tests asserted a major-17 ledger row this change
  deletes (`integration/ConnectorFieldMapping:transform`,
  `ui/PageHeaderProps:icon`); they now assert the one-major rule — the 17
  table is gone and the row with it.
- The check-mode leaf-collision fixtures lost their premise: the
  `data.query.distinct` clause left with the 16.x conversions and the
  `data/Index:type` tombstone with the 16.x tombstones. Re-picked to
  `api/PackageInstallManifest:capabilities` against the registered
  `manifest.capabilities` (kernel/Manifest's row, inherited through
  `ManifestSchema.extend`) and `system/BookGroup:translations` against
  `manifest.contributes.translations`.
- Five tree-scoped absence kits found the fixtures this change moved onto
  surviving 17.x keys (migrate-meta-write, migrations.test,
  view-union-retirement-prescription, lint-liveness-properties); each kit's
  EXCLUDED set names the file with its reason.

Console twins (`@object-ui/types`): `AppComponentSchema` and
`DashboardComponentSchema` derive from the spec shapes by reference over
a `.passthrough()` base, so the ten tombstones that aged out of
`AppSchema` / `DashboardSchema` (version, homePageId, objects, apis,
sharing, embed, mobileNavigation, aria; aria, performance) stopped being
refused and were kept in silence. A new `agedOutSpecKeyRefusal` helper
(`zod/tombstone.zod.ts`) declares each as the console's own refusal:
`invalid_type` at the key's path, opening with the spec's unknown-key
sentence and the guide sentence (a copy pinned equal to the spec's export,
since the mirrors' import boundary admits no spec string), then where the
value went. The TS twins carry `?: never`; the parity, twins-by-reference
and dashboard-aria pins assert the refusal on both faces with the spec's
unknown-key verdict as the control. The strict `DashboardWidgetSchema`
needs no arm: its five aged-out keys meet the `.strict()` door, and the
report-chart pins now read zod's `unrecognized_keys` there with the spec's
guide-naming refusal as the contract leg.

driver-sql: the storage/presentation classification of FieldSchema keys
drops its `conditionalRequired` row (the test beside it refuses a
classification the spec no longer declares).

Console pins: the layout conversion-coverage row for
`page:card → properties.body[]` is gone with the conversion; the react
useActionEngine fixture keeps its stale `shortcut` on an entry of its own
(an object literal with a mistyped member is not also checked for excess
members, so the directive reported unused once `shortcut` left the
mirror). The SDUI manifest is re-recorded for the moved source digests.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
Brings in the 29 commits main gained since the previous merge, among them
the re-pointed ObjectSchema corpus guards (#26231) this branch's console
pins were waiting on. One text conflict: the sdui core source record
(scripts/sdui-manifest.sources/core.json), re-recorded on both sides, is
taken from main and then re-recorded from the merged tree by the manifest
generator. Generator-owned artifacts are regenerated from the merged tree
with the spec generators, not by hand.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
…sentence; the guidance floor follows the one-major rule

CI on 6d80aba stopped twice on readers of the removed kits.

Type Check UI stopped at `check:spec-symbols` (before tsc): the console's
own refusal of an aged-out spec key (`agedOutSpecKeyRefusal`,
zod/tombstone.zod.ts) carried a hand-written const under the spec's own
export name, `RETIRED_KEY_UPGRADE_GUIDE_SENTENCE`, and the gate refuses a
declaration that shadows a spec export (a reader takes it for the spec's
definition) and the alignment claim its docblock made. The sentence is now
imported from `@objectstack/spec/shared`. The mirrors' import boundary
(imported-defaults-8317) gains the fourth declared kind of read it
enumerates — a spec TEXT, held name → binding with its own assertion that
each entry is a non-empty string the mirrors read raw and not a schema —
beside vocabularies, type positions and refinements; a string has no Zod
graph and no default, so it cannot write a value into a parsed document,
which is all the boundary polices. The parity test keeps asserting the
refusal carries the spec's sentence; its comment no longer describes a
copy. The SDUI manifest's types record is re-recorded for the moved source
digest.

Test Core 4/8 and 6/8 (packages/cli): migrate-meta-engine-guidance pins an
anti-vacuity floor of the entries whose guidance was rewritten, 244 ids,
and asserts the chain's family still holds each. 75 of them are major-17
entries this change retired one major on, so the family lost each by
name. The floor is the rewritten entries the registry still holds — the
75 rows leave with their guidance, 169 remain — and the file says the
rule: an entry retired one major on leaves both the chain and this list.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
Brings in the 28 commits main gained since the previous merge. Three text
conflicts: the sdui core and types source records
(scripts/sdui-manifest.sources/), re-recorded on both sides, are taken from
main and then re-recorded from the merged tree by the manifest generator;
the types test object-view-unmirrored-keys-7779 takes main's membership
assertions (#26238) with this branch's aged-out legs kept beside them. The
six generator-owned artifacts the merge driver deferred (the data
authorable surface and five reference pages) are regenerated from the
merged tree with the spec generators, not by hand.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
…s `username`

Test Core's cli slice 2/4 (the 4/8 shard; 3/8 at the previous head)
failed audit-mail-bodies.archive case 7: the fixture's declared postgres
archive carried `user`, the pre-17 spelling of `username`, which the
`user` → `username` conversion rewrote at artifact load until that kit
aged out of the chain one major on (ADR-0087). The strict config door now
refuses the key by name — the command exits 1 with the refusal as its
whole JSON and no `archive` entry, which is not the case the test pins
(a declared archive this boot cannot CONNECT, reported `not-connected`
and `failed-degraded`). The fixture spells the live key; the case's
reading is unchanged and the file is green alone.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
Brings in the 28 commits main gained since the previous merge (the PR had
gone un-mergeable against main before CI could start on 1331519). Two
text conflicts: the sdui core source record is taken from main and
re-recorded from the merged tree by the manifest generator; the
visibility-alias-keys absence kit keeps both EXCLUDED entries — this
branch's view-union prescription pin and #25712's sdui-parser base-props
pin. The one generator-owned artifact the merge driver deferred (the
automation-api reference page) is regenerated from the merged tree with
the spec generators, not by hand.

Claude-Session: https://claude.ai/code/session_01H37vtTJRVBf1LkyYNW5yfo
Co-authored-by: Claude <noreply@anthropic.com>
…the spec keys that aged out at 18.2

The re-review of 2718c3c found the console's `ListViewSchema` twin built
like the App and Dashboard twins this PR already fixed: `BaseSchema`
(`.passthrough()`) plus the spec list view's members by reference. The five
list view tombstones that aged out of the spec at 18.2 (`striped`,
`bordered`, `virtualScroll`, `responsive`, `performance`) left the
projection with no refusal of their own, so an authored `striped: true`
parsed green and was kept. Each is now an `agedOutSpecKeyRefusal` arm on
the twin. The TS face is derived from the twin (`ListViewInferred`), so
each key is `?: undefined` there with no hand-kept member; the page
`list-view` node's `properties` bag reads the twin's shape and refuses the
five too, and its flat spelling gets the row's own retirement.

A parse census of the other removed component tombstones found three more
flat spellings kept in silence on public-block arms: `page:header` `icon`,
`page:card` `actions` and `record:details` `layout`. `flatPropRefusals`
reads the spec row's key set, and the row no longer carries the name, so
`BaseSchema` kept a flat value. Each arm now declares the console's own
refusal; the bag spelling was already refused by the strict row.

Pins: list-view-aged-out-spec-keys-25767 (twin, bag, flat, TS face) and
public-block-aged-out-spec-keys-25767 (flat, bag), each with the spec's
own unknown-key verdict as the control.

Claude-Session: https://claude.ai/code/session_018wdyGVjfpxCUwq2UMrZkwi
Co-authored-by: Claude <noreply@anthropic.com>
… as their own category

`list-view-spec-parity`'s rogue-key check read the five new
`agedOutSpecKeyRefusal` arms as objectui-only fields. They are neither a
sanctioned local extension nor a spec member: a fifth category,
`AGED_OUT_SPEC_REFUSALS`, checked like `HANDLER_KEY_REFUSALS` — each member
must be declared on the arm, refuse every value, and be undeclared by the
spec, so the set cannot park a live field.

Claude-Session: https://claude.ai/code/session_018wdyGVjfpxCUwq2UMrZkwi
Co-authored-by: Claude <noreply@anthropic.com>
…rge of main

`node scripts/gen-sdui-manifest-node.mjs` over the merged tree: the
manifest itself is unchanged (sha256 1f6e1ccb925a…, 106 components); the
core and types records move with main's sources and this round's
ui/packages/types edits.

Claude-Session: https://claude.ai/code/session_018wdyGVjfpxCUwq2UMrZkwi
Co-authored-by: Claude <noreply@anthropic.com>
…w keys again

`ObjectView.relayRungCensus-7559` reads every `ListViewSchema` member and
asks for a relay rung or a declared absence. The five list view keys whose
spec tombstones aged out at 18.2 had left that shape, so their `unread`
rows were dropped; the twin now declares each as its own refusal
(`agedOutSpecKeyRefusal`), which makes them members again. They return as
`unread`, each reason naming the age-out and the refusal: an authored
value never parses, so there is nothing for a rung to relay. CI's
Test UI (15/16) on 045298a was red on exactly this.

Claude-Session: https://claude.ai/code/session_018wdyGVjfpxCUwq2UMrZkwi
Co-authored-by: Claude <noreply@anthropic.com>
# Conflicts:
#	scripts/sdui-manifest.sources/core.json
#	scripts/sdui-manifest.sources/types.json
…ide; the branch's aged-out data.json lines stay deleted

Main moved packages/spec/authorable-surface/data.json (Seed files rows) and two
reference pages; the merge took main's side and these are the regenerated
results. Field:conditionalRequired, Index:partial and Index:type stay deleted:
they aged out on this branch (objectstack#25767).

Claude-Session: https://claude.ai/code/session_018wdyGVjfpxCUwq2UMrZkwi
Co-authored-by: Claude <noreply@anthropic.com>
# Conflicts:
#	docs/adr/0087-metadata-protocol-upgrade-contract.md
…ide; the branch's aged-out RetryPolicy.retryDelayMs line stays deleted

Claude-Session: https://claude.ai/code/session_018wdyGVjfpxCUwq2UMrZkwi
Co-authored-by: Claude <noreply@anthropic.com>
…etirement kits age out (#25767 ruling 6434038284)

The maintainer's 2026-10-27 ruling keeps kit aging (tombstones and their
D2 conversions retired at or below 16.x leave; the strict refusal names the
upgrade guide) and withdraws the chain cut. Restored to origin/main: the
migration entries (159 major-17 files and the conversionIds links in the
three protocol-18 entries), registry.ts.template with MIGRATION_SUPPORT_FLOOR
= 16, chain.ts, migrations.test.ts, semantic-relevance.test.ts, the cli
migrate meta command and its twelve tests, ADR-0142, and the floor prose in
the upgrade guide and the upgrading page.

The retirement sentences follow the conversion, not the floor: an edit whose
toMajor-17 conversion survives (the twelve retiredAfter 17.7.0 ones) goes back
to the house 'Run os migrate meta --from 16' sentence; an edit whose 16.x
conversion left says it left with the 16.x conversions and links the guide.
ADR-0087's kit-aging amendments are reworded (the kit ages out, the step
stays) and its chain amendments are withdrawn; the changeset is rewritten.
The release-tag dating source in build-schemas.ts stays: 46 of the 61
deleted [RETIRED] lines predate RETIRED_KEYS_BY_MAJOR, so the registry alone
cannot date them.

Claude-Session: https://claude.ai/code/session_018wdyGVjfpxCUwq2UMrZkwi
Co-authored-by: Claude <noreply@anthropic.com>
…re-point the pins that replayed them

Restoring registry.ts.template to main brought back step 17's graduated
conversionIds list, which named the 45 conversions retired at 16.1.0 that left
the registry with their kits; the chain's integrity pins refused the dangling
ids. Those ids leave the list (the step and its semantic entries stay, the
floor stays 16). The replay pin in migrations.test.ts, the cli cross-hop pin,
the --from 16 protocol-range shape and the --write fixture now replay
conversions step 17 still graduates (sharing-rule full to edit, the CRUD
flow-node object alias) or protocol-18 ones. The three repo-project pins on
RETIRED_KEYS_BY_MAJOR[17] go back to main with the restored rows. ADR-0087's
addendum and the changeset say the step-17 list lost those ids.

Claude-Session: https://claude.ai/code/session_018wdyGVjfpxCUwq2UMrZkwi
Co-authored-by: Claude <noreply@anthropic.com>
…ide; the branch's five aged-out api.json lines stay deleted

Claude-Session: https://claude.ai/code/session_018wdyGVjfpxCUwq2UMrZkwi
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/driver-sql, @objectstack/lint, @objectstack/metadata-core, @objectstack/spec, touching 348 documentable anchor(s). ⚠️ 36 changed file(s) yielded no anchor (packages/lint/src/validate-object-field-refs.ts, packages/lint/src/validate-security-posture.ts, packages/spec/api-surface/shared.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

106 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 7c34e5dc211fd54f86db04a650916950301ddfd8.

⛔ 18 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 36 changed file(s) yielded no anchor (packages/lint/src/validate-object-field-refs.ts, packages/lint/src/validate-security-posture.ts, packages/spec/api-surface/shared.json, …) — pages documenting those are invisible to this run
  • 2 anchor(s) matched too much of the corpus to be a work list: created_at (literal, 39 pages), requiredPermissions (literal, 32 pages)
  • 18 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 55 of 207 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 — 149 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 7c34e5dc211fd54f86db04a650916950301ddfd8 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 839b466eb9f1dc2402b888d6ba21c4593c34f619 — the merge of head a0e848fb6e69a29cbe48b2820b2c7a9ffd3d3bb4 into base 7c34e5dc211fd54f86db04a650916950301ddfd8, 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 839b466eb9f1dc2402b888d6ba21c4593c34f619 && git checkout 839b466eb9f1dc2402b888d6ba21c4593c34f619
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7c34e5dc211fd54f86db04a650916950301ddfd8 a0e848fb6e69a29cbe48b2820b2c7a9ffd3d3bb4 && git checkout -B drift-repro 7c34e5dc211fd54f86db04a650916950301ddfd8 && git merge --no-ff a0e848fb6e69a29cbe48b2820b2c7a9ffd3d3bb4

node scripts/docs-audit/affected-docs.mjs --json 7c34e5dc211fd54f86db04a650916950301ddfd8

⚠️ 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 7c34e5dc211fd54f86db04a650916950301ddfd8 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…s `username` again

The rework restored this file to main and lost #26138's c61ede6. Test
Core 4/8 failed audit-mail-bodies.archive case 7 at cd60de5: the
fixture's postgres archive carried `user`, the pre-17 spelling of
`username`. The `datasource-config-driver-key-aliases` conversion that
rewrote it at artifact load aged out with its kit, so the strict config
door refuses the key and the command never reaches the not-connected
archive the case pins. The fixture spells the live key.

A census of packages/cli for the other spellings that conversion and the
removed datasource conversions carried (sqlite file/database, postgres and
mysql connectionString, mongo uri, inert blocks, capabilities, readReplicas,
connector rateLimit) finds no other datasource declaration that uses one.

Claude-Session: https://claude.ai/code/session_018wdyGVjfpxCUwq2UMrZkwi
Co-authored-by: Claude <noreply@anthropic.com>
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit dbf7e04 Oct 27, 2026
62 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-q3olv3 branch October 27, 2026 11:23
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 40493270857 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test UI (1/16) — 失败步骤: Run the ui tests (shard 1/16)

    × unmount unsubscribes: a later pulse reads nothing 23ms
    FAIL   dom  packages/plugin-chatbot/src/__tests__/ChatbotEnhanced.pendingCountPulse-26471.test.tsx > objectstack#26471 — the draft card re-reads its pending count on the metadata pulse > unmount unsub
      ↳ 失败原因: Error: expect(element).toHaveTextContent()
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 5 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

os-tesla pushed a commit that referenced this pull request Oct 27, 2026
#26599 retired the conditionalRequired row, so the shipped field ledger's
warned set is externalId alone; the lint pin and the regenerated field count
shard say so.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibvE77Ed7MKdzPxJTQQ
zhuangjianguo pushed a commit that referenced this pull request Oct 27, 2026
Conflicts were counts only, resolved on main's side plus this branch's
additions: the type-alias isomorphic pins (792 on main, 796 here) and the
dropped-refinements baseline totals (248 / 869 on main, 251 / 887 here).
The authorable-surface baselines keep main's retirements (#26599) and this
branch's Queue and AssignmentRule keys. Generated references regenerated
from the merged tree.

Claude-Session: https://claude.ai/code/session_01GTntg542RUmmRRbMY6wMZD
Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang pushed a commit that referenced this pull request Oct 28, 2026
…26599 aged out (#27015)

`ui/PageCardProps:body` left the live authorable surface in #26599 and
stayed authorable in the self-test only through the stale anchor. With the
anchor re-anchored, the case named a key neither side carries. The
specimen is now `ui/Dashboard:refreshInterval`, a 18.x tombstone that is
on the live ratchet until major 20.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QUs3Bshfmr8srPY21BVS4u
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants