Skip to content

Persist image assets and add Craft service discovery - #3

Merged
Artsen merged 1 commit into
mainfrom
feat/persistent-library-service-contract
Jul 26, 2026
Merged

Persist image assets and add Craft service discovery#3
Artsen merged 1 commit into
mainfrom
feat/persistent-library-service-contract

Conversation

@Artsen

@Artsen Artsen commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • replace process-local image stores with a durable managed image repository
  • persist stable source and explicit-output IDs in a strict schema-version-1 manifest
  • use atomic complete-manifest replacement, last-known-good backup, startup reconciliation, leases, transactional deletion rollback, and explicit cleanup
  • add source/output library listing, metadata, deletion, storage status, and cleanup APIs
  • add static Craft discovery plus runtime metadata, health, readiness, and capability reporting
  • preserve Recipe v1, deterministic preview/export behavior, the current Flask/JavaScript interface, port 5000, and the legacy video workflow

Storage contract

The configurable managed data root contains:

data/
  manifest.json
  manifest.json.bak
  sources/images/
  outputs/images/
  temporary/
  recovery/

Manifest schema version 1 stores strict immutable source records and output records. Source records retain display metadata, decoded format/MIME/dimensions, a stable seed, UTC creation time, and a server-ID-derived relative path. Output records retain the source ID, display filename, dimensions, MIME type, UTC creation time, and the exact strict Recipe v1 snapshot and seed used for export.

Only normalized managed relative POSIX paths are persisted. Absolute paths, traversal, unknown fields, unsupported kinds, inconsistent IDs, non-UTC dates, mismatched format/MIME/extension metadata, invalid dimensions, and invalid embedded recipes are rejected. Public APIs never return managed or absolute paths.

Every mutation validates and serializes the complete next manifest, writes and flushes a same-filesystem temporary file, uses fsync where supported, preserves a last-known-good backup, and atomically replaces the primary. In-memory state advances only after persistence succeeds. Newly installed files are removed if manifest persistence fails.

At startup, a missing manifest initializes cleanly. A corrupt/missing primary with a valid backup is restored and readiness is degraded for that process. If both primary and backup are invalid, neither is overwritten: health remains available, readiness returns 503/not-ready, and persistent asset operations return controlled 503 responses.

Reconciliation removes records for missing managed files while leaving existing output files usable if their source is missing. Orphans are reported and never deleted automatically. Recovery reports remain private in the recovery area.

Output deletion removes one record/file. Source deletion conflicts while outputs reference it unless cascade=true applies one logical manifest mutation. Active leases return 409. Persistent images are never age-expired. Cleanup defaults to dry-run; temporary cleanup is age-based and orphan deletion requires explicit opt-in plus a minimum age.

Library APIs

  • GET /api/image-sources
  • GET /api/image-sources/{source_id}
  • DELETE /api/image-sources/{source_id}?cascade=true
  • GET /api/image-outputs
  • GET /api/image-outputs/{output_id}/metadata
  • DELETE /api/image-outputs/{output_id}
  • GET /api/storage
  • POST /api/storage/cleanup

Existing upload, original, preview, export, inline-output, and download URLs remain compatible.

Craft discovery

  • GET /app-manifest.json provides static GlitchCraft identity, version 0.1.0, truthful http://127.0.0.1:5000 defaults, endpoint links, capability slugs, and a provisional SVG mark
  • GET /metadata provides request-derived runtime addresses, schema versions, supported formats, effect types, library counts, and redacted links
  • GET /health provides lightweight process/route liveness only
  • GET /ready reports ready, degraded, or not_ready with redacted storage, image, registry, reconciliation, FFmpeg, and optional FFprobe checks
  • GET /api/capabilities distinguishes implemented, currently available, and optional capabilities
  • GET /api/storage reports redacted counts, bytes, writability, free space when available, and reconciliation state

The service remains unauthenticated and loopback-only. No broad CORS, analytics, cloud processing, external media service, executable paths, disk paths, commands, environment values, or tracebacks are exposed.

Compatibility

  • Recipe schema version 1, effect IDs/types/validation, seed derivation, canonical RGB processing, and ordered execution are unchanged
  • upload-once, inline original, debounced deterministic preview, stale-response protection, explicit export, inline result, and explicit download are preserved
  • matching recipe/seed preview and export pixels remain identical
  • automatic preview creates no manifest record or permanent file
  • the current Flask/JavaScript interface is unchanged visually
  • Flask still serves UI and API together on port 5000
  • video upload, still preview, in-memory tasks, full processing, progress, range streaming, download, and failure reporting are preserved

Validation

  • python check.py: 169 tests passed
  • statement coverage: 96.96% (1497/1544)
  • branch coverage: 90.70% (234/258)
  • Ruff format and lint passed
  • strict Mypy passed across 28 source files
  • Playwright: 5 Chromium workflows passed using an isolated test data root
  • responsive coverage: 320, 768, 1024, and 1440 px
  • axe: zero serious or critical violations
  • repository consistency and git diff --check passed
  • live restart smoke: original, preview, new export, existing output inline, existing output download, library listing, and persisted recipe metadata all worked after process restart
  • live recovery smoke: corrupt primary recovered from valid backup, readiness was degraded, existing output remained available, and an orphan was reported without deletion
  • live dual-corruption smoke: health stayed ok, readiness returned HTTP 503/not_ready, and both invalid files remained byte-for-byte unchanged
  • live image smoke: preview and explicit export bytes were identical; output deletion, source conflict, cascade deletion, and cleanup dry-run passed
  • live video smoke: preview seed 9753, full task completion/progress 100, HTTP 206 range streaming, HTTP 200 download, and corrupt-input failed state with no result passed
  • GitHub Actions: quality passed in 36 seconds; browser passed in 55 seconds

Known limitations

  • persistent storage currently covers images only
  • video sources, jobs, previews, and outputs remain temporary
  • video task state remains in memory and is not restart-safe
  • audio handling is unchanged
  • manifest writes support one application process; multi-process writers and distributed locking are unsupported
  • there is no authentication; the service must remain loopback-bound and should not be exposed to the public internet
  • there is no visible library interface
  • saved recipe management and recipe import/export are deferred
  • the interface remains Flask with transitional JavaScript and legacy jQuery for video
  • the final React workspace and proposed 5175/4200 frontend/API split are deferred
  • persistent video identity, bounded queues, cancellation, and restart recovery are deferred
  • WVO handoff is not implemented
  • the orchestration dashboard and remote control are not implemented

@Artsen
Artsen marked this pull request as ready for review July 26, 2026 04:19
@Artsen
Artsen merged commit a751b13 into main Jul 26, 2026
2 checks passed
@Artsen
Artsen deleted the feat/persistent-library-service-contract branch July 26, 2026 04:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant