Repository navigation
feat(lint)!: the visibility-predicate family runs on the page save door (ruling B on #23388) - #24509
Conversation
…e verdict (#24491) Red before the registry change: the page door does not run the family yet. Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
…or (ruling B on #23388) validateVisibilityPredicates and validatePredicatePathRefs declare runtimeTypes ['view', 'page'], so a page component visibleWhen the build refuses is refused at Studio, REST /meta and MCP with the build's finding. Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
…#24491) Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
…ng (#24491) Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
…ge-visibility-predicates-runtime
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 4 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 185a71d91c9db652a17a19dd1ad453b260b326d8 && git checkout 185a71d91c9db652a17a19dd1ad453b260b326d8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3ae0b777d4b94187abedc74f3ae316ff8e063e35 6d416dfc398aa693a11f807b9577fb30e55053bb && git checkout -B drift-repro 3ae0b777d4b94187abedc74f3ae316ff8e063e35 && git merge --no-ff 6d416dfc398aa693a11f807b9577fb30e55053bb
node scripts/docs-audit/affected-docs.mjs --json 3ae0b777d4b94187abedc74f3ae316ff8e063e35
|
席位核对 (seat check) · PR #24509 ·
|
| item | reading |
|---|---|
| Shape | Draft, base main, assignee zhuangjianguo. Line 1 is Fixes #24491. Clause-②: no (narrowing) starts a line. 6 files, +423 / −7. Not governed. The changeset is minor on @objectstack/lint with a ! title, FROM / TO / Fix lines, and an ADR-0087 not-required (no-migration-prescription) marker. |
| The change | validateVisibilityPredicates and validatePredicatePathRefs declare runtimeTypes: ['view', 'page'] (packages/lint/src/authoring-rules.ts), with the page-crossing block above them. runtime-gate.ts and protocol.ts are unchanged (G2 measured: page already maps to pages). |
| Premises | P1 true: before, the page rule set had 4 rules and the door returned no error for totallyBogusFn(1) while os validate refused it. After, the door returns the build's visibility-predicate-unknown-function at pages[0].regions[0].components[0]. P2 true: neither rule gives a false finding on a page. validatePredicatePathRefs walks stack.views only, so it judges nothing on a page at either door. P3 true: 36 shipped pages carry 8 component visibleWhen sites, with 0 findings and 0 door errors, before and after. |
| Pins | A new lint pin, runtime-gate.page-visibility-predicate-writes.test.ts (15 cases), and a 7-case block in protocol.runtime-authoring-gate.test.ts through the real saveMetaItem, publish and package-draft publish. Before the registry change: 14 of 15 red and 4 of 7 red. The ablation turns the same cases red. A stored page loads, and its next save is refused (case (d)). |
| G1, one leg false | "A wrong path ref is refused" cannot hold on a page, because the path rule reads views only. The pin covers the bare-identifier refusal instead, and shows the path half inert. Both entries stay wired because authoring-rule-wiring.test.ts pins one type set for the family. This keeps door = build: the build does not judge a page's path refs either. |
| CI | On this head at this act: 38 runs, 17 success, 5 skipped, 16 in progress, 0 failure. Landing waits for green. |
| Accepted deviations | (1) Two files outside the claim's surface, each made false by the change: validate-predicate-path-refs.test.ts pinned ['view'], and the content/docs/deployment/validating-metadata.mdx table cell moves from ✓ᵛ to ✓ᵛᵖ. (2)–(6) are measurement notes with no contract effect. |
| For the review | The docs row now reads "views[] and page-component conditional-visibility predicates — … schema path refs". On a page component, schema path refs are not judged at any door. Judge whether the row over-claims (Prime Directive #10) and whether it needs a scoping word. |
| Landing | After a contract-review PASS and a green head: pr_ready then automerge_enable. Fixes #24491 closes the card. |
Generated by Claude Code
Contract reviewServed-tier: Narrowing face: a metadata save door accepts less. Inputs were the card's body and every comment (6262435305, 6262575675, 6262629357, 6263909370), the PR body, its six-file list and the net diff against merge base ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL One item: the docs row title in Generated by Claude Code |
席位核对 (seat check) · PR #24509 ·
|
…s to views[] (#24491) validatePredicatePathRefs walks stack.views only, so no door judges schema path refs on a page component. Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Round 2 of contract review ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Round 1's one item is fixed exactly and truthfully; the rest of the diff is byte-identical to the head round 1 judged; Generated by Claude Code |
|
Contract review adopted ·
Generated by Claude Code |
Fixes #24491
Clause-②: no (narrowing)
The visibility-predicate family now runs at the runtime save door for a
pageas it does for aview. This follows the principle of ruling B on #23388 (ruling of record6161173492, the maintainer: 「23388 如果页面有问题,不是应该修改了再保存吗?」): a page with a finding is fixed, then saved, so one rule holds at every door that accepts a page. A page componentvisibleWhenthatos validate/os build/os lintrefuse is now refused at Studio,PUT /api/v1/meta/page/:nameand MCP with the build's own finding: same rule, location, message and hint.What changes
packages/lint/src/authoring-rules.ts:validateVisibilityPredicatesandvalidatePredicatePathRefsdeclareruntimeTypes: ['view', 'page']. The [finding] The fourviews[]visibility-predicate rules are CLI-only — a Studio/REST/MCPviewwrite bypasses all of them; if they move to runtime-publish, they must move together #7220 family comment block gains a section on the page crossing: the ruling, the measurement that no gate change was needed, the inert path half, stored pages, and the corpus count. The runtime-input-tier paragraph now names the page-sidevisibilitytombstone. ThevalidatePredicatePathRefsentry comment says it declarespageand judges nothing there. The rules' own code is not touched.pagealready maps topagesinTYPE_TO_STACK_KEY, and a page snapshot ispages: [item]with noviews. The P1 "after" reading below shows both rules dispatched and the build's finding returned.packages/lint/src/runtime-gate.tsis unchanged, and so ispackages/metadata-protocol/src/protocol.ts.packages/lint/src/runtime-gate.page-visibility-predicate-writes.test.ts, shaped likeruntime-gate.object-action-predicate-writes.test.ts, throughruntimeAuthoringRulesFor('page')/runRuntimeAuthoringRules. It covers:Penvelope, and ascurrent_user.canApprove(...); a bare identifier (the wrong reference); a syntax fault;has(),size(),upper(),page.VAR,current_user.*, thePenvelope);viewcontrol.6262594842): a block inpackages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts, beside the [Decision] Pages saved through Studio, REST or MCP skip the component-props rule thatos validaterefuses: cross it to the runtime door, and on what evidence #23388 page block, through the realsaveMetaItem/publishMetaItem/loadMetaFromDb/getMetaItemfor apage. It covers:totallyBogusFn(1)is refused with422 INVALID_METADATA, rulevisibility-predicate-unknown-functionatpages[0].regions[0].components[0], and nothing is stored; the draft promotion is refused the same way;loaded: 1, errors: 0, invalid: 0), registers, and is served as stored. Its next save is refused, the stored row is unchanged, and the fixed page then saves..changeset/24491-page-save-door-visibility-predicates.md,@objectstack/lintminorwith the BREAKING banner. The level is the one PR feat(lint)!: the two page component rules run on the runtime publish door (ruling B) #23418 and the finding(lint): the object save door gives no build verdict on validation conditions, field-rule slots (requiredWhen etc.), option visibleWhen or action predicates; os build refuses them, a metadata save stores them (#22019's sibling) #22032 object-door crossings took for an accept-set narrowing duringnextpre mode. The changeset has a!title,Clause-②: no (narrowing), FROM / TO / Fix lines, the stored-page sentence, and the ADR-0087 dispositionnot-required (no-migration-prescription). The disposition is explained under G3 below.Premises (each measured on this branch)
P1. Holds. One probe ran before and after, on the page
{ type: 'page:header', properties: { title }, visibleWhen: 'totallyBogusFn(1)' }.origin/maince2e08ee3b, lint built from it):runtimeAuthoringRulesFor('page')=["validatePresetComparands","validateComponentProps","validateComponentTypes","validatePrintPageBlocks"].errors=[].runAuthoringRules('validate')errors =[{"rule":"visibility-predicate-unknown-function","path":"pages[0].regions[0].components[0]","message":"visibility predicate calls unregisteredtotallyBogusFn(found no matching overload for 'totallyBogusFn(int)'), so view/page elements always render and actions hide for everyone"}].saveMetaItem(the new protocol block, run against that lint build):AssertionError: the save resolved — the door still accepts the page predicate: expected { success: true, …(4) } to be an instance of Error. Four of the block's seven cases were red: (a), (a) promotion, (b) and (d). The three admitted cases were green.runtimeAuthoringRulesFor('page')= the four above +"validateVisibilityPredicates","validatePredicatePathRefs".errors=[{"rule":"visibility-predicate-unknown-function","path":"pages[0].regions[0].components[0]"}], the build's finding.P2. Holds for both rules, with one reading the seat should see.
validateVisibilityPredicatesreadsstack.viewsandstack.pagesand no other collection. It needs no object context. A page component takes the rule's defaultruntimelayer, which is the correct layer for a page.validatePredicatePathRefsreadsstack.viewsonly (collectionEntries(stack.views, 'views'), with nopagesread anywhere in the file). Its oracle isgetMetadataTypeSchema(schemaId)of a schema-bound form view, and a page component declares noschemaId. So the object-context question does not arise at the page door: the rule judges nothing on a page, at the door or at the build. That means no false finding and no finding at all.P3. Holds. No shipped page carries a predicate either rule refuses. The pages ran as follows (36 distinct;
crm_welcomewas read twice, from the composed stack and from its module):examples/app-crm(composed stack):crm_welcome.examples/app-showcase(src/ui/pages/index.ts, the barrelobjectstack.config.tsimports; the composed stack did not load in this worktree because@objectstack/connector-mcpwas unbuilt): 30 pages,showcase_active_projects…showcase_task_workbench.examples/app-todoandexamples/app-multi-package(composed stacks): 0 pages.packages/platform-objects:sys_user_detail,sys_organization_detail.packages/plugins/plugin-approvals:sys_approval_request_detail.packages/cloud-connection:cloud_connection_settings.packages/mcp:connect_agent.packages/qa/downstream-contract:dc_welcome.packages/create-objectstackships no page (agit grepforregions/pages:there: 0 hits).The 8 component
visibleWhensites aresys_user_detail(1, aPenvelope readingctx.user.id),showcase_contact_form(1),showcase_my_work(1),showcase_page_variables(4) andshowcase_task_detail(1). All 8 are clean.Mechanism assumptions, measured
G1. Shape held, with one leg falsified. "A wrong path ref is refused" does not hold for the path rule on a page, because
validatePredicatePathRefsnever readspages(P2). On a page, a wrong reference is the visibility rule's verdict:visibility-bare-identifier), and that is pinned;data.-rooted path draws the advisoryvisibility-root-mislayeredat both doors and no refusal, and that is pinned as "the path half judges nothing on a page".Following the ruling's intent, both entries are wired anyway. The family is one wall on the type axis:
authoring-rule-wiring.test.tspins one type set for both entries, and the path rule cannot refuse a valid page because it reads none.G2. Held. No
runtime-gate.tschange was needed (above).G3. Held in part. The level is
minor+ BREAKING, as in PR feat(lint)!: the two page component rules run on the runtime publish door (ruling B) #23418, with a!title,Clause-②: no (narrowing)and FROM / TO / Fix lines.already-registerednamed D3 ids that recorded its props narrowings. No D3 entry covers the visibility-predicate family (a search ofpackages/spec/src/migrations/entriesfound none), so naming any id would be false.not-required (no-migration-prescription), as the five finding(lint): the object save door gives no build verdict on validation conditions, field-rule slots (requiredWhen etc.), option visibleWhen or action predicates; os build refuses them, a metadata save stores them (#22019's sibling) #22032 object-door crossings of the same shape used: a build verdict moved onto one more door.200→422), not a consumer rewrite, so the gate reads no prescription.node scripts/check-adr-0087-registration.mjs --base origin/mainexits 0.G4. Main moved twice (
ce2e08ee3b→7e58e0a27), and I merged it throughbash scripts/pm/os-regen-merge.sh. Steps 1–3 left no additional changes. Step 4: the spec closure was rebuilt andpnpm --filter @objectstack/spec check:generatedreports all 14 artifacts up to date. This branch edits no generated artifact.Stored pages
As ruling B read for the two component rules: no stored preview and no migration. A stored page with such a predicate keeps loading and serving, and its next save is refused with the fix (pin (d)). P3 found no shipped page that would be refused.
File surface beyond the claim, and why
packages/lint/src/validate-predicate-path-refs.test.ts(one assertion and its comment): its registry pin assertedruntimeTypesequal to['view'], so it went red with this change (1 failed of 6888). It now asserts['view', 'page']. None of the 7 open PRs touches this file (their file lists were read in this act). The excludedvalidate-visibility-predicates.test.ts(PR feat(formula): current_user.hasCapability(name) — a page predicate can ask whether the viewer holds a system capability #24478) is untouched, and it carries noruntimeTypespin.content/docs/deployment/validating-metadata.mdx(one table row): the runtime-publish cell lists one superscript per declared type, and✓ᵛbecame false. It now reads✓ᵛᵖ, and the row title names page components, with schema path refs scoped toviews[]only (patch round 1, contract review6264267243③ 1:validatePredicatePathRefswalksstack.viewsonly, so no door judges schema path refs on a page component).Verification (head
079ebf8b5)pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2: 146 files, 6893 tests passed.typecheck(withcheck:test-typecheck) passed.pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2: 247 passed, 3 skipped (250 files); 20699 tests passed, 19 skipped. This ran on the pre-merge head58b7f78bc; the merge brought nometadata-protocolchange. On079ebf8b5,src/protocol.runtime-authoring-gate.test.tspassed 152/152, andtypecheckpassed.validateVisibilityPredicatesdeclares['view', 'ablated_24491_no_page']instead of['view', 'page'], throughscripts/ablation-replace.mjs(anchor 1 → 0, blob2572e080ca6d→76e2558ad8df). Lint was rebuilt, andablation-dist-preflightfound the marker in 4 built files.viewcontrol). The protocol block had 4 red ((a), (a) promotion, (b), (d)) and 3 green (the admitted pages).git checkout HEAD, blob equal to the HEAD blob,git diff HEADempty, rebuild,--absent(absent from all 20 built files, tree clean). Lint pin 15/15 and protocol block 7/7 green.git grepforvisibleWhenacross every*.test.tsthat saves apagenames no test outsidelintandmetadata-protocolthat saves one through the door.Gates (head
079ebf8b5; patch round 1 at6d416dfc3changed one docs line, re-ran the 40 families that docs path reaches, all exit 0, and--ranreconciled 93 derived: 92 run, 1 NOT-MEASURED,check:dual-build-cjs-loads)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, with no paths (6 changed paths vs merge base7e58e0a27), derived 93 families. Each ran verbatim, with its exit code recorded before any pipe. The--ranreconciliation answered "93 derived famil(ies) accounted for — 92 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)", exit 0.check-adr-0087-registration --base origin/main,check-empty-changeset --base origin/main,check-changeset-no-major --base origin/main,check:nul-bytes,check:cross-package-test-inputs,check:test-source-alias,check:type-check-coverageandcheck:type-check-debt.check:skill-examplesandcheck:lean-entry-closurefirst exited 3, because theclient,client-reactandobjectqldist were unbuilt. I built those three packages and re-ran both: exit 0.check:dual-build-cjs-loads. It exited 3 (PREREQUISITE NOT MET) because it needs every package built, and 72 have no dist here. Left to CI.pnpm --filter @objectstack/spec check:generated: all 14 generated artifacts are up to date, after the merge and a spec rebuild.test:repo(no repo-project test changed, and the derivation does not name it) andpnpm lint(a repo-wide scan, which CI owns).Acceptance notes
authoring-rule-wiring.test.tssays "the record of where [finding] The fourviews[]visibility-predicate rules are CLI-only — a Studio/REST/MCPviewwrite bypasses all of them; if they move to runtime-publish, they must move together #7220 left this: both are wired, forview". That is still true as a record, and its exactviewrule list is unchanged, so it is not edited.validatePredicatePathRefsonpageis inert by construction (P2). If page components ever gain a schema-bound data source, it lights with no registry edit.Generated by Claude Code