feat(v1.1.5): files SDK + files:* triggers

Filesystem-backed blob storage as the fifth concrete trigger kind.

- `files::collection(c).{create,head,get,update,delete,list}` Rhai SDK
  (blob in/out; metadata maps; missing-field throws naming the field).
- `FilesService` trait in picloud-shared; `FsFilesRepo` (atomic
  write: temp→fsync→rename→fsync-dir→DB; single-pass SHA-256;
  checksum-verified reads → Corrupted) + `FilesServiceImpl` in
  manager-core. Metadata in Postgres (0018), bytes on disk under
  PICLOUD_FILES_ROOT with 0o700 shard dirs.
- `files:*` trigger kind via the Layout-E pattern (0019: widen both
  CHECKs + files_trigger_details), TriggerEvent::Files (metadata only,
  no bytes), emit_files fan-out, dispatcher arm, admin endpoint
  POST /triggers/files (reuses validate_trigger_target).
- AppFilesRead/AppFilesWrite capabilities → script:read/script:write
  (seven-scope commitment held). AppPubsubPublish reserved for v1.1.6.
- Admin files API (list + delete) + dashboard Files view per app.

Cross-app isolation keyed on cx.app_id at every layer. ~45 new tests
(service in-memory, fs tempdir, bridge integration). No DB required
for the suite. publish_ephemeral and the orphan sweep stay deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-03 21:18:17 +02:00
parent 03d03ea6e7
commit 6e132b6ee0
29 changed files with 3599 additions and 31 deletions

View File

@@ -0,0 +1,25 @@
-- v1.1.5: filesystem-backed blob storage. The row holds metadata +
-- the SHA-256 checksum; the blob bytes live on disk at
-- <PICLOUD_FILES_ROOT>/files/<app_id>/<collection>/<id[0:2]>/<id>
-- (never in Postgres). Identity tuple is (app_id, collection, id) per
-- docs/sdk-shape.md, matching KV/docs collection scoping.
--
-- The checksum is computed in a single pass during the atomic write and
-- re-verified on read (FilesError::Corrupted on mismatch). Per-app
-- quotas are deferred to v1.2; only the per-file size cap is enforced
-- (in the service, not the schema).
CREATE TABLE files (
app_id UUID NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
collection TEXT NOT NULL,
id UUID NOT NULL,
name TEXT NOT NULL,
content_type TEXT NOT NULL,
size_bytes BIGINT NOT NULL,
checksum_sha256 TEXT NOT NULL, -- hex, 64 chars, lowercase
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (app_id, collection, id)
);
-- List + cursor pagination scans by (app_id, collection).
CREATE INDEX idx_files_app_collection ON files (app_id, collection);