Scene 7 teaches one idea: a screenshot found during an OSINT search can become
source evidence.

The player opens an otherwise minimal OSINT board, reads the assignment
**“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 a written report are not required.

The normal path must feel like one continuous action:

```text
paste screenshot -> document appears -> scanning feedback -> source verified
                 -> Scene 7 complete -> continue to Scene 8
=======
Scene 7 teaches two linked ideas: a screenshot found during an OSINT search can
become source evidence, and a finding is only as useful as the report that cites
and explains that 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, and recognizes the source. The player connects that document to the authored
Claim, completes the evidentiary statement, and files the generated Case Report.

```text
paste screenshot -> document appears -> source is verified -> connect to Claim
                 -> submit thin report -> provenance feedback -> accepted -> Scene 8

## Product decisions

These are decisions for this slice, not open design questions:

- One suitable Google Patents screenshot is sufficient evidence.
- Pasting and file upload are equivalent inputs and use the same server path.
- The uploaded image and extracted text are retained as a real document on the
  player's level.
- The known patent source is recognized with deterministic OCR/fuzzy matching.
  This is the fast, cheap, reproducible victory path.
- A small LLM judge is a semantic fallback for other credible evidence and for
  distinguishing Nils from his father. It must not overrule a trusted known-source
  match.
- The player does not have to provide a URL when the screenshot text itself
  establishes provenance.
- Evidence about Barricelli's father may unlock an optional discovery, but it
  must not clear the assignment unless the evidence also supports the claim
  about **Nils Aall Barricelli**.
- The boarding-house fire article belongs to the later age/rescue assignment,
  not to Scene 7's inventor victory condition.
- Scene 7 records completion; Scene 8 owns the merit ceremony and awards
  `barricelli_luggage`.
- A failed or inconclusive evaluation never deletes the uploaded document and
  never penalizes the player.

## Existing foundation — reuse it

Do not build a second upload, OCR, flag, or story system for this scene.

- Migration `025_level_document_flags.sql` provides clonable document gates,
  level flags, and reveal state.
- Migration `026_achievements.sql` provides playthrough achievements.
- Migration `027_evidence_text_matching.sql` provides immutable asset text
  extractions, board-owned match rules/anchors, level-owned evaluations, and
  auditable flag awards.
- `server/ocr.ts` extracts plain text and runs Tesseract for images.
- `server/evidenceMatching.ts` implements normalized fuzzy anchor matching.
- `POST /api/levels/:id/documents` already persists the document, OCR result,
  deterministic evaluations, and newly awarded level flags.
- Screenshot paste already routes through document upload in the board UI.
- The story graph already has level nodes and playthroughs with
  `current_node_id` and `current_level_id`.

The deterministic matcher has already handled noisy historic OCR, including a
hyphenated `Bar- ricelli`, at useful confidence. Scene 7 should add authored
patent anchors and a completion contract, not replace that matcher.

## Proposed flags and identifiers

Keep all identifiers authored in template data; these names are the recommended
contract between independently developed branches.

| Purpose | Key |
|---|---|
| Level goal | `barricelli.inventor-proof` |
| Scene 7 completion flag | `scene7.nils_inventor_proved` |
| Optional father discovery | `scene7.father_inventor_discovered` |
| Scene 8 reward | `barricelli_luggage` |

The first three are Scene 7 state. The last is a playthrough achievement awarded
by Scene 8, never by the document upload endpoint.

## Architecture

### 1. Separate recognition from completion

Recognition answers **“what does this document support?”** Completion answers
**“are this level's authored requirements now satisfied?”** Do not hide level
completion in a React conditional or special-case `Barricelli` in server code.

Add a small, generic, board-owned goal model in the next migration (currently
expected to be `028`; verify the migration number immediately before creating
it):

- `level_goals`
  - belongs to a board and clones with a template;
  - has a stable `goal_key`, player-facing title/instructions, enabled state, and
    optional completion message;
  - uses the existing `origin_*` pattern for cloned authoring objects.
- `level_goal_flag_requirements`
  - maps a goal to one or more required level `flag_key` values;
  - Scene 7 has one requirement: `scene7.nils_inventor_proved`;
  - all requirements are required for the first implementation. Add `any/all`
    policy only when a real authored level needs it.

Goal state is derived from level flags; do not add a second mutable `completed`
boolean that can drift out of sync. If completion needs a timestamp, record a
single idempotent goal-completion event with provenance.

### 2. Known-source fast path

Author one enabled `evidence_match_rule` on the Scene 7 template board. Its
anchors should be distinctive passages visible in the actual Google Patents
screenshot, such as a combination of patent number/title, inventor name, and
invention language. Do not rely on the name alone.

When enough anchors pass their authored thresholds, the existing evaluation
awards `scene7.nils_inventor_proved`. The goal requirement consequently becomes
satisfied in the same upload transaction.

The reference OCR, anchor phrases, thresholds, canonical source metadata, and
player-facing copy are template data. None belong in TypeScript constants or
React branches. Expected/reference text must not be returned in play-mode API
responses.

### 3. Semantic fallback, not a free-form LLM gate

Add a provider-independent `EvidenceJudge` interface in a new server module. It
receives only allowlisted goal data and extracted OCR text and returns validated
structured data, for example:

```ts
type EvidenceVerdict = {
  subject: 'nils' | 'father' | 'ambiguous' | 'neither'
  supportsInventorClaim: boolean
=======
- One suitable Google Patents screenshot is sufficient evidence, but not by itself
  a complete investigation.
- 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.
- Scene 7 begins with one normalized, pinned Claim exhibit: **“Nils Aall
  Barricelli was an inventor.”**
- A new thread begins with **“Proof that…”**. This placeholder intentionally
  produces a report that must be improved.
- An accepted source connected to the Claim can earn a qualified pass while the
  report is returned for missing date/source provenance.
- The source link is encouraged but optional; date, source citation, investigator,
  and a completed evidentiary statement are required.
- Story advancement requires an accepted report when the template marks its report
  as required.

## 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.
- `031_claim_case_reports.sql`: Claim exhibits, stable exhibit citations, report
  configuration, immutable submissions, and normalized submission issues.
- `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
}
```

The provider/model name comes from environment configuration. Do not hard-code a
Claude model identifier into level content or business logic. Treat OCR as
untrusted quoted material: the prompt must explicitly ignore instructions found
inside it, and the response must pass a strict schema before it can award a flag.

Persist semantic rule configuration with the board and clone it with the
template. Persist each evaluation against the level, document, extraction,
rule/evaluator version, model/provider, verdict, excerpt, confidence, timestamps,
and sanitized failure state. Add semantic-evaluation provenance to any level flag
it awards. Never put provider credentials in PostgreSQL.

Verdict routing for this scene:

| Verdict | Result |
|---|---|
| Nils + inventor claim supported, high confidence | award `scene7.nils_inventor_proved` |
| Father only + inventor claim supported | award `scene7.father_inventor_discovered`; do not complete |
| Ambiguous, neither, unsupported, or below threshold | retain document; award nothing |
| Provider unavailable/invalid response | retain document; mark evaluation retryable |

Keep this evaluator narrower than the generic story-graph `llm_gate`. Scene 7 is
judging a single uploaded source, not a report or arbitrary player state.

### 4. Two-step server flow

The primary Google Patents path remains synchronous and deterministic:

1. Upload/paste persists the asset, document, OCR extraction, fuzzy evaluation,
   flags, and current goal state in one transaction.
2. If the trusted rule clears the goal, return success immediately and do not
   spend an LLM call.
3. If OCR succeeded but no trusted rule clears the goal, the client automatically
   calls an idempotent semantic-judge endpoint for that document.
4. The semantic endpoint uses a strict timeout, persists its result, and returns
   refreshed goal state. A timeout is retryable and cannot roll back the upload.

This avoids coupling document durability to an external provider without
requiring a job queue for the demo. Make semantic evaluation idempotent for the
same `(level, document, goal/rule, evaluator_version)`.

### 5. Story progression contract

Completing a board goal must be a server-authoritative transition:

- verify that the JWT user owns the active playthrough;
- verify that its `current_level_id` is the level being evaluated;
- observe the derived completed goal;
- idempotently record/promote `scene7.nils_inventor_proved` into the playthrough
  state needed by the story runtime;
- expose Scene 7's successful terminal so the player can continue to Scene 8.

Do not let the browser award achievements through the current development-only
achievement route. Do not make upload silently navigate before the player sees
what was learned. Show the verification result, then expose a single **Continue**
action (or a short authored transition that ends in the same action).

The Scene 6 branch only needs to route its successful terminal to the Scene 7
level node. The Scene 8 branch may depend on the completion state above and owns
the `barricelli_luggage` award.

## Work packages

The packages are ordered for integration, but most implementation can happen on
separate branches after the contracts above are agreed.

### S7-A — Goal model and template cloning

- [ ] Confirm the next free migration number; never edit applied migrations
  `025`–`027`.
- [ ] Add `level_goals` and `level_goal_flag_requirements` with board-scoped
  foreign keys, uniqueness, indexes, and comments.
- [ ] Extend template freeze/clone/instantiate so goals and requirements are
  copied and retain origin provenance.
- [ ] Derive `pending | complete` goal state from the level's current flags.
- [ ] Add repository tests for cloning, isolation between two playthroughs, and
  idempotent completion.
- [ ] Keep the schema generic; there must be no Barricelli-specific column or
  table.

### S7-B — Scene content and deterministic recognition

- [ ] Create/import the Scene 7 template and its brief as data.
- [ ] Start the board without any solution-bearing document.
- [ ] Obtain the exact target Google Patents screenshot used for acceptance and
  run it through the local OCR service.
- [ ] Author two or more distinctive match anchors from that extraction; avoid a
  generic `Nils Barricelli`-only rule.
- [ ] Tune thresholds against the target screenshot plus negative fixtures.
- [ ] Configure the rule to award `scene7.nils_inventor_proved`.
- [ ] Configure the goal requirement to consume that flag.
- [ ] Store canonical patent/source metadata for administrators, while keeping a
  pasted URL optional for players.
- [ ] Add the content to the normal manifest/import path rather than SQL seed
  literals or frontend code.

### S7-C — Semantic judge

- [ ] Add the provider-neutral `EvidenceJudge` interface and strict verdict
  schema.
- [ ] Add board-owned semantic rule configuration and level-owned evaluation
  history with clone support and flag provenance.
- [ ] Add environment variables for provider, model, timeout, maximum OCR
  characters, and confidence threshold; document safe defaults in
  `.env.example` without overwriting concurrent OCR configuration work.
- [ ] Send extracted text, not raw image bytes, unless a later explicit design
  requires a vision model.
- [ ] Delimit and escape untrusted OCR content in the prompt.
- [ ] Add an authenticated, ownership-checked, idempotent document-judge endpoint.
- [ ] Award the completion or father-discovery flag only from validated persisted
  verdicts.
- [ ] Make timeouts, malformed responses, quota failures, and disabled provider
  safe and retryable.
- [ ] Do not log full evidence text or provider credentials.

### S7-D — API and story bridge

- [ ] Return compact goal state from the level response and document-upload
  response: goal key, status, newly completed state, and player-facing message.
- [ ] Never return reference anchors, expected text, private evaluator prompts,
  or unpublished author data in play mode.
- [ ] Add the semantic fallback endpoint/result to the typed client API.
- [ ] Resolve the active playthrough for the level and enforce user ownership.
- [ ] Promote completion server-side exactly once.
- [ ] Make Scene 7's success terminal available only after the required goal is
  complete.
- [ ] Route that terminal to the Scene 8 node without implementing Scene 8's
  ceremony in this branch.
- [ ] Remove or fence the player-facing development route that can arbitrarily
  grant achievements before production deployment.

### S7-E — Board experience

- [ ] Show the exact assignment prominently when Scene 7 opens.
- [ ] Preserve both clipboard paste and drag/file upload; both call the same API.
- [ ] Place the pasted screenshot as a new image document using the normal board
  placement rules.
- [ ] Show restrained stages such as **Saving source**, **Reading text**, and
  **Checking evidence** without blocking board interaction unnecessarily.
- [ ] On success, visually identify the accepted document and show:
  **SOURCE VERIFIED — NILS AALL BARRICELLI: INVENTOR**.
- [ ] After the player sees the result, expose one **Continue** action to Scene 8.
- [ ] On father-only evidence, acknowledge the useful discovery and make clear
  that evidence about Nils is still required.
- [ ] On inconclusive evidence, keep the document and provide neutral guidance;
  do not say that the player is wrong.
- [ ] Respect reduced-motion settings and provide readable mobile feedback.
- [ ] Do not introduce Scene 7 checks into generic exhibit components.

### S7-F — Tests and acceptance fixtures

- [ ] Add the actual Google Patents screenshot as a legally appropriate test
  fixture, or store a compact derived OCR fixture if redistributing the image is
  undesirable.
- [ ] Unit-test OCR normalization and fuzzy matching for realistic line breaks,
  punctuation, cropping, and name hyphenation.
- [ ] Add negative fixtures: unrelated patent, father-only evidence, a generic
  Barricelli biography, low-quality/empty OCR, and prompt-injection-like text.
- [ ] Contract-test the semantic judge with a fake provider; CI must not call a
  paid external model.
- [ ] Integration-test target upload -> one document -> completion flag -> goal
  complete, including a repeat upload/evaluation.
- [ ] Integration-test father-only -> discovery flag -> goal still pending.
- [ ] Integration-test provider failure -> document retained -> retry succeeds.
- [ ] Integration-test two users/playthroughs so one player's evidence cannot
  complete another player's level.
- [ ] Browser-test clipboard paste through the success state and Continue action.
- [ ] Run migrations against an empty database and an existing database at
  migration `027`.
- [ ] Run the full unit/integration suite, production build, and Docker smoke test.

## API shape to converge on

Exact route naming may follow the repository's conventions, but the frontend and
backend branches should agree on a compact result like this before coding:

```ts
type LevelGoalState = {
  key: string
  title: string
  status: 'pending' | 'complete'
  newlyCompleted: boolean
  message?: string
}

type DocumentAnalysis = {
  extractionStatus: 'succeeded' | 'unsupported' | 'failed'
  matchedFlags: string[]
  awardedFlags: string[]
  semanticStatus: 'not_needed' | 'available' | 'pending' | 'succeeded' | 'failed'
  goals: LevelGoalState[]
}
```

`newlyCompleted` describes this mutation's effect and is not persisted as goal
state. Re-fetching a completed level returns `status: 'complete'` and
`newlyCompleted: false`.

## Security, privacy, and cost limits

- Player endpoints require the same JWT identity and level ownership checks as
  playthrough progression; admin authoring remains admin-only.
- Limit upload bytes, OCR text sent to the model, model output tokens, request
  duration, and retries.
- Do not expose answer anchors or semantic judging instructions to the browser.
- Do not trust filenames, MIME declarations, OCR text, or model output.
- Use schema validation and a confidence threshold before mutating flags.
- Store enough provenance to explain why a level cleared without retaining
  unnecessary provider request/response payloads.
- A deterministic trusted-source match saves cost and is authoritative. The LLM
  is never called merely to reconfirm it.

## Explicitly out of scope

- Terminal game, Glitch University signup, Dobby, and Glitch Hunter scenes
  (Scenes 1–6).
- Scene 8's ceremony/3D luggage implementation and Scene 9's fire mystery.
- A general knowledge graph, Case Report, claims, red-thread reasoning, or
  multi-document synthesis.
- Crawling the web, fetching a pasted URL, or validating a URL as a victory
  requirement.
- Training a custom OCR or language model.
- Generalizing the story graph's future `llm_gate`; this slice may share a
  provider adapter later, but does not depend on that larger feature.
- Automatic rejection or deletion of irrelevant player evidence.

## Merge guidance for independent branches

Prefer new modules and narrow glue commits. Current high-conflict files include
`server/index.ts`, `server/narrativeRepository.ts`, `src/App.tsx`, `src/main.tsx`,
and the play entrypoint. Assign one integrator to make the final small changes in
those files after the isolated work lands.

Suggested merge order:

1. S7-A schema/repository and clone support.
2. S7-B authored content and deterministic fixtures.
3. S7-C judge service/evaluation persistence.
4. S7-D story/API glue.
5. S7-E UI.
6. S7-F acceptance hardening.

Each branch should state its migration dependency and avoid renumbering an
already-shared migration silently. If two branches need schema changes, reserve
migration numbers before implementation or keep one branch schema-free.

## Definition of done

From a fresh playthrough, a player reaches Scene 7 and sees the inventor
assignment. They paste one accepted Google Patents screenshot. One source
document appears on their board, the server persists the asset and OCR, the
authored match rule records an auditable evaluation, and the level obtains
`scene7.nils_inventor_proved`. The UI clearly confirms what the evidence proved
and offers Continue; the story then enters Scene 8. Reloading preserves the
document and completed state, repeating the evaluation grants nothing twice,
another player's level is unaffected, no URL was required, and
`barricelli_luggage` has not yet been awarded.

=======
  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 1–6, Scene 8's ceremony/3D model, and Scene 9's fire mystery.
- Knowledge graph, general-purpose claim ontology, 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 sees one authored Claim.
They paste one accepted Google Patents screenshot, connect Exhibit 1 to the Claim,
and see **“Proof that…”** appear in the typewriter report. The first thin submission
passes the evidence but is returned for provenance; adding the date, source citation,
and a proper evidentiary statement produces an accepted report and enables Continue.
inIO asset, OCR, match provenance, stable exhibit number, connection, report
