Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ htmlcov/
node_modules/
playwright-report/
test-results/
data/
build/
dist/

Expand Down
29 changes: 20 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ scan, distortion, and signal treatments for images and video.
The current interface is a transitional Flask/jQuery workspace. Images upload
once, keep their original visible, update a deterministic processed preview as
controls change, and create a downloadable PNG only when explicitly exported.
Video remains on the legacy preview and background-processing path.
Image sources and explicit exports now have persistent local identity. Video
remains on the temporary legacy preview and background-processing path.

## Requirements

Expand All @@ -34,7 +35,10 @@ python app.py
```

Open <http://127.0.0.1:5000>. The default port and existing routes are preserved.
Uploaded and generated files are temporary and ignored by Git.
The managed image library defaults to `data/` and is ignored by Git. Configure
`DATA_ROOT` to move it to another local user-data location. The service has no
authentication and is intended only for loopback use; do not expose it directly
to the public internet.

### Image workflow

Expand All @@ -45,8 +49,12 @@ Uploaded and generated files are temporary and ignored by Git.
- Choose **Export PNG**, then explicitly download the managed result.

Automatic previews return temporary PNG bytes and do not create output files.
Sources and exported outputs use opaque, in-memory identity and expire from
temporary storage. Their IDs do not survive an application restart.
Sources and explicit exports use opaque persistent identity and survive restarts
when the same data root is reused. They remain until explicitly deleted; they
are not deleted automatically by age.

Machine-readable local discovery is available at `/app-manifest.json`,
`/metadata`, `/health`, `/ready`, `/api/capabilities`, and `/api/storage`.

## Validate

Expand All @@ -64,18 +72,21 @@ statement and branch coverage, and repository consistency checks.
- [Architecture](docs/architecture.md)
- [Effect engine](docs/effect-engine.md)
- [Image workflow and API](docs/image-workflow.md)
- [Persistent storage](docs/storage.md)
- [Recovery](docs/recovery.md)
- [Craft service contract](docs/service-contract.md)
- [Testing](docs/testing.md)
- [Product direction](docs/product-direction.md)

The internal recipe contract supports a schema version, root seed, ordered effect
instances, stable IDs, enabled states, and strict effect-specific parameters.
User-facing recipe import/export and seed controls are intentionally deferred.

Task, image-source, and output identity remain in memory with no cancellation,
bounded queue, library, or restart recovery. Storage remains temporary, video
and audio behavior are unchanged, and true geometric distortion/datamoshing are
not implemented. jQuery remains the one CDN dependency for the legacy video
path; Bootstrap and Google Fonts are no longer used.
Image source and explicit output identity are persistent. Video task state,
sources, and outputs remain temporary and process-local with no cancellation,
bounded queue, or restart recovery. Video and audio behavior are unchanged, and
true geometric distortion/datamoshing are not implemented. jQuery remains the
one CDN dependency for the legacy video path.

## License

Expand Down
14 changes: 0 additions & 14 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,12 @@

from glitchcraft import create_app
from glitchcraft.cleanup import CleanupScheduler
from glitchcraft.image_assets import ImageOutputStore, ImageSourceStore

app = create_app()
source_store: ImageSourceStore = app.extensions["image_source_store"]
output_store: ImageOutputStore = app.extensions["image_output_store"]


def cleanup_image_records() -> None:
source_store.cleanup_expired()
output_store.cleanup_expired()


def protected_image_paths() -> set[Path]:
return source_store.known_paths() | output_store.known_paths()


cleanup = CleanupScheduler(
(Path(app.config[key]) for key in ("UPLOAD_FOLDER", "OUTPUT_FOLDER", "PREVIEW_FOLDER")),
protected_paths=protected_image_paths,
before_cleanup=cleanup_image_records,
)

if __name__ == "__main__":
Expand Down
17 changes: 17 additions & 0 deletions check.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
"""Run the complete local quality baseline with one command."""

import json
import subprocess
import sys
from pathlib import Path

from glitchcraft.service_contract import CAPABILITY_SLUGS
from glitchcraft.version import APP_ID, APP_VERSION


def run(label: str, command: list[str]) -> None:
print(f"\n==> {label}", flush=True)
Expand All @@ -19,11 +23,24 @@ def repository_consistency() -> None:
Path("glitchcraft/effects/registry.py"),
Path("docs/effect-engine.md"),
Path("docs/image-workflow.md"),
Path("docs/storage.md"),
Path("docs/recovery.md"),
Path("docs/service-contract.md"),
Path("glitchcraft/storage/contracts.py"),
Path("glitchcraft/storage/repository.py"),
Path("glitchcraft/version.py"),
Path("static/app.js"),
Path("static/app-manifest.json"),
Path("static/glitchcraft-mark.svg"),
]
missing = [str(path) for path in required if not path.is_file()]
if missing:
raise SystemExit(f"Missing required repository files: {', '.join(missing)}")
manifest = json.loads(Path("static/app-manifest.json").read_text(encoding="utf-8"))
if manifest.get("id") != APP_ID or manifest.get("version") != APP_VERSION:
raise SystemExit("Static application identity does not match the Python runtime.")
if tuple(manifest.get("capabilities", ())) != CAPABILITY_SLUGS:
raise SystemExit("Static capability slugs do not match the runtime contract.")


def main() -> None:
Expand Down
35 changes: 20 additions & 15 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
# Architecture

GlitchCraft is currently a local Flask prototype with a deterministic processing
core. `app.py` is only a development launcher. `glitchcraft.application.create_app`
constructs the application without starting servers, cleanup threads, schedulers,
or background work during import.
GlitchCraft is a local Flask application with a deterministic processing core.
`app.py` is only a development launcher. `glitchcraft.application.create_app`
constructs the application without starting servers, cleanup threads,
schedulers, or background work during import.

The package boundaries are:

- `contracts`: strict recipe and legacy-request validation.
- `effects`: metadata, isolated randomness, operations, and ordered execution.
- `media`: Pillow/OpenCV color boundaries, file I/O, and FFmpeg invocation.
- `web`: compatibility routes and request-to-recipe translation.
- `tasks`: the temporary thread-safe in-memory task store.
- `image_assets`: opaque, thread-safe identity and leases for temporary image
sources and explicit outputs.
- `cleanup`: launcher-owned expiration of temporary local files.
- `tasks`: the temporary thread-safe in-memory video task store.
- `storage`: strict manifest contracts, atomic persistence, path ownership,
leases, reconciliation, deletion, metrics, and cleanup for image assets.
- `service_contract`: static capability slugs and runtime availability metadata.
- `cleanup`: launcher-owned expiration of temporary legacy media files.

All processing is local. The application currently trusts the local operator and
does not provide authentication, content isolation, persistent source identity,
or a hardened multi-user deployment model.
All processing is local. The application trusts the local operator and does not
provide authentication, content isolation, or a hardened multi-user deployment
model. Metadata is redacted and broad CORS is not enabled.

Task and image identity state are process-local. A restart loses status, source
IDs, and output IDs. There is no bounded queue, cancellation, recovery,
persistent library, or database. These limitations are intentional in the
current transitional application.
Image sources and explicit outputs live in a versioned managed library and retain
opaque identity across restarts. Manifest mutations are serialized by a process
reentrant lock; multiple writer processes sharing a data root are not supported.

Video task and asset identity remains process-local. A restart loses video job
status, and there is no bounded queue, cancellation, or persistent video library.
The current Flask process serves the interface and API together on port 5000. A
future 5175/4200 frontend/API split is a plan, not current behavior.
59 changes: 29 additions & 30 deletions docs/image-workflow.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Source-aware image workflow

The transitional Flask/jQuery interface now treats image work as a local,
The transitional Flask/JavaScript interface treats image work as a local,
interactive workspace:

1. Upload a supported static image once.
Expand All @@ -10,51 +10,50 @@ interactive workspace:
5. Compare original and processed frames.
6. Export a PNG only after an explicit user action.

Automatic previews return lossless PNG bytes directly. They do not create output
files. Export runs the same full-resolution source, recipe, seed, frame index, and
`apply_effect_stack` engine, then creates one temporary managed PNG.

Preview currently processes the full-resolution image. A future workspace may
introduce a bounded preview representation without changing export semantics.
Automatic previews return lossless PNG bytes directly and never create output
records or permanent files. Export runs the same full-resolution source, recipe,
seed, frame index, and `apply_effect_stack` engine, then creates one persistent
managed PNG and exact Recipe v1 snapshot. Preview currently processes the
full-resolution image.

## Identity and lifecycle

`ImageSourceStore` and `ImageOutputStore` issue unpredictable opaque identifiers.
Browser requests contain those IDs rather than server paths or managed
filenames. The stores are thread-safe and remain in memory. Records protect their
files from generic cleanup while active and expire after 24 hours by default.
Missing files and expired IDs become controlled 404 responses.

Sources and outputs are not persistent across application restart. This is
temporary local storage, not a project library.
`ImageAssetRepository` issues unpredictable opaque identifiers. Browser requests
contain those IDs rather than server paths or managed filenames. A
schema-version-1 manifest persists records and output recipe snapshots. Sources
and outputs survive restart with the same data root and remain until explicit
deletion. Age cleanup never removes manifest-referenced image files.

Uploads are extension-checked before writing, decoded with Pillow, restricted to
PNG, JPEG, BMP, or TIFF, limited to 40 million pixels, and guarded against
decompression-bomb conditions. Animated or mismatched content is rejected.
Uploads are extension-checked before installation, decoded with Pillow,
restricted to PNG, JPEG, BMP, or TIFF, limited to 40 million pixels, and guarded
against decompression-bomb conditions. Animated or mismatched content is
rejected.

## API

- `POST /api/image-sources` — multipart source upload; returns metadata, seed,
opaque source ID, and inline original URL.
- `POST /api/image-sources` — multipart source upload.
- `GET /api/image-sources` and `GET /api/image-sources/<id>` — source library
listing and metadata.
- `GET /api/image-sources/<id>/original` — inline original with `no-store`.
- `POST /api/image-sources/<id>/preview` — strict `{ "recipe": ... }` JSON;
returns inline PNG bytes without creating an output.
- `POST /api/image-sources/<id>/export` — the same strict recipe; returns an
opaque output ID, display filename, inline URL, and download URL.
- `POST /api/image-sources/<id>/preview` — strict recipe request and inline PNG.
- `POST /api/image-sources/<id>/export` — explicit persistent PNG export.
- `GET /api/image-outputs` and `GET /api/image-outputs/<id>/metadata` — output
listing, links, source availability, and recipe snapshots.
- `GET /api/image-outputs/<id>` — inline exported PNG.
- `GET /api/image-outputs/<id>/download` — the same PNG as an attachment.
- `DELETE /api/image-outputs/<id>` — explicit output deletion.
- `DELETE /api/image-sources/<id>?cascade=true` — source deletion with optional
dependent-output deletion.

The legacy routes remain, and video preview/processing continues through the
legacy path.
temporary legacy path. There is no visible library interface in this PR.

## Transitional frontend

The image-side script owns explicit source, seed, recipe, request, preview,
The image-side script retains explicit source, seed, recipe, request, preview,
export, and error state. A 175 ms debounce limits slider requests. An
`AbortController` cancels prior requests, a monotonically increasing revision
rejects late results, and replaced object URLs are revoked.

jQuery remains CDN-hosted for the legacy video path. Bootstrap and Google Fonts
have been removed; the image workspace uses a system font stack and repository
CSS. The planned family-aligned React workspace and final jQuery removal remain
future work.
jQuery remains CDN-hosted for the legacy video path. The planned family-aligned
React workspace, visible library, and final jQuery removal remain future work.
12 changes: 9 additions & 3 deletions docs/product-direction.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ The family relationship is a shared precision-oriented shell and interaction
grammar. GlitchCraft will retain its own signal, interference, and transformation
identity; another application's visual theme will not be copied wholesale.

This first PR deliberately makes no visual redesign. It establishes inspectable
recipes, capability metadata, deterministic processing, truthful task state, and
testable boundaries needed by that future workspace.
The current sequence deliberately avoids a final visual redesign. It establishes
inspectable recipes, deterministic processing, persistent image identity, Craft
discovery metadata, truthful readiness, and testable boundaries needed by that
future workspace.

A future orchestration dashboard may discover and check GlitchCraft, ColorCraft,
and Web Video Optimizer through related contracts. It is not implemented here
and cannot launch or remotely control this service. The family-aligned React
workspace and proposed 5175/4200 frontend/API split are also deferred.
33 changes: 33 additions & 0 deletions docs/recovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Storage recovery and reconciliation

Startup creates an empty schema-version-1 manifest when no managed state exists.
A valid primary loads normally. If the primary is missing or invalid and the
backup is valid, GlitchCraft restores the primary without overwriting the
last-known-good backup, records a redacted warning, writes a private recovery
report, and reports degraded readiness for that process.

If both primary and backup are invalid, neither is overwritten. Lightweight
`/health` remains available, `/ready` returns `not_ready`, and persistent asset
mutations return controlled 503 responses. Local logs may contain diagnostics;
public responses never include paths, raw manifest contents, temporary names,
tracebacks, or operating-system errors.

Reconciliation uses deterministic rules:

- a source record whose managed file is missing is removed;
- an output record whose managed file is missing is removed;
- an output file remains usable when its source record/file is missing, and its
metadata reports `sourceAvailable: false`;
- unreferenced source and output files are reported as orphans and are not
deleted automatically.

Startup changes and backup restoration create timestamped JSON reports inside
`recovery/`. That directory is not served publicly. `/api/storage` exposes only
redacted counts, bytes, writability, free space when available, and the last
reconciliation summary.

Atomic manifest replacement reduces partial-write risk but cannot make manifest
and filesystem deletion one disk transaction. Deletion first moves files to a
same-root quarantine, persists one complete manifest mutation, and restores the
files if persistence fails. A crash can still leave quarantined or orphaned
files; reconciliation reports these for explicit cleanup.
41 changes: 41 additions & 0 deletions docs/service-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Craft service contract

GlitchCraft exposes a local discovery and operations contract compatible with a
future Craft application dashboard:

- `GET /app-manifest.json` — static identity, version, truthful port-5000
defaults, endpoint links, and stable capability slugs.
- `GET /metadata` — request-derived runtime addresses, schema versions, format
and effect metadata, links, and library counts.
- `GET /health` — lightweight process/route liveness only.
- `GET /ready` — `ready`, `degraded`, or `not_ready` operational checks.
- `GET /api/capabilities` — implemented versus currently available image/video
capabilities and optional FFmpeg/FFprobe status.
- `GET /api/storage` — redacted persistent-storage counts, bytes, writability,
free space when available, and reconciliation state.

Health does not write storage, mutate the manifest, or invoke FFmpeg. Readiness
is `not_ready` for core manifest or image-storage failure. Missing FFmpeg,
backup recovery, or reported orphans produce `degraded` while image work remains
available. An empty healthy library is `ready`.

The canonical application version is `glitchcraft.version.APP_VERSION`; tests
prevent drift with the static manifest. The provisional SVG mark is discovery
support, not final brand identity.

## Trust model

The service has no authentication and is intended for loopback use only. It
should not be exposed directly to the public internet. User media, manifest
records, and recipes remain local. Metadata is redacted. No analytics, cloud
processing, external media service, or broad CORS policy is added.

The current Flask application serves both web and API traffic at
`http://127.0.0.1:5000`. Ports 5175 and 4200 describe a possible future
frontend/API split and are not advertised as running services. There is no
dashboard remote control or application launch contract in this version.

Persistent storage currently covers images only. Video jobs and assets remain
temporary, task state remains in memory, and audio behavior is unchanged. Saved
recipe management, WVO handoff, persistent video identity, authentication, and a
visible library interface are deferred.
Loading
Loading