feat(contrail): community DID provisioning on stock PDS (#31)
* feat(contrail-community): provision schema, types, adapter CRUD
* feat(contrail-community): PDS + PLC + service-auth helpers
* feat(contrail-community): provision orchestrator + XRPC route + session cache
* feat(contrail-community): reap CLI for stuck provisioning rows
* test+docs(contrail): provision e2e, docs, changeset, deps
* fix(contrail-community): harden provision allowlist + reap (PR #31 review)
Security/review follow-ups on the community DID provisioning feature:
- Provisioning fails closed: with allowProvisioning=true, an empty/undefined
allowlist no longer accepts any caller pdsEndpoint (fail-open). Require a
non-empty allowlist, or the explicit, loud allowAnyProvisionPdsEndpoint.
Renamed allowedPdsEndpoints -> allowedProvisionPdsEndpoints to scope it to
provisioning (Contrail still reads/indexes from every PDS).
- reap --all-stuck gains a mandatory age floor (listStuckAttempts(olderThanMs)
+ --older-than <minutes>, default 30) so a bulk run can't tombstone an
in-flight provision. --attempt-id still targets a known row regardless.
- reap is reachable: ship it as a contrail-community bin (under pnpm the core
contrail CLI can't resolve the community package; the dynamic import there
now logs instead of an empty catch). Adds Postgres support via
--db/DATABASE_URL alongside the D1 binding.
- archiveStuckAttempt is now atomic (db.batch) + idempotent (ON CONFLICT DO
NOTHING), so a retry after a partial failure can't PK-conflict.
- Minor: export bytesToB64url from plc and reuse in service-auth; drop the
duplicate reap mutual-exclusion validator.
mint-route allowlist gap (pre-existing on main) tracked separately in om-vyok.
* test(contrail-community): drop tautological builder-shape assertions
Remove the 'buildTombstoneOp produces the expected shape' case and the two
pass-through type/prev assertions in the update-op test: both assert literals
the builder just set or args just passed in, exercising object-literal
semantics rather than runtime behavior. The real signing behavior
(signTombstoneOp / signUpdateOp base64url sig) and the live-PLC CID round-trip
remain covered.
* docs(contrail-community): document SSRF exposure of allowAnyProvisionPdsEndpoint
The accept-any escape hatch bypasses endpoint validation: the caller-supplied
pdsEndpoint reaches describeServer + createAccount fetches with no
private-IP/metadata guard. Note inline that it must be paired with trusted auth
+ egress network policy + IMDSv2; the default allowlist path contains this by
construction. No behavior change.
feat(contrail): community DID provisioning on stock PDS (#31)
* feat(contrail-community): provision schema, types, adapter CRUD
* feat(contrail-community): PDS + PLC + service-auth helpers
* feat(contrail-community): provision orchestrator + XRPC route + session cache
* feat(contrail-community): reap CLI for stuck provisioning rows
* test+docs(contrail): provision e2e, docs, changeset, deps
* fix(contrail-community): harden provision allowlist + reap (PR #31 review)
Security/review follow-ups on the community DID provisioning feature:
- Provisioning fails closed: with allowProvisioning=true, an empty/undefined
allowlist no longer accepts any caller pdsEndpoint (fail-open). Require a
non-empty allowlist, or the explicit, loud allowAnyProvisionPdsEndpoint.
Renamed allowedPdsEndpoints -> allowedProvisionPdsEndpoints to scope it to
provisioning (Contrail still reads/indexes from every PDS).
- reap --all-stuck gains a mandatory age floor (listStuckAttempts(olderThanMs)
+ --older-than <minutes>, default 30) so a bulk run can't tombstone an
in-flight provision. --attempt-id still targets a known row regardless.
- reap is reachable: ship it as a contrail-community bin (under pnpm the core
contrail CLI can't resolve the community package; the dynamic import there
now logs instead of an empty catch). Adds Postgres support via
--db/DATABASE_URL alongside the D1 binding.
- archiveStuckAttempt is now atomic (db.batch) + idempotent (ON CONFLICT DO
NOTHING), so a retry after a partial failure can't PK-conflict.
- Minor: export bytesToB64url from plc and reuse in service-auth; drop the
duplicate reap mutual-exclusion validator.
mint-route allowlist gap (pre-existing on main) tracked separately in om-vyok.
* test(contrail-community): drop tautological builder-shape assertions
Remove the 'buildTombstoneOp produces the expected shape' case and the two
pass-through type/prev assertions in the update-op test: both assert literals
the builder just set or args just passed in, exercising object-literal
semantics rather than runtime behavior. The real signing behavior
(signTombstoneOp / signUpdateOp base64url sig) and the live-PLC CID round-trip
remain covered.
* docs(contrail-community): document SSRF exposure of allowAnyProvisionPdsEndpoint
The accept-any escape hatch bypasses endpoint validation: the caller-supplied
pdsEndpoint reaches describeServer + createAccount fetches with no
private-IP/metadata guard. Note inline that it must be paired with trusted auth
+ egress network policy + IMDSv2; the default allowlist path contains this by
construction. No behavior change.
add contrail-e2e app with end-to-end tests against a local ATProto stack
New app under apps/contrail-e2e that brings up a full sealed local
ATProto network (PDS, PLC, Jetstream, TAP, Postgres, maildev) via
docker-compose and runs three end-to-end test files against it:
- health.test.ts — service health checks
- ingest-roundtrip.test.ts — publish → index roundtrip, verifying
a calendar event and an RSVP land in the DB and that the grouped
rsvpsGoingCount is populated via an in-process XRPC handler
- cursor-resume.test.ts — regression for runPersistent's durable
cursor. Publishes A, stops the ingester, publishes B + updates B
+ deletes A, restarts, and asserts the saved cursor replays the
gap. The basic roundtrip can't catch a broken cursor because
events always arrive while the ingester is live.
Each test stands up its own runPersistent and XRPC handler
in-process against an isolated pg schema (search_path pinned per
test), so the suite works with just pnpm stack:up and doesn't
fight a dogfooding ingester writing to public.
feat(contrail): community DID provisioning on stock PDS (#31)
* feat(contrail-community): provision schema, types, adapter CRUD
* feat(contrail-community): PDS + PLC + service-auth helpers
* feat(contrail-community): provision orchestrator + XRPC route + session cache
* feat(contrail-community): reap CLI for stuck provisioning rows
* test+docs(contrail): provision e2e, docs, changeset, deps
* fix(contrail-community): harden provision allowlist + reap (PR #31 review)
Security/review follow-ups on the community DID provisioning feature:
- Provisioning fails closed: with allowProvisioning=true, an empty/undefined
allowlist no longer accepts any caller pdsEndpoint (fail-open). Require a
non-empty allowlist, or the explicit, loud allowAnyProvisionPdsEndpoint.
Renamed allowedPdsEndpoints -> allowedProvisionPdsEndpoints to scope it to
provisioning (Contrail still reads/indexes from every PDS).
- reap --all-stuck gains a mandatory age floor (listStuckAttempts(olderThanMs)
+ --older-than <minutes>, default 30) so a bulk run can't tombstone an
in-flight provision. --attempt-id still targets a known row regardless.
- reap is reachable: ship it as a contrail-community bin (under pnpm the core
contrail CLI can't resolve the community package; the dynamic import there
now logs instead of an empty catch). Adds Postgres support via
--db/DATABASE_URL alongside the D1 binding.
- archiveStuckAttempt is now atomic (db.batch) + idempotent (ON CONFLICT DO
NOTHING), so a retry after a partial failure can't PK-conflict.
- Minor: export bytesToB64url from plc and reuse in service-auth; drop the
duplicate reap mutual-exclusion validator.
mint-route allowlist gap (pre-existing on main) tracked separately in om-vyok.
* test(contrail-community): drop tautological builder-shape assertions
Remove the 'buildTombstoneOp produces the expected shape' case and the two
pass-through type/prev assertions in the update-op test: both assert literals
the builder just set or args just passed in, exercising object-literal
semantics rather than runtime behavior. The real signing behavior
(signTombstoneOp / signUpdateOp base64url sig) and the live-PLC CID round-trip
remain covered.
* docs(contrail-community): document SSRF exposure of allowAnyProvisionPdsEndpoint
The accept-any escape hatch bypasses endpoint validation: the caller-supplied
pdsEndpoint reaches describeServer + createAccount fetches with no
private-IP/metadata guard. Note inline that it must be paired with trusted auth
+ egress network policy + IMDSv2; the default allowlist path contains this by
construction. No behavior change.
feat(contrail): community DID provisioning on stock PDS (#31)
* feat(contrail-community): provision schema, types, adapter CRUD
* feat(contrail-community): PDS + PLC + service-auth helpers
* feat(contrail-community): provision orchestrator + XRPC route + session cache
* feat(contrail-community): reap CLI for stuck provisioning rows
* test+docs(contrail): provision e2e, docs, changeset, deps
* fix(contrail-community): harden provision allowlist + reap (PR #31 review)
Security/review follow-ups on the community DID provisioning feature:
- Provisioning fails closed: with allowProvisioning=true, an empty/undefined
allowlist no longer accepts any caller pdsEndpoint (fail-open). Require a
non-empty allowlist, or the explicit, loud allowAnyProvisionPdsEndpoint.
Renamed allowedPdsEndpoints -> allowedProvisionPdsEndpoints to scope it to
provisioning (Contrail still reads/indexes from every PDS).
- reap --all-stuck gains a mandatory age floor (listStuckAttempts(olderThanMs)
+ --older-than <minutes>, default 30) so a bulk run can't tombstone an
in-flight provision. --attempt-id still targets a known row regardless.
- reap is reachable: ship it as a contrail-community bin (under pnpm the core
contrail CLI can't resolve the community package; the dynamic import there
now logs instead of an empty catch). Adds Postgres support via
--db/DATABASE_URL alongside the D1 binding.
- archiveStuckAttempt is now atomic (db.batch) + idempotent (ON CONFLICT DO
NOTHING), so a retry after a partial failure can't PK-conflict.
- Minor: export bytesToB64url from plc and reuse in service-auth; drop the
duplicate reap mutual-exclusion validator.
mint-route allowlist gap (pre-existing on main) tracked separately in om-vyok.
* test(contrail-community): drop tautological builder-shape assertions
Remove the 'buildTombstoneOp produces the expected shape' case and the two
pass-through type/prev assertions in the update-op test: both assert literals
the builder just set or args just passed in, exercising object-literal
semantics rather than runtime behavior. The real signing behavior
(signTombstoneOp / signUpdateOp base64url sig) and the live-PLC CID round-trip
remain covered.
* docs(contrail-community): document SSRF exposure of allowAnyProvisionPdsEndpoint
The accept-any escape hatch bypasses endpoint validation: the caller-supplied
pdsEndpoint reaches describeServer + createAccount fetches with no
private-IP/metadata guard. Note inline that it must be paired with trusted auth
+ egress network policy + IMDSv2; the default allowlist path contains this by
construction. No behavior change.