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:
97
CHANGELOG.md
97
CHANGELOG.md
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user