Repository navigation
Conversation
`OperationGrant` gains `anonymous`, which opens the grant's base operation to callers with no session, and `actingUser`, the key in the governed realm's realm.json settings naming the user an anonymous write is made as. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Staging Submissions PreviewThis PR's content is pushed to the staging submissions realm: https://realms-staging.stack.cards/submissions/ Changed folders:
Updated at 2026-10-05 21:55:36 UTC for commit |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The rendered key can differ from compiler behavior, and the new UI branches lack integration coverage.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Extends OperationGrant for anonymous callers, coordinated with cardstack/boxel#6534.
Changes:
- Adds
anonymousandactingUserfields. - Displays anonymous grants and acting-user configuration.
| File | Description |
|---|---|
realm-policy/realm-policy.gts |
Adds anonymous-grant schema and embedded presentation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| <code | ||
| class='acting-user' | ||
| data-test-operation-grant-acting-user | ||
| >config.{{@model.actingUser}}</code> |
There was a problem hiding this comment.
[Claude Code 🤖] Fixed on the compiler side (cardstack/boxel f87ad865fa). It no longer trims actingUser: the key is used exactly as written, so this view shows the key the realm enforces. A blank key names nothing.
| {{#if @model.anonymous}} | ||
| <Pill class='anonymous' data-test-operation-grant-anonymous> | ||
| anyone | ||
| </Pill> | ||
| {{#if @model.actingUser}} |
There was a problem hiding this comment.
[Claude Code 🤖] Added in cardstack/boxel f87ad865fa. packages/host/tests/integration/realm-policy-test.gts has a test that renders a policy with an anonymous read, an anonymous write with actingUser, and a signed-in-only grant. It checks the "anyone" pill on exactly the two opted-in grants, and config.feedbackWriter on the write alone.
habdelra
left a comment
There was a problem hiding this comment.
[Claude Code 🤖] This review checked the new fields' docs and the embedded template against what the realm's compiler and gate do with them, and checked the pairing declaration.
Nothing blocks. The field docs state two rules more broadly than the realm applies them, and one requirement in them isn't enforced by anything yet.
- Tighten the
anonymousdoc: the operation has to be under its own name, and awherethat readsactor()is skipped rather than never holding. - Say what enforces "must be able to write the realm themselves", or drop that sentence until something does.
- Drop or style the unused
anonymousclass.
| // callers could reach a policy never starts admitting them. Only a grant | ||
| // on a base operation may set it. Such a caller has no actor, so a `where` | ||
| // that reads `actor()` never holds for them: an anonymous grant is scoped by | ||
| // what the target holds. How hard anonymous callers may use the realm, and |
There was a problem hiding this comment.
[Claude Code 🤖] Two rules here are stated more broadly than the realm applies them:
- "Only a grant on a base operation may set it." The compiler also requires the operation to be invoked under its own name. A custom operation like
appendActivityis built ontransform, so an author can read it as "on a base operation" and still be refused. Suggest "only a grant on a base operation under its own name (read,update, …), not a custom operation or a named query". - "A
wherethat readsactor()never holds for them." It can:.public == true or .owner == actor()evaluates totruefor a public card with no actor in the context. What actually keeps it out is that the realm doesn't evaluate such awherefor an anonymous caller. Suggest "the realm doesn't evaluate awherethat readsactor()for them, so such a grant never admits one". The paired boxel review has the matching thread.
Non-blocking.
There was a problem hiding this comment.
[Claude Code 🤖] Fixed in 79bae33. The doc now says the opt-in applies to a base operation under its own name, not a custom operation or a named query. It also says the realm doesn't evaluate a where that reads actor() for such a caller, so such a grant never admits one.
| @field anonymous = contains(BooleanField); | ||
| // For an anonymous grant on a write: the key, in the governed realm's | ||
| // `realm.json` settings, whose value is the user the write is made as. | ||
| // That user must be able to write the realm themselves. The realm names |
There was a problem hiding this comment.
[Claude Code 🤖] "That user must be able to write the realm themselves": what happens when they can't? The paired compiler change doesn't check this, so for now it's a requirement nothing enforces. Please say what the realm does in that case (for example, "otherwise the write is refused") once the change that enforces it exists, or leave the sentence out until then. Non-blocking.
There was a problem hiding this comment.
[Claude Code 🤖] Removed the sentence in 79bae33. The check that the acting user can write the realm lives in the change that admits anonymous writes, and that change states what happens when they can't.
| </span> | ||
| {{/if}} | ||
| {{#if @model.anonymous}} | ||
| <Pill class='anonymous' data-test-operation-grant-anonymous> |
There was a problem hiding this comment.
[Claude Code 🤖] Nothing in the scoped <style> targets class='anonymous', so either drop the class or give it the styling it was meant for. Trivial and non-blocking.
There was a problem hiding this comment.
[Claude Code 🤖] Dropped the unstyled class in 79bae33.
The opt-in applies to a base operation under its own name, and the realm doesn't evaluate a `where` reading `actor()` for an anonymous caller. The acting-user key is used exactly as written. Drop an unstyled class. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
richardhjtan
left a comment
There was a problem hiding this comment.
[Claude Code 🤖] No blocking findings. Merge order: this → boxel#6534.
|
[Claude Code 🤖] Thanks for the approval. This merges first, before boxel#6534, once boxel#6546 lands. #6546 fixes the policy-standing host-test race that this definition makes more likely. |


Important
Merge order: this PR first, then cardstack/boxel#6534.
The fields are additive. Once this merges, re-run the boxel PR's Catalog Test Subset check (its pin is this PR's head), then merge it.
Merges before: cardstack/boxel#6534
OperationGrantgains two fields so a realm's policy can admit callers who aren't signed in:anonymous(boolean, off by default) opens the grant to a caller with no session as well as to signed-in ones. A grant written without it never starts admitting such callers. Only a grantable base operation invoked under its own name may set it (read,readSource,query,create,update,delete,transform,appendContainsMany,appendLine); the realm's compiler refuses it on a custom operation or a named query.actingUser(string) is, for an anonymous grant on a write (only), the key in the governed realm'srealm.jsonsettings whose value is the user the write is made as. The realm names the user rather than the policy, because a policy can live in another realm and its writers must not decide whose identity that realm's writes carry.The realm doesn't evaluate a
wherethat readsactor()for such a caller, so such a grant never admits one; an anonymous grant is scoped by what the target holds. The acting-user key is used exactly as written. How hard anonymous callers may use a realm and which addresses it refuses are the governed realm's ownrealm.jsonsettings, not the policy's. The embedded grant view shows an "anyone" pill and the acting-user key.🤖 Generated with Claude Code