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
37 changes: 18 additions & 19 deletions docs/farcaster-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,36 +350,35 @@ Farcaster, not device display or that the player opened the alert.

Queue-before-webhook races are retained without a token for at most 24 hours,
signed opt-outs erase token material immediately, invalid tokens are purged,
retry attempts are bounded, and one request generation cannot notify twice.
`notify-admitted <fid> --confirm` remains an exact-epoch reconciliation command
for legacy or exceptional already-committed admissions; it is not the normal
admission sequence. Notification preference and delivery add no SpacetimeDB
schema or browser authority.
retry attempts are bounded, and one request generation uses one stable
notification ID and at most one active transport target. The current pending
access request is the sole player-visible admission notification generation.
Already-admitted reconciliation is non-delivering, and the former standalone
operator command has been retired. A genuinely new pending-request timestamp
after a reviewed reset is the only event that permits another admission alert.
Notification preference and delivery add no SpacetimeDB schema or browser
authority.

The reviewed payloads are:

```txt
normal admission:
admission request:
notificationId: warpkeep-access-approved-v2-r<pending-request-timestamp>
title: Admission approved
body: The Hegemony is finalizing your Realm access. Your keep will open shortly.

already-live reconciliation:
notificationId: warpkeep-access-approved-v1-e<positive-auth-epoch>
title: The Hegemony admits you
body: Your keep awaits in Genesis 001. Enter the living Realm.
title: Welcome to the Hegemony Empire
body: The gates have answered your name. Cross the threshold, Founder—your legacy awaits.
targetUrl: https://warpkeep.com/?miniApp=true
```

The titles and bodies are within Farcaster's bounds, contain no identity or
private state, and accurately describe their generation. Copy changes require
a reviewed Worker rollout.
The title and body are within Farcaster's bounds and contain no realm name,
FID, username, other-player event, or private state. Copy changes require a
reviewed Worker rollout.

For a notification launch, the browser retains only
`location.type === "notification"` and a notification ID matching either
`warpkeep-access-approved-v2-r<positiveInteger>` or the rollback-compatible
`warpkeep-access-approved-v1-e<positiveInteger>` within the 128-character
limit. Host title and body are discarded. Warpkeep then shows a short
`warpkeep-access-approved-v2-r<positiveInteger>` or a previously delivered,
rollback-compatible `warpkeep-access-approved-v1-e<positiveInteger>` within the
128-character limit. The latter remains launch-compatible but can no longer be
queued or sent. Host title and body are discarded. Warpkeep then shows a short
confirmation state and runs normal Quick Auth, current admission, Terms, and
canonical-keep checks. A pending or changed account stays pending; the
notification itself never grants access or creates another keep.
Expand Down
16 changes: 8 additions & 8 deletions docs/operations/alpha-activation.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,8 @@ a SpacetimeDB schema change. Roll them out in this order:
request generation before requesting an administrator token. If the player
opted in, require Farcaster provider acceptance before mutating admission;
`queued` or `delivery-exhausted` aborts unchanged. `not-subscribed` is an
explicit audited fallback for a player without consent. Keep
`notify-admitted` only for idempotent already-live reconciliation.
explicit audited fallback for a player without consent. Never queue a
post-admission reconciliation notification; that legacy path is retired.

### Owner canary and end-to-end acceptance

Expand All @@ -279,7 +279,7 @@ it.
fact that the client presentation gate is still `false`. Do not record a
real FID, webhook body, notification token, or delivery URL.
2. Use a dedicated owner-controlled account in the exact production Mini App.
Before any admission or `notify-admitted` action for that test cycle, accept
Before the admission action for that test cycle, accept
Farcaster's native add prompt. In the bounded log window, require exactly the
fixed events `miniapp_webhook_verified` and
`miniapp_notification_subscribed`; no caller data is valid evidence.
Expand Down Expand Up @@ -310,11 +310,11 @@ it.
complete acceptance on current Farcaster iOS and Android before declaring
the client rollout complete.

The normal pending-request notification is `Admission approved` with
`The Hegemony is finalizing your Realm access. Your keep will open shortly.` The older
`The Hegemony admits you` payload remains only for already-live reconciliation.
Both are bounded and privacy-safe. Any copy change requires a separate reviewed
Worker rollout.
The sole pending-request notification is `Welcome to the Hegemony Empire` with
`The gates have answered your name. Cross the threshold, Founder—your legacy awaits.`
It contains no realm name or player identity. The admitted-epoch payload is
retired and must be cancelled without delivery. Any copy change requires a
separate reviewed Worker rollout.

For rollback, set `APPROVAL_NOTIFICATIONS_ENABLED=false` first, then return
`VITE_WARPKEEP_ADMISSION_NOTIFICATIONS_ENABLED=false` and deploy the last
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@
"stdb:seed-world": "tsx scripts/hermes-admin.ts seed-world",
"stdb:expand-world-v3": "tsx scripts/hermes-admin.ts expand-world-v3",
"stdb:admit-founder": "tsx scripts/hermes-admin.ts admit-founder",
"stdb:notify-admitted": "tsx scripts/hermes-admin.ts notify-admitted",
"stdb:allow-fid": "tsx scripts/hermes-admin.ts allow-fid",
"stdb:disable-fid": "tsx scripts/hermes-admin.ts disable-fid",
"stdb:bump-auth-epoch": "tsx scripts/hermes-admin.ts bump-auth-epoch",
Expand Down
29 changes: 1 addition & 28 deletions scripts/hermes-admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ type Command =
| 'inspect-access-request-reset'
| 'reset-access-request'
| 'admit-founder'
| 'notify-admitted'
| 'allow-fid'
| 'disable-fid'
| 'bump-auth-epoch'
Expand Down Expand Up @@ -382,7 +381,6 @@ function commandFrom(value: string | undefined): Command {
|| value === 'inspect-access-request-reset'
|| value === 'reset-access-request'
|| value === 'admit-founder'
|| value === 'notify-admitted'
|| value === 'allow-fid'
|| value === 'disable-fid'
|| value === 'bump-auth-epoch'
Expand All @@ -403,7 +401,7 @@ function commandFrom(value: string | undefined): Command {
}
fail(
'Usage: hermes-admin.ts '
+ '<seed-world|expand-world-v3|list-access-requests|inspect-access-request-reset|reset-access-request|admit-founder|notify-admitted|allow-fid|disable-fid|bump-auth-epoch|backfill-resources|seed-alpha-component|activate-alpha-water|inspect-alpha|inspect-alpha-v2|inspect-alpha-v3|inspect-alpha-v4|inspect-alpha-v8|inspect-alpha-v10|inspect-alpha-v12|inspect-publish-pre-v12|inspect-publish-post-v12> '
+ '<seed-world|expand-world-v3|list-access-requests|inspect-access-request-reset|reset-access-request|admit-founder|allow-fid|disable-fid|bump-auth-epoch|backfill-resources|seed-alpha-component|activate-alpha-water|inspect-alpha|inspect-alpha-v2|inspect-alpha-v3|inspect-alpha-v4|inspect-alpha-v8|inspect-alpha-v10|inspect-alpha-v12|inspect-publish-pre-v12|inspect-publish-post-v12> '
+ '[...args] [--dry-run] [--confirm]. admit-founder requires private stdin: '
+ '--input-stdin --dry-run creates a reviewed plan; --input-stdin --confirm consumes it; '
+ 'allow-fid only re-enables an existing complete founder. list-access-requests accepts '
Expand Down Expand Up @@ -474,8 +472,6 @@ export function parseHermesArguments(arguments_: readonly string[] = process.arg
|| command === 'disable-fid'
|| command === 'bump-auth-epoch'
? 3
: command === 'notify-admitted'
? 2
: command === 'backfill-resources' || command === 'seed-alpha-component'
? 2
: 1;
Expand Down Expand Up @@ -508,15 +504,6 @@ export function parseHermesArguments(arguments_: readonly string[] = process.arg
if (flags.has('--dry-run') === flags.has('--confirm')) {
fail('Profiled admission requires exactly one of --dry-run or --confirm.');
}
} else if (command === 'notify-admitted') {
if (
flags.has('--input-stdin')
|| flags.has('--json')
|| flags.has('--dry-run')
|| !flags.has('--confirm')
) {
fail('Admission notification reconciliation requires exactly --confirm.');
}
} else if (command === 'reset-access-request') {
if (flags.has('--json')) {
fail('Hermes command received a flag that is invalid for this operation.');
Expand Down Expand Up @@ -2073,7 +2060,6 @@ async function main() {
&& command !== 'expand-world-v3'
&& command !== 'reset-access-request'
&& command !== 'admit-founder'
&& command !== 'notify-admitted'
&& command !== 'seed-alpha-component'
&& command !== 'activate-alpha-water'
&& process.env.WARPKEEP_HERMES_NONINTERACTIVE === 'yes'
Expand All @@ -2091,7 +2077,6 @@ async function main() {
let fid = command === 'allow-fid'
|| command === 'disable-fid'
|| command === 'bump-auth-epoch'
|| command === 'notify-admitted'
|| command === 'inspect-access-request-reset'
|| (command === 'reset-access-request' && dryRun)
? readFid(positional[1])
Expand Down Expand Up @@ -2205,9 +2190,6 @@ async function main() {
if (command === 'activate-alpha-water' && !dryRun && !confirmed) {
fail('Refusing Water activation without --confirm.');
}
if (command === 'notify-admitted' && !confirmed) {
fail('Refusing admission notification reconciliation without --confirm.');
}
if (command === 'reset-access-request' && !dryRun && !confirmed) {
fail('Refusing access request reset without --confirm.');
}
Expand Down Expand Up @@ -2275,15 +2257,6 @@ async function main() {
const bridgeUrl = prevalidatedBridgeUrl
?? readHttpsUrl(process.env.WARPKEEP_AUTH_BRIDGE_URL, 'WARPKEEP_AUTH_BRIDGE_URL');
requireCredentialedProductionTarget(uri, database, bridgeUrl);
if (command === 'notify-admitted' && fid !== undefined) {
const status = await requestAdmissionNotification(
bridgeUrl,
fid,
readNotificationOperatorSecret(notificationOperatorSecret),
);
console.log(JSON.stringify({ admissionNotification: status }));
return;
}
if (
command === 'reset-access-request'
&& process.env.WARPKEEP_ADMIN_TOKEN_SECRET !== undefined
Expand Down
14 changes: 10 additions & 4 deletions services/auth-bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,10 +389,16 @@ object, are never returned to the browser or stored in SpacetimeDB, and expire
within 366 days. Signed opt-outs remain accepted while delivery is paused and
erase raw token material immediately. The deployed v1 consent record retains
its rollback-compatible shape; pending-request work and receipts use a separate
private v2 record. Each send rechecks either the exact current pending-request
timestamp while admission is disabled, or the exact current live admission
epoch. Stable notification IDs, retry ceilings, replay tombstones, and bounded
generation receipts make retries idempotent.
private v2 record. The exact current pending-request timestamp while admission
is disabled is the only generation allowed to produce a player-visible alert.
Legacy admitted-epoch queues remain readable only so they can be cancelled
without delivery. One deterministic transport target, a stable request-scoped
notification ID, retry ceilings, replay tombstones, and a durable request
receipt prevent a request from fanning out or producing a post-admission alert.
Once selected, a request's transport target is immutable; opt-out, token
rotation, expiry, or client removal terminates that generation rather than
redirecting it. Terminal request timestamps are monotonic high-water marks, so
rollback or stale operator input cannot revive an older alert.
The operator-only status projection contains only queue state, generation kind,
aggregate attempt counts, static retry categories, and bounded retry timing. It
never returns a request timestamp, notification token, delivery URL, webhook
Expand Down
Loading