feat(v1.1.6): realtime channels + v1.1.5 follow-ups + version bumps

Server-side realtime SSE on per-app pub/sub topics, plus the three
v1.1.5 follow-ups and the version bumps.

Realtime:
- topics registry (0021) + admin endpoints + Capability::AppTopicManage
  (-> app:admin; no new scope).
- GET /realtime/topics/{topic} SSE endpoint (orchestrator-core data
  plane): Host -> app, RealtimeAuthority gate (404 missing/internal,
  401 bad/absent token), broadcast::Receiver stream + heartbeat.
- RealtimeBroadcaster / RealtimeEvent / RealtimeAuthority traits
  (picloud-shared); InProcessBroadcaster + GC (orchestrator-core);
  DB-backed RealtimeAuthorityImpl (manager-core). Publish path fans out
  to in-process subscribers after the durable outbox commit (best-effort,
  panic-isolated).
- HMAC subscriber tokens (subscriber_token.rs) + app_secrets table (0022)
  + pubsub::subscriber_token SDK (schema 1.6 -> 1.7). TTL clamp + env
  overrides.
- Dashboard Topics tab (register/list/edit/delete, prominent external
  badge, flip confirmation).

v1.1.5 follow-ups:
- Empty blobs accepted (NewFile/FileUpdate::validate) + round-trip test.
- Orphan *.tmp.* sweeper (spawn_files_orphan_sweep).
- Dispatcher e2e tests, one per trigger kind (DATABASE_URL-gated).

Versions: workspace 1.1.6, SDK 1.7, dashboard 0.12.0. Schema-snapshot
golden re-blessed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-04 20:18:50 +02:00
parent d064681c49
commit fcbcc576a2
35 changed files with 4333 additions and 63 deletions

View File

@@ -1,5 +1,91 @@
# PiCloud Changelog
## v1.1.6 — Realtime Channels & Client Library (unreleased)
The first **external realtime surface** and the first **frontend
library**, co-shipped per the §5/§6 design-notes decisions. Browser
clients can subscribe over SSE to per-app pub/sub topics that have been
explicitly externalized; everything else stays internal-only. The
`@picloud/client` TypeScript package wraps typed HTTP, SSE, auth, and
React/Svelte hooks. Plus three v1.1.5 follow-ups.
### Added — Realtime
- **`topics` registry** (`migrations/0021_topics.sql`) — pub/sub topics
are internal-only by default; a `topics` row with
`external_subscribable = true` opts one into external SSE subscription.
`auth_mode` is `'public'` or `'token'`.
- **Topic admin endpoints** under `/api/v1/admin/apps/{id}/topics`
`POST` (register), `GET` (list), `PATCH /{name}` (flip
external/auth_mode — its own audited surface), `DELETE /{name}`
(unregister + disconnect live subscribers). Gated by the new
`Capability::AppTopicManage``app:admin` scope (no new scope; the
seven-scope commitment holds).
- **SSE endpoint `GET /realtime/topics/{topic}`** — data-plane surface
(deliberately not under `/api/`). Resolves `Host` → app, authorizes
via the `RealtimeAuthority` (404 for missing/internal topics, 401 for
bad/absent tokens), then streams `data: {topic,message,published_at}`
events with a configurable heartbeat (`PICLOUD_REALTIME_HEARTBEAT_SEC`,
default 30). Token via `Authorization: Bearer` or `?token=`.
- **`RealtimeBroadcaster` + `RealtimeEvent` + `RealtimeAuthority`**
traits (`picloud-shared`); in-process `InProcessBroadcaster`
(`tokio::sync::broadcast`, per-channel capacity
`PICLOUD_REALTIME_BROADCAST_CAPACITY` default 64, periodic empty-channel
GC) and the DB-backed `RealtimeAuthorityImpl` (orchestrator-core /
manager-core respectively). The publish path now also fans out to
in-process SSE subscribers, best-effort, after the durable outbox
fan-out commits — a broadcast failure never fails the publish.
- **`pubsub::subscriber_token(topics, ttl)`** Rhai SDK (SDK schema
1.6 → 1.7) — mints an HMAC-SHA256 subscriber token (URL-safe
`payload.signature`) scoped to externally-subscribable topics.
Requires an authenticated principal + the pub/sub publish capability.
TTL clamped to `[10s, 24h]` (default 1h), env-overridable via
`PICLOUD_SUBSCRIBER_TOKEN_TTL_{MIN,MAX,DEFAULT}_SEC`. Per-app signing
keys persist in the new `app_secrets` table
(`migrations/0022_app_secrets.sql`), created lazily on first mint. No
per-token revocation (rotation invalidates wholesale; short TTL is the
safety mechanism).
- **Dashboard Topics tab** — register/list/edit/delete topics with a
prominent external/internal badge, auth-mode radio (conditional on
external), and a confirmation when flipping a topic external.
### Added — `@picloud/client` (TypeScript, v1.0.0)
- New top-level package `clients/typescript/` (tsup dual ESM+CJS +
`.d.ts`, vitest). Typed HTTP via `endpoint<Req,Res>(path).get()/.post()`
with auth-token injection and structured errors; SSE `subscribe(topic,
cb, {token, onTokenExpired})` with exponential-backoff reconnect,
401 token-refresh, and `Last-Event-ID` resume; `auth.login/logout/token`
over dev-defined endpoints; React (`useTopic`/`useEndpoint` +
`PicloudProvider`) and Svelte (`topicStore`/`endpointStore`) subpath
exports. Optional zod/valibot runtime validation via a `{ parse }`
adapter (no hard dep). Hybrid model: no direct service access from the
browser.
### Changed / Fixed — v1.1.5 follow-ups
- **Empty blobs accepted** — `NewFile::validate` / `FileUpdate::validate`
no longer reject zero-length `data`; empty files are a valid stored
state (sentinels, placeholders). Non-breaking.
- **Orphan `*.tmp.*` sweeper** — a startup tokio task
(`spawn_files_orphan_sweep`) walks the files root every
`PICLOUD_FILES_ORPHAN_SWEEP_INTERVAL_SEC` (default 6h) and unlinks temp
blobs older than `PICLOUD_FILES_ORPHAN_TMP_TTL_SEC` (default 1h). No DB
cross-check (that full reconciler is v1.3+).
- **Dispatcher end-to-end tests** — `crates/picloud/tests/dispatcher_e2e.rs`,
one per trigger kind (kv/docs/cron/files/pubsub/dead_letter),
DATABASE_URL-gated (skip cleanly when unset).
### Notes
- New deps: `hmac` (token signing, picloud-shared), `tokio-stream` (SSE
body stream, orchestrator-core).
- New env vars: `PICLOUD_REALTIME_HEARTBEAT_SEC`,
`PICLOUD_REALTIME_BROADCAST_CAPACITY`,
`PICLOUD_SUBSCRIBER_TOKEN_TTL_{MIN,MAX,DEFAULT}_SEC`,
`PICLOUD_FILES_ORPHAN_SWEEP_INTERVAL_SEC`,
`PICLOUD_FILES_ORPHAN_TMP_TTL_SEC`.
## v1.1.5 — Files & Pub/Sub (unreleased)
Two stateful services + two trigger kinds. **`files::*`** is