HTTP (`http::*`):
- `HttpService` trait (picloud-shared) + reqwest-backed `HttpServiceImpl`
(manager-core), wired into the `Services` bundle.
- SSRF deny-list applied to the resolved IP via a custom reqwest
`dns_resolver` (covers every redirect hop + defeats DNS rebinding) plus
a literal-IP check at URL-parse time. Scheme/port restrictions, request
+ response body caps (stream-with-cap), layered timeout. Error reason is
a CIDR category, never the IP. `PICLOUD_HTTP_ALLOW_PRIVATE` dev override
(logs a startup warning).
- Rhai bridge with three-arg split `verb(url, body, opts)` (resolves the
brief's body-vs-opts contradiction; unknown opt keys throw). Body
dispatch by type; response `#{status,headers,body,body_raw}` with JSON
auto-parse; non-2xx does not throw.
- `Capability::AppHttpRequest` → existing `script:write` scope (no new
Scope variant). `SdkCallCx` gains `script_id` (attribution + User-Agent).
Cron triggers (4th trigger kind):
- Migration 0017 widens the kind/source_kind CHECKs and adds
`cron_trigger_details`. `cron`/`chrono-tz` parse + validate 6-field
schedules and IANA timezones.
- `spawn_cron_scheduler` polls due triggers and enqueues to the universal
outbox; the dispatcher delivers them (one-line match-arm extension).
Catch-up fires exactly once per trigger per tick, not once per missed
window. `ctx.event.cron` for handlers.
- `POST /api/v1/admin/apps/{id}/triggers/cron` reuses the v1.1.3
cross-app + kind!=module target check.
- Dashboard: admin-gated Triggers tab (cron create form + list).
Follow-ups: redact module backend errors at the resolver boundary (log
original at error level); pin `rhai = "=1.24"`; CHANGELOG incl. retroactive
v1.1.3 cross-app-trigger security note. Version bumps: workspace 1.1.4,
SDK 1.5, dashboard 0.10.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
44 lines
2.2 KiB
SQL
44 lines
2.2 KiB
SQL
-- v1.1.4: Extend the triggers framework to recognise `cron` as the
|
|
-- fourth concrete kind (after `kv` v1.1.1, `dead_letter` v1.1.1, `docs`
|
|
-- v1.1.2). Mirrors the 0014 docs extension: two CHECK constraints widen
|
|
-- (strictly gaining `'cron'`), one new detail table.
|
|
--
|
|
-- Cron rows route through the SAME generic dispatcher path as kv/docs/
|
|
-- dead_letter (single match-arm extension on the Rust side). The only
|
|
-- new machinery is a scheduler task that enqueues due cron triggers
|
|
-- into the outbox; dispatch itself is unchanged.
|
|
|
|
-- Extend triggers.kind to include 'cron'. No existing row carries a
|
|
-- value outside the widened set, so the drop+add is safe.
|
|
ALTER TABLE triggers DROP CONSTRAINT triggers_kind_check;
|
|
ALTER TABLE triggers ADD CONSTRAINT triggers_kind_check
|
|
CHECK (kind IN ('kv', 'dead_letter', 'docs', 'cron'));
|
|
|
|
-- Extend outbox.source_kind to include 'cron'. v1.1.x's existing
|
|
-- source_kinds ('http', 'kv', 'dead_letter', 'docs') stay.
|
|
ALTER TABLE outbox DROP CONSTRAINT outbox_source_kind_check;
|
|
ALTER TABLE outbox ADD CONSTRAINT outbox_source_kind_check
|
|
CHECK (source_kind IN ('http', 'kv', 'dead_letter', 'docs', 'cron'));
|
|
|
|
-- One row per cron trigger.
|
|
-- schedule — 6-field cron expression (with seconds), validated
|
|
-- at insert time by the `cron` crate.
|
|
-- timezone — IANA tz name (e.g. "America/Los_Angeles"), validated
|
|
-- via chrono-tz. Required so schedules like "every
|
|
-- weekday at 9am" are unambiguous. Defaults to UTC.
|
|
-- last_fired_at — set transactionally with each enqueue. NULL until
|
|
-- the trigger first fires. The scheduler computes the
|
|
-- next fire time in-process from
|
|
-- (schedule, timezone, last_fired_at); there is no
|
|
-- stored next_fire column (kept stateless on purpose).
|
|
CREATE TABLE cron_trigger_details (
|
|
trigger_id UUID PRIMARY KEY REFERENCES triggers(id) ON DELETE CASCADE,
|
|
schedule TEXT NOT NULL,
|
|
timezone TEXT NOT NULL DEFAULT 'UTC',
|
|
last_fired_at TIMESTAMPTZ
|
|
);
|
|
|
|
-- Hot lookup for the scheduler: "all enabled cron triggers due now"
|
|
-- scans by last_fired_at.
|
|
CREATE INDEX idx_cron_triggers_due ON cron_trigger_details (last_fired_at);
|