dev.atfs.server
Schema Diff
+14 -23
Compatibility Analysis
Breaking Changes Detected
10 breaking changes, 2 non-breaking changes.
Breaking Changes (10)
- RequiredEdgeAdded RequiredEdgeAdded { vertex_id: "dev.atfs.server:body", src: "dev.atfs.server:body", tgt: "dev.atfs.server:body.accounts", kind: "prop", name: Some("accounts") }
- RemovedVertex RemovedVertex { vertex_id: "dev.atfs.server:body.endpoints" }
- RemovedVertex RemovedVertex { vertex_id: "dev.atfs.server:body.endpoints:items" }
- RemovedVertex RemovedVertex { vertex_id: "dev.atfs.server:body.ipfs" }
- RemovedVertex RemovedVertex { vertex_id: "dev.atfs.server:body.ipfs.port" }
- RemovedEdge RemovedEdge { src: "dev.atfs.server:body", tgt: "dev.atfs.server:body.endpoints", kind: "prop", name: Some("endpoints") }
- RemovedEdge RemovedEdge { src: "dev.atfs.server:body", tgt: "dev.atfs.server:body.ipfs", kind: "prop", name: Some("ipfs") }
- RemovedEdge RemovedEdge { src: "dev.atfs.server:body.endpoints", tgt: "dev.atfs.server:body.endpoints:items", kind: "items", name: None }
- RemovedEdge RemovedEdge { src: "dev.atfs.server:body.ipfs", tgt: "dev.atfs.server:body.ipfs.port", kind: "prop", name: Some("port") }
- ConstraintTightened ConstraintTightened { vertex_id: "dev.atfs.server:body.accounts:items", sort: "format", old_value: "at-identifier", new_value: "did" }
Non-Breaking Changes (2)
- AddedVertex AddedVertex { vertex_id: "dev.atfs.server:body.requestBodyCap" }
- AddedEdge AddedEdge { src: "dev.atfs.server:body", tgt: "dev.atfs.server:body.requestBodyCap", kind: "prop", name: Some("requestBodyCap") }
Migration Guidance
Removed Elements
RemovedVertex { vertex_id: "dev.atfs.server:body.endpoints" }RemovedVertex { vertex_id: "dev.atfs.server:body.endpoints:items" }RemovedVertex { vertex_id: "dev.atfs.server:body.ipfs" }RemovedVertex { vertex_id: "dev.atfs.server:body.ipfs.port" }
Added Elements
AddedVertex { vertex_id: "dev.atfs.server:body.requestBodyCap" }
Constraint Changes
- ConstraintTightened ConstraintTightened { vertex_id: "dev.atfs.server:body.accounts:items", sort: "format", old_value: "at-identifier", new_value: "did" }
Additional Notes
- Breaking: RequiredEdgeAdded { vertex_id: "dev.atfs.server:body", src: "dev.atfs.server:body", tgt: "dev.atfs.server:body.accounts", kind: "prop", name: Some("accounts") }
- Breaking: RemovedEdge { src: "dev.atfs.server:body", tgt: "dev.atfs.server:body.endpoints", kind: "prop", name: Some("endpoints") }
- Breaking: RemovedEdge { src: "dev.atfs.server:body", tgt: "dev.atfs.server:body.ipfs", kind: "prop", name: Some("ipfs") }
- Breaking: RemovedEdge { src: "dev.atfs.server:body.endpoints", tgt: "dev.atfs.server:body.endpoints:items", kind: "items", name: None }
- Breaking: RemovedEdge { src: "dev.atfs.server:body.ipfs", tgt: "dev.atfs.server:body.ipfs.port", kind: "prop", name: Some("port") }
- Non-breaking: AddedEdge { src: "dev.atfs.server:body", tgt: "dev.atfs.server:body.requestBodyCap", kind: "prop", name: Some("requestBodyCap") }
1
1
{
2
2
"id": "dev.atfs.server",
3
3
"defs": {
4
4
"main": {
5
5
"key": "any",
6
6
"type": "record",
7
7
"record": {
8
8
"type": "object",
9
+
"required": [
10
+
"accounts"
11
+
],
9
12
"properties": {
10
-
"ipfs": {
11
-
"type": "object",
12
-
"properties": {
13
-
"port": {
14
-
"type": "integer",
15
-
"description": "The libp2p listen port (tcp+quic). Zero, or an absent ipfs object, means the default (4001)."
16
-
}
17
-
},
18
-
"description": "This instance's boxo/libp2p participation config."
19
-
},
20
13
"follows": {
21
14
"type": "array",
22
15
"items": {
23
16
"type": "string",
24
17
"format": "at-uri"
25
18
},
26
-
"description": "Other atfs instances this one mirrors, each named by the at-uri of that instance's own dev.atfs.server record (at://{their-owner}/dev.atfs.server/{their-peer-id}). Identity rather than a URL, so a followed instance can move endpoints without breaking the follow: the follower resolves this record to learn where to poll (its endpoints) and whose name to hold the mirrored claims under (its serviceDid, or an identity derived from its peer ID when it declares none). The follower periodically walks the followed instance's dev.atfs.repo.listFiles, pins whatever is new, and releases whatever has been absent from two consecutive listings. Following is unilateral and needs no consent: listFiles is public and every pinned cid is already a DHT provider record, so an opt-in would be unenforceable. Only directly-claimed content is exported by listFiles, so following an instance never transitively mirrors what *it* follows — follow each origin you want. Removing an entry releases every claim that instance's mirror held here, and the content is deleted once nothing else claims it."
19
+
"description": "Other atfs instances this one mirrors, each named by the at-uri of that instance's own dev.atfs.server record (at://{their-owner}/dev.atfs.server/{their-peer-id}). Identity rather than a URL, so a followed instance can move without breaking the follow: the follower resolves this record to learn where to poll (its bare-domain did:web serviceDid, derived the same way as above) and whose name to hold the mirrored claims under (that same serviceDid, or an identity derived from its peer ID when it declares none). The follower periodically walks the followed instance's dev.atfs.repo.listFiles, pins whatever is new, and releases whatever has been absent from two consecutive listings. Following is unilateral and needs no consent: listFiles is public and every pinned cid is already a DHT provider record, so an opt-in would be unenforceable. Only directly-claimed content is exported by listFiles, so following an instance never transitively mirrors what *it* follows — follow each origin you want. Removing an entry releases every claim that instance's mirror held here, and the content is deleted once nothing else claims it."
27
20
},
28
21
"accounts": {
29
22
"type": "array",
30
23
"items": {
31
24
"type": "string",
32
-
"format": "at-identifier"
33
-
},
34
-
"description": "atproto accounts (DIDs or handles), beyond the owner, allowed to upload. The owner can always upload regardless of whether it's listed here."
35
-
},
36
-
"endpoints": {
37
-
"type": "array",
38
-
"items": {
39
-
"type": "string",
40
-
"format": "uri"
25
+
"format": "did"
41
26
},
42
-
"description": "Advisory HTTPS base URLs (not full URLs — each is expected to answer both /ipfs/<cid> and a dev.atfs.repo.getFile-style XRPC) this instance is reachable at, ordered by operator preference with the primary public base URL first. Lets an app discovering this record know where to POST without resolving the instance's DID first — the same purpose dev.atfs.file's providers field serves for individual files. The first entry also feeds the instance's did:web self-serve document's serviceEndpoint, when serviceDid is a did:web (served at /.well-known/did.json). Never required for correctness: it's a hint, not a substitute for DID-based discovery."
27
+
"minItems": 1,
28
+
"description": "The complete upload allowlist: atproto accounts permitted to call uploadFile/uploadBlob and pinFile. Required, and the owner is NOT implicitly included — an operator who wants to upload to their own instance must list themselves like any other account. DIDs only, never handles: an entry then survives a handle change, and a reverse index over accounts can answer 'which atfs instances can this person use' without resolving anything first."
43
29
},
44
30
"serviceDid": {
45
31
"type": "string",
46
32
"format": "did",
47
-
"description": "This instance's own DID: the `aud` uploaders must address in their inter-service auth JWTs. Uploads are disabled entirely until this is set."
33
+
"description": "This instance's own identity and address. It's the `aud` uploaders must address in their inter-service auth JWTs, and — only when it's a bare-domain did:web (exactly one segment after `did:web:`, so no path and no port suffix: `did:web:atfs.example.com`, never `did:web:atfs.example.com:user:alice` or an encoded-port form) — it also doubles as this instance's single HTTPS base URL, and atfs serves its own DID document at that domain's /.well-known/did.json. Uploads stay disabled until this is a bare-domain did:web and accounts names at least one account."
34
+
},
35
+
"requestBodyCap": {
36
+
"type": "integer",
37
+
"minimum": 1,
38
+
"description": "An operator-declared ceiling on the request bodies this instance's ingress will actually let through, in bytes. atfs cannot discover this on its own — it never sees its own ingress, only what a fronting proxy or tunnel lets past — so this is one of three sources describeServer combines with the lowest winning, alongside the ATFS_MAX_REQUEST_BODY env var and headers sniffed off arriving requests (e.g. Cloudflare's CF-Ray, read as at least its Free/Pro plan figure). Set this when those under- or over-report — for example a Cloudflare zone on a paid plan above the Free/Pro figure sniffing assumes."
48
39
}
49
40
}
50
41
}
51
42
}
52
43
},
53
44
"$type": "com.atproto.lexicon.schema",
54
45
"lexicon": 1,
55
-
"description": "An atfs instance's own configuration, read at boot — and re-read whenever it changes, the instance having subscribed to the owner's repo — by the instance whose libp2p peer ID is the record key (rkey) — at://{owner-did}/dev.atfs.server/{peer-id}. One owner repo can configure many instances this way. Changes to accounts, serviceDid and follows apply without a restart; ipfs.port is bound at startup and needs one. Unknown fields are tolerated and ignored, so a running instance is never broken by a schema field it predates. All fields are optional; an absent record is not an error — the instance runs with defaults, and uploads stay disabled until serviceDid is set."
46
+
"description": "An atfs instance's own configuration, read at boot — and re-read whenever it changes, the instance having subscribed to the owner's repo — by the instance whose libp2p peer ID is the record key (rkey) — at://{owner-did}/dev.atfs.server/{peer-id}. One owner repo can configure many instances this way. Changes to accounts, serviceDid and follows apply without a restart; the did:web document atfs serves for a bare-domain serviceDid is bound at startup and needs one to pick up a change. Unknown fields are tolerated and ignored, so a running instance is never broken by a schema field it predates. An absent record is not an error — the instance runs with defaults, and uploads stay disabled until accounts and serviceDid are both set."
56
47
}