Skip to content

ARN-65: harden Foresight no-wedge reliability - #422

Draft
rita-aga wants to merge 47 commits into
mainfrom
codex/searched-corridor
Draft

ARN-65: harden Foresight no-wedge reliability#422
rita-aga wants to merge 47 commits into
mainfrom
codex/searched-corridor

Conversation

@rita-aga

@rita-aga rita-aga commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Lane 3 minimum no-wedge delta for ARN-65: add World.Seeding -> ResumeSeed self-heal so a surveyor/bookmaker batch that never reports SeedComplete cannot wedge the world in Seeding.

This PR is against the active handoff branch/worktree codex/searched-corridor and intentionally avoids the dirty user files:

  • os-apps/paw-foresight/wasm/seed_world/src/lib.rs
  • os-apps/paw-foresight/wasm/grade_hindcast/Cargo.lock
  • os-apps/paw-foresight/wasm/register_forecasts/Cargo.lock
  • Dockerfile.foresight-patch
  • railway.foresight-patch.toml

Changes

  • crates/temperpaw/tests/corridor_engine_contract.rs: red/green contract for Seeding self-heal.
  • os-apps/paw-foresight/specs/world.ioa.toml: removes Seeding from indefinite states, adds 1200s state_timeout to ResumeSeed, and adds ResumeSeed self-loop that triggers seed_world.
  • os-apps/paw-foresight/specs/model.csdl.xml: exposes bound World.ResumeSeed.
  • os-apps/paw-foresight/policies/foresight.cedar: permits system-only ResumeSeed with the other World self-heal actions.

No ADR added: this is the last missing application of existing ADR-007 self-heal semantics to a single wait state, not a new architectural decision.

Evidence

Red:

  • 9cd44847 test(foresight): pin seeding self-heal contract
  • cargo test -p temperpaw --test corridor_engine_contract world_seeding_self_heals_when_surveyor_never_reports_seed_complete failed as expected: World.Seeding must not be indefinite.

Green:

  • 66129a71 fix(foresight): self-heal world seeding
  • cargo test -p temperpaw --test corridor_engine_contract world_seeding_self_heals_when_surveyor_never_reports_seed_complete passed.
  • cargo test -p temperpaw --test corridor_engine_contract passed: 17/17.
  • cargo build -p temperpaw passed.
  • cargo build --manifest-path os-apps/paw-foresight/wasm/seed_world/Cargo.toml --target wasm32-unknown-unknown --release --locked passed and produced os-apps/paw-foresight/wasm/seed_world/target/wasm32-unknown-unknown/release/seed_world.wasm.
  • Post-build git status still shows only the pre-existing dirty files listed above.

Known caveat from dirty user work:

  • cargo test --manifest-path os-apps/paw-foresight/wasm/seed_world/Cargo.toml --locked is currently red under the pre-existing dirty seed_world/src/lib.rs: 6 passed, 1 failed (surveyor_prompt_carries_the_event_node_and_seed_complete_contract, missing exact "world_id": "w-1" prompt assertion). This PR did not edit that file.

Live/canonical proof status

Read-only Railway discovery shows dedicated foresight production service is running ghcr.io/nerdsane/temperpaw:sha-0271650 at https://foresight-production-72d1.up.railway.app, with new Postgres-v79y also running. This PR is not deployed there yet.

Unauthenticated readiness probe:

  • curl -fsS https://foresight-production-72d1.up.railway.app/readyz returned ready.
  • curl -fsS https://foresight-production-72d1.up.railway.app/paw/version returned 401, and this shell has no TEMPER_API_KEY/TEMPER_* live API credential.

Canonical no-wedge e2e remains pending on deploy/install of this PR to the current canonical foresight target and an authorized API key to run scripts/prove_corridor_e2e.py against that target. Lane 3 is not taking the DB migration; it should coordinate with Lane 4 on the final DB target before running the proof.

rita-aga and others added 30 commits June 12, 2026 14:59
The crystallized 0.3 formulation: decompose imagined futures into
separable claims, search routes per claim with bounded revision and
pruning, and price amendment (deformation flags) so drifting back to
consensus is never free. Governing commitments recorded: cost constants
are tunable priors, authored-claim resolution stays judged, evidence
adapters are pluggable, amendment is never silent. RFC opens with the
one-paragraph formulation; ADR-002 points here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One separable load-bearing assertion of an imagined future (ADR-004).
States Proposed→Bridging→Settled|Unreachable|Failed; amendment only via
the diff-carrying AmendText action; original_text frozen at creation;
route/round bookkeeping for the search budget; settled_route_id as the
per-claim single-writer guard. Registered in the corridor spec contract
(red first) and the merged-CSDL uniqueness gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Endpoint.SubmitForRepair now spawns a decomposer session that splits the
bundle into 3-8 separable load-bearing claims (DecompositionComplete);
the module creates Claim entities (original_text frozen) and fans out
Claim.SubmitForBridge in chunks of 2 through the bounded SpawnNextBridge
self-loop. spawn_repairers refits to per-claim routes: the Path gains
claim_id/route_index/round_count/revision_brief, the bundle is inlined
into the repairer prompt (the v1 temper.read silently failed — walls
13/15), and the prompt carries the AmendText + deformation drift
contract and the UpdateEdges depends_on contract. Cedar: Claim section
(search loop system-only, AmendText relay-writable), decomposition and
UpdateEdges relay permits; matrix pins the verdict denials.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…scade

aggregate_costs v2 owns the deterministic search policy (ADR-004), all
constants named tunable priors: the prune gate cuts routes whose
repairer-phase cost already exceeds the claim's best ceiling before any
adversary spend; expensive objections (any flag >= 20 pts) within the
2-round budget send the route back to its repairer with a brief of the
objections; claims settle on the cheapest acceptable route (<= 60), open
alternate routes (max 3) briefed on standing objections, or are honestly
MarkUnreachable; the claim -> endpoint -> world cascade derives endpoint
weights from per-claim verdicts (unreachable claims price at the bound)
and reports PathsScored once, guarded by canonical_path_id. Deformation
flags price like contradictions so amendment toward consensus is never
free. Legacy claim-less paths keep the 0.2 classify pass verbatim
(in-place evolution). spawn_repairers gains revision mode; the Path spec
gains RequestChallenge/Prune/RevisionRequested and the Pruned state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On a 'no' resolution, evidence_ingest walks the depends_on edges
(declared by repairers via EventNode.UpdateEdges) transitively —
cycle-safe, depth-bounded — retiring every node whose premise died, then
re-prices settled claims whose routes required dead or invalidated nodes
(+50/node, a contradiction/high: the bridge now contradicts reality). A
claim flipping classification dispatches World.BeginUpdate, alongside the
existing canonical-route check. Walk and reprice are pure and pinned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… CSDL

World gains AnimateDwellers/DwellersCast/SpawnNextDweller (casting +
bounded dweller fan-out) and AdjudicateNodes (judged resolution, as-of
date as data); EventNode gains AdjudicationVerdict (session-reported,
module-validated — sessions never decide truth) and FlagForHuman;
Dweller.RecordContradiction now carries claim_id + note and triggers the
flag-feedback reprice. Cedar: operator/system/relay permits per the
matrix; crate skeletons for animate_dwellers and adjudicate_nodes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…stories

One casting session proposes 2-3 personas grounded in the settled
claims; the module creates Agent + persona File + Dweller entities and
fans out one 50-turn traversal/story session per tick (bounded
self-loop). Dwellers live the canonical route as biography, record
traversals, file contradictions — which append to the route's challenge
flags and re-price the claim (contradiction/low, 12.5 pts, a tunable
prior) — and write first-person stories submitted through the
consistency gate. Gate-cleared artifacts now publish automatically
(PassCheck triggers the gate's publish arm): stories reach the feed
without an operator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
adjudicate_nodes: on World.AdjudicateNodes, overdue authored Confirmed
nodes (cap 5/dispatch, loud) each get one adjudicator session — web
evidence, rubric, self-reports EventNode.AdjudicationVerdict; the module
alone validates (yes/no AND confidence >= 0.8 AND >= 2 unique evidence
refs) and dispatches Resolve or FlagForHuman. Hindcast worlds never
adjudicate; sessions are never told to decide truth directly. 18 tests.

Hindcast library: three new frozen fixtures (2024h2 AI coding tools,
2025h1 LLM API market, 2023h2 consumer assistants), corpora vantage-
clean, actuals fact-checked; README documents that entry order and
needle narrowness are load-bearing for the matcher.

calibrate_constants.py: offline grid over kind weights / severities /
decay / tail ceiling, recomputing deterministically from recorded flags
(no model re-runs), ordering constraint enforced, honest about what the
metrics cannot identify; --self-test pins the recompute against
aggregate_costs' exact vectors.

Cedar: session-facing AdjudicationVerdict permit (mirrors
CheckerVerdict); artifact PassCheck trigger renamed
consistency_gate_publish (trigger-name ambiguity rejected the bundle —
caught by verify_specs, boot now clean).

Independent review: PASS after two re-review rounds; both P1s (Cedar
gap, fixture compound-question ordering) verified fixed empirically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The host's Cedar gate allowlists modules by name for http_call and
access_secret; decompose_endpoint, animate_dwellers, and adjudicate_nodes
were missing, so the decomposer's first outbound call died with 'no
matching permit policy' and both endpoints failed (found live, run 1).
Live tenant policy patched via the policies API in the same shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ClaimsAttached and SpawnNextBridge are module-dispatched (system);
DecompositionComplete and DwellersCast are session self-reports (mirror
SubmitForRepair/SeedComplete). Found live: the fan-out died 403 after
the decomposers had created all 15 claims.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ution

Run 1 produced 15 scored routes costing 90-312.5 (median ~245) against
an acceptability bound of 60 — every claim was marked Unreachable and
the verdict vector degenerated. The bound was set before any live
claim-level flag data existed. New priors: acceptability 240, reachable
120, revision threshold 40 (at 20, nearly every route revised and
session spend tripled), unreachable penalty 240. Mirrors updated in
evidence_ingest and animate_dwellers. Still priors (ADR-004 commitment
1); the hindcast calibrator owns the next revision with outcome data.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The first live run never finished unaided: the debug server aborted under
v2 concurrency and every interruption orphaned in-flight sessions, leaving
Paths stuck in Solving/Repaired/Challenged and Claims in Bridging with no
re-drive — recovered only by dozens of hand-dispatched actions.

Fix: release build for runs (debug can't take the load), and replace the
corridor wait states' allow_indefinite_states with state_timeouts whose
on_timeout re-spawns the dead work — ResumeRepair (re-spawn repairer),
RequestChallenge (re-spawn adversary), ResumeCosting (re-run costing),
ResumeBridge (re-run the claim decision). Budgets sit above a healthy
session so live routes never double-spawn; crash recovery rides the
ADR-0056 hydration re-arm. All re-drives are system-only in Cedar and
reuse existing spawn modules (no duplicate entities). The search budget
bounds the self-heal. verify_specs 10/10, contract+matrix green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ctions

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (D1)

Pure cosine/distance + the four decision functions the consumers need:
is_diverse + farthest_point_order (D3 diversity gate), cluster_by_threshold
(D4 synthesis: cross-world agreement), nearest (D2 reconcile, D4 hindcast
match), and parse_embeddings (Ollama batch/single + OpenAI shapes, so the
endpoint is swappable by secret). The semantic judgment comes from the
external model (mxbai-embed-large via Ollama); these decisions are pure,
deterministic, in-WASM. 7 golden-vector tests; compiles to wasm32.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ggler (D0)

claim_decision waited on ANY in-flight route, so a claim with a cheap-enough
scored route could deadlock forever behind a wedged straggler (a Path whose
adversary died and whose Repaired timeout keeps re-spawning). That made
ADR-007's "search budget bounds the self-heal" promise false in practice.

Gate the wait: a still-churning route only blocks the claim while the outcome
could still change — when the route budget allows a fresh alternate, or when
no acceptable route exists yet. Once the budget is spent (route_count >=
MAX_ROUTES) AND an acceptable route exists, settle on the cheapest acceptable
route; a stuck or flapping route can only match or slightly beat one we'd
already accept. A claim with no acceptable route still waits for its last
in-flight route. Refine ADR-007's consequences to describe this claim-level
bound accurately (the route-level round/route budget alone does not bound a
flapping adversary).

Red-green TDD: added wedged-straggler / budget-left / no-acceptable cases to
claim_decisions_wait_search_settle_and_give_up_honestly. 14/14 host tests pass
(cargo test --target aarch64-apple-darwin); aggregate_costs.wasm rebuilt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ing)

The repairer proposed authored EventNodes with a bare "resolve_by:
YYYY-MM-DD" instruction and no date guidance, so it eyeballed tidy
month/quarter ends (run-1: six nodes dated to round dates in one backward
pass). The world's lag_table_file_id and target_date were never read.

Read both from the world in both spawn modes (SubmitForBridge and
RevisionRequested/ResumeRepair), fetch + inline the lag table file, and add a
DATE DISCIPLINE block to the repairer prompt: date each node by adding the
historical lag for its transition (from the inlined table) to its
prerequisite's date, between TODAY and the horizon (target_date); compressing
below a historical lag raises a "lag" cost flag (severity by how far below),
tying rushed dates to the existing pricing mechanism. A missing lag table
degrades loudly (explicit "no lag table" instruction), never a silent date.

"Today" is rhetorical, exactly as the surveyor frames it — there is no stored
present-date field. frontier_date is the SCOREABLE HORIZON (per world.ioa.toml
and register_forecasts: claims resolving before it are registered), NOT the
present, so it is deliberately not passed as "today" — doing so would collapse
the timeline. Live repairers verify the current date via web search; hindcast
repairers read the vantage from the corpus.

Red-green TDD: lag_table_and_date_discipline_anchor_dates_to_history,
hindcast_date_discipline_pins_the_vantage_not_the_clock,
missing_lag_table_degrades_loudly_not_silently,
empty_horizon_falls_back_to_a_descriptive_phrase. 12/12 host tests pass;
spawn_repairers.wasm rebuilt. Live e2e exercise lands in the C6 flagship run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d PATCH

The dweller/story spine never completed end-to-end. Root cause, found on
run-1: animate_dwellers' DwellersCast handler persisted the dweller roster
with a raw PATCH /tdata/Worlds('...'), which Cedar denies (403 — only Admin
may PATCH a World; system flows drive it through actions). The handler
returned that error, the World transitioned to Failed (terminal), and the
three cast dwellers were orphaned with no traversal/story sessions. No
kind:story artifact had ever been produced because the flow died here.

Fix, entity-first: carry dweller_ids as a SpawnNextDweller action param
(exactly as check_count already rides), so the roster is persisted by the
permitted action transition rather than a raw field write. It is set on the
first tick (from DwellersCast) and persists across the omitting re-ticks, like
name/target_date/canonical_path_id. The denied PATCH is removed entirely.

- world.ioa.toml: SpawnNextDweller params ["check_count"] -> ["dweller_ids", "check_count"]
- model.csdl.xml: declare dweller_ids on the SpawnNextDweller action (the API
  silently drops undeclared params, so the binding must list it)
- animate_dwellers: drop the PATCH; emit dweller_ids in the SpawnNextDweller result

Verified: corridor_engine_contract (12/12) and corridor_cedar_matrix (10/10)
pass; animate_dwellers host tests (6/6) pass; wasm rebuilt. Run-1's World is
terminally Failed and cannot host the re-drive, so the full dweller -> story ->
gate -> publish path is verified live in the next fresh run.

The phase_contradiction handler PATCHes Paths the same way but is non-fatal
(warns; the Reprice action carries the pricing feedback) — noted as a separate
follow-up, not on the story-spine critical path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… D0 liveness)

The liveness fix (021ea51) lets a claim settle on its cheapest acceptable
route past an in-flight straggler once the route budget is spent — but left
that straggler self-healing. If the straggler can never complete (a
permanently-broken session), its Repaired/Solving/Challenged state_timeout
(ADR-007) re-spawns it forever on a claim that no longer needs it: the one
unbounded self-heal left after settlement.

Close it: when claim_phase settles, dispatch Path.Fail on every still-in-flight
sibling route, marked "superseded" (not an error — the claim found its answer
and the budget is spent). This fires only in the wedged-straggler case the
liveness fix introduced; a live route that completes normally lands in `scored`
and is classified Canonical/Tail/Reject as before. Now the search budget bounds
the self-heal fully: a flapping route can delay a settle, never outlive it.

14/14 host tests pass; aggregate_costs.wasm rebuilt. Exercised live in the next
fresh run (the HTTP settlement path is not unit-testable; claim_decision, which
gates this, is).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ster_forecasts (D2)

D1: corridor_embed gains build_embed_request — the {"model","input":[...]} body
both Ollama /api/embed and OpenAI /v1/embeddings accept, so dev (local Ollama)
and prod (hosted) differ only by the endpoint/model config. The pure math/parse
core was already committed; this completes the fetch contract.

D2 reconcile (G1): register_forecasts no longer registers an authored EventNode
that merely restates an already-determined fact (the "first-party agents grow as
rivals" p=0.55 bug — a present truth dated to the future, resolving yes for free
and poisoning calibration). It embeds the determined reference + the registrable
authored candidates in one batch and collapses any candidate within a strict
cosine distance of a determined node, logging every decision with its distance
and the matched fact. Degrades to exact-text matching when no embedder is
reachable (never a silent pass). Endpoint/model from config with local-Ollama
defaults; module-gated Cedar egress already permits the call.

The threshold (RECONCILE_MAX_DISTANCE=0.10) is a tunable prior, now calibrated
live against mxbai-embed-large: domain restatements measure ~0.06 (collapse),
distinct forecasts ~0.29-0.35 (kept) — a wide clean margin. It is a conservative
backstop; the primary G1 fixes are upstream (web-grounded surveyor, repairer not
minting already-true facts), and embedding distance alone cannot tell "restates
a present fact" from "a future change to one", so the threshold stays strict.

Verified: corridor_embed 8/8, register_forecasts 10/10 host tests; both build to
wasm32 with corridor_embed linked; threshold validated against the running
embedder. Live in-corridor exercise lands in the next fresh run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ADR-005 records the grounding decisions shipped in D2: web-grounded surveyor
present-state, lag-disciplined repairer dates, and the conservative embedding
reconcile backstop in register_forecasts (with the calibrated 0.10 threshold
and the strict-by-design rationale).

ADR-006 records the embedding capability (corridor_embed, D1, implemented) and
proposes the diversity design: named uncertainty axes on the sampling side
(surprising-vs-consensus) plus an embedding diversity gate on the verification
side (distinct-from-each-other). The gate needs a barrier; the World-barrier
(A) vs per-endpoint (B) choice is left open for review — recommended (A),
verified live in a flagship run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e corridor (D3)

The first run's modal and anti-modal worlds converged (G2): distinct stances,
near-identical futures, and the corridor then spent ~30 sessions each repairing
two versions of the same world. ADR-006 option A closes this with a World-level
barrier + embedding gate, so the budget only buys distinct worlds.

Flow: the endpoint writer now self-reports BundleWritten (Endpoint Sampled ->
Written, bundle recorded, NO decompose) instead of SubmitForRepair. On each
BundleWritten, sample_endpoints runs the all-written barrier; once no endpoint
is still Sampled it dispatches World.GateDiversity (round-counted). The gate
embeds each Written world's bundle-head (mxbai caps at ~512 tokens, so a head,
not the 30KB bundle; claims don't exist pre-decompose), greedily keeps the
diverse ones (corridor_embed::select_diverse vs already-released references),
releases them via Endpoint.SubmitForRepair (-> decompose -> repair), and
re-steers collapsed ones via Endpoint.ReSteer (re-spawn the writer with a
"diverge from <nearest world>" brief). After GATE_MAX_ROUNDS=2 a persistent
near-duplicate is Discarded. DIVERSITY_MIN_DISTANCE=0.15 is a tunable prior
(logged per round). If the embedder is unreachable the gate releases all worlds
loudly rather than wedge the pass.

Spec/CSDL/Cedar: new Endpoint state Written; actions BundleWritten (writer,
session-relay), SubmitForRepair (now paramless, system — gate releases),
ReSteer (system); Endpoint.revision_brief; World.gate_rounds + GateDiversity
(system). SubmitForRepair moved from writer-self-report to system-dispatched.

Tests: corridor_embed select_diverse (9/9), sample_endpoints prompt/contract
(10/10), corridor_engine_contract (12/12), corridor_cedar_matrix (10/10); all
wasm rebuilt. Gate orchestration (barrier/re-steer/release) verified live in the
flagship run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
run-1b wedged: both endpoint writers ran, then their BundleWritten self-report
was Cedar-denied (→ routed to the missing request_approval module → session
Failed), leaving the endpoints stuck in Sampled with no bundles. Root cause:
session self-reports arrive as the service:wasm-runtime relay principal, and
the Endpoint relay permit still listed only the OLD writer action
(SubmitForRepair). D3 renamed the writer's self-report to BundleWritten but
added only the generic ["system","agent"] permit — which the relay principal
(agent_type "service") does not match.

Fix: the relay permit now grants BundleWritten + DecompositionComplete + Fail
on Endpoint; SubmitForRepair and ReSteer are removed from it (the diversity
gate dispatches those as system, never a session). Regression guard added to
corridor_cedar_matrix::decomposition_and_edges_ride_the_relay: the relay grants
BundleWritten and is denied SubmitForRepair/ReSteer. 10/10 matrix tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ng side)

run-1c proved the diversity GATE works but the SAMPLING was weak: generic
"anti-modal" stances ("85th percentile on the most load-bearing uncertainty")
converged onto the modal world, so budget-2 collapsed to a single distinct
world (the gate correctly discarded the duplicate, but it cannot manufacture
diversity the sampling never created). This is run-1's exact G2 failure.

Fix (ADR-006 sampling side): the surveyor now names the top 3-5 load-bearing
UNCERTAINTY AXES of the domain — the dimensions on which the future genuinely
forks — and the consensus pole of each, reported via SeedComplete and stored on
World.uncertainty_axes. driver_stance assigns each anti-modal world a DIFFERENT
named axis to invert (slot 0 modal; slot i inverts axes[i-1] against its
consensus pole), so the worlds are distinct by construction before the gate ever
runs. Falls back to the generic tail stance when there are fewer named axes than
budget slots. The gate remains the safety net for residual convergence.

- seed_world: surveyor names axes + reports uncertainty_axes in SeedComplete
- world.ioa.toml + CSDL: uncertainty_axes field + SeedComplete param
- sample_endpoints: parse_axes + axis-naming driver_stance(i, axes)

Tests: seed_world 6/6 (surveyor names axes), sample_endpoints 10/10
(stance_assignment_inverts_named_axes_and_falls_back), corridor_engine_contract
12/12, corridor_cedar_matrix 10/10. All wasm rebuilt. Live in the next run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… pace note

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ng needles (D4)

grade_hindcast matched actuals to forecasts by a case-insensitive substring
`.find`, which silently took the FIRST of several matches — order-sensitive and
fragile. It now matches by event_node_id exact first, then by nearest embedding
of the actual's descriptor to the forecast questions (deterministic, via
corridor_embed::nearest within MATCH_MAX_DISTANCE), and falls back to substring
only when the embedder is unreachable. Each embedding match logs its distance
for calibration. Endpoint/model from config with local-Ollama defaults; module-
gated Cedar egress already permits the call.

Note: the bundled fixtures use bare keyword needles ('gpt-5', 'price'), which
embed weakly; richer full-text actual descriptors would sharpen matching — a
fixture-data follow-up, separate from this matching mechanism.

Tests: grade_hindcast 9/9 (substring tests adapted to the index helper); wasm
rebuilt with corridor_embed linked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dor pace)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ies, grounded)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rita-aga and others added 17 commits June 14, 2026 02:38
… calibration)

run-1d exposed a gate-signal flaw: with named axes, the anti-modal world inverts
ONE axis (autonomy) but holds consensus on the other four, so the two worlds'
bundle-HEADS (shared dated-market retrospectives) measured only 0.111 apart —
below the 0.15 threshold — and the gate re-steered a genuinely-forked world.
Their SUMMARIES (the writers' one-line theses: "mature market" vs "policy-gated
autonomous production") measured 0.298 apart, correctly distinct.

So the gate now embeds the endpoint SUMMARY (which captures a world's thesis and
divergence), falling back to the bundle-head only when a summary is missing.
0.298 > 0.15 keeps both named-axis worlds; near-identical summaries still
re-steer. Simpler too — the summary is already on the endpoint (no bundle fetch).

sample_endpoints 10/10 host tests pass; wasm rebuilt. Verified live next run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… 2 distinct worlds

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…howcase needs clean env (2.7GB DB exhausted)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… collapses an unmeasurable world

The summary-gate fix (6ab85c3) was necessary but not sufficient. phase_gate
read each endpoint's Summary from the OData list projection, which lags the
authoritative state BundleWritten had just committed. When the just-written
endpoint showed an empty summary, the code fell back to the bundle-HEAD — the
exact 0.11 false-collapse signal 6ab85c3 meant to retire. So distinct worlds
were still collapsed under projection lag (run-1c en-019ec480: summaries 0.298
apart yet one Discarded; ec4d2: 0.254 apart yet one Discarded — both settled as
single live worlds, defeating the diverse-portfolio guarantee).

Fix:
- phase_gate re-reads every endpoint authoritatively (fetch_entity), never the
  lagging list projection, for the gate decision.
- Delete the bundle-head fallback (fetch_bundle_head + BUNDLE_HEAD_CHARS): the
  head is a known-bad signal and must never drive a discard.
- New pure gate_decision(has_summary, diverse, rounds): a world we cannot yet
  measure (summary still absent after the authoritative read) is Released, never
  collapsed. Discard fires only past the re-steer round cap.

Unit-proven (12/12 host tests incl. 2 new gate tests); wasm rebuilt (8360a1ef)
and confirmed loaded. Live validation of the full diverse portfolio is blocked
by local file-SQLite write saturation (2.7GB, "database is locked"); the logic
is grounded in the forensics above + select_diverse's tested behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… restart)

Captures verified deploy state mid-flight: prod openpaw up, Genesis registry,
full bootstrap-refs string (bump only paw-foresight), publish-via-MCP steps,
and the v0.1→corridor model-replacement decision. So the post-restart session
(temper MCP repointed at openpaw) resumes without losing context.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Self-contained context dump for agent/operator takeover covering
architecture, branches, progress, live blocker, and playbook.
Copy the active C0-C8 / D0-D4 plan into the repo as the canonical
portable source (replacing ~/.claude/plans/ path). Update handoff
§4, quick-reference, and read-next list accordingly.
Endpoint.Sampled sat in allow_indefinite_states with no state_timeout, so a
writer session that died — or a server restart that orphaned its in-flight
provider call — wedged the endpoint forever, and the diversity-gate barrier
never fired because one sibling bundle was never written. Observed live on prod
world en-019ed392 (2026-06-16): all 3 endpoint writers hung on their first model
call; the run could only continue via hand re-spawn.

Mirror Path's Solving/Repaired/Challenged self-heal: a 900s state_timeout on
Sampled re-spawns the writer via a new ResumeWriter self-loop action, which
shares phase_resteer with the gate's ReSteer (re-spawn with the endpoint's
stance + any revision_brief). Budget sits well above a healthy writer (~3 min
observed) so a live session is never double-spawned; a late duplicate's
BundleWritten no-ops once the endpoint has left Sampled.

- specs/endpoint.ioa.toml: drop Sampled from allow_indefinite_states; add the
  Sampled state_timeout and the ResumeWriter action.
- wasm/sample_endpoints: route ReSteer | ResumeWriter -> phase_resteer.
- policies/foresight.cedar: permit system-dispatched ResumeWriter on Endpoint.
- corridor_engine_contract: pin Sampled self-heal (13 passing; +1).

Host tests 12/12; contract suite 13/13.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… writer-stall root-caused

First clean corridor run on prod Postgres (ref paw-foresight@7c19bf9). The
diversity gate fired (gate_rounds=1) and released all 3 distinct worlds with 0
discards — the live proof of the e3543f7 authoritative-summary gate fix. Logs
the writer-phase stall + recovery and the durable self-heal follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + lesson

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + run resumed via route nudge

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… API-guessing)

Adversary (and the other session-spawning modules') prompts said only "Write a
<thing> with temper.write" with no call signature or return shape, then asked
the agent to splice in "<file-id-from-temper.write>". A small fraction of agents
(observed ~2/200+ on prod world en-019ed392) reverse-engineered file creation
through temper.action against the Directories entity set
(Create/AddFile/CreateFile/Files.Create) — actions that don't exist on the
Directory automaton — tripping a Cedar implicit-deny -> request_approval ->
Session WaitingForApproval. A spawned agent has no chat channel, so no human
approves; it auto-resumes, re-guesses, and loops forever — the Path never
reaches ChallengeComplete and the owning Claim stays wedged in Bridging.

Class fix across all six session-spawning modules + five soul mirrors: replace
the bare token with an explicit, copyable recipe — result = temper.write("/<name>.md", "...")
returns {file_id, path, workspace_id}; use result["file_id"] — plus an explicit
prohibition on inventing a file/dir-creation API via temper.action (the
workspace already exists). No Cedar change: the temper.write path is already
permitted (system-relayed agent write); Directory file-create permits were
deliberately NOT added (those actions don't exist). Each prohibition is scoped
so temper.create("EventNodes"/"Artifacts") in repairer/surveyor/dweller/author
still works. Red-green prompt-contract test added per module; all six wasm
rebuilt. Host tests 55/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dents, fixes, blocker

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
)

Add World.Active ResumeWorldCascade and Endpoint.UnderRepair
ResumeEndpointScoring state_timeouts with aggregate_costs handlers.
Re-running world_cascade when all claims are terminal but canonical_path_id
is unset prevents the en-019ed392 wedge; endpoint scoring self-heal covers
missed ScoreComplete dispatches. Contract tests 3/3 pass.
…pace lookup, self-heal gaps #1-4

- Inline repair log + bundle at adversary spawn; strip temper_read from toolset
- Reuse per-world workspace via OData lookup + retry (avoids HTTP 500 dead paths)
- Writer/endpoint/world self-heal timeouts (gaps #1, #3, #4) already in specs+wasm
@rita-aga

Copy link
Copy Markdown
Collaborator Author

Lane 3 merge-state diagnosis / safe path update

GitHub's mergeStateStatus=DIRTY is a real conflict (mergeable=CONFLICTING), not a stale status.

Root cause: PR #422's current head is the broad codex/searched-corridor history, while main has since merged/changed overlapping corridor/platform work. A read-only git merge-tree $(git merge-base origin/main HEAD) origin/main HEAD shows conflicts across the broad branch, including:

  • crates/temperpaw/tests/corridor_engine_contract.rs
  • os-apps/paw-foresight/policies/foresight.cedar
  • os-apps/paw-foresight/specs/endpoint.ioa.toml
  • os-apps/paw-foresight/specs/model.csdl.xml
  • os-apps/paw-foresight/specs/world.ioa.toml
  • multiple paw-foresight WASM manifests/sources including dirty-work-adjacent seed_world/src/lib.rs, grade_hindcast, register_forecasts, sample_endpoints, aggregate_costs, spawn_repairers

So resolving #422 as-is would require broad searched-corridor reconciliation beyond Lane 3 and would touch/decide dirty-work-adjacent files.

Safe mergeable shape tested in a temp clone instead:

  • Temp branch: /tmp/arn65-selfheal-stack-check, based on origin/main
  • Commits replayed narrowly: 22795542 Endpoint.Sampled writer self-heal, 638ff9b1 Endpoint.UnderRepair + World.Active aggregate resume, 9cd44847 Seeding red contract, 66129a71 Seeding fix
  • Only one mechanical conflict during replay: contract-test placement around corridor_wasm_modules_are_packaged_for_core_startup; resolved by keeping main's packaging test and appending the self-heal tests.
  • Resulting diff touches only:
    • crates/temperpaw/tests/corridor_engine_contract.rs
    • os-apps/paw-foresight/policies/foresight.cedar
    • os-apps/paw-foresight/specs/endpoint.ioa.toml
    • os-apps/paw-foresight/specs/model.csdl.xml
    • os-apps/paw-foresight/specs/world.ioa.toml
    • os-apps/paw-foresight/wasm/aggregate_costs/src/lib.rs
    • os-apps/paw-foresight/wasm/sample_endpoints/src/lib.rs

Verification on that temp main + self-heal stack:

  • cargo test -p temperpaw --test corridor_engine_contract passed 17/17.
  • cargo build -p temperpaw passed.
  • cargo build --manifest-path os-apps/paw-foresight/wasm/sample_endpoints/Cargo.toml --target wasm32-unknown-unknown --release --locked passed.
  • cargo build --manifest-path os-apps/paw-foresight/wasm/aggregate_costs/Cargo.toml --target wasm32-unknown-unknown --release --locked passed.
  • cargo build --manifest-path os-apps/paw-foresight/wasm/seed_world/Cargo.toml --target wasm32-unknown-unknown --release --locked passed.

I have not force-pushed this temp stack over codex/searched-corridor, because that would rewrite/drop the broad active branch history from PR #422's remote head. It would not touch the local dirty files, but it is still a branch-ownership decision.

Precise next choice needed:

  1. Authorize replacing PR ARN-65: harden Foresight no-wedge reliability #422's head (codex/searched-corridor) with the tested minimal self-heal stack from /tmp/arn65-selfheal-stack-check via force-with-lease, keeping one PR; or
  2. Keep ARN-65: harden Foresight no-wedge reliability #422 as the broad searched-corridor handoff and allow a new minimal Lane 3 PR from the tested stack.

Canonical target after Lane 4: Railway foresight deployment 55c673b1-1aa2-455f-befe-065b35683e70 is running ghcr.io/nerdsane/temperpaw:sha-0271650 against Postgres-v79y; /readyz is ready. Authenticated e2e still needs deployment/install of the self-heal stack plus a live API key (/paw/version returns 401 without one).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant