Skip to content

feat: vault relay-server (Postgres + /api/vault)#7

Merged
guohai merged 3 commits into
mainfrom
feature/vault-relay-server
May 30, 2026
Merged

feat: vault relay-server (Postgres + /api/vault)#7
guohai merged 3 commits into
mainfrom
feature/vault-relay-server

Conversation

@guohai

@guohai guohai commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the relay-server side of the vault per docs/specs/2026-05-29-vault-relay-server-design.md — a durable, append-only, versioned shared context store that collaborating atems read/write. This is the first persistent store in the relay-server (previously fully in-memory).

What's new

  • vault_store.rsVaultStore trait with two impls:
    • InMemoryVaultStore (tests + DB-less runs)
    • PgVaultStore (sqlx, runtime queries so no live DB is needed at build time)
    • Append/override run in a transaction (no (vault_id, entry_no, version) collisions). Current view = highest version per entry_no; history = all rows by seq; ?since filters by seq.
  • migrations/0001_vault.sqlvaults + vault_entries tables; run automatically at startup via sqlx::migrate!.
  • AppState.vault: Arc<dyn VaultStore> — main() uses Postgres when DATABASE_URL is set, else in-memory with a warning (keeps the server runnable without a DB for its other features).
  • Session→work_session (Option A)Session gains astation_id; resolve_caller binds work_session_id to it (falls back to the verify cache). can_read/can_write enforce in-session vs. past-writer.
  • vault_routes.rs — 5 endpoints (POST/GET /api/vault, GET/POST /api/vault/:id, POST /api/vault/:id/summary) with 401/403/404 handling.
  • atem_id sanitizer fix (separate commit) — percent-decode first, preserve non-ASCII (CJK hostnames), restrict ASCII to [A-Za-z0-9-].
  • docker-compose postgres service + DATABASE_URL; README documents endpoints + env var.

Wire contract (matches the shipped atem CLI)

Authorization: session <session_id>   ?id=<client_id>
POST /api/vault              {summary}                  -> {vault_id}
GET  /api/vault                                         -> [{vault_id, summary}]
GET  /api/vault/<id>         [?since&history=true]      -> [VaultEntry]
POST /api/vault/<id>         {text, entry_id?}          -> {entry_no, version, seq}
POST /api/vault/<id>/summary {text}                     -> {}

Test plan

  • cargo build + cargo build --release succeed (no DB needed at build time)
  • 171 tests pass (was 146)
    • Store (10): create→read, append→override (current vs history), since, writer_list dedup, list authz, multi-append increments, override isolation per entry_no, override-unknown→v1, not-found on every op, cross-vault isolation, summary-in-list, vault_id format
    • Routes (12): create→read, append→override history, since, authz (in-session r/w, out-of-session past-writer read-only → 403 write, stranger → 403 read), 401 missing/ungranted session, 400 missing ?id, 404 missing vault, list isolation by work session, incrementing entry_no over HTTP, set-summary
    • Sanitizer (3): CJK round-trip, unsafe-ASCII strip, empty fallback
  • Live Postgres integration (run with DATABASE_URL, docker compose up) — acceptance test in the spec §Acceptance (the only remaining gap; PgVaultStore is exercised only at runtime)

Design note

The spec's Task 1 suggested fail-fast when DATABASE_URL is unset. I made it an in-memory fallback + warning instead so the relay's RTC/voice/pairing features still run without Postgres; vault durability requires DATABASE_URL. Easy to flip to hard-fail if preferred.

Generated with SMT smt@agora.build

guohai added 3 commits May 29, 2026 12:26
atem now generates ids that may contain non-ASCII characters and
percent-encodes them into the relay URL. The old sanitizer stripped
non-ASCII and never decoded. Now decode first, keep non-ASCII, and
restrict ASCII to [A-Za-z0-9-] (matches atem's identity rule).
Extracted into sanitize_atem_id() with round-trip tests.

🤖 Built with SMT <smt@agora.build>
Implements the relay-server side of the vault per
docs/specs/2026-05-29-vault-relay-server-design.md:

- vault_store.rs: VaultStore trait with an InMemoryVaultStore (tests /
  DB-less runs) and a PgVaultStore (sqlx, runtime queries so no DB is
  needed at build time). Append/override run in a transaction; current
  view = highest version per entry_no, history = all rows by seq,
  ?since filters by seq.
- migrations/0001_vault.sql: vaults + vault_entries tables.
- AppState gains `vault: Arc<dyn VaultStore>`; main() uses Postgres when
  DATABASE_URL is set (runs migrations), else in-memory with a warning.
- Session gains astation_id (Option A); resolve_caller binds
  work_session_id to the session's astation_id (falls back to the
  verify cache). can_read/can_write enforce in-session vs past-writer.
- vault_routes.rs: 5 endpoints (create/list/read/write/set-summary) with
  401/403/404 handling. 6 oneshot handler tests + 4 store tests.
- docker-compose: postgres service + DATABASE_URL; README documents the
  endpoints and env var.

🤖 Built with SMT <smt@agora.build>
Store: multiple appends increment entry_no/seq, override isolation per
entry_no, override of unknown entry starts at v1, not-found on every op,
cross-vault isolation, set_summary reflected in list, vault_id format.
Routes: 404 on missing vault, 400 on missing ?id, 401 on ungranted
session, list isolation by work session, incrementing entry_no over HTTP.

171 tests pass (was 159).

🤖 Built with SMT <smt@agora.build>
@guohai guohai merged commit 1736e94 into main May 30, 2026
3 checks passed
@guohai guohai deleted the feature/vault-relay-server branch May 30, 2026 03:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant