Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions realm-policy/realm-policy.gts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
import PolicyPredicateField from '@cardstack/catalog/fields/policy-predicate/policy-predicate';
import { subscribeToRealm } from '@cardstack/runtime-common';
import StringField from 'https://cardstack.com/base/string';
import BooleanField from 'https://cardstack.com/base/boolean';
import {
BoxelInput,
Button,
Expand Down Expand Up @@ -59,6 +60,22 @@ export class OperationGrant extends FieldDef {
// A BXL boolean expression over the caller and the target. Absent means
// the grant is unconditional.
@field where = contains(PolicyPredicateField);
// Whether the grant also admits a caller who isn't signed in. Off, it
// admits only signed-in callers, so a grant written before anonymous
// callers could reach a policy never starts admitting them. Only a grant on
// a base operation under its own name (`read`, `update`, …) may set it, not
// one on a custom operation or a named query. The realm doesn't evaluate a
// `where` that reads `actor()` for such a caller, so a grant whose `where`
// does never admits one: an anonymous grant is scoped by what the target
// holds. How hard anonymous callers may use the realm, and which addresses
// it refuses, are the governed realm's own settings, not the policy's.
@field anonymous = contains(BooleanField);
// For an anonymous grant on a write: the key, exactly as written, 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 the
// policy can live in another realm and its writers must not decide whose
// identity this realm's writes carry.
@field actingUser = contains(StringField);

static embedded = class Embedded extends Component<typeof OperationGrant> {
<template>
Expand All @@ -74,6 +91,18 @@ export class OperationGrant extends FieldDef {
always
</span>
{{/if}}
{{#if @model.anonymous}}
<Pill data-test-operation-grant-anonymous>
anyone
</Pill>
{{#if @model.actingUser}}
Comment on lines +94 to +98

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.

<span class='keyword'>as</span>
<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.

{{/if}}
{{/if}}
</div>
<style scoped>
.operation-grant {
Expand All @@ -91,6 +120,10 @@ export class OperationGrant extends FieldDef {
font-size: var(--boxel-font-size-sm);
color: var(--muted-foreground, var(--boxel-450));
}
.acting-user {
font-family: var(--boxel-monospace-font-family, monospace);
font-size: var(--boxel-font-size-sm);
}
</style>
</template>
};
Expand Down
Loading