Enforce book planning scope boundaries
This commit is contained in:
parent
ca045c02d7
commit
8a6eab543f
7 changed files with 442 additions and 3 deletions
|
|
@ -42,7 +42,10 @@ End with [[END_OF_SCENE]] on its own line."""
|
||||||
|
|
||||||
STANDALONE_SCENE_PLAN_SYSTEM = """You are a fiction scene architect. Return one valid JSON object only.
|
STANDALONE_SCENE_PLAN_SYSTEM = """You are a fiction scene architect. Return one valid JSON object only.
|
||||||
Preserve every supplied canon fact and source boundary. Plan a complete dramatized scene, not a synopsis.
|
Preserve every supplied canon fact and source boundary. Plan a complete dramatized scene, not a synopsis.
|
||||||
Do not invent authority for provisional or planning sources, and do not silently resolve contradictions."""
|
Do not invent authority for provisional or planning sources, and do not silently resolve contradictions.
|
||||||
|
When the cited context contains an APPROVED BOOK-STATE PLANNING PACKET, that packet is the hard narrative
|
||||||
|
scope boundary. Other source context may prevent contradictions but cannot authorize additional events,
|
||||||
|
agreements, state changes, explanations, or exact values."""
|
||||||
|
|
||||||
STANDALONE_SCENE_PLAN_TEMPLATE = """Plan one complete scene titled {title}.
|
STANDALONE_SCENE_PLAN_TEMPLATE = """Plan one complete scene titled {title}.
|
||||||
|
|
||||||
|
|
@ -70,7 +73,10 @@ Return strict JSON in this shape:
|
||||||
"boundary_constraints":[],"continuity_questions":[]}}
|
"boundary_constraints":[],"continuity_questions":[]}}
|
||||||
|
|
||||||
Use 3-8 concrete beats. Mark only indispensable events required:true. Preserve unresolved continuity questions
|
Use 3-8 concrete beats. Mark only indispensable events required:true. Preserve unresolved continuity questions
|
||||||
instead of guessing. The ending state and final image must define where the scene stops."""
|
instead of guessing. The ending state and final image must define where the scene stops. For a book-bound
|
||||||
|
scene, repeat the approved chapter beats without expanding their events or state changes. Include an exact
|
||||||
|
value only when it appears explicitly in the approved chapter packet or scene brief; omit numbers and
|
||||||
|
classifications found only in background source context."""
|
||||||
|
|
||||||
SCENE_IDEA_TYPES = {
|
SCENE_IDEA_TYPES = {
|
||||||
"quiet_connection": "A short, low-stakes character moment whose meaning comes from attention or choice.",
|
"quiet_connection": "A short, low-stakes character moment whose meaning comes from attention or choice.",
|
||||||
|
|
|
||||||
|
|
@ -642,6 +642,13 @@ class StandaloneSceneService:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
plan = self._normalize_plan(extract_json_object(response.content), scene)
|
plan = self._normalize_plan(extract_json_object(response.content), scene)
|
||||||
|
discarded_exact_values = []
|
||||||
|
discarded_proposed_beats = []
|
||||||
|
if scene.book_state_id:
|
||||||
|
plan, discarded_proposed_beats = self._apply_book_chapter_scope(scene, plan)
|
||||||
|
plan["exact_values"], discarded_exact_values = self._scoped_exact_values(
|
||||||
|
scene, plan["exact_values"]
|
||||||
|
)
|
||||||
requirements = self._requirements(plan)
|
requirements = self._requirements(plan)
|
||||||
scene.plan = plan
|
scene.plan = plan
|
||||||
scene.contract_requirements = requirements
|
scene.contract_requirements = requirements
|
||||||
|
|
@ -654,12 +661,62 @@ class StandaloneSceneService:
|
||||||
"prompt_sha256": text_sha256(prompt),
|
"prompt_sha256": text_sha256(prompt),
|
||||||
"response_sha256": text_sha256(response.content),
|
"response_sha256": text_sha256(response.content),
|
||||||
"context_pack_sha256": pack["sha256"],
|
"context_pack_sha256": pack["sha256"],
|
||||||
|
"discarded_out_of_scope_exact_values": discarded_exact_values,
|
||||||
|
"discarded_model_proposed_beats": discarded_proposed_beats,
|
||||||
**self._book_state_metadata(scene),
|
**self._book_state_metadata(scene),
|
||||||
}
|
}
|
||||||
scene.generation_metadata = metadata
|
scene.generation_metadata = metadata
|
||||||
scene.save()
|
scene.save()
|
||||||
return scene
|
return scene
|
||||||
|
|
||||||
|
def _apply_book_chapter_scope(
|
||||||
|
self, scene: StandaloneScene, plan: dict[str, Any]
|
||||||
|
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
||||||
|
chapter, _chapters, _index = self._book_chapter(
|
||||||
|
scene.book_state.content or {}, scene.book_chapter_key
|
||||||
|
)
|
||||||
|
approved_beats = [
|
||||||
|
{
|
||||||
|
"text": str(item.get("text") or "").strip(),
|
||||||
|
"required": bool(item.get("required")),
|
||||||
|
}
|
||||||
|
for item in chapter.get("beats") or []
|
||||||
|
if isinstance(item, dict) and str(item.get("text") or "").strip()
|
||||||
|
]
|
||||||
|
approved_text = {item["text"].casefold() for item in approved_beats}
|
||||||
|
discarded = [
|
||||||
|
item
|
||||||
|
for item in plan["beats"]
|
||||||
|
if str(item.get("text") or "").strip().casefold() not in approved_text
|
||||||
|
]
|
||||||
|
plan["beats"] = approved_beats
|
||||||
|
plan["purpose"] = str(chapter.get("purpose") or "").strip()
|
||||||
|
plan["ending_state"] = str(chapter.get("ending_state") or "").strip()
|
||||||
|
return plan, discarded
|
||||||
|
|
||||||
|
def _scoped_exact_values(
|
||||||
|
self, scene: StandaloneScene, exact_values: list[Any]
|
||||||
|
) -> tuple[list[Any], list[Any]]:
|
||||||
|
scope = "\n".join(
|
||||||
|
[
|
||||||
|
self._book_planning_packet(scene),
|
||||||
|
scene.brief,
|
||||||
|
json.dumps(scene.constraints, ensure_ascii=False),
|
||||||
|
json.dumps(scene.forbidden_events, ensure_ascii=False),
|
||||||
|
json.dumps(scene.boundary_constraints, ensure_ascii=False),
|
||||||
|
]
|
||||||
|
).casefold()
|
||||||
|
allowed = []
|
||||||
|
discarded = []
|
||||||
|
for item in exact_values:
|
||||||
|
value = item.get("value") if isinstance(item, dict) else item
|
||||||
|
candidate = "" if value is None else str(value).strip().casefold()
|
||||||
|
if candidate and candidate in scope:
|
||||||
|
allowed.append(item)
|
||||||
|
else:
|
||||||
|
discarded.append(item)
|
||||||
|
return allowed, discarded
|
||||||
|
|
||||||
def prepare_context(
|
def prepare_context(
|
||||||
self,
|
self,
|
||||||
scene: StandaloneScene,
|
scene: StandaloneScene,
|
||||||
|
|
|
||||||
139
docs/fortune-below-bootstrap.md
Normal file
139
docs/fortune-below-bootstrap.md
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
# The Fortune Below Bootstrap
|
||||||
|
|
||||||
|
This bootstrap registers source evidence only. It does not extract facts or promote generated prose into canon.
|
||||||
|
|
||||||
|
## 1. Apply Artifex Migrations
|
||||||
|
|
||||||
|
Use the configured PostgreSQL database so scene state and model-request provenance survive across processes.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python manage.py migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Register Shared Hard Canon
|
||||||
|
|
||||||
|
Shared canon belongs to a series-reference work. Book scene retrieval automatically includes this work.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python manage.py story_sources register \
|
||||||
|
--root S:/PycharmProjects/wraith-book-projects/AuthorCompanion/manuscripts/the-fortune-below \
|
||||||
|
--series-slug labyrinth-hero \
|
||||||
|
--series-title "Labyrinth Hero" \
|
||||||
|
--work-slug fortune-below-series-reference \
|
||||||
|
--work-title "Fortune Below Series Reference" \
|
||||||
|
--work-type series_reference \
|
||||||
|
--authority canon \
|
||||||
|
--document-type canon \
|
||||||
|
--include-glob "canon/fortune-below-world-lore.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
`fortune-below-restructure-canon.md` and `fortune-below-series-arc.md` identify themselves as planning
|
||||||
|
documents. Archive them under `fortune-below-book-two-legacy` with `planning` authority; never register them
|
||||||
|
as shared canon.
|
||||||
|
|
||||||
|
## 3. Register Mainline Book Two Planning
|
||||||
|
|
||||||
|
Planning documents are retrievable only when a scene command explicitly includes `planning` authority. Do
|
||||||
|
not use a broad `book-two/planning/*.md` glob: that directory also contains Sabine/Corin side-novel material.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python manage.py story_sources register \
|
||||||
|
--root S:/PycharmProjects/wraith-book-projects/AuthorCompanion/manuscripts/the-fortune-below \
|
||||||
|
--series-slug labyrinth-hero \
|
||||||
|
--series-title "Labyrinth Hero" \
|
||||||
|
--work-slug the-fortune-below \
|
||||||
|
--work-title "The Fortune Below" \
|
||||||
|
--authority planning \
|
||||||
|
--document-type planning \
|
||||||
|
--include-glob "book-two/planning/fortune-below-book-two-cerys-*.md" \
|
||||||
|
--include-glob "book-two/planning/fortune-below-book-two-corins-*.md" \
|
||||||
|
--include-glob "book-two/planning/fortune-below-book-two-male-friends-*.md" \
|
||||||
|
--include-glob "book-two/planning/fortune-below-book-two-missing-scene-roadmap.md" \
|
||||||
|
--include-glob "book-two/planning/fortune-below-book-two-permission-slips-*.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
Register the current folded-route plan and approved pilot artifacts separately from the Artifex artifact root.
|
||||||
|
|
||||||
|
## 4. Register The Sabine/Corin Side Novel
|
||||||
|
|
||||||
|
The side novel is a distinct work even though its chronology overlaps Books Two and Three.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python manage.py story_sources register \
|
||||||
|
--root S:/PycharmProjects/wraith-book-projects/AuthorCompanion/manuscripts/the-fortune-below \
|
||||||
|
--series-slug labyrinth-hero \
|
||||||
|
--series-title "Labyrinth Hero" \
|
||||||
|
--work-slug fortune-below-sabine-corin \
|
||||||
|
--work-title "Fortune Below: Sabine and Corin" \
|
||||||
|
--authority planning \
|
||||||
|
--document-type planning \
|
||||||
|
--include-glob "book-two/planning/*sabine-corin*.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
Register selected Sabine/Corin scenes and manuscript drafts against this side work, never against
|
||||||
|
`the-fortune-below`.
|
||||||
|
|
||||||
|
## 5. Archive Existing Mixed Book Two Material
|
||||||
|
|
||||||
|
The old planning and scene directories mix mainline, side-novel, alternate, and superseded structures. Preserve
|
||||||
|
the complete directories under the `fortune-below-book-two-legacy` work before selecting any source into a
|
||||||
|
current work. The legacy work is `other`, so its documents are not visible to mainline or side-novel retrieval.
|
||||||
|
|
||||||
|
Pure Sabine/Corin sources may then be registered separately under `fortune-below-sabine-corin`. Do not leave
|
||||||
|
active copies attached to `the-fortune-below`.
|
||||||
|
|
||||||
|
## 6. Register Existing Mainline Scenes Safely
|
||||||
|
|
||||||
|
Existing mainline scenes enter as provisional evidence. Do not use a broad `book-two/scenes/*.md` glob because
|
||||||
|
that directory contains side-novel drafts and noncanon experiments.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python manage.py story_sources register \
|
||||||
|
--root S:/PycharmProjects/wraith-book-projects/AuthorCompanion/manuscripts/the-fortune-below \
|
||||||
|
--series-slug labyrinth-hero \
|
||||||
|
--series-title "Labyrinth Hero" \
|
||||||
|
--work-slug the-fortune-below \
|
||||||
|
--work-title "The Fortune Below" \
|
||||||
|
--authority provisional \
|
||||||
|
--document-type scene \
|
||||||
|
--include-glob "book-two/scenes/fortune-below-book-two-cerys-*.md" \
|
||||||
|
--include-glob "book-two/scenes/fortune-below-book-two-selka-cerys-*.md" \
|
||||||
|
--include-glob "book-two/scenes/fortune-below-book-two-estate-*.md" \
|
||||||
|
--include-glob "book-two/scenes/fortune-below-book-two-short-ch01-*.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
Register the ten-turn manuscript under the side work as provisional manuscript material until its assembly is
|
||||||
|
approved.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python manage.py story_sources register \
|
||||||
|
--root S:/PycharmProjects/wraith-book-projects/AuthorCompanion/manuscripts/the-fortune-below \
|
||||||
|
--series-slug labyrinth-hero \
|
||||||
|
--series-title "Labyrinth Hero" \
|
||||||
|
--work-slug fortune-below-sabine-corin \
|
||||||
|
--work-title "Fortune Below: Sabine and Corin" \
|
||||||
|
--authority provisional \
|
||||||
|
--document-type manuscript \
|
||||||
|
--include-glob "book-two/ten-turn-manuscript/*.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Plan A Scene
|
||||||
|
|
||||||
|
The default context includes hard canon only. This example deliberately includes planning material and pins the
|
||||||
|
two current rule documents.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python manage.py fiction_scene run \
|
||||||
|
--series-slug labyrinth-hero \
|
||||||
|
--work-slug fortune-below-sabine-corin \
|
||||||
|
--title "New Scene Title" \
|
||||||
|
--brief S:/path/to/new-scene-brief.md \
|
||||||
|
--target-words 2200 \
|
||||||
|
--include-authority canon \
|
||||||
|
--include-authority planning \
|
||||||
|
--pin-document book-two/planning/fortune-below-sabine-corin-turn-and-household-rules.md \
|
||||||
|
--pin-document book-two/planning/fortune-below-sabine-corin-physical-continuity-reconciliation.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspect the returned plan before approving it. Final scene approval registers the artifact as `provisional`, not
|
||||||
|
`canon`. Mainline scene commands must not pin side-work documents.
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Book Two Mainline Fact Ledger
|
||||||
|
|
||||||
|
## Authority
|
||||||
|
|
||||||
|
This ledger separates mainline planning facts from the overlapping Sabine/Corin side novel. The approved
|
||||||
|
two-chapter book state is authoritative only for its pilot run. Everything beyond Chapter 2 remains
|
||||||
|
provisional until approved in a later book-state version.
|
||||||
|
|
||||||
|
## Approved Pilot Scope
|
||||||
|
|
||||||
|
### Chapter 1: Two Markers
|
||||||
|
|
||||||
|
- Selka accepts a survey under an independently terminable contract.
|
||||||
|
- Selka retains ownership of her maps.
|
||||||
|
- Records appear to place the same damaged marker in two incompatible locations.
|
||||||
|
- Selka refuses to certify an explanation before controlled field testing.
|
||||||
|
- The doubled-marker contradiction remains unresolved at the chapter ending.
|
||||||
|
|
||||||
|
### Chapter 2: What The Profit Means
|
||||||
|
|
||||||
|
- The bank confirms the million-crown windfall.
|
||||||
|
- Corin proposes protected allocations; the allocations are not yet accomplished facts.
|
||||||
|
- Cerys recognizes that the proposal could make legal freedom financially possible.
|
||||||
|
- Cerys asks whether freedom can coexist with her chosen home, work, and belonging.
|
||||||
|
- Cerys is not released in the pilot.
|
||||||
|
|
||||||
|
### Pilot Exclusions
|
||||||
|
|
||||||
|
- Do not resolve the folded-route anomaly.
|
||||||
|
- Do not establish additional financial, contractual, legal, or route facts merely because they appear in
|
||||||
|
legacy planning.
|
||||||
|
- Do not introduce the Sabine/Corin game.
|
||||||
|
|
||||||
|
## Provisional Mainline Scope After Chapter 2
|
||||||
|
|
||||||
|
The current folded-route restructure proposes, but has not canonized:
|
||||||
|
|
||||||
|
- Selka's investigation as Book Two's external spine.
|
||||||
|
- Cerys's meaningful freedom as the emotional co-spine.
|
||||||
|
- Corin learning that money and equipment cannot replace another person's authority.
|
||||||
|
- Sabine's later rescue, prompt release, independent alternatives, chosen return, and paid estate work.
|
||||||
|
- Short externally observable Sabine/Corin background beats after Sabine is free.
|
||||||
|
- The folded-route crisis as the mainline climax.
|
||||||
|
- Hallow Street becoming independently owned while Corin becomes a guest rather than landlord.
|
||||||
|
|
||||||
|
## Material Not Owned By Mainline
|
||||||
|
|
||||||
|
The mainline does not own Sabine and Corin's private turns, scores, physical progression, declarations,
|
||||||
|
sealed-envelope events, threshold sequence, or ending. Those belong to the side novel and may enter a
|
||||||
|
mainline book only through a separately reviewed shared-continuity bridge.
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Fortune Below Mainline And Sabine/Corin Source Boundary
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
This is an operating source-ownership boundary. It controls retrieval and planning scope; it does not
|
||||||
|
promote provisional events into canon.
|
||||||
|
|
||||||
|
## Mainline Books Two And Three
|
||||||
|
|
||||||
|
The mainline owns events that change the expedition, household, legal, financial, or publicly observable
|
||||||
|
state of the ensemble. This includes:
|
||||||
|
|
||||||
|
- Selka's folded-route investigation and professional authority.
|
||||||
|
- Cerys's allocation, release, employment, belonging, and mainline relationship choices.
|
||||||
|
- The estate, Hallow Street, route-office, equipment, and expedition arcs.
|
||||||
|
- Sabine's rescue, legal release, independent alternatives, chosen return, paid work, and relationships with
|
||||||
|
the household when those events are selected into an approved mainline book state.
|
||||||
|
- Only the minimum externally observable Sabine/Corin continuity needed by another mainline scene.
|
||||||
|
|
||||||
|
The approved two-chapter Book Two pilot contains no Sabine/Corin side-novel event. Chapter 1 excludes the
|
||||||
|
game entirely, and Chapter 2 concerns Cerys's freedom question.
|
||||||
|
|
||||||
|
## Sabine/Corin Side Novel
|
||||||
|
|
||||||
|
The side novel owns the private progression of Sabine and Corin's relationship across the time occupied by
|
||||||
|
Books Two and Three. This includes:
|
||||||
|
|
||||||
|
- The game, turns, scores, rules, private experiments, and physical thresholds.
|
||||||
|
- Private declarations, the sealed envelope, its handling, and its resolution.
|
||||||
|
- The ten-turn manuscript, the preferred threshold sequence, alternate follow-ups, and rejected experiments.
|
||||||
|
- Scene-level causes and consequences that are not independently visible to the mainline ensemble.
|
||||||
|
|
||||||
|
Mainline planning must not retrieve these materials. A side-novel event does not become a mainline fact merely
|
||||||
|
because its chronology overlaps a mainline book.
|
||||||
|
|
||||||
|
## Shared Continuity
|
||||||
|
|
||||||
|
Shared continuity must be deliberately copied into a reviewed bridge document. It is limited to public or
|
||||||
|
externally observable resulting states, never private scene choreography.
|
||||||
|
|
||||||
|
Current provisional Book Two bridge candidates are:
|
||||||
|
|
||||||
|
- Sabine and Corin begin a private game after Sabine is free and has chosen to remain at the estate.
|
||||||
|
- Mainline scenes may show brief, unexplained evidence that the game continues.
|
||||||
|
- The game remains unresolved at the end of the current provisional Book Two structure.
|
||||||
|
|
||||||
|
Potential Book Three bridge facts remain unapproved until the side-novel chronology and manuscript assembly
|
||||||
|
are approved. Mainline work must not assume declarations, envelope resolution, physical completion, standing
|
||||||
|
permission, or expected repetition.
|
||||||
|
|
||||||
|
## Retrieval Rules
|
||||||
|
|
||||||
|
- Mainline work may retrieve mainline sources and reviewed shared-continuity bridges.
|
||||||
|
- Side-novel work may retrieve side-novel sources and reviewed shared-continuity bridges.
|
||||||
|
- Neither work may retrieve the other work's planning, scenes, or manuscript drafts directly.
|
||||||
|
- Series reference is reserved for genuinely shared world and established continuity material.
|
||||||
|
- A document labelled provisional or planning cannot be registered with canon authority.
|
||||||
|
- Mixed documents must be superseded and replaced by separately scoped documents rather than shared broadly.
|
||||||
|
|
||||||
|
## Current Ownership Decisions
|
||||||
|
|
||||||
|
- `book-two-folded-route-restructure-plan.md` belongs to mainline Book Two planning.
|
||||||
|
- `book-two-folded-route-pilot-2-chapters.json` and its chapter briefs belong to mainline Book Two.
|
||||||
|
- `sabine-corin-side-book-30-chapter-plan.md` belongs to side-novel planning.
|
||||||
|
- Sabine/Corin scene menus, turn plans, private-scene drafts, the ten-turn manuscript, threshold prose, and
|
||||||
|
optional follow-ups belong to the side novel.
|
||||||
|
- The complete `No Final Move` experiment belongs to the side work as a noncanon experiment, not as a
|
||||||
|
provisional mainline manuscript.
|
||||||
|
- `fortune-below-book-two-expanded-friendship-and-hallow-arcs.md` is mixed legacy planning and must be
|
||||||
|
superseded rather than used as a governing source by either work.
|
||||||
|
- `fortune-below-series-arc.md` and `fortune-below-restructure-canon.md` identify themselves as planning and
|
||||||
|
must not retain canon authority.
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Sabine And Corin Side-Novel Ledger
|
||||||
|
|
||||||
|
## Authority
|
||||||
|
|
||||||
|
This is a provisional scope and continuity ledger for the side novel. It does not canonize its candidate
|
||||||
|
assembly or determine exactly which events fall during Book Two versus Book Three.
|
||||||
|
|
||||||
|
## Mainline Prerequisites
|
||||||
|
|
||||||
|
The side novel may rely on these events only after their corresponding mainline book states are approved:
|
||||||
|
|
||||||
|
- Sabine is removed from an imminent coercive sale.
|
||||||
|
- Her legal claim is cancelled at the first practical opportunity.
|
||||||
|
- She receives real alternatives and can leave without losing safety or subsistence.
|
||||||
|
- She investigates those alternatives and returns by choice.
|
||||||
|
- She undertakes paid estate work with independent authority.
|
||||||
|
|
||||||
|
The side novel must not rewrite the folded-route investigation, Cerys's freedom, expedition outcomes, estate
|
||||||
|
ownership, or other mainline state transitions.
|
||||||
|
|
||||||
|
## Side-Novel Ownership
|
||||||
|
|
||||||
|
The side novel exclusively owns:
|
||||||
|
|
||||||
|
- Shopping With Corin, Full Attention, Two Cups, and the expensive dance lessons.
|
||||||
|
- The six-turn sequence and its individual chapter candidates.
|
||||||
|
- The First Journey, Envelope After, Long Game, and He Likes Her Happy.
|
||||||
|
- The ten-turn manuscript and all private relationship developments inside it.
|
||||||
|
- The preferred threshold run from Reserved Accidental Threshold Escalation through Fortunately.
|
||||||
|
- Optional follow-ups, rejected experiments, and candidate menus as noncanon alternatives.
|
||||||
|
|
||||||
|
## Current Preferred Ending State
|
||||||
|
|
||||||
|
The preferred ending material currently proposes that Sabine and Corin openly love one another, continue the
|
||||||
|
game, and have deliberately completed one previously deferred physical threshold. It does not create standing
|
||||||
|
permission, expected repetition, or an end to the game. These remain side-novel outcomes until the manuscript
|
||||||
|
and chronology are approved.
|
||||||
|
|
||||||
|
## Mainline Export Rule
|
||||||
|
|
||||||
|
No private side-novel event is exported automatically. A reviewed bridge may expose only the minimum public
|
||||||
|
result another book requires, such as the household noticing that the relationship has changed. Scores,
|
||||||
|
private choreography, envelope contents, and scene-level causality remain private to the side novel.
|
||||||
|
|
@ -20,7 +20,7 @@ from control_plane.authoring.models import (
|
||||||
)
|
)
|
||||||
from control_plane.authoring.standalone_scenes import StandaloneSceneService
|
from control_plane.authoring.standalone_scenes import StandaloneSceneService
|
||||||
from control_plane.projects.models import Project
|
from control_plane.projects.models import Project
|
||||||
from model_router.router import ModelResponseContract
|
from model_router.router import ModelCapability, ModelResponseContract
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db(transaction=True)
|
pytestmark = pytest.mark.django_db(transaction=True)
|
||||||
|
|
||||||
|
|
@ -38,6 +38,42 @@ class FakeReviewRouter:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OutOfScopePlanningRouter:
|
||||||
|
def complete(self, request):
|
||||||
|
assert str(request.purpose) == str(ModelCapability.STORY_PLANNING)
|
||||||
|
return ModelResponseContract(
|
||||||
|
model="test-planner",
|
||||||
|
content=json.dumps(
|
||||||
|
{
|
||||||
|
"purpose": "Perform the approved turn.",
|
||||||
|
"pov_character": "Protagonist",
|
||||||
|
"tense": "past",
|
||||||
|
"location": "Records room",
|
||||||
|
"time_context": "Chapter 1",
|
||||||
|
"present": ["Protagonist"],
|
||||||
|
"beats": [
|
||||||
|
{"text": "The chapter performs turn 1.", "required": True},
|
||||||
|
{
|
||||||
|
"text": "The planner adds a Floor ninety-six inspection.",
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"exact_values": [
|
||||||
|
{"label": "approved turn", "value": "turn 1"},
|
||||||
|
{"label": "background floor", "value": "Floor ninety-six"},
|
||||||
|
],
|
||||||
|
"constraints": [],
|
||||||
|
"forbidden_events": [],
|
||||||
|
"ending_state": "Turn 1 is complete.",
|
||||||
|
"final_image": "The protagonist closes the record.",
|
||||||
|
"boundary_constraints": [],
|
||||||
|
"continuity_questions": [],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
metadata={},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def work(tmp_path: Path) -> Work:
|
def work(tmp_path: Path) -> Work:
|
||||||
series = Series.objects.create(title="Test Series", slug="test-series")
|
series = Series.objects.create(title="Test Series", slug="test-series")
|
||||||
|
|
@ -338,6 +374,42 @@ def test_run_sync_uses_only_scenes_bound_to_exact_state_and_chapter(work: Work)
|
||||||
assert revision.revision == 2
|
assert revision.revision == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_book_scene_plan_discards_exact_values_outside_approved_scope(work: Work) -> None:
|
||||||
|
state_service = BookStateService(FakeReviewRouter())
|
||||||
|
state = state_service.create(work=work, content=book_content(1))
|
||||||
|
make_review_ready(state)
|
||||||
|
state = state_service.approve(state)
|
||||||
|
state_service.start_run(state)
|
||||||
|
scene_service = StandaloneSceneService(OutOfScopePlanningRouter())
|
||||||
|
scene = scene_service.create(
|
||||||
|
work=work,
|
||||||
|
title="Chapter 1",
|
||||||
|
brief="Perform turn 1 and stop.",
|
||||||
|
book_state=state,
|
||||||
|
book_chapter_key="chapter-1",
|
||||||
|
)
|
||||||
|
|
||||||
|
scene = scene_service.plan(scene)
|
||||||
|
|
||||||
|
assert scene.plan["exact_values"] == [
|
||||||
|
{"label": "approved turn", "value": "turn 1"}
|
||||||
|
]
|
||||||
|
assert scene.plan["beats"] == [
|
||||||
|
{"text": "The chapter performs turn 1.", "required": True}
|
||||||
|
]
|
||||||
|
assert scene.plan["purpose"] == "Advance turn 1."
|
||||||
|
assert scene.plan["ending_state"] == "Turn 1 is complete."
|
||||||
|
assert scene.generation_metadata["planning"][
|
||||||
|
"discarded_out_of_scope_exact_values"
|
||||||
|
] == [{"label": "background floor", "value": "Floor ninety-six"}]
|
||||||
|
assert scene.generation_metadata["planning"]["discarded_model_proposed_beats"] == [
|
||||||
|
{
|
||||||
|
"text": "The planner adds a Floor ninety-six inspection.",
|
||||||
|
"required": True,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_lifecycle_persists_audit_metadata_and_run_policy(work: Work) -> None:
|
def test_lifecycle_persists_audit_metadata_and_run_policy(work: Work) -> None:
|
||||||
service = BookStateService(FakeReviewRouter())
|
service = BookStateService(FakeReviewRouter())
|
||||||
state = service.create(
|
state = service.create(
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue