Skip to content

Let an operation grant admit callers who aren't signed in - #802

Merged
habdelra merged 3 commits into
mainfrom
cs-13533-policy-compile-a-grants-anonymous-opt-in-and-acting-user-key
Oct 6, 2026
Merged

habdelra merged 3 commits into
mainfrom
cs-13533-policy-compile-a-grants-anonymous-opt-in-and-acting-user-key

Conversation

@habdelra

@habdelra habdelra commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

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

OperationGrant gains 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's realm.json settings 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 where that reads actor() 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 own realm.json settings, not the policy's. The embedded grant view shows an "anyone" pill and the acting-user key.

🤖 Generated with Claude Code

`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>
Copilot AI balanced review requested due to automatic review settings October 5, 2026 16:40
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Staging Submissions Preview

This PR's content is pushed to the staging submissions realm: https://realms-staging.stack.cards/submissions/

Changed folders:

  • realm-policy/

Updated at 2026-10-05 21:55:36 UTC for commit 2845e72. Shared realm: only this PR's changed files are pushed; files touched by multiple PRs reflect whichever pushed last, and deleted files are not removed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Medium severity · 1 Low severity

Open (2)
What changed in this PR

Extends OperationGrant for anonymous callers, coordinated with cardstack/boxel#6534.

Changes:

  • Adds anonymous and actingUser fields.
  • 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>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Comment on lines +94 to +98
{{#if @model.anonymous}}
<Pill class='anonymous' data-test-operation-grant-anonymous>
anyone
</Pill>
{{#if @model.actingUser}}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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 habdelra left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

  1. Tighten the anonymous doc: the operation has to be under its own name, and a where that reads actor() is skipped rather than never holding.
  2. Say what enforces "must be able to write the realm themselves", or drop that sentence until something does.
  3. Drop or style the unused anonymous class.

Comment thread realm-policy/realm-policy.gts Outdated
Comment on lines +65 to +68
// 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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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 appendActivity is built on transform, 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 where that reads actor() never holds for them." It can: .public == true or .owner == actor() evaluates to true for a public card with no actor in the context. What actually keeps it out is that the realm doesn't evaluate such a where for an anonymous caller. Suggest "the realm doesn't evaluate a where that reads actor() for them, so such a grant never admits one". The paired boxel review has the matching thread.

Non-blocking.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Comment thread realm-policy/realm-policy.gts Outdated
@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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Comment thread realm-policy/realm-policy.gts Outdated
</span>
{{/if}}
{{#if @model.anonymous}}
<Pill class='anonymous' data-test-operation-grant-anonymous>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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>
@habdelra
habdelra requested a review from a team October 5, 2026 17:07

@richardhjtan richardhjtan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] No blocking findings. Merge order: this → boxel#6534.

@habdelra

habdelra commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

[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.

@habdelra
habdelra merged commit 9dcd29e into main Oct 6, 2026
12 checks passed

This branch was successfully deployed

1 active deployment
production — 2845e727 Deployed Oct 6, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants