Friend hit a 500 on /api/register/complete because the Postgres unique
constraint fired after the WebAuthn ceremony — they'd already burned a
passkey creation by the time the server refused. Pre-check the username in
/api/register/start so the join page shows "Username is already taken"
before the browser prompts. Also catch the 23505 duplicate-key error in
/api/register/complete as a race-condition safety net.
The /join and OIDC accept pages were calling /api/register/complete without
the public key extracted from the WebAuthn attestation. storeCredential writes
a credentials row with a NOT NULL public_key column, so the request crashed
with an unhandled exception and Hono replied "Internal Server Error" as plain
text — the client JSON.parse choked on it.
- Extract credential.response.getPublicKey() on the client and include it in
the completion payload (both /join and /oidc/accept flows).
- Add app.onError to return JSON 500s on /api/* routes so any future crashes
produce parseable error bodies instead of cascading into confusing "Unexpected
token 'I'" errors in the browser.
The /api/internal/* routes (user lookups, space invites, fund claims, agent
mailbox creds) were publicly reachable via auth.rspace.online because Traefik
forwards everything under the encryptid host rule. They were designed for
service-to-service calls over the Docker network only.
Add a Hono middleware that rejects /api/internal/* with 404 when the request
arrives through the public edge (X-Forwarded-For / X-Real-IP set by Traefik)
unless it carries a valid X-Internal-Key matching INTERNAL_API_KEY. Direct
container-to-container fetches don't set forwarded headers so existing
callers keep working untouched.
- rApp dropdown no longer shows a "Manage rApps" panel for disabled modules.
Enable/disable lives in Edit Space → Modules only.
- /api/internal/user-email/:userId now resolves by id OR did (real did🔑z6Mk…
or legacy synthetic did🔑<32-char id prefix>), so Edit Space members list
shows display names instead of raw DIDs.
- /api/spaces/:slug/invites admin check now accepts ownerDID stored as either
raw userId or did🔑. Routes the list through a new internal endpoint on
encryptid so Automerge-owned spaces without a space_members row still work.
- /join page registration payload was missing userId, causing every invite
claim to fail with "Missing required fields: userId, credential, username".
Same fix applied to the OIDC accept page flow.
Space-agent and mailbox emails set From to {slug}-agent@rspace.online but
the envelope MAIL FROM was noreply@rmail.online. Rspamd DKIM-signs with
the envelope domain (rmail.online), which fails DMARC alignment against
the rspace.online From header — Gmail was quarantining invites.
Set envelope.from to match the From header so rspamd signs with
rspace.online's DKIM key and SPF/DKIM align. Verified via test send:
DKIM_SIGNED{rspace.online:s=dkim}, FROM_EQ_ENVFROM, no DMARC:Quarantine.
Covers: space invite emails (identity + existing-user paths), agent
notifications, rinbox MI agent blasts, mailbox approval sends.
No longer needed — sidecar lifecycle is delegated to Sablier via HTTP API
(see ee251fd). Removes an unnecessary Docker socket exposure from the
main app container.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the custom Docker Engine API implementation in sidecar-manager.ts
with HTTP calls to Sablier's blocking strategy endpoint. Sablier owns the
Docker socket, handles start + readiness + session-TTL idle stop.
- Drops ~80 lines of Docker API plumbing and the idle-watcher interval
- Public API (ensureSidecar/markSidecarUsed/isSidecarRunning/startIdleWatcher)
unchanged — callers in server/index.ts untouched
- SABLIER_URL defaults to http://sablier:10000 (reachable once sablier is
attached to rspace-online_rspace-internal; dev-ops change separate)
- SIDECAR_SESSION_DURATION env (default 5m) matches previous idle timeout
- Graceful no-op when Sablier unreachable (local dev)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copying the sablier-encryptid.yml labels would immediately apply
traefik.enable=false to the live encryptid container, breaking auth
before Sablier has a middleware route in place. Reduced to empty
services:{} with a comment explaining what's needed to activate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
.env on Netcup sets COMPOSE_FILE=docker-compose.yml:docker-compose.sablier-support.yml
but the file was never created, causing docker compose commands to fail
without explicit -f flag. Adds the missing override with Sablier labels for
encryptid services (matching the existing sablier-encryptid.yml pattern).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase B's full booking UI + admin dashboard + cancel page bundles are
now built. Bumping cache version forces Cloudflare + browser to fetch
the new code instead of the Phase A stub.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- rename old rSchedule (cron/automations) → rMinders
- native port of schedule-jeffemmett as new rSchedule (Calendly-style booking)
All 8 phases complete: scaffold, public booking UI, availability engine,
Google Calendar OAuth+sync, admin UI, emails, in-process cron, cross-space
invitations + timezone shift.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lib/availability.ts (getAvailableSlots) was imported by mod.ts but never
committed — caused module load to fail on server, leaving renderShell
callers with undefined 'modules' and producing 500s on every rschedule
route. Plus small updates to booking/admin/cancel components and mod.ts
wiring.
Schema for the new Calendly-style rSchedule port plus two new shared
canvas components (folk-app-canvas, folk-widget) that replace per-rApp
tab-based nav with a unified widget-on-canvas surface.
Complete the rename started in dda7760 (which removed rschedule/ but
left callers unmigrated and the rminders/ dir uncommitted). Updates
vite.config.ts build entries, API base fetches in folk-comment-pin,
folk-rapp widget map, module-display meta, calendar reminder-drop
route, docs comment-panel, e2e fixtures, shell/landing/mcp-server
references, and backlog/ONTOLOGY docs.
Fixes vite build failure: "Could not resolve entry module
modules/rschedule/components/folk-schedule-app.ts".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files existed on disk and were referenced by vite.config.ts entry
config, but were never git-added when the rschedule→rminders rename
happened in dda7760. Build on a fresh clone failed with "Could not
resolve entry module modules/rschedule/components/folk-schedule-app.ts"
because vite was picking up the stale pre-rename config on disk.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Traefik → container hop in the rSpace stack sets x-forwarded-proto: http
even when the real client request is https. That was leaking into the
"Open in <rApp>" links emitted in chronicle.mw. Simplify: any non-local
host gets https, period.
Previous limits (avg 120/min, burst 30) had no sourceCriterion. Traefik
default groups by request Host, so ALL users of rspace.online shared a
single 120/min bucket — tripped almost immediately under normal load
(repeated 429s in rpast, api/mi/models, etc).
Scope per Cloudflare client IP (CF-Connecting-IP header) and raise to
600/min average with 150 burst — interactive use can spike above 120/min
from one client easily (module loads + polling + autosave).
Adds e2e/tests/rsocials-campaign-flow.spec.ts — 13 tests covering the
unified campaign flow UX: dashboard → planner navigation, brief canvas
node (+ preview banner), markdown import modal, wizard handoff, and
API shape. 36 passed / 3 AI-skipped across chromium/firefox/mobile.
Bug fixes uncovered by the suite:
- markDownstreamStale only redraws when a node actually flips stale,
so typing in an input node no longer destroys the open inline-edit
overlay.
- executeSave wraps the local-first write in try/catch and nulls the
client on failure, so a half-initialised client (WS down, IDB
unavailable) falls through to localStorage instead of throwing
"Document not open".
- init-failure path also nulls the client so the first save after a
failed subscribe doesn't hit a doc that was never opened.
Test infra:
- server/security.ts + server/index.ts honour DISABLE_RATE_LIMIT=1
(and NODE_ENV=test) to bypass HTTP rate limiter and anon WS-per-IP
cap so the suite can run under 8 parallel workers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- docker-compose.yml: rspace service now uses
localhost:3000/jeffemmett/rspace-online:${IMAGE_TAG:-latest} so
CI's `docker pull ... && docker compose up -d --no-build` actually
picks up the new image without a manual retag step.
- modules/rpast/mod.ts: base URL defaults to https for any non-loopback
host, honoring x-forwarded-proto only when set to http|https. Fixes
"Open in <rApp>" links previously emitted as http:// on CF/Traefik.
Users on an older SW built before rpast existed saw a cached
/rpast shell with an outdated module list (rmail/rfunds/rwork/...).
Bumping the cache version forces activation-time cleanup to drop
the old entries on next visit.
- Rename 'Add' to 'Invite' in the By-Username add-member row to match
the By-Email path and reflect that it goes through an invite flow.
- Add 10px top margin to .add-member-row so the role dropdown and
button don't visually collide with the search input/selected-user
area above.