Commit Graph

7 Commits

Author SHA1 Message Date
MechaCat02
a7d3dad129 chore(v1.1.7): re-bless schema snapshot for secrets + email migrations
Captures migrations 0023 (secrets), 0024 (email_trigger_details +
widened kind/source CHECKs), 0025 (app_secrets encrypted columns +
NULL-able plaintext). Diff is exactly the new tables/columns/constraints.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 22:39:24 +02:00
MechaCat02
fcbcc576a2 feat(v1.1.6): realtime channels + v1.1.5 follow-ups + version bumps
Server-side realtime SSE on per-app pub/sub topics, plus the three
v1.1.5 follow-ups and the version bumps.

Realtime:
- topics registry (0021) + admin endpoints + Capability::AppTopicManage
  (-> app:admin; no new scope).
- GET /realtime/topics/{topic} SSE endpoint (orchestrator-core data
  plane): Host -> app, RealtimeAuthority gate (404 missing/internal,
  401 bad/absent token), broadcast::Receiver stream + heartbeat.
- RealtimeBroadcaster / RealtimeEvent / RealtimeAuthority traits
  (picloud-shared); InProcessBroadcaster + GC (orchestrator-core);
  DB-backed RealtimeAuthorityImpl (manager-core). Publish path fans out
  to in-process subscribers after the durable outbox commit (best-effort,
  panic-isolated).
- HMAC subscriber tokens (subscriber_token.rs) + app_secrets table (0022)
  + pubsub::subscriber_token SDK (schema 1.6 -> 1.7). TTL clamp + env
  overrides.
- Dashboard Topics tab (register/list/edit/delete, prominent external
  badge, flip confirmation).

v1.1.5 follow-ups:
- Empty blobs accepted (NewFile/FileUpdate::validate) + round-trip test.
- Orphan *.tmp.* sweeper (spawn_files_orphan_sweep).
- Dispatcher e2e tests, one per trigger kind (DATABASE_URL-gated).

Versions: workspace 1.1.6, SDK 1.7, dashboard 0.12.0. Schema-snapshot
golden re-blessed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:18:50 +02:00
MechaCat02
4595db7a7a chore(v1.1.5): version bumps, CI workflow, schema-snapshot un-ignore
- Workspace 1.1.4 → 1.1.5; SDK 1.5 → 1.6; dashboard 0.10.0 → 0.11.0.
- CHANGELOG v1.1.5 entry; CLAUDE.md runtime-config table gains
  PICLOUD_FILES_ROOT + PICLOUD_FILES_MAX_FILE_SIZE_BYTES.
- schema_snapshot test: drop #[ignore] + #[sqlx::test]; run against
  DATABASE_URL when set, skip cleanly when absent. Re-blessed golden
  picks up files / files_trigger_details / pubsub_trigger_details, the
  two widened CHECKs, and the pubsub partial index.
- First CI workflow (.github/workflows/ci.yml): postgres:15 service +
  fmt + clippy + cargo test --workspace; separate dashboard check job.
- Add files/pubsub admin-trigger reject-coverage tests (module +
  cross-app + bad-pattern), mirroring the v1.1.3 regression set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 21:44:12 +02:00
MechaCat02
10b5f655d5 feat(v1.1.4): outbound HTTP SDK + cron triggers
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>
2026-06-03 20:23:18 +02:00
MechaCat02
d435322f9c feat(manager-core): add 0006 users_authz migration
Adds instance_role + reserved email/mfa_secret columns to admin_users,
creates app_members for per-app role grants, and creates api_keys for
bearer-token credentials. Schema snapshot re-blessed.

Reserves invites and service_accounts shapes in a trailing comment
block — both land in their own migrations when those flows ship.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 21:33:40 +02:00
MechaCat02
4c41374db4 feat(manager-core,orchestrator-core): multi-app scoping (Phase 3b)
Apps become the isolation boundary for scripts, routes, domains, and
later data. Doing this now — while the surface is small — avoids
several migrations on populated tables once v1.1 data-plane services
ship.

Schema (migration 0005_apps.sql):
- New tables: apps, app_domains (with shape_key UNIQUE for collision
  detection), app_slug_history (for permanent slug-rename redirects).
- app_id added to scripts, routes, execution_logs (non-null, cascading
  rules per row).
- Script-name uniqueness becomes per-app; the route unique index is
  swapped for an app-scoped version.
- The "default" app is seeded unconditionally with a localhost claim;
  existing scripts/routes backfill into it. Fresh installs additionally
  get the Hello World seed via seed_hello_world_if_fresh after
  migrations run (idempotent — only fires when the default app has no
  scripts).

Orchestrator dispatch is two-phase: AppDomainTable resolves Host →
app_id (most-specific match wins, exact beats wildcard), then the
existing route matcher runs against that app's partitioned slice via
RouteTable. Unknown hosts return 404 at the app layer with a clear
message; /api/v1/execute/{id} still works as the implicit
__internal__ claim, decoupled from any public domain.

Manager API: full CRUD for /api/v1/admin/apps/* and
/api/v1/admin/apps/{id_or_slug}/domains/*, with slug:check + force
takeover semantics implementing the rename-history flow (two-step
check → confirm, never a single endpoint). Script create requires
app_id; list accepts ?app= filter. Route create validates host
against the parent app's claims; conflict detection stays strictly
intra-app.

Dashboard: /admin/apps and /admin/apps/{slug} (overview + scripts +
domains + settings tabs, with slug-history-aware redirects). Root
path redirects to the apps list. Script detail page gains an app
breadcrumb and threads app_id into the route preview.

Deferred per design: per-app admin roles. The require_admin middleware
remains the seam where role checks will slot in later.

Blueprint §11.5 and roadmap updated to reflect what shipped; docs/
versioning.md notes the schema 3 → 5 bump.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 21:03:05 +02:00
MechaCat02
878cbe9439 test(manager-core): schema snapshot guardrail
Boots a fresh Postgres via sqlx::test, applies every migration in
order, dumps the resulting public schema (tables, columns with type
+ nullability + default, indexes, constraints, applied migration
manifest), and compares against a checked-in golden text file.

What this catches:
  * Someone edits a committed migration — schema diverges from the
    snapshot, test fails with a precise diff.
  * Someone adds a migration but forgets to update the snapshot —
    same divergence; test reminds them.
  * Two migrations drift apart in any other way — snapshot is the
    source of truth about the post-replay schema.

Update workflow when adding a migration intentionally:

  BLESS=1 DATABASE_URL=postgres://... \
    cargo test -p picloud-manager-core --test schema_snapshot \
    -- --include-ignored

Review the snapshot diff in the same PR. The header comment makes
it clear the file is not for hand-editing.

  * Snapshot dump uses information_schema.columns + pg_indexes +
    pg_constraint with pg_get_constraintdef. Output is sorted on
    every dimension so cosmetic differences (insertion order,
    etc.) never cause spurious diffs.

  * #[ignore]'d by default for the same reason as the integration
    tests — needs DATABASE_URL pointing at a writable Postgres.

  * Initial expected_schema.txt blessed from the current
    migrations/ contents (3 tables, 9 indexes, 12 constraints).

Wires up enforcement item (4) from docs/versioning.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 22:21:25 +02:00