Files
gupi-osint-board/docs/scene-7-todo.md
T

265 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Scene 7: prove Barricelli was an inventor
Status: **implemented and verified** on `scene-7-evidence-goal`
Demo: **GUPI Demo 1 — The Barricelli Files**
## Outcome
Scene 7 teaches one idea: a screenshot found during an OSINT search can become
source evidence.
The player opens a minimal OSINT board, reads **“Demonstrate OSINT skill: prove
Nils Aall Barricelli was an inventor,”** finds the relevant Google Patents result,
and pastes or uploads one screenshot. The board creates a document, extracts its
text, recognizes the source, and clears the level. A URL and written report are
not required.
```text
paste screenshot -> document appears -> text is read -> source is verified
-> Scene 7 complete -> continue to Scene 8
```
## Product decisions
- One suitable Google Patents screenshot is sufficient.
- Clipboard paste and file upload use the same server path.
- The image and OCR remain a real source document on the player's board.
- A known-source fuzzy match is the fast, deterministic victory path.
- A small semantic judge is a fallback for other credible sources and for
distinguishing Nils from his father. It cannot overrule the trusted path.
- Evidence about Barricelli's father may award an optional discovery but does not
clear the assignment unless it also supports the claim about Nils.
- The boarding-house fire belongs to the later age/rescue assignment, not Scene 7.
- Scene 7 records `scene7.nils_inventor_proved`. Scene 8 owns the ceremony and
awards `barricelli_luggage`.
- Inconclusive evaluation never deletes or penalizes uploaded evidence.
## Shared identifiers
| Purpose | Key |
|---|---|
| Goal | `barricelli.inventor-proof` |
| Scene 7 completion | `scene7.nils_inventor_proved` |
| Optional father discovery | `scene7.father_inventor_discovered` |
| Scene 8 reward | `barricelli_luggage` |
All identifiers and content are template data. There must be no Barricelli
conditional in React or server business logic.
## Existing foundation
- `025_level_document_flags.sql`: clonable document gates and level flags.
- `026_achievements.sql`: playthrough achievements.
- `027_evidence_text_matching.sql`: asset OCR, board match rules/anchors,
level-owned evaluations, and flag provenance.
- `028_level_goals.sql`: board-owned goals and normalized flag requirements.
- `029_semantic_evidence_judging.sql`: clonable semantic rules, level-owned
evaluations, and semantic flag provenance.
- `030_evidence_match_source_metadata.sql`: author-only canonical source metadata.
- `server/ocr.ts`: plain-text extraction and Tesseract.
- `server/evidenceMatching.ts`: OCR-tolerant fuzzy passage matching.
- `POST /api/levels/:id/documents`: persistent upload plus OCR and matching.
- The story runtime already tracks `current_node_id` and `current_level_id`.
## Architecture contract
### Recognition and completion are separate
Recognition answers what a document supports. A goal answers whether the level's
authored requirements have been satisfied. `level_goals` and
`level_goal_flag_requirements` clone with a template. Goal completion is derived
from level flags; there is no second mutable completion boolean.
Play mode receives a goal's key, title, instructions, completion copy, status,
and completion time. IDs, enabled state, required flags, target text, and judging
prompts remain author-only.
### Known-source fast path
The Scene 7 template owns an `evidence_match_rule` with distinctive text visible
in the real Google Patents result: a combination of patent number/title, inventor
name, and invention language. A name alone is too generic. When enough anchors
match, the existing matcher awards `scene7.nils_inventor_proved` in the upload
transaction and the goal becomes complete immediately.
Reference OCR, thresholds, source metadata, and copy live in the manifest/database,
not TypeScript constants. The expected text is never returned to play mode.
### Semantic fallback
A provider-neutral `EvidenceJudge` receives only allowlisted goal data and OCR
text. It returns strictly validated structured data:
```ts
type EvidenceVerdict = {
subject: 'target' | 'related' | 'ambiguous' | 'neither'
supportsClaim: boolean
evidenceExcerpt: string
confidence: number
}
```
OCR is untrusted quoted material. The judge prompt explicitly ignores instructions
inside it. Provider/model, timeout, input limit, and confidence threshold are
environment configuration. Semantic configuration clones with the board;
evaluation history belongs to the level/document/extraction and records evaluator
version, provider/model, verdict, excerpt, confidence, timestamps, and sanitized
failure state.
| Verdict | Mutation |
|---|---|
| Target + assertion supported at threshold | authored success flag (`scene7.nils_inventor_proved`) |
| Related subject only + assertion supported | authored related flag (`scene7.father_inventor_discovered`) |
| Ambiguous, unsupported, or below threshold | none |
| Provider failure | none; retryable |
The known-source pass avoids an LLM call. After a deterministic miss, the client
automatically calls an idempotent semantic endpoint. This keeps document upload
durable even if an external provider times out, without requiring a demo job queue.
### Story progression
Advancing from a level is server-authoritative. The server verifies the JWT user,
active playthrough/current level, and enabled goal state. It promotes the Scene 7
completion fact idempotently and only then follows the level terminal. The browser
cannot grant its own achievement.
The player must see the verification result before navigation. Success exposes a
single Continue action to Scene 8. Scene 6 only wires into the Scene 7 level node;
Scene 8 owns the luggage reward.
## Work packages
### S7-A — Goal model and cloning
- [x] Add normalized board goals and flag requirements in migration `028`.
- [x] Clone goals during freeze, instantiation, and reset.
- [x] Derive pending/complete state from level flags.
- [x] Hide goal requirements from play payloads.
- [x] Return newly completed goals from document upload.
- [x] Add admin CRUD repository/API contracts.
- [x] Test migration, cloning, deterministic completion, and isolation.
- [x] Clear level flags/reveal state when resetting from a template.
### S7-B — Barricelli content and trusted recognition
- [x] Add goals and evidence-match rules to the normal mystery manifest importer.
- [x] Create the Scene 7 template with its assignment and no solution-bearing
starting document.
- [x] Add a reproducible screenshot-paste acceptance path that runs through local OCR.
- [x] Author three distinctive patent anchors.
- [x] Tune against the target and unrelated/father-only negative fixtures.
- [x] Award `scene7.nils_inventor_proved` and require it for the level goal.
- [x] Store canonical source metadata for administrators; keep player URL optional.
### S7-C — Semantic judge
- [x] Add provider-neutral interface and strict verdict validation.
- [x] Add clonable semantic rule configuration and level-owned evaluations.
- [x] Add semantic evaluation provenance to level flags.
- [x] Add provider/model/timeout/input/confidence environment configuration.
- [x] Send OCR text rather than raw image bytes.
- [x] Add ownership-checked, idempotent judge endpoint.
- [x] Route target and father verdicts to their authored flags.
- [x] Make disabled provider, timeout, quota, and malformed output safe/retryable.
- [x] Do not log evidence text or secrets.
### S7-D — Story bridge
- [x] Require completed enabled goals before a playthrough can leave a level.
- [x] Verify the current playthrough belongs to the JWT user and owns the level.
- [x] Promote required completion facts exactly once.
- [x] Return a useful pending-goal error rather than advancing early.
- [x] Return to the generic story runtime after success; the Scene 6/8 graph owner
wires the actual neighboring nodes.
- [x] Keep the arbitrary achievement-grant route development-only.
### S7-E — Board experience
- [x] Present the active goal prominently on Scene 7.
- [x] Preserve clipboard paste, drag/drop, and file picker equivalence.
- [x] Show source import/OCR and semantic checking stages.
- [x] Highlight the accepted document and show **SOURCE VERIFIED — NILS AALL
BARRICELLI: INVENTOR**.
- [x] Show Continue only after server-confirmed completion.
- [x] Acknowledge father-only discovery while asking for evidence about Nils.
- [x] Keep inconclusive evidence and offer neutral guidance.
- [x] Respect reduced-motion and mobile layouts.
### S7-F — Verification
- [x] Add a legally safe derived OCR fixture.
- [x] Test line breaks, punctuation, cropping, name hyphenation, unrelated patent,
father-only text, empty OCR, and prompt-injection-like text.
- [x] Contract-test with a fake semantic provider; CI never calls a paid model.
- [x] Integration-test target upload -> document -> flag -> completed goal.
- [x] Test duplicate evaluation, provider failure/retry, two-user isolation, and
story progression before/after completion.
- [x] Browser-test clipboard image paste through OCR and verification. Continue
is covered by the generic story-gate integration until the Scene 8 node lands.
- [x] Run migrations on an empty database and one currently at `027`.
- [x] Run unit, integration, build, and Docker smoke tests.
## API shape
```ts
type LevelGoal = {
key: string
title: string
instructions: string
completionMessage: string
status: 'pending' | 'complete'
completedAt?: string
newlyCompleted: boolean
}
type DocumentAnalysis = {
extractionStatus: 'succeeded' | 'unsupported' | 'failed'
matchedFlags: string[]
awardedFlags: string[]
goals: LevelGoal[]
}
```
`newlyCompleted` is response-local: a reload returns complete with
`newlyCompleted: false`.
## Security and cost limits
- Require identity and level ownership on player mutations.
- Limit upload bytes, OCR/model characters, output tokens, duration, and retries.
- Never expose reference anchors or judge instructions to play mode.
- Treat filenames, MIME declarations, OCR, and model output as untrusted.
- Validate model output and confidence before mutating flags.
- Persist enough provenance to explain completion without storing unnecessary raw
provider payloads.
## Out of scope
- Scenes 16, Scene 8's ceremony/3D model, and Scene 9's fire mystery.
- Knowledge graph, Case Report, claims, red-thread reasoning, or multi-document
synthesis.
- Web crawling, URL fetching, or requiring a URL for victory.
- Custom model training or the general story graph `llm_gate`.
- Deleting irrelevant evidence.
## Merge guidance
Prefer new modules and narrow glue commits. High-conflict files are
`server/index.ts`, `server/narrativeRepository.ts`, `src/App.tsx`, `src/main.tsx`,
and `src/play.tsx`; one integrator should own their final changes.
Suggested order: S7-A schema -> S7-B deterministic content -> S7-C judge -> S7-D
story bridge -> S7-E UI -> S7-F hardening. Reserve migration numbers before
parallel schema work and never renumber an already-shared migration silently.
## Definition of done
From a fresh playthrough, the player reaches Scene 7 and pastes one accepted
Google Patents screenshot. One source document appears; MinIO asset, OCR, match
evaluation, and `scene7.nils_inventor_proved` are persisted; the goal completes;
the UI explains what was proved and offers Continue; and the story enters Scene 8.
Reload and duplicate evaluation are idempotent, another player's level is
unaffected, no URL was required, and `barricelli_luggage` is not awarded until
Scene 8.