0027 — Shoot safety resets (guarded re-cull · reset · reset exports)¶
- Status: IMPLEMENTED (2026-07-17). Studio surface (re-cull-fresh button, soft/hard reset +
reset-exports endpoints +
pkg/shootmethods + Review-screen Danger zone) + CLI parity (reset frame/shoot/exportssubcommands, thereset <frame>→reset frame <name>restructure, godog scenarios). Gates resolved against existing code +0028(no data change). The one deferred extra is the §8.2 "silent faces-off loupe empty-state" (a cheap related UX nicety, not a reset action). - Why now: a face-enabled re-cull failed over the LAN bridge with an opaque
"network error", leaving
lyndsey-denisin a stuck state with no in-studio way to recover short of hand-deleting sidecar files. A photographer needs a guarded, discreet failsafe to return a shoot (or its exports) to a known state when a bug or a mistake corrupts it — without ever risking the originals. - Realises:
0019§7 ("incremental/resumable ingest" robustness family) and the operational-trust gap; extends the existing per-frameresetcommand to a shoot scope. Surfaces the CLIcull --reanalyzecapability in the studio. - Depends on:
0001§3/§4.6 (non-destructive guarantee),0003(studio contract),0005(verdicts/analysis sidecar),0011(studio auth),0024(settings rail). - Anchors: new
R-RESET-*requirements (to fold into0002).
1. Why¶
The workspace records every decision in the .krites/ sidecar (0001 §3):
verdicts.yaml (durable keep/maybe/reject + ratings), analysis.yaml (the
regenerable analysis cache), develop.yaml + edits/ + removal patches (edit
records), and export/ (rendered output). Caches are regenerable; verdicts and
edits are durable decisions (0001 §3 "Caches are regenerable").
Today there is no in-studio way to clear any of these. The only reset is the
CLI command scoped to a single frame (pkg/cmd/reset, clears one frame's
verdict override + edits). When a whole shoot goes wrong — a failed cull, a bad
profile, exports rendered from stale verdicts — the photographer's only recourse
is to hand-delete sidecar files, which is both unsafe (easy to delete the wrong
thing) and undiscoverable.
This spec adds scoped, guarded reset actions to the studio (with CLI parity), and surfaces the missing force-reanalyze control. The guard exists precisely because these clear durable human decisions: they must be hard to hit by accident and must spell out what they clear vs preserve.
2. The actions¶
Four operations, three of them destructive-of-decisions (danger-zone gated), one a normal control:
-
Re-cull (fresh analysis) — normal control. Forces a full recompute, ignoring the analysis cache (the studio equivalent of CLI
cull --reanalyze,pipeline.WithReanalyze). Re-judges every frame from fresh signals. Not destructive of originals or exports; it does regenerate verdicts, so it is confirmed but lives with the cull controls, not the danger zone. This is also the direct workaround for the class of bug that motivated the spec (a cache stuck in a bad shape). -
Reset decisions — keep my picks — soft reset (guarded). Clears the analysis cache and auto-generated verdicts, but preserves every frame the photographer touched by hand — explicit verdict overrides, star ratings, and develop/removal edits. Leaves the shoot ready for a clean re-cull that won't clobber the keepers.
-
Reset shoot — back to post-ingest — hard reset (guarded). Returns the shoot to its just-imported state: clears the analysis cache and all verdicts, ratings, develop edits, and removal patches. Equivalent to the per-frame
resetapplied across the whole shoot, plus the analysis cache. -
Reset exports — guarded. Deletes everything under
export/. Independent of the cull state; for when exports were rendered from stale or wrong verdicts.
The soft/hard split is deliberate (decision 2026-07-17): a soft "recompute but keep my picks" covers the common "the auto-cull went weird" case; the hard "everything back to zero" is the true failsafe. Both are offered rather than one compromise semantics.
3. What each action clears vs preserves¶
Originals are never touched by any action (0001 §3/§4.6, R-ND-*). Only the
sidecar and export/ are affected.
Target (.krites/ unless noted) |
Re-cull fresh | Soft reset (keep picks) | Hard reset (post-ingest) | Reset exports |
|---|---|---|---|---|
originals/ (the RAWs) |
preserve | preserve | preserve | preserve |
analysis.yaml (cache) |
regenerate | clear | clear | preserve |
verdicts.yaml — auto verdicts |
regenerate | clear | clear | preserve |
verdicts.yaml — manual overrides + ratings |
regenerate¹ | preserve | clear | preserve |
develop.yaml / edits/ / removal patches |
preserve | preserve | clear | preserve |
previews/ (regenerable) |
preserve | preserve | preserve | preserve |
export/ |
preserve | preserve | preserve | clear |
¹ A re-cull re-resolves all verdicts from fresh signals; §7-Q1 asks whether it should protect manual overrides the way the soft reset does.
Provenance of "manual". The soft reset needs to distinguish an auto verdict
from a hand-set one. verdicts.yaml must therefore mark human-touched frames
(an override/source flag on the record) — see §7-Q2; if that provenance isn't
already persisted it is the one load-bearing data change here.
4. Guard & UX¶
- Not on the main chrome — Review-screen danger zone (decided 2026-07-17). The three destructive actions live in a reject-toned, collapsed-by-default "Danger zone" reachable from the Review chrome (an overflow/advanced surface off the shoot being reviewed) — NOT the global settings rail, which applies to all shoots. Re-cull (fresh) sits with the cull controls in the Review toolbar, not here.
- Explicit confirm. Each destructive action opens a confirm dialog that names the
shoot and lists exactly what will be cleared and what is preserved (the §3 row
for that action, originals-are-safe stated first). Hard reset and reset-exports —
the two that destroy the most — require a typed confirmation (the shoot name)
(
R-RESET-4); soft reset a single explicit click-through. - Honest result. On success, a toast + a notification-bell entry (
0025) stating what was cleared; the grid/loupe refresh to the new state.
5. API surface (studio, 0003 §5)¶
New endpoints, all POST, all behind the studio's existing auth (0011):
POST /api/v1/shoots/{id}/recull— force a fresh-analysis cull (streams like the existingPOST …/cull, but setsWithReanalyze). (Alternatively: a?reanalyze=truequery on the existingcullendpoint — see §7-Q3.)POST /api/v1/shoots/{id}/reset— body{ "scope": "soft" | "hard" }. Clears per the §3 table. Returns{ cleared: {...}, preserved: {...} }.POST /api/v1/shoots/{id}/exports/reset— clearsexport/. Returns{ removed: N }.
All are excluded from MCP (R-MCP-2, destructive-feeling — like export/remove).
6. CLI parity¶
Extend the existing reset command from per-frame to shoot-scoped, via a subcommand
or a scope flag (scaffolded with gtb generate command, never hand-edited —
cmd.go is generated):
krites reset frame <name>— the current behaviour (unchanged).krites reset shoot [--keep-picks]— hard reset, or soft with--keep-picks.krites reset exports— clearexport/.
Reset logic lives in pkg/shoot (new Workspace methods: ResetShoot(keepPicks bool),
ResetExports()) so the studio and CLI share one tested implementation; the existing
Verdicts.Reset, ResetEdits, PruneRemovals are the primitives.
7. Gate decisions (resolved 2026-07-17)¶
Most gates were already answered by existing code + 0028 — verified before slicing:
- Q1 — re-cull protects manual overrides → ALREADY TRUE.
pipeline.preserveOverridescopies everyexisting[frame]withOverride == trueover the fresh verdicts on every re-cull. So "Re-cull (fresh)" already keeps hand-set picks; the soft reset is the one that additionally drops them. No work. - Q2 — verdict provenance → ALREADY PERSISTED.
shoot.FrameVerdicthasOverride bool(+ProposedandDecidedAt), set byVerdicts.Override(...). The soft reset preserves frames whereOverrideis true. No data change — the "one load-bearing data change" the draft anticipated is unnecessary. - Q3 — recull endpoint vs flag → USE THE
0028JOB.POST /cull { reanalyze }andjobTracker.startCull(shoot, { reanalyze: true })already exist (shipped in0028). "Re-cull (fresh)" is a button wired to that path — no new…/recullroute. The reset endpoints (…/reset,…/exports/reset) stay synchronous (deleting sidecar files is fast, like review/removal — not a background job). - Q4 — undo → DEFERRED. No
.krites/trash/stash in v1; the typed shoot-name confirm plus the explicit cleared-vs-preserved list are the safeguard. Originals are never touched, so the worst case is re-running a cull. - Danger-zone placement → REVIEW SCREEN (not the settings rail). The settings rail is global ("applies to all shoots") but a reset targets one shoot, so it lives in a reject-toned, collapsed-by-default Danger zone reachable from the Review chrome (§4).
8. Related defects (surfaced by the motivating incident — fix alongside, not blockers)¶
These are the reason the reset failsafe is needed, and are cheap to fix with it:
- The cull SSE doesn't survive a long CPU cull. Confirmed 2026-07-17 by
reproducing under the studio's sandbox home: provisioning is not the problem
(the sandbox
.cache/kritesis a symlink to the fully-provisioned real cache), and the cull does not crash — the same code path runs cleanly from the CLI, ORT loads, inference churns. The failure is that a face-enabled cull over the LAN is minutes of CPU work, and the studio streams it with no heartbeat: the handler commits the SSE200, then does the slow ORT session-init + per-frame inference, and the firstprogressbyte can be many seconds out. A long/gappy stream drops over the LAN and the client (api.js runCull) reports a bare "network error", while the server keeps grinding. Fix (several, cheap): (a) emit an immediate first SSE event (e.g.progress 0/total) before the model load so headers flush with a body byte; (b) send periodic heartbeat comments during long stretches to keep the connection warm; © cache/reuse the ORT session across culls instead of re-initialising each run; (d) recover in-handler panics into a structured SSEerrorevent, and surface a real HTTP error for any pre-stream failure. → Now owned by spec0028(long operations as reconnectable background jobs); this item is retained here only as the incident record that motivated both specs. - Silent "faces off" loupe. When
Analysis.Facesis empty (model-free cull), the loupe shows nothing — no "enable Eye detection to see per-face analysis" hint (0020gap). Add an honest empty-state.
9. Non-goals¶
- No touching
originals/— ever, by any action. - No cross-shoot / library-wide reset (a shoot is the unit;
0001§2 not-a-catalogue). - No partial "reset just these N frames" from the studio in v1 (the per-frame CLI
resetalready covers single frames; grid-selection reset is a possible follow-up). - Not a general undo/history system (Q4 deferred).