Add Repixelizer witness advertisement export
This commit is contained in:
parent
6679de4ffd
commit
1e1a3441fe
9 changed files with 673 additions and 14 deletions
|
|
@ -73,7 +73,9 @@ That is the machine described in `docs/lean-optimizer-algorithm-map.md`.
|
|||
Comparison mode just runs that same `phase-field` result next to the baselines.
|
||||
|
||||
For hosted service state, queue visibility, and the future CultCache/CultMesh/Eve
|
||||
operator surface, see `docs/verse-service-contract.md`.
|
||||
operator surface, see `docs/verse-service-contract.md`. The read-only Odin
|
||||
advertisement fixture/export is documented in
|
||||
`docs/repixelizer-witness-contract.md`.
|
||||
|
||||
## Current Status
|
||||
|
||||
|
|
|
|||
191
docs/fixtures/repixelizer.provider-advertisement.json
Normal file
191
docs/fixtures/repixelizer.provider-advertisement.json
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
{
|
||||
"commands": [
|
||||
{
|
||||
"authority": "repixelizer-route-policy",
|
||||
"command": "repixelizer.job.submit",
|
||||
"method": "POST",
|
||||
"result": "accepted-denied-or-queued",
|
||||
"route": "/api/jobs",
|
||||
"surfaceId": "repixelizer.web.app",
|
||||
"transport": "http"
|
||||
},
|
||||
{
|
||||
"authority": "repixelizer-job-owner",
|
||||
"command": "repixelizer.job.cancel_own",
|
||||
"method": "DELETE",
|
||||
"result": "accepted-denied-or-not-found",
|
||||
"route": "/api/jobs/{job_id}",
|
||||
"surfaceId": "repixelizer.web.app",
|
||||
"transport": "http"
|
||||
}
|
||||
],
|
||||
"contacts": [
|
||||
{
|
||||
"kind": "repo",
|
||||
"path": "E:\\Projects\\repixelizer"
|
||||
}
|
||||
],
|
||||
"freshness": {
|
||||
"lastSeenAt": "2026-06-03T00:00:00Z",
|
||||
"maxAgeMs": 15000,
|
||||
"state": "fresh"
|
||||
},
|
||||
"kind": "service.product",
|
||||
"nestedVerses": [
|
||||
{
|
||||
"authorityBoundary": "repixelizer-local-session-or-heimdall-account",
|
||||
"carryRules": {
|
||||
"commands": "repixelizer-route-policy-only",
|
||||
"identity": "heimdall-claims-projected-when-enabled"
|
||||
},
|
||||
"kind": "session-space",
|
||||
"parentVerseId": "gamecult.local",
|
||||
"stateSchemas": [
|
||||
"repixelizer.job.v0",
|
||||
"repixelizer.job_event.v0",
|
||||
"repixelizer.auth_projection.v0"
|
||||
],
|
||||
"surfaceIds": [
|
||||
"repixelizer.web.app"
|
||||
],
|
||||
"verseId": "repixelizer.session.{sessionId}"
|
||||
},
|
||||
{
|
||||
"authorityBoundary": "read-only-first-cut",
|
||||
"kind": "hosted-operator-space",
|
||||
"parentVerseId": "gamecult.local",
|
||||
"stateSchemas": [
|
||||
"repixelizer.job.v0",
|
||||
"repixelizer.queue_snapshot.v0",
|
||||
"repixelizer.job_event.v0",
|
||||
"repixelizer.runtime_config.v0",
|
||||
"repixelizer.auth_projection.v0"
|
||||
],
|
||||
"surfaceIds": [
|
||||
"repixelizer.operator.surface"
|
||||
],
|
||||
"verseId": "repixelizer.operator"
|
||||
}
|
||||
],
|
||||
"providerId": "repixelizer",
|
||||
"runtime": {
|
||||
"auth": {
|
||||
"enabled": false,
|
||||
"mode": "off",
|
||||
"protectQueue": false,
|
||||
"providers": [],
|
||||
"required": false
|
||||
},
|
||||
"hostedDemo": false,
|
||||
"queueCapacity": 32,
|
||||
"showQueuePanel": false,
|
||||
"spoolPath": "C:\\Users\\Meta\\AppData\\Local\\Temp\\repixelizer-gui-spool"
|
||||
},
|
||||
"schema": "gamecult.eve.provider_advertisement.v1",
|
||||
"schemas": [
|
||||
{
|
||||
"authority": "accepted",
|
||||
"description": "Job id, owner binding, status, timestamps, input/output artifact refs, error summary, and retention state.",
|
||||
"owner": "repixelizer.gui",
|
||||
"portable": true,
|
||||
"schema": "repixelizer.job.v0",
|
||||
"storage": "memory-dev-only-with-cc-witness-reserved"
|
||||
},
|
||||
{
|
||||
"authority": "accepted",
|
||||
"description": "Queue capacity, waiting/running counts, active job ids, oldest waiting age, and hosted mode.",
|
||||
"owner": "repixelizer.gui",
|
||||
"portable": true,
|
||||
"schema": "repixelizer.queue_snapshot.v0",
|
||||
"storage": "memory-dev-only-with-cc-witness-reserved"
|
||||
},
|
||||
{
|
||||
"authority": "redacted-projection",
|
||||
"description": "Redacted progress event, stage, index, timestamp, and job id.",
|
||||
"owner": "repixelizer.gui",
|
||||
"portable": true,
|
||||
"schema": "repixelizer.job_event.v0",
|
||||
"storage": "memory-dev-only-with-cc-witness-reserved"
|
||||
},
|
||||
{
|
||||
"authority": "accepted",
|
||||
"description": "Hosted flags, queue limits, visible UI flags, solver config hash placeholder, deployment id, and spool path.",
|
||||
"owner": "repixelizer.gui",
|
||||
"portable": true,
|
||||
"schema": "repixelizer.runtime_config.v0",
|
||||
"storage": "memory-dev-only-with-cc-witness-reserved"
|
||||
},
|
||||
{
|
||||
"authority": "external-authority-projection",
|
||||
"description": "Auth mode, provider availability, subject capability summary, and Heimdall claim freshness without raw tokens.",
|
||||
"owner": "repixelizer.access",
|
||||
"portable": true,
|
||||
"schema": "repixelizer.auth_projection.v0",
|
||||
"storage": "heimdall-projection-with-cc-witness-reserved"
|
||||
}
|
||||
],
|
||||
"serviceId": "repixelizer.service",
|
||||
"styleCapabilities": [
|
||||
{
|
||||
"lossiness": {
|
||||
"read-only-first-cut": "operator surface names state and routes but does not own queue truth",
|
||||
"tui": "no-pixel-canvas-editing; status, queue, artifact refs, and command intents only"
|
||||
},
|
||||
"preferredLowerings": [
|
||||
"css",
|
||||
"android-native",
|
||||
"native-eve",
|
||||
"tui"
|
||||
],
|
||||
"styleProfile": "repixelizer.product",
|
||||
"tokenGroups": [
|
||||
"repixelizer.comparisonCanvas",
|
||||
"repixelizer.cleanupTool",
|
||||
"repixelizer.queuePanel",
|
||||
"repixelizer.operatorStatus"
|
||||
]
|
||||
}
|
||||
],
|
||||
"surfaces": [
|
||||
{
|
||||
"audience": "operator",
|
||||
"commands": [],
|
||||
"key": "cultmesh://repixelizer/surfaces/operator",
|
||||
"mode": "read-only-first-cut",
|
||||
"schema": "gamecult.eve.surface.v1",
|
||||
"status": "planned",
|
||||
"surfaceId": "repixelizer.operator.surface",
|
||||
"transport": "cultmesh-document"
|
||||
},
|
||||
{
|
||||
"canonical": false,
|
||||
"canonicalSurfaceId": "repixelizer.operator.surface",
|
||||
"schema": "gamecult.eve.surface.v1",
|
||||
"surfaceId": "repixelizer.web.app",
|
||||
"transport": "browser-lowering",
|
||||
"url": "https://repixelizer.gamecult.org/app/"
|
||||
}
|
||||
],
|
||||
"title": "Repixelizer",
|
||||
"updatedAt": "2026-06-03T00:00:00Z",
|
||||
"verseId": "gamecult.local",
|
||||
"witnesses": [
|
||||
{
|
||||
"freshness": {
|
||||
"state": "planned",
|
||||
"updatedAt": "2026-06-03T00:00:00Z"
|
||||
},
|
||||
"id": "repixelizer.hosted.witness",
|
||||
"kind": "cc-export-path-reserved",
|
||||
"path": "state/repixelizer.witness.cc",
|
||||
"redaction": "bulk-images-raw-tokens-and-private-claims-removed",
|
||||
"schemas": [
|
||||
"repixelizer.job.v0",
|
||||
"repixelizer.queue_snapshot.v0",
|
||||
"repixelizer.job_event.v0",
|
||||
"repixelizer.runtime_config.v0",
|
||||
"repixelizer.auth_projection.v0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
80
docs/repixelizer-witness-contract.md
Normal file
80
docs/repixelizer-witness-contract.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# Repixelizer Witness Contract
|
||||
|
||||
Repixelizer's hosted queue is still owned by `src/repixelizer/gui.py`.
|
||||
This document defines the read-only first cut for making that state visible to
|
||||
Odin, Huginn, CultMesh, and Eve without moving queue authority.
|
||||
|
||||
## Objective
|
||||
|
||||
Publish a `gamecult.eve.provider_advertisement.v1` fixture/export that names
|
||||
Repixelizer's typed witness shapes and reserved CultCache `.cc` witness path.
|
||||
The export is an authority map. It is not the queue store, not an Eve renderer,
|
||||
and not a replacement for the current hosted routes.
|
||||
|
||||
Generate it with:
|
||||
|
||||
```powershell
|
||||
repixelize witness-advertisement --out docs/fixtures/repixelizer.provider-advertisement.json
|
||||
```
|
||||
|
||||
The checked-in fixture is:
|
||||
|
||||
`docs/fixtures/repixelizer.provider-advertisement.json`
|
||||
|
||||
## Authority Map
|
||||
|
||||
- Owner: `src/repixelizer/gui.py` owns queue admission, job lifecycle, progress
|
||||
events, cancellation, heartbeat expiry, and spool cleanup.
|
||||
- Inputs: uploaded image bytes, normalized hosted options, queue capacity,
|
||||
worker heartbeat, cancellation requests, app-local subject binding, and
|
||||
Heimdall-derived access projection.
|
||||
- Outputs: in-process job records, queue summaries, SSE progress events, output
|
||||
image artifacts, diagnostics refs, and route-level JSON projections.
|
||||
- Derived state: provider advertisement JSON, future `.cc` witness export,
|
||||
CultMesh publication, Odin discovery state, and Eve operator surfaces.
|
||||
- Forbidden writers: Odin, Eve renderers, provider-advertisement fixtures,
|
||||
witness exporters, and `.cc` inspection tools do not decide queue truth.
|
||||
- Shared paths: browser GUI, hosted API routes, future `.cc` witness export,
|
||||
future CultMesh publication, and Eve surfaces must describe the same
|
||||
Repixelizer-owned job state.
|
||||
- Cut line: this pass reserves witness names and paths only. A later pass may
|
||||
add a real `.cc` writer or move queue ownership, but that must be an explicit
|
||||
rebuild of ownership, not an exporter pretending to be persistence.
|
||||
|
||||
## Witness Schemas
|
||||
|
||||
The first advertisement names these schemas:
|
||||
|
||||
- `repixelizer.job.v0`: job id, account/session binding, status, timestamps,
|
||||
input artifact ref, output artifact ref, error summary, and retention state.
|
||||
- `repixelizer.queue_snapshot.v0`: queue capacity, waiting/running counts,
|
||||
active job ids, oldest waiting age, and hosted-demo mode.
|
||||
- `repixelizer.job_event.v0`: redacted progress event, stage, index, timestamp,
|
||||
and job id.
|
||||
- `repixelizer.runtime_config.v0`: hosted flags, queue protection, visible UI
|
||||
flags, solver config hash placeholder, deployment id, and spool path.
|
||||
- `repixelizer.auth_projection.v0`: auth mode, provider availability, subject
|
||||
capability summary, and Heimdall claim freshness without raw tokens.
|
||||
|
||||
The reserved witness path is `state/repixelizer.witness.cc`.
|
||||
|
||||
## Provider Advertisement
|
||||
|
||||
`repixelize witness-advertisement` emits:
|
||||
|
||||
- `schema: gamecult.eve.provider_advertisement.v1`
|
||||
- `providerId: repixelizer`
|
||||
- schema catalog entries for the witness shapes above
|
||||
- a reserved `.cc` witness entry with bulk image bytes, raw tokens, and private
|
||||
claims marked as redacted
|
||||
- a planned canonical Eve operator surface key:
|
||||
`cultmesh://repixelizer/surfaces/operator`
|
||||
- the current browser lowering at `/app/`
|
||||
- route-owned command boundaries for job submit and own-job cancellation
|
||||
- nested Verse declarations for session and hosted-operator spaces
|
||||
- runtime projection from `HostedDemoConfig.from_env()` and
|
||||
`AccessController.from_env()`
|
||||
|
||||
This is deliberately read-only. The current queue is still in-process memory.
|
||||
The advertisement only tells discovery organs where truth lives and what shape
|
||||
future durable witnesses must take.
|
||||
|
|
@ -87,20 +87,35 @@ tool palettes, and component variants. A Kotlin Android Eve runtime should be
|
|||
able to run the cleanup flow with native controls while preserving the same
|
||||
command boundary and product identity.
|
||||
|
||||
## Read-Only Witness Export
|
||||
|
||||
The first read-only provider advertisement is generated by:
|
||||
|
||||
```powershell
|
||||
repixelize witness-advertisement --out docs/fixtures/repixelizer.provider-advertisement.json
|
||||
```
|
||||
|
||||
It names the witness schemas above, reserves `state/repixelizer.witness.cc` for
|
||||
the future CultCache export, and publishes the planned Eve operator surface key
|
||||
without changing queue ownership. The detailed authority map lives in
|
||||
`docs/repixelizer-witness-contract.md`.
|
||||
|
||||
## Migration Order
|
||||
|
||||
1. Define Repixelizer CultCache document shapes for job, queue, runtime, event,
|
||||
and auth projection witness state.
|
||||
2. Add a read-only export from the current `gui.py` queue/job manager into a
|
||||
2. Publish a read-only `gamecult.eve.provider_advertisement.v1` fixture/export
|
||||
that names those shapes and the reserved `.cc` witness path.
|
||||
3. Add a read-only export from the current `gui.py` queue/job manager into a
|
||||
`.cc` witness.
|
||||
3. Publish the witness through CultMesh.
|
||||
4. Add an Eve DSL provider over the witness and existing health/config routes.
|
||||
5. Translate the existing browser GUI style and tool behavior into Eve style
|
||||
4. Publish the witness through CultMesh.
|
||||
5. Add an Eve DSL provider over the witness and existing health/config routes.
|
||||
6. Translate the existing browser GUI style and tool behavior into Eve style
|
||||
tokens, canvas primitives, and command descriptors.
|
||||
6. Lower the current website from the Eve surface instead of treating it as the
|
||||
7. Lower the current website from the Eve surface instead of treating it as the
|
||||
product UI owner.
|
||||
7. Register the surface with Odin.
|
||||
8. Only after the witness is stable, decide whether queue ownership should move
|
||||
8. Register the surface with Odin.
|
||||
9. Only after the witness is stable, decide whether queue ownership should move
|
||||
from in-process memory into the typed store.
|
||||
|
||||
The invariant: Heimdall owns shared auth; Repixelizer owns jobs and artifacts.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from .gui import main as gui_main
|
|||
from .pipeline import run_pipeline
|
||||
from .spritesheet import run_spritesheet
|
||||
from .tuning import tune_solver_hyperparams
|
||||
from .witness import main as witness_advertisement_main
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
|
|
@ -131,6 +132,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
tune_parser.add_argument("--workers", type=int, default=1, help="Parallel benchmark worker processes per trial")
|
||||
prepare_parser = subparsers.add_parser("prepare-corpus", help="Normalize imported corpus sheets into single sprites.")
|
||||
prepare_parser.add_argument("--corpus-dir", default="examples/corpus", help="Corpus root containing originals/")
|
||||
witness_parser = subparsers.add_parser(
|
||||
"witness-advertisement",
|
||||
help="Emit the read-only Repixelizer Eve provider advertisement.",
|
||||
)
|
||||
witness_parser.add_argument("--out", default=None, help="Write JSON to this path instead of stdout.")
|
||||
witness_parser.add_argument("--updated-at", default=None, help="Override the advertisement timestamp.")
|
||||
witness_parser.add_argument("--verse-id", default=None, help="Authoritative Verse id.")
|
||||
witness_parser.add_argument("--public-base-url", default=None, help="Hosted browser lowering base URL.")
|
||||
witness_parser.add_argument("--cc-witness-path", default=None, help="Reserved CultCache .cc witness path.")
|
||||
witness_parser.add_argument("--eve-surface-key", default=None, help="Planned CultMesh Eve surface key.")
|
||||
gui_parser = subparsers.add_parser("gui", help="Launch the web GUI.")
|
||||
gui_parser.add_argument("--host", default="127.0.0.1", help="Host interface to bind")
|
||||
gui_parser.add_argument("--port", type=int, default=8000, help="Port to bind")
|
||||
|
|
@ -140,7 +151,19 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
raw_argv = list(sys.argv[1:] if argv is None else argv)
|
||||
if not raw_argv or raw_argv[0] not in {"run", "compare", "spritesheet", "benchmark", "tune", "prepare-corpus", "gui", "-h", "--help"}:
|
||||
commands = {
|
||||
"run",
|
||||
"compare",
|
||||
"spritesheet",
|
||||
"benchmark",
|
||||
"tune",
|
||||
"prepare-corpus",
|
||||
"witness-advertisement",
|
||||
"gui",
|
||||
"-h",
|
||||
"--help",
|
||||
}
|
||||
if not raw_argv or raw_argv[0] not in commands:
|
||||
raw_argv = ["run", *raw_argv]
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(raw_argv)
|
||||
|
|
@ -218,6 +241,19 @@ def main(argv: list[str] | None = None) -> int:
|
|||
if command == "prepare-corpus":
|
||||
prepare_corpus(args.corpus_dir)
|
||||
return 0
|
||||
if command == "witness-advertisement":
|
||||
witness_args: list[str] = []
|
||||
for option, value in (
|
||||
("--out", args.out),
|
||||
("--updated-at", args.updated_at),
|
||||
("--verse-id", args.verse_id),
|
||||
("--public-base-url", args.public_base_url),
|
||||
("--cc-witness-path", args.cc_witness_path),
|
||||
("--eve-surface-key", args.eve_surface_key),
|
||||
):
|
||||
if value is not None:
|
||||
witness_args.extend([option, str(value)])
|
||||
return witness_advertisement_main(witness_args)
|
||||
if command == "gui":
|
||||
return gui_main(host=args.host, port=args.port, reload=args.reload)
|
||||
|
||||
|
|
|
|||
266
src/repixelizer/witness.py
Normal file
266
src/repixelizer/witness.py
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, TextIO
|
||||
|
||||
from .access import AccessController
|
||||
from .gui import HostedDemoConfig
|
||||
|
||||
|
||||
PROVIDER_ADVERTISEMENT_SCHEMA = "gamecult.eve.provider_advertisement.v1"
|
||||
PROVIDER_ID = "repixelizer"
|
||||
SERVICE_ID = "repixelizer.service"
|
||||
DEFAULT_VERSE_ID = "gamecult.local"
|
||||
DEFAULT_PUBLIC_BASE_URL = "https://repixelizer.gamecult.org"
|
||||
DEFAULT_CC_WITNESS_PATH = "state/repixelizer.witness.cc"
|
||||
DEFAULT_EVE_SURFACE_KEY = "cultmesh://repixelizer/surfaces/operator"
|
||||
|
||||
WITNESS_SCHEMAS: tuple[dict[str, Any], ...] = (
|
||||
{
|
||||
"schema": "repixelizer.job.v0",
|
||||
"owner": "repixelizer.gui",
|
||||
"authority": "accepted",
|
||||
"storage": "memory-dev-only-with-cc-witness-reserved",
|
||||
"portable": True,
|
||||
"description": "Job id, owner binding, status, timestamps, input/output artifact refs, error summary, and retention state.",
|
||||
},
|
||||
{
|
||||
"schema": "repixelizer.queue_snapshot.v0",
|
||||
"owner": "repixelizer.gui",
|
||||
"authority": "accepted",
|
||||
"storage": "memory-dev-only-with-cc-witness-reserved",
|
||||
"portable": True,
|
||||
"description": "Queue capacity, waiting/running counts, active job ids, oldest waiting age, and hosted mode.",
|
||||
},
|
||||
{
|
||||
"schema": "repixelizer.job_event.v0",
|
||||
"owner": "repixelizer.gui",
|
||||
"authority": "redacted-projection",
|
||||
"storage": "memory-dev-only-with-cc-witness-reserved",
|
||||
"portable": True,
|
||||
"description": "Redacted progress event, stage, index, timestamp, and job id.",
|
||||
},
|
||||
{
|
||||
"schema": "repixelizer.runtime_config.v0",
|
||||
"owner": "repixelizer.gui",
|
||||
"authority": "accepted",
|
||||
"storage": "memory-dev-only-with-cc-witness-reserved",
|
||||
"portable": True,
|
||||
"description": "Hosted flags, queue limits, visible UI flags, solver config hash placeholder, deployment id, and spool path.",
|
||||
},
|
||||
{
|
||||
"schema": "repixelizer.auth_projection.v0",
|
||||
"owner": "repixelizer.access",
|
||||
"authority": "external-authority-projection",
|
||||
"storage": "heimdall-projection-with-cc-witness-reserved",
|
||||
"portable": True,
|
||||
"description": "Auth mode, provider availability, subject capability summary, and Heimdall claim freshness without raw tokens.",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def utc_now_iso() -> str:
|
||||
return datetime.now(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
||||
|
||||
|
||||
def build_provider_advertisement(
|
||||
*,
|
||||
updated_at: str | None = None,
|
||||
verse_id: str = DEFAULT_VERSE_ID,
|
||||
public_base_url: str = DEFAULT_PUBLIC_BASE_URL,
|
||||
cc_witness_path: str = DEFAULT_CC_WITNESS_PATH,
|
||||
eve_surface_key: str = DEFAULT_EVE_SURFACE_KEY,
|
||||
config: HostedDemoConfig | None = None,
|
||||
access_controller: AccessController | None = None,
|
||||
) -> dict[str, Any]:
|
||||
config = config or HostedDemoConfig.from_env()
|
||||
access_controller = access_controller or AccessController.from_env(hosted_demo=config.hosted_demo)
|
||||
updated_at = updated_at or utc_now_iso()
|
||||
auth_payload = access_controller.public_payload()
|
||||
|
||||
return {
|
||||
"schema": PROVIDER_ADVERTISEMENT_SCHEMA,
|
||||
"providerId": PROVIDER_ID,
|
||||
"serviceId": SERVICE_ID,
|
||||
"verseId": verse_id,
|
||||
"title": "Repixelizer",
|
||||
"kind": "service.product",
|
||||
"updatedAt": updated_at,
|
||||
"freshness": {
|
||||
"state": "fresh",
|
||||
"lastSeenAt": updated_at,
|
||||
"maxAgeMs": 15000,
|
||||
},
|
||||
"schemas": list(WITNESS_SCHEMAS),
|
||||
"witnesses": [
|
||||
{
|
||||
"id": "repixelizer.hosted.witness",
|
||||
"kind": "cc-export-path-reserved",
|
||||
"path": cc_witness_path,
|
||||
"schemas": [entry["schema"] for entry in WITNESS_SCHEMAS],
|
||||
"redaction": "bulk-images-raw-tokens-and-private-claims-removed",
|
||||
"freshness": {
|
||||
"state": "planned",
|
||||
"updatedAt": updated_at,
|
||||
},
|
||||
}
|
||||
],
|
||||
"surfaces": [
|
||||
{
|
||||
"surfaceId": "repixelizer.operator.surface",
|
||||
"schema": "gamecult.eve.surface.v1",
|
||||
"transport": "cultmesh-document",
|
||||
"key": eve_surface_key,
|
||||
"audience": "operator",
|
||||
"mode": "read-only-first-cut",
|
||||
"commands": [],
|
||||
"status": "planned",
|
||||
},
|
||||
{
|
||||
"surfaceId": "repixelizer.web.app",
|
||||
"schema": "gamecult.eve.surface.v1",
|
||||
"transport": "browser-lowering",
|
||||
"url": f"{public_base_url.rstrip('/')}/app/",
|
||||
"canonical": False,
|
||||
"canonicalSurfaceId": "repixelizer.operator.surface",
|
||||
},
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"command": "repixelizer.job.submit",
|
||||
"surfaceId": "repixelizer.web.app",
|
||||
"transport": "http",
|
||||
"route": "/api/jobs",
|
||||
"method": "POST",
|
||||
"authority": "repixelizer-route-policy",
|
||||
"result": "accepted-denied-or-queued",
|
||||
},
|
||||
{
|
||||
"command": "repixelizer.job.cancel_own",
|
||||
"surfaceId": "repixelizer.web.app",
|
||||
"transport": "http",
|
||||
"route": "/api/jobs/{job_id}",
|
||||
"method": "DELETE",
|
||||
"authority": "repixelizer-job-owner",
|
||||
"result": "accepted-denied-or-not-found",
|
||||
},
|
||||
],
|
||||
"nestedVerses": [
|
||||
{
|
||||
"verseId": "repixelizer.session.{sessionId}",
|
||||
"parentVerseId": verse_id,
|
||||
"kind": "session-space",
|
||||
"authorityBoundary": "repixelizer-local-session-or-heimdall-account",
|
||||
"surfaceIds": ["repixelizer.web.app"],
|
||||
"stateSchemas": [
|
||||
"repixelizer.job.v0",
|
||||
"repixelizer.job_event.v0",
|
||||
"repixelizer.auth_projection.v0",
|
||||
],
|
||||
"carryRules": {
|
||||
"identity": "heimdall-claims-projected-when-enabled",
|
||||
"commands": "repixelizer-route-policy-only",
|
||||
},
|
||||
},
|
||||
{
|
||||
"verseId": "repixelizer.operator",
|
||||
"parentVerseId": verse_id,
|
||||
"kind": "hosted-operator-space",
|
||||
"authorityBoundary": "read-only-first-cut",
|
||||
"surfaceIds": ["repixelizer.operator.surface"],
|
||||
"stateSchemas": [entry["schema"] for entry in WITNESS_SCHEMAS],
|
||||
},
|
||||
],
|
||||
"styleCapabilities": [
|
||||
{
|
||||
"styleProfile": "repixelizer.product",
|
||||
"tokenGroups": [
|
||||
"repixelizer.comparisonCanvas",
|
||||
"repixelizer.cleanupTool",
|
||||
"repixelizer.queuePanel",
|
||||
"repixelizer.operatorStatus",
|
||||
],
|
||||
"preferredLowerings": ["css", "android-native", "native-eve", "tui"],
|
||||
"lossiness": {
|
||||
"tui": "no-pixel-canvas-editing; status, queue, artifact refs, and command intents only",
|
||||
"read-only-first-cut": "operator surface names state and routes but does not own queue truth",
|
||||
},
|
||||
}
|
||||
],
|
||||
"contacts": [
|
||||
{
|
||||
"kind": "repo",
|
||||
"path": "E:\\Projects\\repixelizer",
|
||||
}
|
||||
],
|
||||
"runtime": {
|
||||
"hostedDemo": config.hosted_demo,
|
||||
"queueCapacity": config.queue_capacity,
|
||||
"showQueuePanel": config.show_queue_panel,
|
||||
"spoolPath": str(config.spool_dir),
|
||||
"auth": {
|
||||
"enabled": bool(auth_payload.get("enabled")),
|
||||
"mode": auth_payload.get("mode"),
|
||||
"required": bool(auth_payload.get("required")),
|
||||
"protectQueue": bool(auth_payload.get("protectQueue")),
|
||||
"providers": auth_payload.get("providers", []),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def write_provider_advertisement(
|
||||
output: Path | None,
|
||||
*,
|
||||
updated_at: str | None = None,
|
||||
verse_id: str = DEFAULT_VERSE_ID,
|
||||
public_base_url: str = DEFAULT_PUBLIC_BASE_URL,
|
||||
cc_witness_path: str = DEFAULT_CC_WITNESS_PATH,
|
||||
eve_surface_key: str = DEFAULT_EVE_SURFACE_KEY,
|
||||
stream: TextIO = sys.stdout,
|
||||
) -> None:
|
||||
payload = build_provider_advertisement(
|
||||
updated_at=updated_at,
|
||||
verse_id=verse_id,
|
||||
public_base_url=public_base_url,
|
||||
cc_witness_path=cc_witness_path,
|
||||
eve_surface_key=eve_surface_key,
|
||||
)
|
||||
encoded = json.dumps(payload, indent=2, sort_keys=True)
|
||||
if output is None:
|
||||
stream.write(f"{encoded}\n")
|
||||
return
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(f"{encoded}\n", encoding="utf-8")
|
||||
|
||||
|
||||
def build_witness_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="repixelize witness-advertisement",
|
||||
description="Emit Repixelizer's read-only Eve provider advertisement fixture.",
|
||||
)
|
||||
parser.add_argument("--out", type=Path, default=None, help="Write JSON to this path instead of stdout.")
|
||||
parser.add_argument("--updated-at", default=None, help="Override the advertisement timestamp.")
|
||||
parser.add_argument("--verse-id", default=DEFAULT_VERSE_ID, help="Authoritative Verse id.")
|
||||
parser.add_argument("--public-base-url", default=DEFAULT_PUBLIC_BASE_URL, help="Hosted browser lowering base URL.")
|
||||
parser.add_argument("--cc-witness-path", default=DEFAULT_CC_WITNESS_PATH, help="Reserved CultCache .cc witness path.")
|
||||
parser.add_argument("--eve-surface-key", default=DEFAULT_EVE_SURFACE_KEY, help="Planned CultMesh Eve surface key.")
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = build_witness_parser()
|
||||
args = parser.parse_args(argv)
|
||||
write_provider_advertisement(
|
||||
args.out,
|
||||
updated_at=args.updated_at,
|
||||
verse_id=args.verse_id,
|
||||
public_base_url=args.public_base_url,
|
||||
cc_witness_path=args.cc_witness_path,
|
||||
eve_surface_key=args.eve_surface_key,
|
||||
)
|
||||
return 0
|
||||
|
|
@ -34,3 +34,4 @@
|
|||
{"ts": "2026-05-30T22:47:57+00:00", "type": "implementation", "status": "ok", "note": "Added spritesheet CLI mode: detect separated foreground regions or select requested largest components, call run_pipeline_rgba per crop, pack outputs in source-row order, and write spritesheet diagnostics; fixture auto-detects six sprites."}
|
||||
{"ts": "2026-05-30T23:39:55+00:00", "type": "implementation", "status": "ok", "note": "Spritesheet automatic sizing now uses a sheet-level median texel density before reconstruction; on the 12 generated spritesheets in 019e7aa3-4bbb-7302-bafd-273f2aa31e53, worst per-sheet density drift improved from 42.21% to 1.786% across 153 sprites."}
|
||||
{"ts": "2026-05-31T01:13:17+00:00", "type": "implementation", "status": "ok", "note": "Spritesheet mode now supports explicit grid ownership via sheet_columns/sheet_rows plus individual sprite export. The 12 generated character sheets were rerun as 3x2 grids, producing 72 exports with no component chopping and worst within-sheet density deviation 0.124%."}
|
||||
{"ts": "2026-06-03T02:21:02+00:00", "type": "implementation", "status": "ok", "note": "Added read-only Repixelizer provider advertisement fixture/export naming job/queue/event/runtime/auth witness schemas and reserved state/repixelizer.witness.cc path without moving gui.py queue ownership."}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ accepted_design:
|
|||
- Hosted mode owns the public front door in `src/repixelizer/gui.py`: `/` is a landing page there, while local/self-host runs keep root as a plain redirect to `/app/`.
|
||||
- `src/repixelizer/access.py` owns the hosted access seam: trusted-header ingress still works, `GC_ACCESS_MODE=heimdall` verifies Heimdall Ed25519 access tokens locally, and auth attempts are adopted into local httpOnly cookie sessions.
|
||||
- In Heimdall mode, Repixelizer keeps a separate httpOnly refresh cookie and asks Heimdall to reissue a short-lived access claim before forcing the user back through provider OAuth.
|
||||
- `src/repixelizer/witness.py` owns the read-only first-cut Odin provider advertisement export. It names Repixelizer job, queue, event, runtime, and auth witness schemas plus the reserved `state/repixelizer.witness.cc` path, but it does not own queue truth or write CultCache.
|
||||
- The solver starts from fixed lattice centers, adds tiny seeded noise, follows a direct hierarchical flatness signal, enforces local lattice coherence through spring relaxation, then rounds to real source pixels for final output.
|
||||
- Cleanup is local and secondary; it does not define the core reconstruction model.
|
||||
- `source_fidelity` and `source_structure` separate lattice agreement from visible structure.
|
||||
|
|
@ -68,6 +69,7 @@ landed_baseline:
|
|||
- Spritesheet CLI mode detects separated foreground regions or selects the largest requested sprite count, then repixelizes each crop independently through `phase-field`.
|
||||
- `source_structure` reporting alongside `source_fidelity`.
|
||||
- Hosted landing page and Heimdall auth seam in the web layer.
|
||||
- Read-only `gamecult.eve.provider_advertisement.v1` fixture/export for Odin discovery and future CultCache/Eve witness work.
|
||||
|
||||
rejected_paths:
|
||||
- name: tray_optimizer_sprawl
|
||||
|
|
@ -95,11 +97,16 @@ current_status:
|
|||
endpoint before falling back to provider OAuth, while
|
||||
`src/repixelizer/gui.py` owns provider-start routes, direct backend callback
|
||||
receipt, hosted landing-page login buttons, route guards, and queue/job
|
||||
ownership checks. Self-host remains permissive by default unless
|
||||
`GC_ACCESS_*` turns the seam on. The solver is shipping with a usable but
|
||||
not final tuned config: local search radius `0.215`, blend `0.24`, move
|
||||
weight `0.09`, grid alignment `4.75`, signal energy power `1.4`, and signal
|
||||
peak power `3.25`, aligned in `config/solver_params.json` and the docs.
|
||||
ownership checks. `src/repixelizer/witness.py` now emits a read-only
|
||||
`gamecult.eve.provider_advertisement.v1` export and checked-in fixture that
|
||||
names the Repixelizer job/queue/event/runtime/auth witness schemas and
|
||||
reserved `state/repixelizer.witness.cc` path for Odin/Huginn discovery,
|
||||
without moving queue ownership out of `gui.py`. Self-host remains
|
||||
permissive by default unless `GC_ACCESS_*` turns the seam on. The solver is
|
||||
shipping with a usable but not final tuned config: local search radius
|
||||
`0.215`, blend `0.24`, move weight `0.09`, grid alignment `4.75`, signal
|
||||
energy power `1.4`, and signal peak power `3.25`, aligned in
|
||||
`config/solver_params.json` and the docs.
|
||||
Diagnostic edge analysis remains an observer/GUI gauge. The tuning pass proved that wide
|
||||
local-search windows caused neighbor feature poaching and that tighter radius
|
||||
plus stronger blend is the current motion-geometry basin. It did not fully
|
||||
|
|
|
|||
61
tests/test_witness.py
Normal file
61
tests/test_witness.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from repixelizer.cli import main as cli_main
|
||||
from repixelizer.witness import build_provider_advertisement
|
||||
|
||||
|
||||
def test_provider_advertisement_names_witness_schemas(monkeypatch, tmp_path: Path) -> None:
|
||||
monkeypatch.setenv("REPIXELIZER_HOSTED_DEMO", "1")
|
||||
monkeypatch.setenv("REPIXELIZER_QUEUE_CAPACITY", "7")
|
||||
monkeypatch.setenv("REPIXELIZER_SPOOL_DIR", str(tmp_path / "spool"))
|
||||
monkeypatch.setenv("GC_ACCESS_MODE", "off")
|
||||
|
||||
payload = build_provider_advertisement(updated_at="2026-06-03T00:00:00Z")
|
||||
|
||||
assert payload["schema"] == "gamecult.eve.provider_advertisement.v1"
|
||||
assert payload["providerId"] == "repixelizer"
|
||||
assert payload["runtime"]["hostedDemo"] is True
|
||||
assert payload["runtime"]["queueCapacity"] == 7
|
||||
assert payload["runtime"]["auth"]["mode"] == "off"
|
||||
|
||||
schema_names = {entry["schema"] for entry in payload["schemas"]}
|
||||
assert schema_names == {
|
||||
"repixelizer.auth_projection.v0",
|
||||
"repixelizer.job.v0",
|
||||
"repixelizer.job_event.v0",
|
||||
"repixelizer.queue_snapshot.v0",
|
||||
"repixelizer.runtime_config.v0",
|
||||
}
|
||||
witness = payload["witnesses"][0]
|
||||
assert witness["kind"] == "cc-export-path-reserved"
|
||||
assert witness["path"] == "state/repixelizer.witness.cc"
|
||||
assert witness["freshness"]["state"] == "planned"
|
||||
|
||||
|
||||
def test_witness_advertisement_cli_writes_json(monkeypatch, tmp_path: Path) -> None:
|
||||
monkeypatch.setenv("REPIXELIZER_SPOOL_DIR", str(tmp_path / "spool"))
|
||||
monkeypatch.setenv("GC_ACCESS_MODE", "off")
|
||||
out = tmp_path / "repixelizer.provider-advertisement.json"
|
||||
|
||||
exit_code = cli_main(
|
||||
[
|
||||
"witness-advertisement",
|
||||
"--out",
|
||||
str(out),
|
||||
"--updated-at",
|
||||
"2026-06-03T00:00:00Z",
|
||||
"--verse-id",
|
||||
"gamecult.test",
|
||||
"--cc-witness-path",
|
||||
"state/test-repixelizer.witness.cc",
|
||||
]
|
||||
)
|
||||
|
||||
assert exit_code == 0
|
||||
payload = json.loads(out.read_text(encoding="utf-8"))
|
||||
assert payload["verseId"] == "gamecult.test"
|
||||
assert payload["witnesses"][0]["path"] == "state/test-repixelizer.witness.cc"
|
||||
assert payload["commands"][0]["route"] == "/api/jobs"
|
||||
Loading…
Reference in a new issue