diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..21d96bf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,88 @@ +# PiCloud Changelog + +## v1.1.1 — Storage & Events (unreleased) + +The triggers framework — KV store + universal outbox + dispatcher + +NATS-style sync HTTP + per-route async dispatch + dead-letter +handling + dashboard surface. Every subsequent v1.1.x service module +(docs, files, pubsub, …) hangs off the dispatcher built here. + +### Added + +- **KV store** — `kv_entries` table keyed `(app_id, collection, key)` + with JSONB values. Rhai SDK exposes the handle pattern: + `kv::collection(name).{get,set,has,delete,list}`. Cursor-style + pagination with opaque base64 cursors. Cross-app isolation + enforced via `cx.app_id` (never script-passed). +- **Triggers framework (Layout E)** — parent `triggers` table + + per-kind detail tables (`kv_trigger_details`, + `dead_letter_trigger_details`). Trigger CRUD admin endpoints + (`/api/v1/admin/apps/{id}/triggers/{kv,dead_letter}`) + + `Capability::AppManageTriggers(AppId)`. +- **Universal outbox + dispatcher** — single tokio task that polls + the outbox via `FOR UPDATE SKIP LOCKED`, routes due rows to the + executor through the shared `ExecutionGate`. Retry with + exponential backoff + ±jitter; on exhaustion, dead-letter. +- **NATS-style sync HTTP via outbox** — `InboxRegistry` (in-process + oneshot map) lets the orchestrator await dispatcher delivery on + every sync HTTP request. Cluster mode (v1.3+) swaps this for + `LISTEN/NOTIFY` behind the same `InboxResolver` trait. +- **`dispatch_mode: async` on routes** — `POST` to a route with + `dispatch_mode = 'async'` returns `202 Accepted` immediately; + the script runs via the dispatcher (with retries / dead-letter). +- **Dead-letter handling** — separate `dead_letters` table per + design notes §4. `dead_letters::{replay,resolve}` Rhai SDK + + admin endpoints + `Capability::AppDeadLetterManage(AppId)`. + Recursion-stop rule: dead-letter handler failures annotate the + original row as `resolution = 'handler_failed'` and never produce + a new dead-letter or retry. +- **Dashboard surface for dead letters** — unresolved-count red + badge on the apps list + per-app page; per-app dead-letters list + view at `/admin/apps/{slug}/dead-letters` with Replay + Mark + resolved per-row actions and expandable payload detail. +- **`abandoned_executions` table** — forensic row written by the + dispatcher when it tries to resolve an inbox the orchestrator + already abandoned (timed out). Counter metric path reserved. +- **Trigger-depth limit** — `cx.trigger_depth > max_trigger_depth` + (default 8) skips execution + logs; does NOT dead-letter + (depth-exceeded means "you built a loop"). +- **GC sweepers** — weekly retention sweeps for `dead_letters` + (30 days) and `abandoned_executions` (7 days), both with + `FOR UPDATE SKIP LOCKED` for cluster-mode safety. +- **Env-overridable trigger config** — `TriggerConfig::from_env` + reads `PICLOUD_MAX_TRIGGER_DEPTH`, `PICLOUD_TRIGGER_RETRY_*`, + `PICLOUD_DEAD_LETTER_RETENTION_DAYS`, + `PICLOUD_ABANDONED_EXECUTIONS_RETENTION_DAYS`. + +### Changed + +- **Workspace version**: `1.1.0` → `1.1.1`. +- **Rhai SDK version**: `1.1` → `1.2` (additive — every v1.1 script + still runs unchanged; new surfaces: `kv::*`, `dead_letters::*`, + `ctx.event` for triggered handlers). +- **Dashboard version**: `0.6.0` → `0.7.0` for the dead-letters UI. +- **`Services` bundle** — replaces v1.1.0's no-arg `Services::new()` + with explicit `Services::new(kv, dead_letters, events)`. Tests + use `Services::default()` for an all-noop bundle. +- **`SdkCallCx`** grows `is_dead_letter_handler: bool` and + `event: Option` fields. +- **`ExecRequest`** mirrors the new `SdkCallCx` fields and grows + `event` for serializable trigger payload transport. +- **Routes table** grows `dispatch_mode TEXT NOT NULL DEFAULT 'sync'` + (CHECK in {sync, async}). +- **Schema version**: 6 → 12 (migrations 0007 through 0012). + +### Migrations + +- `0007_kv.sql` — `kv_entries` table + index +- `0008_triggers.sql` — `triggers` + `kv_trigger_details` + + `dead_letter_trigger_details` +- `0009_outbox.sql` — universal `outbox` table + due-row partial index +- `0010_dead_letters.sql` — `dead_letters` table + unresolved partial + index + GC index +- `0011_abandoned_executions.sql` — forensic table + GC index +- `0012_routes_dispatch_mode.sql` — `routes.dispatch_mode` column + +## v1.1.0 — Foundation & Standard Library + +See `docs/v1.1.x-design-notes.md` §7 for the full v1.1.x roadmap. diff --git a/Cargo.lock b/Cargo.lock index 7da1d69..657ec08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1505,7 +1505,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "picloud" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "async-trait", @@ -1531,7 +1531,7 @@ dependencies = [ [[package]] name = "picloud-cli" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "assert_cmd", @@ -1552,7 +1552,7 @@ dependencies = [ [[package]] name = "picloud-executor" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "picloud-executor-core", @@ -1564,7 +1564,7 @@ dependencies = [ [[package]] name = "picloud-executor-core" -version = "1.1.0" +version = "1.1.1" dependencies = [ "async-trait", "base64", @@ -1585,7 +1585,7 @@ dependencies = [ [[package]] name = "picloud-manager" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "picloud-manager-core", @@ -1597,7 +1597,7 @@ dependencies = [ [[package]] name = "picloud-manager-core" -version = "1.1.0" +version = "1.1.1" dependencies = [ "argon2", "async-trait", @@ -1622,7 +1622,7 @@ dependencies = [ [[package]] name = "picloud-orchestrator" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "picloud-orchestrator-core", @@ -1634,7 +1634,7 @@ dependencies = [ [[package]] name = "picloud-orchestrator-core" -version = "1.1.0" +version = "1.1.1" dependencies = [ "async-trait", "axum", @@ -1653,7 +1653,7 @@ dependencies = [ [[package]] name = "picloud-shared" -version = "1.1.0" +version = "1.1.1" dependencies = [ "async-trait", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 3e97692..b23884c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ members = [ ] [workspace.package] -version = "1.1.0" +version = "1.1.1" edition = "2021" rust-version = "1.92" license = "MIT OR Apache-2.0" diff --git a/crates/shared/src/version.rs b/crates/shared/src/version.rs index 8691fed..e412ac4 100644 --- a/crates/shared/src/version.rs +++ b/crates/shared/src/version.rs @@ -19,7 +19,10 @@ pub const PRODUCT_VERSION: &str = env!("CARGO_PKG_VERSION"); /// /// 1.1 additions: `ctx.request.params`, `ctx.request.query`, /// `ctx.request.rest`. -pub const SDK_VERSION: &str = "1.1"; +/// +/// 1.2 additions (v1.1.1): `kv::collection(name).{get,set,has,delete,list}`, +/// `dead_letters::{replay,resolve}`, `ctx.event` for triggered handlers. +pub const SDK_VERSION: &str = "1.2"; /// HTTP API major version. Appears in URL paths as `/api/v{N}/...`. /// Bump (new integer + new URL prefix) when the request/response diff --git a/dashboard/package.json b/dashboard/package.json index 3845826..8c578e6 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -1,6 +1,6 @@ { "name": "picloud-dashboard", - "version": "0.6.0", + "version": "0.7.0", "private": true, "type": "module", "scripts": {