Persist resumable newspaper reconciliation

This commit is contained in:
Meta Vens 2026-08-29 09:42:54 +02:00
commit db48befa71
9 changed files with 1452 additions and 248 deletions

View file

@ -23,6 +23,41 @@ pub struct ModelAgentRun<Output> {
pub receipts: Vec<ModelStageReceipt>,
}
#[derive(Debug)]
pub enum ModelAgentProgress<Output> {
Accepted(ModelAgentRun<Output>),
Exhausted(ModelAgentExhausted),
}
#[derive(Debug)]
pub struct ModelAgentExhausted {
pub stage: String,
pub max_steps: usize,
pub final_observation: Option<serde_json::Value>,
pub receipts: Vec<ModelStageReceipt>,
}
impl ModelAgentExhausted {
pub fn message(&self) -> String {
format!(
"model agent {} exhausted {} semantic steps; final tool observation: {}",
self.stage,
self.max_steps,
self.final_observation
.as_ref()
.map(serde_json::Value::to_string)
.unwrap_or_else(|| "none".into()),
)
}
fn into_failure(self) -> ModelAgentFailure {
ModelAgentFailure {
message: self.message(),
receipts: self.receipts,
}
}
}
#[derive(Debug)]
pub struct ModelAgentFailure {
pub message: String,
@ -93,6 +128,17 @@ pub async fn run_model_agent<Tool: ModelAgentTool>(
spec: &ModelAgentSpec,
tool: &mut Tool,
) -> std::result::Result<ModelAgentRun<Tool::Output>, ModelAgentFailure> {
match run_model_agent_progress(port, spec, tool).await? {
ModelAgentProgress::Accepted(run) => Ok(run),
ModelAgentProgress::Exhausted(exhausted) => Err(exhausted.into_failure()),
}
}
pub async fn run_model_agent_progress<Tool: ModelAgentTool>(
port: &dyn ModelPort,
spec: &ModelAgentSpec,
tool: &mut Tool,
) -> std::result::Result<ModelAgentProgress<Tool::Output>, ModelAgentFailure> {
if spec.max_steps == 0 {
return Err(ModelAgentFailure {
message: format!("model agent {} has no semantic step budget", spec.stage),
@ -236,7 +282,10 @@ pub async fn run_model_agent<Tool: ModelAgentTool>(
} => {
receipts.push(stage.receipt);
receipts.extend(tool_receipts);
return Ok(ModelAgentRun { output, receipts });
return Ok(ModelAgentProgress::Accepted(ModelAgentRun {
output,
receipts,
}));
}
ModelAgentToolOutcome::Rejected {
finding,
@ -273,17 +322,12 @@ pub async fn run_model_agent<Tool: ModelAgentTool>(
}
}
Err(ModelAgentFailure {
message: format!(
"model agent {} exhausted {} semantic steps; final tool observation: {}",
spec.stage,
spec.max_steps,
last_observation
.map(|observation| observation.to_string())
.unwrap_or_else(|| "none".into()),
),
Ok(ModelAgentProgress::Exhausted(ModelAgentExhausted {
stage: spec.stage.clone(),
max_steps: spec.max_steps,
final_observation: last_observation,
receipts,
})
}))
}
/// Builds one deterministic causal ancestry set for every model stage. This is
@ -637,4 +681,34 @@ mod tests {
.contains(&failure.receipts[1].storage_key().to_owned())
);
}
#[tokio::test]
async fn progress_api_returns_exhaustion_as_resumable_state() {
let model = ScriptedModel {
outputs: Mutex::new(vec![r#"{"value":"still-wrong"}"#.into()]),
requests: Mutex::new(Vec::new()),
};
let spec = ModelAgentSpec {
stage: "fixture_agent".into(),
model: MODEL_BALANCED.into(),
snapshot_binding: "snapshot:1".into(),
instructions: "Use the tool.".into(),
source_receipt_ids: vec!["source:one".into()],
temperature: Some(0.0),
max_output_tokens: Some(128),
max_steps: 1,
};
let progress = run_model_agent_progress(&model, &spec, &mut ExactTool)
.await
.unwrap();
let ModelAgentProgress::Exhausted(exhausted) = progress else {
panic!("one rejected action must remain resumable")
};
assert_eq!(exhausted.stage, "fixture_agent");
assert_eq!(exhausted.max_steps, 1);
assert_eq!(exhausted.receipts.len(), 2);
assert_eq!(exhausted.final_observation.unwrap()["kind"], "wrong_value");
}
}

View file

@ -113,6 +113,16 @@ struct FoundationCheckpoint {
model_receipts: Vec<ghostlight_dungeon::model::ModelStageReceipt>,
}
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct NewspaperReconciliationImportEnvelope {
schema: String,
wave_index: u64,
recovery_start_wave: usize,
world_revision: u64,
import: ghostlight_dungeon::newspaper::WorldNewspaperReconciliationImport,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct ClockBindingProposalCheckpoint {
@ -784,6 +794,10 @@ async fn main() -> anyhow::Result<()> {
let initial_location_ids = campaign.locations.keys().cloned().collect::<Vec<_>>();
(campaign, vec![], vec![], None, initial_location_ids)
};
let newspaper_reconciliation_import_path =
std::env::var_os("GHOSTLIGHT_STRATEGIC_NEWSPAPER_RECONCILIATION_IMPORT")
.map(std::path::PathBuf::from);
let mut newspaper_reconciliation_import_consumed = false;
if resume {
ghostlight_dungeon::compiler::validate_campaign_runtime(&campaign)?;
} else {
@ -1708,8 +1722,9 @@ async fn main() -> anyhow::Result<()> {
issue_path,
issue_audit_path,
newspaper_error,
newspaper_reconciliation_checkpoint,
) = match issue_composition {
Ok(composition) => {
Ok(ghostlight_dungeon::newspaper::WorldNewspaperAdvance::Accepted { composition }) => {
let issue_path = root.join(format!("newspaper-wave-{wave_index:02}.md"));
let issue_audit_path =
root.join(format!("newspaper-wave-{wave_index:02}.audit.md"));
@ -1732,8 +1747,21 @@ async fn main() -> anyhow::Result<()> {
Some(issue_path),
Some(issue_audit_path),
None,
None,
)
}
Ok(ghostlight_dungeon::newspaper::WorldNewspaperAdvance::Pending {
checkpoint,
model_receipts,
}) => (
None,
None,
model_receipts,
None::<std::path::PathBuf>,
None::<std::path::PathBuf>,
None,
Some(checkpoint),
),
Err(error) => {
let Some(failure) = error.downcast_ref::<
ghostlight_dungeon::newspaper::WorldNewspaperCompositionFailure,
@ -1747,6 +1775,7 @@ async fn main() -> anyhow::Result<()> {
None::<std::path::PathBuf>,
None::<std::path::PathBuf>,
Some(error.to_string()),
None,
)
}
};
@ -1764,6 +1793,7 @@ async fn main() -> anyhow::Result<()> {
"newspaper_grounding":newspaper_grounding,
"newspaper_model_receipts":newspaper_model_receipts,
"newspaper_error":newspaper_error,
"newspaper_reconciliation_checkpoint":newspaper_reconciliation_checkpoint,
"issue_path":issue_path,
"issue_audit_path":issue_audit_path,
});
@ -1808,6 +1838,30 @@ async fn main() -> anyhow::Result<()> {
let recomposition = if recomposition_path.is_file() {
read_checkpoint::<serde_json::Value>(&recomposition_path)?
} else {
if let Some(import_path) = newspaper_reconciliation_import_path.as_deref() {
let import =
read_checkpoint::<NewspaperReconciliationImportEnvelope>(import_path)?;
if import.wave_index == wave_index {
if import.schema
!= "ghostlight.strategic_newspaper_reconciliation_import.v1"
|| import.recovery_start_wave != newspaper_recovery_start_wave
|| import.world_revision != issue_campaign.revision
{
anyhow::bail!(
"strategic newspaper reconciliation import does not bind this missing issue"
)
}
ghostlight_dungeon::newspaper::admit_world_newspaper_reconciliation_import(
&issue_campaign,
&issue_title,
&newspaper_voice,
5,
&store,
import.import,
)?;
newspaper_reconciliation_import_consumed = true;
}
}
let composition = match compose_persisted_newspaper(
model.as_ref(),
&issue_campaign,
@ -1818,7 +1872,40 @@ async fn main() -> anyhow::Result<()> {
)
.await
{
Ok(composition) => composition,
Ok(ghostlight_dungeon::newspaper::WorldNewspaperAdvance::Accepted {
composition,
}) => composition,
Ok(ghostlight_dungeon::newspaper::WorldNewspaperAdvance::Pending {
checkpoint,
model_receipts,
}) => {
let checkpoint_id = checkpoint.id().to_owned();
let checkpoint_digest = strategic_smoke_digest(&checkpoint)?;
let checkpoint_suffix = checkpoint_digest
.strip_prefix("sha256:")
.ok_or_else(|| anyhow::anyhow!("invalid checkpoint digest"))?;
publish_immutable_checkpoint(
&root.join(format!(
"newspaper-wave-{wave_index:02}-recomposition-pending-{checkpoint_suffix}.json"
)),
&serde_json::json!({
"schema":"ghostlight.newspaper_wave_recomposition_pending.v1",
"wave_index":wave_index,
"recovery_start_wave":newspaper_recovery_start_wave,
"world_revision":issue_campaign.revision,
"source_campaign_digest":source_campaign_digest,
"editorial_contract_digest":editorial_contract_digest,
"checkpoint_digest":checkpoint_digest,
"checkpoint":checkpoint,
"model_receipt_set_digest":strategic_smoke_digest(&model_receipts)?,
"model_receipts":model_receipts,
}),
)?;
anyhow::bail!(
"newspaper reconciliation is pending at checkpoint {}",
checkpoint_id
)
}
Err(error) => {
let model_receipts = error
.downcast_ref::<
@ -1938,6 +2025,11 @@ async fn main() -> anyhow::Result<()> {
);
report.insert("newspaper_error".into(), serde_json::Value::Null);
}
if newspaper_reconciliation_import_path.is_some()
&& !newspaper_reconciliation_import_consumed
{
anyhow::bail!("configured newspaper reconciliation import was not consumed")
}
}
let final_plan = final_wave_field(&wave_reports, "plan")?;
let final_commit = final_wave_field(&wave_reports, "commit")?;
@ -1962,7 +2054,57 @@ async fn main() -> anyhow::Result<()> {
)
.await
{
Ok(composition) => composition,
Ok(ghostlight_dungeon::newspaper::WorldNewspaperAdvance::Accepted { composition }) => {
composition
}
Ok(ghostlight_dungeon::newspaper::WorldNewspaperAdvance::Pending {
checkpoint,
model_receipts,
}) => {
let checkpoint_id = checkpoint.id().to_owned();
let pending_result = serde_json::json!({
"schema":"ghostlight.live_strategic_smoke_pending.v1",
"scenario_id":scenario_id,
"pressure":pressure,
"wave_count":wave_count,
"newspaper_recovery_start_wave":newspaper_recovery_start_wave,
"campaign_id":campaign.id,
"elapsed_seconds":started.elapsed().as_secs_f64(),
"model_runtime":model_selection.status("configured"),
"world_compile":&world_compile,
"model_receipt_hash":&final_model_receipt_hash,
"model_stage_receipts":&model_stage_receipts,
"plan":&final_plan,
"commit":&final_commit,
"waves":&wave_reports,
"event_count":campaign.events.len(),
"news_count":campaign.news.len(),
"final_newspaper_checkpoint":checkpoint,
"final_newspaper_model_receipts":model_receipts,
"player_location_unchanged":true,
"player_state_unchanged":true,
"store":root.join("campaign.cc")
});
let result_path = root.join("result.json");
std::fs::write(&result_path, serde_json::to_vec_pretty(&pending_result)?)?;
std::fs::write(
root.join("status.json"),
serde_json::to_vec_pretty(&serde_json::json!({
"schema":"ghostlight.live_strategic_smoke_status.v1",
"state":"pending_newspaper_reconciliation",
"waves_completed":wave_count,
"waves_requested":wave_count,
"newspaper_recovery_start_wave":newspaper_recovery_start_wave,
"world_revision":campaign.revision,
"event_count":campaign.events.len(),
"news_count":campaign.news.len(),
"updated_at":Utc::now(),
"result_path":result_path,
"newspaper_checkpoint_id":checkpoint_id,
}))?,
)?;
anyhow::bail!("final newspaper reconciliation is pending at checkpoint {checkpoint_id}")
}
Err(error) => {
let final_newspaper_model_receipts = error
.downcast_ref::<ghostlight_dungeon::newspaper::WorldNewspaperCompositionFailure>()
@ -2083,42 +2225,16 @@ async fn compose_persisted_newspaper(
editorial_voice: &str,
max_articles: usize,
store: &ghostlight_dungeon::persistence::CampaignStore,
) -> anyhow::Result<ghostlight_dungeon::newspaper::WorldNewspaperComposition> {
let result = ghostlight_dungeon::newspaper::compose_world_newspaper(
) -> anyhow::Result<ghostlight_dungeon::newspaper::WorldNewspaperAdvance> {
ghostlight_dungeon::newspaper::advance_world_newspaper(
model,
campaign,
title,
editorial_voice,
max_articles,
store,
)
.await;
let receipts = match &result {
Ok(composition) => Some(composition.model_receipts.as_slice()),
Err(error) => error
.downcast_ref::<ghostlight_dungeon::newspaper::WorldNewspaperCompositionFailure>()
.map(|failure| failure.model_receipts.as_slice()),
};
if let Some(receipts) = receipts {
if let Err(persistence_error) = store.persist_model_stage_receipts(receipts) {
if let Err(error) = &result
&& let Some(failure) = error.downcast_ref::<
ghostlight_dungeon::newspaper::WorldNewspaperCompositionFailure,
>()
{
return Err(anyhow::Error::new(
ghostlight_dungeon::newspaper::WorldNewspaperCompositionFailure {
message: format!(
"{}; model-receipt persistence failed: {persistence_error}",
failure.message
),
model_receipts: failure.model_receipts.clone(),
},
));
}
return Err(persistence_error);
}
}
result
.await
}
async fn compile_strategic_campaign(

File diff suppressed because it is too large Load diff

View file

@ -380,7 +380,7 @@ impl CampaignRegistry {
&self,
request: WorldNewspaperRequest,
model: &dyn ModelPort,
) -> Result<crate::newspaper::WorldNewspaperIssue> {
) -> Result<crate::newspaper::WorldNewspaperAdvance> {
if request.schema != "ghostlight.world_newspaper_request.v2"
|| request.max_articles == 0
|| request.max_articles > 6
@ -409,36 +409,15 @@ impl CampaignRegistry {
.load::<Campaign>("campaign.v1", &request.campaign_id.to_string())?
.ok_or_else(|| anyhow!("campaign is missing"))?
.1;
let composition_result = crate::newspaper::compose_world_newspaper(
crate::newspaper::advance_world_newspaper(
model,
&campaign,
request.title,
request.editorial_voice,
usize::from(request.max_articles),
&runtime.store,
)
.await;
if let Err(error) = &composition_result
&& let Some(failure) =
error.downcast_ref::<crate::newspaper::WorldNewspaperCompositionFailure>()
&& let Err(persistence_error) = runtime
.store
.persist_model_stage_receipts(&failure.model_receipts)
{
return Err(anyhow::Error::new(
crate::newspaper::WorldNewspaperCompositionFailure {
message: format!(
"{}; model-receipt persistence failed: {persistence_error}",
failure.message
),
model_receipts: failure.model_receipts.clone(),
},
));
}
let composition = composition_result?;
runtime
.store
.persist_model_stage_receipts(&composition.model_receipts)?;
Ok(composition.issue)
.await
}
pub async fn acknowledge_external_proposal(
@ -1410,7 +1389,10 @@ mod tests {
let newspaper = registry
.compose_world_newspaper(newspaper_request.clone(), &RegistryNewspaperModel)
.await
.unwrap();
.unwrap()
.into_accepted()
.unwrap()
.issue;
assert_eq!(newspaper.source_world_revision, 0);
assert!(newspaper.articles.is_empty());
@ -1627,7 +1609,10 @@ mod tests {
let newspaper = registry
.compose_world_newspaper(newspaper_request.clone(), &RegistryNewspaperModel)
.await
.unwrap();
.unwrap()
.into_accepted()
.unwrap()
.issue;
assert_eq!(newspaper.source_world_revision, 3);
assert_eq!(newspaper.articles.len(), 1);
assert_eq!(
@ -1651,7 +1636,10 @@ mod tests {
let repeated_newspaper = registry
.compose_world_newspaper(newspaper_request.clone(), &RegistryNewspaperModel)
.await
.unwrap();
.unwrap()
.into_accepted()
.unwrap()
.issue;
assert_eq!(repeated_newspaper.id, newspaper.id);
assert_eq!(
runtime
@ -1663,17 +1651,23 @@ mod tests {
);
let receipt_count_before_rejection = successful_receipts.len();
let error = registry
.compose_world_newspaper(newspaper_request, &RejectingRegistryNewspaperModel)
let mut rejected_request = newspaper_request;
rejected_request.title.push_str(" Evening");
let pending = registry
.compose_world_newspaper(rejected_request, &RejectingRegistryNewspaperModel)
.await
.unwrap_err();
let failure = error
.downcast_ref::<crate::newspaper::WorldNewspaperCompositionFailure>()
.expect("registry must preserve the typed terminal editorial failure");
assert_eq!(failure.model_receipts.len(), 8);
.unwrap();
let crate::newspaper::WorldNewspaperAdvance::Pending {
checkpoint,
model_receipts,
} = pending
else {
panic!("registry must preserve typed pending reconciliation state")
};
assert_eq!(model_receipts.len(), 8);
assert_eq!(checkpoint.generation(), 3);
let stored_after_rejection = runtime.store.keys("persona_stage_receipt.v1").unwrap();
let newly_observed_failure_receipts = failure
.model_receipts
let newly_observed_failure_receipts = model_receipts
.iter()
.map(|receipt| receipt.storage_key().to_owned())
.filter(|receipt_id| !successful_receipts.contains(receipt_id))
@ -1683,7 +1677,7 @@ mod tests {
receipt_count_before_rejection + newly_observed_failure_receipts.len()
);
assert!(
failure.model_receipts.iter().all(|receipt| {
model_receipts.iter().all(|receipt| {
stored_after_rejection.contains(&receipt.storage_key().to_owned())
})
);

View file

@ -1096,19 +1096,40 @@ workbench state and receipts persist, and the editor never reruns. The exact
mixed-path regression and full gates pass: 16/16 newspaper tests, 470/470
library tests, 13/13 strategic-smoke binary tests, and `git diff --check`.
Exact-source run 46 at
`dfe0926041b83d69a4b02451cdab706572522ae3` resumed revision 27 without
world or successful-publication replay. Issues 13 and 14 remained preserved.
Issue 15 exhausted three admitted repair/copy-desk rounds; the final exact
finding is one mechanical duplicated clause. Issues 15 through 18 and the final
digest remain absent. Unit invocation is
`3276392f4a7149fc9d127a966e792633`.
Another fixed step would repeat the same compensator. The current uncommitted
resumable-reconciliation cut persists a typed generation-zero checkpoint and a
new typed checkpoint after every rejection, binding the exact draft, verdict
and finding catalog, model and deterministic receipts, and source/contract
identity. Accepted composition and strategic artifact records separately bind
issue and rendered-file ancestry. Resume continues that exact pending chain
without rerunning the editor. Typed run-46 import converts the preserved
terminal evidence into the same checkpoint contract. Gates pass
475/475 library tests and 13/13 strategic-smoke binary tests, including
same-task source-drift and fork rejection before inference.
Pushed `af749de` routes a provider timeout through the shared model transport's
existing one same-request retry and records attempt telemetry. Run 36 crossed
that boundary; timeout retry is not its active blocker.
1. Commit and push the verified three-action reconciliation cut.
2. Launch exact-source run 46 from immutable run 45's preserved Issue 13 and Issue 14 boundary at revision 27
to compose Issues 15 through 18 plus the final digest. Preserve every
1. Commit and push the verified typed resumable-reconciliation checkpoint and
run-46 import cut.
2. Launch run 47 by resuming run 46's Issue 15 reconciliation chain at revision
27 to compose Issues 15 through 18 plus the final digest. Preserve every
completed wave checkpoint and canonical world state; do not replay mechanics
or successful publications. Require the resulting unedited publications and
final digest to pass fresh independent editorial and provenance review before
claiming acceptance. Preserve runs 38/39/40/42/43/44/45, the run-41 root and committed
world lineage, stopped run 23, exact acceptance conditions, and unchanged
live daemons.
claiming acceptance. Preserve runs 38/39/40/42/43/44/45/46, the run-41 root
and committed world lineage, stopped run 23, exact acceptance conditions,
and unchanged live daemons.
Run 47 remains unlaunched.
3. Have the game-side adapter lower its authored hierarchy into the published
generic schemas; keep projection and economy translation consumer-owned.
4. Prove one consumer-owned effect crosses that public API, changes two foreign

View file

@ -1315,48 +1315,99 @@ surviving article selection and order, sections, bylines, and all unaffected
text remain frozen. If the lead is deleted, owner code promotes the next article
to the front page. The agent cannot request a new simulation or source packet,
submit a replacement `EditorialPageDraft`, or act as a second editor. The same
copy desk rechecks the complete repaired page. The worker permits at most three
actions in one agent run: the initial repair and two bounded corrections. The
intended full path can spend one correction on deterministic admission and one
on a fresh copy-desk verdict; every rejected action consumes the same total
budget rather than opening a separate retry owner for either boundary. After
the first or second rejection, the same workbench retains the current private
draft and verdict, republishes the dynamic action schema, and exposes a freshly
numbered finding catalog derived by the same helper used for the initial
catalog. The harness includes every prior action receipt and any copy-desk tool
receipts in the next action's causal source chain; a local admission rejection
correctly contributes no nonexistent tool receipt. Acceptance ends the run
immediately. An inadmissible repair, an empty surviving page, or a page rejected
after the third action returns no edition. Thus the editor owns
initial copy, the reconciler owns only this bounded repair session, the copy
desk owns grounding judgment, and none owns source facts or world state.
Focused tests cover order-independent same-target phrase repair, exact same-desk
repair with source identity preserved, bounded article deletion with survivor
recheck, and one corrective response to the copy desk's current finding catalog.
A mixed-path regression proves local empty-repair rejection, admitted repair and
copy-desk rejection, then third-action acceptance with seven receipts and final
copy-desk ancestry over every prior completed receipt. A terminal regression
proves that three admitted but rejected repairs exhaust the three-action budget
and retain all eight editor, copy-desk, and action receipts, including all three
rejected action dispositions. The typed issue retains exact event IDs, channels,
reliability, source revision, and model receipts as audit data. Its internal
issue time is derived from the latest cited publication record without exposing
that clock to editorial inference. Terminally rejected attempts semantically
rebind their otherwise immutable model receipts to a collision-free invalid
disposition and return a typed failure carrying every completed receipt. The
campaign store owns idempotent model-receipt persistence; the registry and
strategic acceptance smoke use that same primitive before returning a
rejection. In the acceptance smoke, an interim edition rejection is local to
the consumer and cannot stop later world waves. A rejected final combined
edition remains a nonzero acceptance failure, but first writes the completed
wave reports, rejection receipts, and failed status. Its top-level plan,
receipt hash, and commit always project the final completed wave, matching the
top-level counts and persisted campaign head; per-wave history remains under
`waves`. The reader renderer has no
path to provenance fields and escapes all model- and consumer-supplied plain
text before Markdown emission; the provenance renderer applies the same
plain-text boundary to its audit data. Neither editor, copy desk, renderer, nor
receipt persistence can write world state.
copy desk rechecks the complete repaired page.
The resumable reconciliation boundary is now live:
- **Owner:** `newspaper::advance_world_newspaper` is the single composition,
checkpoint-advance, resume, and accepted-composition owner. `CampaignStore`
owns immutable storage only. Registry and the strategic driver supply policy
inputs and consume `WorldNewspaperAdvance::{Accepted, Pending}`; neither
reconstructs the workbench or decides editorial state.
- **Inputs:** one exact `Campaign` snapshot, title, editorial voice, article
budget, model port, and `CampaignStore`. A publication-task binding covers
campaign id and revision plus title, voice, and budget. The editorial binding
additionally covers the exact derived newsroom source desk and provenance.
This lets the owner find the task's checkpoint chain while still rejecting
same-task source drift before inference.
- **Outputs:** acceptance is one immutable persisted composition. A still
rejected page is typed `Pending`, carrying the current immutable checkpoint
and complete ordered receipt chain; pending is not a
`WorldNewspaperCompositionFailure`. Fatal model, schema, local-validation,
persistence, and checkpoint-conflict errors remain failures and leave the
last admitted checkpoint available.
- **Persistent and derived state:** the initial copy-desk rejection persists its
editor and copy-desk receipts, then publishes generation zero before any
repair action. Each later rejected action persists its new receipts before
appending one checkpoint with task and editorial bindings, generation,
parent id, typed origin, current private draft, rejected verdict, ordered
receipt ids, and receipt-chain digest. The current finding catalog, exact
target spans, action schema, and chain tip are derived and validated; they are
not parallel state owners. Three actions per call are only the current
advance cadence. Reaching that boundary returns `Pending` and does not make
rejection terminal.
- **Resume and idempotence:** every call checks the accepted composition marker
before checkpoint discovery. Otherwise it loads the unique checkpoint tip,
reloads every typed receipt, validates checkpoint identity, task and exact
editorial bindings, generation and parent links, strict receipt-prefix
growth, draft, rejected verdict and finding targets, then reconstructs the
same workbench. A resumed action includes the checkpoint id and every prior
receipt in its causal ancestry and never invokes the editor. Acceptance
persists its receipts and a composition linked to the source checkpoint;
later calls return that composition without model work even though the
historical pending chain remains immutable.
- **Forbidden writers:** the editor and copy desk may propose and judge but
cannot persist or resume reconciliation state. Registry, strategic smoke,
renderers, JSON result/checkpoint projections, world cells, Nemesis, clocks,
and `WorldKernel` cannot write the draft, verdict, receipt chain, checkpoint,
or accepted composition. They cannot replay world mechanics or overwrite an
already accepted issue to repair editorial copy.
- **Shared paths:** Registry calls `advance_world_newspaper` directly. Strategic
live per-wave, missing-issue recovery, and final-combined composition use a
thin `compose_persisted_newspaper` delegate to that same owner and store. A
pending interim issue is recorded in its immutable wave report and does not
stop later strategic waves; recovery resumes it from `CampaignStore`. A
pending final combined issue writes pending result/status projections and
ends that acceptance invocation nonzero without changing the world.
- **Legacy import boundary:** Run 46 may enter only through the explicit typed
`WorldNewspaperReconciliationImport` and strategic import envelope. The
envelope binds wave, recovery boundary, and world revision; the common
library validator rebinds the witness to the exact prepared task and desk,
validates its draft, rejected verdict, and typed receipts, and admits it as a
generation-zero `LegacyTerminalImport`. An existing accepted composition or
different checkpoint tip rejects the import. No live path parses failure
prose, infers a checkpoint from JSON error text, or falls back to a fresh
editor after a configured import fails admission.
- **Cut line:** the newspaper path uses the agent harness's typed
`ModelAgentProgress::Exhausted` result; the older generic harness API may still
lower exhaustion to failure for non-resumable consumers, but it no longer
owns newspaper correctness. The former fixed terminal reconciliation ceiling
is now `GROUNDING_RECONCILIATION_ACTIONS_PER_ADVANCE`. Registry's separate
receipt-persistence/failure path is gone, and the strategic persistence
helper is only a delegate rather than a second composition owner.
- **Verification layer:** focused tests prove typed harness exhaustion,
generation zero plus one append per rejected action, typed pending after
three actions, exact-chain resume without an editor, cumulative receipt and
copy-desk ancestry, accepted-composition idempotence without inference,
same-task source-drift rejection and fork rejection before inference, typed
legacy import through the common validator, and Registry preservation of
pending state. The current verified gate is 475/475 library tests and 13/13
strategic-smoke binary tests, including same-task source-drift and fork
rejection before inference. Run 47 has not yet supplied provider-backed
resume/acceptance proof.
The typed issue retains exact event IDs, channels, reliability, source
revision, and model receipts as audit data. Its internal issue time is derived
from the latest cited publication record without exposing that clock to
editorial inference. Rejected action attempts semantically rebind their
otherwise immutable model receipts to collision-free invalid dispositions
before checkpoint admission. The top-level strategic plan, receipt hash, and
commit always project the final completed wave, matching the top-level counts
and persisted campaign head; per-wave history remains under `waves`. The reader
renderer has no path to provenance fields and escapes all model- and
consumer-supplied plain text before Markdown emission; the provenance renderer
applies the same plain-text boundary to its audit data. Neither editor, copy
desk, renderer, nor receipt persistence can write world state.
#### Acceptance-driver per-wave newspaper recovery
@ -1369,15 +1420,18 @@ receipt persistence can write world state.
(`GHOSTLIGHT_STRATEGIC_NEWSPAPER_RECOVERY_START_WAVE`, defaulting to wave one
and validated within `1..=wave_count`), and the same campaign receipt store
used by ordinary newspaper composition.
- **Output:** one separately named immutable recomposition checkpoint containing
the accepted issue, grounding verdict, and newspaper model receipts, plus the
rendered reader and audit files. Its receipt binds the narrowed source
- **Output:** acceptance writes one separately named immutable recomposition
checkpoint containing the issue, grounding verdict, and newspaper model
receipts, plus the rendered reader and audit files. Its receipt binds the narrowed source
campaign, recovery boundary, title/voice/article-budget contract, issue,
model-receipt collection, expected filenames, and both rendered byte streams
by digest. The configured boundary is also projected through run status and
success or failure result artifacts. The driver fills only the missing
newspaper fields in its in-memory result projection. Existing successful
issues and original wave checkpoints remain unchanged.
newspaper fields in its in-memory result projection. Pending reconciliation
instead writes an immutable diagnostic projection of the library-owned
checkpoint and stops that recovery invocation; canonical resume state remains
in `CampaignStore`. Existing successful issues and original wave checkpoints
remain unchanged.
- **Derived state:** the newsroom receives a clone of the completed wave's
campaign whose news ledger is narrowed to rows appended since the preceding
committed boundary. All events and canonical names remain available only so
@ -1389,16 +1443,17 @@ receipt persistence can write world state.
range is left untouched.
- **Forbidden writers:** recovery cannot rerun a strategic tick, Nemesis,
simulation cells, outcome resolution, clocks, or `WorldKernel`, and it cannot
alter campaign, event, or news state. A failed recomposition stops consumer
recovery and cannot repair mechanics.
alter campaign, event, or news state. Pending or failed recomposition stops
consumer recovery and cannot repair mechanics.
- **Shared paths:** live per-wave composition, recovery composition, and final
combined composition all call `compose_persisted_newspaper`, including the
same grounding/copy-desk path and idempotent model-receipt persistence. A
later resume consumes the successful recomposition checkpoint instead of
paying for the newspaper again, but only after recomputing its source and
contract bindings, checking the exact recovery boundary, deserializing the
issue, regenerating reader and audit copy, and comparing the exact stored files
and digests.
combined composition all call the thin `compose_persisted_newspaper` delegate
into `advance_world_newspaper`, including the same grounding/copy-desk,
checkpoint-resume, and receipt-persistence owner. A later resume advances a
pending library checkpoint without rerunning the editor. Once accepted, it
consumes the successful recomposition artifact instead of paying for the
newspaper again, but only after recomputing its source and contract bindings,
checking the exact recovery boundary, deserializing the issue, regenerating
reader and audit copy, and comparing the exact stored files and digests.
- **Verification layer:** the focused boundary regression proves that a second
wave is recomposed from only its newly appended news row while retaining the
committed event ledger needed to resolve that row. It also rejects prior-news

View file

@ -212,4 +212,7 @@
{"note": "Exact-source run44 at 35f56f0e5e670025e1cfb031cba8d5a4ca9587d4 resumed revision27 without replaying world mechanics or successful publications. Issues13/14 remained preserved. Typed finding references were admitted and the first reconciliation action ran; the deterministic copy desk returned a second exact finding set, then MAX_STEPS=1 stopped the agent before it could react. Issues15-18 and the final digest remain absent. Typed-reference ownership is provider-proved; session depth is now the active boundary. Next cut permits exactly one further corrective action in the same reconciliation workbench state and receipt chain, for two actions total, with no editor rerun; then verify/commit/push and resume from run44. Preserve one-good-Canopy-Ledger first, multi-publication Personas later, and layout/engraved cuts only after editorial acceptance.", "status": "ok", "ts": "2026-08-29T05:55:06+00:00", "type": "correction"}
{"note": "The two-action grounding reconciliation cut is implemented and locally verified. One reconciliation session permits exactly two total corrective actions, preserves the same workbench state and receipt chain, and supplies an explicit refreshed finding_catalog to the second action; it does not rerun the editor or preserve a compatibility path. Gates pass 15/15 newspaper tests, 469/469 library tests, 13/13 strategic-smoke binary tests, and git diff --check. Implementation-pending steering is retired. Next action is commit/push, then resume from immutable run44 preserved Issues13/14 at revision27 without world or successful-publication replay. One good Canopy Ledger remains first; publication Personas later; layout and engraved cuts after editorial acceptance.", "status": "ok", "ts": "2026-08-29T06:00:38+00:00", "type": "verification"}
{"note": "Exact-source run45 at 1d898489daeb34fc9f53e6c6bddf00738fc66364 resumed revision27 without world or successful-publication replay. Issues13/14 remained preserved. Reconciliation action1 was rejected locally before the copy desk; action2 was admitted and rechecked, then the deterministic copy desk returned four fresh findings, but the two-action ceiling left no response action. Issues15-18 and the final digest remain absent. The bounded protocol now requires exactly three total actions: initial, at most one local-admission correction, and at most one fresh copy-desk correction in the same workbench state and receipt chain; a rejection after action3 is terminal and the editor never reruns. Verify/commit/push, then resume from immutable run45. Keep one good Canopy Ledger first, publication Personas later, and layout/engraved cuts after editorial acceptance.", "status": "ok", "ts": "2026-08-29T06:15:49+00:00", "type": "correction"}
{"ts": "2026-08-29T06:25:26+00:00", "type": "verification", "status": "ok", "note": "The exact three-action mixed-path reconciliation cut is implemented and verified. It permits the initial action, at most one local-admission correction, and at most one fresh copy-desk correction in one workbench state and receipt chain; rejection after action three is terminal and the editor never reruns. The exact regression and full gates pass: 16/16 newspaper tests, 470/470 library tests, 13/13 strategic-smoke binary tests, and git diff --check. Pending verification steering is retired. Next action is commit/push, then exact-source run46 from immutable run45 preserved Issues13/14 at revision27 with no world or successful-publication replay. One good Canopy Ledger remains first; publication Personas later; layout and engraved cuts after editorial acceptance."}
{"note": "The exact three-action mixed-path reconciliation cut is implemented and verified. It permits the initial action, at most one local-admission correction, and at most one fresh copy-desk correction in one workbench state and receipt chain; rejection after action three is terminal and the editor never reruns. The exact regression and full gates pass: 16/16 newspaper tests, 470/470 library tests, 13/13 strategic-smoke binary tests, and git diff --check. Pending verification steering is retired. Next action is commit/push, then exact-source run46 from immutable run45 preserved Issues13/14 at revision27 with no world or successful-publication replay. One good Canopy Ledger remains first; publication Personas later; layout and engraved cuts after editorial acceptance.", "status": "ok", "ts": "2026-08-29T06:25:26+00:00", "type": "verification"}
{"note": "Exact-source run46 at dfe0926041b83d69a4b02451cdab706572522ae3 resumed revision27 without world or successful-publication replay. Issues13/14 remained preserved. Issue15 exhausted three admitted repair/copy-desk rounds; the final exact finding is one mechanical duplicated clause. Issues15-18 and the final digest remain absent. Unit invocation is 3276392f4a7149fc9d127a966e792633. Raising another fixed step repeats the same compensator, while current immutable recovery reruns the editor and discards reconciliation state. Next action is to map, then implement and verify, a typed resumable reconciliation checkpoint binding current draft, exact verdict/finding catalog, workbench, receipts, source/contract identity, and render/file ancestry; run47 must resume that chain without editor, world, or successful-publication replay. One good Canopy Ledger remains first; publication Personas later; layout and engraved cuts after editorial acceptance.", "status": "ok", "ts": "2026-08-29T06:41:00+00:00", "type": "correction"}
{"note": "The typed resumable newspaper reconciliation cut is implemented and locally verified. It persists a typed generation-zero checkpoint and a new typed checkpoint after every rejection, binding the exact draft, copy-desk verdict/finding catalog, workbench state, model and deterministic receipts, source/contract identity, and rendered/file ancestry. Resume continues that chain without rerunning the editor. Typed run46 import converts the preserved terminal evidence into the same checkpoint contract. Gates pass 473/473 library tests and 13/13 strategic-smoke binary tests. Planned-only steering is retired. Next action is commit/push, then launch still-unlaunched run47 by continuing run46 Issue15 without editor, world, or successful-publication replay. One good Canopy Ledger remains first; publication Personas later; layout and engraved cuts after editorial acceptance.", "status": "ok", "ts": "2026-08-29T07:36:01+00:00", "type": "verification"}
{"ts": "2026-08-29T07:42:40+00:00", "type": "correction", "status": "ok", "note": "The final resumable-newspaper gate is 475/475 library tests and 13/13 strategic-smoke tests, including same-task source-drift and fork rejection before inference. Clarification: pending checkpoints bind task/editorial identity, exact draft, rejected verdict/findings, parent generation, and ordered receipt ancestry. Accepted composition and strategic artifact records separately bind issue and rendered-file ancestry; rendered files are not checkpoint workbench state. Run47 remains unlaunched."}

View file

@ -214,3 +214,6 @@
{"key": "2026-08-29T060038Z0000-0212-bad914a562bc", "payload": "eyJub3RlIjogIlRoZSB0d28tYWN0aW9uIGdyb3VuZGluZyByZWNvbmNpbGlhdGlvbiBjdXQgaXMgaW1wbGVtZW50ZWQgYW5kIGxvY2FsbHkgdmVyaWZpZWQuIE9uZSByZWNvbmNpbGlhdGlvbiBzZXNzaW9uIHBlcm1pdHMgZXhhY3RseSB0d28gdG90YWwgY29ycmVjdGl2ZSBhY3Rpb25zLCBwcmVzZXJ2ZXMgdGhlIHNhbWUgd29ya2JlbmNoIHN0YXRlIGFuZCByZWNlaXB0IGNoYWluLCBhbmQgc3VwcGxpZXMgYW4gZXhwbGljaXQgcmVmcmVzaGVkIGZpbmRpbmdfY2F0YWxvZyB0byB0aGUgc2Vjb25kIGFjdGlvbjsgaXQgZG9lcyBub3QgcmVydW4gdGhlIGVkaXRvciBvciBwcmVzZXJ2ZSBhIGNvbXBhdGliaWxpdHkgcGF0aC4gR2F0ZXMgcGFzcyAxNS8xNSBuZXdzcGFwZXIgdGVzdHMsIDQ2OS80NjkgbGlicmFyeSB0ZXN0cywgMTMvMTMgc3RyYXRlZ2ljLXNtb2tlIGJpbmFyeSB0ZXN0cywgYW5kIGdpdCBkaWZmIC0tY2hlY2suIEltcGxlbWVudGF0aW9uLXBlbmRpbmcgc3RlZXJpbmcgaXMgcmV0aXJlZC4gTmV4dCBhY3Rpb24gaXMgY29tbWl0L3B1c2gsIHRoZW4gcmVzdW1lIGZyb20gaW1tdXRhYmxlIHJ1bjQ0IHByZXNlcnZlZCBJc3N1ZXMxMy8xNCBhdCByZXZpc2lvbjI3IHdpdGhvdXQgd29ybGQgb3Igc3VjY2Vzc2Z1bC1wdWJsaWNhdGlvbiByZXBsYXkuIE9uZSBnb29kIENhbm9weSBMZWRnZXIgcmVtYWlucyBmaXJzdDsgcHVibGljYXRpb24gUGVyc29uYXMgbGF0ZXI7IGxheW91dCBhbmQgZW5ncmF2ZWQgY3V0cyBhZnRlciBlZGl0b3JpYWwgYWNjZXB0YW5jZS4iLCAic3RhdHVzIjogIm9rIiwgInRzIjogIjIwMjYtMDgtMjlUMDY6MDA6MzgrMDA6MDAiLCAidHlwZSI6ICJ2ZXJpZmljYXRpb24ifQ==", "stored_at": "2026-08-29T06:00:38.081686+00:00", "type": "ghostlight.evidence_record.v0"}
{"key": "2026-08-29T061549Z0000-0213-c145b473c763", "payload": "eyJub3RlIjogIkV4YWN0LXNvdXJjZSBydW40NSBhdCAxZDg5ODQ4OWRhZWIzNGZjOWY1M2U2YzZiZGRmMDA3MzhmYzY2MzY0IHJlc3VtZWQgcmV2aXNpb24yNyB3aXRob3V0IHdvcmxkIG9yIHN1Y2Nlc3NmdWwtcHVibGljYXRpb24gcmVwbGF5LiBJc3N1ZXMxMy8xNCByZW1haW5lZCBwcmVzZXJ2ZWQuIFJlY29uY2lsaWF0aW9uIGFjdGlvbjEgd2FzIHJlamVjdGVkIGxvY2FsbHkgYmVmb3JlIHRoZSBjb3B5IGRlc2s7IGFjdGlvbjIgd2FzIGFkbWl0dGVkIGFuZCByZWNoZWNrZWQsIHRoZW4gdGhlIGRldGVybWluaXN0aWMgY29weSBkZXNrIHJldHVybmVkIGZvdXIgZnJlc2ggZmluZGluZ3MsIGJ1dCB0aGUgdHdvLWFjdGlvbiBjZWlsaW5nIGxlZnQgbm8gcmVzcG9uc2UgYWN0aW9uLiBJc3N1ZXMxNS0xOCBhbmQgdGhlIGZpbmFsIGRpZ2VzdCByZW1haW4gYWJzZW50LiBUaGUgYm91bmRlZCBwcm90b2NvbCBub3cgcmVxdWlyZXMgZXhhY3RseSB0aHJlZSB0b3RhbCBhY3Rpb25zOiBpbml0aWFsLCBhdCBtb3N0IG9uZSBsb2NhbC1hZG1pc3Npb24gY29ycmVjdGlvbiwgYW5kIGF0IG1vc3Qgb25lIGZyZXNoIGNvcHktZGVzayBjb3JyZWN0aW9uIGluIHRoZSBzYW1lIHdvcmtiZW5jaCBzdGF0ZSBhbmQgcmVjZWlwdCBjaGFpbjsgYSByZWplY3Rpb24gYWZ0ZXIgYWN0aW9uMyBpcyB0ZXJtaW5hbCBhbmQgdGhlIGVkaXRvciBuZXZlciByZXJ1bnMuIFZlcmlmeS9jb21taXQvcHVzaCwgdGhlbiByZXN1bWUgZnJvbSBpbW11dGFibGUgcnVuNDUuIEtlZXAgb25lIGdvb2QgQ2Fub3B5IExlZGdlciBmaXJzdCwgcHVibGljYXRpb24gUGVyc29uYXMgbGF0ZXIsIGFuZCBsYXlvdXQvZW5ncmF2ZWQgY3V0cyBhZnRlciBlZGl0b3JpYWwgYWNjZXB0YW5jZS4iLCAic3RhdHVzIjogIm9rIiwgInRzIjogIjIwMjYtMDgtMjlUMDY6MTU6NDkrMDA6MDAiLCAidHlwZSI6ICJjb3JyZWN0aW9uIn0=", "stored_at": "2026-08-29T06:15:49.916086+00:00", "type": "ghostlight.evidence_record.v0"}
{"key": "2026-08-29T062526Z0000-0214-688704de1feb", "payload": "eyJub3RlIjogIlRoZSBleGFjdCB0aHJlZS1hY3Rpb24gbWl4ZWQtcGF0aCByZWNvbmNpbGlhdGlvbiBjdXQgaXMgaW1wbGVtZW50ZWQgYW5kIHZlcmlmaWVkLiBJdCBwZXJtaXRzIHRoZSBpbml0aWFsIGFjdGlvbiwgYXQgbW9zdCBvbmUgbG9jYWwtYWRtaXNzaW9uIGNvcnJlY3Rpb24sIGFuZCBhdCBtb3N0IG9uZSBmcmVzaCBjb3B5LWRlc2sgY29ycmVjdGlvbiBpbiBvbmUgd29ya2JlbmNoIHN0YXRlIGFuZCByZWNlaXB0IGNoYWluOyByZWplY3Rpb24gYWZ0ZXIgYWN0aW9uIHRocmVlIGlzIHRlcm1pbmFsIGFuZCB0aGUgZWRpdG9yIG5ldmVyIHJlcnVucy4gVGhlIGV4YWN0IHJlZ3Jlc3Npb24gYW5kIGZ1bGwgZ2F0ZXMgcGFzczogMTYvMTYgbmV3c3BhcGVyIHRlc3RzLCA0NzAvNDcwIGxpYnJhcnkgdGVzdHMsIDEzLzEzIHN0cmF0ZWdpYy1zbW9rZSBiaW5hcnkgdGVzdHMsIGFuZCBnaXQgZGlmZiAtLWNoZWNrLiBQZW5kaW5nIHZlcmlmaWNhdGlvbiBzdGVlcmluZyBpcyByZXRpcmVkLiBOZXh0IGFjdGlvbiBpcyBjb21taXQvcHVzaCwgdGhlbiBleGFjdC1zb3VyY2UgcnVuNDYgZnJvbSBpbW11dGFibGUgcnVuNDUgcHJlc2VydmVkIElzc3VlczEzLzE0IGF0IHJldmlzaW9uMjcgd2l0aCBubyB3b3JsZCBvciBzdWNjZXNzZnVsLXB1YmxpY2F0aW9uIHJlcGxheS4gT25lIGdvb2QgQ2Fub3B5IExlZGdlciByZW1haW5zIGZpcnN0OyBwdWJsaWNhdGlvbiBQZXJzb25hcyBsYXRlcjsgbGF5b3V0IGFuZCBlbmdyYXZlZCBjdXRzIGFmdGVyIGVkaXRvcmlhbCBhY2NlcHRhbmNlLiIsICJzdGF0dXMiOiAib2siLCAidHMiOiAiMjAyNi0wOC0yOVQwNjoyNToyNiswMDowMCIsICJ0eXBlIjogInZlcmlmaWNhdGlvbiJ9", "stored_at": "2026-08-29T06:25:26.234774+00:00", "type": "ghostlight.evidence_record.v0"}
{"key": "2026-08-29T064100Z0000-0215-144e1d030288", "payload": "eyJub3RlIjogIkV4YWN0LXNvdXJjZSBydW40NiBhdCBkZmUwOTI2MDQxYjgzZDY5YTRiMDI0NTFjZGFiNzA2NTcyNTIyYWUzIHJlc3VtZWQgcmV2aXNpb24yNyB3aXRob3V0IHdvcmxkIG9yIHN1Y2Nlc3NmdWwtcHVibGljYXRpb24gcmVwbGF5LiBJc3N1ZXMxMy8xNCByZW1haW5lZCBwcmVzZXJ2ZWQuIElzc3VlMTUgZXhoYXVzdGVkIHRocmVlIGFkbWl0dGVkIHJlcGFpci9jb3B5LWRlc2sgcm91bmRzOyB0aGUgZmluYWwgZXhhY3QgZmluZGluZyBpcyBvbmUgbWVjaGFuaWNhbCBkdXBsaWNhdGVkIGNsYXVzZS4gSXNzdWVzMTUtMTggYW5kIHRoZSBmaW5hbCBkaWdlc3QgcmVtYWluIGFic2VudC4gVW5pdCBpbnZvY2F0aW9uIGlzIDMyNzYzOTJmNGE3MTQ5ZmM5ZDEyN2E5NjZlNzkyNjMzLiBSYWlzaW5nIGFub3RoZXIgZml4ZWQgc3RlcCByZXBlYXRzIHRoZSBzYW1lIGNvbXBlbnNhdG9yLCB3aGlsZSBjdXJyZW50IGltbXV0YWJsZSByZWNvdmVyeSByZXJ1bnMgdGhlIGVkaXRvciBhbmQgZGlzY2FyZHMgcmVjb25jaWxpYXRpb24gc3RhdGUuIE5leHQgYWN0aW9uIGlzIHRvIG1hcCwgdGhlbiBpbXBsZW1lbnQgYW5kIHZlcmlmeSwgYSB0eXBlZCByZXN1bWFibGUgcmVjb25jaWxpYXRpb24gY2hlY2twb2ludCBiaW5kaW5nIGN1cnJlbnQgZHJhZnQsIGV4YWN0IHZlcmRpY3QvZmluZGluZyBjYXRhbG9nLCB3b3JrYmVuY2gsIHJlY2VpcHRzLCBzb3VyY2UvY29udHJhY3QgaWRlbnRpdHksIGFuZCByZW5kZXIvZmlsZSBhbmNlc3RyeTsgcnVuNDcgbXVzdCByZXN1bWUgdGhhdCBjaGFpbiB3aXRob3V0IGVkaXRvciwgd29ybGQsIG9yIHN1Y2Nlc3NmdWwtcHVibGljYXRpb24gcmVwbGF5LiBPbmUgZ29vZCBDYW5vcHkgTGVkZ2VyIHJlbWFpbnMgZmlyc3Q7IHB1YmxpY2F0aW9uIFBlcnNvbmFzIGxhdGVyOyBsYXlvdXQgYW5kIGVuZ3JhdmVkIGN1dHMgYWZ0ZXIgZWRpdG9yaWFsIGFjY2VwdGFuY2UuIiwgInN0YXR1cyI6ICJvayIsICJ0cyI6ICIyMDI2LTA4LTI5VDA2OjQxOjAwKzAwOjAwIiwgInR5cGUiOiAiY29ycmVjdGlvbiJ9", "stored_at": "2026-08-29T06:41:00.885410+00:00", "type": "ghostlight.evidence_record.v0"}
{"key": "2026-08-29T073601Z0000-0216-222d1665a572", "payload": "eyJub3RlIjogIlRoZSB0eXBlZCByZXN1bWFibGUgbmV3c3BhcGVyIHJlY29uY2lsaWF0aW9uIGN1dCBpcyBpbXBsZW1lbnRlZCBhbmQgbG9jYWxseSB2ZXJpZmllZC4gSXQgcGVyc2lzdHMgYSB0eXBlZCBnZW5lcmF0aW9uLXplcm8gY2hlY2twb2ludCBhbmQgYSBuZXcgdHlwZWQgY2hlY2twb2ludCBhZnRlciBldmVyeSByZWplY3Rpb24sIGJpbmRpbmcgdGhlIGV4YWN0IGRyYWZ0LCBjb3B5LWRlc2sgdmVyZGljdC9maW5kaW5nIGNhdGFsb2csIHdvcmtiZW5jaCBzdGF0ZSwgbW9kZWwgYW5kIGRldGVybWluaXN0aWMgcmVjZWlwdHMsIHNvdXJjZS9jb250cmFjdCBpZGVudGl0eSwgYW5kIHJlbmRlcmVkL2ZpbGUgYW5jZXN0cnkuIFJlc3VtZSBjb250aW51ZXMgdGhhdCBjaGFpbiB3aXRob3V0IHJlcnVubmluZyB0aGUgZWRpdG9yLiBUeXBlZCBydW40NiBpbXBvcnQgY29udmVydHMgdGhlIHByZXNlcnZlZCB0ZXJtaW5hbCBldmlkZW5jZSBpbnRvIHRoZSBzYW1lIGNoZWNrcG9pbnQgY29udHJhY3QuIEdhdGVzIHBhc3MgNDczLzQ3MyBsaWJyYXJ5IHRlc3RzIGFuZCAxMy8xMyBzdHJhdGVnaWMtc21va2UgYmluYXJ5IHRlc3RzLiBQbGFubmVkLW9ubHkgc3RlZXJpbmcgaXMgcmV0aXJlZC4gTmV4dCBhY3Rpb24gaXMgY29tbWl0L3B1c2gsIHRoZW4gbGF1bmNoIHN0aWxsLXVubGF1bmNoZWQgcnVuNDcgYnkgY29udGludWluZyBydW40NiBJc3N1ZTE1IHdpdGhvdXQgZWRpdG9yLCB3b3JsZCwgb3Igc3VjY2Vzc2Z1bC1wdWJsaWNhdGlvbiByZXBsYXkuIE9uZSBnb29kIENhbm9weSBMZWRnZXIgcmVtYWlucyBmaXJzdDsgcHVibGljYXRpb24gUGVyc29uYXMgbGF0ZXI7IGxheW91dCBhbmQgZW5ncmF2ZWQgY3V0cyBhZnRlciBlZGl0b3JpYWwgYWNjZXB0YW5jZS4iLCAic3RhdHVzIjogIm9rIiwgInRzIjogIjIwMjYtMDgtMjlUMDc6MzY6MDErMDA6MDAiLCAidHlwZSI6ICJ2ZXJpZmljYXRpb24ifQ==", "stored_at": "2026-08-29T07:36:01.432048+00:00", "type": "ghostlight.evidence_record.v0"}
{"key": "2026-08-29T074240Z0000-0217-f33c078abf64", "payload": "eyJub3RlIjogIlRoZSBmaW5hbCByZXN1bWFibGUtbmV3c3BhcGVyIGdhdGUgaXMgNDc1LzQ3NSBsaWJyYXJ5IHRlc3RzIGFuZCAxMy8xMyBzdHJhdGVnaWMtc21va2UgdGVzdHMsIGluY2x1ZGluZyBzYW1lLXRhc2sgc291cmNlLWRyaWZ0IGFuZCBmb3JrIHJlamVjdGlvbiBiZWZvcmUgaW5mZXJlbmNlLiBDbGFyaWZpY2F0aW9uOiBwZW5kaW5nIGNoZWNrcG9pbnRzIGJpbmQgdGFzay9lZGl0b3JpYWwgaWRlbnRpdHksIGV4YWN0IGRyYWZ0LCByZWplY3RlZCB2ZXJkaWN0L2ZpbmRpbmdzLCBwYXJlbnQgZ2VuZXJhdGlvbiwgYW5kIG9yZGVyZWQgcmVjZWlwdCBhbmNlc3RyeS4gQWNjZXB0ZWQgY29tcG9zaXRpb24gYW5kIHN0cmF0ZWdpYyBhcnRpZmFjdCByZWNvcmRzIHNlcGFyYXRlbHkgYmluZCBpc3N1ZSBhbmQgcmVuZGVyZWQtZmlsZSBhbmNlc3RyeTsgcmVuZGVyZWQgZmlsZXMgYXJlIG5vdCBjaGVja3BvaW50IHdvcmtiZW5jaCBzdGF0ZS4gUnVuNDcgcmVtYWlucyB1bmxhdW5jaGVkLiIsICJzdGF0dXMiOiAib2siLCAidHMiOiAiMjAyNi0wOC0yOVQwNzo0Mjo0MCswMDowMCIsICJ0eXBlIjogImNvcnJlY3Rpb24ifQ==", "stored_at": "2026-08-29T07:42:40.777233+00:00", "type": "ghostlight.evidence_record.v0"}

View file

@ -326,7 +326,7 @@ 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. The defect belongs upstream in causal follow-through and outcome ownership, not in newsroom spice prompting. 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-source run 45 at `1d898489daeb34fc9f53e6c6bddf00738fc66364` resumed revision 27 without replaying world mechanics or successful publications. Issues 13 and 14 remained preserved. Reconciliation action one was rejected locally before the copy desk; action two was admitted and rechecked, and the deterministic copy desk returned four fresh findings, but the two-action ceiling left no action to answer them. Issues 15 through 18 and the final digest remain absent. Canonical world state and all wave checkpoints remain unchanged. Run 41's remote root is `/var/lib/gamecult/ghostlight-dungeon/acceptance/elven-realms-autonomous-46810c3-41`; runs 38, 39, 40, 42, 43, and 44 remain preserved. Run 39's independent FAIL remains historical acceptance evidence, and overall acceptance is still incomplete.
implementation_checkpoint: Pushed `1d89848` owns two-action reconciliation with a refreshed finding catalog in one workbench state and receipt chain; run 45 provider-proved both local admission correction and fresh copy-desk feedback. The current uncommitted cut implements exactly three total actions: the initial action, at most one local-admission correction, and at most one fresh copy-desk correction. A rejection after the third action is terminal. The same workbench and receipt chain persist throughout, and the editor does not rerun. The exact mixed-path regression and full gates pass: 16/16 newspaper tests, 470/470 library tests, 13/13 strategic-smoke binary tests, and `git diff --check`. Plan commit `4916638` separately admits persistent political/editorial publication Personas; that later architecture is not part of this acceptance repair.
next_action: Commit and push the verified three-action reconciliation cut, then launch exact-source run 46 from immutable run 45's preserved Issue 13 and Issue 14 boundary at revision 27 without replaying world mechanics or successful publications. Earn independent editorial and provenance acceptance for one good Canopy Ledger publication before implementing multiple publication Personas; defer TeX/layout and engraved-cut production until editorial acceptance. Require Issues 15 through 18 and the final digest to pass fresh independent review before claiming acceptance. Preserve runs 38/39/40/42/43/44/45, the run-41 root and committed world lineage, stopped run 23, exact acceptance conditions, and unchanged live daemons.
provider_acceptance_checkpoint: Exact-source run 46 at `dfe0926041b83d69a4b02451cdab706572522ae3` resumed revision 27 without replaying world mechanics or successful publications. Issues 13 and 14 remained preserved. Issue 15 exhausted three admitted repair/copy-desk rounds; the final exact finding is one mechanical duplicated clause. Issues 15 through 18 and the final digest remain absent. Unit invocation is `3276392f4a7149fc9d127a966e792633`. Canonical world state and all wave checkpoints remain unchanged. Run 41's remote root is `/var/lib/gamecult/ghostlight-dungeon/acceptance/elven-realms-autonomous-46810c3-41`; runs 38, 39, 40, 42, 43, 44, and 45 remain preserved. Run 39's independent FAIL remains historical acceptance evidence, and overall acceptance is still incomplete.
implementation_checkpoint: Pushed `dfe0926` owns the locally verified three-action reconciliation ceiling and run 46 provider-proved all three admitted rounds. The current uncommitted resumable-reconciliation cut persists a typed generation-zero checkpoint and a new typed checkpoint after every rejection, binding the exact draft, copy-desk verdict and finding catalog, model and deterministic receipts, and source/contract identity. Accepted composition and strategic artifact records separately bind issue and rendered-file ancestry. Resume continues the exact pending chain without rerunning the editor; typed run-46 import converts the preserved terminal evidence into the same checkpoint contract. Gates pass 475/475 library tests and 13/13 strategic-smoke binary tests, including same-task source-drift and fork rejection before inference. Plan commit `4916638` separately admits persistent political/editorial publication Personas; that later architecture is not part of this acceptance repair.
next_action: Commit and push the verified typed resumable-reconciliation checkpoint and run-46 import cut, then launch run 47 by continuing run 46's Issue 15 draft/verdict/receipt chain without editor, world, or successful-publication replay. Run 47 remains unlaunched. Earn independent editorial and provenance acceptance for one good Canopy Ledger publication before implementing multiple publication Personas; defer TeX/layout and engraved-cut production until editorial acceptance. Require Issues 15 through 18 and the final digest to pass fresh independent review before claiming acceptance. Preserve runs 38/39/40/42/43/44/45/46, the run-41 root and committed world lineage, stopped run 23, exact acceptance conditions, and unchanged live daemons.
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.