Commit graph

1663 commits

Author SHA1 Message Date
Shay
b2b4d79bc0
feat: integrate hardened CLOSE yardstick into determinism & teaching regression surfaces (#792)
- docs/testing-lanes.md + Makefile: recommended determinism regression invocation (uv run python -m evals.close_derived_climb + contract pytest) as part of standard verification story / rerun flows.
- evals/anti_regression/run_demo.py + tests/test_anti_regression_demo.py: hermetic embedding of the yardstick (lived IdleTickResult flag, semantic determine rule=direct, content_replay_checksum) into the core anti-regression / teaching demo flow (core demo anti-regression now carries + reports the Claim-B signals; active corpus remains untouched).
- Supporting docs: contract.md (uv + refs), anti_regression_demo.md (complementary note), runtime_contracts.md (determination surface cross-ref).
- New ratification artifact (pre-impl) + all success criteria met.

See ratification for chosen approach + why only correct path.
All existing tests/invariants green; hermeticity preserved; no core/engine changes.
2026-06-16 17:36:25 -07:00
Shay
7a20356ab6
feat(eval): harden close-derived-climb yardstick to full Claim B (#791)
- Semantic: determine() asserts (rule=direct) on positives post-FP in climbs
- Lived flag: _proposal_flag_effect now uses real idle_tick + IdleTickResult.derived_close_proposals_emitted (temp sink isolation)
- Checksum: added content_replay_checksum (canonical closures + proposal bodies)
- Docs: contract.md + module docstrings updated for accuracy

Ratification artifact created before impl (see docs/analysis/...-ratification-...md).
All original metrics, wrong_total=0, invariants preserved.
Closes the 3 gaps from post-merge audit for Claim B.
2026-06-16 17:10:11 -07:00
Shay
cf1e73716c
feat(eval): yardstick for CLOSE derived climb (#790)
* feat(eval): add yardstick lane for CLOSE derived climb (is-a + relational + flag + wrong=0)

* fix(eval): correct proposal flag measurement in yardstick (direct emit for isolation; runner now reports True)

* fix(eval): measure CLOSE climb from live queries

* fix(eval): require strict CLOSE climb growth
2026-06-16 16:09:56 -07:00
Shay
f4afb34cb9
feat(proposals): bridge derived CLOSE facts into review proposals (#789)
* test(proposals): cover derived CLOSE proposal bridge

- Flag-off and emission for member/subset + relational transitive derived facts.
- Stable dedupe, skips for non-derived/malformed/unsupported.
- No status upgrade, no corpus side-effects.
- Runtime flag wiring smoke (consolidate + bridge on → emits after idle_tick).
- Re-runs of PR-1 tests + invariants stay green.

* feat(proposals): emit review-gated proposals from derived CLOSE facts

- New focused emitter generate/determine/derived_close_proposals.py:
  - collect eligible derived (member/subset + TRANSITIVE) with entailed Derivation + SPECULATIVE.
  - deterministic sort + stable dedupe key (predicate+args+derivation+structure_key).
  - write proposal-only artifacts (source=derived_close_fact) to dedicated sink.
  - best-effort, counts returned, safe skips.
- RuntimeConfig: review_derived_close_proposals (default False), documented.
- ChatRuntime.idle_tick: after consolidation (when flag on), call emitter best-effort (no did_work, no engine checkpoint).
- IdleTickResult: new field derived_close_proposals_emitted (additive, documented).
- No change to existing proposal_review contract or comprehension-failure sink.
- No serving/determine side-effects.

* docs(proposals): document derived CLOSE proposal bridge

- docs/runtime_contracts.md: new subsection under idle passes describing eligibility,
  contract (proposal-only, review-gated, default-off, no corpus/COHERENT/serving change),
  and cross-ref to the analysis note.
- New dated analysis note with why (flywheel bridge after PR-1), architecture/boundaries,
  artifact schema, dedupe, tests, non-goals, and composition with PR-1/PR-3.
- Tiny high-level pointer not added (no suitable learning-loop section in README at this granularity).

* fix(proposals): harden derived CLOSE proposal emission
2026-06-16 15:39:46 -07:00
Shay
a1bdaa2ce5
feat(determine): consolidate declared relational transitive CLOSE facts (#788)
* test(determine): cover relational transitive CLOSE consolidation

- extend with rel_pack + _tell_rel/_ask_rel/_rel_facts helpers using comprehend_relational for strict-order facts
- positives: less_than(a,b)+less_than(b,c) -> consolidate less_than(a,c); before_event same; greater/after covered
- derived record shape: .derived=True, epistemic_status="speculative", derivation.rule="transitive", .verdict="entailed", premise_structure_keys from verified grounds
- direct recall (_rel_facts sees it) + determine() answers directly post-consolidate
- multi-hop climb: p(a,b) p(b,c) p(c,d); tick1 adds a-c/b-d; tick2 adds a-d; fixed point no-op
- negatives/wrong=0: parent_of/sibling_of/left_of chains refused (no derive, Undetermined); inverse mix (less+greater) does not leak; TRANSITIVE_PREDICATES set pinned to exactly the four declared; member/member fallacy test untouched
- existing is-a (member/subset) and idle_tick tests remain green

TDD: tests added first (RED observed), impl followed to GREEN. Targeted consolidation/relational/invariants pass.

* feat(determine): consolidate declared relational transitive derived facts

- import TRANSITIVE_PREDICATES and _relational_transitive (narrow reuse of Phase C verifier)
- add _relational_transitive_one_hop_candidates(ctx): for p in TRANSITIVE only, direct 2-hops p(a,c) from realized p(a,b)+p(b,c), no reflexive (a==c), dedup vs existing, same-pred only (no inverse/symmetric/cross-predicate), deterministic sort
- consolidate_once: recall member/subset as before + rel_cands via new helper; unify + global (predicate,subject,object) sort for determinism independent of recall
- processing loop: if predicate in TRANSITIVE_PREDICATES: det = _relational_transitive(...) ; if not Determined continue; realize_derived(..., rule="transitive", premise_structure_keys from det.grounds) ; else: original _verify_subsumption + _RULE...
- verification mandatory before any write (candidate construction insufficient); realize_derived produces SPECULATIVE record with replayable derivation metadata
- budget: per-p _TRANSITIVE_EDGE_BUDGET enforced inside the reused verifier (safe no-write on over); is-a subset budget unchanged
- no change to member/subset paths or _one_hop sig for is-a; member∨member fallacy structurally unreachable as before
- wrong_total=0, INV-30 (open-world, no answer=False), only the four declared preds, no FrameVerdict, no corpus/ratified mutation

Extends CLOSE (Step D) to climb declared relational transitive substrate (Phase C) exactly as specified. Existing subsumption behaviour preserved.

* docs(determine): document relational CLOSE extension + capability-slice PR workflow

- Extended the canonical "Idle consolidation (Step D — CLOSE)" section in runtime_contracts.md to name the new declared relational transitive support (less_than/greater_than/before/after) while re-stating every existing contract (SPECULATIVE honesty, wrong=0 by verifier, replayable provenance, session-only, no COHERENT, no parallel path, etc.).
- Added dated analysis note docs/analysis/close-relational-transitive-pr1-2026-06-16.md: the reusable, organized record of (a) what PR-1 delivered, (b) full evidence, (c) the 3-PR sequence with wait conditions, (d) exact branch/worktree starting discipline (clean origin/main reconcile + force-fresh feat/ creation to avoid hygiene mix), (e) finishing/PR discipline (logical slices, 9-section report format), and (f) the documentation obligation when design/capability changes land.
- Tiny accurate cross-ref in root README.md near the learning-loop demo (high-level only; contract details live in runtime_contracts.md).

This satisfies the "properly update README.md's / documentation etc." rule for a design/capability extension. The note itself is the "solid organized, clean plan" for future slices.
2026-06-16 15:15:59 -07:00
Shay
5c048f9782 chore(repo): remove accidental local runtime artifacts 2026-06-16 12:42:41 -07:00
Shay
b30a1cf538 work done towards furthering comprehension, contemplation, learning, and also began working on fixing some failing tests 2026-06-16 12:27:59 -07:00
Shay
ba05ebb0cd
feat(frame-verdict): closed-world FrameVerdict substrate — PR-1..4 + hardening (ADR-0222 B4) (#787)
* wip(b4): lift _basis to generate/epistemic_basis.py (behavior-preserving)

B4 PR-1 foundation checkpoint — the shared epistemic-basis helper extracted
byte-identically from determine.py so the closed-world frame_verdict evaluator
(coming) can compute standing without importing determine (ADR-0222 §8 A3).
determine.py imports it aliased; its 3 Determined sites + INV-30 unchanged.
PAUSED: rest of B4 (FrameVerdict type, evaluator, INV-31, PR-2/3/4) resumes
after the suite-speed arc.

* feat(frame-verdict): closed-world envelope + isolated evaluator + INV-31 (B4 PR-1)

The sealed, off-serving FrameVerdict type + the isolated text-frame evaluator + the
INV-31 firewall (ADR-0222). Composes proof_chain.entail (no second prover); a
closed-world False comes ONLY from an ROBDD refutation; absence is never false.

- generate/frame_verdict/{types,evaluate,__init__}: FrameVerdict / ClosedFrame /
  ClosedWorldProof + PositiveRefutationKind enum. evaluate_frame_verdict(frame, query)
  maps entail ENTAILED/REFUTED/UNKNOWN/REFUSED -> entailed_true/false/undetermined/
  contradiction/scope_boundary; OPEN / undeclared-closure / non-TEXT -> scope_boundary.
  __post_init__ admissibility: entailed_false needs a NAMED positive refutation; a
  generic FALSIFIED raises. trace_hash is order-invariant + replay-stable.
- INV-31 (test_architectural_invariants.py): A1 determine.py clean+visible; A2 exact
  construction allowlist (evals-inclusive, tests-excluded) + non-vacuity anchor; A3
  transitive spine -/-> frame_verdict (reuses INV-27 walker) + resolve anchor; B1
  determine() refuses a ClosedFrame. B2 deferred to the governance slice (no
  non-vacuous surface yet; ADR §8 permits).

No serving wire. determine.py unchanged except the _basis import. No Determined(answer=False).
INV-30 green. Shapes follow the B4 operator master brief (PositiveRefutationKind enum,
closure_declared/source/provenance) — a consistent refinement of ADR-0222 §3.

Verified: 97 (full INV incl. INV-31 + FrameVerdict + OWA floor) + 20 frame_verdict + 35 determine.

* feat(frame-verdict): text closed-world (CWA) evaluation lane (B4 PR-2)

A measure-only lane over the text FrameVerdict evaluator (ADR-0222). Proves the sealed
type evaluates propositional closed frames safely — incl. a sound entailed_false —
without touching determine() or runtime serving.

- evals/frame_verdict_text_cwa/: cases.jsonl (12 cases across all 5 verdict kinds +
  gating + absence safety), oracle.py (an INDEPENDENT truth-table propositional checker —
  own recursive-descent parser + brute-force enumeration, disjoint from the ROBDD; imports
  no engine module), score.py, README.
- tests/test_frame_verdict_text_cwa_lane.py: wrong=0; the disjoint oracle confirms every
  gold (non-vacuity) AND the engine matches the oracle on every case; entailed_false is
  proof-backed (ROBDD_REFUTATION); absence / OPEN / undeclared-closure are never
  entailed_false; SHA-pinned fixtures.

Input contract: propositional-formula strings (no prose lowering). Capability-index
deliberately NOT touched (off-serving lane; documented). No Determined, no answer=False,
no serving wire. INV-30 / INV-31 / ProofWriter-OWA stay green.

* feat(frame-verdict): perception changed-slot falsification adapter (B4 PR-3)

Lift an ADR-0211 FalsificationRun into a FrameVerdict SAFELY (ADR-0222 §5.2). Critical
doctrine: "FALSIFIED" is not enough — only a POSITIVELY observed changed-slot contradiction
produces entailed_false (PERCEPTION_CHANGED_SLOT). Missing observation (absence), unexpected
extra (over-observation), and a whole-missing actual frame NEVER become false — they refuse
(undetermined / scope_boundary). SUPPORTED -> entailed_true (frame-conformance proven).

- generate/frame_verdict/perception_adapter.py: frame_verdict_from_perception_falsification;
  the proof carries the FULL run trace_hash (binds expected+actual+verdict, ADR §5.4).
- generate/frame_verdict/_construct.py: extracted the single FrameVerdict builder; the text
  evaluator + perception adapter both funnel through it, so the literal FrameVerdict(...)
  lives in ONE file -> INV-31 ALLOWED_FRAME_VERDICT_SITES = {_construct.py}.
- tests: changed-slot -> entailed_false (+ empty-hash raises); missing/unexpected/whole-missing
  never false; supported -> entailed_true; non-perception -> scope_boundary.

Off-serving; perception_adapter is in generate.frame_verdict, so INV-31 A3 already proves it
unreachable from the open-world spine. No determine() change, no answer=False. INV-30 / INV-31
/ ProofWriter-OWA / text-CWA lane stay green.

* feat(response-governance): default-dark FrameVerdict surface mapping (B4 PR-4)

The only lawful surface path for a closed-world verdict (ADR-0222 §7/§14). Lowers a
FrameVerdict to a served disposition through the EXISTING epistemic_disclosure tables (no
parallel object): entailed_true/false -> COMMIT at INFERRED + DisclosureClaim.NONE (a
committed "Yes"/"No"; entailed_false is an answer, NOT a contradiction/refusal/LimitationKind);
contradiction -> REPORT; undetermined -> REFUSE; scope_boundary -> EXPLAIN.

DEFAULT-DARK: a NEW module that changes no existing file — the open-world govern_response /
shape_surface STRICT path is byte-identical, and nothing in the live runtime calls it. The
TYPE is the closed-world tag: a forged dict / untagged object cannot widen serving (raises).

INV-31: A3 still green — core/response_governance/frame_verdict.py is NOT imported by the
package __init__ or the open-world spine, so frame_verdict stays unreachable from
chat/runtime/session/vault. The B2 anchor is now live (the forged-object rejection); all six
INV-31 anchors firm. The open-world render_determination cannot render a FrameVerdict;
determine() still has no answer=False.

Verified: 84 (governance + full INV incl. INV-31 A3/B2) green.

* harden(frame-verdict): fold in adversarial-review findings (B4)

A 4-skeptic adversarial read of PR-1..4 against real source surfaced one
soundness gap (major) and four defensive gaps (minor). All folded in; the
4 B4 files + full INV suite stay green (128 passed).

S1 (major) — perception negation was not frame-gated. The text evaluator
refuses OPEN / undeclared-closure frames (-> SCOPE_BOUNDARY) but the
perception adapter only gated frame_kind + the missing-frame sentinel, so a
PERCEPTION + OPEN + changed-slot residual produced entailed_false with
world_assumption=OPEN — a verdict that self-contradicts the type invariant
(OPEN => negation illegal). Fixed two ways:
  * perception_adapter: gate OPEN / not-closure_declared -> SCOPE_BOUNDARY,
    mirroring the text evaluator (graceful upstream refusal);
  * types.__post_init__ §(0): STRUCTURAL backstop — ENTAILED_FALSE + OPEN
    raises, frame-general, so NO producer (text/perception/future) can emit
    an OPEN-world negation even if it forgets the gate.

S4-a — entailed_true was admissibility-asymmetric. Only entailed_false was
proof-gated at construction; a committed "Yes." leaned entirely on the
INV-31-A2 allowlist. Added a symmetric §(2) guard: entailed_true requires a
positive entailment/support proof (proof_chain.entail/ENTAILED or
sensorium.falsification/SUPPORTED), non-empty sha, and NO refutation kind —
a mutation test can now trip a forged positive.

S2 — A2 construction detector extended to flag FrameVerdict.<factory>(...)
classmethod constructions and module-qualified mod.FrameVerdict(...), so a
future alternate constructor cannot evade ALLOWED_FRAME_VERDICT_SITES. The
dataclasses.replace gap is RECORDED (a blanket replace() match would
false-positive tree-wide) for the serving-wiring PR, not faked.

S4-b/c — A3 firewall now bars core.response_governance.frame_verdict
DIRECTLY (not only via its re-import of generate.frame_verdict), plus a new
test proving the response_governance package __init__ stays default-dark
(does not re-export the adapter).

S3 — oracle grammar is a strict SUBSET of proof_chain.entail (it mis-parses
`false` as a free atom). Corrected the docstring and added a lane guard:
every DECIDED cases.jsonl formula must stay inside the subset, so a future
out-of-subset case fails loudly at SHA-add review instead of as a confusing
engine-vs-oracle red. SCOPE_BOUNDARY-gold garbage is exempt (both refuse).

S1-minor (construction-time-only admissibility) documented in __post_init__:
a future codec/deserialization path must re-construct via build_frame_verdict.

* docs(analysis): B4 FrameVerdict implementation lookback (PR-1..4 + hardening)

Mandatory multi-slice lookback (CLAUDE.md triggers 2+3). Audits all six B4
commits across the lookback template: documentation drift, test-coverage /
parity gaps, wrong=0 hazard surface, cross-slice consistency, honest LOC.

Categorized findings: solid (INV-30 untouched; entailed_false positive-only;
INV-31 firewall non-vacuous; default-dark; replay determinism) / hazards (the
5 adversarial-review findings, ALL fixed in the hardening commit) / recorded
gaps (dataclasses.replace A2 hole + construction-time-only admissibility,
both deferred with an explicit guard obligation on the serving-wiring PR) /
drift (type shapes are a consistent refinement of ADR-0222 §3; one non-gating
ADR note filed). Global red-line ledger: all green.
2026-06-16 06:23:03 -07:00
Shay
af4db1f79d
test: fast/slow/full lanes via central slow registry in conftest (#786)
A handful of soak / bench / replay / proof / eval-matrix tests dominate the
suite wall-clock (~50 tests ≈ the entire 73-min serial runtime; the other ~10k
are near-instant). Classify them so developers can run a fast lane locally.

Mechanism — central registry in conftest.py beside QUARANTINE (cost is empirical
test-infra metadata, not test semantics, so it lives in one auditable place, not
as decorators across ~24 files). Marker-only: it stamps `slow`, it NEVER skips,
so `-m slow` SELECTS the slow tests.

- SLOW_FILES (10): whole-file, where a module/session fixture carries the cost
  (marking one test would just shift the fixture cost to the next requester).
- SLOW_TESTS (26 nodeids): mixed files, so the file's fast predicate/unit tests
  stay in the fast lane.
- pytest_collection_modifyitems stamps `slow`; marker registered in pyproject.
  912 of 10,596 tests classified (801 = test_cognition_eval_register_matrix
  eval-matrix; called out in docs — honest accounting).

Lanes (Makefile + docs/testing-lanes.md):
  fast: pytest -m "not quarantine and not slow"
  slow: pytest -m "slow and not quarantine"
  full: pytest -m "not quarantine"        (unchanged — what CI runs)

CI is unaffected: smoke.yml and full-pytest.yml run `-m "not quarantine"`, which
still includes the slow tests. No CI behavior change; no test logic touched (the
change cannot alter pass/fail in the full lane — it only adds a marker).

Timings (10-core, numpy): full 73min serial / 25min -n auto; fast ~26min serial
/ 9.5min -n auto (7.7x combined). The 975s test_inner_loop_phase2 outlier was
probed (expected proof-scale work, not a bug; finding in docs). Deferred to
follow-up PRs: -n auto by default (needs xdist hermeticity — fresh-env-dict
subprocess + report.json/proposals writers race under parallel workers) and a
warm-runtime fixture for the remaining 1-15s ChatRuntime tail.
2026-06-15 17:30:50 -07:00
Shay
90ed891ae0
docs(handoff): B4 PR-1 brief — FrameVerdict type + isolated evaluator + INV-31 (gated) (#785)
Durable handoff brief for B4 PR-1, the first implementation step of the ADR-0222
closed-world boundary: the sealed FrameVerdict type + the isolated text-frame evaluator
+ the INV-31 two-part firewall, plus the behavior-preserving _basis lift.

Red line: PR-1 is NOT a closed-world serving feature — sealed type/evaluator + INV-31
firewall only. No runtime caller, no governance integration, no session/vault/chat wire,
no perception adapter, no CWA lane, no prose lowering.

Documentation/handoff ONLY — no implementation code in this PR. Source of truth: ADR-0222 (#780).
2026-06-15 17:10:06 -07:00
Shay
3f803211bd
docs(analysis): Step-3 relational-surface lookback (#775 + #781 + #783) (#784)
Cross-PR lookback review of the composed relational surface on merged main:
one-hop inverse/symmetric (#775) + transitive strict-order (#781) + overlaps_event
finite-verb reader (#783). Verdict: SOLID — 37 solid findings across predicate
tables / interactions / soundness / measurement / documentation; 0 hazards, 0 drift,
0 fix-before-next-phase.

Verified on merged main 0be18ebd: 128 relational/capability/OWA tests + 19 INV
firewalls (INV-30/29/21/25/27) + 99 smoke, all green. The three capabilities compose
with no cross-PR regression; every determination stays open-world True-only; the
B4/closed-world boundary (ADR-0222) is untouched and design-only (no runtime
FrameVerdict; INV-31 is a future obligation).

Cleanup (the only finding): remove an unused `pack` fixture parameter from
test_symmetric_table_matches_pack_ontology — cosmetic, the test re-loads the pack
internally and is non-vacuous.

Step-3 relational implementation complete.
2026-06-15 16:56:03 -07:00
Shay
0be18ebdb9
feat(reader): add overlaps_event finite-verb reader surface (B3) (#783)
Add a closed finite-verb relational surface to comprehend_relational, narrowly:
declarative <A> overlaps <B>, interrogative Does <A> overlap <B>?, predicate
overlaps_event ONLY (closed finite-verb table, default-off).

Kept SEPARATE from the copula-connective grammar (byte-unchanged): the other
connectives (before/after/during/inside/adjacent/…) still REQUIRE the copula, so
"Monday before Friday." stays a refusal — no connective bypass (adversarial hazard #2).

Fail-closed slot gate: each finite-verb argument slot must be EXACTLY ONE content
token (after article stripping). An enumerated adverb/negation blocklist is unbounded
and leaks — the adversarial audit found "Meeting never overlaps lunch." committed as a
POSITIVE overlaps_event(meeting_never, lunch), plus almost/sometimes/trailing-qualifier/
second-verb compound fabrications in BOTH slots and the query path. The single-token
gate closes the whole COMPOUND-fabrication class (any extra token refuses); the
_FINITE_VERB_MODIFIERS and _CONNECTIVE_TOKENS checks add precise reasons for common
bare-modifier / second-verb slots. Multi-word entities in the finite-verb surface are
deferred (they need a positive content lexicon, which OOV entities preclude). A lone
bare token remains an entity (the reader's universal OOV single-token contract — the
copula and general readers behave identically), not a compound fabrication.

Hazards pinned (adversarial audit hazard #1 + #2): adverb absorption, negation-as-
positive ('never'), interrogative double-verb, trailing qualifier, and the copula-bypass
firewall.

Measurement: migrated ref-009 "Sunrise overlaps dawn." (the documented coverage gap) to
a positive rel-018 — comprehension_relational_predicate 17->18, wrong_total 0, breadth
still 11 (coverage added, not a new domain), baseline digest re-frozen. Refusal floor
rose 9 -> 24 (finite-verb confusers replace the one migrated input). The #775 inference
and #781 transitive lane SHA pins are UNTOUCHED. determine.py is untouched (reader-only):
no answer=False, no FrameVerdict, INV-30 unaffected.

Verified in the worktree: finite-verb + reader + reader-lane + #775 inference lane +
#781 transitive lane + #779 OWA floor + capability baseline/index + INV-30/29/21 + full
smoke — all green. Two read-only adversarial passes: the first found 4 real slot-
fabrication blockers, closed by the single-token gate; a black-box probe of 24
adversarial inputs confirms the compound-fabrication class is fully closed.
2026-06-15 14:56:09 -07:00
Shay
01966ef92e
test: isolate default engine-state dir per test (env var + opt-out marker) (#782)
Completes the recommended fix in docs/issues/default-engine-state-test-hygiene.md.
The root conftest autouse fixture already redirected engine_state._DEFAULT_DIR
per test (the in-process half, req #1); this adds the two missing halves:

- monkeypatch.setenv("CORE_ENGINE_STATE_DIR", isolated) so subprocess / CLI
  tests that re-import engine_state in a child process inherit the same
  isolation (req #2). A child that sets its own env still overrides and wins.
- @pytest.mark.uses_default_engine_state opt-out (registered in pyproject.toml)
  for tests that intentionally exercise the real process-default dir (req #3).

Adds tests/test_conftest_engine_state_isolation.py: non-vacuous proof that each
half fires and that the marker opts out; each assertion fails if its half is
removed (CLAUDE.md "Schema-Defined Proof Obligations").

Validation: full serial baseline on origin/main (95a06a20) = 38 pre-existing
reds (21 failed + 17 error). The only new behavior over baseline is the setenv,
whose sole live effect is child processes; a full re-run of the subprocess/CLI
blast radius shows zero new failures, and the documented test_achat
identity-continuity warning is gone. Does NOT add -n auto adoption or the
fresh-env-dict subprocess hermeticity fix (tracked separately).
2026-06-15 14:46:20 -07:00
Shay
1ff06726a6
feat(determine): add transitive strict-order relational inference (#781)
* feat(determine): add transitive strict-order relational inference

Add sound transitive closure for declared strict-order relational predicates —
less_than, greater_than, before_event, after_event, and ONLY these. A query
p(a, c) determines True when a same-predicate chain p(a, b), p(b, c), ... over the
predicate's OWN realized edges is found (BFS reachability) and the proof_chain ROBDD
verifies the transitive entailment (search-then-verify, through the UNCHANGED
evaluate_entailment via a new lower_transitive_chain). Mirrors _determine_subsumption.

Scope / firewall:
- TRANSITIVE_PREDICATES is closed and default-off; sibling_of, parent_of,
  left_of/right_of, inside_of, during_event, overlaps_event are EXCLUDED.
- Same-predicate edges only — no transitive-through-inverse, no cross-predicate
  (triple firewall: recall filter + lowering re-reject + ROBDD re-verify).
- Open-world: asserts only answer=True via the shared _relational_determined
  surface (rule="transitive"), so INV-30 stays at exactly 3 Determined sites and
  no answer=False is added. One-hop inverse/symmetric (#775) is unchanged.
  FrameVerdict / closed-world (ADR-0222) is untouched.

wrong=0 bite (unit + lane + independent oracle): non-transitive-predicate chains,
non-admitted spatial chains, mixed-predicate chains, disjoint chains, reflexive
cycles, and inverse+transitive composition all REFUSE.

Measurement: new evals/relational_transitive lane with an INDEPENDENT BFS
transitive-closure oracle (imports no engine module; INV-25/27), cross-checked both
directions (positives oracle-True, confusers oracle-False). Capability-index breadth
10 -> 11, wrong_total 0, deterministic digest re-frozen.

Cross-PR reconcile: migrated the one-hop confuser rinf-ref-001 (a less_than chain,
which now correctly determines transitively) to a B2 positive; repurposed its unit
test to a non-transitive parent_of chain; corrected a pre-existing stale breadth==9
assertion (#775 added a 10th domain but never updated test_capability_index).

Verified in the worktree: determine + transitive/one-hop lanes + capability
baseline/index + ProofWriter-OWA floor + INV-30/29/21/02 + full smoke — all green.
A 3-skeptic read-only adversarial review found no wrong=0 leak.

* test(determine): B2 hardening — TRANSITIVE_PREDICATES pin + lower_transitive defensive + budget

Add the required B2 hardening tests before merge:

- test_transitive_predicates_closed_and_excludes: the table is EXACTLY
  {less_than, greater_than, before_event, after_event}, every member is in
  RELATIONAL_PREDICATES, and every deliberately-excluded predicate (sibling_of,
  spouse_of, parent_of, child_of, left_of, right_of, inside_of, during_event,
  overlaps_event) is explicitly absent. (Closes the gap where relational.py's
  comment claimed this test existed but it did not.)

- tests/test_composition_lower_transitive.py: direct defensive tests on
  lower_transitive_chain — empty path, mislabeled (cross-predicate) edge, wrong
  arity, non-contiguous path, and path-not-reaching-target all lower to None
  (refuse); plus exact 2-hop and 3-hop theory pins.

- test_edge_budget_exhaustion_refuses: above _TRANSITIVE_EDGE_BUDGET the transitive
  search declines (a safe coverage refusal), never proves.
2026-06-15 14:20:30 -07:00
Shay
ea7b2d5cbd
docs(adr): ratify ADR-0222 FrameVerdict closed-world design
Ratifies ADR-0222 as the design-only B4 artifact for FrameVerdict: a frame-general closed-world verdict type distinct from open-world Determined.

No runtime type, entry point, lane, or answer=False path is added in this PR. Implementation remains gated behind PR-1 with INV-31, the landed OWA refusal floor, and the staged plan in the ADR.

Final ratification patches included:
- positive_refutation_kind discriminator for entailed_false admissibility;
- PR-1-scoped Determined count wording;
- grounded-negative governance resolution as COMMIT-with-negative-surface at EpistemicState.INFERRED + DisclosureClaim.NONE.
2026-06-15 13:09:35 -07:00
Shay
a6403edcd9
feat(eval): ProofWriter-OWA refusal-floor lane (B1) — independent oracle, measure-only (#779)
* feat(eval): ProofWriter-OWA refusal-floor lane (B1) — independent oracle, measure-only

Mastery-v2 Step-3 Brief 1. Proves the open-world soundness floor: determine() never
asserts a query True when the open-world truth is Unknown or False. Hardens
"unknown != false" before the transitive-chain (B2) and closed-world (B4) work can
stress it.

- evals/proofwriter_owa/oracle.py: a SEPARATE minimal OWA label oracle (its own parser
  + reasoner + inverse/symmetric tables), importing NONE of determine, comprehend/
  MeaningGraph realization, or production predicate-entailment helpers (INV-25/27 — the
  gold producer is disjoint from the solver). Grammar: member/subset/is-a closure,
  explicit negation (No X is a Y -> disjointness, the source of gold-False), and #775
  inverse/symmetric ONE-HOP relational rules. No transitive relational chains.
- evals/proofwriter_owa/fixtures.jsonl: 19 hand-authored ProofWriter-OWA-style items
  (9 True / 7 Unknown / 3 False), SHA-pinned. Gold computed by the oracle; the fixture's
  hand-authored `expected` pins the oracle (so the oracle is itself verified).
- evals/proofwriter_owa/score.py: runs the production path (comprehend/comprehend_relational
  -> realize -> determine) vs the oracle gold; wrong = determine asserted True on a non-True
  gold.
- tests/test_proofwriter_owa_lane.py: SHA pin; oracle==expected; wrong==0; every
  serving_support gold-True determines True (no coverage gaps); no answer=False (INV-30).

Result: 19 items -> 9 correct (all serving_support True), 10 refused (Unknown/False),
0 wrong. Measure-only: no engine code touched; deliberately NOT a capability-index domain
(a refusal floor has low coverage and would drag coverage_geomean). INV-30 green; smoke 99/99.

Note: no live ProofWriter dataset access in this environment, so items are hand-authored
in OWA style (provenance.md cites ProofWriter V2020.12.3 / arXiv:2012.13048 for semantics,
attribution only) — but the GOLD is oracle-computed and verified, not hand-asserted.

* harden(eval): OWA lane CLI fails on coverage_gaps too (review #779)

score.py::main() exited nonzero only on wrong>0; it now ALSO exits nonzero when
coverage_gaps is non-empty (serving-supported gold-True items that refused), matching
the acceptance rule the tests already enforce and the module docstring. Both failure
modes print to stderr; exit is 1 if either fires. Verified non-vacuously (injected gap
-> exit 1). Lane test 5/5 green.
2026-06-15 12:28:20 -07:00
Shay
95a06a20ef
docs(issues): default engine_state/ test-hygiene hazard + interim rule (#778)
Documents the systemic non-hermetic-test hazard surfaced during ADR-0220: ~340 of
469 ChatRuntime constructions across 123 test files default to the shared
engine_state/ dir, so tests read/pollute each other's checkpoints (the spurious
test_achat identity-continuity warning). Records the interim rule (new/edited
tests must pass engine_state_path=tmp_path or no_load_state) and the recommended
future fix (root conftest autouse fixture monkeypatching engine_state._DEFAULT_DIR
+ CORE_ENGINE_STATE_DIR), deferred to its own full-suite-validated PR. Docs only.
2026-06-15 12:15:42 -07:00
Shay
eed20749db
docs(handoff): Step-3 relational-reasoning brief pack (OWA → transitive → finite-verb → FrameVerdict) (#777)
* docs(handoff): Step-3 relational-reasoning brief pack

Four parallel-safe briefs continuing the comprehend->determine arc after #775
(one-hop relational inference, breadth 10). Ordered OWA-first per the merge-safety
sequencing — harden unknown!=false before transitive/closed-world stress it:

1. ProofWriter-OWA refusal-floor lane (measure-only; standalone wrong=0 gate, NOT a
   capability-index domain) — dispatch first.
2. Transitive relational chains (strict-order predicates only; reuses the proof_chain
   ROBDD search-then-verify; non-transitive predicates must refuse).
3. overlaps_event finite-verb reader surface (closes #775's known coverage gap).
4. FrameVerdict / two-sided closed-world — DESIGN ONLY, gated behind brief 1 landing
   and design ratification; no answer=False path, INV-30 preserved.

Each brief carries the shared acceptance gates (independent oracle, pos+neg fixtures,
capability-index movement, replay-stable, wrong=0, INV-30 green, no template spam) +
dispatch lines. B1/B2/B3 are file-disjoint and parallel-safe; B4 is gated.

* docs(handoff): tighten Step-3 brief dispatch semantics

* docs(handoff): address gemini-code-assist review on the Step-3 brief pack

- B1 (OWA): gold extended to {True, Unknown, False}; include gold-False items
  (negation entailed). determine() is True-or-refuse, so asserting True on ANY
  non-True gold (Unknown OR False) is the wrong=0 breach — strengthens the floor.
- B2 (transitive): inside_of + during_event are ALSO transitive (containment);
  now explicitly DEFERRED from the first cut (admit only with their own confuser
  fixtures), not silently omitted.
- B3 (finite-verb): added the finite-verb QUERY surface as scope — _read_relational_clause
  requires questions to start with 'is', so an overlaps_event query is unparseable
  today; pin one interrogative form (Does A overlap B?) and fail-closed, else the
  symmetric case refuses and yields no coverage.
2026-06-15 12:08:06 -07:00
Shay
ac5fb35d45
fix(identity): harden ADR-0220 reconciliation inputs (follow-up to #774) (#776)
The two Gemini robustness nits raced the #774 merge: the patch landed on the
branch after GitHub had merged the pre-patch head, so main shipped the
architecture without the migration-input hardening. This re-lands ONLY the
robustness fixes — no identity-semantics change.

- chat/runtime.py: parse identity_scheme with try/except (TypeError, ValueError)
  -> fallback to legacy scheme 1; revision str(... or '') so a null becomes ''
  (unverifiable -> conservative DIVERGED), not the literal 'None'.
- workbench/readers.py: stored_revision=written_at_revision or '' so a None
  revision is handled identically by the shared reconcile helper.
- tests: malformed identity_scheme does not crash the load guard (migrates);
  reader falls back to legacy on malformed scheme; reader missing revision is a
  conservative break.

Verified: 50 identity/migration/reader tests pass. No lane/serving path touched.
2026-06-15 12:01:29 -07:00
Shay
6f70e834ff
feat: one-hop sound relational entailment (inverse/symmetric) + capability-index lane (#775)
* feat(determine): one-hop sound relational entailment — inverse/converse + symmetric

Mastery-v2 Step 3 lead capability (core). DETERMINE could perceive relational
structure (16 predicates, breadth-complete) but could not derive the simplest
entailed relational facts — it read only the stored direction. This adds two
SOUND one-hop rules that read a stored edge in its other lawful direction:

  INVERSE/converse   greater_than(a,b)  <=  told less_than(b,a)
  SYMMETRIC          sibling_of(b,a)    <=  told sibling_of(a,b)

Strictly scoped (per review): OPEN-WORLD (asserts only True, never False — the
answer=False path stays unbuilt, INV-30's firewall holds), ONE hop (NO transitive
chaining), DECLARED rules only. Ontology/metadata-driven, not prose intuition:

- generate/meaning_graph/relational.py: declarative algebra tables — _INVERSE_PAIRS
  (the converse edges; the pack carries no inverse metadata) + INVERSE_OF (derived,
  an involution) + SYMMETRIC_PREDICATES (mirrors the pack's graph.edge.symmetric tag).
  load_relational_pack_symmetric() reads the pack ontology; a test pins the constant
  equal to it (no silent divergence).
- generate/determine/determine.py: _relational_one_hop() between direct entailment
  and transitive subsumption; new Determined.rule provenance field (direct/inverse/
  symmetric/subsumption) — replay-safe (render reads only basis; trace hashes surface).

wrong=0 confuser block proves the rule cannot over-fire: less_than is not
self-inverse, sibling_of does not imply parent_of, greater_than does not imply
equal_to, NO transitive chain (direct or through-inverse), and no answer=False is
ever emitted. An obsolete pin (test_symmetric_converse_is_not_faked, which asserted
the pre-rule direct-only floor) is updated to the new sound behavior and a preserved
asymmetric-converse bite.

Tests: test_determine_relational_inference.py (13, the capability contract) +
updated test_relational_reader.py. Broad relational/determine/grounding +
capability_index baseline sweep: 114 green; baseline digest UNCHANGED (this is a
determination capability; the comprehension lanes are untouched).

REMAINING for the full lead PR (next): the measurement lane — an independent-oracle
evals/relational_inference lane + a capability_index adapter + baseline re-freeze
(breadth 9->10, wrong_total still 0), so the capability registers on the yardstick.

INTEGRATION NOTE (cross-PR): this adds 2 Determined() construction sites (now 4:
direct/inverse/symmetric/subsumption, ALL answer=True). When rebased onto main with
INV-30 (PR #770), update INV-30's test_determine_construction_sites_are_visible count
2 -> 4 and confirm all four assert True. Merge order: #770 -> this.

* feat(capability-index): relational-inference lane — breadth 9->10, wrong=0

Mastery-v2 Step 3 lead, measurement half. Puts the one-hop relational-inference
capability ON the capability index with an independent-oracle lane:

- evals/relational_inference/v1/: cases.jsonl (13 positive: 8 inverse + 5 symmetric,
  authored from the relation algebra INDEPENDENTLY of determine/relational per
  INV-25/27), refusals.jsonl (8 confusers that MUST refuse), provenance.md (incl. the
  honest overlaps_event reader-surface coverage gap).
- evals/comprehension/relational_inference_runner.py: told fact(s) -> determine(query)
  vs gold; refusal = coverage miss, disagreement = wrong (structurally 0 on positives).
- evals/capability_index/adapters.py: comprehension_relational_inference_result added
  to ADAPTERS.
- baseline.json re-frozen: breadth 9 -> 10, capability_score 0.94403 -> 0.949483,
  wrong_total still 0, digest deliberately changed (35dea2b2...).
- tests/test_relational_inference_lane.py: SHA-pinned gold, positive wrong=0 +
  coverage>0, and the confuser wrong=0 BITE (every confuser must refuse).

Also reconciles INV-30 (now in main via #770): determine.py grew from 2 to 3
Determined() construction sites (direct, the shared relational one-hop constructor,
transitive subsumption) — ALL answer=True. test_determine_construction_sites_are_visible
updated 2 -> 3 (correcting the prior commit note's overcount of 4; inverse and symmetric
share the single _relational_determined constructor, so it is ONE new site).

Also fixes a .gitignore gap: ADR-0219 gen-dir checkpoints (engine_state/current,
engine_state/gen-*/) were not ignored (only the old flat engine_state/*.json patterns
were), so runtime checkpoints could be committed by accident.

Verification: new lane 13/0/0 coverage 1.0; capability_baseline digest matches the
re-freeze; INV-30 + architectural invariants green; smoke 99/99.

* docs+test: reconcile stale relational-inference docstrings; assert rule provenance (review #775)

Addresses two review-requested cleanups on #775:

1. Stale docstrings. generate/meaning_graph/relational.py said the symmetric converse
   is "a sound-but-incomplete refusal at DETERMINE" and there is "NO transitive/
   symmetric/rule inference"; generate/determine/determine.py said "symmetric-converse
   questions are Undetermined", "no transitive/symmetric/rule inference", and "asserts
   only answer=True on a direct hit". All false after the one-hop relational algebra
   landed. Updated: the reader does direct-reading only; DETERMINE applies declared
   one-hop inverse/converse + pack-declared symmetric (plus the existing member/subset
   subsumption), still open-world / never answer=False; transitive relational closure,
   negation, and closed-world falsehood remain out of scope.

2. Rule-provenance gold now meaningful. The relational_inference runner compared only
   (answer, predicate, subject, object), ignoring res.rule though the gold carries
   "rule": "inverse"/"symmetric". Added res.rule to the got/gold tuple so a "right
   answer via the wrong rule path" can no longer pass silently. Lane still 13/0/0;
   baseline counts (and digest) unchanged.

Verification: runner 13/0/0; lane + capability_baseline + unit + INV-30 green (22).
2026-06-15 11:39:41 -07:00
Shay
512453b6fc
feat(identity): split engine identity from build provenance (ADR-0220 PR C) (#774)
Removes code_revision from the engine-identity hash: engine_identity is now the
sha256 of the 5 ratified packs ONLY. The build revision is provenance (the
manifest's written_at_revision), not identity — so a behavior-neutral rebuild is
the SAME identity and the always-on daemon no longer flag-day strict-breaks on
every commit (the ADR-0220 defect).

Core:
- core/engine_identity.py: ratified_substrate/compute_engine_identity drop the
  git_revision arg (packs-only); add compute_legacy_engine_identity (reproduces
  the pre-split packs+rev hash, for migration verification), ENGINE_IDENTITY_SCHEME=2,
  IdentityReconciliation enum, and reconcile_loaded_identity — the single source of
  truth for the runtime guard AND the workbench reader.
- chat/runtime.py: the load guard reconciles via scheme. Current scheme -> direct
  packs-only compare. Legacy (code_revision-folded) stamp -> a VERIFYING migration:
  reconstruct the legacy hash from the persisted written_at_revision; a match proves
  packs unchanged (warn + re-stamp, resume, no break) — a mismatch means the packs
  genuinely changed (DIVERGED -> strict-refuse). Preserves 'distinct packs => refuse'.
- engine_state/save_manifest: stamp identity_scheme alongside engine_identity
  (additive-optional; no schema_version bump).
- workbench/readers.py: continuity reader uses the same reconcile (no phantom break
  on legacy checkpoints). cli.py break message reworded (packs, not 'build revision').

Callsite cleanup: drop the now-unused git_revision arg + imports across
always_on.py, evals/l10_always_on/runner.py, workbench/readers.py.

Tests:
- test_identity_provenance_split.py (new): 5 reconcile unit proofs + 3 runtime
  integration proofs incl. the wrong=identity defense (legacy stamp of DIFFERENT
  packs still strict-refuses) and the re-stamp migration.
- test_engine_identity.py: invert the code-revision test (rev no longer changes
  identity); assert the substrate is packs-only.
- Restore 3 lineage tests silently red since ADR-0219 (flat-path _manifest helper
  now resolves the gen-dir).
- Update remaining callsites/monkeypatches for the new signature.

Hygiene: .gitignore now covers the ADR-0219 gen-dir runtime files
(current, gen-*/, session_state.json, proposals.jsonl).

Verified: 59 identity/migration/lineage/workbench + 32 L10 + 76 invariants/cli +
34 smoke pass; serving lane SHAs unchanged (no derivation/reliability_gate touch).
2026-06-15 11:38:04 -07:00
Shay
8ad5fecf3a
docs(adr): ADR-0221 — required-checks-only branch protection (solo-maintainer repo) (#773)
Records why main is protected with CI status checks only — no required approvals,
no required code-owner review. Public exclusion comes from repo WRITE ACCESS, not
a review rule; a self-approval requirement on a single-identity repo only creates
an unsatisfiable deadlock that forces --admin on every merge (cf #772).

- ADR-0221: context (the deadlock), the misconception corrected, the decision,
  an explicit 'do not re-add human-review gates' guard for future agents, the
  applied gh-api change, and the break-glass log.
- CODEOWNERS: comment corrected to ADVISORY-ONLY (require_code_owner_reviews is
  now false); keeps * @AssetOverflow for ownership/auto-request, not gating.

Applied 2026-06-15: required_approving_review_count 1->0, require_code_owner_reviews
true->false; required status checks unchanged. This PR is the first through the
fixed normal path — its clean, approval-free, no-admin merge is the proof.
2026-06-15 10:27:10 -07:00
Shay
366ea2a125
docs(adr-0170): reconcile status to shipped reality — W1/W2 serve; fix sealed-lane comment (#771)
Mastery-v2 Step 2 (DCS/W2 reconciliation audit). The reviewer flagged that
ADR-0170 might still read as pending while the code shipped. Audit (read source
+ provenance + measured the metric) confirms:

- DCS-S1 acquisition (W2) is INTENTIONALLY SERVING, not a boundary escape:
  PR #377 (b190f3b6) landed it in serving _INJECTORS BEFORE the sealed lane
  (ADR-0186 = PR #487) existed. The sealed lane (_SEALED_INJECTORS = {}) is
  correctly empty, reserved for future W3-W5.
- wrong=0 held: train_sample committed 4/0/46, confirmed live on current code
  (test_adr_0126_train_sample_runner 4/4 green); 6 committed cases exercise the
  acquisition path (collected x4, collects, receives).
- Placement already mechanically pinned by the existing test pair —
  test_adr_0170_w2 (W2 emits via serving _INJECTORS) + test_adr_0186 (sealed
  lane is an empty no-op) — so NO new test is added (would be redundant).

Two stale docs, now reconciled (documentation-only; no code/logic change):
1. ADR-0170 status: "Proposed / no runtime change" -> "Accepted; W1+W2 shipped";
   implementation-outline annotated with shipped/deferred status per W-stage.
2. recognizer_anchor_inject.py sealed-lane comment: "resume ADR-0170 W2-W5" ->
   "W3-W5", noting W2 ships from serving _INJECTORS (PR #377), never the lane.

W1+W2/sealed-lane/train_sample lanes: 36/36 green.
2026-06-15 10:22:58 -07:00
Shay
e8ad11f9e6
test(invariants): INV-30 open-world DETERMINE never asserts False; document typed learning boundary (#770)
Adds the one missing mechanical firewall in the learning-boundary set: the
open-world determination gear (generate/determine/determine.py) may construct
only Determined(answer=True) or refuse (Undetermined) — it can never assert
answer=False. Absence never refutes (open-world); a False from absence would be
an unsound, wrong=0-class assertion. The planned DEEPEN/Step-2b closed-world
entailed-negation capability (ProofWriter-CWA / FOLIO two-sided labels) must use
a distinct closed-world result type and entry point, so this invariant keeps it
lane-scoped by construction rather than by reviewer vigilance.

INV-30 follows the existing suite's Schema-Defined-Proof-Obligation discipline:
- test_no_determined_asserts_false  — the firewall (project-wide AST scan)
- test_detector_is_non_vacuous       — proves it FAILS on injected answer=False
- test_detector_ignores_true_and_reads — proves no false positives
- test_determine_construction_sites_are_visible — proves the scan is not blind

Doctrine: CLAUDE.md Teaching Safety + docs/runtime_contracts.md now state the
corrected typed-learning boundary (durable=reviewed/proof-carrying;
provisional=autonomous iff typed/isolated/replayable/non-masquerading) and cite
the enforcing invariants INV-21/22/23/24/29/30, including the honest wrinkle that
ADR-0148 promote_eligible_entries is a second, opt-in, default-off COHERENT path.
No source code changed; firewall is additive.

Full architectural invariant suite: 65/65 green.
2026-06-15 10:22:47 -07:00
Shay
b3a1366980
feat(cli): always-on --engine-state PATH + safe identity-break recovery (ADR-0220 PR B) (#772)
* feat(cli): always-on --engine-state PATH + safe identity-break recovery (ADR-0220 PR B)

Operator ergonomics only — no identity-hashing, strict_identity_continuity, or
manifest-schema change (those are gated on ADR-0220 ratification, PR C).

- core always-on --engine-state PATH: surface the existing per-life state-root
  concept. run_daemon already accepts engine_state_path; cmd_always_on now
  threads the flag through (default None -> $CORE_ENGINE_STATE_DIR / in-repo dir).
- _always_on_identity_break_message: replace the terse IdentityContinuityError
  text with revision-aware recovery guidance (reads written_at_revision so
  'git checkout <rev>' is copy-pasteable; offers --engine-state and an in-place
  clear-runtime-files path). Safe by construction: never suggests mv/rm of the
  engine_state dir, which under the default IS the tracked Python package.
- Suppress ADR-0157's revision-mismatch warning while the formatter reads the
  manifest (message builder, not a load path).
- Tests: flag default/parse, threading to run_daemon, message is safe +
  revision-aware + placeholder-on-no-manifest.

* fix(cli): fall back to <engine_state_dir> placeholder when state dir unresolvable

Gemini review nit on #772: if EngineStateStore construction fails AND no
--engine-state was given, state_dir stayed None and the recovery message printed
a bare 'None' as the dir to clear. Fall back to '<engine_state_dir>'. Adds a test
that forces the store-resolution exception path, and guards the no-manifest test
against 'None' leaking into the message.
2026-06-15 08:19:39 -07:00
Shay
44194e3ef3 docs(adr): ADR-0220 — engine identity vs build provenance (code_revision in identity hash)
Documents the contradiction between ADR-0157 (revision mismatch = non-fatal
warning) and engine_identity.py:99 (same revision folded into the identity hash
= hard raise under strict continuity). Proposes the identity_substrate_hash vs
build_provenance_hash split (O3), staged A/B/C, with corrected operator-recovery
guidance (never mv/rm the default engine_state dir — it is the tracked package).
No code change; proposed, awaiting ratification.
2026-06-15 07:02:26 -07:00
Shay
f5c6914d00 test(l10): W2-R arbitrary-interruption recovery harness (ADR-0219)
Adds empirical coverage of the three ADR-0219 checkpoint sub-steps where
a process kill can occur: PARTIAL_GEN (gen dir exists but partially written),
FULL_GEN_BEFORE_SWAP (all four files written but current pointer not yet swapped),
and AFTER_SWAP (clean-commit control case, already covered by rec_a/rec_b).

For each of the two non-trivial cut-points the harness:
1. runs a probe soak to commit reboot_turn turns,
2. injects the corresponding orphan shape,
3. reads the on-disk manifest to confirm the loader follows `current` and reads
   the committed generation (not the orphan),
4. runs two independent recovery soaks (each injecting the same orphan at the
   reboot boundary) and verifies their post-reboot trace_hash tails converge,
5. checks versor_condition < 1e-6 throughout every recovery soak.

New predicate evaluate_p4_arbitrary_interruption (predicates.py) expresses
this as a PredicateOutcome with a CutPointEvidence carrier. It has:
- holds gate: recovered_turn_count == expected (orphan was ignored)
- holds gate: tail_hashes_a == tail_hashes_b (two recoveries converge)
- holds gate: all versor_conditions < ceiling (closure throughout)
- bites gate: tested by four mutation tests (wrong count, diverging tails,
  vc violation, empty tails)

contract.md: all spec predicates P1-P5c are now covered (NOT_COVERED is empty).

Per CLAUDE.md schema-as-proof: every predicate gate is verified by a *_bites
mutation test — 9 tests total (3 injection unit, 2 real-soak holds, 4 bites)
all pass in 109s.
2026-06-15 02:45:29 -07:00
Shay
ffcc61920a
docs(workbench): mark Vault P0/P1/P2 complete; document recall endpoint (#768)
Reconcile the living docs with the shipped Vault evidence surface:

- README "Shipped surfaces": record the Vault P0 (honest empty/unavailable
  framing, #760), P1 (inspector depth + status/facet/text filters +
  evidence-rail progression, #762/#763/#764), and P2 (read-only exact-CGA
  recall, #766) arc as complete; enrich the Vault entry row. Read-only
  throughout — no runtime controls.
- wave-R: extend the Vault surface bullet with the recall endpoint and the
  P0/P1/P2 completion note + the +inf-self-match-sentinel honesty point.
- api-contract-v1: add GET /vault/entries/{index}/recall (purpose, errors,
  trust boundary, cross-link to the data shape).
- data-shapes-v1: add VaultRecallHit / VaultRecall types.
- UI-UX-GUIDE: enrich the Vault row (inspector depth, filters, rail, opt-in
  exact-CGA recall; read-only).

Docs-only; no code or contract behavior change.
2026-06-15 02:44:45 -07:00
Shay
2b32bd2f8f feat(l10): idle backpressure telemetry — observational flywheel pressure gauge
Adds W1-T: an observational telemetry leaf for idle_tick's learning flywheel,
modeled on core.cognition.leeway (the B4 precedent).  Never gates, refuses,
or alters trace_hash.

New core/cognition/backpressure.py:
  BackpressureRecord(pending_proposals, candidate_backlog, cap, headroom,
    contemplated_this_tick, created_this_tick, at_fixed_point, did_work)
  build_backpressure_record(...) — pure function, resolves ADR-0161 cap from
    env (CORE_HITL_PENDING_CAP or 256), derives headroom = max(0, cap-pending),
    at_fixed_point = (candidate_backlog==0 and created_this_tick==0).

chat/runtime.py:
  IdleTickResult gains backpressure: BackpressureRecord | None = None (always
    set on a completed tick).
  idle_tick builds the record from counts already computed (pending_proposals,
    candidate_backlog, contemplated_count, created, did_work) — no new
    computation, no serving-path change.
  Appends one JSONL line per tick to engine_state/idle_telemetry.jsonl
    (best-effort, never crashes the continuous life; accumulates across all
    generations so history survives reboot independently of the checkpoint).

tests/test_idle_backpressure_telemetry.py (16 tests):
  - field derivation: headroom, cap, at_fixed_point (holds + bites)
  - cap resolution: default 256, env override, invalid env fallback
  - firewall proof: trace_hash of a served turn is byte-identical whether
    idle_tick ran before it or not (the load-bearing invariant)
  - integration: record fields on a fresh runtime; telemetry written to
    engine_state dir; history accumulates across ticks

All 16 tests pass.  ADR-0161 cap and queue_full control logic untouched.
2026-06-15 02:36:44 -07:00
Shay
71eed1b73d
workbench(vault): exact-CGA recall evidence for persisted entries (#766)
Close the deferred Vault item 4: a read-only endpoint proving a selected
vault entry is recallable by CORE's actual exact CGA machinery, surfaced
in the entry inspector.

Backend (read-only, cold-persisted only):
- GET /vault/entries/{index}/recall rehydrates the persisted VaultStore
  (VaultStore.from_dict — bit-exact versors, no reprojection) and runs the
  real VaultStore.recall using the entry's own stored versor as the query.
  Exact cga_inner scan — never ANN / cosine / approximate.
- recall's +inf exact-self-match sentinel never crosses the boundary: the
  genuine finite cga_inner is reported plus an exact_self_match flag. The
  raw versor never leaves the engine — only content-addressed digests.
- Trust boundary: caller-controlled index -> 404 (out of range / non-int);
  absent persisted snapshot -> 501. The file is never written; the live
  runtime is never touched; recall is deterministic over persisted bytes.

Frontend:
- "Exact CGA Recall" inspector panel, collapsed by default so the read is
  opt-in (the query hook only mounts on expand). Copy says "exact CGA
  recall" / cga_inner — never similarity / relevance / score / ANN /
  cosine. Honestly surfaces that a byte-identical self-match is promoted
  ahead of metric ranking (CGA null-vector self inner-product ~0).

INV-24: register workbench/readers.py in VAULT_RECALL_SITES as
EVIDENCE_TELEMETRY (operator inspection evidence, not claim-shaping), and
tighten the recall-site detector to recognise VaultStore.from_dict factory
bindings so the obligation is real rather than silently bypassed.

Tests: backend 501/404/self-recall/determinism/no-mutation/JSON-safety +
API status codes; frontend collapsed-doctrine + expanded self-recall
evidence. Full workbench suite (192) + architectural invariants (61) +
vault-touching vitest (70) all green.
2026-06-15 02:28:37 -07:00
Shay
ff1581f85f test(l10): P5a recall-precision predicate — cross-reboot vault exact-match gate
Adds the P5a recall-precision predicate that was listed as NOT_COVERED in
the L10 continuity lane.  Closes the gap in the schema-as-proof discipline
(CLAUDE.md): every predicate must have both a *_holds test (real soak) and
a *_bites mutation test so a passing lane cannot silently miss the violation
it nominally catches.

Changes:
- runner.py: ProbeRecord dataclass + probe_at/verify_probes_at params on
  run_soak().  Registers a field state (float32 bytes, matching vault's
  _exact_index dtype) at a named turn and recalls it against the vault at a
  later turn — including after a reboot, which is the cross-reboot claim.
- predicates.py: evaluate_p5a_recall_precision — fails if any ProbeRecord
  has rank=None or rank>top_k, or if no cross-reboot probe was recorded.
- report.py: wires P5a into build_report(); probe registered at turn 1,
  verified at reboot_turn+2 (intentionally before the vault's
  null_project auto-reproject cycle at store_count=20, which would destroy
  all CGA inner-product scores — documented as a real finding, deferred to
  a follow-up increment); NOT_COVERED is now empty ().
- contract.md: P5a row in the predicate table + reprojection-boundary
  scope note.
- test_l10_continuity.py: 4 tests — holds (real 6-turn soak across reboot)
  + 3 bites (rank=None, no cross-reboot probe, empty probe_records).

Key finding: vault.null_project() fires every vault_reproject_interval=20
stores and produces CGA-orthogonal versors (inner product → 0.0 with the
original), completely destroying both exact-match and ranked recall.  This
is a long-horizon vault stability issue, recorded here rather than silently
avoided.  The P5a probe window is constrained to the pre-reproject interval
to keep wrong=0 intact while documenting the gap.
2026-06-15 02:16:00 -07:00
Shay
103def317d feat(engine-state): generation-dir atomic checkpoint (ADR-0219)
Closes the cross-file checkpoint-atomicity gap in ADR-0156.  The four
checkpoint files now live in a committed gen-NNNN/ directory; the single
atomic os.replace of a 'current' pointer file is the commit boundary.
A kill before the pointer swap leaves the prior committed generation
intact; a kill after commits the new generation.  Unreferenced gen dirs
are ignored.  ADR-0156's deferred parent-dir fsync is also closed.

Key changes:
- engine_state/__init__.py: begin_generation() + commit_generation() +
  _resolve_dir() for all load_* methods.  Flat-layout legacy checkpoints
  migrate into gen-0000 on first begin_generation call.  GC retains K=2
  committed generations.
- chat/runtime.py: checkpoint_engine_state uses the two-phase commit;
  finalize_turn_trace_hash no longer writes discovery_candidates outside
  the generation sequence (the second unguarded write path is closed).
- evals/l10_continuity/runner.py: _inject_orphan_tmp updated to inject
  the two orphan shapes of the generation model.
- tests/test_adr_0219_generation_checkpoint.py: 18 tests, one per
  acceptance-gate bullet + biting mutation variant each.

L10 lane: all_gates_pass=true; versor_condition<1e-6 throughout.
Smoke: 95 passed. Runtime: 20 passed.
2026-06-15 02:01:52 -07:00
Shay
b1708a76d5
workbench(vault): give the evidence rail an honest vault-entry progression (#764)
Stop the rail reading as a wall of 'not applicable to vault entries':

- each dim stage now carries a specific honest reason (intent: a stored
  field has no originating user intent; authority: vault recall is
  read-only; action: no emission)
- replay lights on the recorded versor_digest exactly as a turn lights
  on its trace_hash — a content-addressable fingerprint, NOT a verified
  replay claim (honesty contract sanctioned)
- provenance/admissibility derivations enriched (versor_digest + metadata;
  epistemic_state / epistemic_status)

Fixed 7-stage spine and honesty contract preserved; labels unchanged.
Strengthened the meaningfully-fail test to cover replay + dim stages.
2026-06-15 01:49:54 -07:00
Shay
3e8f40b685
workbench(vault): filter entries by status, metadata text, and facets (#763)
Make a populated vault navigable beyond opaque digests:

- status facet: 'all' + the distinct epistemic_status values present in
  the loaded entries (derived, never a hardcoded closed set — the status
  vocabulary is engine-owned)
- text search now reaches metadata keys/values, not just the epistemic
  labels + index
- boolean facets: 'Has proposition' (propositional_form) and 'Has
  promotion digest' (promotion_certificate_digest); dropped 'has replay
  hash' — no such field exists
- filter-empty stays filter-empty (not persistence guidance)

No new endpoint; filters operate on already-loaded entries.
2026-06-15 01:38:09 -07:00
Shay
74dc7c7a3e
workbench(vault): deepen vault entry inspector + raw metadata drawer (#762)
Surface a selected vault entry's real evidence instead of just
epistemic_state + a digest:

- widen VaultEntrySubjectData to carry epistemic_status + the full
  metadata dict (data already reaches the inspector at runtime; this is
  a type-only unlock, no new fetch/endpoint)
- inspector shows epistemic_status and epistemic_state distinctly, core
  identity rows (turn, role) with honest 'not recorded' when absent,
  present-only rows (corrected, energy_*, promotion_certificate_digest),
  a propositional_form headline when present, copyable vault:<index> and
  versor_digest handles, and a collapsible key-sorted raw metadata drawer
- curated rows key off fields that actually exist on vault entries; the
  raw drawer is the catch-all as the open metadata schema grows

Exact-recall doctrine preserved: no similarity/relevance/score text.
2026-06-15 01:29:12 -07:00
Shay
4522e5b1cf
workbench(vault): frame empty and unavailable vault states honestly (#760) 2026-06-15 01:05:50 -07:00
Shay
8aa18760e5
docs(handoff): add L10 continuity hardening brief pack (#759) 2026-06-15 00:41:06 -07:00
Shay
efd280d45f
feat(l10): autonomous idle frontier-contemplation — the always-on life learns on its own (#758)
* feat(l10): autonomous idle frontier-contemplation — the always-on life learns on its own

Frontier survey move #2, path (c): give the always-on idle life a stream of experience via
SYMBOLIC intake (not the afferent→field seam, which is deferred pending a commensurability
ruling — docs/analysis/afferent-field-ingest-scope-2026-06-15.md). The idle heartbeat
converges (idle_tick drains a finite backlog + saturates is-a closure, then does nothing
forever); this makes it autonomously MINE its frontier with no user turn.

Reuse-heavy (the mechanism largely existed): a gated idle pass wires ADR-0080 contemplation
into the always-on loop.

- core/config.py: contemplate_frontier_during_idle (default off → no behavior change).
- chat/runtime.py: idle_tick pass 2b — when on, runs core.contemplation.run_contemplation()
  (mines the checked-in frontier-compare reports) and persists a reviewable ContemplationRun
  to <engine_state>/contemplation_runs/idle_<substrate_hash>.json. IDEMPOTENT per frontier
  (an already-mined frontier is not re-persisted → the life converges, no churn) and MEMOIZED
  per session (the static frontier is mined once, not re-read every beat). SPECULATIVE-only
  (ADR-0080: ContemplationFinding.__post_init__ RAISES if not SPECULATIVE → wrong=0 by
  construction; never ratified here — the HITL path is untouched). IdleTickResult gains
  frontier_findings.
- chat/always_on.py: run_continuous did_work + HeartbeatRecord count frontier work, so the
  heartbeat/soak convergence (H3) stays honest.
- core/cli.py: `core always-on --contemplate-frontier` (explicit learning-daemon mode) +
  honest per-beat log ("+N findings").

Adversarial 3-lens review (wrong=0/convergence, determinism/trust-boundary, test-vacuity)
found 7; fixed the real ones: the MEDIUM torn-write (write_contemplation_run is now atomic
temp+os.replace — the indefinite-uptime daemon expects SIGKILL mid-write, and the skip-guard
would never repair a torn canonical file), the gitignore breadth (contemplation_runs/ at any
engine-state root), the 64-bit filename truncation (full substrate_hash), and the
re-mine-every-beat cost (session memoization). Deferred + documented: the shared
contemplation miner embeds absolute report paths in finding CONTENT — a portability wrinkle
that does NOT affect wrong=0/convergence/soak determinism (those key on the content-only
substrate_hash) and touches shared code + its test surface; a follow-up.

Tests (non-vacuous): 7 — off-by-default (no behavior change), mines-when-enabled,
findings-SPECULATIVE (wrong=0), converges-idempotent, heartbeat-mines-then-converges,
mines-once-per-session (memoization), atomic-no-orphan. 55 idle/contemplation/always-on +
96 invariants/contemplation + 28 smoke green; `core always-on --contemplate-frontier` mines
end-to-end. engine_state contemplation_runs are per-life runtime state (gitignored).

* fix(l10): isolate idle frontier pass + persist frontier_findings (#758)

Three review blockers on the autonomous idle frontier-contemplation pass:

1. Daemon isolation. idle_tick's pass 2b called run_contemplation /
   write_contemplation_run bare inside the always-on loop (run_continuous
   wraps idle_tick in nothing but an exit-checkpoint finally), so a malformed
   frontier report or a transient FS fault would propagate out and KILL the
   continuous life. Wrap the optional pass best-effort (the exit-checkpoint
   boundary idiom): degrade to 0 findings + RuntimeWarning, leave did_work
   untouched, let a later beat retry.

2. Durable did_work explanation. HeartbeatRecord carried frontier_findings
   but serialize_report omitted it and AlwaysOnReport had no run total, so
   lived_life.json could show did_work=true with facts=0/proposals=0 and no
   persisted cause. Persist frontier_findings per-record + total_frontier_findings.

3. Honest HITL claim. The docstring/CLI/config said findings are "reviewable
   via the HITL path", but the existing HITL queue + workbench reader scan
   <repo>/contemplation/runs while the pass writes <engine_state>/contemplation_runs/.
   Narrow the claim to "persisted reviewable artifact, not yet in the HITL
   queue" (wiring that discovery is a follow-up).

Tests: 4 new non-vacuous regressions (mining-failure degrade, write-failure
degrade, always-on loop survives frontier failure, lived_life persists
frontier_findings) — all fail against the unpatched pass. 38 L10/always-on/
workbench tests green.
2026-06-14 23:59:49 -07:00
Shay
ee970d8306
Merge pull request #757 from AssetOverflow/codex/l10-heartbeat-soak
test(l10): falsifiable long-horizon soak for the always-on idle heartbeat
2026-06-14 18:30:35 -07:00
Shay
aed273b14a test(l10): falsifiable long-horizon soak for the always-on IDLE heartbeat
The always-on PROCESS shipped (run_continuous + daemon + CLI), but the claim "it holds
over uptime" was UNFALSIFIED: the existing evals/l10_continuity soak drives the TURN loop
and is disjoint from run_continuous; the daemon path was covered only by ≤5-beat unit
tests. This lane converts "built" → "proven over horizon" for the idle path.

evals/l10_always_on/ mirrors the l10_continuity predicate/mutation harness but drives the
IDLE heartbeat: it seeds a real continuous life (held self + a cognitive turn to excite the
field), runs N beats with NO user turn, and gates four falsifiable predicates, each with a
*_holds (real soak) AND a *_bites (mutation) test per CLAUDE.md schema-as-proof:

- H1 closure — every observed idle beat versor_condition < 1e-6 (held over uptime, READ
  never repaired); bites on a breached beat AND on a vacuous no-field soak.
- H2 bounded idle — a no-work beat adds NOTHING to the vault (no idle leak — invisible at
  5 beats, fatal at 100k); bites on idle vault growth.
- H3 convergence — a saturated idle life SETTLES and stays settled (no re-awakening), at
  rest at the end, closure intact on the tail; bites on never-settling + on re-awakening.
- H4 reboot resume — a reboot mid-soak resumes the SAME life (strict identity guard passes,
  pre-reboot DERIVED learning survives, post-reboot closure holds); bites on failed
  resume + on lost learning.

Measured (5000 idle beats, reboot@2500, ~20s): ALL gates pass — versor_condition flat at
1.389e-07 (no drift, no repair), vault bounded at 6 (no idle leak), converged at beat 1
with a 4999-beat at-rest tail, reboot resumed the same life with learning intact. This is
the empirical resolution of the L10 riskiest-unknown FOR THE IDLE PATH (the closure-by-
construction ruling covered the field-transition walk; this covers indefinite idle uptime).

Honestly NOT covered (recorded in the report, no silent skip): the continuously-LEARNING
life's resource cost under a sustained new-fact stream (O(n²) snapshot; per-run lived_life)
— out of scope until an afferent/intake feed + incremental persistence exist (the next
frontier).

Run on demand: PYTHONPATH=. python -m evals.l10_always_on [n_beats] [reboot_beat]. Path-
scoped out of CI smoke (like l10_continuity); runs in post-merge full-pytest. Additive — a
new eval lane, no existing files touched. 12/12 lane tests + architectural invariants green.
2026-06-14 18:20:48 -07:00
Shay
1e4d10affe docs(handoff): demo capture shot-list & script for first public assets 2026-06-14 18:20:23 -07:00
Shay
f3f23520d8
docs(workbench): add superseded banners to historical wave docs (#755)
The wave-* planning docs record their wave, not current state. Add a
consistent "Historical record — superseded" banner to each, pointing to
README (Current Status), UI-UX-GUIDE, and the route registry as the live
sources. wave-1/R/M-worthiness/M-consolidation covered.

Docs only.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 17:49:18 -07:00
Shay
7945bffc86
docs(workbench): reconcile living docs with shipped state (#754)
Bring the four living workbench docs up to date with what actually
shipped, and document the TruncatedCell full-content reveal.

- README: route count 14 -> 16; add Lived Life (Evidence) and CORE-Logos
  (Substrate) to the shipped-surfaces table; full Trace tab list; add the
  TruncatedCell reveal to cross-cutting; link the visual-evidence handoff;
  bump status date.
- UI-UX-GUIDE: §12 now reflects the built read-only /logos reader vs the
  unbuilt Studio; §13 moves the cognitive-pipeline visualizer, field/
  versor_condition reader, and identity-continuity items from "absent" to
  "shipped"; new §14 documents the truncated-cell reveal pattern + the
  <a>/<Link> non-nesting boundary; bump date.
- ui-component-map: add TruncatedCell to shared data viewers.
- design-system: add the Truncated Cell Reveal shared-component note.

Docs only; no code change.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 17:46:54 -07:00
Shay
87d4b2fb9a
Merge pull request #752 from AssetOverflow/codex/always-on-daemon
feat(l10): always-on daemon/CLI — the process that runs the continuous-life heartbeat
2026-06-14 17:44:19 -07:00
Shay
3fb50b1ae3
docs(handoff): visual evidence for workbench TruncatedCell (#749/#750) (#753)
Add a handoff brief with five screenshots captured by driving the real
vite-preview build with Playwright (mocked API), confirming the
full-content reveal works across the originally reported proposal queue
table, the trace propagation edges, and the trace selection rails — with
the row staying unselected on reveal (stopPropagation holds).

Verification only; no code change. Records the harness gotcha that a
role-by-name query resolves to the row, not the icon trigger.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 17:37:35 -07:00
Shay
18e25580f3 feat(l10): always-on daemon/CLI — the process that runs the continuous-life heartbeat
The L10 heartbeat loop (run_continuous) had no process to drive it; `core always-on`
is that process — the T-experience spine made runnable. It ticks idle_tick on a wall-clock
cadence so the engine LIVES and LEARNS with no user turn, persists lived_life.json (the
workbench Lived Life surface) + the checkpoint, and resumes the SAME life on restart.

- chat/always_on.py: run_continuous gains unbounded operation (heartbeats=None, runs until
  stop) + an interruptible inter-beat wait (_sleep_until_stop) so shutdown latency is one
  slice, not the cadence interval. Persists the run's identity pack ids (resume-verdict
  faithfulness). on_heartbeat is now best-effort (a broken log pipe can't kill the life).
- chat/always_on_daemon.py (new): the daemon shell — a single-instance OS lock (fcntl.flock:
  kernel-held, atomic, auto-released on death — no stale window, no PID-reuse, no half-written
  race), SIGINT/SIGTERM -> graceful stop (handlers saved/restored), the continuous-life config
  FORCED on, ephemeral (--no-load-state) writes no durable artifact. Foreground + explicit:
  no hidden background execution (CLAUDE.md); only writes the engine-state dir it was given.
- core/cli.py: `core always-on [--interval --max-beats --no-load-state --quiet]` with per-beat
  + summary logging; validates --interval; reports IdentityContinuityError / IncompatibleEngine
  StateError (the "different life / newer build" cases) as clean refusals, not tracebacks.
- workbench/readers.py: ENGINE_STATE_ROOT now honors CORE_ENGINE_STATE_DIR (= the daemon's
  resolved dir), so the workbench can't be split-brained (reading REPO_ROOT/engine_state while
  the daemon writes elsewhere); the Lived Life resume verdict recomputes from the persisted
  pack config, not a default config (no false substrate_changed for a non-default-pack life).
- Lived Life absence state now points at the real `core always-on` command (loop closed).

Adversarial 4-lens review (lock/concurrency, signals/shutdown, invariants/trust-boundary,
test-vacuity/CLI) caught 16 findings; this fixes all real ones — the HIGH lock races (two
daemons over one life), the env split-brain, the IO-kill, the uncaught identity/schema errors,
the unvalidated interval, the ephemeral-artifact shadow, and the resume-verdict pack-id bug —
and closes the two test-coverage gaps it flagged (real SIGTERM path + config-forcing-at-the-
runtime boundary).

Tests (non-vacuous): 11 daemon (flock live-holder refusal, leftover-lock reclaim, unbounded+
stop, interruptible sleep, forced-config-at-boundary, no-load-state guard, REAL SIGTERM
subprocess) + the reader pack-id discrimination test (fails under the old default-config bug).
245 workbench+invariants+always-on Python green; frontend tsc + vitest green; `core always-on`
verified end-to-end (bounded, real SIGTERM graceful stop, interval rejection).

engine_state/always_on.lock is runtime state (gitignored, ADR-0146 pattern).
2026-06-14 17:33:37 -07:00
Shay
0cb01ee3f1
Merge pull request #751 from AssetOverflow/feat/workbench-health-footer
feat(workbench): wire /health liveness probe into the status footer
2026-06-14 17:23:57 -07:00
Shay
f2028de0a1 docs(workbench): note the /health liveness signal in ADR-0162 footer spec
StatusFooter now surfaces four signals, not three — add the GET /health
liveness dot to the design-system footer contract to match shipped UI.
2026-06-14 17:18:13 -07:00
Shay
0bdc2eef6d feat(workbench): wire /health liveness probe into the status footer
The /health endpoint was the only W-026 API route with no UI consumer.
Surface it as a liveness indicator (colored dot + label) in the status
footer, isolated from /runtime/status so a live server still reads
"Healthy" even when runtime status is degraded.

- types: HealthStatus matching the server's {"status":"ok"} payload
- client: fetchHealth() with a short 3s timeout for fast failure
- queries: useHealth() polling every 15s, retry disabled
- footer: HealthIndicator rendered in both normal and degraded branches;
  any non-ok status fails safe to "Unhealthy"
- tests: healthy / errored / non-ok / checking / survives-runtime-failure
2026-06-14 17:16:43 -07:00
Shay
de2d5da4f3
feat(workbench): extend TruncatedCell to trace edges and selection rails (#750)
Follow-up to #749, which deliberately scoped TruncatedCell to the
non-virtualized columnar tables and left trace edges + single-column
selection rails as-is. Wire it into those too so every truncated data
cell in the workbench has the same full-content reveal (popover +
copy + modal-for-long).

Component:
- Add `align` prop ("start" | "end"). `end` hugs the right edge and
  right-justifies the display, for right-aligned columns like the trace
  `to_stage` cell. Defaults to "start" (unchanged).

Wiring:
- Trace propagation edges: from_stage / to_stage (to_stage align=end).
- Trace pipeline stage rail: stage label + summary.
- Trace turns rail: prompt + surface excerpts.
- Runs rail: session_id. Replay rail: prompt excerpt. Packs rail:
  pack_id. CORE-Logos pack rail: pack_id. Logos alignment rail: edge
  (rich source→target display, plain-text value for copy/reveal).
- Contemplation run rail: run_id; stage-evidence ids.
- Demos rail: title; scenario trace hash.
- Proposal detail: replay-equivalence hash, evidence hashes;
  proposal chain provenance.

Two row elements that were real <button>s (trace stage rail,
contemplation run row) are converted to <div role="button"> with an
Enter/Space onKeyDown handler — the codebase's established selectable-row
pattern (ProposalTable, RowShell) — so nesting TruncatedCell's button
trigger is valid HTML rather than a button-in-button.

Intentionally left as-is (documented):
- RunsRoute TurnRefRow is an <a>/<Link> row; nesting an interactive
  trigger inside an anchor is invalid HTML. The surface excerpt is
  reachable by following the link.
- Digests everywhere keep DigestBadge (already copy + full-value title).
- Section headings (e.g. the trace stage-detail <h3>) are not data cells.

Evidence: workbench-ui `pnpm build` (tsc -b) green; full vitest suite
535 passed / 60 files, incl. a new align assertion on TruncatedCell.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 17:11:47 -07:00