[READ-ONLY] Mirror of https://github.com/flo-bit/contrail. atproto backend in a bottle flo-bit.dev/contrail/
0

Configure Feed

Select the types of activity you want to include in your feed.

fix space naming, switch to ats:// for spaces

Florian (Apr 25, 2026, 2:41 PM +0200) 469bf65f 0e88594f

+173 -193
+18
.changeset/space-no-underscore.md
··· 1 + --- 2 + "@atmo-dev/contrail": minor 3 + "@atmo-dev/contrail-sync": minor 4 + --- 5 + 6 + unify the per-space marker field on records as `space` everywhere. previously `listRecords` / `getRecord` HTTP responses used `space: <spaceUri>` while watch events and `WatchRecord` exposed it as `_space`. the underscored form was inconsistent with the surrounding fields (`uri`, `cid`, `did`, etc.) and forced consumers to remember which path produced which name. 7 + 8 + **breaking.** anywhere you read `r._space` on a `WatchRecord` (or a watch event payload's `record._space` / `child._space`), rename to `r.space`. drop-in. 9 + 10 + ```ts 11 + // before 12 + if (record._space) ... 13 + 14 + // after 15 + if (record.space) ... 16 + ``` 17 + 18 + no migration needed for `listRecords` / `getRecord` consumers — that path was already `space`.
+20
.changeset/space-uri-scheme.md
··· 1 + --- 2 + "@atmo-dev/contrail": minor 3 + "@atmo-dev/contrail-sync": minor 4 + "@atmo-dev/contrail-lexicons": minor 5 + --- 6 + 7 + permissioned spaces now use the `ats://` scheme instead of `at://`. tracks the [permissioned data spec](https://dholms.leaflet.pub/3mhj6bcqats2o), which floats `ats://` as a distinct scheme so spaces can't be confused with atproto record URIs at any layer (logs, query params, dispatch, error messages). 8 + 9 + ``` 10 + - at://did:plc:alice/com.example.event.space/birthday 11 + + ats://did:plc:alice/com.example.event.space/birthday 12 + ``` 13 + 14 + what changed: 15 + - `buildSpaceUri` / `parseSpaceUri` (`@atmo-dev/contrail`) emit / accept `ats://`. anything else returns `null` from `parseSpaceUri`. 16 + - generated lexicons no longer claim `format: "at-uri"` on `spaceUri` params, on the `space` record-output field, or on `spaceView.uri` — they're plain `string`. (atproto's `at-uri` format would reject `ats://`.) regenerate committed `lexicons/generated/*` with `contrail-lex generate`; downstream `lex-cli generate` then emits `v.string()` instead of `v.resourceUriString()` for those fields. 17 + - realtime topics are unchanged in shape (`space:<uri>`), but `<uri>` is now an `ats://` URI. 18 + - record URIs (the `uri` on a record, the `appPolicyRef` field, `notifyOfUpdate` payloads) keep `at://` — those are still atproto record URIs. 19 + 20 + **breaking.** anywhere you build a space URI by string concatenation (`` `at://${did}/${type}/${key}` ``), switch to `ats://` or call `buildSpaceUri()`. anywhere you persist space URIs in your own DB, migrate (`UPDATE … SET space_uri = REPLACE(space_uri, 'at://', 'ats://') WHERE space_uri LIKE 'at://%'`).
+3 -1
.claude/settings.local.json
··· 10 10 "Bash(npm test:*)", 11 11 "Bash(npx lex-cli:*)", 12 12 "Bash(pnpm vitest:*)", 13 - "WebFetch(domain:flo-bit.dev)" 13 + "WebFetch(domain:flo-bit.dev)", 14 + "WebFetch(domain:atproto.com)", 15 + "WebFetch(domain:docs.bsky.app)" 14 16 ] 15 17 } 16 18 }
+2 -2
docs/04-auth.md
··· 79 79 80 80 Server-side minting comes in two flavours: 81 81 82 - - `com.example.realtime.ticket` — POST `{ topic }` (e.g. `"space:at://..."`) → `{ ticket, topics, expiresAt }`. Bare topic-list ticket, used with the generic `<ns>.realtime.subscribe` endpoint. 82 + - `com.example.realtime.ticket` — POST `{ topic }` (e.g. `"space:ats://..."`) → `{ ticket, topics, expiresAt }`. Bare topic-list ticket, used with the generic `<ns>.realtime.subscribe` endpoint. 83 83 - `<collection>.watchRecords?mode=ws&spaceUri=…` (or `&actor=…`) handshake — returns `{ snapshot, ticket, wsUrl, sinceTs, ticketTtlMs, querySpec }`. The ticket is bound to `(did, topics, querySpec)` and is the one to use for the per-collection `watchRecords` stream — both for SSE (`?ticket=…`) and the subsequent WS upgrade. 84 84 85 85 Both flavours are signed by `realtime.ticketSecret` (a 32-byte random, configured once). Clients hand the ticket off via `?ticket=...` on connect. ··· 88 88 89 89 ```ts 90 90 createWatchStore({ 91 - url: "/xrpc/com.example.message.watchRecords?spaceUri=at://...", 91 + url: "/xrpc/com.example.message.watchRecords?spaceUri=ats://...", 92 92 mintTicket: async () => (await fetch("/api/ticket")).then((r) => r.text()), 93 93 }); 94 94 ```
+2 -2
docs/05-spaces.md
··· 6 6 7 7 > A **space** is a bag of records with one lock. The **member list** says who has the key. 8 8 9 - - One owner (DID), one type (NSID), one key. Identified by `at://<owner>/<type>/<key>`. 9 + - One owner (DID), one type (NSID), one key. Identified by `ats://<owner>/<type>/<key>` — distinct scheme from atproto record URIs (`at://`) so the two can't be confused at any layer. 10 10 - Every member (including owner) has read + write inside the space. Delete is scoped to your own records — no one can remove records they didn't author, owner included. To wipe everything, delete the space. 11 11 - Optional **app policy** gates which OAuth clients can act in the space. 12 12 ··· 51 51 | `?spaceUri=…` + JWT | one space (ACL-gated) | 52 52 | JWT, no `spaceUri` | public **unioned** with every space the caller is a member of | 53 53 54 - Filters, sorts, hydration, and references work across all three. Records from a space carry a `space: <spaceUri>` field on `listRecords`/`getRecord` responses (the watchRecords stream uses `_space` instead — see [Sync](./07-sync.md)). 54 + Filters, sorts, hydration, and references work across all three. Records from a space carry a `space: <spaceUri>` field — same on `listRecords`/`getRecord` responses and `watchRecords` stream events. 55 55 56 56 ## Invites 57 57
+1 -1
packages/contrail/tests/community-delegation.test.ts
··· 278 278 expect(createRes.status).toBe(200); 279 279 const bridged = ((await createRes.json()) as any).space.uri as string; 280 280 281 - const firstAdmin = `at://${COMMUNITY_DID}/tools.atmo.event.space/$admin`; 281 + const firstAdmin = `ats://${COMMUNITY_DID}/tools.atmo.event.space/$admin`; 282 282 expect((await grant(app, DIANA, bridged, { spaceUri: firstAdmin }, "member")).status).toBe(200); 283 283 284 284 // Alice has no direct grant in the second community, but is reachable via
+8 -8
packages/contrail/tests/community-e2e.test.ts
··· 131 131 it("adopts a community and creates reserved spaces with creator as owner", async () => { 132 132 const did = await adopt(app, ALICE); 133 133 134 - const adminUri = `at://${did}/tools.atmo.event.space/$admin`; 135 - const publishersUri = `at://${did}/tools.atmo.event.space/$publishers`; 134 + const adminUri = `ats://${did}/tools.atmo.event.space/$admin`; 135 + const publishersUri = `ats://${did}/tools.atmo.event.space/$publishers`; 136 136 137 137 // whoami in both reserved spaces → owner 138 138 for (const uri of [adminUri, publishersUri]) { ··· 171 171 }); 172 172 expect(res.status).toBe(200); 173 173 const body = (await res.json()) as any; 174 - expect(body.space.uri).toBe(`at://${COMMUNITY_DID}/tools.atmo.event.space/general`); 174 + expect(body.space.uri).toBe(`ats://${COMMUNITY_DID}/tools.atmo.event.space/general`); 175 175 expect(body.space.ownerDid).toBe(COMMUNITY_DID); 176 176 }); 177 177 ··· 193 193 }); 194 194 195 195 it("owner grants Bob member access to #general; reconciler populates spaces_members", async () => { 196 - const spaceUri = `at://${COMMUNITY_DID}/tools.atmo.event.space/general`; 196 + const spaceUri = `ats://${COMMUNITY_DID}/tools.atmo.event.space/general`; 197 197 const res = await call(app, "POST", "/xrpc/test.comm.community.space.grant", ALICE, { 198 198 spaceUri, 199 199 subject: { did: BOB }, ··· 214 214 }); 215 215 216 216 it("manager cannot grant higher than own level", async () => { 217 - const spaceUri = `at://${COMMUNITY_DID}/tools.atmo.event.space/general`; 217 + const spaceUri = `ats://${COMMUNITY_DID}/tools.atmo.event.space/general`; 218 218 // Promote Bob to manager 219 219 await call(app, "POST", "/xrpc/test.comm.community.space.grant", ALICE, { 220 220 spaceUri, ··· 232 232 }); 233 233 234 234 it("grant cannot downgrade a subject who outranks the caller", async () => { 235 - const spaceUri = `at://${COMMUNITY_DID}/tools.atmo.event.space/general`; 235 + const spaceUri = `ats://${COMMUNITY_DID}/tools.atmo.event.space/general`; 236 236 // Bob is currently manager (promoted earlier in this describe block). 237 237 // Alice is owner of the space. Bob tries to downgrade Alice to member via grant. 238 238 const res = await call(app, "POST", "/xrpc/test.comm.community.space.grant", BOB, { ··· 245 245 }); 246 246 247 247 it("revokes and reconciler removes from spaces_members", async () => { 248 - const spaceUri = `at://${COMMUNITY_DID}/tools.atmo.event.space/general`; 248 + const spaceUri = `ats://${COMMUNITY_DID}/tools.atmo.event.space/general`; 249 249 const res = await call(app, "POST", "/xrpc/test.comm.community.space.revoke", ALICE, { 250 250 spaceUri, 251 251 subject: { did: BOB }, ··· 262 262 }); 263 263 264 264 it("cannot delete a reserved space", async () => { 265 - const adminUri = `at://${COMMUNITY_DID}/tools.atmo.event.space/$admin`; 265 + const adminUri = `ats://${COMMUNITY_DID}/tools.atmo.event.space/$admin`; 266 266 const res = await call(app, "POST", "/xrpc/test.comm.community.space.delete", ALICE, { 267 267 spaceUri: adminUri, 268 268 });
+1 -1
packages/contrail/tests/community-mint.test.ts
··· 149 149 expect(ourCalls[0]!.body.rotationKeys).toHaveLength(2); 150 150 151 151 // Reserved spaces exist with the caller as owner. 152 - const adminUri = `at://${body.communityDid}/tools.atmo.event.space/$admin`; 152 + const adminUri = `ats://${body.communityDid}/tools.atmo.event.space/$admin`; 153 153 const whoami = await call(app, "GET", `/xrpc/test.comm.spaceExt.whoami?spaceUri=${encodeURIComponent(adminUri)}`, ALICE); 154 154 expect(((await whoami.json()) as any).accessLevel).toBe("owner"); 155 155 });
+2 -2
packages/contrail/tests/community-publishing.test.ts
··· 132 132 133 133 describe("community publishing + reauth — stage 3", () => { 134 134 let app: Hono; 135 - const publishers = `at://${COMMUNITY_DID}/tools.atmo.event.space/$publishers`; 136 - const admin = `at://${COMMUNITY_DID}/tools.atmo.event.space/$admin`; 135 + const publishers = `ats://${COMMUNITY_DID}/tools.atmo.event.space/$publishers`; 136 + const admin = `ats://${COMMUNITY_DID}/tools.atmo.event.space/$admin`; 137 137 138 138 beforeAll(async () => { 139 139 app = await makeApp();
+1 -1
packages/contrail/tests/invite-unified.test.ts
··· 129 129 appPassword: "anything", 130 130 }); 131 131 expect(adopt.status).toBe(200); 132 - adminUri = `at://${COMMUNITY_DID}/tools.atmo.event.space/$admin`; 132 + adminUri = `ats://${COMMUNITY_DID}/tools.atmo.event.space/$admin`; 133 133 134 134 // Alice (owner in $admin) creates a child space. 135 135 const create = await call(app, "POST", "/xrpc/test.inv.community.space.create", ALICE, {
+1 -1
packages/contrail/tests/spaces-acl.test.ts
··· 4 4 5 5 function mkSpace(overrides: Partial<SpaceRow> = {}): SpaceRow { 6 6 return { 7 - uri: "at://did:plc:alice/tools.atmo.event.space/s1", 7 + uri: "ats://did:plc:alice/tools.atmo.event.space/s1", 8 8 ownerDid: "did:plc:alice", 9 9 type: "tools.atmo.event.space", 10 10 key: "s1",
+1 -1
packages/contrail/tests/spaces-e2e.test.ts
··· 96 96 expect(res.status).toBe(200); 97 97 const { space } = await asJson(res); 98 98 spaceUri = space.uri; 99 - expect(spaceUri).toBe(`at://${ALICE}/tools.atmo.event.space/birthday-2026`); 99 + expect(spaceUri).toBe(`ats://${ALICE}/tools.atmo.event.space/birthday-2026`); 100 100 }); 101 101 102 102 it("owner can write a location record", async () => {
+5 -10
packages/lexicons/src/generate.ts
··· 220 220 ? { 221 221 space: { 222 222 type: "string", 223 - format: "at-uri", 224 - description: "Present when the record was read from a permissioned space; its value is the space URI.", 223 + description: "Present when the record was read from a permissioned space; its value is the `ats://` space URI.", 225 224 }, 226 225 } 227 226 : {}), ··· 279 278 ? { 280 279 space: { 281 280 type: "string", 282 - format: "at-uri", 283 - description: "Present when the record was read from a permissioned space.", 281 + description: "Present when the record was read from a permissioned space; `ats://` URI.", 284 282 }, 285 283 } 286 284 : {}), ··· 320 318 ? { 321 319 space: { 322 320 type: "string", 323 - format: "at-uri", 324 - description: "Present when the record was read from a permissioned space.", 321 + description: "Present when the record was read from a permissioned space; `ats://` URI.", 325 322 }, 326 323 } 327 324 : {}), ··· 591 588 ? { 592 589 spaceUri: { 593 590 type: "string", 594 - format: "at-uri", 595 - description: "If set, query records inside this permissioned space (requires service-auth JWT or a read-grant invite token).", 591 + description: "If set, query records inside this permissioned space (requires service-auth JWT or a read-grant invite token). `ats://` URI.", 596 592 }, 597 593 byUser: { 598 594 type: "string", ··· 725 721 ? { 726 722 spaceUri: { 727 723 type: "string", 728 - format: "at-uri", 729 - description: "If set, fetch from this permissioned space (requires service-auth JWT or a read-grant invite token).", 724 + description: "If set, fetch from this permissioned space (requires service-auth JWT or a read-grant invite token). `ats://` URI.", 730 725 }, 731 726 inviteToken: { 732 727 type: "string",
+1 -1
packages/sync/src/index.ts
··· 22 22 time_us?: number; 23 23 indexed_at?: number; 24 24 /** Set when the record originates from a per-space table. */ 25 - _space?: string; 25 + space?: string; 26 26 /** Present on optimistic entries added via `addOptimistic` — not set by 27 27 * records arriving from the stream. Auto-dropped when a real record 28 28 * with the same rkey arrives via `record.created`. */
+1 -1
packages/contrail/src/core/types.ts
··· 292 292 /** Set when the row originates from a per-space table. Used by the 293 293 * pipeline/hydration/response layers to route child queries to the same 294 294 * space and tag the output. */ 295 - _space?: string; 295 + space?: string; 296 296 } 297 297 298 298 export interface IngestEvent {
+3 -3
packages/lexicons/lexicon-templates/community/defs.json
··· 22 22 "type": "object", 23 23 "required": ["uri", "ownerDid", "type", "key", "serviceDid", "createdAt"], 24 24 "properties": { 25 - "uri": { "type": "string", "format": "at-uri" }, 25 + "uri": { "type": "string" }, 26 26 "ownerDid": { "type": "string", "format": "did" }, 27 27 "type": { "type": "string", "format": "nsid" }, 28 28 "key": { "type": "string" }, ··· 35 35 "description": "Exactly one of `did` or `spaceUri` must be set.", 36 36 "properties": { 37 37 "did": { "type": "string", "format": "did" }, 38 - "spaceUri": { "type": "string", "format": "at-uri" } 38 + "spaceUri": { "type": "string" } 39 39 } 40 40 }, 41 41 "memberRow": { ··· 61 61 "required": ["tokenHash", "spaceUri", "accessLevel", "createdBy", "createdAt", "usedCount"], 62 62 "properties": { 63 63 "tokenHash": { "type": "string", "description": "SHA-256 of the raw token. Stable id for list/revoke; never grants access on its own." }, 64 - "spaceUri": { "type": "string", "format": "at-uri" }, 64 + "spaceUri": { "type": "string" }, 65 65 "accessLevel": { "type": "ref", "ref": "#accessLevel" }, 66 66 "createdBy": { "type": "string", "format": "did" }, 67 67 "createdAt": { "type": "integer" },
+1 -1
packages/lexicons/lexicon-templates/invite/create.json
··· 11 11 "type": "object", 12 12 "required": ["spaceUri"], 13 13 "properties": { 14 - "spaceUri": { "type": "string", "format": "at-uri" }, 14 + "spaceUri": { "type": "string" }, 15 15 "kind": { "type": "string", "knownValues": ["join", "read", "read-join"], "description": "For user-owned spaces. join: redeem to become a member. read: bearer-only read access, no membership. read-join: anonymous read + signed-in redeem to join." }, 16 16 "accessLevel": { "type": "string", "knownValues": ["member", "manager", "admin", "owner"], "description": "For community-owned spaces. The access level granted on redemption — the creator's own level caps what they can grant." }, 17 17 "expiresAt": { "type": "integer", "description": "Unix ms timestamp. Omit for no expiry." },
+1 -1
packages/lexicons/lexicon-templates/invite/defs.json
··· 8 8 "required": ["tokenHash", "spaceUri", "createdBy", "createdAt", "usedCount"], 9 9 "properties": { 10 10 "tokenHash": { "type": "string", "description": "Stable identifier for list/revoke operations." }, 11 - "spaceUri": { "type": "string", "format": "at-uri" }, 11 + "spaceUri": { "type": "string" }, 12 12 "kind": { "type": "string", "knownValues": ["join", "read", "read-join"], "description": "Set for user-owned spaces. Absent for community-owned." }, 13 13 "accessLevel": { "type": "string", "knownValues": ["member", "manager", "admin", "owner"], "description": "Set for community-owned spaces. Absent for user-owned." }, 14 14 "createdBy": { "type": "string", "format": "did" },
+1 -1
packages/lexicons/lexicon-templates/invite/list.json
··· 9 9 "type": "params", 10 10 "required": ["spaceUri"], 11 11 "properties": { 12 - "spaceUri": { "type": "string", "format": "at-uri" }, 12 + "spaceUri": { "type": "string" }, 13 13 "includeRevoked": { "type": "boolean", "default": false } 14 14 } 15 15 },
+1 -1
packages/lexicons/lexicon-templates/invite/redeem.json
··· 21 21 "type": "object", 22 22 "required": ["spaceUri"], 23 23 "properties": { 24 - "spaceUri": { "type": "string", "format": "at-uri" }, 24 + "spaceUri": { "type": "string" }, 25 25 "kind": { "type": "string", "description": "Set for user-owned spaces — echoes the invite kind consumed." }, 26 26 "accessLevel": { "type": "string", "description": "Set for community-owned spaces — the level granted." }, 27 27 "communityDid": { "type": "string", "format": "did", "description": "Set for community-owned spaces." }
+1 -1
packages/lexicons/lexicon-templates/invite/revoke.json
··· 11 11 "type": "object", 12 12 "required": ["tokenHash"], 13 13 "properties": { 14 - "spaceUri": { "type": "string", "format": "at-uri", "description": "Optional — ownership is inferred from the invite row; required for user-owned spaces for a sanity check." }, 14 + "spaceUri": { "type": "string", "description": "Optional — ownership is inferred from the invite row; required for user-owned spaces for a sanity check." }, 15 15 "tokenHash": { "type": "string" } 16 16 } 17 17 }
+1 -1
packages/lexicons/lexicon-templates/spaces/addMember.json
··· 11 11 "type": "object", 12 12 "required": ["spaceUri", "did"], 13 13 "properties": { 14 - "spaceUri": { "type": "string", "format": "at-uri" }, 14 + "spaceUri": { "type": "string" }, 15 15 "did": { "type": "string", "format": "did" } 16 16 } 17 17 }
+3 -3
packages/lexicons/lexicon-templates/spaces/defs.json
··· 7 7 "type": "object", 8 8 "required": ["uri", "ownerDid", "type", "key", "serviceDid", "createdAt"], 9 9 "properties": { 10 - "uri": { "type": "string", "format": "at-uri" }, 10 + "uri": { "type": "string" }, 11 11 "ownerDid": { "type": "string", "format": "did" }, 12 12 "type": { "type": "string", "format": "nsid" }, 13 13 "key": { "type": "string" }, ··· 30 30 "type": "object", 31 31 "required": ["spaceUri", "collection", "authorDid", "rkey", "record", "createdAt"], 32 32 "properties": { 33 - "spaceUri": { "type": "string", "format": "at-uri" }, 33 + "spaceUri": { "type": "string" }, 34 34 "collection": { "type": "string", "format": "nsid" }, 35 35 "authorDid": { "type": "string", "format": "did" }, 36 36 "rkey": { "type": "string" }, ··· 63 63 "required": ["tokenHash", "spaceUri", "kind", "usedCount", "createdBy", "createdAt"], 64 64 "properties": { 65 65 "tokenHash": { "type": "string" }, 66 - "spaceUri": { "type": "string", "format": "at-uri" }, 66 + "spaceUri": { "type": "string" }, 67 67 "kind": { "type": "string", "knownValues": ["join", "read", "read-join"] }, 68 68 "expiresAt": { "type": "integer" }, 69 69 "maxUses": { "type": "integer" },
+1 -1
packages/lexicons/lexicon-templates/spaces/deleteRecord.json
··· 11 11 "type": "object", 12 12 "required": ["spaceUri", "collection", "rkey"], 13 13 "properties": { 14 - "spaceUri": { "type": "string", "format": "at-uri" }, 14 + "spaceUri": { "type": "string" }, 15 15 "collection": { "type": "string", "format": "nsid" }, 16 16 "rkey": { "type": "string" } 17 17 }
+1 -1
packages/lexicons/lexicon-templates/spaces/getBlob.json
··· 9 9 "type": "params", 10 10 "required": ["spaceUri", "cid"], 11 11 "properties": { 12 - "spaceUri": { "type": "string", "format": "at-uri" }, 12 + "spaceUri": { "type": "string" }, 13 13 "cid": { "type": "string", "format": "cid" }, 14 14 "inviteToken": { 15 15 "type": "string",
+1 -1
packages/lexicons/lexicon-templates/spaces/getRecord.json
··· 9 9 "type": "params", 10 10 "required": ["spaceUri", "collection", "author", "rkey"], 11 11 "properties": { 12 - "spaceUri": { "type": "string", "format": "at-uri" }, 12 + "spaceUri": { "type": "string" }, 13 13 "collection": { "type": "string", "format": "nsid" }, 14 14 "author": { "type": "string", "format": "did" }, 15 15 "rkey": { "type": "string" },
+1 -1
packages/lexicons/lexicon-templates/spaces/getSpace.json
··· 9 9 "type": "params", 10 10 "required": ["uri"], 11 11 "properties": { 12 - "uri": { "type": "string", "format": "at-uri" }, 12 + "uri": { "type": "string" }, 13 13 "inviteToken": { "type": "string", "description": "Read-grant invite token. When supplied, replaces JWT auth for this read." } 14 14 } 15 15 },
+1 -1
packages/lexicons/lexicon-templates/spaces/leaveSpace.json
··· 11 11 "type": "object", 12 12 "required": ["spaceUri"], 13 13 "properties": { 14 - "spaceUri": { "type": "string", "format": "at-uri" } 14 + "spaceUri": { "type": "string" } 15 15 } 16 16 } 17 17 },
+1 -1
packages/lexicons/lexicon-templates/spaces/listBlobs.json
··· 9 9 "type": "params", 10 10 "required": ["spaceUri"], 11 11 "properties": { 12 - "spaceUri": { "type": "string", "format": "at-uri" }, 12 + "spaceUri": { "type": "string" }, 13 13 "byUser": { "type": "string", "format": "did", "description": "Only blobs uploaded by this DID." }, 14 14 "limit": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 }, 15 15 "cursor": { "type": "string" }
+1 -1
packages/lexicons/lexicon-templates/spaces/listMembers.json
··· 9 9 "type": "params", 10 10 "required": ["spaceUri"], 11 11 "properties": { 12 - "spaceUri": { "type": "string", "format": "at-uri" } 12 + "spaceUri": { "type": "string" } 13 13 } 14 14 }, 15 15 "output": {
+1 -1
packages/lexicons/lexicon-templates/spaces/listRecords.json
··· 9 9 "type": "params", 10 10 "required": ["spaceUri", "collection"], 11 11 "properties": { 12 - "spaceUri": { "type": "string", "format": "at-uri" }, 12 + "spaceUri": { "type": "string" }, 13 13 "collection": { "type": "string", "format": "nsid" }, 14 14 "byUser": { "type": "string", "format": "did", "description": "Only return records authored by this DID." }, 15 15 "cursor": { "type": "string" },
+1 -1
packages/lexicons/lexicon-templates/spaces/putRecord.json
··· 11 11 "type": "object", 12 12 "required": ["spaceUri", "collection", "record"], 13 13 "properties": { 14 - "spaceUri": { "type": "string", "format": "at-uri" }, 14 + "spaceUri": { "type": "string" }, 15 15 "collection": { "type": "string", "format": "nsid" }, 16 16 "rkey": { "type": "string" }, 17 17 "record": { "type": "unknown" }
+1 -1
packages/lexicons/lexicon-templates/spaces/removeMember.json
··· 11 11 "type": "object", 12 12 "required": ["spaceUri", "did"], 13 13 "properties": { 14 - "spaceUri": { "type": "string", "format": "at-uri" }, 14 + "spaceUri": { "type": "string" }, 15 15 "did": { "type": "string", "format": "did" } 16 16 } 17 17 }
+1 -1
packages/lexicons/lexicon-templates/spaces/uploadBlob.json
··· 9 9 "type": "params", 10 10 "required": ["spaceUri"], 11 11 "properties": { 12 - "spaceUri": { "type": "string", "format": "at-uri" } 12 + "spaceUri": { "type": "string" } 13 13 } 14 14 }, 15 15 "input": { "encoding": "*/*" },
+3 -3
apps/group-chat/src/lib/rooms/uri.ts
··· 2 2 * 3 3 * The URIs are returned as `ResourceUri` (branded) so they flow into 4 4 * lexicon-typed XRPC params without per-call casts. We apply the brand 5 - * once here — structurally these are just `at://…` strings. */ 5 + * once here — structurally these are just `ats://…` strings. */ 6 6 7 7 import type { ResourceUri } from '@atcute/lexicons'; 8 8 9 9 const SPACE_TYPE = 'tools.atmo.chat.space'; 10 10 11 11 export function buildSpaceUri(communityDid: string, key: string): ResourceUri { 12 - return `at://${communityDid}/${SPACE_TYPE}/${key}` as ResourceUri; 12 + return `ats://${communityDid}/${SPACE_TYPE}/${key}` as ResourceUri; 13 13 } 14 14 15 15 export function buildMembersUri(communityDid: string): ResourceUri { ··· 20 20 return buildSpaceUri(communityDid, '$admin'); 21 21 } 22 22 23 - const SPACE_URI_RE = new RegExp(`^at://([^/]+)/${SPACE_TYPE.replace(/\./g, '\\.')}/([^/]+)$`); 23 + const SPACE_URI_RE = new RegExp(`^ats://([^/]+)/${SPACE_TYPE.replace(/\./g, '\\.')}/([^/]+)$`); 24 24 25 25 export function parseSpaceUri(uri: string): { communityDid: string; key: string } | null { 26 26 const m = SPACE_URI_RE.exec(uri);
+1 -1
apps/group-chat/src/lib/rooms/watch.svelte.ts
··· 71 71 time_us?: number; 72 72 indexed_at?: number; 73 73 cid?: string | null; 74 - _space?: string; 74 + space?: string; 75 75 /** Present on optimistic entries added via `addOptimistic`. */ 76 76 optimistic?: 'pending' | 'failed'; 77 77 /** Error attached via `markFailed`. */
+1 -1
packages/contrail/src/core/db/records.ts
··· 793 793 record: row.record, 794 794 time_us: row.time_us, 795 795 indexed_at: row.indexed_at, 796 - ...(spaceUri ? { _space: spaceUri } : {}), 796 + ...(spaceUri ? { space: spaceUri } : {}), 797 797 }; 798 798 if (countCols.length > 0) { 799 799 const counts: Record<string, number> = {};
+4 -4
packages/contrail/src/core/realtime/query-filter.ts
··· 24 24 record: Record<string, unknown>; 25 25 time_us: number; 26 26 indexed_at: number; 27 - _space: string; 27 + space: string; 28 28 }; 29 29 }; 30 30 } ··· 41 41 collection: string; 42 42 cid: string | null | undefined; 43 43 record: Record<string, unknown>; 44 - _space: string; 44 + space: string; 45 45 }; 46 46 }; 47 47 } ··· 89 89 record: event.payload.record, 90 90 time_us: event.ts * 1000, 91 91 indexed_at: event.ts, 92 - _space: spec.spaceUri 92 + space: spec.spaceUri 93 93 } 94 94 } 95 95 } ··· 130 130 collection: event.payload.collection, 131 131 cid: event.payload.cid, 132 132 record: event.payload.record, 133 - _space: spec.spaceUri 133 + space: spec.spaceUri 134 134 } 135 135 } 136 136 }
+3 -11
packages/contrail/src/core/router/collection.ts
··· 141 141 collection: event.payload.collection, 142 142 cid: event.payload.cid, 143 143 value: event.payload.record, 144 - _space: event.payload.space 144 + space: event.payload.space 145 145 } 146 146 }); 147 147 } else { ··· 188 188 value: event.payload.record, 189 189 time_us: nowUs, 190 190 indexed_at: event.ts, 191 - _space: event.payload.space 191 + space: event.payload.space 192 192 } 193 193 }); 194 194 } else { ··· 257 257 } 258 258 } 259 259 } 260 - // Normalize `space` → `_space` so snapshot records carry the same 261 - // field name as live `record.created` payloads. Clients then have a 262 - // single key to read regardless of origin. 263 - const normalized = record as Record<string, unknown>; 264 - if (normalized.space !== undefined && normalized._space === undefined) { 265 - normalized._space = normalized.space; 266 - delete normalized.space; 267 - } 268 - send("snapshot.record", { record: normalized }); 260 + send("snapshot.record", { record }); 269 261 } 270 262 send("snapshot.end", { cursor: result.cursor }); 271 263 snapshotDone = true;
+1 -1
packages/contrail/src/core/router/helpers.ts
··· 28 28 collection: row.collection, 29 29 rkey: row.rkey, 30 30 time_us: row.time_us, 31 - ...(row._space ? { space: row._space } : {}), 31 + ...(row.space ? { space: row.space } : {}), 32 32 }; 33 33 } 34 34
+4 -4
packages/contrail/src/core/router/hydrate.ts
··· 10 10 import { batchedInQuery, formatRecord } from "./helpers"; 11 11 12 12 /** Group rows by their origin: public (undefined key) or a specific spaceUri. */ 13 - function groupBySource<T extends { _space?: string }>(rows: T[]): Map<string | undefined, T[]> { 13 + function groupBySource<T extends { space?: string }>(rows: T[]): Map<string | undefined, T[]> { 14 14 const groups = new Map<string | undefined, T[]>(); 15 15 for (const r of rows) { 16 - const key = r._space; 16 + const key = r.space; 17 17 const g = groups.get(key); 18 18 if (g) g.push(r); 19 19 else groups.set(key, [r]); ··· 130 130 formatRecord({ 131 131 ...(row as any), 132 132 collection: childNsid, 133 - ...(sourceSpace ? { _space: sourceSpace } : {}), 133 + ...(sourceSpace ? { space: sourceSpace } : {}), 134 134 } as RecordRow) 135 135 ); 136 136 } ··· 212 212 result[parentUri][refName] = formatRecord({ 213 213 ...(row as any), 214 214 collection: refNsid, 215 - ...(sourceSpace ? { _space: sourceSpace } : {}), 215 + ...(sourceSpace ? { space: sourceSpace } : {}), 216 216 } as RecordRow); 217 217 } 218 218 }
+7 -7
packages/contrail/src/core/spaces/uri.ts
··· 1 1 /** Centralized space URI construction / parsing. 2 2 * 3 - * Permissioned spaces are addressed by (ownerDid, type, key). The rough spec 4 - * (https://dholms.leaflet.pub/3mhj6bcqats2o) floats `ats://` as a likely 5 - * distinct scheme for permissioned data, but that's unresolved — we keep 6 - * `at://` today and isolate the format here so swapping is a one-liner. 3 + * Permissioned spaces are addressed by (ownerDid, type, key) and use the 4 + * `ats://` scheme — distinct from atproto record URIs (`at://`) so the two 5 + * can't be confused at any layer (logs, params, dispatch). Tracks the rough 6 + * spec at https://dholms.leaflet.pub/3mhj6bcqats2o. 7 7 * 8 8 * Record URIs inside a space are minted by authorDid for index purposes 9 9 * (`at://<authorDid>/<collection>/<rkey>`); the spec is explicitly undecided ··· 18 18 19 19 /** Build a space URI from its three addressing components. */ 20 20 export function buildSpaceUri(parts: SpaceUriParts): string { 21 - return `at://${parts.ownerDid}/${parts.type}/${parts.key}`; 21 + return `ats://${parts.ownerDid}/${parts.type}/${parts.key}`; 22 22 } 23 23 24 24 /** Parse a space URI into its components, or null if malformed. */ 25 25 export function parseSpaceUri(uri: string): SpaceUriParts | null { 26 - if (!uri.startsWith("at://")) return null; 27 - const rest = uri.slice("at://".length); 26 + if (!uri.startsWith("ats://")) return null; 27 + const rest = uri.slice("ats://".length); 28 28 const [ownerDid, type, key, ...extra] = rest.split("/"); 29 29 if (!ownerDid || !type || !key || extra.length > 0) return null; 30 30 return { ownerDid, type, key };
+1 -1
packages/lexicons/lexicon-templates/community/space/delete.json
··· 11 11 "type": "object", 12 12 "required": ["spaceUri"], 13 13 "properties": { 14 - "spaceUri": { "type": "string", "format": "at-uri" } 14 + "spaceUri": { "type": "string" } 15 15 } 16 16 } 17 17 },
+1 -1
packages/lexicons/lexicon-templates/community/space/deleteRecord.json
··· 11 11 "type": "object", 12 12 "required": ["spaceUri", "collection", "rkey"], 13 13 "properties": { 14 - "spaceUri": { "type": "string", "format": "at-uri" }, 14 + "spaceUri": { "type": "string" }, 15 15 "collection": { "type": "string", "format": "nsid" }, 16 16 "rkey": { "type": "string" } 17 17 }
+1 -1
packages/lexicons/lexicon-templates/community/space/grant.json
··· 11 11 "type": "object", 12 12 "required": ["spaceUri", "subject", "accessLevel"], 13 13 "properties": { 14 - "spaceUri": { "type": "string", "format": "at-uri" }, 14 + "spaceUri": { "type": "string" }, 15 15 "subject": { "type": "ref", "ref": "tools.atmo.community.defs#subject" }, 16 16 "accessLevel": { "type": "ref", "ref": "tools.atmo.community.defs#accessLevel" } 17 17 }
+1 -1
packages/lexicons/lexicon-templates/community/space/listMembers.json
··· 9 9 "type": "params", 10 10 "required": ["spaceUri"], 11 11 "properties": { 12 - "spaceUri": { "type": "string", "format": "at-uri" }, 12 + "spaceUri": { "type": "string" }, 13 13 "flatten": { "type": "boolean", "description": "If true, return the flat DID list instead of raw rows." } 14 14 } 15 15 },
+1 -1
packages/lexicons/lexicon-templates/community/space/putRecord.json
··· 11 11 "type": "object", 12 12 "required": ["spaceUri", "collection", "record"], 13 13 "properties": { 14 - "spaceUri": { "type": "string", "format": "at-uri" }, 14 + "spaceUri": { "type": "string" }, 15 15 "collection": { "type": "string", "format": "nsid" }, 16 16 "rkey": { "type": "string" }, 17 17 "record": { "type": "unknown" }
+1 -1
packages/lexicons/lexicon-templates/community/space/resync.json
··· 11 11 "type": "object", 12 12 "required": ["spaceUri"], 13 13 "properties": { 14 - "spaceUri": { "type": "string", "format": "at-uri" } 14 + "spaceUri": { "type": "string" } 15 15 } 16 16 } 17 17 },
+1 -1
packages/lexicons/lexicon-templates/community/space/revoke.json
··· 11 11 "type": "object", 12 12 "required": ["spaceUri", "subject"], 13 13 "properties": { 14 - "spaceUri": { "type": "string", "format": "at-uri" }, 14 + "spaceUri": { "type": "string" }, 15 15 "subject": { "type": "ref", "ref": "tools.atmo.community.defs#subject" } 16 16 } 17 17 }
+1 -1
packages/lexicons/lexicon-templates/community/space/setAccessLevel.json
··· 11 11 "type": "object", 12 12 "required": ["spaceUri", "subject", "accessLevel"], 13 13 "properties": { 14 - "spaceUri": { "type": "string", "format": "at-uri" }, 14 + "spaceUri": { "type": "string" }, 15 15 "subject": { "type": "ref", "ref": "tools.atmo.community.defs#subject" }, 16 16 "accessLevel": { "type": "ref", "ref": "tools.atmo.community.defs#accessLevel" } 17 17 }
+1 -1
packages/lexicons/lexicon-templates/spaces/ext/whoami.json
··· 9 9 "type": "params", 10 10 "required": ["spaceUri"], 11 11 "properties": { 12 - "spaceUri": { "type": "string", "format": "at-uri" } 12 + "spaceUri": { "type": "string" } 13 13 } 14 14 }, 15 15 "output": {
+3 -3
apps/group-chat/src/routes/c/[communityDid]/+layout.svelte
··· 38 38 r: (typeof channelsQuery.records)[number] 39 39 ): ChannelMeta | null { 40 40 const rec = r.value; 41 - if (!r._space || rec.communityDid !== data.communityDid || !rec.name) return null; 41 + if (!r.space || rec.communityDid !== data.communityDid || !rec.name) return null; 42 42 if (!rec.createdAt) return null; 43 43 let visibility: 'public' | 'private'; 44 44 if (rec.visibility === 'public') visibility = 'public'; 45 45 else if (rec.visibility === 'private') visibility = 'private'; 46 46 else return null; 47 - const parsed = parseSpaceUri(r._space); 47 + const parsed = parseSpaceUri(r.space); 48 48 if (!parsed || parsed.key.startsWith('$') || parsed.key === 'members') return null; 49 49 return { 50 - spaceUri: r._space, 50 + spaceUri: r.space, 51 51 key: parsed.key, 52 52 name: rec.name, 53 53 topic: rec.topic,
-2
apps/group-chat/lexicons/generated/tools/atmo/chat/channel/getRecord.json
··· 22 22 }, 23 23 "spaceUri": { 24 24 "type": "string", 25 - "format": "at-uri", 26 25 "description": "If set, fetch from this permissioned space (requires service-auth JWT or a read-grant invite token)." 27 26 }, 28 27 "inviteToken": { ··· 72 71 }, 73 72 "space": { 74 73 "type": "string", 75 - "format": "at-uri", 76 74 "description": "Present when the record was read from a permissioned space; its value is the space URI." 77 75 }, 78 76 "profiles": {
+1 -3
apps/group-chat/lexicons/generated/tools/atmo/chat/channel/listRecords.json
··· 28 28 }, 29 29 "spaceUri": { 30 30 "type": "string", 31 - "format": "at-uri", 32 31 "description": "If set, query records inside this permissioned space (requires service-auth JWT or a read-grant invite token)." 33 32 }, 34 33 "byUser": { 35 34 "type": "string", 36 35 "format": "did", 37 - "description": "Only used with spaceUri — filter to records authored by this DID." 36 + "description": "Only used with spaceUri \u2014 filter to records authored by this DID." 38 37 }, 39 38 "inviteToken": { 40 39 "type": "string", ··· 135 134 }, 136 135 "space": { 137 136 "type": "string", 138 - "format": "at-uri", 139 137 "description": "Present when the record was read from a permissioned space; its value is the space URI." 140 138 } 141 139 }
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/channel/watchRecords.json
··· 28 28 }, 29 29 "spaceUri": { 30 30 "type": "string", 31 - "format": "at-uri", 32 31 "description": "If set, query records inside this permissioned space (requires service-auth JWT or a read-grant invite token)." 33 32 }, 34 33 "byUser": { 35 34 "type": "string", 36 35 "format": "did", 37 - "description": "Only used with spaceUri — filter to records authored by this DID." 36 + "description": "Only used with spaceUri \u2014 filter to records authored by this DID." 38 37 }, 39 38 "inviteToken": { 40 39 "type": "string",
+3 -6
apps/group-chat/lexicons/generated/tools/atmo/chat/community/defs.json
··· 53 53 ], 54 54 "properties": { 55 55 "uri": { 56 - "type": "string", 57 - "format": "at-uri" 56 + "type": "string" 58 57 }, 59 58 "ownerDid": { 60 59 "type": "string", ··· 84 83 "format": "did" 85 84 }, 86 85 "spaceUri": { 87 - "type": "string", 88 - "format": "at-uri" 86 + "type": "string" 89 87 } 90 88 } 91 89 }, ··· 147 145 "description": "SHA-256 of the raw token. Stable id for list/revoke; never grants access on its own." 148 146 }, 149 147 "spaceUri": { 150 - "type": "string", 151 - "format": "at-uri" 148 + "type": "string" 152 149 }, 153 150 "accessLevel": { 154 151 "type": "ref",
+1 -1
apps/group-chat/lexicons/generated/tools/atmo/chat/community/mint.json
··· 36 36 }, 37 37 "recoveryKey": { 38 38 "type": "unknown", 39 - "description": "Creator's rotation key as a private JWK. Shown once — cannot be retrieved later." 39 + "description": "Creator's rotation key as a private JWK. Shown once \u2014 cannot be retrieved later." 40 40 } 41 41 } 42 42 }
+3 -4
apps/group-chat/lexicons/generated/tools/atmo/chat/invite/create.json
··· 14 14 ], 15 15 "properties": { 16 16 "spaceUri": { 17 - "type": "string", 18 - "format": "at-uri" 17 + "type": "string" 19 18 }, 20 19 "kind": { 21 20 "type": "string", ··· 34 33 "admin", 35 34 "owner" 36 35 ], 37 - "description": "For community-owned spaces. The access level granted on redemption — the creator's own level caps what they can grant." 36 + "description": "For community-owned spaces. The access level granted on redemption \u2014 the creator's own level caps what they can grant." 38 37 }, 39 38 "expiresAt": { 40 39 "type": "integer", ··· 62 61 "properties": { 63 62 "token": { 64 63 "type": "string", 65 - "description": "Raw token. Shown once — cannot be retrieved later." 64 + "description": "Raw token. Shown once \u2014 cannot be retrieved later." 66 65 }, 67 66 "invite": { 68 67 "type": "ref",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/invite/defs.json
··· 18 18 "description": "Stable identifier for list/revoke operations." 19 19 }, 20 20 "spaceUri": { 21 - "type": "string", 22 - "format": "at-uri" 21 + "type": "string" 23 22 }, 24 23 "kind": { 25 24 "type": "string",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/invite/list.json
··· 12 12 ], 13 13 "properties": { 14 14 "spaceUri": { 15 - "type": "string", 16 - "format": "at-uri" 15 + "type": "string" 17 16 }, 18 17 "includeRevoked": { 19 18 "type": "boolean",
+3 -4
apps/group-chat/lexicons/generated/tools/atmo/chat/invite/redeem.json
··· 28 28 ], 29 29 "properties": { 30 30 "spaceUri": { 31 - "type": "string", 32 - "format": "at-uri" 31 + "type": "string" 33 32 }, 34 33 "kind": { 35 34 "type": "string", 36 - "description": "Set for user-owned spaces — echoes the invite kind consumed." 35 + "description": "Set for user-owned spaces \u2014 echoes the invite kind consumed." 37 36 }, 38 37 "accessLevel": { 39 38 "type": "string", 40 - "description": "Set for community-owned spaces — the level granted." 39 + "description": "Set for community-owned spaces \u2014 the level granted." 41 40 }, 42 41 "communityDid": { 43 42 "type": "string",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/invite/revoke.json
··· 15 15 "properties": { 16 16 "spaceUri": { 17 17 "type": "string", 18 - "format": "at-uri", 19 - "description": "Optional — ownership is inferred from the invite row; required for user-owned spaces for a sanity check." 18 + "description": "Optional \u2014 ownership is inferred from the invite row; required for user-owned spaces for a sanity check." 20 19 }, 21 20 "tokenHash": { 22 21 "type": "string"
-2
apps/group-chat/lexicons/generated/tools/atmo/chat/message/getRecord.json
··· 22 22 }, 23 23 "spaceUri": { 24 24 "type": "string", 25 - "format": "at-uri", 26 25 "description": "If set, fetch from this permissioned space (requires service-auth JWT or a read-grant invite token)." 27 26 }, 28 27 "inviteToken": { ··· 72 71 }, 73 72 "space": { 74 73 "type": "string", 75 - "format": "at-uri", 76 74 "description": "Present when the record was read from a permissioned space; its value is the space URI." 77 75 }, 78 76 "profiles": {
+1 -3
apps/group-chat/lexicons/generated/tools/atmo/chat/message/listRecords.json
··· 28 28 }, 29 29 "spaceUri": { 30 30 "type": "string", 31 - "format": "at-uri", 32 31 "description": "If set, query records inside this permissioned space (requires service-auth JWT or a read-grant invite token)." 33 32 }, 34 33 "byUser": { 35 34 "type": "string", 36 35 "format": "did", 37 - "description": "Only used with spaceUri — filter to records authored by this DID." 36 + "description": "Only used with spaceUri \u2014 filter to records authored by this DID." 38 37 }, 39 38 "inviteToken": { 40 39 "type": "string", ··· 134 133 }, 135 134 "space": { 136 135 "type": "string", 137 - "format": "at-uri", 138 136 "description": "Present when the record was read from a permissioned space; its value is the space URI." 139 137 } 140 138 }
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/message/watchRecords.json
··· 28 28 }, 29 29 "spaceUri": { 30 30 "type": "string", 31 - "format": "at-uri", 32 31 "description": "If set, query records inside this permissioned space (requires service-auth JWT or a read-grant invite token)." 33 32 }, 34 33 "byUser": { 35 34 "type": "string", 36 35 "format": "did", 37 - "description": "Only used with spaceUri — filter to records authored by this DID." 36 + "description": "Only used with spaceUri \u2014 filter to records authored by this DID." 38 37 }, 39 38 "inviteToken": { 40 39 "type": "string",
+1 -1
apps/group-chat/lexicons/generated/tools/atmo/chat/realtime/ticket.json
··· 4 4 "defs": { 5 5 "main": { 6 6 "type": "procedure", 7 - "description": "Mint a short-lived subscription ticket for the given topic. The caller's access is checked and — for `community:<did>` topics — expanded to the concrete list of `space:<uri>` topics the caller can see. Server-side callers may skip tickets and use their JWT directly on `.subscribe`.", 7 + "description": "Mint a short-lived subscription ticket for the given topic. The caller's access is checked and \u2014 for `community:<did>` topics \u2014 expanded to the concrete list of `space:<uri>` topics the caller can see. Server-side callers may skip tickets and use their JWT directly on `.subscribe`.", 8 8 "input": { 9 9 "encoding": "application/json", 10 10 "schema": {
-2
apps/group-chat/lexicons/generated/tools/atmo/chat/server/getRecord.json
··· 22 22 }, 23 23 "spaceUri": { 24 24 "type": "string", 25 - "format": "at-uri", 26 25 "description": "If set, fetch from this permissioned space (requires service-auth JWT or a read-grant invite token)." 27 26 }, 28 27 "inviteToken": { ··· 72 71 }, 73 72 "space": { 74 73 "type": "string", 75 - "format": "at-uri", 76 74 "description": "Present when the record was read from a permissioned space; its value is the space URI." 77 75 }, 78 76 "profiles": {
+1 -3
apps/group-chat/lexicons/generated/tools/atmo/chat/server/listRecords.json
··· 28 28 }, 29 29 "spaceUri": { 30 30 "type": "string", 31 - "format": "at-uri", 32 31 "description": "If set, query records inside this permissioned space (requires service-auth JWT or a read-grant invite token)." 33 32 }, 34 33 "byUser": { 35 34 "type": "string", 36 35 "format": "did", 37 - "description": "Only used with spaceUri — filter to records authored by this DID." 36 + "description": "Only used with spaceUri \u2014 filter to records authored by this DID." 38 37 }, 39 38 "inviteToken": { 40 39 "type": "string", ··· 130 129 }, 131 130 "space": { 132 131 "type": "string", 133 - "format": "at-uri", 134 132 "description": "Present when the record was read from a permissioned space; its value is the space URI." 135 133 } 136 134 }
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/server/watchRecords.json
··· 28 28 }, 29 29 "spaceUri": { 30 30 "type": "string", 31 - "format": "at-uri", 32 31 "description": "If set, query records inside this permissioned space (requires service-auth JWT or a read-grant invite token)." 33 32 }, 34 33 "byUser": { 35 34 "type": "string", 36 35 "format": "did", 37 - "description": "Only used with spaceUri — filter to records authored by this DID." 36 + "description": "Only used with spaceUri \u2014 filter to records authored by this DID." 38 37 }, 39 38 "inviteToken": { 40 39 "type": "string",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/space/addMember.json
··· 15 15 ], 16 16 "properties": { 17 17 "spaceUri": { 18 - "type": "string", 19 - "format": "at-uri" 18 + "type": "string" 20 19 }, 21 20 "did": { 22 21 "type": "string",
+3 -6
apps/group-chat/lexicons/generated/tools/atmo/chat/space/defs.json
··· 15 15 ], 16 16 "properties": { 17 17 "uri": { 18 - "type": "string", 19 - "format": "at-uri" 18 + "type": "string" 20 19 }, 21 20 "ownerDid": { 22 21 "type": "string", ··· 78 77 ], 79 78 "properties": { 80 79 "spaceUri": { 81 - "type": "string", 82 - "format": "at-uri" 80 + "type": "string" 83 81 }, 84 82 "collection": { 85 83 "type": "string", ··· 171 169 "type": "string" 172 170 }, 173 171 "spaceUri": { 174 - "type": "string", 175 - "format": "at-uri" 172 + "type": "string" 176 173 }, 177 174 "kind": { 178 175 "type": "string",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/space/deleteRecord.json
··· 16 16 ], 17 17 "properties": { 18 18 "spaceUri": { 19 - "type": "string", 20 - "format": "at-uri" 19 + "type": "string" 21 20 }, 22 21 "collection": { 23 22 "type": "string",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/space/getBlob.json
··· 13 13 ], 14 14 "properties": { 15 15 "spaceUri": { 16 - "type": "string", 17 - "format": "at-uri" 16 + "type": "string" 18 17 }, 19 18 "cid": { 20 19 "type": "string",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/space/getRecord.json
··· 15 15 ], 16 16 "properties": { 17 17 "spaceUri": { 18 - "type": "string", 19 - "format": "at-uri" 18 + "type": "string" 20 19 }, 21 20 "collection": { 22 21 "type": "string",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/space/getSpace.json
··· 12 12 ], 13 13 "properties": { 14 14 "uri": { 15 - "type": "string", 16 - "format": "at-uri" 15 + "type": "string" 17 16 }, 18 17 "inviteToken": { 19 18 "type": "string",
+2 -3
apps/group-chat/lexicons/generated/tools/atmo/chat/space/leaveSpace.json
··· 4 4 "defs": { 5 5 "main": { 6 6 "type": "procedure", 7 - "description": "Remove the caller from a space's member list. The owner cannot leave — they must delete the space instead.", 7 + "description": "Remove the caller from a space's member list. The owner cannot leave \u2014 they must delete the space instead.", 8 8 "input": { 9 9 "encoding": "application/json", 10 10 "schema": { ··· 14 14 ], 15 15 "properties": { 16 16 "spaceUri": { 17 - "type": "string", 18 - "format": "at-uri" 17 + "type": "string" 19 18 } 20 19 } 21 20 }
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/space/listBlobs.json
··· 12 12 ], 13 13 "properties": { 14 14 "spaceUri": { 15 - "type": "string", 16 - "format": "at-uri" 15 + "type": "string" 17 16 }, 18 17 "byUser": { 19 18 "type": "string",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/space/listMembers.json
··· 12 12 ], 13 13 "properties": { 14 14 "spaceUri": { 15 - "type": "string", 16 - "format": "at-uri" 15 + "type": "string" 17 16 } 18 17 } 19 18 },
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/space/listRecords.json
··· 13 13 ], 14 14 "properties": { 15 15 "spaceUri": { 16 - "type": "string", 17 - "format": "at-uri" 16 + "type": "string" 18 17 }, 19 18 "collection": { 20 19 "type": "string",
+1 -1
apps/group-chat/lexicons/generated/tools/atmo/chat/space/listSpaces.json
··· 4 4 "defs": { 5 5 "main": { 6 6 "type": "query", 7 - "description": "List spaces the caller has access to. Default scope is 'member' (spaces the caller is a member of, including owned); 'owner' lists only spaces the caller owns. When scope='member', the optional 'owner' param narrows to spaces owned by that DID — useful for listing all channels in a specific community the caller can access.", 7 + "description": "List spaces the caller has access to. Default scope is 'member' (spaces the caller is a member of, including owned); 'owner' lists only spaces the caller owns. When scope='member', the optional 'owner' param narrows to spaces owned by that DID \u2014 useful for listing all channels in a specific community the caller can access.", 8 8 "parameters": { 9 9 "type": "params", 10 10 "properties": {
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/space/putRecord.json
··· 16 16 ], 17 17 "properties": { 18 18 "spaceUri": { 19 - "type": "string", 20 - "format": "at-uri" 19 + "type": "string" 21 20 }, 22 21 "collection": { 23 22 "type": "string",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/space/removeMember.json
··· 15 15 ], 16 16 "properties": { 17 17 "spaceUri": { 18 - "type": "string", 19 - "format": "at-uri" 18 + "type": "string" 20 19 }, 21 20 "did": { 22 21 "type": "string",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/space/uploadBlob.json
··· 12 12 ], 13 13 "properties": { 14 14 "spaceUri": { 15 - "type": "string", 16 - "format": "at-uri" 15 + "type": "string" 17 16 } 18 17 } 19 18 },
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/spaceExt/whoami.json
··· 12 12 ], 13 13 "properties": { 14 14 "spaceUri": { 15 - "type": "string", 16 - "format": "at-uri" 15 + "type": "string" 17 16 } 18 17 } 19 18 },
+1 -1
apps/group-chat/lexicons/generated/tools/atmo/chat/community/space/create.json
··· 4 4 "defs": { 5 5 "main": { 6 6 "type": "procedure", 7 - "description": "Create a community-owned space (group/role/channel). Caller needs `admin` or higher in the community's `$admin` space. Reserved keys (`$admin`, `$publishers`, …) are rejected here.", 7 + "description": "Create a community-owned space (group/role/channel). Caller needs `admin` or higher in the community's `$admin` space. Reserved keys (`$admin`, `$publishers`, \u2026) are rejected here.", 8 8 "input": { 9 9 "encoding": "application/json", 10 10 "schema": {
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/community/space/delete.json
··· 14 14 ], 15 15 "properties": { 16 16 "spaceUri": { 17 - "type": "string", 18 - "format": "at-uri" 17 + "type": "string" 19 18 } 20 19 } 21 20 }
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/community/space/deleteRecord.json
··· 16 16 ], 17 17 "properties": { 18 18 "spaceUri": { 19 - "type": "string", 20 - "format": "at-uri" 19 + "type": "string" 21 20 }, 22 21 "collection": { 23 22 "type": "string",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/community/space/grant.json
··· 16 16 ], 17 17 "properties": { 18 18 "spaceUri": { 19 - "type": "string", 20 - "format": "at-uri" 19 + "type": "string" 21 20 }, 22 21 "subject": { 23 22 "type": "ref",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/community/space/listMembers.json
··· 12 12 ], 13 13 "properties": { 14 14 "spaceUri": { 15 - "type": "string", 16 - "format": "at-uri" 15 + "type": "string" 17 16 }, 18 17 "flatten": { 19 18 "type": "boolean",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/community/space/putRecord.json
··· 16 16 ], 17 17 "properties": { 18 18 "spaceUri": { 19 - "type": "string", 20 - "format": "at-uri" 19 + "type": "string" 21 20 }, 22 21 "collection": { 23 22 "type": "string",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/community/space/resync.json
··· 14 14 ], 15 15 "properties": { 16 16 "spaceUri": { 17 - "type": "string", 18 - "format": "at-uri" 17 + "type": "string" 19 18 } 20 19 } 21 20 }
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/community/space/revoke.json
··· 15 15 ], 16 16 "properties": { 17 17 "spaceUri": { 18 - "type": "string", 19 - "format": "at-uri" 18 + "type": "string" 20 19 }, 21 20 "subject": { 22 21 "type": "ref",
+1 -2
apps/group-chat/lexicons/generated/tools/atmo/chat/community/space/setAccessLevel.json
··· 16 16 ], 17 17 "properties": { 18 18 "spaceUri": { 19 - "type": "string", 20 - "format": "at-uri" 19 + "type": "string" 21 20 }, 22 21 "subject": { 23 22 "type": "ref",