Workspace package version 1.1.1 -> 1.1.2; dashboard 0.7.0 -> 0.8.0 (workspace alignment, no docs-specific UI yet); SDK_VERSION 1.2 -> 1.3 for the docs:: surface + ctx.event.docs additions. CHANGELOG entry documents the docs store, the query DSL subset, the docs:* trigger kind, the prev_data change-data-capture surface, and the new AppDocsRead/AppDocsWrite capabilities. Includes a downgrade caveat (v1.1.2 -> v1.1.1 with queued docs outbox rows would fail TriggerEvent deserialization) and known-limitations notes for the text-lex comparison gotcha and the concurrent-update prev_data race. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.5 KiB
PiCloud Changelog
v1.1.2 — Documents (unreleased)
docs::* SDK — schemaless JSONB document storage with a first-cut
query DSL — plus docs:* triggers as the second concrete kind on the
v1.1.1 triggers framework. Sets the precedent for the v1.2 query DSL
expansion and dead_letters::list.
Added
- Docs store —
docstable keyed(app_id, collection, id)with JSONB values and a GIN-on-jsonb_path_opsindex. Rhai SDK exposes the handle pattern:docs::collection(name).{create,get,find,find_one,update,delete,list}. Cursor-style pagination onlist. Cross-app isolation enforced viacx.app_id(never script-passed). Document envelope shape returned by reads:#{ id, data: #{...}, created_at, updated_at }— explicit metadata + user-data separation (sets precedent for v1.2dead_letters::list). - Query DSL (v1.1.2 subset) — implicit equality at top level
(
#{ tier: "gold" }), operator-object form (#{ created_at: #{ "$gt": "..." } }), dotted field paths up to 5 levels ("user.email"), and operators$eq/$ne/$gt/$gte/$lt/$lte/$in. Filter modifiers$sort(single field) and$limit. Unsupported operators ($or,$regex, etc.) reject with a clear v1.2-pointer error. - Docs triggers (
docs:*) —docs_trigger_detailstable mirrorskv_trigger_details. Admin endpointPOST /api/v1/admin/apps/{id}/triggers/docsaccepts the same DTO shape as the KV endpoint withopsofDocsEventOp(create / update / delete). Dispatcher routesOutboxSourceKind::Docsthrough the same generic path as KV + dead-letter. ctx.event.docs.prev_data— change-data-capture surface for docs trigger handlers.prev_datacarries the document state prior to the mutation (Nonefor create), letting handlers see what changed. The repo reads the old row in the same SQL statement as the write so the trigger event has the prior value.Capability::AppDocsRead(AppId)+AppDocsWrite(AppId)— granted to Viewer / Editor respectively in the per-app role table. Same trust shape as KV'sAppKvRead/AppKvWrite.
Changed
- Workspace version:
1.1.1→1.1.2. - Rhai SDK version:
1.2→1.3(additive — every v1.2 script still runs unchanged; new surfaces:docs::collection(name).{...},ctx.event.docsfor triggered handlers). - Dashboard version:
0.7.0→0.8.0. Workspace alignment; no docs-specific UI in v1.1.2 (the dashboard's Rhai-mode hints don't list KV completions either — focused UX pass is a separate task). Servicesbundle — grows adocs: Arc<dyn DocsService>field. Constructor signature becomesServices::new(kv, docs, dead_letters, events).- Scope mapping: API keys with
script:readscope can calldocs::find/get/list;script:writecan calldocs::create/update/delete. Same trust shape as KV — honors the seven-scope commitment from v1.1.0.
Migrations
0013_docs.sql—docstable + per-(app_id, collection)index + GIN-on-jsonb_path_opsindex.0014_docs_triggers.sql— extendstriggers.kindandoutbox.source_kindCHECK constraints to include'docs'; addsdocs_trigger_detailstable.
Downgrade caveats
Rolling a deployment back from v1.1.2 → v1.1.1 with docs-source
outbox rows still queued will cause the v1.1.1 dispatcher to fail
deserialising TriggerEvent::Docs (#[serde(tag = "source")]
rejects unknown variants). Drain or delete
outbox WHERE source_kind = 'docs' before downgrading. Trunk-only
deployments don't hit this.
Known limitations
- Text-lex comparison for
$gt/$gte/$lt/$lteis incorrect for unpadded numbers crossing digit-count boundaries ('10' < '9'is TRUE under any text collation). Workaround: zero-pad numeric strings. v1.2's advanced query expansion adds numeric-aware operators. - Concurrent
update()s on the same doc may both emit the pre-updateprev_data(last-writer-wins). Inherited from KV'ssetpattern; documented for forensic-trace use cases. - v1.1.2 has no partial-update DSL — scripts that want partial
update do
get + modify + update. Planned for v1.2.
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_entriestable 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 viacx.app_id(never script-passed). - Triggers framework (Layout E) — parent
triggerstable + 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 sharedExecutionGate. 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 forLISTEN/NOTIFYbehind the sameInboxResolvertrait. dispatch_mode: asyncon routes —POSTto a route withdispatch_mode = 'async'returns202 Acceptedimmediately; the script runs via the dispatcher (with retries / dead-letter).- Dead-letter handling — separate
dead_letterstable 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 asresolution = '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-letterswith Replay + Mark resolved per-row actions and expandable payload detail. abandoned_executionstable — 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) andabandoned_executions(7 days), both withFOR UPDATE SKIP LOCKEDfor cluster-mode safety. - Env-overridable trigger config —
TriggerConfig::from_envreadsPICLOUD_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.eventfor triggered handlers). - Dashboard version:
0.6.0→0.7.0for the dead-letters UI. Servicesbundle — replaces v1.1.0's no-argServices::new()with explicitServices::new(kv, dead_letters, events). Tests useServices::default()for an all-noop bundle.SdkCallCxgrowsis_dead_letter_handler: boolandevent: Option<TriggerEvent>fields.ExecRequestmirrors the newSdkCallCxfields and growseventfor 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_entriestable + index0008_triggers.sql—triggers+kv_trigger_details+dead_letter_trigger_details0009_outbox.sql— universaloutboxtable + due-row partial index0010_dead_letters.sql—dead_letterstable + unresolved partial index + GC index0011_abandoned_executions.sql— forensic table + GC index0012_routes_dispatch_mode.sql—routes.dispatch_modecolumn
v1.1.0 — Foundation & Standard Library
See docs/v1.1.x-design-notes.md §7 for the full v1.1.x roadmap.