diff --git a/.changeset/itchy-rice-kick.md b/.changeset/itchy-rice-kick.md deleted file mode 100644 index 84f0750..0000000 --- a/.changeset/itchy-rice-kick.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -"@atmo-dev/contrail-community": minor -"@atmo-dev/contrail": minor ---- - -A third community-creation mode: **provision**. alongside the existing `adopt` (caller already has a `did:plc`) and `mint` (caller wants a DID but brings their own PDS) modes, contrail can now provision a community on a stock `@atproto/pds` end-to-end — minting the `did:plc`, creating and activating the PDS account, generating an app password, and persisting credentials so the existing `community.putRecord` / `.deleteRecord` publish path keeps working. contrail never holds PDS admin credentials. - -**`xrpc/{ns}.community.provision`** runs the five-step PLC + PDS dance (key generation → PLC genesis → `createAccount` → `getRecommendedDidCredentials` + signed PLC update op → `activateAccount`), persists each step in a new `provision_attempts` table so a partially-failed attempt can be resumed, mints an app password, and seeds the session cache. - -**`contrail-community reap [--all-stuck] [--older-than ] [--db ] [--dry-run]`** new CLI (a bin shipped by `@atmo-dev/contrail-community`) that cleans up provision attempts which didn't reach `status='activated'` by tombstoning their PLC entries. `--dry-run` is the default; per-row confirmation is required for live reaping unless `--all-stuck` is given. `--all-stuck` only acts on rows idle at least `--older-than` minutes (default 30) so a bulk run can't tombstone an in-flight provision. Runs against the Cloudflare D1 binding by default, or against the decoupled Postgres index when `--db`/`DATABASE_URL` is set. It ships as a contrail-community bin because the PR #30 package split removed contrail's edge into community code: under pnpm's isolated `node_modules` the core `contrail` CLI can't resolve `@atmo-dev/contrail-community`, so `contrail reap` only registers in hoisted installs where both packages sit together. - -custody model: the caller supplies a `rotationKey` and that key sits at `rotationKeys[0]` — the highest-priority rotation slot on the resulting DID. contrail generates a subordinate keypair and persists it (AES-GCM-encrypted under `masterKey`) at `rotationKeys[1]`, so it can submit later PLC ops on the community's behalf — most importantly the post-activation PLC update during provision, and the tombstone op that `reap` issues to clean up stuck DIDs. - -the caller's key dominates: PLC's 72-hour nullification window means any op contrail signs with its subordinate key can be overridden within 72h by an op signed with the caller's key. with this caveat: a tombstone is irrevocable. a malicious or compromised contrail instance could tombstone any DID it provisioned. there is no managed code path, no shared rotation, and `rootCredentials` are returned to the caller in the response so they can also be persisted out-of-band. - -what you need to configure / know: - -- new `community` config block: `masterKey` (32-byte AES-GCM envelope key for the encrypted credential columns), `allowedProvisionPdsEndpoints` (URL-origin matching, collapses scheme case / default ports / trailing slash / IDN), optional `plcDirectory` override. - -- **provisioning fails closed.** When `allowProvisioning` is true, `allowedProvisionPdsEndpoints` MUST be non-empty — a missing/empty allowlist no longer means "accept any PDS" (that was a fail-open hole: any caller could have a PLC genesis op signed by Contrail's rotation key against an attacker-chosen PDS). To deliberately accept any endpoint, set the separate, loud `allowAnyProvisionPdsEndpoint: true`. The field was renamed from `allowedPdsEndpoints` to make clear it gates *provisioning* only, not which PDSes Contrail reads/indexes. - -- new tables `provision_attempts` and `community_credentials`. credentials are stored AES-GCM-encrypted under that key; lose the key, lose the ability to mint sessions for previously-provisioned communities. diff --git a/.changeset/private-network-overrides.md b/.changeset/private-network-overrides.md deleted file mode 100644 index 874794e..0000000 --- a/.changeset/private-network-overrides.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -"@atmo-dev/contrail-base": minor -"@atmo-dev/contrail-appview": minor -"@atmo-dev/contrail-community": minor ---- - -Private-network deployment support via a new optional `ContrailConfig.networkOverrides` block. - -`networkOverrides` carries three optional subfields, all defaulting to the current public-internet behavior (omit the block entirely and nothing changes): - -- **`resolver`** — a custom `DidDocumentResolver` used during DID-doc PDS fallback, labeler-endpoint resolution, and spaces service-auth JWT verification. Lets a deployment point at a private PLC mirror or inject a custom fetch (mTLS, retry, instrumentation). Trusted; not SSRF-checked. -- **`slingshotUrl`** — override the slingshot identity-resolver endpoint. Trusted; not SSRF-checked. -- **`additionalAllowedHosts`** — hostnames that bypass the default SSRF guard when validating a resolved PDS or labeler endpoint. Match is exact, case-insensitive, port-agnostic (e.g. `["pds.dev.svc.cluster.local"]`). This is the only knob that widens the validator; there is no "disable SSRF" flag. - -The overrides are threaded through PDS/identity resolution (`resolvePDS`, `getPDS`, `getClient`, `resolveIdentity*`, `refreshStaleIdentities`), labeler endpoint resolution and ingest (`resolveLabelerEndpoint`, `getLabelerState`, label subscribe cycles), and service-auth verification (`buildVerifier` in both the appview router and the community integration). The in-scope `config` is now also passed at every appview call site that resolves identities or PDS endpoints — the live-ingest refresh cycle (`runIngestCycle` → `refreshStaleIdentities`), the on-demand `refresh` path, and the router actor/identity/PDS resolution paths (`getProfile`, `getFeed`, collection queries, profile hydration, notify) — so private-network deploys honor the override on those paths instead of silently falling back to the public resolver and un-widened SSRF guard. - -The SSRF guard is now a single shared validator: `validateExternalUrl(url, additionalAllowedHosts?)` is exported from `contrail-base` and consumed by both the PDS client and labeler-endpoint resolution. `validateEndpointUrl` remains exported as a thin alias for backward compatibility. This removes the previous duplicate validator (`validatePdsUrl` + `validateEndpointUrl`) where an allowlist or SSRF-rule edit could be applied to only one copy. - -Also hardens schema initialization for concurrent/Postgres deployments: a dialect-aware `addColumnIfNotExists` (Postgres `ADD COLUMN IF NOT EXISTS`; SQLite pre-check), narrow absorption of the Postgres concurrent-`CREATE` race (42P07 / 23505 on pg_type/pg_class/pg_namespace indexes), and per-statement (rather than batched) DDL during `initSchema` / `initSpacesSchema` / spaces schema. Genuine DDL errors (syntax, type mismatch, missing column/table) still propagate. diff --git a/packages/contrail-appview/CHANGELOG.md b/packages/contrail-appview/CHANGELOG.md index cd18649..371296c 100644 --- a/packages/contrail-appview/CHANGELOG.md +++ b/packages/contrail-appview/CHANGELOG.md @@ -1,5 +1,30 @@ # @atmo-dev/contrail-appview +## 0.8.0 + +### Minor Changes + +- d7e0936: Private-network deployment support via a new optional `ContrailConfig.networkOverrides` block. + + `networkOverrides` carries three optional subfields, all defaulting to the current public-internet behavior (omit the block entirely and nothing changes): + + - **`resolver`** — a custom `DidDocumentResolver` used during DID-doc PDS fallback, labeler-endpoint resolution, and spaces service-auth JWT verification. Lets a deployment point at a private PLC mirror or inject a custom fetch (mTLS, retry, instrumentation). Trusted; not SSRF-checked. + - **`slingshotUrl`** — override the slingshot identity-resolver endpoint. Trusted; not SSRF-checked. + - **`additionalAllowedHosts`** — hostnames that bypass the default SSRF guard when validating a resolved PDS or labeler endpoint. Match is exact, case-insensitive, port-agnostic (e.g. `["pds.dev.svc.cluster.local"]`). This is the only knob that widens the validator; there is no "disable SSRF" flag. + + The overrides are threaded through PDS/identity resolution (`resolvePDS`, `getPDS`, `getClient`, `resolveIdentity*`, `refreshStaleIdentities`), labeler endpoint resolution and ingest (`resolveLabelerEndpoint`, `getLabelerState`, label subscribe cycles), and service-auth verification (`buildVerifier` in both the appview router and the community integration). The in-scope `config` is now also passed at every appview call site that resolves identities or PDS endpoints — the live-ingest refresh cycle (`runIngestCycle` → `refreshStaleIdentities`), the on-demand `refresh` path, and the router actor/identity/PDS resolution paths (`getProfile`, `getFeed`, collection queries, profile hydration, notify) — so private-network deploys honor the override on those paths instead of silently falling back to the public resolver and un-widened SSRF guard. + + The SSRF guard is now a single shared validator: `validateExternalUrl(url, additionalAllowedHosts?)` is exported from `contrail-base` and consumed by both the PDS client and labeler-endpoint resolution. `validateEndpointUrl` remains exported as a thin alias for backward compatibility. This removes the previous duplicate validator (`validatePdsUrl` + `validateEndpointUrl`) where an allowlist or SSRF-rule edit could be applied to only one copy. + + Also hardens schema initialization for concurrent/Postgres deployments: a dialect-aware `addColumnIfNotExists` (Postgres `ADD COLUMN IF NOT EXISTS`; SQLite pre-check), narrow absorption of the Postgres concurrent-`CREATE` race (42P07 / 23505 on pg_type/pg_class/pg_namespace indexes), and per-statement (rather than batched) DDL during `initSchema` / `initSpacesSchema` / spaces schema. Genuine DDL errors (syntax, type mismatch, missing column/table) still propagate. + +### Patch Changes + +- Updated dependencies [d7e0936] + - @atmo-dev/contrail-base@0.8.0 + - @atmo-dev/contrail-authority@0.8.0 + - @atmo-dev/contrail-record-host@0.8.0 + ## 0.7.0 ### Patch Changes diff --git a/packages/contrail-appview/package.json b/packages/contrail-appview/package.json index f2627dd..d1eedd2 100644 --- a/packages/contrail-appview/package.json +++ b/packages/contrail-appview/package.json @@ -1,6 +1,6 @@ { "name": "@atmo-dev/contrail-appview", - "version": "0.7.0", + "version": "0.8.0", "description": "Public-records appview for contrail — jetstream ingestion, backfill, query layer, feeds, labels, profiles, per-collection XRPC routes.", "type": "module", "sideEffects": false, diff --git a/packages/contrail-authority/CHANGELOG.md b/packages/contrail-authority/CHANGELOG.md index de82f64..bccf371 100644 --- a/packages/contrail-authority/CHANGELOG.md +++ b/packages/contrail-authority/CHANGELOG.md @@ -1,5 +1,12 @@ # @atmo-dev/contrail-authority +## 0.8.0 + +### Patch Changes + +- Updated dependencies [d7e0936] + - @atmo-dev/contrail-base@0.8.0 + ## 0.7.0 ### Patch Changes diff --git a/packages/contrail-authority/package.json b/packages/contrail-authority/package.json index a088146..3b2f6de 100644 --- a/packages/contrail-authority/package.json +++ b/packages/contrail-authority/package.json @@ -1,6 +1,6 @@ { "name": "@atmo-dev/contrail-authority", - "version": "0.7.0", + "version": "0.8.0", "description": "Default space-authority implementation for contrail — member list, invites, app policy, credential issuance. Contrail's binary-membership ACL flavor; for ladder-style access levels see @atmo-dev/contrail-community.", "type": "module", "sideEffects": false, diff --git a/packages/contrail-base/CHANGELOG.md b/packages/contrail-base/CHANGELOG.md index e6ed047..4c6e069 100644 --- a/packages/contrail-base/CHANGELOG.md +++ b/packages/contrail-base/CHANGELOG.md @@ -1,3 +1,21 @@ # @atmo-dev/contrail-base +## 0.8.0 + +### Minor Changes + +- d7e0936: Private-network deployment support via a new optional `ContrailConfig.networkOverrides` block. + + `networkOverrides` carries three optional subfields, all defaulting to the current public-internet behavior (omit the block entirely and nothing changes): + + - **`resolver`** — a custom `DidDocumentResolver` used during DID-doc PDS fallback, labeler-endpoint resolution, and spaces service-auth JWT verification. Lets a deployment point at a private PLC mirror or inject a custom fetch (mTLS, retry, instrumentation). Trusted; not SSRF-checked. + - **`slingshotUrl`** — override the slingshot identity-resolver endpoint. Trusted; not SSRF-checked. + - **`additionalAllowedHosts`** — hostnames that bypass the default SSRF guard when validating a resolved PDS or labeler endpoint. Match is exact, case-insensitive, port-agnostic (e.g. `["pds.dev.svc.cluster.local"]`). This is the only knob that widens the validator; there is no "disable SSRF" flag. + + The overrides are threaded through PDS/identity resolution (`resolvePDS`, `getPDS`, `getClient`, `resolveIdentity*`, `refreshStaleIdentities`), labeler endpoint resolution and ingest (`resolveLabelerEndpoint`, `getLabelerState`, label subscribe cycles), and service-auth verification (`buildVerifier` in both the appview router and the community integration). The in-scope `config` is now also passed at every appview call site that resolves identities or PDS endpoints — the live-ingest refresh cycle (`runIngestCycle` → `refreshStaleIdentities`), the on-demand `refresh` path, and the router actor/identity/PDS resolution paths (`getProfile`, `getFeed`, collection queries, profile hydration, notify) — so private-network deploys honor the override on those paths instead of silently falling back to the public resolver and un-widened SSRF guard. + + The SSRF guard is now a single shared validator: `validateExternalUrl(url, additionalAllowedHosts?)` is exported from `contrail-base` and consumed by both the PDS client and labeler-endpoint resolution. `validateEndpointUrl` remains exported as a thin alias for backward compatibility. This removes the previous duplicate validator (`validatePdsUrl` + `validateEndpointUrl`) where an allowlist or SSRF-rule edit could be applied to only one copy. + + Also hardens schema initialization for concurrent/Postgres deployments: a dialect-aware `addColumnIfNotExists` (Postgres `ADD COLUMN IF NOT EXISTS`; SQLite pre-check), narrow absorption of the Postgres concurrent-`CREATE` race (42P07 / 23505 on pg_type/pg_class/pg_namespace indexes), and per-statement (rather than batched) DDL during `initSchema` / `initSpacesSchema` / spaces schema. Genuine DDL errors (syntax, type mismatch, missing column/table) still propagate. + ## 0.7.0 diff --git a/packages/contrail-base/package.json b/packages/contrail-base/package.json index 5535411..a97edb4 100644 --- a/packages/contrail-base/package.json +++ b/packages/contrail-base/package.json @@ -1,6 +1,6 @@ { "name": "@atmo-dev/contrail-base", - "version": "0.7.0", + "version": "0.8.0", "description": "Shared infrastructure for the contrail family of packages — interfaces (SpaceAuthority, RecordHost, CommunityIntegration), credential primitives, binding resolvers, realtime infra, schema scaffolding. No routes, no tables of its own.", "type": "module", "sideEffects": false, diff --git a/packages/contrail-community/CHANGELOG.md b/packages/contrail-community/CHANGELOG.md index 163f0e5..ffe7196 100644 --- a/packages/contrail-community/CHANGELOG.md +++ b/packages/contrail-community/CHANGELOG.md @@ -1,5 +1,48 @@ # @atmo-dev/contrail-community +## 0.8.0 + +### Minor Changes + +- bea0dd2: A third community-creation mode: **provision**. alongside the existing `adopt` (caller already has a `did:plc`) and `mint` (caller wants a DID but brings their own PDS) modes, contrail can now provision a community on a stock `@atproto/pds` end-to-end — minting the `did:plc`, creating and activating the PDS account, generating an app password, and persisting credentials so the existing `community.putRecord` / `.deleteRecord` publish path keeps working. contrail never holds PDS admin credentials. + + **`xrpc/{ns}.community.provision`** runs the five-step PLC + PDS dance (key generation → PLC genesis → `createAccount` → `getRecommendedDidCredentials` + signed PLC update op → `activateAccount`), persists each step in a new `provision_attempts` table so a partially-failed attempt can be resumed, mints an app password, and seeds the session cache. + + **`contrail-community reap [--all-stuck] [--older-than ] [--db ] [--dry-run]`** new CLI (a bin shipped by `@atmo-dev/contrail-community`) that cleans up provision attempts which didn't reach `status='activated'` by tombstoning their PLC entries. `--dry-run` is the default; per-row confirmation is required for live reaping unless `--all-stuck` is given. `--all-stuck` only acts on rows idle at least `--older-than` minutes (default 30) so a bulk run can't tombstone an in-flight provision. Runs against the Cloudflare D1 binding by default, or against the decoupled Postgres index when `--db`/`DATABASE_URL` is set. It ships as a contrail-community bin because the PR #30 package split removed contrail's edge into community code: under pnpm's isolated `node_modules` the core `contrail` CLI can't resolve `@atmo-dev/contrail-community`, so `contrail reap` only registers in hoisted installs where both packages sit together. + + custody model: the caller supplies a `rotationKey` and that key sits at `rotationKeys[0]` — the highest-priority rotation slot on the resulting DID. contrail generates a subordinate keypair and persists it (AES-GCM-encrypted under `masterKey`) at `rotationKeys[1]`, so it can submit later PLC ops on the community's behalf — most importantly the post-activation PLC update during provision, and the tombstone op that `reap` issues to clean up stuck DIDs. + + the caller's key dominates: PLC's 72-hour nullification window means any op contrail signs with its subordinate key can be overridden within 72h by an op signed with the caller's key. with this caveat: a tombstone is irrevocable. a malicious or compromised contrail instance could tombstone any DID it provisioned. there is no managed code path, no shared rotation, and `rootCredentials` are returned to the caller in the response so they can also be persisted out-of-band. + + what you need to configure / know: + + - new `community` config block: `masterKey` (32-byte AES-GCM envelope key for the encrypted credential columns), `allowedProvisionPdsEndpoints` (URL-origin matching, collapses scheme case / default ports / trailing slash / IDN), optional `plcDirectory` override. + + - **provisioning fails closed.** When `allowProvisioning` is true, `allowedProvisionPdsEndpoints` MUST be non-empty — a missing/empty allowlist no longer means "accept any PDS" (that was a fail-open hole: any caller could have a PLC genesis op signed by Contrail's rotation key against an attacker-chosen PDS). To deliberately accept any endpoint, set the separate, loud `allowAnyProvisionPdsEndpoint: true`. The field was renamed from `allowedPdsEndpoints` to make clear it gates _provisioning_ only, not which PDSes Contrail reads/indexes. + + - new tables `provision_attempts` and `community_credentials`. credentials are stored AES-GCM-encrypted under that key; lose the key, lose the ability to mint sessions for previously-provisioned communities. + +- d7e0936: Private-network deployment support via a new optional `ContrailConfig.networkOverrides` block. + + `networkOverrides` carries three optional subfields, all defaulting to the current public-internet behavior (omit the block entirely and nothing changes): + + - **`resolver`** — a custom `DidDocumentResolver` used during DID-doc PDS fallback, labeler-endpoint resolution, and spaces service-auth JWT verification. Lets a deployment point at a private PLC mirror or inject a custom fetch (mTLS, retry, instrumentation). Trusted; not SSRF-checked. + - **`slingshotUrl`** — override the slingshot identity-resolver endpoint. Trusted; not SSRF-checked. + - **`additionalAllowedHosts`** — hostnames that bypass the default SSRF guard when validating a resolved PDS or labeler endpoint. Match is exact, case-insensitive, port-agnostic (e.g. `["pds.dev.svc.cluster.local"]`). This is the only knob that widens the validator; there is no "disable SSRF" flag. + + The overrides are threaded through PDS/identity resolution (`resolvePDS`, `getPDS`, `getClient`, `resolveIdentity*`, `refreshStaleIdentities`), labeler endpoint resolution and ingest (`resolveLabelerEndpoint`, `getLabelerState`, label subscribe cycles), and service-auth verification (`buildVerifier` in both the appview router and the community integration). The in-scope `config` is now also passed at every appview call site that resolves identities or PDS endpoints — the live-ingest refresh cycle (`runIngestCycle` → `refreshStaleIdentities`), the on-demand `refresh` path, and the router actor/identity/PDS resolution paths (`getProfile`, `getFeed`, collection queries, profile hydration, notify) — so private-network deploys honor the override on those paths instead of silently falling back to the public resolver and un-widened SSRF guard. + + The SSRF guard is now a single shared validator: `validateExternalUrl(url, additionalAllowedHosts?)` is exported from `contrail-base` and consumed by both the PDS client and labeler-endpoint resolution. `validateEndpointUrl` remains exported as a thin alias for backward compatibility. This removes the previous duplicate validator (`validatePdsUrl` + `validateEndpointUrl`) where an allowlist or SSRF-rule edit could be applied to only one copy. + + Also hardens schema initialization for concurrent/Postgres deployments: a dialect-aware `addColumnIfNotExists` (Postgres `ADD COLUMN IF NOT EXISTS`; SQLite pre-check), narrow absorption of the Postgres concurrent-`CREATE` race (42P07 / 23505 on pg_type/pg_class/pg_namespace indexes), and per-statement (rather than batched) DDL during `initSchema` / `initSpacesSchema` / spaces schema. Genuine DDL errors (syntax, type mismatch, missing column/table) still propagate. + +### Patch Changes + +- Updated dependencies [bea0dd2] +- Updated dependencies [d7e0936] + - @atmo-dev/contrail@0.8.0 + - @atmo-dev/contrail-base@0.8.0 + ## 0.7.0 ### Minor Changes @@ -90,6 +133,7 @@ **Migration** For most deployments running spaces today, the migration is: + 1. Update the config: split `spaces.{type, serviceDid, blobs}` into `spaces.authority.{type, serviceDid}` and `spaces.recordHost.{blobs}`. 2. Generate and store an authority signing key diff --git a/packages/contrail-community/package.json b/packages/contrail-community/package.json index b0f2c47..a5afdb6 100644 --- a/packages/contrail-community/package.json +++ b/packages/contrail-community/package.json @@ -1,6 +1,6 @@ { "name": "@atmo-dev/contrail-community", - "version": "0.7.0", + "version": "0.8.0", "description": "Community module for contrail — community-owned spaces with tiered access levels (member → moderator → admin), invite tokens, DID provisioning, and the access-level reconciler that keeps spaces_members in sync.", "type": "module", "sideEffects": false, diff --git a/packages/contrail-record-host/CHANGELOG.md b/packages/contrail-record-host/CHANGELOG.md index bbc13ac..5dad428 100644 --- a/packages/contrail-record-host/CHANGELOG.md +++ b/packages/contrail-record-host/CHANGELOG.md @@ -1,5 +1,12 @@ # @atmo-dev/contrail-record-host +## 0.8.0 + +### Patch Changes + +- Updated dependencies [d7e0936] + - @atmo-dev/contrail-base@0.8.0 + ## 0.7.0 ### Patch Changes diff --git a/packages/contrail-record-host/package.json b/packages/contrail-record-host/package.json index b0f50f9..2beced2 100644 --- a/packages/contrail-record-host/package.json +++ b/packages/contrail-record-host/package.json @@ -1,6 +1,6 @@ { "name": "@atmo-dev/contrail-record-host", - "version": "0.7.0", + "version": "0.8.0", "description": "Default record-host implementation for contrail — stores records and blobs for permissioned spaces, enforces local enrollment as the host's consent layer.", "type": "module", "sideEffects": false, diff --git a/packages/contrail/CHANGELOG.md b/packages/contrail/CHANGELOG.md index d404678..d50a581 100644 --- a/packages/contrail/CHANGELOG.md +++ b/packages/contrail/CHANGELOG.md @@ -1,5 +1,35 @@ # @atmo-dev/contrail +## 0.8.0 + +### Minor Changes + +- bea0dd2: A third community-creation mode: **provision**. alongside the existing `adopt` (caller already has a `did:plc`) and `mint` (caller wants a DID but brings their own PDS) modes, contrail can now provision a community on a stock `@atproto/pds` end-to-end — minting the `did:plc`, creating and activating the PDS account, generating an app password, and persisting credentials so the existing `community.putRecord` / `.deleteRecord` publish path keeps working. contrail never holds PDS admin credentials. + + **`xrpc/{ns}.community.provision`** runs the five-step PLC + PDS dance (key generation → PLC genesis → `createAccount` → `getRecommendedDidCredentials` + signed PLC update op → `activateAccount`), persists each step in a new `provision_attempts` table so a partially-failed attempt can be resumed, mints an app password, and seeds the session cache. + + **`contrail-community reap [--all-stuck] [--older-than ] [--db ] [--dry-run]`** new CLI (a bin shipped by `@atmo-dev/contrail-community`) that cleans up provision attempts which didn't reach `status='activated'` by tombstoning their PLC entries. `--dry-run` is the default; per-row confirmation is required for live reaping unless `--all-stuck` is given. `--all-stuck` only acts on rows idle at least `--older-than` minutes (default 30) so a bulk run can't tombstone an in-flight provision. Runs against the Cloudflare D1 binding by default, or against the decoupled Postgres index when `--db`/`DATABASE_URL` is set. It ships as a contrail-community bin because the PR #30 package split removed contrail's edge into community code: under pnpm's isolated `node_modules` the core `contrail` CLI can't resolve `@atmo-dev/contrail-community`, so `contrail reap` only registers in hoisted installs where both packages sit together. + + custody model: the caller supplies a `rotationKey` and that key sits at `rotationKeys[0]` — the highest-priority rotation slot on the resulting DID. contrail generates a subordinate keypair and persists it (AES-GCM-encrypted under `masterKey`) at `rotationKeys[1]`, so it can submit later PLC ops on the community's behalf — most importantly the post-activation PLC update during provision, and the tombstone op that `reap` issues to clean up stuck DIDs. + + the caller's key dominates: PLC's 72-hour nullification window means any op contrail signs with its subordinate key can be overridden within 72h by an op signed with the caller's key. with this caveat: a tombstone is irrevocable. a malicious or compromised contrail instance could tombstone any DID it provisioned. there is no managed code path, no shared rotation, and `rootCredentials` are returned to the caller in the response so they can also be persisted out-of-band. + + what you need to configure / know: + + - new `community` config block: `masterKey` (32-byte AES-GCM envelope key for the encrypted credential columns), `allowedProvisionPdsEndpoints` (URL-origin matching, collapses scheme case / default ports / trailing slash / IDN), optional `plcDirectory` override. + + - **provisioning fails closed.** When `allowProvisioning` is true, `allowedProvisionPdsEndpoints` MUST be non-empty — a missing/empty allowlist no longer means "accept any PDS" (that was a fail-open hole: any caller could have a PLC genesis op signed by Contrail's rotation key against an attacker-chosen PDS). To deliberately accept any endpoint, set the separate, loud `allowAnyProvisionPdsEndpoint: true`. The field was renamed from `allowedPdsEndpoints` to make clear it gates _provisioning_ only, not which PDSes Contrail reads/indexes. + + - new tables `provision_attempts` and `community_credentials`. credentials are stored AES-GCM-encrypted under that key; lose the key, lose the ability to mint sessions for previously-provisioned communities. + +### Patch Changes + +- Updated dependencies [d7e0936] + - @atmo-dev/contrail-base@0.8.0 + - @atmo-dev/contrail-appview@0.8.0 + - @atmo-dev/contrail-authority@0.8.0 + - @atmo-dev/contrail-record-host@0.8.0 + ## 0.7.0 ### Minor Changes @@ -90,6 +120,7 @@ **Migration** For most deployments running spaces today, the migration is: + 1. Update the config: split `spaces.{type, serviceDid, blobs}` into `spaces.authority.{type, serviceDid}` and `spaces.recordHost.{blobs}`. 2. Generate and store an authority signing key @@ -179,6 +210,7 @@ ``` what changed: + - `buildSpaceUri` / `parseSpaceUri` (`@atmo-dev/contrail`) emit / accept `ats://`. anything else returns `null` from `parseSpaceUri`. - 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. - realtime topics are unchanged in shape (`space:`), but `` is now an `ats://` URI. @@ -205,6 +237,7 @@ ``` changes: + - `#record` def now requires `["uri", "cid", "value"]` (matches atproto's standard `com.atproto.repo.listRecords#record`). `did`/`collection`/`rkey`/`time_us` remain in the response but are optional. - `getRecord` top-level output requires `["uri", "value"]` (matches atproto's `com.atproto.repo.getRecord`). - profile entries in `?profiles=true` responses use `value` instead of `record` for the profile record body. @@ -244,6 +277,7 @@ - ad3a61d: add `contrail dev` — local dev wrapper for cloudflare workers deployments. replaces `wrangler dev --test-scheduled` + a separate cron-trigger script with one command. on start it: + 1. connects to your local D1 via wrangler's `getPlatformProxy`, inspects state 2. prompts to run `backfillAll` if no completed backfills exist yet 3. prompts to run `refresh` if the ingest cursor is older than 60 minutes (configurable with `--stale-after`) @@ -271,6 +305,7 @@ options: `binding` (D1 binding name, default `"DB"`), `lexicons` (see below), `onInit` (one-shot app-specific setup). **`/xrpc/.lexicons` endpoint + `contrail-lex pull-service`** lets consumer apps typegen against a deployed contrail over HTTP, no PDS or DNS required: + - `contrail-lex generate` now emits a barrel `lexicons/generated/index.ts` that imports every lexicon the deployment speaks: generated + pulled + custom. The pulled lexicons are needed so consumer typegen can resolve `$ref`s out of the generated schemas. - Pass `{ lexicons }` to `createWorker` (or `createHandler(contrail, { lexicons })`) and the service exposes them at `GET /xrpc/.lexicons`. - From a consumer app: @@ -288,6 +323,7 @@ unlike `backfillAll`, it ignores the `backfills` state table and sweeps fresh. useful after jetstream outages or after leaving a dev deployment idle for days. each record in each configured collection is classified as: + - **missing** — PDS has it, DB doesn't - **stale update** — DB has it with a different CID, _and_ the DB row was written before the ignore window (default 60s, configurable) - **in sync** — same CID, or DB row is within the ignore window diff --git a/packages/contrail/package.json b/packages/contrail/package.json index 0219561..88158b2 100644 --- a/packages/contrail/package.json +++ b/packages/contrail/package.json @@ -1,6 +1,6 @@ { "name": "@atmo-dev/contrail", - "version": "0.7.0", + "version": "0.8.0", "description": "Index AT Protocol records with typed XRPC endpoints. Cloudflare Workers + D1, SvelteKit, Node.js.", "type": "module", "sideEffects": false, diff --git a/packages/lexicons/CHANGELOG.md b/packages/lexicons/CHANGELOG.md index ab5abd0..a51f8b5 100644 --- a/packages/lexicons/CHANGELOG.md +++ b/packages/lexicons/CHANGELOG.md @@ -1,5 +1,12 @@ # @atmo-dev/contrail-lexicons +## 0.4.8 + +### Patch Changes + +- Updated dependencies [bea0dd2] + - @atmo-dev/contrail@0.8.0 + ## 0.4.7 ### Patch Changes @@ -60,6 +67,7 @@ ``` what changed: + - `buildSpaceUri` / `parseSpaceUri` (`@atmo-dev/contrail`) emit / accept `ats://`. anything else returns `null` from `parseSpaceUri`. - 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. - realtime topics are unchanged in shape (`space:`), but `` is now an `ats://` URI. @@ -92,6 +100,7 @@ ``` changes: + - `#record` def now requires `["uri", "cid", "value"]` (matches atproto's standard `com.atproto.repo.listRecords#record`). `did`/`collection`/`rkey`/`time_us` remain in the response but are optional. - `getRecord` top-level output requires `["uri", "value"]` (matches atproto's `com.atproto.repo.getRecord`). - profile entries in `?profiles=true` responses use `value` instead of `record` for the profile record body. @@ -115,6 +124,7 @@ options: `binding` (D1 binding name, default `"DB"`), `lexicons` (see below), `onInit` (one-shot app-specific setup). **`/xrpc/.lexicons` endpoint + `contrail-lex pull-service`** lets consumer apps typegen against a deployed contrail over HTTP, no PDS or DNS required: + - `contrail-lex generate` now emits a barrel `lexicons/generated/index.ts` that imports every lexicon the deployment speaks: generated + pulled + custom. The pulled lexicons are needed so consumer typegen can resolve `$ref`s out of the generated schemas. - Pass `{ lexicons }` to `createWorker` (or `createHandler(contrail, { lexicons })`) and the service exposes them at `GET /xrpc/.lexicons`. - From a consumer app: diff --git a/packages/lexicons/package.json b/packages/lexicons/package.json index 066e06c..c35fcb9 100644 --- a/packages/lexicons/package.json +++ b/packages/lexicons/package.json @@ -1,6 +1,6 @@ { "name": "@atmo-dev/contrail-lexicons", - "version": "0.4.7", + "version": "0.4.8", "description": "Generate atproto lexicon JSON (and optionally TypeScript types via @atcute/lex-cli) from a Contrail config.", "type": "module", "files": [ diff --git a/packages/sync/CHANGELOG.md b/packages/sync/CHANGELOG.md index aa4b041..444bc23 100644 --- a/packages/sync/CHANGELOG.md +++ b/packages/sync/CHANGELOG.md @@ -1,5 +1,7 @@ # @atmo-dev/contrail-sync +## 0.8.0 + ## 0.7.0 ### Minor Changes @@ -90,6 +92,7 @@ **Migration** For most deployments running spaces today, the migration is: + 1. Update the config: split `spaces.{type, serviceDid, blobs}` into `spaces.authority.{type, serviceDid}` and `spaces.recordHost.{blobs}`. 2. Generate and store an authority signing key @@ -128,6 +131,7 @@ ``` what changed: + - `buildSpaceUri` / `parseSpaceUri` (`@atmo-dev/contrail`) emit / accept `ats://`. anything else returns `null` from `parseSpaceUri`. - 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. - realtime topics are unchanged in shape (`space:`), but `` is now an `ats://` URI. @@ -154,6 +158,7 @@ ``` changes: + - `#record` def now requires `["uri", "cid", "value"]` (matches atproto's standard `com.atproto.repo.listRecords#record`). `did`/`collection`/`rkey`/`time_us` remain in the response but are optional. - `getRecord` top-level output requires `["uri", "value"]` (matches atproto's `com.atproto.repo.getRecord`). - profile entries in `?profiles=true` responses use `value` instead of `record` for the profile record body. diff --git a/packages/sync/package.json b/packages/sync/package.json index 9bd77e4..d914e2a 100644 --- a/packages/sync/package.json +++ b/packages/sync/package.json @@ -1,6 +1,6 @@ { "name": "@atmo-dev/contrail-sync", - "version": "0.7.0", + "version": "0.8.0", "description": "Client-side reactive watch-store over contrail's watchRecords endpoints. SSE + WebSocket transports, optimistic updates, optional IndexedDB cache.", "type": "module", "sideEffects": false,