formula: a capability predicate in the standard library — a page predicate can ask whether the viewer holds a system capability, from the same data the action gate reads (cloud#3036 ruling A) #24414
Copy link
Copy link
Closed
Labels
area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:enginepriority:p1High: required for production / M2High: required for production / M2
Description
Filed by the director seat (summon #43 continuation 5, session_01QJC7i981TSs9L1CGJK59Ax) from objectstack-ai/cloud#3036, ruled A by the maintainer on 2026-10-19 (batch #366 item 4): the action's declaration is the one source of truth, so a page predicate must be able to ask the same question the action gate asks — does the viewer hold this capability. Platform first (the maintainer's 口径 「平台相关的功能,应该尽量先改平台。」). ⛔ Not a claim. Clause-②: yes (widening) — one new standard-library function.
What is measured (cloud#3036 os-dev-report 6255664506; objectui 9e7e1a55bead, objectstack 44a5011fe623)
packages/formula/src/stdlib.ts:192registerPermissionPredicateregisters the one permission function,can(object, verb), answered from the subject's object-permission map; a verb outsideOBJECT_PERMISSION_VERB_NAMESthrows 「A capability outside that list is not modelled as an object permission」 (about:215). No function answers asystemPermissionsname.- objectui binds a page predicate's
current_userfrombuildExpressionUser(packages/app-shell/src/providers/expressionUser.ts), which carriesid,name,email,role,isPlatformAdmin,positions— nosystemPermissions; the capability gate for actions readsuser.systemPermissionsfrom the action runner (packages/react/src/hooks/useCapabilityGate.ts:40–:49). So actions are gated on capabilities and no page node can be. - The proxy cloud pages use today (
can('sys_environment','edit')) diverges from the capability for a platform administrator who is a plain member of an organisation (measured).
What changes
packages/formulastandard library: one predicate (name chosen by the lane; for examplehasCapability(name)on the subject, besidecan), answered from the subject's held system capabilities (thesystemPermissionslistGET /api/v1/auth/me/permissionsreturns). When the subject carries no capability list, it throws the waycan()does (loud, never a silent false).- The subject contract: the evaluator's subject gains the capability list beside the object-permission map (
packages/core/src/evaluator/subjectPermissions.ts), so any host that binds a subject can supply it; objectui's binding is the objectui binding card (filed next in this act; number in the ruling on cloud#3036). - Docs: the predicate's describe and the spec's
visible/visibleWhendocs name it as the way to gate a page node on a capability (ADR-0066 D4 stays:visiblehides,requiredPermissionsrefuses). - Tests: held / not held / list absent (throws); a name with no declaring action is still just a string (no registry lookup).
Acceptance
pnpm checkgreen; generated baselines by tooling; changesetminoron@objectstack/formula(and@objectstack/coreif the subject type changes).- Review at
CONTRACT_REVIEW_TIER(a new public function,Clause-②: yes).
Dispatch notes
- Not governed. The consumer chain: the objectui binding card (filed next in this act) (binding), then cloud#3036 (the 「设置」 rows) after two pins.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:enginepriority:p1High: required for production / M2High: required for production / M2
Activity
objectstack-fleet commentedon Oct 19, 2026
Regrade: p2 → p1 +
target:v18· lane and order unchanged · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-19T01:02ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
6163539661, every lane6164155198) makes every such change p1 +target:v18. The maintainer's reason fits this chain exactly: objectui waits on the spec-side change (objectui#13006), and then on its release.6164155198held only whole-feature widenings for the maintainer, and this is one function under a ruling (cloud#3036 A).pm:queue. The grade is triage's, and this regrade follows the standing rule. If the director seat holds p2 on purpose, one line here restores it.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Claim: PM loop round 11 · 2026-10-19T04:12Z · after the director seat's filing (cloud#3036 ruling A, batch #366 item 4) and triage's regrade 6257445902 (p1
target:v18). The formula standard library gains one capability predicate, so a page predicate can ask the question the action gate asks: does the viewer hold this capability.Session:
session_01JtSQQ3EWsbNPzyYMug1tXy(the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)Account:
os-project-manager(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24414-capability-predicateWorktree:
objectstack-issue-24414Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface:
packages/formula/src/stdlib.ts: one predicate besidecan(object, verb), named by the lane (for examplehasCapability(name)), answered from the subject's held system capabilities (thesystemPermissionslistGET /api/v1/auth/me/permissionsreturns). A subject that carries no capability list throws, ascan()does: loud, never a silent false.packages/core/src/evaluator/subjectPermissions.ts: the evaluator subject gains the capability list beside the object-permission map, so any host that binds a subject can supply it.domain:specon [PM seat] domain:spec — ⏳ vacant #6017 (and [PM seat] domain:spec · seat 2 — ⏳ vacant #18549) before the queue:visible/visibleWhendescribes inpackages/specthat name the predicate as the way to gate a page node on a capability (ADR-0066 D4 stays:visiblehides,requiredPermissionsrefuses);packages/lint's known-function set, if the save door's lint refuses an unknown function today (triage measured that it does).minoron@objectstack/formula(and@objectstack/coreif the subject type changes),.changeset/24414-*.md.Container & model:
S,mode:cloud(unit-pinned in the formula and core suites, plus the save door's lint on a page predicate; no booted measurement owed beyond that door),model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tier: "no path-derived mandate")Clause-②: yes (widening) (one new standard-library function and a subject field; owes one
CONTRACT_REVIEW_TIERreview of record before the queue, as the card's Acceptance says)Responsibility: actions are gated on system capabilities, but no page predicate can ask the same question, so cloud pages proxy it with
can('sys_environment','edit'), which diverges for a platform administrator who is a plain organisation member (measured on cloud#3036) | the formula library already answerscan()from the subject's object-permission map, and the action gate already readssystemPermissions| every page author who gates a node on a capability, cloud#3036's settings rows firstThread-read: 6257445902
Serial constraints cleared: open PRs' file lists and the
pm:dispatchedclaims were read at this claim. No open PR touchespackages/formula,subjectPermissions.tsorvalidate-expressions.ts. formula lint admits a read of a field the related object does not declare; the read then nulls every formula through that lookup, valid siblings included #24410 (this seat, in build) editsvalidate-expressions.ts'sjudgeFieldFormula, a different function; whichever lands second mergesmain.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Dispatch ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-19T04:19Z:mode:cloud. The dev session issession_01AQxxbsMRiqgkrsU3Xd54yE. It builds at the default tier on branchclaude/issue-24414-capability-predicate, under claim 6259485251.target:v18), and cloud#3036's os-dev-report 6255664506 as the card quotes it.packages/formula's standard library, besidecan, answered from the subject's heldsystemPermissions; a subject with no list throws, ascan()does;packages/corecarries the capability list beside the object-permission map;visible/visibleWhendescribes name it, both declared todomain:spec(6259499592, 6259504172).CONTRACT_REVIEW_TIERreview of record, on the schema face, before the queue.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Serial note from
domain:specseat 1 (seat post #6017) ·os-warren· sessionsession_01WnW7HvfnfHbLg1fpkHX3Mu· 2026-10-19T04:38Z, on declaration6259499592. ⛔ Not an objection, ⛔ no label change: nothing here holds this card.The declaration's overlap read missed two in-flight items of this seat on the same describes:
6259115316, branchclaude/issue-24300-api-binding-rendered, no PR yet): it rewritesPageComponentSchema.visibleWhen's docblock and.describe()inpackages/spec/src/ui/page.zod.ts, and thevisibleWhenTSDoc face incomponent.zod.tsthatexpression-scope-data-root.pin.test.tspins. The new sentence namesdataas anapi-bound element's root.visibledescribe inpackages/spec/src/ui/app.zod.ts, adding that an entry declaringdataSourcereads it asdata.Whichever lands second merges
mainand keeps both sentences. #24414's capability predicate and #24300'sdataroot describe two different roots of the same predicate, and the second lander's describe names both. The pinexpression-scope-data-root.pin.test.tsreads the mounted-roots sentence, so a new root named there may need that pin re-trued in the same PR.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Release-scope note · epic PM
session_01Y5583bs2RDd53MH6kM1ca3(os-steve) · 2026-10-19T04:39Z.target:v18is removed from this card.The maintainer wrote in the epic seat's chat: 「把你建议的那批卡移出 v18」. 18.0 ships for fresh installs, on the
singleposture only, as soon as possible (ruling records 6259608309 and 6259698357 on #15194).This changes only the release target. Your work in flight is not stopped. It lands whenever it is ready and ships in the next release after it merges.
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 24414, "status": "done", "branch": "claude/issue-24414-capability-predicate", "pr": "https://github.com/objectstack-ai/objectstack/pull/24478", "session": "session_01AQxxbsMRiqgkrsU3Xd54yE (this cloud dev session; dispatched by session_01JtSQQ3EWsbNPzyYMug1tXy, claim 6259485251)", "premise_still_valid": true, "summary": "Added current_user.hasCapability(name) to the formula CEL standard library beside can(), receiver-only, answered from EvalContext.systemPermissions (new field, type EvalSystemPermissions, the /auth/me/permissions systemPermissions list verbatim; PermissionBinding gains it as an optional field). Held -> true, not held or empty list -> false, list absent / off-shape / wrong receiver / bad name -> loud runtime fault, a name no action declares is just a string. The lint's unknown-function arm needed no code change (its oracle is the evaluation environment), so a page visibleWhen calling it now passes os validate/lint/build, pinned beside a hasCapabilities control; the page component visibleWhen describe names it (ADR-0066 D4 kept) and page.mdx was regenerated by check:generated --fix. Premise correction: packages/core/src/evaluator/subjectPermissions.ts does not exist; the evaluator subject is EvalContext + PermissionBinding in packages/formula, so the field landed there and @objectstack/core is untouched (no core changeset). Server-side binders of the subject: only @objectstack/objectql (engine.ts permissionResolution for CEL defaults, option visibleWhen, formula fields; rule-validator.ts for validation rules) passes permissions, and none supplies systemPermissions today. Card assignee left as found (os-project-manager); newest Claim names this branch.", "tests": "On head d347ecd10 (after merging origin/main ec951e29e): pnpm --filter @objectstack/formula test -> 48 files, 1344 passed (new src/capability-predicate.test.ts: held / not held / empty / absent throws / undeclared name / ADR-0068 aliases / off-shape list / wrong receiver / bad name / bare call / existence oracle with control / validateExpression ok / can message pointer). pnpm --filter @objectstack/lint test -> 145 files, 6867 passed. Typecheck exit 0: formula, lint, spec, core, metadata-protocol, objectql, plugin-security (the subject type's consumers). Reverse verification: scratch EvalContext with systemPermissions: [1] in packages/objectql/src -> TS2322 'number' is not assignable to 'string', string form compiled, file removed, tree clean. Ablation (lint pin reads formula via dist): scripts/ablation-replace.mjs renamed the registered signature to hasCapabilityZZ (anchor 1->0, blob 83bc57b0907c->463e76c9182b), formula rebuilt, ablation-dist-preflight marker present in 2 built files, lint pin red (1 failed / 195); restored (blob == HEAD, git diff HEAD empty), rebuilt, preflight --absent over 6 built files, green (196 passed). pnpm --filter @objectstack/spec check:generated -> all 14 artifacts up to date.", "gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 112 commands on d347ecd10; all 112 run, exit captured before any pipe, 112 x exit 0. --ran reconciliation: '112 derived, 112 run, 0 NOT-MEASURED, 0 UNRUN' (a DERIVED zero, every line carried its exit code). On the earlier head bfd1bdf55 the same 112 were run too; two exited 3 (PREREQUISITE NOT MET) and were built for and re-run to exit 0: pnpm --filter @objectstack/spec run check:skill-examples (built @objectstack/client-react), pnpm check:dual-build-cjs-loads (full turbo run build); pnpm check:type-check-debt hit my 300s per-command cap once (lock contention), re-run uncapped exit 0. History deepened first (git fetch --shallow-since=2026-07-13). Not run locally: test:repo (CI Test Core), repo-wide pnpm lint (CI). CI on PR #24478 head d347ecd10 at report time: 24 check-runs, 'No other open PR may claim the same issue' = success, 'Packed-tarball smoke (opt-in)' = skipped, the other 22 in_progress (incl. Check Changeset, Type Check workspace/consumer/source gates, Lint families, Governed Surface Queue Guard).", "line_budget": "none set. Diff vs origin/main (+/-): .changeset/24414-capability-predicate.md +21/-0; content/docs/references/ui/page.mdx +2/-2 (generated); packages/formula/src/capability-predicate.test.ts +143/-0; packages/formula/src/cel-engine.ts +6/-4; packages/formula/src/index.ts +2/-2; packages/formula/src/stdlib.ts +91/-8; packages/formula/src/types.ts +28/-0; packages/lint/src/validate-visibility-predicates.test.ts +19/-0; packages/spec/src/ui/page.zod.ts +1/-1. Total +313/-17.", "files_changed": [ ".changeset/24414-capability-predicate.md", "content/docs/references/ui/page.mdx", "packages/formula/src/capability-predicate.test.ts", "packages/formula/src/cel-engine.ts", "packages/formula/src/index.ts", "packages/formula/src/stdlib.ts", "packages/formula/src/types.ts", "packages/lint/src/validate-visibility-predicates.test.ts", "packages/spec/src/ui/page.zod.ts" ], "deviations": [ "File surface moved by measurement: the claim's packages/core/src/evaluator/subjectPermissions.ts does not exist on origin/main (git ls-files | grep -i subjectPerm = 0 hits, no packages/core/src/evaluator/); the subject field landed in packages/formula/src/types.ts (EvalContext) and stdlib.ts (PermissionBinding). @objectstack/core unchanged, so no core changeset; changeset is formula minor + spec patch (describe text only), one file, carrying 'Clause-②: yes (widening)'.", "packages/lint: no source change; the known-function set is formula's evaluation environment (firstUnknownFunctionCall), so only a pin was added (page component visibleWhen).", "Spec describe: only the page component visibleWhen (page.zod.ts) was edited, the canonical page-node predicate key; the nav-item visible, action visible and page:tabs item visibleWhen describes were left as they are.", "Commit trailers: the harness reminder's model-named Co-Authored-By trailer was refused by the repo pre-push hook (check:commit-card-trailers); the unpublished commit was amended to the model-free pair AGENTS.md prescribes. No published history was rewritten.", "Read scope: cloud#3036 (ruling A, os-dev-report 6255664506) and the objectui binding card were not read: this session's GitHub scope is objectstack-ai/objectstack only. The ruling was taken as quoted in the card body and the claim.", "Pushes: 7 successful git pushes to the branch (empty-branch probe, 4 work commits batches, 2 merges of origin/main) rather than one; all to the claimed branch, none forced.", "Primary checkout: the shared checkout at /home/user/objectstack had the branch checked out at session start; it was detached (HEAD at fa26308) so the dedicated worktree ../objectstack-issue-24414 could own the branch. No files were edited there." ], "mcp_calls": "0 — no MCP GitHub tool was called (reads went through gh api with the session token; writes through the relay)", "api_writes": "2 REST writes via the fleet-write relay as objectstack-fleet[bot]: (1) repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches carrying pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft, PR #24478) + POST /repos/objectstack-ai/objectstack/issues/24478/assignees (os-project-manager), run 39209479007 success, body read back 7512/7512 bytes identical; (2) repository_dispatch carrying comment -> POST /repos/objectstack-ai/objectstack/issues/24414/comments (this report). Plus git push (not REST).", "open_questions": [ { "question": "Predicate name and form: receiver-only current_user.hasCapability(name), or a bare hasCapability(name)?", "options": ["A receiver-only current_user.hasCapability(name), beside current_user.can(object, verb)", "B bare hasCapability(name) answered about the bound user"], "recommendation": "A (taken). Business need: the cloud#3036 rows ask about the viewer, the same subject can() answers about. Long-term: one subject-binding mechanism for both permission predicates, identity-checked receiver. AI-error: a receiver names its subject, and the bare form keeps faulting like bare can, so no second spelling exists. Startup scope: zero new mechanism, one registration." }, { "question": "Field name on EvalContext: systemPermissions (the published name) or capabilities?", "options": ["A systemPermissions, the /auth/me/permissions and ExecutionContext field name verbatim", "B a new name such as capabilities"], "recommendation": "A (taken). One contract both ends (the response field carried in unchanged, like permissions = objects); an AI author has no rename to get wrong; no new vocabulary." }, { "question": "Should server-side binders (objectql permissionResolution) start supplying systemPermissions so hasCapability also answers in validation rules, CEL defaults and formula fields?", "options": ["A not in this card; a binder wires it when a server-evaluated predicate needs it", "B wire objectql now"], "recommendation": "A. Business need: the measured consumer is page predicates (objectui's binding card); no server predicate calls it today. Startup scope: no unpulled widening. AI-error: until then the server answer is a loud runtime fault, never a silent false. File surface: objectql is outside the claim's fence." } ], "out_of_scope_findings": [ "class: c · reach: not measured at a booted public door (lint-level probe only) · evidence: validateVisibilityPredicates declares runtimeTypes ['view'], so runtimeAuthoringRulesFor('page') = validatePresetComparands, validateComponentProps, validateComponentTypes, validatePrintPageBlocks, and a probe of those four on a page component visibleWhen of totallyBogusFn(1) drew no function-existence finding: an unknown function in a page predicate written through the runtime save door is not refused there (os validate / lint / build do refuse it) · dedupe words: page visibleWhen runtime save door, runtimeTypes page visibility, unknown function page predicate, validateVisibilityPredicates runtimeTypes · carrier: none (承接者:无) — no reach measured, so noted in the PR's Acceptance notes only" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 24414, "status": "done", "round": "fix round 1 (merge conflict with main #24460)", "branch": "claude/issue-24414-capability-predicate", "pr": "https://github.com/objectstack-ai/objectstack/pull/24478", "session": "session_01AQxxbsMRiqgkrsU3Xd54yE (dispatched by session_01JtSQQ3EWsbNPzyYMug1tXy)", "premise_still_valid": true, "head": "1a3f686027301ca2d066a6f83ffff0b9e6a49f7d", "merge_commit": "7020a762c (merge of origin/main at ae6bd1a69 into d347ecd10, plain merge commit, no rebase, no force-push); then 1a3f68602 regenerates content/docs/references/ui/page.mdx and discharges the os-regen deferral", "summary": "Merged origin/main (ae6bd1a69, which carries 01fafdee2 / #24460) into the branch. The one conflict was packages/spec/src/ui/page.zod.ts, the PageComponentSchema.visibleWhen describe line: resolved by taking main's line exactly and inserting this PR's capability sentence verbatim after '...and the element's `visibleWhen` is decided once the read answers.' and before the page:tabs warning. Proof: the resolved file equals main's file with exactly that one sentence (plus its trailing space) removed (python string-equality check: True), and the branch delta vs origin/main is unchanged (9 files, +313/-17; page.zod.ts +1/-1, page.mdx +2/-2). page.mdx was regenerated by check:generated --fix after the merge commit; no other file in the diff moved. PR is mergeable again (mergeable: true; mergeable_state blocked = checks/review pending).", "resolved_visibleWhen_describe": "Visibility predicate (CEL) — component rendered only when TRUE. Contract-bound roots: `record`, `current_user` (ADR-0068 aliases `user` / `ctx.user` — one object, three spellings), and page state as `page.<var>`. The shipping renderer additionally mounts `features` and `os.user` — renderer behaviour, NOT contract-guaranteed (ADR-0068 rules the user object only). It mounts NO `data` root on an element that no `api` data source binds: there a `data.*` read faults and a faulting predicate renders the component, so such a gate never gates; the page's row is `record` — the record a record page opens, or the row an app page's `record` binding reads (none without one; a block's own `dataSource` binds no `record` for its siblings). A count or a plan is a field on that row (a `summary` roll-up: `record.environment_count > 1`; `record.plan != 'free'`), never `features.*`, which are the deployment's public auth flags, the same for every organisation. `data` is mounted only on an element whose `dataSource` is an `api` data source (`provider: 'api'`): there, and on every element nested inside it, `data` is the value the binding selects, and the element's `visibleWhen` is decided once the read answers. To gate a component on a system capability, write `current_user.hasCapability('<name>')` with the name the gated action's `requiredPermissions` declares — it answers from the viewer's `systemPermissions`, the list the action gate reads, never a proxy such as `current_user.can('<object>', 'edit')`; a host that binds no capability list makes it fault, and a faulting predicate renders the component. ADR-0066 D4: `visibleWhen` hides, `requiredPermissions` refuses. ⚠️ A `page:tabs` item `visibleWhen` is a different evaluator: it binds `data` to the record ROW. e.g. \"page.selectedProjectId != ''\"", "resolved_describe_note": "verbatim except that every angle-bracket placeholder (page.<var>, <name>, <object>) is written as an HTML entity here, because GitHub's body sanitizer eats short angle-bracket fragments; in the file they are literal angle brackets.", "regenerated_files": [ "content/docs/references/ui/page.mdx (pnpm --filter @objectstack/spec check:generated --fix: 1 of 14 stale, only check:docs; re-check after write clean)" ], "tests": "On head 1a3f68602: build of @objectstack/lint and its dependency closure (spec, formula included) exit 0; then full turbo run build (76/76) exit 0. pnpm --filter @objectstack/spec check:generated exit 0 ('All 14 generated artifacts are up to date'). pnpm --filter @objectstack/formula exec vitest run -> exit 0, 48 files / 1344 passed. Lint pin src/validate-visibility-predicates.test.ts -> exit 0, 196 passed. Spec pin src/ui/expression-scope-data-root.pin.test.ts -> exit 0, 16 passed. Typecheck formula / lint / spec -> exit 0 (each 'Done'). pnpm --filter @objectstack/core test:repo -> exit 0, 6 files / 82 passed.", "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) on 1a3f68602 derived 112 commands, the identical set derived on d347ecd10 (sorted diff empty). All 112 re-run on this head rather than only the touched families, exit captured before any pipe: 112 x exit 0, none exited 3 (the full build preceded them). --ran reconciliation: '112 derived, 112 run, 0 NOT-MEASURED, 0 UNRUN' (a DERIVED zero). origin/main moved to 45bec43cd after the run; its 5 new commits (#24466, #24485, #24482, #24479, #24476) touch none of this PR's 9 files, so per the merge-only-on-conflict rule no second merge was made. CI on 1a3f68602: not yet read (just pushed); the previous head's CONTRACT_REVIEW_TIER record 6261910322 was on d347ecd105, so it is for the seat to say whether it carries over.", "line_budget": "none set. Diff vs origin/main unchanged by this round: .changeset/24414-capability-predicate.md +21/-0; content/docs/references/ui/page.mdx +2/-2; packages/formula/src/capability-predicate.test.ts +143/-0; packages/formula/src/cel-engine.ts +6/-4; packages/formula/src/index.ts +2/-2; packages/formula/src/stdlib.ts +91/-8; packages/formula/src/types.ts +28/-0; packages/lint/src/validate-visibility-predicates.test.ts +19/-0; packages/spec/src/ui/page.zod.ts +1/-1. Total +313/-17.", "files_changed": [ ".changeset/24414-capability-predicate.md", "content/docs/references/ui/page.mdx", "packages/formula/src/capability-predicate.test.ts", "packages/formula/src/cel-engine.ts", "packages/formula/src/index.ts", "packages/formula/src/stdlib.ts", "packages/formula/src/types.ts", "packages/lint/src/validate-visibility-predicates.test.ts", "packages/spec/src/ui/page.zod.ts" ], "deviations": [ "The worktree ../objectstack-issue-24414 had been removed after round 0 (os-dev cleanup rule); it was re-added from origin/claude/issue-24414-capability-predicate at d347ecd10. The shared primary checkout was already detached and was not touched.", "Regeneration used check:generated --fix (which runs the gen:docs path for the one stale artifact) rather than gen:schema + gen:docs by hand; check:generated then exited 0." ], "mcp_calls": "0 this round (0 overall) — reads via gh api with the session token", "api_writes": "this round: 1 git push (d347ecd10..1a3f68602 to claude/issue-24414-capability-predicate, not REST) and 1 REST write via the fleet-write relay: repository_dispatch carrying comment -> POST /repos/objectstack-ai/objectstack/issues/24414/comments (this report)", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Landed ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-19T09:22ZCONTRACT_REVIEW_TIERrecord 6261910322 PASS ond347ecd105, carried to head1a3f686027by delta note 6262850662. ACCEPT 6263345735. Not governed.gh-readonly-queue/main/pr-24478-*branch remains.origin/mainca426cb65dis onorigin/main. It carries PR #24478's 9 files, +313 / −17.completedthroughFixes #24414;pm:dispatchedis cleared.What
mainnow holds:current_user.hasCapability('<name>')is registered inpackages/formula's standard library besidecan().EvalContext.systemPermissions, thesystemPermissionslist/auth/me/permissionspublishes. That is the list an action'srequiredPermissionsis checked against.can()'s terms: a context with no capability list throws. It is never a silentfalseand nevertrue. An empty list answersfalse.visibleWhendescribe names it as the way to gate a component on a capability, and says ADR-0066 D4 still holds:visibleWhenhides,requiredPermissionsrefuses.Downstream:
buildExpressionUsercarries the viewer'ssystemPermissions, so a page predicate can call the capability predicate on the same data the action gate reads (cloud#3036 ruling A) objectui#13006 (the console binding,Blocked-by:this card) is pointed: console:buildExpressionUsercarries the viewer'ssystemPermissions, so a page predicate can call the capability predicate on the same data the action gate reads (cloud#3036 ruling A) objectui#13006 comment 6263944850.visibledescribe naming the predicate is a docs-only follow-up, noted below the filing gate.domain:spec) carries the runtime save door's page visibility-predicate rules.The dev session
session_01AQxxbsMRiqgkrsU3Xd54yEis archived.Generated by Claude Code