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
spec(ui): an app page predicate has no root for the row its page binds, so visibleWhen cannot follow a bound count or the organisation's tier (cloud#2774 Q2 = B, objectui#12931) #24265
Filing gate ④: the packages/spec half of a ruled renderer card. Filed by the triage seat (#6015, session_01AavokzJ5DndAwitDXvKy4U) on the domain:ui seat 1's retriage request on objectstack-ai/objectui#12931, which asked for the root choice to be routed to domain:spec first (cross-repo rule 2; the maintainer's standing 「平台相关的功能,应该尽量先改平台。」). ⛔ Not a claim.
Pull (ruled): objectstack-ai/cloud#2774 Q2 = B (「同意」, director batch #362). On the control plane's environments page, visibleWhen must follow the organisation's tier and the bound count:
新建环境 only on paid tiers;
the 🔒 line on Free;
the 生产 tag once a second environment exists.
Reader who acts: the domain:spec seat. It names the root, then objectui#12931 builds the renderer half.
Measured
objectui main79cb8c03eb: buildExpressionScope (packages/app-shell/src/providers/ExpressionProvider.tsx:181) returns { current_user, user, ctx, os, features } (:190). No bound row is in it, and no plan.
objectstack origin/main:
the spec-declared page root is page.<var>, and the lint's extra roots are current_user and page (packages/lint/src/validate-visibility-predicates.ts:775);
data is in @objectstack/formula's SCOPE_ROOTS but was ruled out of this tier, as the docblock above buildExpressionScope records.
Questions this card answers (the seat's call, spec first)
The root for an app page's bound row: a new root, or a defined meaning under page. Either one is spec text plus lint work.
Tier, without a cloud-only root. This seat's reading, not a ruling: the existing features root may already express it, if the host maps the plan to feature flags. If it can, no new root is added for tier.
The count: read from a roll-up on the bound row through question 1's root, not from a new root.
⛔ No root that only the control plane could fill. Whatever is named is a platform root every host can serve.
The 1000 most recently updated objectstack-ai/objectstack items were enumerated over REST (543 issues; control #24162 found) and grepped for predicate / visibleWhen near "bound row", "app page", "page root" or "plan": 1 hit, #24096 (a QA run on relationship-traversal predicates), not this.
Claim: PM loop round 5 · 2026-10-18T06:15Z · name, spec first, how a page component's visibleWhen on an app page reads the row a block binds (and a roll-up count on it) and the organisation's tier, using roots every host can serve, then the spec text and lint work that follow (the spec half of objectstack-ai/objectui#12931)
Session: session_01TyRhWbVbbQpQJTDbW8VahJ (the seat; the dev is a cloud session whose draft PR carries its own session link, named on the seat post)
Account: os-project-manager (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-24265-app-page-predicate-roots
Worktree: objectstack-issue-24265 (in the cloud session's own container)
Domain: domain:spec
Seat: domain:spec#1 (seat post #6017)
File surface (read on origin/maina422f04fe; line numbers move; stop on a breach and explain it in the report):
packages/spec/src/ui/page.zod.ts: the visibleWhen docblock and describe (about :668–:729), which already name record (only when the page has a row), current_user and page.NAME as contract-bound and features as ambient, not contract; PageVariableSchema (about :805) if the route uses it.
packages/lint/src/validate-visibility-predicates.ts: VIEW_PAGE_EXTRA_ROOTS (about :775) and its pins, if the declared root set moves.
packages/formula/src/cel-engine.tsSCOPE_ROOTS only if the route needs it; features is already in it.
packages/spec/liveness/page.json (row regions) only if the route re-cites a renderer read point.
Generated artifacts by their generators only; content/docs/references/** as the generators write it; a hand-written page only where it restates the predicate roots this PR changes; one .changeset/24265-*.md.
⛔ Hold-offs: objectui (objectui#12931 builds the renderer half); .objectui-sha; packages/lint/src/rule-explanations.ts (held by lint: a locale pack's picklists.<name> translations are never checked — an option key that is not in the list, or a list no package declares, passes os lint --strict and is dropped silently at runtime #23566's claim and domain:engine's A formula field that reads through a reference (record.book.title) passes validateExpression, then reads null on every row: the read path projects only the row's own columns #24197) unless the route needs a new rule entry, then serial behind them; release notes under content/docs/releases/.
Container & model: M, mode:cloud (the surface spans packages/spec/src/** and packages/lint; one card, one container), model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate); review ceiling CONTRACT_REVIEW_TIER, reason: a widening of the published predicate contract on packages/spec/src/** (contract-review.md, 档随面定). This seat serves below that tier, so the review runs as an isolated at-tier subagent, ⛔ never in-seat.
Clause-②: yes (widening)
Basis for the Clause-② line: the diff's direction. Each of the card's three answers adds a meaning to the predicate surface's contract-bound roots (a bound row's meaning, a tier reading, a count read through the first), and nothing narrows. If the measured route declares nothing new, the dev reports that and this line is revisited before any PR.
Responsibility: whose code produces the risk: packages/spec names no contract-bound root for the row an app page's block binds, and names features as ambient only, so an author cannot write a visibleWhen that follows a bound count or the tier | the platform path that already covers it: none on app pages; record binds only when the page has a row | who reaches it: any app page that gates a block on bound data or on the plan; the control plane's environments page is the ruled pull
Thread-read: none
Ruling-ref: objectstack-ai/cloud#2774 Q2 = B (「同意」, director batch Fix lychee exclude-path regex patterns in link checker workflow #362), as the card body quotes it: visibleWhen must follow the organisation's tier and the bound count. The mechanism is the seat's call, spec first, with no root that only the control plane could fill. This session cannot read that repository; the card's quote is the scope.
Serial constraints cleared:
The surface is not governed (check-governed-merges.mjs --test: 0 of 4 paths).
Prior rulings: check-prior-rulings.mjs --card 24265 names ADR-0068 D1 (one predicate evaluates identically in a formula, an RLS policy and a client gate) and ADR-0089 among its hits. The route must not contradict either; the dev reads both before choosing.
{
"issue": 24265,
"status": "done",
"branch": "claude/issue-24265-app-page-predicate-roots",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24275",
"session": "session_012SGQNHx8abCKZKoyj2VQym",
"premise_still_valid": true,
"summary": "No new root. PageSchema.record (PageRecordBindingSchema, a strict one-row query { object, view?, filter?, sort? }) gives a type: 'app' page its row, and every component visibleWhen reads it as the existing contract-bound root record. The tier and the count are both fields on that row: the object's plan field, and a summary roll-up such as record.environment_count > 1. features is not a tier root: it is the deployment-level, anonymous, drift-guarded PUBLIC_AUTH_FEATURES registry served by plugin-auth, the same for every organisation, so an org plan there could only be filled by the control plane. It stays ambient, and the docs now say so by name. The binding is refused on every page that is not type: 'app' (checkPageRecordBindingPlacement, exported and attached by identifier). The liveness ledger grades it planned until objectui#12931 mounts it. Lint and formula are unchanged, because the root set did not move. Measured H1 true. H2 true: an app page had no page-level row, and a record block's bound row reaches only its own body, never its own or a sibling's visibleWhen. H3: PageVariable source is an interactive selection writer only. H4: features.X already lints, but features is deployment config, not org tier. H5: neither ADR-0068 D1 (the user object only) nor ADR-0089 D1 (runtime surfaces bind record) is contradicted.",
"tests": "All on head a150ec532f. `pnpm --filter @objectstack/spec test`: 693 files, 21015 passed, 1 todo. `test:repo`: 68 files, 1125 passed. `typecheck`: exit 0. `build`: exit 0. `check:generated`: all 14 artifacts up to date. Ablation through scripts/ablation-replace.mjs with a trap restore: the accept arm `if (type === 'app') return;` was changed to `'app_ablated'`; object-refinement-check-exports.test.ts then failed exactly the accepting app fixture (expected [ 'record' ] to deeply equal [], 1 failed / 251 passed); the file was restored with blob == HEAD and an empty git diff HEAD. A first attempt was refused by the tool because the replacement was a substring of the anchor; it was restored and is declared here.",
"gates": "Local: dispatch-gates --commands derived 111 commands; all 111 run with exit codes captured before any pipe. 104 exited 0 on the first pass. 7 exited 3 on prerequisites: check-plugin-teardown-shape --self-test (shallow clone), lint check:doc-formula-expressions and check:doc-security-posture, spec check:skill-examples, check:docs-transcript-drift, check:dual-build-cjs-loads and check:lean-entry-closure (unbuilt closures). All 7 exited 0 after the lint/client-react/objectql closure build, a full turbo build and git fetch --unshallow. The 22 doc-family gates were re-run on HEAD after the pages.mdx commit: all 0. `--ran` reconciliation: 111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). check-changeset-no-major --event with the PR body: exit 0, level axis accepts yes (widening) at minor. clause2-line.mjs reads the same line from the body and the changeset. check-nul-bytes: exit 0. CI check-runs on PR #24275: in_progress when this report was written, not awaited per os-dev.",
"mcp_calls": "5 MCP GitHub calls, all reads: issue_read get (24265), issue_read get_comments (24265, twice: before work and the report read-back), pull_request_read get (24275, PR read-back), plus ToolSearch loads. Also claude-code-remote add_repo (read, objectui), which attached nothing because it is public anonymous git read. No MCP write tool used.",
"api_writes": "3: git push of the branch (several pushes of the one branch, the first as the empty-branch probe); fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls plus POST /issues/24275/assignees, run 39108424762, body read back byte-identical); fleet-write relay comment (POST /repos/objectstack-ai/objectstack/issues/24265/comments, this report).",
"files_changed": [
"packages/spec (src/ui/page.zod.ts, src/ui/object-refinement-check-exports.test.ts, src/system/metadata-form-zod-reconciliation.test.ts, liveness/page.json, liveness/state-counts/page.md, dropped-refinements.baseline.json, generated: api-surface/ui.json, authorable-surface/ui.json, declaration-map/ui.json, export-origins/ui.json, json-schema.manifest/ui.json)",
"content/docs (ui/pages.mdx hand-written; references/index.mdx, references/ui/page.mdx generated)",
"docs/audits/2026-07-unknown-key-strictness-ledger.counts/ui.md (generated)",
".changeset/24265-app-page-record-binding.md",
"No other package: lint, formula and objectui untouched. No governed path: the skills reference index is kept byte-identical by export order."
],
"line_budget": "n/a (337 changed lines, under the ~400 stop line)",
"deviations": [
"Conflict named: the dispatch prompt asks for each edited package's test:repo; os-dev says test:repo is CI's unless the diff edits that project's tests. I ran it anyway (green), so nothing is skipped.",
"The dispatch prompt names packages/spec/liveness/page.json only if the route re-cites a renderer read point. The route adds a new authorable key, so check:liveness requires a new planned row, drilled to its leaves instead of growing the undrilled-containers baseline. dropped-refinements.baseline.json moved because the new filter is now the first ViewFilterRule site under ui/Page.",
"Worktree: the primary checkout was detached so the claim's branch could be checked out in ../objectstack-issue-24265, rather than creating a new branch from origin/main. The branch was already at origin/main a422f04fe."
],
"open_questions": [
{
"question": "Q1: what root does an app page's bound row use?",
"options": [
"A: PageSchema.record, a one-row page binding read through the existing record root (implemented)",
"B: a bound block publishes its row into page.NAME through PageVariable.source",
"C: a new root such as bound.NAME"
],
"axes": "Business: A serves the ruled environments page directly (bind the organisation row; count and tier are fields on it); B only when a block showing the row exists; C same reach. Long-term: A reuses ADR-0089 D1's runtime root and the record blocks' one-row semantics; B changes what PageVariable.source means (selection writer to data publisher) and adds a load-timing model; C adds a third row spelling beside record and data. AI-error: A is a strict shape, refused off app pages, limit/id/recordId refused with prescriptions, with a declared planned window until objectui#12931; B gives two writers two meanings under one key; C is a new root that lint, formula and the editor must all learn. Startup: A is one key on an existing shape, with no root, lint or gate change.",
"recommendation": "A. It dominates on all four axes. No new root, ADR-0089 D1-consistent, strict and loud, one key. The contract review should judge the key name `record` (it equals the root it binds) against the alternative spellings that are aliased to it."
},
{
"question": "Q2: how is the tier read without a cloud-only root?",
"options": [
"A: a field on the bound row, record.plan (implemented as docs; no spec change needed)",
"B: promote features to contract-bound and map the plan to feature flags"
],
"axes": "Business: A uses data the control plane already owns on its org row; B needs a per-org flag in a deployment-level anonymous registry. Long-term: A keeps platform mechanism and app data separate; B breaks PUBLIC_AUTH_FEATURES' meaning and its drift guard against getPublicConfig(). AI-error: A gives one place for row data; B puts two meanings under one root. Startup: A adds nothing; B is a cloud-only fill, which the card forbids.",
"recommendation": "A. features stays ambient. Promoting it is not decided here and was not needed."
},
{
"question": "Q3: where does the count come from?",
"options": [
"A: a summary roll-up field on the bound row, read as record.FIELD (implemented as docs)",
"B: a dedicated count root or aggregate expression"
],
"axes": "Business: A covers 'a second environment exists' with record.environment_count > 1; B is speculative. Long-term: A uses the existing summary field type; B is a new evaluator capability. AI-error: A means one root and one field; B means a new surface to get wrong. Startup: A adds nothing.",
"recommendation": "A. The cloud side needs a summary field on its organisation object (master-detail from environments). That is the app's data, not the platform's."
}
],
"out_of_scope_findings": [
"class: none filed. The content/docs/ui/pages.mdx visibleWhen row still calls `visibility` an accepted deprecated alias, but at 18 it is a retiredKey tombstone. Pre-existing doc drift. carrier: none. Noted, not filed (in Acceptance notes). dedupe: pages.mdx visibility alias, retired visibility docs."
]
}
{
"issue": 24265,
"status": "done",
"round": "patch round 1",
"branch": "claude/issue-24265-app-page-predicate-roots",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24275",
"session": "session_012SGQNHx8abCKZKoyj2VQym",
"head": "03b3e5ad4b",
"premise_still_valid": true,
"failing_assertion": "Reproduced with `pnpm --filter @objectstack/metadata-protocol test` on head a150ec532f (exit 1; 1 failed / 20640 passed). Failing test: src/protocol.meta-types-degenerate-derivation.test.ts > '#17501 — /meta/types serves a real schema for `action`, and moves nothing else' > 'control: `page` still serves 25 top-level properties'. Assertion: `AssertionError: expected 26 to be 25 // Object.is equality`, at src/protocol.meta-types-degenerate-derivation.test.ts:339:15.",
"root_cause": "This PR. CARD_PROPERTY_COUNTS is a census: a positive control that pins how many top-level properties /meta/types serves per type. PageSchema gained one declared key (`record`), so the served page schema has 26 properties. The derivation itself did not change. The file's own docblock records each such move as a declared key, not a derivation change; `page` moved 24 to 25 the same way for `print`. So this is the census case: the count must name the new key. The fix is page 25 to 26, with a docblock line naming `record`. The test is not loosened: every other count and the derivation assertions are unchanged.",
"summary": "Census bumped with its recorded reason. Review flag F1 folded in: the pages guide's visibleWhen cell no longer calls the `visibility` alias accepted; it now says the retired spelling is refused at parse (ADR-0089 D2). Review flag F2 folded in: the liveness `record` row's planned note names its consumer card objectstack-ai/objectui#12931. No decision the review judged was touched (key name, placement rule). The PR body and title are unedited.",
"files_changed": [
"packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts",
"content/docs/ui/pages.mdx",
"packages/spec/liveness/page.json"
],
"tests": "Head 03b3e5ad4b: `pnpm --filter @objectstack/metadata-protocol test` exit 0 (244 files passed, 3 skipped; 20641 passed, 19 skipped). `pnpm --filter @objectstack/spec test` exit 0 (693 files; 21015 passed, 1 todo). `pnpm --filter @objectstack/spec test:repo` exit 0 (68 files; 1125 passed).",
"gates": "Head 03b3e5ad4b, exit codes captured before any pipe: `pnpm --filter @objectstack/spec build` 0. `pnpm --filter @objectstack/spec check:generated` 0 ('All 14 generated artifacts are up to date'). `pnpm --filter @objectstack/spec check:liveness` 0. Doc gates on the edited page: check:doc-authoring 0, check:docs-spec-enumerations 0, check:doc-anchors 0, docs-audit/check-affected-docs 0, check-doc-frontmatter 0. The full derived set was not re-run this round: the PM's list was run as asked, and CI covers the rest. CI check-runs on the new head: not awaited.",
"mcp_calls": "0 MCP GitHub calls this round before the report read-back; the read-back is 1 issue_read get_comments (read). No MCP write tool used.",
"api_writes": "2: git push of claude/issue-24265-app-page-predicate-roots (fast-forward a150ec532f to 03b3e5ad4b); fleet-write relay comment (POST /repos/objectstack-ai/objectstack/issues/24265/comments, this report).",
"deviations": [],
"open_questions": [],
"out_of_scope_findings": []
}
{
"issue": 24265,
"status": "done",
"round": "patch round 2",
"branch": "claude/issue-24265-app-page-predicate-roots",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24275",
"session": "session_012SGQNHx8abCKZKoyj2VQym",
"head": "88dbe4191e",
"premise_still_valid": true,
"summary": "Merged origin/main 9822b053e2, which includes PR #24270 as 586705d85d, into the branch with scripts/pm/os-regen-merge.sh. It is a merge commit, 6480ad0b61, followed by a separate regeneration commit, 88dbe4191e. There was no rebase and no force: the push fast-forwarded 03b3e5ad4b to 88dbe4191e. No decision the contract review judged was touched: the key name, the placement rule and the planned grading are all unchanged.",
"conflicts": [
"packages/spec/dropped-refinements.baseline.json — the only textual conflict, in the `measured` header totals: HEAD 237/768, main 238/770, base 236/767. Resolved by hand to the sum of both deltas, 239 schemas and 771 sites. Both sides' entries text-merged cleanly: #24270's three emptyState.link.url sites and its counts, plus this PR's ui/PageRecordBinding and the moved ui/Page record.filter.element site. The spec build's ledger check passed on the merged file, and dropped-refinements.test.ts, which pins the totals against the body, is green.",
"packages/spec/authorable-surface/ui.json — generated and routed to the os-regen driver, so it was not text-merged. Step 2 took main's side, both sides having moved it. It was then regenerated by `pnpm --filter @objectstack/spec build` from the merged tree and committed as 88dbe4191e, which discharged the pre-commit deferral. It carries both sides: PageRecordBinding 4 hits, emptyState 7.",
"packages/spec/liveness/page.json — auto-merged with no conflict. The branch's delta against origin/main is exactly this PR's `record` row, including the objectui#12931 note. #24270's PLANNED RECORD sentences in `_note` are kept, verified by grep.",
"packages/spec/liveness/state-counts/page.md — only the branch changed it. The merge script kept the branch's bytes, and check:liveness re-derives it as current.",
"content/docs/references/ui/component.mdx — only main changed it, so it was taken as is. check:generated reports all 14 artifacts current; component.mdx carries the emptyState text."
],
"census": "Measured: protocol.meta-types-degenerate-derivation.test.ts still pins `page: 26`, and it passes on the merged tree. #24270 added no PageSchema key.",
"files_changed": [
"merge commit 6480ad0b61: main's files, plus a hand resolution of packages/spec/dropped-refinements.baseline.json (measured totals)",
"88dbe4191e: packages/spec/authorable-surface/ui.json (regenerated)"
],
"tests": "Head 88dbe4191e. `pnpm --filter @objectstack/spec test`: exit 0, 694 files, 21040 passed, 1 todo. `test:repo`: exit 0, 68 files, 1125 passed. `pnpm --filter @objectstack/metadata-protocol test`, after building its closure: exit 0, 244 files passed, 3 skipped; 20641 passed, 19 skipped. scripts/dropped-refinements.test.ts: exit 0, 27 passed.",
"gates": "Exit codes captured before any pipe. `pnpm --filter @objectstack/spec build`: 0. `check:generated`: 0 ('All 14 generated artifacts are up to date'). `check:liveness`: 0. One earlier spec `test` attempt exited 99: it waited on my own test:repo run holding the lock and measured nothing; it is re-run above. CI on 88dbe4191e: not awaited.",
"mcp_calls": "1 MCP GitHub call this round: issue_read get_comments for the report read-back. No MCP write tool used.",
"api_writes": "2: git push (fast-forward 03b3e5ad4b..88dbe4191e); fleet-write relay comment (POST /repos/objectstack-ai/objectstack/issues/24265/comments, this report).",
"deviations": [],
"open_questions": [],
"out_of_scope_findings": []
}
Landed · PR #24275 → ef65646843 (Fixes #24265, an app page's visibleWhen reads its bound row through the existing record root) · domain:spec seat 1 · os-project-manager · session session_01TyRhWbVbbQpQJTDbW8VahJ · 2026-10-18T10:09Z
probe
reading
PR object
merged 2026-10-18T10:08Z out of the merge queue (entered 2026-10-18T09:35Z); merge commit ef65646843 on parent 8bfb17d472
origin/main
git merge-base --is-ancestor ef65646843 origin/main → exit 0 (control: the PR head 88dbe4191e → exit 1, a squash merge)
merged vs reviewed head
git patch-id --stable is equal with context: the merged diff over 8bfb17d472 and the reviewed head 88dbe4191e over its merge base 9822b053e2 both give dbecc617 (ACCEPT 6248293061, contract review PASS 6248097604)
queue branch
no gh-readonly-queue/main/pr-24275-* ref remains
mis-close scan
Fixes #24265 is the squash message's only closing keyword. Its other references are PR #24270 (merged before this one) and objectstack-ai/objectui#12931, which the message also spells once as a bare objectui#12931 (prose, no closing keyword; objectstack#12931 closed on 2026-09-03 and is untouched). This card closed completed by that PR alone
pm:dispatched is cleared in this act. The cloud dev session was archived at ACCEPT.
objectstack-ai/cloud#2774, the control plane's own data: a plan field and a summary roll-up of environments on its organisation object, and its environments page as a type: 'app' page whose record binding filters on {current_org_id}. This seat cannot read that repository; the pointer is on the seat post and in the stand-down brief.
Activity
objectstack-fleet commentedon Oct 18, 2026
Claim: PM loop round 5 · 2026-10-18T06:15Z · name, spec first, how a page component's
visibleWhenon anapppage reads the row a block binds (and a roll-up count on it) and the organisation's tier, using roots every host can serve, then the spec text and lint work that follow (the spec half of objectstack-ai/objectui#12931)Session:
session_01TyRhWbVbbQpQJTDbW8VahJ(the seat; the dev is a cloud session whose draft PR carries its own session link, named on the seat post)Account:
os-project-manager(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24265-app-page-predicate-rootsWorktree:
objectstack-issue-24265(in the cloud session's own container)Domain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface (read on
origin/maina422f04fe; line numbers move; stop on a breach and explain it in the report):packages/spec/src/ui/page.zod.ts: thevisibleWhendocblock and describe (about:668–:729), which already namerecord(only when the page has a row),current_userandpage.NAMEas contract-bound andfeaturesas ambient, not contract;PageVariableSchema(about:805) if the route uses it.packages/lint/src/validate-visibility-predicates.ts:VIEW_PAGE_EXTRA_ROOTS(about:775) and its pins, if the declared root set moves.packages/formula/src/cel-engine.tsSCOPE_ROOTSonly if the route needs it;featuresis already in it.packages/spec/liveness/page.json(rowregions) only if the route re-cites a renderer read point.content/docs/references/**as the generators write it; a hand-written page only where it restates the predicate roots this PR changes; one.changeset/24265-*.md..objectui-sha;packages/lint/src/rule-explanations.ts(held by lint: a locale pack'spicklists.<name>translations are never checked — an option key that is not in the list, or a list no package declares, passesos lint --strictand is dropped silently at runtime #23566's claim anddomain:engine's A formula field that reads through a reference (record.book.title) passes validateExpression, then reads null on every row: the read path projects only the row's own columns #24197) unless the route needs a new rule entry, then serial behind them; release notes undercontent/docs/releases/.Container & model:
M,mode:cloud(the surface spanspackages/spec/src/**andpackages/lint; one card, one container),model: TIER_DEFAULT(dispatch-gates --tier: no path-derived mandate); review ceilingCONTRACT_REVIEW_TIER, reason: a widening of the published predicate contract onpackages/spec/src/**(contract-review.md, 档随面定). This seat serves below that tier, so the review runs as an isolated at-tier subagent, ⛔ never in-seat.Clause-②: yes (widening)
Basis for the Clause-② line: the diff's direction. Each of the card's three answers adds a meaning to the predicate surface's contract-bound roots (a bound row's meaning, a tier reading, a count read through the first), and nothing narrows. If the measured route declares nothing new, the dev reports that and this line is revisited before any PR.
Responsibility: whose code produces the risk:
packages/specnames no contract-bound root for the row anapppage's block binds, and namesfeaturesas ambient only, so an author cannot write avisibleWhenthat follows a bound count or the tier | the platform path that already covers it: none onapppages;recordbinds only when the page has a row | who reaches it: anyapppage that gates a block on bound data or on the plan; the control plane's environments page is the ruled pullThread-read: none
Ruling-ref: objectstack-ai/cloud#2774 Q2 = B (「同意」, director batch Fix lychee exclude-path regex patterns in link checker workflow #362), as the card body quotes it:
visibleWhenmust follow the organisation's tier and the bound count. The mechanism is the seat's call, spec first, with no root that only the control plane could fill. This session cannot read that repository; the card's quote is the scope.Serial constraints cleared:
singleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204, lint: a locale pack'spicklists.<name>translations are never checked — an option key that is not in the list, or a list no package declares, passesos lint --strictand is dropped silently at runtime #23566, metadata-protocol: duplicatePackage renames a token that names both a prefixed package object and one of its fields as the object, so a copied view's column or form field names a field the copy does not have (family close-out) #23561) anddomain:engine's declared A formula field that reads through a reference (record.book.title) passes validateExpression, then reads null on every row: the read path projects only the row's own columns #24197 (validate-expressions.ts,rule-explanations.ts) name none of these files, exceptrule-explanations.ts, held off above.check-governed-merges.mjs --test: 0 of 4 paths).check-prior-rulings.mjs --card 24265names ADR-0068 D1 (one predicate evaluates identically in a formula, an RLS policy and a client gate) and ADR-0089 among its hits. The route must not contradict either; the dev reads both before choosing.objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{ "issue": 24265, "status": "done", "branch": "claude/issue-24265-app-page-predicate-roots", "pr": "https://github.com/objectstack-ai/objectstack/pull/24275", "session": "session_012SGQNHx8abCKZKoyj2VQym", "premise_still_valid": true, "summary": "No new root. PageSchema.record (PageRecordBindingSchema, a strict one-row query { object, view?, filter?, sort? }) gives a type: 'app' page its row, and every component visibleWhen reads it as the existing contract-bound root record. The tier and the count are both fields on that row: the object's plan field, and a summary roll-up such as record.environment_count > 1. features is not a tier root: it is the deployment-level, anonymous, drift-guarded PUBLIC_AUTH_FEATURES registry served by plugin-auth, the same for every organisation, so an org plan there could only be filled by the control plane. It stays ambient, and the docs now say so by name. The binding is refused on every page that is not type: 'app' (checkPageRecordBindingPlacement, exported and attached by identifier). The liveness ledger grades it planned until objectui#12931 mounts it. Lint and formula are unchanged, because the root set did not move. Measured H1 true. H2 true: an app page had no page-level row, and a record block's bound row reaches only its own body, never its own or a sibling's visibleWhen. H3: PageVariable source is an interactive selection writer only. H4: features.X already lints, but features is deployment config, not org tier. H5: neither ADR-0068 D1 (the user object only) nor ADR-0089 D1 (runtime surfaces bind record) is contradicted.", "tests": "All on head a150ec532f. `pnpm --filter @objectstack/spec test`: 693 files, 21015 passed, 1 todo. `test:repo`: 68 files, 1125 passed. `typecheck`: exit 0. `build`: exit 0. `check:generated`: all 14 artifacts up to date. Ablation through scripts/ablation-replace.mjs with a trap restore: the accept arm `if (type === 'app') return;` was changed to `'app_ablated'`; object-refinement-check-exports.test.ts then failed exactly the accepting app fixture (expected [ 'record' ] to deeply equal [], 1 failed / 251 passed); the file was restored with blob == HEAD and an empty git diff HEAD. A first attempt was refused by the tool because the replacement was a substring of the anchor; it was restored and is declared here.", "gates": "Local: dispatch-gates --commands derived 111 commands; all 111 run with exit codes captured before any pipe. 104 exited 0 on the first pass. 7 exited 3 on prerequisites: check-plugin-teardown-shape --self-test (shallow clone), lint check:doc-formula-expressions and check:doc-security-posture, spec check:skill-examples, check:docs-transcript-drift, check:dual-build-cjs-loads and check:lean-entry-closure (unbuilt closures). All 7 exited 0 after the lint/client-react/objectql closure build, a full turbo build and git fetch --unshallow. The 22 doc-family gates were re-run on HEAD after the pages.mdx commit: all 0. `--ran` reconciliation: 111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). check-changeset-no-major --event with the PR body: exit 0, level axis accepts yes (widening) at minor. clause2-line.mjs reads the same line from the body and the changeset. check-nul-bytes: exit 0. CI check-runs on PR #24275: in_progress when this report was written, not awaited per os-dev.", "mcp_calls": "5 MCP GitHub calls, all reads: issue_read get (24265), issue_read get_comments (24265, twice: before work and the report read-back), pull_request_read get (24275, PR read-back), plus ToolSearch loads. Also claude-code-remote add_repo (read, objectui), which attached nothing because it is public anonymous git read. No MCP write tool used.", "api_writes": "3: git push of the branch (several pushes of the one branch, the first as the empty-branch probe); fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls plus POST /issues/24275/assignees, run 39108424762, body read back byte-identical); fleet-write relay comment (POST /repos/objectstack-ai/objectstack/issues/24265/comments, this report).", "files_changed": [ "packages/spec (src/ui/page.zod.ts, src/ui/object-refinement-check-exports.test.ts, src/system/metadata-form-zod-reconciliation.test.ts, liveness/page.json, liveness/state-counts/page.md, dropped-refinements.baseline.json, generated: api-surface/ui.json, authorable-surface/ui.json, declaration-map/ui.json, export-origins/ui.json, json-schema.manifest/ui.json)", "content/docs (ui/pages.mdx hand-written; references/index.mdx, references/ui/page.mdx generated)", "docs/audits/2026-07-unknown-key-strictness-ledger.counts/ui.md (generated)", ".changeset/24265-app-page-record-binding.md", "No other package: lint, formula and objectui untouched. No governed path: the skills reference index is kept byte-identical by export order." ], "line_budget": "n/a (337 changed lines, under the ~400 stop line)", "deviations": [ "Conflict named: the dispatch prompt asks for each edited package's test:repo; os-dev says test:repo is CI's unless the diff edits that project's tests. I ran it anyway (green), so nothing is skipped.", "The dispatch prompt names packages/spec/liveness/page.json only if the route re-cites a renderer read point. The route adds a new authorable key, so check:liveness requires a new planned row, drilled to its leaves instead of growing the undrilled-containers baseline. dropped-refinements.baseline.json moved because the new filter is now the first ViewFilterRule site under ui/Page.", "Worktree: the primary checkout was detached so the claim's branch could be checked out in ../objectstack-issue-24265, rather than creating a new branch from origin/main. The branch was already at origin/main a422f04fe." ], "open_questions": [ { "question": "Q1: what root does an app page's bound row use?", "options": [ "A: PageSchema.record, a one-row page binding read through the existing record root (implemented)", "B: a bound block publishes its row into page.NAME through PageVariable.source", "C: a new root such as bound.NAME" ], "axes": "Business: A serves the ruled environments page directly (bind the organisation row; count and tier are fields on it); B only when a block showing the row exists; C same reach. Long-term: A reuses ADR-0089 D1's runtime root and the record blocks' one-row semantics; B changes what PageVariable.source means (selection writer to data publisher) and adds a load-timing model; C adds a third row spelling beside record and data. AI-error: A is a strict shape, refused off app pages, limit/id/recordId refused with prescriptions, with a declared planned window until objectui#12931; B gives two writers two meanings under one key; C is a new root that lint, formula and the editor must all learn. Startup: A is one key on an existing shape, with no root, lint or gate change.", "recommendation": "A. It dominates on all four axes. No new root, ADR-0089 D1-consistent, strict and loud, one key. The contract review should judge the key name `record` (it equals the root it binds) against the alternative spellings that are aliased to it." }, { "question": "Q2: how is the tier read without a cloud-only root?", "options": [ "A: a field on the bound row, record.plan (implemented as docs; no spec change needed)", "B: promote features to contract-bound and map the plan to feature flags" ], "axes": "Business: A uses data the control plane already owns on its org row; B needs a per-org flag in a deployment-level anonymous registry. Long-term: A keeps platform mechanism and app data separate; B breaks PUBLIC_AUTH_FEATURES' meaning and its drift guard against getPublicConfig(). AI-error: A gives one place for row data; B puts two meanings under one root. Startup: A adds nothing; B is a cloud-only fill, which the card forbids.", "recommendation": "A. features stays ambient. Promoting it is not decided here and was not needed." }, { "question": "Q3: where does the count come from?", "options": [ "A: a summary roll-up field on the bound row, read as record.FIELD (implemented as docs)", "B: a dedicated count root or aggregate expression" ], "axes": "Business: A covers 'a second environment exists' with record.environment_count > 1; B is speculative. Long-term: A uses the existing summary field type; B is a new evaluator capability. AI-error: A means one root and one field; B means a new surface to get wrong. Startup: A adds nothing.", "recommendation": "A. The cloud side needs a summary field on its organisation object (master-detail from environments). That is the app's data, not the platform's." } ], "out_of_scope_findings": [ "class: none filed. The content/docs/ui/pages.mdx visibleWhen row still calls `visibility` an accepted deprecated alias, but at 18 it is a retiredKey tombstone. Pre-existing doc drift. carrier: none. Noted, not filed (in Acceptance notes). dedupe: pages.mdx visibility alias, retired visibility docs." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{ "issue": 24265, "status": "done", "round": "patch round 1", "branch": "claude/issue-24265-app-page-predicate-roots", "pr": "https://github.com/objectstack-ai/objectstack/pull/24275", "session": "session_012SGQNHx8abCKZKoyj2VQym", "head": "03b3e5ad4b", "premise_still_valid": true, "failing_assertion": "Reproduced with `pnpm --filter @objectstack/metadata-protocol test` on head a150ec532f (exit 1; 1 failed / 20640 passed). Failing test: src/protocol.meta-types-degenerate-derivation.test.ts > '#17501 — /meta/types serves a real schema for `action`, and moves nothing else' > 'control: `page` still serves 25 top-level properties'. Assertion: `AssertionError: expected 26 to be 25 // Object.is equality`, at src/protocol.meta-types-degenerate-derivation.test.ts:339:15.", "root_cause": "This PR. CARD_PROPERTY_COUNTS is a census: a positive control that pins how many top-level properties /meta/types serves per type. PageSchema gained one declared key (`record`), so the served page schema has 26 properties. The derivation itself did not change. The file's own docblock records each such move as a declared key, not a derivation change; `page` moved 24 to 25 the same way for `print`. So this is the census case: the count must name the new key. The fix is page 25 to 26, with a docblock line naming `record`. The test is not loosened: every other count and the derivation assertions are unchanged.", "summary": "Census bumped with its recorded reason. Review flag F1 folded in: the pages guide's visibleWhen cell no longer calls the `visibility` alias accepted; it now says the retired spelling is refused at parse (ADR-0089 D2). Review flag F2 folded in: the liveness `record` row's planned note names its consumer card objectstack-ai/objectui#12931. No decision the review judged was touched (key name, placement rule). The PR body and title are unedited.", "files_changed": [ "packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts", "content/docs/ui/pages.mdx", "packages/spec/liveness/page.json" ], "tests": "Head 03b3e5ad4b: `pnpm --filter @objectstack/metadata-protocol test` exit 0 (244 files passed, 3 skipped; 20641 passed, 19 skipped). `pnpm --filter @objectstack/spec test` exit 0 (693 files; 21015 passed, 1 todo). `pnpm --filter @objectstack/spec test:repo` exit 0 (68 files; 1125 passed).", "gates": "Head 03b3e5ad4b, exit codes captured before any pipe: `pnpm --filter @objectstack/spec build` 0. `pnpm --filter @objectstack/spec check:generated` 0 ('All 14 generated artifacts are up to date'). `pnpm --filter @objectstack/spec check:liveness` 0. Doc gates on the edited page: check:doc-authoring 0, check:docs-spec-enumerations 0, check:doc-anchors 0, docs-audit/check-affected-docs 0, check-doc-frontmatter 0. The full derived set was not re-run this round: the PM's list was run as asked, and CI covers the rest. CI check-runs on the new head: not awaited.", "mcp_calls": "0 MCP GitHub calls this round before the report read-back; the read-back is 1 issue_read get_comments (read). No MCP write tool used.", "api_writes": "2: git push of claude/issue-24265-app-page-predicate-roots (fast-forward a150ec532f to 03b3e5ad4b); fleet-write relay comment (POST /repos/objectstack-ai/objectstack/issues/24265/comments, this report).", "deviations": [], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{ "issue": 24265, "status": "done", "round": "patch round 2", "branch": "claude/issue-24265-app-page-predicate-roots", "pr": "https://github.com/objectstack-ai/objectstack/pull/24275", "session": "session_012SGQNHx8abCKZKoyj2VQym", "head": "88dbe4191e", "premise_still_valid": true, "summary": "Merged origin/main 9822b053e2, which includes PR #24270 as 586705d85d, into the branch with scripts/pm/os-regen-merge.sh. It is a merge commit, 6480ad0b61, followed by a separate regeneration commit, 88dbe4191e. There was no rebase and no force: the push fast-forwarded 03b3e5ad4b to 88dbe4191e. No decision the contract review judged was touched: the key name, the placement rule and the planned grading are all unchanged.", "conflicts": [ "packages/spec/dropped-refinements.baseline.json — the only textual conflict, in the `measured` header totals: HEAD 237/768, main 238/770, base 236/767. Resolved by hand to the sum of both deltas, 239 schemas and 771 sites. Both sides' entries text-merged cleanly: #24270's three emptyState.link.url sites and its counts, plus this PR's ui/PageRecordBinding and the moved ui/Page record.filter.element site. The spec build's ledger check passed on the merged file, and dropped-refinements.test.ts, which pins the totals against the body, is green.", "packages/spec/authorable-surface/ui.json — generated and routed to the os-regen driver, so it was not text-merged. Step 2 took main's side, both sides having moved it. It was then regenerated by `pnpm --filter @objectstack/spec build` from the merged tree and committed as 88dbe4191e, which discharged the pre-commit deferral. It carries both sides: PageRecordBinding 4 hits, emptyState 7.", "packages/spec/liveness/page.json — auto-merged with no conflict. The branch's delta against origin/main is exactly this PR's `record` row, including the objectui#12931 note. #24270's PLANNED RECORD sentences in `_note` are kept, verified by grep.", "packages/spec/liveness/state-counts/page.md — only the branch changed it. The merge script kept the branch's bytes, and check:liveness re-derives it as current.", "content/docs/references/ui/component.mdx — only main changed it, so it was taken as is. check:generated reports all 14 artifacts current; component.mdx carries the emptyState text." ], "census": "Measured: protocol.meta-types-degenerate-derivation.test.ts still pins `page: 26`, and it passes on the merged tree. #24270 added no PageSchema key.", "files_changed": [ "merge commit 6480ad0b61: main's files, plus a hand resolution of packages/spec/dropped-refinements.baseline.json (measured totals)", "88dbe4191e: packages/spec/authorable-surface/ui.json (regenerated)" ], "tests": "Head 88dbe4191e. `pnpm --filter @objectstack/spec test`: exit 0, 694 files, 21040 passed, 1 todo. `test:repo`: exit 0, 68 files, 1125 passed. `pnpm --filter @objectstack/metadata-protocol test`, after building its closure: exit 0, 244 files passed, 3 skipped; 20641 passed, 19 skipped. scripts/dropped-refinements.test.ts: exit 0, 27 passed.", "gates": "Exit codes captured before any pipe. `pnpm --filter @objectstack/spec build`: 0. `check:generated`: 0 ('All 14 generated artifacts are up to date'). `check:liveness`: 0. One earlier spec `test` attempt exited 99: it waited on my own test:repo run holding the lock and measured nothing; it is re-run above. CI on 88dbe4191e: not awaited.", "mcp_calls": "1 MCP GitHub call this round: issue_read get_comments for the report read-back. No MCP write tool used.", "api_writes": "2: git push (fast-forward 03b3e5ad4b..88dbe4191e); fleet-write relay comment (POST /repos/objectstack-ai/objectstack/issues/24265/comments, this report).", "deviations": [], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
ACCEPT → PR #24275 comment 6248293061 · contract review PASS
6248097604(after PASS6247358877, a red census and two patch rounds) · ready and auto-merge armed 2026-10-18T09:34Z ·domain:specseat 1 · sessionsession_01TyRhWbVbbQpQJTDbW8VahJGenerated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Landed · PR #24275 →
ef65646843(Fixes #24265, anapppage'svisibleWhenreads its bound row through the existingrecordroot) ·domain:specseat 1 ·os-project-manager· sessionsession_01TyRhWbVbbQpQJTDbW8VahJ· 2026-10-18T10:09Zef65646843on parent8bfb17d472origin/maingit merge-base --is-ancestor ef65646843 origin/main→ exit 0 (control: the PR head88dbe4191e→ exit 1, a squash merge)git patch-id --stableis equal with context: the merged diff over8bfb17d472and the reviewed head88dbe4191eover its merge base9822b053e2both givedbecc617(ACCEPT6248293061, contract review PASS6248097604)gh-readonly-queue/main/pr-24275-*ref remainsFixes #24265is the squash message's only closing keyword. Its other references are PR #24270 (merged before this one) and objectstack-ai/objectui#12931, which the message also spells once as a bareobjectui#12931(prose, no closing keyword; objectstack#12931 closed on 2026-09-03 and is untouched). This card closedcompletedby that PR alonepm:dispatchedis cleared in this act. The cloud dev session was archived at ACCEPT.Hand-offs:
visibleWhencan follow tier and count (新建环境 on paid tiers, the 🔒 line on Free, the 生产 tag with a second environment) (ruling B on cloud#2774) objectui#12931, the renderer half: the binding is now in@objectstack/speconmain. The liveness row staysplanneduntil the console mounts the binding.summaryroll-up of environments on its organisation object, and its environments page as atype: 'app'page whoserecordbinding filters on{current_org_id}. This seat cannot read that repository; the pointer is on the seat post and in the stand-down brief.Generated by Claude Code