From b6787d909bd79b68ed3a4d143b7f6dd438e00fa2 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 31 Aug 2026 14:35:43 +0200 Subject: [PATCH] Bind newsroom conflicts to cited parties --- crates/ghostlight-dungeon/src/newspaper.rs | 226 +++++++++++++++++++-- crates/ghostlight-dungeon/src/registry.rs | 3 +- notes/fresh-workspace-handoff.md | 46 ++++- notes/ghostlight-current-system-map.md | 21 +- state/map.yaml | 8 +- 5 files changed, 265 insertions(+), 39 deletions(-) diff --git a/crates/ghostlight-dungeon/src/newspaper.rs b/crates/ghostlight-dungeon/src/newspaper.rs index f5013fa..aafe9e8 100644 --- a/crates/ghostlight-dungeon/src/newspaper.rs +++ b/crates/ghostlight-dungeon/src/newspaper.rs @@ -27,7 +27,7 @@ use std::{ const MAX_FRONT_PAGE_ARTICLES: usize = 6; const MAX_PUBLIC_RECORD_QUERY_RESULTS: usize = 24; const MAX_NARRATIVE_SELECTION_STEPS: usize = 12; -const NEWSROOM_CONTRACT_VERSION: &str = "character-newsroom.v14"; +const NEWSROOM_CONTRACT_VERSION: &str = "character-newsroom.v15"; const EDITION_LABEL: &str = "Current Edition"; const ALLOWED_SECTIONS: [&str; 6] = [ "Front Page", @@ -488,6 +488,23 @@ pub struct WorldNewspaperStoryPitch { pub tension: String, #[schemars(length(min = 1, max = 500))] pub public_question: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub conflict_axis: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct WorldNewspaperConflictAxis { + #[schemars(length(min = 1, max = 160))] + pub first_party: String, + #[schemars(length(min = 1, max = 160))] + pub first_party_citation: String, + #[schemars(length(min = 1, max = 160))] + pub opposing_party: String, + #[schemars(length(min = 1, max = 160))] + pub opposing_party_citation: String, + #[schemars(length(min = 1, max = 500))] + pub conflict: String, } #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] @@ -1045,7 +1062,8 @@ impl ModelAgentTool for NarrativeSelectionWorkbench<'_> { "focus_citation", "narrative_claim", "tension", - "public_question" + "public_question", + "conflict_axis" ], "properties":{ "lead":{"type":"boolean"}, @@ -1069,6 +1087,23 @@ impl ModelAgentTool for NarrativeSelectionWorkbench<'_> { "narrative_claim":{"type":"string","minLength":1,"maxLength":500}, "tension":{"type":"string","minLength":1,"maxLength":500}, "public_question":{"type":"string","minLength":1,"maxLength":500} + ,"conflict_axis":{ + "oneOf":[ + {"type":"null"}, + { + "type":"object", + "additionalProperties":false, + "required":["first_party","first_party_citation","opposing_party","opposing_party_citation","conflict"], + "properties":{ + "first_party":{"type":"string","minLength":1,"maxLength":160}, + "first_party_citation":{"type":"string","minLength":1,"maxLength":160}, + "opposing_party":{"type":"string","minLength":1,"maxLength":160}, + "opposing_party_citation":{"type":"string","minLength":1,"maxLength":160}, + "conflict":{"type":"string","minLength":1,"maxLength":500} + } + } + ] + } } } } @@ -1442,6 +1477,55 @@ fn validate_editorial_agenda( validate_editorial_frame(&pitch.narrative_claim, "story narrative claim")?; validate_editorial_frame(&pitch.tension, "story tension")?; validate_editorial_frame(&pitch.public_question, "public question")?; + if matches!( + pitch.narrative_function, + WorldNewspaperNarrativeFunction::Accountability + | WorldNewspaperNarrativeFunction::Opposition + | WorldNewspaperNarrativeFunction::CounterNarrative + ) && pitch.conflict_axis.is_none() + { + return Err(anyhow!( + "editorial pitch {index} declares adversarial work without binding two cited parties" + )); + } + if let Some(axis) = &pitch.conflict_axis { + validate_editorial_frame(&axis.first_party, "conflict first party")?; + validate_editorial_frame(&axis.opposing_party, "conflict opposing party")?; + validate_editorial_frame(&axis.conflict, "conflict axis")?; + if axis.first_party == axis.opposing_party { + return Err(anyhow!( + "editorial pitch {index} binds the same party to both sides" + )); + } + for (party, citation, label) in [ + (&axis.first_party, &axis.first_party_citation, "first"), + ( + &axis.opposing_party, + &axis.opposing_party_citation, + "opposing", + ), + ] { + if !selected.contains(citation.as_str()) { + return Err(anyhow!( + "editorial pitch {index} {label} party citation is not selected" + )); + } + let record = records + .iter() + .find(|record| record.record_id == *citation) + .ok_or_else(|| anyhow!("conflict citation is absent from the ledger"))?; + let named = record.facts.iter().any(|fact| { + fact.named_people.iter().any(|person| person.name == *party) + || fact.institutions.iter().any(|name| name == party) + || fact.populations.iter().any(|name| name == party) + }); + if !named { + return Err(anyhow!( + "editorial pitch {index} {label} party is not named by its citation" + )); + } + } + } } Ok(()) } @@ -1772,7 +1856,7 @@ async fn select_editorial_agenda( .collect::>(), }); let instructions = format!( - "You are {editor}, assignment editor of `{title}`. Investigate its frozen public ledger with query_public_records, then assign a page. An empty query browses; terms, exact entity names, status, channel, and an inspected cursor narrow or page. The workbench keeps a compact index of inspected records instead of replaying old query pages; use fetch_public_records with exact IDs when you need their full facts again. Cite only returned record IDs. Search backward when procedure hides the rupture and sideways for opposition, countermoves, reaction, scandal, lived cost, and independent beats.\n\nFirst decide the issue shape. A special issue is earned when one upheaval genuinely reorganizes several parts of public life; its lead alone owns the shared chronology and later stories assume that chronology while performing distinct narrative functions. A general issue carries independent beats whose stories are self-contained. Do not declare a special issue merely because one incident generated many records, and do not manufacture variety when the whole realm is plainly living through one rupture. State the evidence-based rationale.\n\nThe first pitch is the Front Page lead; later pitches use another allowed section. Give each pitch a narrative function and context role. Choose the staff journalist whose beat, biases, source instincts, voice, and blind spots make them the most revealingly partial observer, and state that assignment reason. Give them the exact record grouping, one focus record the lede cannot bury, a pointed narrative claim, the live tension, and the public question. A routine update to a vivid continuing incident should cite both and say what changed. Remembering, filing, warning, and planning are context unless they themselves produce a public consequence. Editorial frames may insinuate and judge but cannot invent concrete facts. The copy editor, not you or the reporters, owns fact checking.\n\nPropose before committing. The workbench returns issue shape, reporter fit, the lead, and what it would bury; revise or query again if the proof exposes a stronger or more honest page.\n\nPUBLICATION VOICE:\n{voice}\n\nCOMPACT STAFF BOOK:\n{staff}\n\nLEDGER PERIOD DIRECTORY (navigation only; query exact records before citation):\n{directory}", + "You are {editor}, assignment editor of `{title}`. Investigate its frozen public ledger with query_public_records, then assign a page. An empty query browses; terms, exact entity names, status, channel, and an inspected cursor narrow or page. The workbench keeps a compact index of inspected records instead of replaying old query pages; use fetch_public_records with exact IDs when you need their full facts again. Cite only returned record IDs. Search backward when procedure hides the rupture and sideways for opposition, countermoves, reaction, scandal, lived cost, and independent beats.\n\nFirst decide the issue shape. A special issue is earned when one upheaval genuinely reorganizes several parts of public life; its lead alone owns the shared chronology and later stories assume that chronology while performing distinct narrative functions. A general issue carries independent beats whose stories are self-contained. Do not declare a special issue merely because one incident generated many records, and do not manufacture variety when the whole realm is plainly living through one rupture. State the evidence-based rationale.\n\nThe first pitch is the Front Page lead; later pitches use another allowed section. Give each pitch a narrative function and context role. Choose the staff journalist whose beat, biases, source instincts, voice, and blind spots make them the most revealingly partial observer, and state that assignment reason. Give them the exact record grouping, one focus record the lede cannot bury, a pointed narrative claim, the live tension, and the public question. Accountability, opposition, and counter-narrative assignments must also bind a conflict_axis: two distinct people, institutions, or populations named by exact selected citations, plus the conflict the reporter should pursue. One cited grievance against an unnamed abstraction is not an opposing side. Consequence, profile, and independent stories may leave conflict_axis null when the ledger honestly supplies no adversarial motion. A routine update to a vivid continuing incident should cite both and say what changed. Remembering, filing, warning, and planning are context unless they themselves produce a public consequence. Editorial frames may insinuate and judge but cannot invent concrete facts. The copy editor, not you or the reporters, owns fact checking.\n\nPropose before committing. The workbench returns issue shape, reporter fit, the lead, and what it would bury; revise or query again if the proof exposes a stronger or more honest page.\n\nPUBLICATION VOICE:\n{voice}\n\nCOMPACT STAFF BOOK:\n{staff}\n\nLEDGER PERIOD DIRECTORY (navigation only; query exact records before citation):\n{directory}", editor = prepared.newsroom.assignment_editor.name, title = prepared.title, voice = prepared.editorial_voice, @@ -3558,32 +3642,36 @@ fn editorial_period_directory( ) -> Vec { const MAX_COMPLETED_HEADLINES_PER_PERIOD: usize = 12; - let mut periods = BTreeMap::, (BTreeSet, usize)>::new(); + let mut periods = BTreeMap::, (Vec, BTreeSet, usize)>::new(); for record in records { let entry = periods.entry(record.at).or_default(); if record.facts.iter().any(|fact| { fact.assertion_status == WorldNewspaperAssertionStatus::MaterialChangeCommitted }) { - entry.0.insert(record.headline.clone()); + if entry.1.insert(record.headline.clone()) { + entry.0.push(record.headline.clone()); + } } else { - entry.1 = entry.1.saturating_add(1); + entry.2 = entry.2.saturating_add(1); } } periods .into_iter() .rev() - .map(|(at, (completed_change_headlines, other_record_count))| { - let completed_change_count = completed_change_headlines.len(); - EditorialPeriodDirectoryEntry { - at, - completed_change_headlines: completed_change_headlines - .into_iter() - .take(MAX_COMPLETED_HEADLINES_PER_PERIOD) - .collect(), - completed_change_count, - other_record_count, - } - }) + .map( + |(at, (completed_change_headlines, _, other_record_count))| { + let completed_change_count = completed_change_headlines.len(); + EditorialPeriodDirectoryEntry { + at, + completed_change_headlines: completed_change_headlines + .into_iter() + .take(MAX_COMPLETED_HEADLINES_PER_PERIOD) + .collect(), + completed_change_count, + other_record_count, + } + }, + ) .collect() } @@ -4338,8 +4426,8 @@ mod tests { const QUERY_ALL_RECORDS: &str = r#"{"command":{"tool":"query_public_records","terms":[],"match_terms":"all","entity_names":[],"assertion_statuses":[],"channels":[],"order":"newest","cursor":null,"limit":24}}"#; const QUERY_FOUNDING_CRISIS: &str = r#"{"command":{"tool":"query_public_records","terms":["speaking child","severed delegation hands","failed grain pumps"],"match_terms":"any","entity_names":[],"assertion_statuses":[],"channels":[],"order":"oldest","cursor":null,"limit":24}}"#; - const ONE_STORY_AGENDA: &str = r#"{"command":{"tool":"propose_agenda","issue_shape":"special_issue","issue_shape_rationale":"The missing royal seal reorganizes public trust in court custody.","dominant_throughline":"The court made its private gambling debt a public crisis of custody and punished the official who exposed it.","reader_stake":"Readers must decide whether dismissal protects the seal or merely the court from its own confession.","story_pitches":[{"lead":true,"section":"Front Page","journalist_id":"aven-tarl","narrative_function":"lead_chronology","context_role":"owns_issue_chronology","assignment_reason":"Aven's court beat and appetite for status reversal fit the scandal's central chronology.","citations":["news:seal-scandal"],"focus_citation":"news:seal-scandal","narrative_claim":"The pawned royal seal and the treasurer's dismissal are one scandal.","tension":"The court admits the loss while directing the immediate consequence at the bearer of that admission.","public_question":"Who is being held accountable for the missing seal?"}]}}"#; - const TWO_STORY_AGENDA: &str = r#"{"command":{"tool":"propose_agenda","issue_shape":"special_issue","issue_shape_rationale":"Failures of custody at court and gate reorganize public access and confidence.","dominant_throughline":"Court custody fails at both the royal seal and the western gate.","reader_stake":"Readers depend on institutions that announce damage only after access or authority has already been compromised.","story_pitches":[{"lead":true,"section":"Front Page","journalist_id":"aven-tarl","narrative_function":"lead_chronology","context_role":"owns_issue_chronology","assignment_reason":"Aven owns the court chronology and its status reversal.","citations":["news:seal-scandal"],"focus_citation":"news:seal-scandal","narrative_claim":"The pawned seal and dismissal are the court's crisis of custody.","tension":"The confession exposes the loss while the treasurer absorbs the immediate institutional consequence.","public_question":"Who is being held accountable for the missing seal?"},{"lead":false,"section":"Dispatches","journalist_id":"lysa-fen","narrative_function":"consequence","context_role":"assumes_issue_chronology","assignment_reason":"Lysa's border beat tests how the custody crisis changes movement at the gate.","citations":["news:west-gate"],"focus_citation":"news:west-gate","narrative_claim":"The gate closure is a practical echo of neglected custody.","tension":"A cracked hinge will close a route at moonrise while travellers wait for a reopening time.","public_question":"How long will the western route remain closed?"}]}}"#; + const ONE_STORY_AGENDA: &str = r#"{"command":{"tool":"propose_agenda","issue_shape":"special_issue","issue_shape_rationale":"The missing royal seal reorganizes public trust in court custody.","dominant_throughline":"The court made its private gambling debt a public crisis of custody and punished the official who exposed it.","reader_stake":"Readers must decide whether dismissal protects the seal or merely the court from its own confession.","story_pitches":[{"lead":true,"section":"Front Page","journalist_id":"aven-tarl","narrative_function":"lead_chronology","context_role":"owns_issue_chronology","assignment_reason":"Aven's court beat and appetite for status reversal fit the scandal's central chronology.","citations":["news:seal-scandal"],"focus_citation":"news:seal-scandal","narrative_claim":"The pawned royal seal and the treasurer's dismissal are one scandal.","tension":"The court admits the loss while directing the immediate consequence at the bearer of that admission.","public_question":"Who is being held accountable for the missing seal?","conflict_axis":null}]}}"#; + const TWO_STORY_AGENDA: &str = r#"{"command":{"tool":"propose_agenda","issue_shape":"special_issue","issue_shape_rationale":"Failures of custody at court and gate reorganize public access and confidence.","dominant_throughline":"Court custody fails at both the royal seal and the western gate.","reader_stake":"Readers depend on institutions that announce damage only after access or authority has already been compromised.","story_pitches":[{"lead":true,"section":"Front Page","journalist_id":"aven-tarl","narrative_function":"lead_chronology","context_role":"owns_issue_chronology","assignment_reason":"Aven owns the court chronology and its status reversal.","citations":["news:seal-scandal"],"focus_citation":"news:seal-scandal","narrative_claim":"The pawned seal and dismissal are the court's crisis of custody.","tension":"The confession exposes the loss while the treasurer absorbs the immediate institutional consequence.","public_question":"Who is being held accountable for the missing seal?","conflict_axis":null},{"lead":false,"section":"Dispatches","journalist_id":"lysa-fen","narrative_function":"consequence","context_role":"assumes_issue_chronology","assignment_reason":"Lysa's border beat tests how the custody crisis changes movement at the gate.","citations":["news:west-gate"],"focus_citation":"news:west-gate","narrative_claim":"The gate closure is a practical echo of neglected custody.","tension":"A cracked hinge will close a route at moonrise while travellers wait for a reopening time.","public_question":"How long will the western route remain closed?","conflict_axis":null}]}}"#; const ACCEPTED_STORY_ACTION: &str = r#"{"tool":"file_story","headline":"Court Sells the Crown's Seal, Then the Treasurer","deck":"A gambling debt reaches the throne room and leaves one official carrying the blame.","dateline":"Room","paragraphs":["The Thorn Court has admitted that its royal seal was pawned to cover a dragon's gambling debt, a confession that turns private embarrassment into a public question of custody.","The treasurer who delivered that admission in open court was dismissed soon afterward. The court has explained the firing; it has not made the seal any less pawned."]}"#; const MALFORMED_LYSA_STORY_ACTION: &str = r#"{"tool":"file_story","headline":"Gate Trouble","deck":"The western route will close at moonrise.","dateline":"Yard","paragraphs":["Too short.","Still short."]}"#; const ACCEPTED_LYSA_STORY_ACTION: &str = r#"{"tool":"file_story","headline":"West Gate to Close at Moonrise","deck":"Masons will replace a cracked hinge after the palace bell keeper's warning.","dateline":"Yard","paragraphs":["Officials warn the west gate is unsafe, and the palace bell keeper says it will close at moonrise while masons replace its cracked hinge.","Travellers using the gate have been told when it will close, though no reopening hour was included in the announcement."]}"#; @@ -4413,6 +4501,7 @@ mod tests { narrative_claim: "The pawned seal opens the custody crisis.".into(), tension: "Admission and dismissal point in different directions.".into(), public_question: "Who answers for the seal?".into(), + conflict_axis: None, }, WorldNewspaperStoryPitch { lead: true, @@ -4426,6 +4515,7 @@ mod tests { narrative_claim: "The gate repeats the pattern.".into(), tension: "Access closes while repair begins.".into(), public_question: "When will the route reopen?".into(), + conflict_axis: None, }, ], }; @@ -4639,6 +4729,7 @@ mod tests { .into(), public_question: "Who benefits when the founding rupture leaves the page?" .into(), + conflict_axis: None, }], }, }; @@ -4717,6 +4808,7 @@ mod tests { .into(), public_question: "Who benefits when the founding rupture leaves the page?" .into(), + conflict_axis: None, }], }, }; @@ -4843,7 +4935,7 @@ mod tests { .unwrap(); let model = ScriptedNewspaperModel::new([ QUERY_FOUNDING_CRISIS, - r#"{"command":{"tool":"propose_agenda","issue_shape":"special_issue","issue_shape_rationale":"The founding rupture still organizes its administrative aftermath.","dominant_throughline":"The founding crisis survived its administrative aftermath.","reader_stake":"Readers still bear the consequences while institutions manage the paperwork.","story_pitches":[{"lead":true,"section":"Front Page","journalist_id":"mera-quill","narrative_function":"lead_chronology","context_role":"owns_issue_chronology","assignment_reason":"Mera's witness-first beat keeps the original lived rupture ahead of its paperwork.","citations":["news:archive:00"],"focus_citation":"news:archive:00","narrative_claim":"The original public record is the fact later notices cannot domesticate.","tension":"Administrative responses multiply while the founding rupture remains unresolved.","public_question":"Who benefits when the cause leaves the front page?"}]}}"#, + r#"{"command":{"tool":"propose_agenda","issue_shape":"special_issue","issue_shape_rationale":"The founding rupture still organizes its administrative aftermath.","dominant_throughline":"The founding crisis survived its administrative aftermath.","reader_stake":"Readers still bear the consequences while institutions manage the paperwork.","story_pitches":[{"lead":true,"section":"Front Page","journalist_id":"mera-quill","narrative_function":"lead_chronology","context_role":"owns_issue_chronology","assignment_reason":"Mera's witness-first beat keeps the original lived rupture ahead of its paperwork.","citations":["news:archive:00"],"focus_citation":"news:archive:00","narrative_claim":"The original public record is the fact later notices cannot domesticate.","tension":"Administrative responses multiply while the founding rupture remains unresolved.","public_question":"Who benefits when the cause leaves the front page?","conflict_axis":null}]}}"#, ]); let run = select_editorial_agenda(&model, &prepared, 3).await.unwrap(); @@ -4914,6 +5006,25 @@ mod tests { assert!(!first_request.contains("news:west-gate")); } + #[test] + fn period_directory_preserves_canonical_ledger_order_instead_of_lexical_headline_order() { + let mut campaign = campaign_with_two_news(); + for event in &mut campaign.events { + event.kind = "strategic_activity_outcome".into(); + } + campaign.news[0].headline = "Zulu: the first admitted change".into(); + campaign.news[1].headline = "Alpha: the later lexical trap".into(); + let records = public_news_records(&campaign).unwrap(); + let directory = editorial_period_directory(&records); + assert_eq!( + directory[0].completed_change_headlines, + [ + "Zulu: the first admitted change", + "Alpha: the later lexical trap" + ] + ); + } + #[test] fn narrative_workbench_has_no_source_count_hole_and_reads_legacy_agendas() { let records = public_news_records(&campaign_with_archive_news()).unwrap(); @@ -4931,6 +5042,7 @@ mod tests { narrative_claim: "One sharp custody failure links the selected record.".into(), tension: "Public consequence runs ahead of public accountability.".into(), public_question: "Who answers for the admitted failure?".into(), + conflict_axis: None, }; let agenda = WorldNewspaperEditorialAgenda { issue_shape: WorldNewspaperIssueShape::SpecialIssue, @@ -4959,6 +5071,76 @@ mod tests { assert_eq!(legacy.narrative_claim, "A legacy framing field."); } + #[test] + fn adversarial_assignments_bind_two_named_parties_to_selected_records() { + let mut campaign = campaign_with_two_news(); + campaign.institutions.insert( + "court".into(), + crate::domain::InstitutionState { + id: "court".into(), + name: "Thorn Court".into(), + resources: vec![], + goals: vec![], + posture: "defending custody".into(), + }, + ); + campaign.institutions.insert( + "wardens".into(), + crate::domain::InstitutionState { + id: "wardens".into(), + name: "West Gate Wardens".into(), + resources: vec![], + goals: vec![], + posture: "closing the route".into(), + }, + ); + campaign.events[0].institution_ids = vec!["court".into()]; + campaign.events[1].institution_ids = vec!["wardens".into()]; + campaign.events[1].summary = "West Gate Wardens announce that the west gate will close at moonrise while masons replace its cracked hinge.".into(); + let records = public_news_records(&campaign).unwrap(); + let pitch = WorldNewspaperStoryPitch { + lead: true, + section: "Front Page".into(), + journalist_id: "aven-tarl".into(), + narrative_function: WorldNewspaperNarrativeFunction::Opposition, + context_role: WorldNewspaperContextRole::SelfContained, + assignment_reason: "Aven follows the institutions contesting public access.".into(), + citations: vec!["news:seal-scandal".into(), "news:west-gate".into()], + focus_citation: "news:seal-scandal".into(), + narrative_claim: "Two authorities defend incompatible forms of custody.".into(), + tension: "Court custody and route closure collide in public.".into(), + public_question: "Which authority bears the consequence?".into(), + conflict_axis: Some(WorldNewspaperConflictAxis { + first_party: "Thorn Court".into(), + first_party_citation: "news:seal-scandal".into(), + opposing_party: "West Gate Wardens".into(), + opposing_party_citation: "news:west-gate".into(), + conflict: "They impose incompatible answers to failed custody.".into(), + }), + }; + let agenda = WorldNewspaperEditorialAgenda { + issue_shape: WorldNewspaperIssueShape::GeneralIssue, + issue_shape_rationale: "One custody rupture reorganizes court and gate.".into(), + dominant_throughline: "Custody has split into rival public authorities.".into(), + reader_stake: "Readers need to know whose closure governs movement.".into(), + story_pitches: vec![pitch], + }; + validate_editorial_agenda(&records, &canopy_ledger_newsroom(), &agenda, 2).unwrap(); + + let mut abstract_opponent = agenda; + abstract_opponent.story_pitches[0] + .conflict_axis + .as_mut() + .unwrap() + .opposing_party = "official authority".into(); + assert!( + validate_editorial_agenda(&records, &canopy_ledger_newsroom(), &abstract_opponent, 2) + .unwrap_err() + .to_string() + .contains("opposing party is not named") + ); + } + #[test] fn journalist_action_schema_is_stable_while_validation_owns_exact_datelines() { let records = public_news_records(&campaign_with_two_news()).unwrap(); @@ -4974,6 +5156,7 @@ mod tests { narrative_claim: "Court custody failed.".into(), tension: "Admission collides with accountability.".into(), public_question: "Who answers?".into(), + conflict_axis: None, }; let lead = JournalistWorkbench { records: &records, @@ -5005,6 +5188,7 @@ mod tests { narrative_claim: "The gate is the practical consequence.".into(), tension: "Repair closes a route.".into(), public_question: "When does it reopen?".into(), + conflict_axis: None, }; let dispatch = JournalistWorkbench { records: &records, diff --git a/crates/ghostlight-dungeon/src/registry.rs b/crates/ghostlight-dungeon/src/registry.rs index ed653c7..312c8cd 100644 --- a/crates/ghostlight-dungeon/src/registry.rs +++ b/crates/ghostlight-dungeon/src/registry.rs @@ -738,7 +738,8 @@ mod tests { "focus_citation":citation, "narrative_claim":"The singular accusation against three auditors is itself the scandal.", "tension":"The court's wording collapses three people into one accused figure.", - "public_question":"What process can answer an accusation framed this way?" + "public_question":"What process can answer an accusation framed this way?", + "conflict_axis":null }] } }) diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 3b14455..3875050 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -525,8 +525,33 @@ Preserve failed Run 70. The current compatibility correction omits legacy empty source times from both rendering and serialization, preserving the inherited v13 receipt bytes and -digest. New v14 nonempty source times remain rendered and serialized. Focused -regression, commit, push, and provider proof remain pending. +digest. New v14 nonempty source times remain rendered and serialized. Exact- +source Run 73 proves this compatibility path and final recomposition +mechanically. + +Run 73 at exact source `b827d897b9579c2091a67c363d35b8695288ba2f` +is terminal complete at +`/var/lib/gamecult/ghostlight-dungeon/acceptance/full-world-delvehold-b827d89-73`: +world revision 27, 12/12 waves, 240 events, and 514 news rows. Inherited +artifacts are byte-identical to Run 68, the manifest passes, and archive SHA-256 +is `090d05f304c8f3d33c9dccd728bffbf3e5dc2969a7aeeffc2f9d7bafe701ac8a`. + +Independent editorial review fails. The paper has a strong throughline, named +actors, scandal, and lived cost, but no opposing formal-authority motive, +response, or countermove, so it reads as one-sided polemic. Independent +grounding review fails on unsupported `None for Appeal`, inflation from a +witnessed event to public accessibility, and `prestigious instruments`. +Preserve Runs 64 through 73; mechanical completion is proven, final newspaper +acceptance is not. + +Current source already gives the assignment editor bounded full-ledger queries +and tells it to search sideways for opposition and countermoves. Do not sample +again yet. Inspect the actual Run 73 query transcript and selection against the +frozen fact desk to distinguish missing records, missed discovery, and rejected +selection. Inspect the copy-desk findings plus Night Editor input/output for the +three grounding failures. Preserve the newsroom roles and fact boundary; cut +missing context or misplaced ownership rather than adding a new judge or output +ban. Sparse inhabited destinations now compile a versioned civic apparatus in place. The first pass establishes institutions, resident populations, political edges, @@ -1600,12 +1625,17 @@ materializing a `NewsIssue`. 2. Preserve the delivered Run 60 PDF and its frozen-copy verification as the earlier baseline, plus the final Run 61 style/flow artifact, v2 manifest, grammar, hashes, frozen copy, and art assets recorded above. -3. Run the focused legacy source-time serialization regression, then commit and - push the empty-vector omission. Launch newsroom-only Run 71 from immutable - Run 68 at revision 27 without simulation or wave replay. Preserve failed Runs - 69 and 70, bind Run 71 to exact source and provenance, then obtain fresh - independent editorial and grounding verdicts plus archive acceptance. This - standalone run does not wait for the Delvehold adapter. +3. Finish and publish the mapped v15 assignment cut. Accountability, + opposition, and counter-narrative pitches bind two distinct named parties to + selected exact citations through a typed conflict axis; deterministic + admission rejects abstract or uncited antagonists. The bounded period + directory preserves canonical ledger order rather than lexically selecting + headlines. Run focused and full gates, commit, and push before one + newsroom-only run from immutable Run 68. Require mechanical and blind + editorial acceptance. Under the deadline contract, post-press grounding + findings become later-edition correction input rather than a same-edition + retry loop. Preserve Runs 64 through 73. This work does not wait for the + Delvehold adapter. 4. Build the Delvehold-owned adapter before any real Delvehold-owned player, civic, dungeon, contract, workshop, or quantitative-economy state participates. Defer multiple publications until separately authorized, or follow the operator's next directed work. diff --git a/notes/ghostlight-current-system-map.md b/notes/ghostlight-current-system-map.md index 4d4a1ee..4366f60 100644 --- a/notes/ghostlight-current-system-map.md +++ b/notes/ghostlight-current-system-map.md @@ -1372,10 +1372,12 @@ The deliberative newsroom boundary is live under this authority map: writers. `WorldKernel` remains the only world-event writer. - **Inputs:** one exact campaign revision, publication title and voice, article budget, a validated embodied newsroom roster, source-receipt ancestry, and - `character-newsroom.v14` contract. Before its first query, the assignment + `character-newsroom.v15` contract. Before its first query, the assignment editor receives a bounded per-period directory containing only timestamps, completed-material-change headlines, the full completed-change count, and - counts of other records. This is + counts of other records. Headline sampling preserves canonical ledger order + within a period rather than lexically privileging whichever headline sorts + first. This is navigation context: it cannot be cited, does not make a record inspected, and does not replace query or exact-ID fetch. Each reporter receives the common stable house charter first, that journalist's stable recurring identity second, and @@ -1406,7 +1408,13 @@ The deliberative newsroom boundary is live under this authority map: pitches. Every pitch names stable public-record IDs, chooses one focus record, section, journalist, narrative function, and context role, and supplies bounded assignment rationale, - narrative claim, tension, and public question. Article count plus bounded paragraphs and output tokens + narrative claim, tension, and public question. Accountability, opposition, + and counter-narrative pitches also carry a typed conflict axis: two distinct + named parties, one selected exact citation for each, and the editorially + framed conflict. Deterministic admission proves both parties are asserted by + their cited records; an abstract antagonist cannot occupy either side. + Consequence and independent stories may omit the axis when the ledger has no + honest adversarial motion. Article count plus bounded paragraphs and output tokens define page space. There is no citation-count page budget. A foundational record may support several continuing stories when each pitch uses it for a distinct throughline. One journalist agent files each assigned story and the @@ -1654,7 +1662,8 @@ The deliberative newsroom boundary is live under this authority map: step-one directive no longer sits in a prefix position that disappears when the first workbench snapshot replaces history. The cut adds no cache store, cache key registry, manual retention control, or retry compensator. - V14 adds only the bounded assignment-navigation directory and source + V15 adds the cited conflict axis and removes lexical bias from the bounded + directory. V14 adds only the bounded assignment-navigation directory and source timestamps in the audit. It does not add a source-packet layer, second event authority, alternate evidence selector, or reporter-context expansion. The roster, query projection, front-page proof, and article assembly reuse the @@ -1665,7 +1674,9 @@ The deliberative newsroom boundary is live under this authority map: added. - **Verification and build budget:** focused newspaper tests retain query admission, exact focus proof, agenda commit, selected-record alignment, and - audit grounding. V14 gates prove the assignment editor sees completed-change + audit grounding. V15 gates prove adversarial assignments bind two distinct + named parties to selected exact records and that period navigation preserves + canonical ledger order. V14 gates prove the assignment editor sees completed-change headlines across distinct periods before querying while stable record IDs and fact bodies remain absent, and that rendered audit citations expose source time. Existing V13 gates prove the stable selector instruction prefix with diff --git a/state/map.yaml b/state/map.yaml index 1be9aac..6611075 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -196,7 +196,7 @@ live_architecture: - Institution, faction, and consumer decision models active_subgoals: - - Close full-world newspaper acceptance over immutable Run 68 facts through a newsroom-only Run 71 after the legacy source-time serialization correction is verified and pushed. Preserve its admitted world and twelve committed waves without replay. The newsroom may navigate a derived per-period directory of completed public changes, while canonical events and news remain the sole fact authority. Preserve Runs 64 through 70 as immutable evidence. The Delvehold adapter is not a prerequisite while the Greathold remains an external boundary observer and no live Delvehold-owned player, civic, dungeon, or quantitative-economy state crosses the boundary. + - Finish and publish the v15 newsroom assignment cut, then run one newsroom-only acceptance over immutable Run 68. Adversarial pitches bind two named parties to selected exact citations; the period directory preserves canonical ledger order. Preserve the existing newsroom roles, one copy-desk checklist, one Night Editor close, canonical fact authority, immutable Runs 64 through 73, and the post-press correction boundary. The Delvehold adapter is not a prerequisite while the Greathold remains an external boundary observer and no live Delvehold-owned player, civic, dungeon, or quantitative-economy state crosses the boundary. - Connect a Delvehold-owned adapter to the admitted generic consumer contract: shared compiler/consumer WorldSeed admission, externally authoritative institution-or-Gestalt snapshots, attributed proposal acknowledgement, and committed-event newspaper composition over typed CultNet, without consumer-specific IDs or schemas in Ghostlight. - Extend strategic/offscreen named-person individuation beyond the proven emerge-then-act path so people plot across sustained political waves, acquire relationships, fold, and return without granting a cell, arena, or model commit authority. - Pressure the proven 200-cell dispatcher with authored consumer hierarchies and measure provider cost; the active budget ceiling is 240 while provider concurrency remains separately capped at 32. @@ -328,8 +328,8 @@ data_flow: current_status: summary: Ghostlight's generic consumer boundary admits sparse WorldSeeds, runs the hundreds-class parallel cell machinery used by Dungeon, and projects grounded newspapers from committed public events. Local acceptance proves 1,000 subjects across 200 cells and two-wave durable named-person emergence. Exact-source provider run 38 at `596e50679244b5d42b4cfdf70d723f815751b8ff` resumed the preserved world at revision 8 without replaying compilation or four committed locality elaborations, recovered 51 of 52 strategic-wave cells on pulse three and the final cell on pulse four, then committed all six waves through revision 14. Pushed commit `3a69306f3004e4af2cd57bba612a6ad8d1b8132f` implements the mapped clock-consequence owner and is verified locally: world clocks own declared consequence text and exact observable scope; a bounded agent may propose legacy scope binding, while `WorldKernel` alone admits it and materializes stable threshold-crossing events and public news through the common mutation path. Binding, emitted-event ancestry, mutation receipts, and the next newspaper boundary persist atomically. Exact-source run 39 resumed run 38 without replaying its six committed waves, bound clock consequences, committed six new waves through revision 21, and emitted Issues 7 through 12 plus a final digest. This completes the mechanical provider acceptance path; all inherited wave 1-6 checkpoint hashes match run 38 exactly and live services were unchanged. Independent blind editorial review returns FAIL: only Issue 7 approaches spicy, while Issues 8-12 are dominated by plans, filings, inspections, warnings, and abstract relationship deltas without betrayal, scandal, public reaction, sustained reversal, or material lived cost. That run-39 diagnosis is superseded for the current phase: run 51 closes the world and reconciliation mechanics, and the active defect belongs to newsroom narrative selection and self-contained semantic provenance over immutable facts. Independent grounding review also returns FAIL: Issue 9 inflates a planned expedited agenda into completion, while Issue 8 assigns unsupported feminine pronouns to Sela Venn and Eryn Tal. Other grounding mechanics pass: 208/208 citations resolve, no future events appear, no replay occurred, the player remained unchanged, all 116/116 manifest entries verify, the archive is exact, and root independently confirmed the live service PIDs and invocation IDs remained unchanged. Editorial and grounding acceptance are terminally failed for run 39. Eight titled elaborators use the generic agent harness under deterministic weighted scheduling, bounded parallelism, and Luna/Terra/Sol routing. Workers remain proposal-only; deterministic admission binds exact frozen schedules, dispatch, receipts, conflicts, and semantic verification, and `WorldKernel` alone owns atomic CultCache persistence. Destination reconciliation is a private transactional workbench, provider schema compatibility is lowered once at the shared model-connector boundary, and durable elaboration and strategic-pulse checkpoints bind to their canonical mutation proofs. The generic ModelAgent harness remains consumer-neutral transport. Provider run 23 remains stopped; earlier failed runs and checkpoint archives remain immutable provenance. The live daemons remain outside the acceptance body. Dungeon play remains regression pressure, the Delvehold game-side adapter remains unimplemented, and unpublished Mars and Kalsa destination previews remain unapproved. provider_acceptance_checkpoint: Exact pushed commit `139b8a6` completed Run 60 under `character-newsroom.v10` without world or per-wave replay. Its acceptance witness and terminal status bind the completed page and exact resume lineage. The independently dispatched blind reviewer, shown only the reader-facing fantasy newspaper, read it as a real left-populist civic broadsheet and did not immediately identify procedural generation, so the narrative reverse-Turing criterion passes. The review's substantive criticism is that the three stories feel like three rewrites of one briefing; accepted Run 60 remains immutable. The run used 96,902 prompt tokens and reported zero cache hits. Its delivered typeset artifact is `output/pdf/canopy-ledger-run-60-front-page.pdf`; frozen-source and visual verification remain accepted. Exact pushed `bc2ca39` then produced frozen-fact newsroom-only Run 61 Issues 13, 15, 16, 17, and 18 without world or wave model replay. Provider output proves `special_issue` convergence in Issues 13 and 15, independent `general_issue` beats in Issues 16 through 18, and assignment-selector cache hits. Blind review of Issues 13, 15, 16, and 17 passes both distinct recurring voices and issue-shape diversity, recognizing Ossan, Mera, Corin, and Lysa's biases plus modest cross-issue evolution. Run 62 exercised v12 without world or wave replay and failed only because two lead articles proposed invalid datelines; recurring reporter calls still recorded zero cache hits. Run 63 used exact pushed v13 `a52d57b`; Issues 17 and 18 completed before root stopped the final weekly recomposition, both repeated bylines again recorded zero cache hits, and live services remained unchanged. Preserve Runs 61 through 63 as stopped or completed immutable evidence. Copy defects remain later deadline-correction work rather than a reason to reopen accepted issue artifacts. - implementation_checkpoint: Exact pushed v13 `a52d57b` keeps one stable reporter action schema, places a common stable house charter before recurring identity and dynamic assignment, and projects deterministic `allowed_datelines` from cited records while retaining copy-desk factual authority and deterministic cited-place admission; 480 library tests and the all-target check pass. Run 63 falsifies reporter-cache pursuit as a useful acceptance target for sporadic independent filings. The proposed v14 cache-floor prompt growth was removed. Append-only agent contexts remain the natural cache beneficiary; accepted Run 60 remains immutable. The strategic cut extracts one shared `validate_strategic_individuation_proposals` owner used by resolution validation and by the scheduler before it returns an optional proposal. An invalid optional identity emits a semantic-invalid receipt and produces no individuation instead of aborting the recoverable wave. Focused valid and invalid regressions pass, and exact-source Run 68 exercises the corrected provider path through twelve committed waves; no further validator implementation is pending. The newsroom-only cut adds a navigation-only directory of completed public changes grouped by period and exact source timestamps to new v14 audit citations. The directory derives context from canonical records and gains no fact or world authority. Legacy empty source times are omitted from both rendering and serialization, preserving inherited v13 receipt bytes and digests; new v14 nonempty source times remain rendered and serialized. Focused regression, commit, push, and provider proof remain pending. - full_world_acceptance_checkpoint: Run 68 at exact source `69c811a2669fc38f19f9199bf57088dc250bfc3e` is mechanically complete at world revision 27 with 12/12 strategic waves, 240 events, 514 public news rows, and a final newspaper. Independent editorial and grounding reviews both fail. Earlier per-wave papers prove the immutable record already contains dramatic completed facts, including an organ breaching the rail capital, a dead pump, and families crossing borders; the final paper failed to make that committed change legible. Preserve Run 68 unchanged. Run 69 at source `84b31e57a462710eb939c4559e0437eb5d6a7a28` failed before any model call when immutable recomposition verification encountered an empty rendered `Source time:` line in inherited v13 per-wave issues. Run 70 at source `ec45952b843e102dc2ebddda3a3bbb4058687175` also failed before any model call: deserialized legacy v13 issues had empty `source_times`, but v14 serialization emitted that empty field and changed the recomposition digest from its receipt. The world remains revision 27 and neither simulation nor any newsroom model stage replayed in either attempt. Preserve failed Runs 69 and 70. Mechanical completion is proven, but newspaper acceptance is not. + implementation_checkpoint: Exact pushed v13 `a52d57b` keeps one stable reporter action schema, places a common stable house charter before recurring identity and dynamic assignment, and projects deterministic `allowed_datelines` from cited records while retaining copy-desk factual authority and deterministic cited-place admission; 480 library tests and the all-target check pass. Run 63 falsifies reporter-cache pursuit as a useful acceptance target for sporadic independent filings. The proposed v14 cache-floor prompt growth was removed. Append-only agent contexts remain the natural cache beneficiary; accepted Run 60 remains immutable. The strategic cut extracts one shared `validate_strategic_individuation_proposals` owner used by resolution validation and by the scheduler before it returns an optional proposal. An invalid optional identity emits a semantic-invalid receipt and produces no individuation instead of aborting the recoverable wave. Focused valid and invalid regressions pass, and exact-source Run 68 exercises the corrected provider path through twelve committed waves; no further validator implementation is pending. The newsroom-only cut adds a navigation-only directory of completed public changes grouped by period and exact source timestamps to new v14 audit citations. The directory derives context from canonical records and gains no fact or world authority. Legacy empty source times are omitted from both rendering and serialization, preserving inherited v13 receipt bytes and digests; new v14 nonempty source times remain rendered and serialized. Exact-source Run 73 proves that compatibility path and final recomposition mechanically. Current source gives the assignment editor bounded full-ledger queries and explicitly asks it to search sideways for opposition and countermoves; Run 73 falsifies the stronger assumption that this access and instruction alone guarantee balanced selection. The v15 cut keeps assignment ownership but makes accountability, opposition, and counter-narrative pitches bind two distinct named parties to selected exact citations through a typed conflict axis; deterministic admission rejects abstract or uncited antagonists. Period navigation preserves canonical ledger order instead of lexically selecting the first twelve distinct headlines. Formatting, 27 focused newspaper tests, 484 full library tests, and 16 strategic-smoke tests pass. Commit, push, and provider proof remain pending. + full_world_acceptance_checkpoint: Run 68 at exact source `69c811a2669fc38f19f9199bf57088dc250bfc3e` is mechanically complete at world revision 27 with 12/12 strategic waves, 240 events, 514 public news rows, and a final newspaper. Independent editorial and grounding reviews both fail. Earlier per-wave papers prove the immutable record already contains dramatic completed facts, including an organ breaching the rail capital, a dead pump, and families crossing borders; the final paper failed to make that committed change legible. Preserve Run 68 unchanged. Run 69 at source `84b31e57a462710eb939c4559e0437eb5d6a7a28` failed before any model call when immutable recomposition verification encountered an empty rendered `Source time:` line in inherited v13 per-wave issues. Run 70 at source `ec45952b843e102dc2ebddda3a3bbb4058687175` also failed before any model call because v14 serialization changed the legacy recomposition digest. Exact-source Run 73 `b827d897b9579c2091a67c363d35b8695288ba2f` is terminal complete at `/var/lib/gamecult/ghostlight-dungeon/acceptance/full-world-delvehold-b827d89-73`: world revision 27, 12/12 waves, 240 events, 514 news rows, inherited artifacts byte-identical to Run 68, manifest valid, and archive SHA-256 `090d05f304c8f3d33c9dccd728bffbf3e5dc2969a7aeeffc2f9d7bafe701ac8a`. Independent editorial review fails despite strong throughline, named actors, scandal, and lived cost because the page has no opposing formal-authority motive, response, or countermove and reads as one-sided polemic. Independent grounding review fails on unsupported `None for Appeal`, witnessed-to-public-accessibility inflation, and `prestigious instruments`. Preserve Runs 64 through 73. Mechanical completion is proven, but final newspaper acceptance is not. presentation_checkpoint: The historical press-layout and seed-ownership split are complete. `notes/historical-newspaper-layout-grammar.md` remains the durable grammar. Manifest v2 at `output/tex/canopy-ledger-run-61-style-1847-flow-723.manifest.toml` independently binds `style_seed=1847`, `flow_seed=723`, frozen source SHA-256 `8f195140f52a40ba705d6a66987146a1500ae89668e5d82b01d1792eb3afb882`, and the final artifact `output/pdf/canopy-ledger-run-61-style-1847-flow-723.pdf`, SHA-256 `a457f4b1702a7a52480e42fce0039f0dbe677ff32958e98f1c412b346237190c`. An identical seed pair produces byte-identical TeX and PDF; changing only flow preserves the exact style section while materially changing a plausible layout, and changing only style preserves flow topology. Cut-first and cut-midstory templates pass visual inspection; the failed split-card template is deleted. Frozen copy/assets, actual PDF `/BM /Multiply`, and economic-layer advertising authority remain invariant. - next_action: Run the focused legacy source-time serialization regression, then commit and push the `source_times` empty-vector omission. Launch newsroom-only Run 71 from immutable Run 68 at revision 27 without simulation or wave replay. Preserve failed Runs 69 and 70 and bind Run 71 to exact source and provenance, then obtain independent editorial and grounding review plus archive acceptance. After this newspaper boundary, require the Delvehold-owned adapter before any live Greathold player, civic, dungeon, contract, or quantitative-economy state or effects participate. Preserve the completed two-seed press checkpoint and economic-layer advertising boundary. + next_action: Commit and push the verified v15 assignment cut, then launch one newsroom-only run from immutable Run 68. Require mechanical and blind editorial acceptance; treat any post-press grounding findings as later-edition correction input under the one-copy-desk/one-Night-Editor deadline contract. Preserve Runs 64 through 73, both live services, the completed two-seed press checkpoint, and the economic-layer advertising boundary. gestalt_population: Gestalt Personas own shared population baselines; persistent member deltas own named individuality; WorldKernel materialization and approval-gated fission preserve lineage and identity. Materialisation/folding changes only representation and the exact member delta, advances resolution epoch, and retires the old cover; it has no Gestalt-wide effect authority. Selected actionable member exceptions are mandatory Projector perspective owners, so aggregation cannot silently erase their decision. Presence projection is limited to nearby active leaves and exact-location dormant members, includes reciprocal player relationship evidence, and may propose an earned cast without committing it. Simulation cells are derived covers only.