Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 28 additions & 17 deletions docs/runbooks/atproto-opt-in-smoke-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,36 +33,47 @@ For a localnet run instead of staging:

## Happy Path

1. Create or log in to a user in keycast.
2. Claim `username.divine.video`.
3. Verify `https://divine.video/.well-known/nostr.json?name=username` or the equivalent subdomain NIP-05 response.
4. Confirm ATProto is still disabled immediately after claim.
- expected: `enabled = false`
- expected: `state = null`
5. Enable ATProto from the authenticated client surface.
6. Verify keycast status reaches `pending`.
7. Verify the same user reaches `ready`.
1. Create or log in to a verified cookie-auth user in `login.divine.video`.
2. Open `settings/security`.
3. In the `Bluesky Account` card, claim `username.divine.video` if the user does not already have one.
4. Verify the card shows the claimed handle preview and does not auto-enable ATProto.
- expected UI: `Enable Bluesky account`
- expected status: `enabled = false`
- expected status: `state = null`
5. Verify `https://divine.video/.well-known/nostr.json?name=username` or the equivalent subdomain NIP-05 response.
6. Click `Enable Bluesky account` from the settings page.
7. Verify the card reaches `pending`.
- expected UI: provisioning in progress
- expected status: `enabled = true`
- expected status: `state = pending`
8. Wait for the same user to reach `ready`.
- expected UI: `@username.divine.video`
- expected UI: visible `did:plc:...`
- keycast status endpoint shows `state = ready`
- `divine-sky` `account_links` shows `pending -> ready`
8. Inspect the `divine-name-server` D1 row for the username.
9. Inspect the `divine-name-server` D1 row for the username.
- expected: `atproto_did = did:plc:...`
- expected: `atproto_state = ready`
9. Inspect the Fastly KV record for `user:<username>`.
10. Inspect the Fastly KV record for `user:<username>`.
- expected payload fields:
- `status = active`
- `atproto_did = did:plc:...`
- `atproto_state = ready`
10. Verify `divine-router` serves `https://username.divine.video/.well-known/atproto-did` and returns the bare DID.
11. Publish a Nostr video for the opted-in user.
12. Verify the mirrored ATProto post exists after the user is `ready`.
13. Disable ATProto.
14. Verify keycast status reaches `disabled`.
15. Verify future mirrored posts stop and `divine-router` returns `404` for `/.well-known/atproto-did`.
11. Verify `divine-router` serves `https://username.divine.video/.well-known/atproto-did` and returns the bare DID.
12. Publish a Nostr video for the opted-in user.
13. Verify the mirrored ATProto post exists after the user is `ready`.
14. Return to `settings/security` and click `Disable Bluesky account`.
15. Verify the card reaches `disabled`.
- expected UI: public DID resolution and future cross-posting are disabled
- expected status: `enabled = false`
- expected status: `state = disabled`
16. Verify future mirrored posts stop and `divine-router` returns `404` for `/.well-known/atproto-did`.

## Failure Checks

- Username claim must not auto-enable ATProto.
- `pending`, `failed`, and `disabled` users must not resolve `/.well-known/atproto-did` through `divine-router`.
- `failed` must show the last provisioning error in the `Bluesky Account` card and keep a retry path on the same page.
- The PDS canary must pass before the user-facing opt-in flow is considered healthy.
- The bridge must not publish while `crosspost_enabled = false`, even if a DID already exists.
- Client feature flags must be required to expose the ATProto controls on mobile and web.
24 changes: 10 additions & 14 deletions docs/runbooks/launch-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@

- Confirm `cargo fmt --check`, `cargo clippy --workspace --all-targets -- -D warnings`, and `bash scripts/test-workspace.sh` pass on the release candidate.
- Verify keycast can claim usernames without enabling ATProto by default.
- Verify a verified cookie-auth user can open `settings/security` and see the `Bluesky Account` card without unlocking private-key export first.
- Verify keycast `/api/user/atproto/enable`, `/status`, and `/disable` work for an authenticated user.
- Verify the PDS publishes `/.well-known/oauth-protected-resource` with `authorization_servers = ["https://login.divine.video"]`.
- Verify `login.divine.video` publishes `/.well-known/oauth-authorization-server` with PAR, token, issuer, `private_key_jwt`, and `client_id_metadata_document_supported = true` metadata for the ATProto auth-server surface.
- Verify the public key derived from keycast `ATPROTO_OAUTH_JWT_PRIVATE_KEY_HEX` matches `rsky-pds` `PDS_ENTRYWAY_JWT_PUBLIC_KEY_HEX`.
- Verify a `ready` linked account can complete PAR, browser approval, authorization-code exchange, refresh-token rotation, and `com.atproto.server.getSession` against the PDS with DPoP proofs and nonce challenges.
- Verify both public-client (`token_endpoint_auth_method = none`) and confidential-client (`private_key_jwt`) delegated login flows succeed with the same DPoP-bound session semantics.
- Verify the settings UI visibly covers the expected lifecycle states:
- claim username
- Bluesky disabled with enable CTA
- pending
- ready with DID shown
- failed with retry path
- disabled with re-enable path
- Verify `divine-handle-gateway` can POST lifecycle callbacks into keycast `/api/internal/atproto/state`.
- Verify `divine-name-server` receives ATProto readiness updates and publishes them to Fastly KV.
- Verify `divine-router` serves `/.well-known/atproto-did` only for active + ready usernames and returns `404` otherwise.
Expand All @@ -28,7 +31,7 @@

- Keep `FeatureFlag.atprotoPublishing` off in mobile by default until backend verification is complete.
- Keep `enableAtprotoPublishing` off in web by default until rollout is explicitly enabled.
- Keep delegated external-app login scoped to an internal or canary cohort until ATProto auth-server smoke tests are repeatable.
- Do not widen rollout until the self-serve `settings/security` flow has passed the smoke test end to end.
- Enable BGS crawl only after relay replay offsets and PDS write auth are verified in staging.
- Review rate limits for relay intake, Blossom fetches, and PDS XRPC writes before widening the cohort.
- Start with an internal cohort, then a small creator cohort, then broader opt-in traffic.
Expand All @@ -38,9 +41,6 @@
- Ensure alerting exists for relay disconnect loops, PDS write failures, and asset-manifest persistence failures.
- Keep a rollback path that disables new opt-ins and stops the bridge without deleting existing AT records.
- Confirm disable flow clears public `atproto_did` resolution and prevents new mirrored posts.
- Confirm disabling the account blocks new delegated app approvals on `login.divine.video` and revokes active delegated refresh sessions so refresh fails immediately.
- Treat externally issued access tokens as short-lived DPoP-bound tokens during rollout; current revocation is immediate for new approvals and refreshes, but existing access tokens expire out naturally.
- Treat DPoP nonce and replay caches as per-instance state during rollout; keep canaries small and watch for multi-instance retry mismatches until cache distribution exists.
- Route DMCA and takedown intake into the moderation queue before enabling public creator onboarding.

## Rollback Gates
Expand All @@ -49,21 +49,17 @@
- If the patched PDS breaks account creation, revert the staging overlay in `../divine-iac-coreconfig` to the previous `rsky-pds` image tag and resync ArgoCD.
- If the Fastly edge rollout is wrong, revert the `divine-router` service to the previous published package and purge the service again.
- If the user-facing ATProto path must be shut off, disable keycast opt-in by removing the staging runtime control-plane wiring or rolling back the keycast staging overlay without deleting existing AT repos.
- If delegated auth breaks, remove `PDS_ENTRYWAY_URL` / `PDS_ENTRYWAY_JWT_PUBLIC_KEY_HEX` from the PDS runtime and roll keycast back to the previous auth-server signing key or image before reopening traffic.
- After any disable or rollback, confirm the Fastly KV record for the canary username no longer advertises `atproto_did` and `atproto_state = ready`.

## Ops

- Record the active `RELAY_SOURCE_NAME`, PDS auth source, and deployed compose/image versions for each rollout.
- Record the delegated auth-server issuer, the deployed PDS entryway trust config, and the auth-server signing-key rollout version.
- Confirm support staff have the disable/export runbook and a tested contact path for account recovery issues.
- Confirm support staff understand the state model: claimed username does not imply ATProto ready.
- Confirm support staff understand the delegated auth caveat: a disabled account stops new approvals and refreshes immediately, but an already-issued access token can continue until expiry.
- Confirm support staff know that `ready` in `login.divine.video` means both public DID resolution and future cross-post eligibility are active.
- Confirm the canonical architecture boundary is still intact:
- keycast owns consent/lifecycle
- keycast owns the delegated ATProto Authorization Server surface
- divine-handle-gateway syncs ready/failed/disabled transitions back into keycast
- divine-name-server owns public read model
- divine-router remains read-only
- rsky-pds remains the protected resource and validates external auth-server tokens
- divine-atbridge only publishes when `crosspost_enabled && ready`
36 changes: 36 additions & 0 deletions docs/runbooks/login-divine-video.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,36 @@

It does not serve `/.well-known/atproto-did`. That read-only host resolution now belongs to `divine-router`, which reads the public state published by `divine-name-server`.

## Phase 1 User Surface

Phase 1 ships the user-visible ATProto controls inside the existing advanced settings page at `settings/security`.

The page exposes a `Bluesky Account` card for authenticated cookie-session users:

- no username claimed:
- show username entry and `Claim username`
- username claimed, Bluesky disabled:
- show `username.divine.video`
- show `Enable Bluesky account`
- pending:
- show provisioning progress and keep polling keycast status
- ready:
- show `@username.divine.video`
- show the resolved `did:plc:...`
- explain that public DID resolution and future cross-posting are active
- failed:
- show the last provisioning error from keycast
- allow retry via `Enable Bluesky account`
- disabled:
- explain that public DID resolution and future cross-posting are off
- allow re-enable with the existing username

The card is visible without the password-unlock flow that protects private-key export. Email verification requirements still follow the existing security settings page rules.

## Route Responsibilities

- `GET /api/user/profile`
Returns the claimed username source of truth for the authenticated user.
- `POST /api/user/profile`
Claims or updates `username.divine.video` for NIP-05 only. This must not auto-enable ATProto.
- `POST /api/user/atproto/enable`
Expand Down Expand Up @@ -119,6 +147,14 @@ For launch, treat the flow as:
- rsky-pds publishes `/.well-known/oauth-protected-resource` and trusts the configured auth-server signing key
- divine-atbridge publishes only for opted-in + ready users

From the user perspective, `ready` means all of the following are true:

- keycast shows `enabled = true` and `state = ready`
- the user can see a `did:plc:...` on `settings/security`
- `divine-name-server` has published the public handle state
- `divine-router` can serve `/.well-known/atproto-did`
- future Nostr video publishes are eligible for mirroring because the bridge sees `crosspost_enabled && ready`

`divine-handle-gateway` also self-heals persisted lifecycle state on startup:

- it replays `pending` rows through provisioning
Expand Down
Loading