1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# KCP: how socialweb-computer uses kcp
- kcp = the state store, API server and composition layer of EVERY application, not shared infra
- companion of [SYSTEM.md](SYSTEM.md); decisions + measurements in [ARCHITECTURE.md](ARCHITECTURE.md) sections 0, 6, 12, 13
- source of truth for types: `api/*/v1alpha1`; for schemas and exports: generated files under `deploy/`
## kcp Is Part Of Each App
- application instance = its own kcp + a manifest set + a profile
- application state = kcp objects (ComputeVM, ComputeContract, NodeClaim, OpenBaoServer, HostProcess, component kinds, Service, Exposure, ...)
- components run in process in the core; third-party binaries are HostProcess units; both reconciled from objects
- apps talk to each other ONLY over the atproto market protocol. a shared kcp is composition + isolation, never a data channel
- only exception: tenant Kubernetes clusters of the Gateway use kcp the traditional way (home workspaces, bound exports, tenant k3s behind kcp)
### Which kcp, by profile
| profile | kcp | workspaces |
|---|---|---|
| prod gateway (`install gateway`) | strict kcp per app, blue/green slots | app root in `root`, provider in `root:socialweb-provider`, requester side in `root:app`, cluster homes |
| prod ssh gateway (`run gateway ssh`, standalone) | its own strict kcp on the gateway host | app root in `root` |
| prod bidder (`install bidder`, per handle) | strict kcp per handle, one slot | app root in `root` |
| CLI requester (`run request` without `--network`) | fresh kcp per invocation, state dir persists | app root in `root` |
| market network (`dev up --profile market-network`, CI, soak) | ONE kcp for every app | network in `root`, shared services `root:global` `root:relay` `root:alice` `root:bob`, each app `root:<app>`, its components `root:<app>:app`, child cores in `root:dev:<instance>-<app>` |
| browser page (kcp fork, js/wasm) | in-tab kcp, memory storage | requester root |
| iOS app (`pkg/embedded`) | in-process kcp + vault | requester root; snapshot bytes hold vault secrets in clear: store encrypted, never log |
- one manifest set serves both shapes: in the network the applier rebases every phase workspace under `root:<app>` (`kcpapply.Options.Base`); `root:*-provider` and sets of scope `kcp` stay shared
- service names in manifests: `<name>.default.{{.ServiceSuffix}}`
- a child app in a network has NO kcp or kine of its own; its core is a HostProcess placed by the network's devsup, scoped to `root:<app>`
- children learn the network from values copied onto their root objects and `valueFrom` refs to other workspaces
## The kcp Runtime: Slots, Front, Kine, Postgres
| piece | gateway | bidder | market network / dev |
|---|---|---|---|
| kcp binary | unpatched kcp-dev/kcp main, pinned asset (`versions.json` `assets.kcp`) | same | same |
| slots | blue/green `socialweb-kcp-gw@a`, `@b` | one slot per handle `socialweb-kcp-<hid>@a` | one slot |
| store | kine over postgres unit `socialweb-kcp-gw-pg` (unix socket) | kine over sqlite | kine |
| front | own L4 front `socialweb-kcp-gw-front` on 127.0.0.1:6443 (caddy -> kcp.socialweb.computer) | port-0 front | front under devsup |
| upgrade | roll: new slot beside old on same store, front flips after `/readyz`, old exits, Lease `kube-system/kcp-controllers` deleted so new slot leads at once; pg_dump before every roll | short restart | `dev roll kcp` |
- slots run `--enable-leader-election` and `--shard-base-url` = the front
- kcp release pairs whose storage versions or bootstrap schemas differ: planned short outage instead of a roll
- caddy stays public TLS only, never reconfigured by a swap
- `KcpInstallation` records the selected slot and the front backend
- operator commands: `units roll kcp` (installed host), `dev roll kcp` (dev), `dev restart <instance> core`
- memory caps: kcp slot 3 GiB (`kcpserver.SlotMemoryMax`), front 256 MiB, kine `kcpdatastore.KineMemoryMax`
- packages: `internal/kcpserver` (slots, kubeconfigs, member tokens, roll), `internal/kcpdatastore` (kine), `internal/apiserverfront` (fronts), `internal/lease/local` (slots, ports, rolls)
- tenant clusters reuse the pattern: per-cluster k3s slots a/b over kine over that cluster's postgres, behind a per-cluster front (`controlplane.json` records `activeSlot`)
## Workspaces, APIExports, APIBindings, WorkspaceTypes
- provider workspace `root:socialweb-provider` (type `universal`) holds every APIResourceSchema and APIExport
- apps bind the exports they need with APIBindings (`spec.reference.export.path: root:socialweb-provider`)
- WorkspaceTypes give new workspaces default bindings
### Exports
| export | resources (first ones) | bound by |
|---|---|---|
| `applications` | MarketNetwork, MarketBidder, MarketRequester, Gateway, SshGateway, component kinds, repos, leases, StateSync | `root` (`applications-binding.yaml`), `socialweb-application` type |
| `clusters` | Cluster, Node, NodeClaim, ClusterLink, ClusterClientCertificate | `cluster` type, `socialweb-application` type |
| `atprotoidentities` | AtprotoIdentity | `account` type, `socialweb-application` type |
| `marketidentities` | MarketIdentity, AtprotoRecord, AtprotoRecordWatch | gateway `root:app` (`deploy/apps/gateway`), application type |
| `computevms` | ComputeVM, FirecrackerVM, Bid, GuestIdentityGrant | `bidder` type, `root` (`computevms-binding.yaml`) |
| `computecontracts` | ComputeContract, ContractEvent, Infra, EphemeralInfra, InfraCollection, EphemeralInfraCollection | requester and gateway bindings |
| `hostprocesses` | HostProcess, Postgres, OpenBaoServer, RemoteOpenBao, Vault | `root` (`hostprocesses-binding.yaml`), `bidder` type |
| `events` | EventStream, EventSource, Pds/Relay/JetstreamEventSource | `root` (`events-binding.yaml`) |
| `exposures` | Service, Exposure, ComponentLink | `root` (`exposures-binding.yaml`) |
| `pds` | RemotePDS | `root` (`pds-binding.yaml`) |
| `policy` | PolicyEngine, PolicyWorkflow, PolicyWorkflowRun, PolicyTrigger | `root` (`policy-binding.yaml`) |
### WorkspaceTypes
| type | default bindings | child type | used for |
|---|---|---|---|
| `account` | atprotoidentities | `cluster` | gateway account home (DID to shard map) |
| `cluster` | clusters | | tenant cluster home workspace |
| `socialweb-application` | all eleven exports | | an application workspace |
| `bidder` | computevms, hostprocesses | | bidder workspace |
| `dev-instances` / `dev-instance` | | `dev-instance` only | `root:dev` and `root:dev:<instance>` of a dev host |
- never use kcp's shipped home (`~`): a bare LogicalCluster that can never be deleted
```mermaid
flowchart TD
subgraph ROOT["workspace root"]
APPROOT["app root object: MarketBidder / Gateway / ..."]
B1["APIBinding applications"]
B2["APIBinding computevms"]
B3["APIBinding hostprocesses, events, exposures, pds, policy"]
WT["WorkspaceTypes: account, cluster, socialweb-application, bidder"]
end
subgraph PROV["workspace root:socialweb-provider"]
ARS["APIResourceSchema v1alpha1-REV.plural.group (immutable)"]
EXP["APIExports: applications, clusters, computevms, computecontracts, hostprocesses, marketidentities, atprotoidentities, events, exposures, pds, policy"]
end
subgraph ACC["account workspace (type account)"]
AID["AtprotoIdentity"]
subgraph CLW["cluster home (type cluster)"]
CL["Cluster, NodeClaim, ClusterLink"]
end
end
ARS --> EXP
EXP -->|"bound by"| B1
EXP -->|"bound by"| B2
EXP -->|"bound by"| B3
WT -->|"defaultAPIBindings"| ACC
WT -->|"defaultAPIBindings"| CLW
```
### Manifest sets
- `deploy/sets.yaml`: named sets of ordered phases; each phase = workspace + files + `after` waits (Workspace Ready, APIExport IdentityValid, APIBinding Ready)
- sets: `provider` (scope kcp: provider workspace, schemas, exports, root bindings, WorkspaceTypes), `bidder`, `hostprocesses`, `dev-host`, grants
- per-app sets: `deploy/apps/{gateway,market-bidder,market-requester,ssh-gateway,repo-pds}` (vault, RBAC, extra bindings, iroh dialer, association watch)
- market network: `deploy/market-network/` (workspaces, network vault, peers RBAC)
- applied by `socialweb-computer apply` / `internal/controllers/runtime/kcpapply`
- every app root re-renders its set's HostProcesses and OpenBaoServers each pass; a missing or Lost one voids the applied-set shortcut and the set is applied again
## The Component Runtime
- one core process per instance (`internal/stack/componentcore`) = controller manager + component runtime + HostProcess supervisor
- component kinds (Bidder, Requester, Pds, Plc, Relay, GuestControlPlane, IngressProxy, DispatcherTLS, GatewayAPI, SshServer, PolicyEngine) are objects; `internal/controllers/component` reconciles each into a running instance in `internal/component`
- a component never listens itself: it registers handlers; a Service names component + handler; an Exposure publishes the Service
- local Exposure port comes from the core's port registry (`<state>/ports/ports.json`, 64-port block of 20000-29999 by state dir), survives a core restart, reported in `status.address`
- HostProcess = a systemd user unit (or proc backend) for a third-party binary; followed via D-Bus signals; adopted across core restarts by unit name + InvocationID
- what a HostProcess runs: kcp slots, kine, postgres, OpenBao, fronts, dumbpipe/iroh dialer, firecracker via `socialweb-vmexec`, child cores (market network), deno for policy
- suspend: root `spec.suspend` stops participation, never a process; per component `spec.components[].suspend` stops that process
```mermaid
flowchart TD
ROOT["App root: MarketBidder"] -->|"composes, SSA socialweb.computer/marketbidder"| MI["MarketIdentity bidder"]
ROOT --> BAO["OpenBaoServer"]
ROOT --> VAULT["Vault (abstract) backendRef -> OpenBaoServer"]
ROOT --> PDS["Pds component"]
ROOT --> BIDC["Bidder component"]
ROOT --> GCP["GuestControlPlane component"]
ROOT --> PE["PolicyEngine"]
ROOT --> ING["IngressProxy"]
ROOT --> ES["EventSource + EventStream"]
ROOT --> LINKS["ComponentLinks"]
BIDC -->|"creates"| BID["Bid"]
BID -->|"owns"| CVM["ComputeVM (abstract)"]
CVM -->|"backendRef"| FVM["FirecrackerVM"]
FVM -->|"runs as"| HP1["HostProcess: vmexec + firecracker"]
BAO -->|"runs as"| HP2["HostProcess: openbao"]
PE -->|"runs"| HP3["deno sandbox"]
BIDC --> SVC["Service"]
SVC --> EXPO["Exposure: status.address"]
```
## Controllers Pattern
- one reconciler per kind: `internal/controllers/<kind>` on `internal/controllers/runtime` (the only home of the dynamic client and unstructured)
- kcp multicluster: every core runs the manager for its own workspaces; a root reconciles only roots of its core's base workspace
- no polling. driven by watches on own kind + every kind it refs
- deadlines: delayed requeue
- errors: rate-limited backoff
- ticker only for an outbound obligation listed in `deploy/obligations.txt` (owner + period)
- systemd units via D-Bus signals, processes via wait
- writers rule (every write = server-side apply, field manager `socialweb.computer/<controller>`):
- a spec is written only by its composer
- a status only by the controller of its kind
- a field another controller reports = its own status subfield + own field manager (e.g. ComputeVM `status.market.guest` by guestcontrolplane)
- every child/intent object has an ownerReference to its composer or creator
- no annotation carries a control signal (release = `spec.release`, restart = `spec.restartedAt`)
- a child edited by another manager is re-applied with force and a `ChildEditReverted` Warning Event
- cross-workspace children cannot carry ownerReferences: they carry root labels (`apps.socialweb.computer/root`, `root-kind`, `root-cluster`), pruned by label, torn down before the root's finalizer goes
- abstract kinds (AtprotoRepo, Vault, ComputeVM, EventSource): `spec.backendRef` to one concrete kind with label `contracts.socialweb.computer/<abstract>`; abstract status mirrors backend; conditions `Ready` + `BackendBound`
- gate: `scripts/check-architecture.sh` (`hack/archcheck`) enforces layers, obligations, bus callers, no GVR literals outside `api/`, no annotation signals, `apigen:"userEndpoint"` on spec endpoints
### Bus and ComponentLinks
- `bus.Resolve[T](ctx, b, from, to, capability)` -> typed contract handle
- caller = the reconciled object or the component instance, never a controller; System only for `deploy/bus-bootstrap-callers.txt`
- the callee holds credentials; a handle never exposes a token
- a call with no Accepted ComponentLink fails `ErrLinkDenied`
- ComponentLink lives in the target's workspace: `spec.from` (ref or selector), `spec.to`, `spec.capabilities` (name + scope), `spec.suspend`
- capabilities: `xrpc.call`, `stream.dial`, `policy.run`, `vault.*`, `repo.*`, `pds.*`, `pds.serviceauth`, `events.*`
- roots generate links from component refs in their desired specs (`internal/app/compose/links.go`) and wait for Accepted before calling
- bound signals: `NotifyBound` (e.g. vault provider) wakes waiters without polling; binding fingerprint includes readiness
```mermaid
sequenceDiagram
autonumber
participant K as kcp
participant RT as controller runtime
participant C as controller (kind X)
participant BUS as bus
participant L as ComponentLink
participant T as target component
K-->>RT: watch event (X or a ref'd kind)
RT->>C: Reconcile(X)
C->>K: read X + refs
C->>BUS: Resolve[T](from=X, to=target, cap)
BUS->>L: accepted link admits cap?
alt no accepted link
BUS-->>C: ErrLinkDenied
else admitted
BUS-->>C: typed handle
C->>T: contract call (callee holds creds)
T-->>C: result
end
C->>K: SSA status (own field manager)
C-->>RT: done / requeue after deadline / backoff on error
```
### StateSync and remote access
- StateSync = durable outbox: source, sink, direction, payload, idempotencyKey, conflict + deletion policy (`Retain`, `DeleteRemote`, `AckThenDelete`), ack, retry
- observe is the authority; Acked only when the observation matches the desired hash; status never holds secrets or payload bytes
- examples: bidder `vm.terminated` before teardown (finalizer waits for Acked); market inbox syncs pruned at 10 min
- RemoteOpenBao: vault access separate from a server's lifecycle; probes health/seal, authenticates, checks mounts; never writes the remote vault
## API Resource Catalog
- every kind is cluster-scoped (kcp workspaces are the isolation; apigen writes `scope: Cluster`)
- schema name: `v1alpha1-<rev>.<plural>.<group>`; rev = value in `resources.go` when this doc was written (source of truth: the file)
- "composer" = who writes spec; status is always the kind's own controller unless noted
- "operator-applied" = has a controller but no Go composer: applied from manifests or kubectl
### App roots (group `socialweb.computer`, export `applications`)
| kind | rev | purpose | composer |
|---|---|---|---|
| MarketNetwork | 31 | a whole local market in one kcp: shared services + child roots | `dev up` |
| MarketBidder | 43 | bidder app: profile, identity, comms, ingress, policy, guestNetwork, firecracker, kcp | `install bidder` / `run bidder` / network |
| MarketRequester | 37 | requester app: identity, comms, secretsFallback, atircParentRef, ingress | `run request` / page / iOS / network |
| Gateway | 38 | hosted k8s: webAppFront, clusterProvider, operator, marketNetwork | `install gateway` / network |
| SshGateway | 35 | ssh as yourself: ssh options, owner sessions, per-user requesters | `run gateway ssh` / network |
| ChildApplication | 2 | placement of a child app in a network (networkRef, app, profile) | operator-applied (no Go composer) |
### Components (`socialweb.computer`, export `applications`)
| kind | rev | purpose | composer |
|---|---|---|---|
| Bidder | 24 | market bidder participant | MarketBidder |
| Requester | 29 | contract runner (RFP, accept, secrets, atirc, release) | MarketRequester, Gateway, SshGateway |
| GuestControlPlane | 11 | guest endpoints at 192.0.2.1 (events, downloads, identity proxy) | MarketBidder |
| Pds | 12 | local PDS (localRepo identities, network PDSes) | roots |
| Plc | 11 | local PLC directory | MarketNetwork |
| Relay | 11 | local atproto relay | MarketNetwork |
| IngressProxy | 12 | did:key / iroh / opentunnel ingress to the app's XRPC | roots |
| DispatcherTLS | 11 | TLS front for the network dispatcher | MarketNetwork |
| GatewayAPI | 16 | k8s.socialweb.computer web app + tenant API | Gateway |
| SshServer | 17 | ssh server, web sign-in, keys API | SshGateway |
| MarketPresence | 2 | bidder offering presence with TTL | Bidder; adopted by its root |
### Market and contracts
| kind | group / export | rev | purpose | composer |
|---|---|---|---|---|
| ComputeContract | socialweb.computer / computecontracts | 31 | one rental: spec (vm, tunnels, secretsRef, agent, association, policy), status.contract flow state | CLI, page, Infra, ssh session, NodeClaim |
| ContractEvent | / computecontracts | 5 | one guest/requester event (sealed signals, secrets), owned by its contract | CLI, page (outbound); Requester (inbound) |
| Infra | / computecontracts | 13 | durable set of contracts + nodes + secrets, mirrored to `computer.socialweb.infra` | owner, NodeClaim controller |
| EphemeralInfra | / computecontracts | 13 | Infra with TTL / endWhen | owner |
| InfraCollection | / computecontracts | 1 | one Infra child per `computer.socialweb.infra` record in a repo | owner |
| EphemeralInfraCollection | / computecontracts | 1 | same for `ephemeralInfra` | owner |
| Bid | socialweb.computer / computevms | 9 | one bid per RFP (name = bid rkey); owns its ComputeVM; status by bidder | Bidder |
| ComputeVM | / computevms | 17 | abstract VM: size, owner, requester, userData ref, contract refs, release | Bidder |
| FirecrackerVM | / computevms | 7 | concrete firecracker backend (unit, network, image) | ComputeVM controller |
| GuestIdentityGrant | / computevms | 3 | guest workload identity grant (entity, api, scopes, claims) | Bidder, GuestControlPlane |
| StateSync | socialweb.computer / applications | 1 | durable outbox (vm.terminated, market inbox) | roles, controllers |
### Identity, repos, records
| kind | group / export | rev | purpose | composer |
|---|---|---|---|---|
| MarketIdentity | socialweb.computer / marketidentities | 15 | app identity: mode (localRepo, pdsAccount, oauthQr, oauthBrowser, oauthDelegated, oauthSessionProxy), associations, pendingAction | roots, SshGateway session owners |
| AtprotoRecord | / marketidentities | 7 | one record to keep in a repo (collection, rkey, swapCid, deletionPolicy) | roots, Infra |
| AtprotoRecordWatch | / marketidentities | 7 | watch rkeys of a collection (associations, vouches) | app sets (`repo-pds`) |
| AtprotoIdentity | socialweb.computer / atprotoidentities | 2 | DID to identity mapping in an account home | gateway |
| AtprotoRepo | / applications | 4 | abstract repo: did, head, rev, writable, address | roots |
| LocalRepo | / applications | 2 | repo on a Pds component (signer handle, no token) | roots |
| RemoteRepo | / applications | 2 | repo on a RemotePDS via a MarketIdentity lease | roots |
| AtprotoSession | / applications | 2 | a session of an account on a PDS | roots (`compose/repo.go`) |
| PdsAccount | / applications | 2 | an account on a Pds component | roots |
| PlcIdentity | / applications | 1 | a DID on the local Plc | roots |
| RemotePDS | pds.socialweb.computer / pds | 3 | a remote PDS endpoint + auth + capabilities | roots |
### Vault and host
| kind | group / export | rev | purpose | composer |
|---|---|---|---|---|
| HostProcess | socialweb.computer / hostprocesses | 4 | a host unit for a third-party binary; status.listeners | roots, controllers |
| Postgres | / hostprocesses | 1 | embedded postgres (kcp store, cluster store, vault store) | gateway, cluster provider |
| OpenBaoServer | / hostprocesses | 11 | local OpenBao: mounts, policies, clients; unseal keys in a 0600 file, root token revoked | roots |
| RemoteOpenBao | / hostprocesses | 4 | access to an existing OpenBao (token / AppRole, https) | roots |
| Vault | / hostprocesses | 1 | abstract vault: address, ready, caBundle | roots |
| VaultCredential | / applications | 2 | a credential held in a vault for a repo account | roots (`compose/repo.go`) |
| VaultCertificate | / applications | 2 | a PKI certificate (vault, mount, role, commonName) | operator-applied |
| KcpInstallation | / applications | 1 | selected kcp slot + front backend | operator-applied |
| HostInstall | / applications | 1 | installed account, version, profile | operator-applied |
| HostRelease | / applications | 1 | installed release version + prune | operator-applied |
| PortLease | / applications | 2 | a leased port (cluster, purpose, port) | MarketNetwork |
| SubnetLease | / applications | 2 | a leased subnet (cluster, purpose, prefix) | operator-applied |
### Tenant clusters (export `clusters`)
| kind | rev | purpose | composer |
|---|---|---|---|
| Cluster | 5 | mount object: status.URL, phase, Draining, nodeExpiry | cluster provider (from XRPC) |
| Node | 2 | one node; the bound on leaks | node provider |
| NodeClaim | 16 | one market node for a cluster; status.contract mirrors the ComputeContract | cluster provider (spec), NodeClaim controller (status) |
| ClusterLink | 2 | iroh listener in front of a cluster apiserver or vault | cluster provider |
| ClusterClientCertificate | 2 | client cert record per DID (serial, notAfter, revoked) | gateway |
| ClusterRoll | 2 | control-plane roll of a cluster (strategy, maxUnavailable) | operator-applied |
### Exposure (group `exposure.socialweb.computer`, export `exposures`)
| kind | rev | purpose | composer |
|---|---|---|---|
| Service | 2 | names a component + handler (+ protocol, auth) | roots |
| Exposure | 5 | publishes a Service: local port, hostname/path, ingress; status.address | roots |
| ComponentLink | 3 | admits bus calls from -> to with capabilities | roots (`compose/links.go`) |
### Events (group `events.socialweb.computer`, export `events`)
| kind | rev | purpose | composer |
|---|---|---|---|
| EventSource | 1 | abstract source: cursor, lag, address | roots |
| PdsEventSource | 1 | subscribeRepos of an AtprotoRepo | roots |
| RelayEventSource | 1 | a relay subscribeRepos (Relay ref or URL) | roots |
| JetstreamEventSource | 1 | a Jetstream (v1 or v2) | roots |
| EventStream | 7 | filter (collections, dids) + cursor + sink over one EventSource; `spec.resume` Live / Recent | roots, requester composer |
| RelayStatus | 2 (`socialweb.computer`) | status of a Relay | roots (`compose/repo.go`) |
### Policy (group `policy.socialweb.computer`, export `policy`)
| kind | rev | purpose | composer |
|---|---|---|---|
| PolicyEngine | 3 | the engine component: limits, trust, cache, sandbox, bundled actions | roots |
| PolicyWorkflow | 2 | a workflow (inline or record ref) + defaults | roots |
| PolicyWorkflowRun | 3 | one decision run: perspective, self/subject/counterparty DIDs, rfp/bid/accept, cache | roles |
| PolicyTrigger | 2 | run a workflow on matching source events | roots |
## Schema Revisions
- kcp holds `APIResourceSchema.spec` IMMUTABLE: an update under the same name is Forbidden
- so each schema is named `v1alpha1-<Revision>.<plural>.<group>`; Revision per kind in `api/*/v1alpha1/resources.go`
- `deploy/apiresourceschema-revisions.txt` = revision lock: every name ever generated + sha256 of its spec, append-only, `merge=union`
- change a Go type without bumping Revision -> generation fails -> `deploy/check.sh` fails before any host sees it
- migration = the manifest set applies the new schema, then the re-pointed export; already-bound APIBindings move to the new schema in seconds, identity hash unchanged, stored objects untouched. no binding deleted
- old schemas stay in kcp, harmless
### Burned revisions
- a revision applied to a prod kcp is taken forever, even if the branch that made it is reverted
- reverted branch -> its lock line is gone -> the generator may hand out the same number with a different spec -> prod kcp refuses the apply -> core crash loop
- known burned on prod:
- Requester `v1alpha1-28` (lock now records 28 and 29 with the same spec)
- MarketRequester `v1alpha1-38` (NOT in the lock: the next MarketRequester bump must be 39)
- rule: before a bump, check the burned list; after a revert of a shipped schema, keep its lock line
- fix for a crash loop: roll back the binary, bump to a fresh revision, roll forward
## apigen And Codegen
```mermaid
flowchart LR
T["api/GROUP/v1alpha1 Go types + resources.go"] --> G["go generate ./api/..."]
G --> CG["hack/codegen.sh: zz_generated.deepcopy.go"]
G --> AG["cmd/socialweb-apigen -root . (internal/apigen)"]
AG --> S["deploy/KIND-apiresourceschema.yaml"]
AG --> E["deploy/EXPORT-apiexport.yaml"]
AG --> L["deploy/apiresourceschema-revisions.txt"]
AG --> R["zz_generated.register.go, zz_generated.conditions.go"]
S --> SET["deploy/sets.yaml phases"]
E --> SET
SET --> KCP["kcp: root:socialweb-provider"]
L --> CHK["deploy/check.sh: regen + diff"]
```
- Go types are the ONLY source of every schema; nothing else writes the outputs
- mapping (no kubebuilder markers: the no-comments rule):
- struct layout from `encoding/json` tags; `required` = no `omitempty` unless `apigen:"optional"`
- enums from source: typed string constants in declaration order; a named string type with no constants = error
- struct tags: `apigen:"enum=a|b"`, `default=`, `minimum=`, `userEndpoint` (required on endpoint fields in spec)
- `metav1.Condition` items open; unknown fields are an error in `apigen.Model.Validate`
- abstract contracts: `APIResource.Contracts` adds the label `contracts.socialweb.computer/<abstract>` to the concrete schema
- printer columns from `APIResource.PrinterColumns`
- checks: `deploy/check.sh --fast` regenerates into a scratch dir and diffs every file `socialweb-apigen -root . -list` names; `go generate ./...` must leave `api`, `internal/atproto/market`, `deploy` unchanged
- rebase conflicts: `scripts/regen-after-rebase.sh`; never hand-merge generated output; resolve generator inputs by hand first
- lexicons: `internal/lexgen` generates market record Go from `lexicons/`
## See Also
- [SYSTEM.md](SYSTEM.md): the whole system
- [ARCHITECTURE.md](ARCHITECTURE.md): section 0 (kcp per app), 6 (CRDs, generation, revisions), 12 (layout, bus, roots), 13 (invariants)
- [../deploy/market-network/README.md](../deploy/market-network/README.md): the one-kcp network
- `deploy/sets.yaml`, `deploy/workspacetype-*.yaml`, `deploy/*-apiexport.yaml`