Skip to content

Commit 3dd882a

Browse files
aspiersclaude
andcommitted
feat(permissions): add org.hyperboards.authWrite; mark design implemented
The repo has a third first-class record namespace — org.hyperboards (board, displayProfile, described in AGENTS.md as the "Hyperboards visual layer") — that had no permission set, so a user's Hyperboards data was not grantable via any published set. Add org.hyperboards.authWrite granting create/update/delete over both org.hyperboards.* record collections, matching the other two namespaces. Verified it enumerates exactly the type:"record" defs on disk and expands to one combined repo: scope. Also update the surrounding docs that the new set and the now-shipped state make stale: - docs/design/permission-sets.md: Status "Proposed" -> "Implemented" (the sets are published lexicon files in this PR, not a proposal); "two sets" -> three throughout, with a new org.hyperboards.authWrite subsection and the three distinct authorities (hypercerts.org, hyperboards.org, certified.app) spelled out; corrected a stray claim that hypercerts/hyperboards "share a domain". - SKILL.md: third table row; "two sets, never one" -> "one set per namespace". - .changeset/permission-sets-crud.md: two -> three sets. - AGENTS.md: the add-a-lexicon permission-set step now lists all three namespaces. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b0e7ba7 commit 3dd882a

6 files changed

Lines changed: 144 additions & 76 deletions

File tree

.agents/skills/building-with-hypercerts-lexicons/SKILL.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -192,16 +192,17 @@ if (result.success) {
192192

193193
## Permission Sets (OAuth scopes)
194194

195-
To **write** Hypercerts or Certified records to a user's repo, your app needs
196-
the user's authorization. AT Protocol grants this via OAuth scopes: each
197-
collection write needs a `repo:<collection>?action=…` scope. Rather than list
198-
every collection by hand, request a **permission set** — a published bundle —
199-
with a single `include:` scope:
200-
201-
| Permission set NSID | Grants write (create/update/delete) on |
202-
| -------------------------- | ----------------------------------------- |
203-
| `org.hypercerts.authWrite` | all `org.hypercerts.*` record collections |
204-
| `app.certified.authWrite` | all `app.certified.*` record collections |
195+
To **write** Hypercerts, Hyperboards, or Certified records to a user's repo,
196+
your app needs the user's authorization. AT Protocol grants this via OAuth
197+
scopes: each collection write needs a `repo:<collection>?action=…` scope. Rather
198+
than list every collection by hand, request a **permission set** — a published
199+
bundle — with a single `include:` scope:
200+
201+
| Permission set NSID | Grants write (create/update/delete) on |
202+
| --------------------------- | ------------------------------------------ |
203+
| `org.hypercerts.authWrite` | all `org.hypercerts.*` record collections |
204+
| `org.hyperboards.authWrite` | all `org.hyperboards.*` record collections |
205+
| `app.certified.authWrite` | all `app.certified.*` record collections |
205206

206207
In your OAuth client's authorization request, ask for the set(s) you need:
207208

@@ -213,10 +214,10 @@ The user's PDS resolves the published set and expands it into the underlying
213214
`repo:` scopes on the consent screen, where the user sees the set's plain-language
214215
description (e.g. "Manage your Hypercerts data"). Notes:
215216

216-
- **Two sets, never one.** A permission set may only reference its own namespace
217-
authority (atproto permission spec), so `org.hypercerts.*` and
218-
`app.certified.*` cannot share a set. An app needing both requests **both**
219-
`include:` scopes.
217+
- **One set per namespace, never combined.** A permission set may only reference
218+
its own namespace authority (atproto permission spec), so `org.hypercerts.*`,
219+
`org.hyperboards.*`, and `app.certified.*` each need their own set. An app
220+
needing more than one requests **each** `include:` scope.
220221
- **Writes only.** These cover create/update/delete. **Reading** records needs no
221222
scope at all — atproto repo records are public.
222223
- **`repo:` scopes carry no `aud`** — request the `include:` without an `?aud=`.

.changeset/permission-sets-crud.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
"@hypercerts-org/lexicon": minor
33
---
44

5-
Add two permission-set lexicons — `org.hypercerts.authWrite` and `app.certified.authWrite` — each granting create/update/delete over every record collection in its namespace.
5+
Add three permission-set lexicons — `org.hypercerts.authWrite`, `org.hyperboards.authWrite`, and `app.certified.authWrite` — each granting create/update/delete over every record collection in its namespace.
66

77
A [permission set](https://atproto.com/specs/permission#permission-sets) lets any AT Protocol app request a whole bundle of `repo:` scopes with a single `include:<nsid>` OAuth scope, instead of enumerating each collection by hand. The user's PDS resolves and expands the set during the OAuth grant; the same published set can also be consumed by services (e.g. the Certified group service) when expanding API-key scopes.
88

9-
There are **two** sets rather than one because the spec requires it: a permission set "is limited to expressing permissions that reference resources under the same NSID namespace as the set itself" and "can not address 'sibling groups' or 'parents'". `org.hypercerts` and `app.certified` are separate namespace authorities, so they cannot be combined in a single set — an app needing both requests both `include:` scopes.
9+
There are **three** sets rather than one because the spec requires it: a permission set "is limited to expressing permissions that reference resources under the same NSID namespace as the set itself" and "can not address 'sibling groups' or 'parents'". `org.hypercerts`, `org.hyperboards`, and `app.certified` are separate namespace authorities, so they cannot be combined in a single set — an app needing more than one requests each `include:` scope.
1010

1111
Permission sets are published as-is (they are the source of truth for what gets published to AT Protocol) but have no TypeScript shape — `lex gen-api` cannot generate code for `permission-set` defs. They are therefore excluded from the codegen globs (`gen-api`/`gen-md`/`gen-ts`) and from `generated/exports.ts`, while still shipping as raw lexicon JSON.
1212

AGENTS.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -235,18 +235,21 @@ scripts/ Build and codegen scripts
235235
- Auto-generate `generated/exports.ts` with all exports
236236

237237
3. Update the relevant **permission set** if appropriate:
238-
- The `permission-set` lexicons (`lexicons/org/hypercerts/authWrite.json`
239-
and `lexicons/app/certified/authWrite.json`) each enumerate **every
238+
- The `permission-set` lexicons (`lexicons/org/hypercerts/authWrite.json`,
239+
`lexicons/org/hyperboards/authWrite.json`, and
240+
`lexicons/app/certified/authWrite.json`) each enumerate **every
240241
`type: "record"` collection in their namespace** — the list is both
241242
the grant and its boundary, with no wildcards allowed inside a set.
242-
- When you **add** a new `org.hypercerts.*` or `app.certified.*` record
243-
lexicon, add its NSID to the matching set's `permissions[].collection`
244-
list, or it will not be grantable via the published set. Likewise
245-
**remove** the NSID when you delete a record lexicon.
243+
- When you **add** a new `org.hypercerts.*`, `org.hyperboards.*`, or
244+
`app.certified.*` record lexicon, add its NSID to the matching set's
245+
`permissions[].collection` list, or it will not be grantable via the
246+
published set. Likewise **remove** the NSID when you delete a record
247+
lexicon.
246248
- A set may only reference collections under **its own namespace
247-
authority**, so `org.hypercerts.*` records go in
248-
`org.hypercerts.authWrite` and `app.certified.*` records in
249-
`app.certified.authWrite` — never the other set.
249+
authority**, so each namespace's records go in its own set
250+
(`org.hypercerts.*``org.hypercerts.authWrite`, `org.hyperboards.*`
251+
`org.hyperboards.authWrite`, `app.certified.*`
252+
`app.certified.authWrite`) — never another set.
250253
- This does not apply to non-record defs (e.g. `query`, `object`, or
251254
`permission-set` lexicons themselves), which are not repo collections.
252255
- See [docs/design/permission-sets.md](docs/design/permission-sets.md)

SCHEMAS.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77

88
Hypercerts-specific lexicons for tracking impact work and claims.
99

10+
### `org.hypercerts.authWrite`
11+
12+
**Description:** Permission set granting create, update, and delete on every Hypercerts (org.hypercerts) record collection.
13+
14+
---
15+
1016
### `org.hypercerts.claim.activity`
1117

1218
**Description:** A hypercert record tracking impact work.
@@ -299,12 +305,6 @@ A free-text string value (e.g. a display name, wallet address, or other identifi
299305

300306
---
301307

302-
### `org.hypercerts.permissions.crud`
303-
304-
**Description:** Permission set granting create, update, and delete on every Hypercerts (org.hypercerts) record collection.
305-
306-
---
307-
308308
### `org.hypercerts.workscope.cel`
309309

310310
**Description:** A structured, machine-evaluable work scope definition using CEL (Common Expression Language). Tags referenced in the expression correspond to org.hypercerts.workscope.tag keys. See https://github.com/google/cel-spec. Note: this is intentionally type 'object' (not 'record') so it can be directly embedded inline in union types (e.g., activity.workScope) without requiring a separate collection or strongRef indirection.
@@ -490,6 +490,12 @@ A labeled URL reference.
490490

491491
---
492492

493+
### `app.certified.authWrite`
494+
495+
**Description:** Permission set granting create, update, and delete on every Certified (app.certified) record collection.
496+
497+
---
498+
493499
### `app.certified.graph.follow`
494500

495501
**Description:** Record declaring a social 'follow' relationship of another account. Duplicate follows will be ignored by the AppView.
@@ -547,12 +553,6 @@ The EIP-712 typed data message that was signed by the wallet. Contains the field
547553

548554
---
549555

550-
### `app.certified.permissions.crud`
551-
552-
**Description:** Permission set granting create, update, and delete on every Certified (app.certified) record collection.
553-
554-
---
555-
556556
### `app.certified.signature.defs`
557557

558558
**Description:** Common type definitions for cryptographic signatures attached to records, per the ATProtocol Attestation Specification.

docs/design/permission-sets.md

Lines changed: 80 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
# Design: Permission Sets for Hypercerts & Certified data
1+
# Design: Permission Sets for Hypercerts, Hyperboards & Certified data
22

3-
Status: **Proposed**
3+
Status: **Implemented** — the sets described here are published as lexicon
4+
files in this repository (`lexicons/org/hypercerts/authWrite.json`,
5+
`lexicons/org/hyperboards/authWrite.json`,
6+
`lexicons/app/certified/authWrite.json`). This document records the design and
7+
the rationale behind it.
48

5-
This document designs **permission sets** for the `org.hypercerts.*` and
6-
`app.certified.*` record collections defined in this repository — published,
7-
reusable bundles of AT Protocol OAuth scopes that any application can request
8-
with a single `include:<nsid>` scope, instead of enumerating every underlying
9-
`repo:` scope by hand.
9+
This document designs **permission sets** for the `org.hypercerts.*`,
10+
`org.hyperboards.*`, and `app.certified.*` record collections defined in this
11+
repository — published, reusable bundles of AT Protocol OAuth scopes that any
12+
application can request with a single `include:<nsid>` scope, instead of
13+
enumerating every underlying `repo:` scope by hand.
1014

1115
## Background: what a permission set is
1216

@@ -35,11 +39,12 @@ set replaces that with one curated, versioned name.
3539
These are general AT Protocol artifacts, not specific to any one service. The
3640
expected consumers, in rough order of generality:
3741

38-
1. **Any atproto app working with Hypercerts / Certified data directly.** An
39-
OAuth client requests `include:org.hypercerts.authWrite` (and/or the
40-
`app.certified.*` set) in its authorization request; the user's PDS expands it
41-
and the app reads/writes those collections in the user's own repo. No
42-
intermediary is involved. This is the broadest case.
42+
1. **Any atproto app working with Hypercerts / Hyperboards / Certified data
43+
directly.** An OAuth client requests `include:org.hypercerts.authWrite`
44+
(and/or the `org.hyperboards.*` or `app.certified.*` set) in its
45+
authorization request; the user's PDS expands it and the app reads/writes
46+
those collections in the user's own repo. No intermediary is involved. This
47+
is the broadest case.
4348
2. **The Certified group service (CGS), via OAuth.** CGS is used as a standard
4449
OAuth resource (reached through AT Protocol service proxying). A client
4550
calling CGS on a user's behalf requests the same sets; the user's PDS expands
@@ -56,17 +61,18 @@ key-creation — reads the same published definition.
5661

5762
## The sets
5863

59-
We define two **record-collection write** sets — one per data namespace in this
60-
repo. Each grants the three `repo:` write actions — create, update, delete —
64+
We define three **record-collection write** sets — one per data namespace in
65+
this repo. Each grants the three `repo:` write actions — create, update, delete —
6166
over all of that namespace's record collections.
6267

63-
| Set NSID | Grants write (create/update/delete) on |
64-
| -------------------------- | ----------------------------------------- |
65-
| `org.hypercerts.authWrite` | all `org.hypercerts.*` record collections |
66-
| `app.certified.authWrite` | all `app.certified.*` record collections |
68+
| Set NSID | Grants write (create/update/delete) on |
69+
| --------------------------- | ------------------------------------------ |
70+
| `org.hypercerts.authWrite` | all `org.hypercerts.*` record collections |
71+
| `org.hyperboards.authWrite` | all `org.hyperboards.*` record collections |
72+
| `app.certified.authWrite` | all `app.certified.*` record collections |
6773

68-
Both are authored and published from **this repository**, which is the namespace
69-
authority for both — see _Namespace authority_ below.
74+
All three are authored and published from **this repository**, which is the
75+
namespace authority for all of them — see _Namespace authority_ below.
7076

7177
> **Write only — no read action.** The leaf is `authWrite` because a `repo:`
7278
> permission has only three actions — `create`, `update`, `delete` (there is no
@@ -134,6 +140,36 @@ Enumerates every `type: "record"` collection under `org.hypercerts.*`:
134140
}
135141
```
136142

143+
### `org.hyperboards.authWrite`
144+
145+
Enumerates every `type: "record"` collection under `org.hyperboards.*`:
146+
147+
```jsonc
148+
{
149+
"lexicon": 1,
150+
"id": "org.hyperboards.authWrite",
151+
"defs": {
152+
"main": {
153+
"type": "permission-set",
154+
"description": "Permission set granting create, update, and delete on every Hyperboards (org.hyperboards) record collection.",
155+
"title": "Manage your Hyperboards data",
156+
"detail": "Create, edit, and delete your Hyperboards records (board configurations and display profiles).",
157+
"permissions": [
158+
{
159+
"type": "permission",
160+
"resource": "repo",
161+
"collection": [
162+
"org.hyperboards.board",
163+
"org.hyperboards.displayProfile",
164+
],
165+
"action": ["create", "update", "delete"],
166+
},
167+
],
168+
},
169+
},
170+
}
171+
```
172+
137173
### `app.certified.authWrite`
138174

139175
Enumerates every `type: "record"` collection under `app.certified.*`:
@@ -170,9 +206,10 @@ Enumerates every `type: "record"` collection under `app.certified.*`:
170206
}
171207
```
172208

173-
> The collection lists are derived from this repo's `lexicons/org/hypercerts/`
174-
> and `lexicons/app/certified/` (`type: "record"` defs at time of writing). They
175-
> must be **kept in sync** as record types are added — see _Maintenance_.
209+
> The collection lists are derived from this repo's `lexicons/org/hypercerts/`,
210+
> `lexicons/org/hyperboards/`, and `lexicons/app/certified/` (`type: "record"`
211+
> defs at time of writing). They must be **kept in sync** as record types are
212+
> added — see _Maintenance_.
176213
177214
## Design rules these sets follow
178215

@@ -188,12 +225,14 @@ and `rpc:` permissions (by lxm NSID).
188225
Consequences here:
189226

190227
- A set under `org.hypercerts.*` may grant `repo:org.hypercerts.*` collections,
191-
and **only** those — it cannot reference `app.certified.*` collections, or any
192-
third party's, or any service's `rpc:` methods. Likewise the `app.certified.*`
193-
set is confined to `app.certified.*`.
194-
- This is why there are **two** sets rather than one combined set: the two
195-
namespaces are distinct authorities and cannot legally be mixed in a single
196-
set. An app that needs both requests both `include:` scopes.
228+
and **only** those — it cannot reference `org.hyperboards.*` or
229+
`app.certified.*` collections, or any third party's, or any service's `rpc:`
230+
methods. Likewise each of the `org.hyperboards.*` and `app.certified.*` sets
231+
is confined to its own namespace.
232+
- This is why there are **three** sets rather than one combined set: the three
233+
namespaces have distinct authorities (`hypercerts.org`, `hyperboards.org`,
234+
`certified.app`) and cannot legally be mixed in a single set. An app that
235+
needs more than one requests each `include:` scope.
197236
- The authority confinement is also what makes a published set **safe for any
198237
client to resolve**: an `include:` can only ever widen access to the set's own
199238
namespace, never smuggle in foreign permissions.
@@ -211,9 +250,10 @@ not what these sets are for.)
211250

212251
### `repo:` permissions carry no `aud`
213252

214-
A `repo:` scope targets the **user's own repo**, so it has no audience. Both sets
215-
here are `repo:`-only, so an `include:` for them is requested **without** any
216-
`?aud=` parameter. Each set expands to a **single combined `repo:` scope** that
253+
A `repo:` scope targets the **user's own repo**, so it has no audience. All
254+
three sets here are `repo:`-only, so an `include:` for them is requested
255+
**without** any `?aud=` parameter. Each set expands to a **single combined
256+
`repo:` scope** that
217257
lists every collection — `IncludeScope.toScopes` coalesces the permission's
218258
collection array into one scope string
219259
(`repo:?collection=<a>&collection=<b>&…&action=create&action=update&action=delete`),
@@ -227,7 +267,7 @@ would.)
227267

228268
## Publication and resolution
229269

230-
Both sets are published like any other Lexicon schema in this repo (see
270+
All three sets are published like any other Lexicon schema in this repo (see
231271
[`PUBLISHING.md`](../PUBLISHING.md)) and resolved at runtime via the standard
232272
**Lexicon resolution system**, to which the permission spec defers:
233273
_"Permission sets are Lexicon schemas and are published and fetched using the
@@ -269,16 +309,17 @@ full detail lives in the CGS design doc
269309

270310
The point for _this_ repo: the sets must be **self-contained and
271311
deployment-agnostic** — no CGS-specific assumptions baked into the definitions.
272-
The two CRUD sets satisfy this trivially (pure `repo:` collection grants).
312+
All three CRUD sets satisfy this trivially (pure `repo:` collection grants).
273313

274314
## Maintenance
275315

276316
The enumerated `collection` lists are the one ongoing obligation. Whenever a
277-
`type: "record"` lexicon is added to or removed from `org.hypercerts.*` or
278-
`app.certified.*`, the corresponding set must be updated and re-published, or the
279-
new collection will be silently uncovered by the set. Worth a check in the
280-
release process (and ideally an automated test asserting each set lists exactly
281-
the `type: "record"` defs in its namespace).
317+
`type: "record"` lexicon is added to or removed from `org.hypercerts.*`,
318+
`org.hyperboards.*`, or `app.certified.*`, the corresponding set must be updated
319+
and re-published, or the new collection will be silently uncovered by the set.
320+
The `AGENTS.md` "Adding / modifying a lexicon" checklist calls this out. Worth a
321+
check in the release process too (and ideally an automated test asserting each
322+
set lists exactly the `type: "record"` defs in its namespace).
282323

283324
> **Known pending update.** PR
284325
> [#219](https://github.com/hypercerts-org/hypercerts-lexicon/pull/219)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"lexicon": 1,
3+
"id": "org.hyperboards.authWrite",
4+
"defs": {
5+
"main": {
6+
"type": "permission-set",
7+
"description": "Permission set granting create, update, and delete on every Hyperboards (org.hyperboards) record collection.",
8+
"title": "Manage your Hyperboards data",
9+
"detail": "Create, edit, and delete your Hyperboards records (board configurations and display profiles).",
10+
"permissions": [
11+
{
12+
"type": "permission",
13+
"resource": "repo",
14+
"collection": [
15+
"org.hyperboards.board",
16+
"org.hyperboards.displayProfile"
17+
],
18+
"action": ["create", "update", "delete"]
19+
}
20+
]
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)