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.
authored by