You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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
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
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.
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
As admin, install a scratch package PKG (POST /api/v1/packages) with an object and an app APP.
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.
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.
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.
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.
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/main2678600d: 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/main2678600d
State: pm:queue → pm:dispatched, assigned to os-project-manager, recorded by this claim. Order: pm-orders/server/order-28663.md in the project.
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"
]
}
Activity
objectstack-fleet commentedon Nov 1, 2026
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.
visiblewith a CEL syntax error passes save, draft diagnostics and publish, and then the entry silently disappears for every viewer. A pagevisibleWhenwith the same fault is refused. Page variables publish with duplicate, empty or wrong-typed declarations. p2.visibility-predicate-syntaxjudgment on navvisible, and validates the page variable declarations, at the authoring gate.Generated by Claude Code
objectstack-fleet commentedon Nov 1, 2026
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 issession_01FEkQ5nHMwmgiBBzV1TsunSAccount:
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:serverSeat:
domain:server#5Ruling-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 ownsvariables,rule-explanations.tsif a rule id is added),packages/metadata-protocol/src/runtime-authoring-gate.ts/protocol-authoring-gate.tsonly if the gate's path list lives there; tests; changesets. ⛔ Nopackages/formula/**(seat 1's #27904), ⛔ nopackages/spec/**, ⛔ noui/**.Container & model:
M,mode:thread, model and effort as the thread was openedClause-②: no
Metadata-protocol: narrowed
Responsibility: the code is the visibility-predicate and page rules in
@objectstack/lintas run by the@objectstack/metadata-protocolauthoring gate · the platform path covers pagevisibleWhenonly, not app navvisiblenor page variables · reached by any author saving an app or page, used today by StudioThread-read: 6558270258
Premise: holds (read at
origin/main2678600d:validate-visibility-predicates.tswalksvisibleWhensurfaces; no rule names appnavigation[].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) claimsmetadata-protocol/src/**for its save path; mergeorigin/mainbefore the PR.Resume-from: none — fresh from
origin/main2678600dState:
pm:queue→pm:dispatched, assigned toos-project-manager, recorded by this claim. Order:pm-orders/server/order-28663.mdin the project.objectstack-fleet commentedon Nov 1, 2026
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
objectstack-fleet commentedon Nov 1, 2026
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_enablethrough the relay; the merge queue closes this card.objectstack-fleet commentedon Nov 1, 2026
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 itsFixesline.256c86b51cpm:dispatchedremoved and the seat unassigned in this stroke,domain:*/area:*keptUnblocked in the same pass: #28725 and #28726 (
pm:blocked→pm:queue).