chore(v1.1.7): version bumps + CHANGELOG

- workspace 1.1.6 → 1.1.7
- SDK schema 1.7 → 1.8 (SecretsService, EmailService, TriggerEvent::Email)
- dashboard 0.12.0 → 0.13.0
- CHANGELOG entry: secrets, outbound email, inbound email, retroactive
  dead_letter fix note, realtime-key encryption migration (+ v1.1.8 drop)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-04 22:35:07 +02:00
parent fffcdf6169
commit b35585195b
5 changed files with 117 additions and 12 deletions

View File

@@ -1,5 +1,102 @@
# PiCloud Changelog
## v1.1.7 — Configuration & Email (unreleased)
The operational-config layer: **encrypted per-app secrets**, **outbound
email**, and an **inbound email trigger** — plus the long-missing
**dead-letter handler wiring** and **at-rest encryption of the realtime
signing key**. All at-rest encryption uses a single process master key
(AES-256-GCM); key rotation is deferred to v1.2.
### Added — Encryption infrastructure
- **Process master key** from `PICLOUD_SECRET_KEY` (base64 of exactly 32
bytes). REQUIRED at startup — an unset or malformed key is fatal.
Generate one with `openssl rand -base64 32`. A deterministic in-memory
dev key is used ONLY when `PICLOUD_SECRET_KEY` is unset AND
`PICLOUD_DEV_MODE=true` (with a prominent startup warning); there is no
quiet unencrypted mode.
- **`picloud_shared::crypto`** — `encrypt`/`decrypt` envelope:
`Aes256Gcm`, 96-bit CSPRNG nonce, 128-bit auth tag appended to the
ciphertext (RustCrypto `Aead` layout). Both ciphertext and nonce are
stored.
- **Key rotation is out of scope.** Changing `PICLOUD_SECRET_KEY` between
deploys renders all existing ciphertext undecryptable. v1.2+ adds
key-version columns + a re-encryption pass.
### Added — Encrypted per-app secrets
- **`secrets::{get,set,delete,list}(name)`** SDK — collection-less,
per-app. `set` accepts a String/Map/Array (JSON-encoded then encrypted);
`get` returns the same Rhai type back; missing → `()`. 64 KB plaintext
cap (`PICLOUD_SECRET_MAX_VALUE_BYTES`). `migrations/0023_secrets.sql`.
- **Admin API** `GET/POST/DELETE /api/v1/admin/apps/{id}/secrets` — list
returns names + `updated_at` only, **never values**.
- **Dashboard Secrets tab** — list names + last-modified, create/update
(masked value with a confirm-gated reveal), delete with confirm.
- `Capability::AppSecretsRead`/`Write` (→ `script:read` / `script:write`).
No new Scope variants (seven-scope commitment). Secret writes
deliberately do **not** emit trigger events.
### Added — Outbound email
- **`email::send` / `email::send_html`** SDK over an SMTP relay
(`lettre`). Config from `PICLOUD_SMTP_HOST/PORT/USER/PASSWORD/TLS/
TIMEOUT_SECS`; if HOST/USER/PASSWORD aren't all set the service runs in
**disabled mode** (every send throws `NotConfigured`, warned at
startup). Required `to`/`from`/`subject` + one of `text`/`html`;
RFC 5322-ish address validation; 25 MB per-message cap
(`PICLOUD_EMAIL_MAX_MESSAGE_BYTES`); `reply_to` defaults to `from`.
Per-call connection (pooling deferred to v1.2); per-app `from`
validation / SPF / DKIM are the operator's SMTP-relay concern.
- `Capability::AppEmailSend` (→ `script:write`).
### Added — Inbound email (`email:receive` trigger)
- **Webhook receiver** `POST /api/v1/email-inbound/{app_id}/{trigger_id}`
— a provider (Mailgun / Postmark / SendGrid / SES) POSTs the generic
JSON shape `{from,to[],cc[],subject,text,html,message_id}`; the
receiver verifies the optional HMAC signature, normalizes to
`TriggerEvent::Email`, and enqueues an outbox row. 202 accepted, 401
bad/missing signature, 404 missing/wrong-kind/cross-app, 422 malformed.
Handlers see `ctx.event.email`. `migrations/0024_email_triggers.sql`.
- **Admin** `POST /api/v1/admin/apps/{id}/triggers/email` +
dashboard form (with the webhook URL + expected payload). The HMAC
`inbound_secret` is stored **encrypted** via the master key (deviation
from the original plaintext design — see HANDBACK §7).
- Provider-specific payload unmarshallers + inbound attachments → v1.2.
Native SMTP listener → v1.3+.
### Security/correctness fix (retroactive) — dead_letter handlers
The `dead_letter` trigger kind has been registerable since v1.1.1 but,
due to missing dispatcher wiring (`list_matching_dead_letter` had no
production caller), handlers have **never fired**. Any deploy running
v1.1.1 through v1.1.6 with `dead_letter` triggers configured has had
silently non-functional handlers. v1.1.7 fixes the wiring; existing
`dead_letters` rows remain (no migration needed) but only NEW
dead-letter events (post-v1.1.7) trigger handlers. To process older
rows, use the existing admin replay surface to re-enqueue them.
### Changed — Realtime signing key encrypted at rest (two-phase)
`app_secrets.realtime_signing_key` was stored as 32 plaintext bytes. It
is now encrypted with the master key. `migrations/0025_encrypt_realtime_keys.sql`
adds NULL-able encrypted columns and drops `NOT NULL` on the plaintext
column; a startup task encrypts pre-existing rows; the read path prefers
the encrypted columns and falls back to plaintext during the compat
window. **v1.1.8 will drop the plaintext `realtime_signing_key`
column** — operators should upgrade through v1.1.7 (which performs the
encryption) before v1.1.8.
### Notes
- **New deps:** `aes-gcm` (RustCrypto AEAD), `lettre` (SMTP).
- **New env vars:** `PICLOUD_SECRET_KEY` (required), `PICLOUD_DEV_MODE`,
`PICLOUD_SECRET_MAX_VALUE_BYTES`, `PICLOUD_SMTP_HOST/PORT/USER/PASSWORD/
TLS/TIMEOUT_SECS`, `PICLOUD_EMAIL_MAX_MESSAGE_BYTES`.
- **SDK schema** 1.7 → 1.8; **dashboard** 0.12.0 → 0.13.0.
## v1.1.6 — Realtime Channels & Client Library (unreleased)
The first **external realtime surface** and the first **frontend