Skip to content

authoring gate: an app navigation visible with a CEL syntax error passes save, draft diagnostics and publish (a page visibleWhen with the same fault is refused), and page variables publish with duplicate, empty or wrong-typed declarations #28663

Closed

Description

@objectstack-fleet

domain:devx#1 · sunhaolin · session_017xRBcVy4VXmkQjn2MGeaWk

Filed by the PM seat for the W3-RT runner session_017xRBcRX4wwwVw5bEckffdp, whose own write was refused by its session permissions (RUNNER rule 2: staged text, posted verbatim through the same door).

QA-source: #28656 · studio-authoring.app-and-nav-properties · observed beside the clauses

What happens

  1. App nav visible with a CEL syntax error is admitted. PUT /api/v1/meta/app/APP?mode=draft&package=PKG with navigation[0].visible = "'org_owner' in" → 200. The draft read shows _diagnostics {"valid":true}, and POST /api/v1/packages/PKG/publish-drafts → published. At runtime the entry is withheld from every viewer (it fails closed), and the console logs [parse] Unexpected token: EOF. So the author gets no signal; the entry silently disappears for everyone.
    • Contrast: the same fault in a page visibleWhen is caught. Draft diagnostics report visibility-predicate-syntax, and publish is refused INVALID_METADATA.
  2. Page variable declarations are not validated (observed from the studio-authoring.authored-page-slots-and-variables probe). Each of these publishes with valid:true:
    • duplicate variable names;
    • an empty variable name;
    • a number variable with defaultValue: "abc";
    • a block source naming a component that does not exist;
    • a visibleWhen reading an undeclared page.nosuch.

Expected: the author-time gate refuses (or at minimum reports in draft _diagnostics) a predicate that does not parse on every surface that carries one, nav visible included, and refuses page variables that are duplicated, unnamed or whose default does not match their type.

Measured at framework 6e11a929 (main, 2026-11-01), on a stock showcase boot with scratch packages installed at runtime.

Reproduction

  1. As admin, install a scratch package PKG (POST /api/v1/packages) with an object and an app APP.
  2. PUT /api/v1/meta/app/APP?mode=draft&package=PKG with a navigation entry whose visible is "'org_owner' in" → 200. GET the draft → _diagnostics.valid: true. Publish → published.
  3. Open the app in the console as admin (who holds org_owner): the entry is absent, and the console log has [parse] Unexpected token: EOF.
  4. Contrast: PUT /api/v1/meta/page/PAGE?mode=draft&package=PKG with a block visibleWhen that does not parse → draft _diagnostics names visibility-predicate-syntax; publish → refused INVALID_METADATA.

Mechanism (suspected)

The runtime authoring gate (packages/metadata-protocol/src/runtime-authoring-gate.ts and protocol-authoring-gate.ts) runs the predicate-syntax rule over page visibleWhen but not over app navigation[].visible or areas[].navigation[].visible. Page variables have no rule for uniqueness, non-empty names or default-versus-type.

Related: #27457 (closed) made the console evaluate bare-string nav predicates as CEL; this card is the author-time half.

Activity

  1. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    on Nov 1, 2026
  2. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: 错的必须被响亮拒绝并给处方 | none | P②

    Triage: p2 · bug · domain:server · area:studio · pm:queue · 2026-11-01T12:58Z

    ⛔ Not a claim.

    • Read fresh. An app nav visible with a CEL syntax error passes save, draft diagnostics and publish, and then the entry silently disappears for every viewer. A page visibleWhen with the same fault is refused. Page variables publish with duplicate, empty or wrong-typed declarations. p2.
    • The fix runs the same visibility-predicate-syntax judgment on nav visible, and validates the page variable declarations, at the authoring gate.

    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 1, 2026
  4. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt

    Claim: PM loop round 1 · card thread · 2026-11-01T13:23Z
    Session: session_01FEkQ5oc56xChD6Wrc4cGSt (the seat); the card thread is session_01FEkQ5nHMwmgiBBzV1TsunS
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-oqpu4s (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#5
    Ruling-ref: 「定级你可以自己决定」 (Leehom, 2026-11-01T12:11Z) with the triage grade as filed; split among the server seats agreed in the project (seat 1 takes #28564, seat 5 takes #28565 #28661 #28658 #28663 #28665).
    File surface: packages/lint/src/ (the visibility-predicate rule, the page rule that owns variables, rule-explanations.ts if a rule id is added), packages/metadata-protocol/src/runtime-authoring-gate.ts / protocol-authoring-gate.ts only if the gate's path list lives there; tests; changesets. ⛔ No packages/formula/** (seat 1's #27904), ⛔ no packages/spec/**, ⛔ no ui/**.
    Container & model: M, mode:thread, model and effort as the thread was opened
    Clause-②: no
    Metadata-protocol: narrowed
    Responsibility: the code is the visibility-predicate and page rules in @objectstack/lint as run by the @objectstack/metadata-protocol authoring gate · the platform path covers page visibleWhen only, not app nav visible nor page variables · reached by any author saving an app or page, used today by Studio
    Thread-read: 6558270258
    Premise: holds (read at origin/main 2678600d: validate-visibility-predicates.ts walks visibleWhen surfaces; no rule names app navigation[].visible)
    Serial constraints cleared: #27904 (seat 1) edits packages/formula/src/validate.ts; this card reuses formula's parser without editing it. #28661 (this seat) touches the roll-up rule, a different rule region. #28413 (seat 3) claims metadata-protocol/src/** for its save path; merge origin/main before the PR.
    Resume-from: none — fresh from origin/main 2678600d

    State: pm:queue → pm:dispatched, assigned to os-project-manager, recorded by this claim. Order: pm-orders/server/order-28663.md in the project.

  5. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5nHMwmgiBBzV1TsunS

    os-dev-report
    {
    "issue": 28663,
    "status": "done",
    "branch": "claude/project-thread-oqpu4s",
    "pr": "#28723",
    "session": "session_01FEkQ5nHMwmgiBBzV1TsunS",
    "premise_still_valid": true,
    "summary": "validateVisibilityPredicates now walks apps[].navigation, apps[].areas[].navigation and every entry's children, and runs the existing visibility-predicate-syntax judgment (celRefusal over formula's parseCelToAstWithReason, no second parser) on each entry's visible (bare string or dialect cel envelope; other dialects and booleans are not judged), located at the key; one syntaxFinding builder serves page/view and nav, differing only in the consequence clause (nav: hidden from every viewer) and the remedy example. Only the syntax verdict runs on nav, because the family's reference checks judge view/page roots and would refuse console-valid nav predicates such as features.cloudManaged != true. A new gating rule validatePageVariables (page-variable-name-duplicate, page-variable-name-empty, page-variable-default-type; null admitted, no type = string) runs on page writes; the visibility family declares runtimeTypes view/page/app, so save, draft _diagnostics, publish and os validate all carry both verdicts with no metadata-protocol source change. No packages/spec or packages/formula edit. Corpus: 1 nav visible and 5 page variables shipped, 0 refused.",
    "tests": "Red on base 2678600: the two new lint files Test Files 2 failed, Tests 20 failed | 11 passed (31, the 11 are controls); head 31/31. Protocol: 5 new rows in protocol.draft-publish-verdict.test.ts (draft saves, reads valid:false with the publish words, publish 422 INVALID_METADATA) plus clean and path tests, 25/25; new #28663 block in protocol.runtime-authoring-gate.test.ts (active save refused, door finding equals build finding key by key, nothing stored; controls land) 6/6. Ablations via scripts/ablation-replace.mjs with lint rebuilt and ablation-dist-preflight on both legs: nav syntax arm off -> lint 7 failed, protocol 2 failed; duplicate off -> 3 / 2; empty off -> 4 / 2; default type off -> 8 / 3; every leg restored blob == HEAD, git diff HEAD empty, preflight --absent exit 0 (pm-orders/server/evidence/28663/ablations.txt). Live probe on a stock showcase boot (pnpm --filter @objectstack/example-showcase dev -- --fresh -p PORT, probe.sh in evidence/28663): base step 2 draft valid:true and publish-drafts published, active PUT 200, variables valid:true; head step 2 draft valid:false at navigation.0.visible, publish-drafts refused INVALID_METADATA, active PUT 422, variables valid:false with the three ids; step 4 page contrast refused on both with the same message. os validate on examples/app-crm with the nav visible planted to 'org_owner' in: base exit 0 Validation passed, head exit 1 at apps[0].navigation[1].children[0].visible. Package suites at 9fcdbbc: lint 186 files / 7749 tests passed; metadata-protocol 291 passed 4 skipped / 21963 passed 26 skipped; both typecheck exit 0; eslint packages/lint packages/metadata-protocol --no-inline-config exit 0.",
    "gates": "Local at bb33ace: dispatch-gates --commands --repo objectstack-ai/objectstack derived 89 families (13 paths plus the 4 docs transcripts); each run with exit captured before any pipe; 89 exit 0; --ran: 89 derived famil(ies) accounted for, 89 run, 0 NOT-MEASURED (record pm-orders/server/28663-gates-ran.txt). Earlier loop at 9fcdbbc: check:docs-transcript-drift exit 1 (CLI transcripts printed 57 rules, registry derives 58), fixed in bb33ace; check-plugin-teardown-shape --self-test exit 3 shallow clone, exit 0 after git fetch --depth=1 origin 621a487. check:type-check-debt under the lock: re-measure OK, 0 raw tsc errors. CI families: PR #28723 check-runs not read at report time (just opened); not re-run locally.",
    "dogfood_battery": "bb33ace7 · os-verify-lock: VERDICT command-exit 0 · Test Files 369 passed | 3 skipped (372) · Tests 2981 passed | 28 skipped (3009) · no failures",
    "line_budget": "17 files changed, 978 insertions(+), 28 deletions(-) vs merge base; no ledger or ratchet budget named by the order",
    "mcp_calls": "0",
    "api_writes": "2 - fleet-write relay pr_create (PR #28723, draft, no labels: the order names none and skip-changeset does not apply, no assignees; read-back 12883 bytes identical) and this os-dev-report comment via post-stamped. Plus 1 git push (not REST). Card and claim reads were single-card REST GETs.",
    "open_questions": [],
    "deviations": [
    "Clause-② line: the claim reads Clause-②: no; the PR body and changeset carry Clause-②: no (narrowing), because check-changeset-no-major.mjs refuses Metadata-protocol: narrowed beside a line without the (narrowing) arm, and the order's Declarations name it a narrowing. The seat may want to amend the claim line.",
    "File surface: content/docs/** four CLI transcripts edited (author-time rules (57) to (58)), forced by check:docs-transcript-drift after the new rule; and four existing lint test pins updated (validate-security-posture.runtime-surface, validate-predicate-path-refs, runtime-gate.page-visibility-predicate-writes) for the app and page doors' new rule lists."
    ],
    "out_of_scope_findings": [
    "class: a · reach: draft read on a stock showcase boot, PUT /api/v1/meta/page/oos_src?mode=draft with variables[0].source = no_such_component, _diagnostics valid:true · a page variable source naming a component the page does not hold is admitted (out of scope by the order) · dedupe words: page variable source unknown component, variables source binding, page-variable-source",
    "class: a · reach: draft read on a stock showcase boot, PUT /api/v1/meta/page/oos_nosuch?mode=draft with visibleWhen page.nosuch == 'x', _diagnostics valid:true · a visibleWhen reading an undeclared page variable is admitted (out of scope by the order) · dedupe words: undeclared page variable, page.nosuch, visibleWhen page root",
    "class: a · reach: draft read on a stock showcase boot, an app nav visible holding a 90-clause || chain (valid CEL over maxAstNodes), _diagnostics valid:true; the page-side twin is visibility-predicate-over-budget; console effect inferred from the shared CEL limits, not measured in a browser · dedupe words: nav visible over budget, navigation visible maxAstNodes, visibility-predicate-over-budget app",
    "carrier: none · noted, not filed: on a nav entry an other-language operator (===) gets the shared operator table's record-rooted example (record.country == 'USA'); the operator correction itself is right"
    ],
    "files_changed": [
    ".changeset/28663-nav-visible-syntax-page-variables.md",
    "content/docs/deployment/cli.mdx",
    "content/docs/deployment/validating-metadata.mdx",
    "content/docs/getting-started/build-with-claude-code.mdx",
    "content/docs/ui/react-pages.mdx",
    "packages/lint/src/authoring-rules.ts",
    "packages/lint/src/index.ts",
    "packages/lint/src/rule-explanations.ts",
    "packages/lint/src/runtime-gate.app-nav-visible-predicate-writes.test.ts",
    "packages/lint/src/runtime-gate.page-variable-writes.test.ts",
    "packages/lint/src/runtime-gate.page-visibility-predicate-writes.test.ts",
    "packages/lint/src/validate-page-variables.ts",
    "packages/lint/src/validate-predicate-path-refs.test.ts",
    "packages/lint/src/validate-security-posture.runtime-surface.test.ts",
    "packages/lint/src/validate-visibility-predicates.ts",
    "packages/metadata-protocol/src/protocol.draft-publish-verdict.test.ts",
    "packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts"
    ]
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt

    ACCEPT → PR #28723 comment 6560250483 · head bb33ace7 · 2026-11-01T15:53Z. Contract review PASS (comment 6560166888). Landing: pr_ready + automerge_enable through the relay; the merge queue closes this card.

  7. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt

    Landed · 2026-11-01T16:17Z · PR #28723 merged through the merge queue as 256c86b51c; this card closed as completed by its Fixes line.

    reading value
    PR state merged, 256c86b51c
    card closed (completed); pm:dispatched removed and the seat unassigned in this stroke, domain:* / area:* kept

    Unblocked in the same pass: #28725 and #28726 (pm:blocked → pm:queue).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions