Repository navigation
State every limit of a realm policy in the skills - #179
Conversation
realm-policy-authoring opens with "What a policy does not do", one line per deliberate limit with a pointer to where it is covered, and the sections gain what was missing: urgent revocation and why removing a realm permission doesn't revoke, the explain/validate and owner-route exceptions to "only widens", the whole-card answer of a card+json write, raw .json not being a superset of a read, the subtype guard on query grants, the snapshot-read log, unwarned computed values, the subtype-flagged nonGrantable case, timing, and who meets an archived realm's seal. card-operations-authoring lists explain and validate among the base operations, qualifies "any caller who can read it can invoke any read", and states the timing caveat and the no-op PATCH answer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
habdelra
left a comment
There was a problem hiding this comment.
[Claude Code 🤖] This review checked every factual claim the diff adds to the two skills, the glossary and the index against the platform code (gate, policy, policy-query, policy-filter, explain, validate, telemetry, dispatch, realm.ts). It also checked that the up-front list's section pointers land on sections that cover each item, and looked for overlap with the in-flight edits to the same skill. It didn't cover the parts of the skills this diff doesn't touch.
No blocking issues. Most of the added claims match the code. Two summary bullets in "What a policy does not do" say less than, or the opposite of, the sections they point at, and those two are worth fixing before merge.
- The revocation bullet implies a policy edit is prompt on the direct lane, which contradicts §3, and "use the direct lane" isn't something the revoker can do. See the inline comment there.
- The archived-realm bullet leaves out that a stored-bytes
GETshows the seal to every signed-in caller. The §13 bullet also groups the scoped-CSS serve with routes it doesn't behave like. See the inline comment. - A question on the §11 subtype
nonGrantablesentence: a named query run on the supertype may still return the subtype's cards. See the inline comment. card-operations-authoring: the failed-to-lower exception should cover the writer clause too.- Two nits: the
nonGrantablepointer should include §11, and "redeclares a field" is broader than what §7 and the code exclude.
Section numbering: nothing is renumbered, and the up-front list's §13 pointers still land on "Refusals a caller sees" if the "Choosing what to grant" section from the paired skill edit goes in after it, as that branch places it. The index.md edit changes the line next to the realm-policy-authoring line that edit also rewrites, so expect a trivial merge conflict there.
| - **Search is not a prompt revocation boundary.** A direct read judges the card | ||
| as stored and refuses on the next request. A search answers from the index and | ||
| keeps listing the card until it is reindexed. To revoke urgently, change the | ||
| card or the policy, and use the direct lane until the reindex lands (§3, §7). |
There was a problem hiding this comment.
[Claude Code 🤖] This bullet's remedy disagrees with the §3 paragraph it points at. "Change the card or the policy, and use the direct lane until the reindex lands" implies a policy edit takes effect on the direct lane at once. §3 says "Both lanes see a policy-card edit once the policy card is reindexed", which matches the code: the gate reads the compiled policy, and that is revalidated off the policy card's index row. Only a card edit is prompt on the direct lane, because the gate judges the predicate against storedSubject, the card's stored source.
"Use the direct lane" also gives the revoker nothing to do. The caller being revoked chooses which lane they use, and their search keeps returning the row whole until the reindex.
Suggested wording: "To revoke urgently, edit the card the predicate reads. The direct lane refuses on the next request, and a search stops listing the card once it is reindexed. A policy-card edit reaches both lanes only once the policy card is reindexed (§3, §7)."
Regression (new text). Non-blocking, but it's the advice a reader acts on during an incident, so worth fixing here.
There was a problem hiding this comment.
[Claude Code 🤖] Fixed in f7ebbad. The bullet now says to change what the grant reads on the card: a direct read refuses at once, and a search once the card is reindexed. A policy-card edit reaches both lanes only once the policy card is reindexed, which matches §3.
| - **An archived realm tells callers it is archived where the policy would | ||
| admit them** (§13). |
There was a problem hiding this comment.
[Claude Code 🤖] The summary understates what §13 says further down. Per §13 (and the code), every signed-in caller of an archived private realm that has a policy meets the seal on a stored-bytes GET, whether or not a grant reaches the file. The cause is that GRANTABLE_BYTES routes are only CONSUMES_COARSE_OUTCOME, and #policyJudges admits any authenticated user once the realm names a policy. So on those routes the seal tells a caller no grant admits that the realm is archived. That is exactly the limit this list exists to state.
Suggested wording: "An archived realm tells callers it is archived where the policy would admit them, and tells every signed-in caller on the routes that serve stored bytes (§13)."
A smaller point on the §13 bullet: the hashed scoped-CSS serve doesn't behave like the byte routes. It is APPLIES_ARCHIVED_SEAL and seals only where it would serve a stylesheet, so a hash the realm holds no stylesheet for is a not-found whether or not the realm is archived (see the comment above isHashedScopedCSSRequest in realm.ts). "Every signed-in caller … meets the seal on a GET" doesn't quite hold for that serve.
Regression (new text), non-blocking.
There was a problem hiding this comment.
[Claude Code 🤖] Fixed in f7ebbad. The summary now says a caller only the policy admits is told where a grant would admit them, and every signed-in caller is told on the stored-bytes routes. §13 no longer groups the scoped-CSS serve with those routes: it shows the seal only where it would serve a stylesheet, so a hash it doesn't hold is not found, archived or not.
| the flag doesn't make it grantable. A flag on a *subtype* is different: a | ||
| grant on the supertype records nothing, and the realm refuses it at runtime | ||
| for the subtype's cards alone. `explain` and `validate` are always |
There was a problem hiding this comment.
[Claude Code 🤖] Does "the realm refuses it at runtime for the subtype's cards alone" hold for a named query? At the gate it holds: gateOperation checks definition.nonGrantable on the target card's own type. A named query (base query) never reaches the gate, though. It is scoped by typeScope in policy-query.ts, which checks ownDeclaration on the queried type and nonGrantableInChain from that type upward. Run the supertype's named query, and the compiled filter matches the subtype's rows too. I can't find anything that drops the rows of a subtype that flagged the query.
If that's right, the sentence needs "except a named query run on the supertype, whose search still returns the subtype's cards". If something does drop those rows, which code is it?
Question, non-blocking.
There was a problem hiding this comment.
[Claude Code 🤖] Confirmed, it doesn't hold for a search. typeScope checks nonGrantableInChain from the queried type up, so a search through the supertype can still return the subtype's cards. Fixed in f7ebbad: §11 now says the realm refuses the operation invoked on one of the subtype's cards, while a search through the supertype is judged by the supertype and its ancestors, so those cards can still come back.
| **The realm's own read/write permissions come first.** Any caller who can write | ||
| the realm can invoke any mutating operation on it, a `nonGrantable` one | ||
| included; any caller who can read it can invoke any read. A realm that names a | ||
| policy is consulted only for what those permissions declined: everything, for | ||
| a caller with no permission on the realm; writes, for one who may read it. | ||
| included; any caller who can read it can invoke any read the card carries, | ||
| except an operation that failed to lower, which refuses everyone, and an | ||
| `explain` or `validate`, which also need read on every other realm they | ||
| consult. A realm that names a policy is consulted only for what those |
There was a problem hiding this comment.
[Claude Code 🤖] The "failed to lower, which refuses everyone" exception is attached only to the read clause, so the writer clause just before it still says a realm writer "can invoke any mutating operation". That's false for an invalid mutating declaration too. dispatch.ts throws the 422 invalid-operation on declared.invalid for every caller, before any permission or gate check. Suggest moving the exception so it covers both clauses, e.g. "…can invoke any read the card carries. An operation that failed to lower refuses everyone, writers included, and an explain or validate also needs read on every other realm it consults."
Regression (new text), non-blocking.
There was a problem hiding this comment.
[Claude Code 🤖] Fixed in f7ebbad. The failed-lowering case is now its own sentence: it refuses every caller, realm writers included, with 422 invalid-operation.
| A def carries `explain` and `validate` only where a card declares an | ||
| operation on them, and each must be `nonGrantable` (`realm-policy-authoring` | ||
| §9, §10). |
There was a problem hiding this comment.
[Claude Code 🤖] Nit: the claim here is that each must be nonGrantable, which realm-policy-authoring covers in §11 ("explain and validate are always nonGrantable"). §9 and §10 cover what the two operations answer. Suggest §9, §10, §11, or only §11 if the pointer is meant for the flag.
Non-blocking.
There was a problem hiding this comment.
[Claude Code 🤖] Fixed in f7ebbad. The pointer now includes §11.
| - **A subtype that redeclares a field a `query` grant compares is left out of | ||
| that search**, though a direct read may still admit its cards (§7). |
There was a problem hiding this comment.
[Claude Code 🤖] Nit: "redeclares a field" is broader than §7 and the code. readsPathAlike excludes only a redeclaration that reads the path differently: computed, query-backed, a different primitive type, or a list. Under snapshot: true a computed one doesn't count. A subtype that redeclares the field with the same declaration is judged as usual. Suggest "redeclares a field a query grant compares, in a way the index holds differently, …".
Non-blocking.
There was a problem hiding this comment.
[Claude Code 🤖] Fixed in f7ebbad. The summary bullet names the differences that exclude a subtype (computed or query-backed field, another type, a list, or not declared), as §7 does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
richardhjtan
left a comment
There was a problem hiding this comment.
[Claude Code 🤖] One blocking finding, inline.
| included; any caller who can read it can invoke any read the card carries, | ||
| except an `explain` or `validate`, which also need read on every other realm | ||
| they consult. An operation that failed to lower refuses every caller, realm | ||
| writers included, with 422 `invalid-operation`. A realm that names a policy is consulted only for what those |
There was a problem hiding this comment.
[Claude Code 🤖] Blocking: 422 invalid-operation isn't what every caller gets. A caller who may not read the realm never sees it. In resolveAndGate (dispatch.ts), a resolution failure for a caller with coarseDeclined === 'all' is rethrown as notPermitted, so that caller gets the not-found answer, as the §5 table in this same file says ("a declaration with findings … 404 target-not-found" for a caller who may not read the realm). Suggest: "refuses every caller, realm writers included: 422 invalid-operation for a caller who may read the realm, and the not-found answer for one who may not."
There was a problem hiding this comment.
[Claude Code 🤖] Fixed in 644a002. The sentence now reads: an operation that failed to lower refuses every caller, realm writers included: 422 invalid-operation for a caller who may read the realm, and the not-found answer for one who may not (§5).
…eration that failed to lower Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit # Conflicts: # index.md
… them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
…policy-authoring-skill-anonymous' into cs-13109-policy-skills-cover-every-limit
Staging Workspace Sync SuccessfulSuccessfully synced changes to staging workspace Sync Details |
Important
Merge order (each must merge before the next):
Compile a policy grant's opt-in to callers who aren't signed in boxel#6534: compile a grant's anonymous opt-in (merged)Policy: admit anonymous reads through a grant that opts in, rate-limited per realm and address boxel#6568(CS-13536): anonymous reads (merged)4a. Policy: let a grant on a declared operation admit a caller who isn't signed in boxel#6579 (CS-13605): anonymous named operations, stacked on #6577. boxel-skills#174's §12 describes it
4b. Policy: explain how a realm treats a caller who isn't signed in boxel#6581 (CS-13609): explaining a visitor's refusal, stacked on #6579, with its panel in Show in the explain panel how a realm treats callers who aren't signed in boxel-catalog#809. boxel-skills#174's §10/§12 describe it
Add the judgment calls to the realm-policy-authoring skill #175(CS-13108): merged, and merged into Teach the realm-policy-authoring skill anonymous grants and a realm's anonymous traffic controls #174 and this PR. The anonymous section is §12, Refusals §13, "Choosing what to grant" §14 and the checklist §15.5a.
Say that no policy grant reaches a path the realm ignores #178(CS-13336): merged, and merged into Teach the realm-policy-authoring skill anonymous grants and a realm's anonymous traffic controls #174 and this PRTeach the realm-policy-authoring skill anonymous grants and a realm's anonymous traffic controls #174(CS-13571): anonymous grants in the skill (merged)mainnow that Teach the realm-policy-authoring skill anonymous grants and a realm's anonymous traffic controls #174 has merged; merges next7a. Say that a policy's rules are not secret from the writers of other realms #181 (CS-13398): a policy's rules leak through a realm's behavior, so keep nothing secret in predicates; stacked on this PR, merges right after it
Every PR in this order carries this callout. Whoever opens one of the PRs above tells the owners of the others, and each owner updates its own callout.
In service of operation-permission policies, which let a realm's policy admit callers the realm's own permissions decline. The skills are the documentation of how a policy works, so every deliberate limit of the system has to be stated in them, prominently, before a reader relies on a policy. This PR audits
realm-policy-authoringandcard-operations-authoringagainst those limits and fills the gaps.realm-policy-authoringgains a section ahead of §1, What a policy does not do, with one line per limit and a pointer to where each is covered. The sections it points at gain what was missing:_permissionsanswers on the permissions alone.POSTorPATCHanswers with the whole indexed card, unprojected byread, even for aPATCHthat changes nothing..jsonis not a superset of a read, since a default read carriesincluded.querygrant's search, thesnapshotand.idexceptions, that a direct read may still admit such a card, and where an explain shows the exclusion.policy-snapshot-readlog line, and when it isn't written.rulesgrants nothing rather than failing closedinvalid-grant's second causenonGrantableflag on a subtype under a supertype's grant.card-operations-authoringchanges:explainandvalidateamong the base operations.PATCHanswer.The index and the glossary follow.
Each claim was checked against the platform code. The behavior a visitor sees in an archived realm depends on the anonymous-reads change, so #174 states it once that change ships.
🤖 Generated with Claude Code