Repository navigation
feat(spec)!: retire the view form's visibleOn and the page component's visibility as keys; their ADR-0089 renames leave the load path (F4 of #23543) - #23836
Conversation
…y (F4 of #23543) Claude-Session: https://claude.ai/code/session_014ruwnS8CSGGVA5UWfJA8xZ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ruwnS8CSGGVA5UWfJA8xZ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ruwnS8CSGGVA5UWfJA8xZ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ruwnS8CSGGVA5UWfJA8xZ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ruwnS8CSGGVA5UWfJA8xZ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ruwnS8CSGGVA5UWfJA8xZ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ruwnS8CSGGVA5UWfJA8xZ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ruwnS8CSGGVA5UWfJA8xZ Co-authored-by: Claude <noreply@anthropic.com>
…-visibility-keys-retired
📓 Docs Drift CheckThis PR changes 4 package(s): 15 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 11 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 143 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 92db036a7d81e93e164da603c7d0a690cb5d8a3e && git checkout 92db036a7d81e93e164da603c7d0a690cb5d8a3e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3c080a65c8c17af97403fed1c26cddac05f43a6a 860f4a18bee5da9ea84a43fb40557d1a5300cd2a && git checkout -B drift-repro 3c080a65c8c17af97403fed1c26cddac05f43a6a && git merge --no-ff 860f4a18bee5da9ea84a43fb40557d1a5300cd2a
node scripts/docs-audit/affected-docs.mjs --json 3c080a65c8c17af97403fed1c26cddac05f43a6a
|
Contract reviewRendered 2026-10-16T08:34Z · adversarial read of PR #23836 for card #23543, family F4 of ruling Served-tier: Inputs: card #23543 body and all 26 comments (the ruling Check-runs on the head at this read: 41 total, 36 ① Derived judgments
② Semver level
③ Boundary flags
Defect (the FAIL)The published reason for narrowing the two lint readers is false at one door. Patch round (prose plus one pin, no code change): rewrite those five sites to name Implemented-by: VERDICT: FAIL Generated by Claude Code |
…lity key's predicate unjudged (F4 of #23543, review patch) Claude-Session: https://claude.ai/code/session_014ruwnS8CSGGVA5UWfJA8xZ Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewRendered 2026-10-16T09:18Z · re-review at the patched head after FAIL Served-tier: Inputs: the FAIL record Check-runs on the head at this read: 41 total, 36 ① Derived judgments
② Semver levelUnchanged by the patch and carries from ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对 · ACCEPT (Tier H, the maintainer's hand) · PR #23836 @
|
Part of #23543
Clause-②: yes (narrowing)
This PR lands family F4 of ruling
6182001882(item 4) only. F5-http stays on #23543 as its own PR, so #23543 remains open.What
Ruling F4 A: "
visibleOn(view) andvisibility(page component) retire as keys:retiredKeytombstones namingvisibleWhenwith the fullspec-property-retirementkit (ADR-0087 markers, baselines, forms, docs, pins); both entries retire in the same change; the three record / strict-exit suites re-point."mainatf606ba2e25(authorable-surface/ui.json):ui/FormField:visibleOn,ui/FormSection:visibleOn,ui/PageComponent:visibility. There are no others. The inline form field and the page-block section entry incomponent.zod.tsnever declaredvisibleOn; they already refuse it by name. Each of the three is now aretiredKey()tombstone whose prescription namesvisibleWhen, for example: "page.components[].visibilitywas removed in @objectstack/spec 18 (ADR-0089 D2) — … UsevisibleWhen. Rename the key; the value (a CEL predicate) is unchanged. Where a component writes both,visibleWhenis the one that was in effect: deletevisibility." There is noos migrate metasentence, on purpose. Both conversions are protocol 15, belowMIGRATION_SUPPORT_FLOOR, so the tool lists no edit for them. This is the F5-jsx precedent, and the removal-version pin's--fromrule (toMajor − 1 = 14) leaves no legal sentence anyway..transform(normalizeVisibleWhen)pipes existed only to fold the old keys, so they are removed and all three shapes are plain closed objects.FormFieldstill omits'visibleOn'from its parsed type, so a reader of parsed output that reaches for the key still failstsc.view-visibleOn-to-visibleWhenandpage-component-visibility-to-visibleWhengetretiredFromLoadPath: trueandretiredAfter: '17.7.0', the stamp F1/F5-jsx/F3/F2 used and still the newest stable (18.0.0-next.2label). Their "recorded verdict at 18" paragraphs are rewritten as retirement records. Fixtures are unchanged: everyincludeRetiredseam still replays them.RETIRED_KEYS_BY_MAJOR[18]entry files, and one D3 entryui-visibility-alias-keys-retiredwith noconversionIds(below the floor, the F5-jsx shape). There is noSTEP18_RATIONALEfragment, because no step-18 conversion exists. The changeset marker isregistered ui-visibility-alias-keys-retired.conversions.test.tsnow keeps one entry,flow-node-http-callout-rename, and lists the two among the retired.view-spelling-walk.test.tsgains a case stating that the stored seam still replays the retired rename. The removal-versionJOINgains rows for both ids (three tombstones). There is a new repo-project pin,ui/visibility-alias-keys-retirement.test.ts, covering:@ts-expect-errorat each authoring type);defineStackrefusals with code + status;@objectstack/spec#testalready declares.维护者速读(草稿)
visibleWhen。表单视图里的旧写法visibleOn和页面组件里的旧写法visibility不再被悄悄转换,作者写了会当场报错,并告诉他改成visibleWhen。skills/objectstack-ui/references/react-blocks.md(一行类型从any[]变成object[]),所以按受管面走人合。domain:spec席位 1),契约复核第二轮 PASS(6212287597,在当前 head860f4a18be上)。第一轮复核 FAIL 只有一处问题:说明文字声称「每个入口都先解析」,其实os lint不解析,已按复核意见只改文字并补一条测试。三处旧键都改成报错并提示visibleWhen,两条旧转换不再在加载时运行,但库里存着的旧行读取时仍会自动改名(复核按代码核实)。CI 全绿。建议批准合并;这是 Tier H(带出skills/**的生成文件),席位不会自己翻 ready 或入队。normalizeVisibleWhen/VISIBILITY_ALIAS_KEYS导出;读合入后第一轮 Spec Shape Gate,若变红就按登记表流程登记。Mechanism hypotheses, measured
derivedKeys, keeping'visibleOn'would NOT give two messages: the tombstone aborts the section parse, so the group-reference refinement never runs on a section that wrote it. I measured one issue, atsections.0.visibleOn. The entry was therefore unreachable and is removed, with thegroupdescribe updated.'17.7.0'(above). The D3reasonandacceptanceCriteriawere tested sentence by sentence against the tool, on a temp project carrying all three old keys, with the built CLI closure:os migrate meta --from 17exits 0, replays protocol 17 → 18, applies no edit for either key, and prints the three tombstone refusals under "Migrated stack does not yet pass schema validation — 3 refusals left after the chain".ui-visibility-alias-keys-retiredamong the manual changes.--writeleaves the source byte-unchanged.packages/spec/src/stack-conversions-record.test.ts(found by the full spec run; not in the claim list) gets a synthetic probe, the F2 route.vi.mock('./conversions/registry')appends a test-owned live copy of the page-component rename (stack-conversions-record-probe,toMajor: 15, so every pinnedretiresIn: 16holds). Its subject is the record, not an entry, and no later family has to re-home it.packages/metadata-core/src/artifact-forward-conversion.test.tsFIRING CONTROL 1 is re-pointed toflow-node-http-callout-rename. Its claim is specifically that a NON-retired entry still fires at the artifact door; FIRING CONTROL 2 already covers retired replay. A probe would have to mock the spec registry behind metadata-core's@objectstack/specimport, which is the callee under test. F5-http inherits it; with no live entry left after F5-http, that PR turns it into "the door converts at all" or retires it.packages/cli/src/commands/validate-json-strict-exit.e2e.test.tsandpackages/cli/test/stack-conversion-record-door.test.tsare re-pointed to the callout rename: a flow node typedhttp_requestagainsthttp. Both spawn the CLI over temp projects that load the built@objectstack/specthrough a symlink, outside vitest's module graph, so no probe can reach them. This is the reason F2 gave for themigrate metasuites. The cells are unchanged: the converting member gives text--strict1,--json --strict1,--json0,warnings: []and one notice; the canonical member gives 0 everywhere. They join the cli suites F5-http already re-homes.conversions.test.ts's "a live-window conversion still applies at load" is re-pointed to the callout rename, beside the two cases F5-http already owns.packages/lint:validate-visibility-predicates.ts(thevisibleWhen ?? visibleOn ?? visibilitylimbs) andvalidate-predicate-path-refs.ts(PREDICATE_KEYS) now readvisibleWhenonly.os validate,os build,os verifythroughjudgeAuthorTimeRules,defineStack, and the metadata save door, which parses theviewbody (422,protocol.ts:23082) before its runtime rule pass at:23192) refuse either retired key by name, so a body they hand these rules cannot carry one.os linthands these readers an UNPARSED stack (cli/src/commands/lint.ts:681-:683). There, a retired key's predicate is unjudged by design, pinned inauthoring-rule-input-tier.test.tsas that door's reading beside a canonical control (review patch860f4a18be, after contract review FAIL6211507698).authoring-rules.tscomments and therule-explanations.tstext ("(orvisibleOn)") are made true.page-envelope-audit.tsis generic (any parse-consumed string re-homed into an envelope), so its code is unchanged. Its one exemplar is gone: a page still writingvisibilitynow fails the parse, which the audit reports oncomponentParseErrorswith the prescription. The negative control is re-pointed to that, plus a canonical control.packages/metadata-core/src/form-predicate-root-policy.ts:FORM_PREDICATE_KEYS=['visibleWhen']. Its one caller scansresult.definitionafterapplyArtifactForwardConversions, which replays the whole chain (retired included) in every window it opens. So a legacyvisibleOnis already renamed when it arrives; this is pinned end to end.packages/sdui-parser/src/validate.ts: unchanged.SDUI_BASE_PROPSis the lockstep port of objectui's ownBaseSchemanode vocabulary for the html/JSX tier, which objectui still reads (SchemaRenderer). It is not the spec'sPageComponentSchema, so it moves with objectui's card.packages/metadata-protocol/src/protocol.ts: no read site of either key. Four comments about the view door's generic moved-key projection used the fold as their example and are made true.packages,examples,skills,content,scripts). Fixtures were triaged one by one:editability-boundary, the lintgroupswalk, the metadata-protocol graft test).visibleOn", "still read through the alias"), each with a refusal or not-read pin plus a canonical control.main63d00da3deand at the pinneddaeb77fc84, identical for this question.visibleOn, 6 SDUI-node reads, and 3 renderer reads of page-componentvisibility. One source type coupling:react/src/SchemaRenderer.tsx:191visibility?: PageComponent['visibility'].plugin-form/src/sectionFields.spec-parity.test.tsreadsSpecFormFieldSchema.in.shape, which no longer exists.types/__tests__/flat-props-refusal-10872.test.tsandSchemaRenderer.expressions.test.tsx:38were not run.normalizeVisibleWhenandVISIBILITY_ALIAS_KEYS.showStarreading), so no break is expected there.Side effects of removing the pipes (measured, each pinned)
.in. The build printed the corrected entries and they were applied as-is: path moves, plus one site (api/ListInstalledPackagesResponse…pages.element.slots.header.options[0].type). That site is the pagetyperefinement already ledgered on the sibling responses, now reached one level shallower. The header total is 763 → 764.typerefusal (a non-aborting refinement) now arrives atslots.header.typeinstead of nested in aninvalid_union;component.test.tspins the new, better-located shape. An unknown key still arrives as the union.skills/objectstack-ui/references/react-blocks.mdis regenerated bygen:react-blocks:object-formsectionsisobject[]instead ofany[], because the pipe had hidden the element type.SKILL.mdset is unchanged, and the change is generated, not hand-authored..in.shape/def.in.shape):component-form-custom-fields-sections-typed.pin,expression-scope-data-root.pin,expression-scope-app-root.pin,component-record-related-list-parent-23134and metadata-core'sform-predicate-root-policy. Each is moved to.shape.buildObjectFormSectionFieldEntry(component.zod.ts) read the pipe at runtime and reads.shapenow.test-typecheck-debt.json: twoTS2339 visibleOnentries VANISHED with the deleted reads, re-recorded as the gate asked.Verification
Readings are on
6e242061, the head beforemainwas merged, unless noted. The merge24622be1brought four commits; none touches these files exceptpackages/spec/src/ui/view.test.ts, in other hunks, which merged cleanly throughos-regen-merge.sh.@objectstack/spec:24622be1:buildexit 0;check:generated"All 14 generated artifacts are up to date";test684 files / 20896 passed (1 todo), exit 0.6e242061:typecheckexit 0;test:repo64 files / 1082 passed.@objectstack/lint:typecheckexit 0;test140 files / 6779 passed.@objectstack/metadata-core:typecheckexit 0;test18 files / 734 passed.@objectstack/metadata-protocol:typecheckexit 0;test245 files, 20594 passed and 19 skipped after the one re-spelled graft fixture; that file passes 46/46.@objectstack/cli:typecheckexit 0, after building the closure--filter '@objectstack/cli^...'; unit tier 297 files / 4432 passed;stack-conversion-record-door.test.ts21/21;validate-json-strict-exit.e2e.test.ts(nightly tier,OS_TEST_TIERS=nightly) 7/7.@objectstack/dogfood:expression-conformance.test.ts7/7.Gates (
dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon24622be1): 123 derived, each run with its exit code captured before any pipe.--ranreports "123 derived famil(ies) accounted for — 119 run, 4 NOT-MEASURED (4 DERIVED from a recorded exit 3)", and all 119 exited 0. The four are prerequisite refusals, declared to CI:check-plugin-teardown-shape --self-test(shallow clone);check:skill-examples(client dist absent);check:dual-build-cjs-loads(full build absent);check:i18n-coverage(@objectstack/connector-slackunbuilt).The local-only families
check:dev-prereqs,check:skill-frame-freshnessandcheck:nul-bytesexit 0.Ablation, one-time (
scripts/ablation-replace.mjs, on6e242061):visibility: retiredKey(→visibility: EvaluatedExpressionInputSchema.optional(), __ablationRetired: retiredKey(inpage.zod.ts, which restores the key as live. On-disk proof: anchor 1 → 0, blobb8bce09a→e5d1edef.page.test.ts×2 (the refusal and beside-visibleWhen), the removal-versionJOINrowpage-component-visibility-to-visibleWhen, andvisibility-alias-keys-retirement.test.ts×2 (the@ts-expect-errortype leg and thedefineStackpage refusal). Direction: red.b8bce09a,git diff HEADempty. Spec tests importsrc, so there is no dist leg.Acceptance notes
normalizeVisibleWhenandVISIBILITY_ALIAS_KEYSstay exported from@objectstack/spec/shared; no caller remains in either repo, and their docblock is made true. Retiring them is an export removal that would make the 18 D3 entryvisibility-strict-options-unexported("which stay exported") false, so it is left to a separate decision.viewandpagethe door parses the body as stored, so the old keys meet the refusal there (422).MAJOR_18_CONVERSIONS, every other conversion entry and objectui are untouched.skip-changeset, and only when nothing publishes. This PR publishes, so it carries no label from me.Generated by Claude Code