Butlin evidence-tier redesign: fix FIFO boost bug, correct indicator taxonomy, replace blended score with honest evidence tiers - #30
Conversation
d37df52 to
83b04a2
Compare
|
I verified the taxonomy claim independently before commenting, since it's the load-bearing one. Pulled arXiv:2308.08708 and extracted the indicator list from the paper itself: exactly 14 unique codes — RPT-1,2 · GWT-1..4 · HOT-1..4 · AST-1 · PP-1 · AE-1, AE-2. A single PP indicator, both AE indicators present, no IIT. The paper states plainly that the authors "do not consider integrated information theory," and gives the reason: IIT violates the computational-functionalism assumption the whole method rests on (Tononi & Koch hold that a system implementing the same algorithm as the brain would not be conscious if its components were different). Your correction matches the source on all three points. On the evidence model — the part I'd defend if someone pushed back: keeping Four things I'd probe, roughly in order of how much they'd bother me: Suite-level aggregation. The old failure was one number ("14/14, mean 0.85") averaging across kinds of evidence. Does the new report structurally prevent that number from returning — is there any path producing a scalar over indicators whose outcomes differ in tier, or is the summary necessarily a vector? If the types already forbid it, that belongs in Ratchet on the architectural constants.
Broken probe vs real refutation. You added On the follow-up you flagged: yes, I'd like to take the baseline artifact and the CI-lane split. Sketch, so you can shoot it down before a PR exists. The baseline is identified by schema version, repo commit, config hash, the feature set it was produced under, the seed set, and a machine profile — a baseline that doesn't say what it was measured on can't be compared against anything, and the schema version is what lets the tool refuse a comparison instead of silently misreading an older file. Per indicator it stores not a float but an effect estimate with dispersion across seeds, plus probe quality and outcome. Comparison happens on effect sizes within tolerance bands, never on raw score equality: the integrity lane fails on sign flips and out-of-band drops, and stays quiet on floating-point noise. Updates need a legitimacy rule — a baseline change lands as its own reviewed commit naming the mechanism that changed and why the shift is expected, with the same asymmetry as above, since a silent improvement is as suspicious as a silent regression. For context on why this particular follow-up interests me, and so you can check whether I'm worth handing it to: I work on the same problem from the action side rather than the measurement side. Method note, since it should weigh on how much the above counts: I read the diff and verified the Butlin taxonomy directly against the paper, but I did not build the crate locally — this machine is saturated with a long-running job and a Rust workspace build would contend with it. So none of this is a test-run claim; it is a design review. Same working arrangement as my earlier comment on #7: human + AI pair. |
…tripping it
The standalone repo's workspace manifest failed to parse at all
(cargo metadata/check/build/test all failed before compiling anything):
symthaea-psych-bench/Cargo.toml declared
`symthaea-backend = [..., "dep:symthaea"]` while the matching
`symthaea = { path = "../../.." }` dependency was commented out (the
sync tooling's generic "strip any path ../../.. dependency" convention,
applied to a case it wasn't written for). symthaea-pulse had the same
dependency stripped even though its main.rs uses it unconditionally,
with no feature gate at all.
An earlier version of this fix kept the feature declared but empty
(dropping the dep:symthaea reference) to make the manifest parse. That
was the wrong fix: it left a declared feature whose gated code
(wm::full's rehearsal-boost tracking, butlin::ablation's real-signal
matrix) can never compile when enabled, and left symthaea-pulse
unbuildable outright.
This repo does contain a root package named `symthaea` (unlike the
mycelix-*/prism-* cases the generic stripping rule was actually written
for, where the referenced path genuinely doesn't exist in this repo),
and the commented `../../..` path correctly resolves to it from both
crates. Restoring the real dependency in both Cargo.tomls is not a
cycle: symthaea-psych-bench is only ever a [dev-dependencies] entry of
the root symthaea crate (Cargo permits dev-dependency cycles), and
symthaea-pulse isn't a dependency of the root crate at all.
Cargo.lock update: confirmed necessary, not optional pruning -- tried
main's original lockfile unchanged with `cargo test --locked` first;
it fails ("cannot update the lock file ... because --locked was
passed"), confirming a real resolve is required once these dependency
edges exist. The resulting diff (37 insertions, 236 deletions) is:
+1 package (symthaea-consciousness-equation, a real transitive
dependency of the newly-reachable symthaea crate), -16 packages no
longer reachable once the graph is correctly resolved (astro,
mycelix-bridge-common, mycelix-claim-types, mycelix_finance_types,
ndarray, orbital-mechanics, pqcrypto-kyber, pqcrypto-sphincsplus,
prism-common, prism-ingest, prism-search, proofs-commitment,
proofs-config, sgp4, sovereign-profile, symtropy-math). All 16 were
lockfile entries orphaned by the prior broken/stripped dependency
graph, not a broad unrelated prune.
Verified in this checkout after restoring both dependencies:
- `cargo metadata --locked --no-deps`: succeeds
- `cargo check --locked -p symthaea-psych-bench -p symthaea-pulse
--features symthaea-psych-bench/symthaea-backend --tests`: clean
- `cargo test -p symthaea-psych-bench --features symthaea-backend --
butlin`: 33/33 pass, 1 ignored (a diagnostic, not a regression check)
- `cargo test -p symthaea-psych-bench --features symthaea-backend --lib
-- wm::full`: 12/12 pass, including test_boost_survives_fifo_index_shift
-- the actual regression test for the bug this PR's second commit fixes,
now genuinely exercised in this repo, not just in the monorepo
- `cargo test -p symthaea-psych-bench -p symthaea-pulse -- butlin`
(default features, no symthaea-backend): still 31/31 pass, confirming
the now-optional dependency doesn't change default-feature behavior
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…with tested evidence-tier redesign
PROVENANCE NOTE (read first): this repo's main already contained an
earlier positional-VecDeque WM rehearsal-boost implementation and an
earlier draft of Butlin real-signal wiring, whose provenance does not
match the monorepo commit cited by the last sync commit
("sync: update from monorepo @ 431d2cafdf") -- verified directly
against that exact commit object, which contains neither. Full
evidence (blob SHAs, diffs, preserved snapshots) is documented and
checksummed at
docs/release/evidence/sync-integrity/2026-07-26-symthaea-standalone/
in the private monorepo, and referenced from
#25 (also private).
That prior WM implementation misattributed rehearsal boosts after
at-capacity FIFO eviction: sync_boost() only resynced on a net length
change, but a normal perceive() at capacity evicts one item and
inserts one in the same step, so length never changes and the boost
silently drifts onto the wrong item -- the ordinary steady-state case,
not an edge case. The prior Butlin draft was also on the wrong
indicator taxonomy (a spurious PP-2/IIT-1 pair; Butlin et al. 2023,
arXiv:2308.08708, has one PP indicator and explicitly excludes IIT)
and used a 0.6*static + 0.4*runtime blended score that could let a
fully-dead live signal still read as "present".
This commit replaces both with the tested monorepo versions:
- wm/full.rs: rehearsal boost keyed by (arrival_tick, rank-among-same-
tick) identity instead of container position -- correct across both
plain FIFO eviction and dream consolidation's merge (which keeps the
earlier arrival tick as the surviving item's identity). Regression
test `test_boost_survives_fifo_index_shift` demonstrates the fixed
invariant directly, plus coverage for multi-eviction survival,
evicted-entry pruning, cross-item isolation, and duplicate-tick rank
disambiguation.
- butlin/{ablation,indicators,report,mod}.rs, harness/live_runner.rs,
tests/butlin_{ablation,live}_integration.rs: the real 14-indicator
Butlin et al. (2023) taxonomy (RPT-1/2, GWT-1/2/3/4, HOT-1/2/3/4,
AST-1, PP-1, AE-1/2), each with an ablation-validated live probe
where one exists. Score model replaced: SupportTier
(ArchitecturalOnly/Observed/CausallySupported/FunctionallySupported)
is separate from EvidenceOutcome (Supported(tier) | NotDemonstrated |
Contradicted) rather than one ordered ladder, since a negative
finding isn't "below" a positive tier. architectural_score and
live_score are reported separately, never blended.
annotate_with_ablation_results() is a pure, strict, provenance-
bearing merge (rejects unknown/duplicate indicator IDs, never
silently upgrades a tier, deterministic and idempotent) -- the only
path that can produce causal/functional support, since a single
evaluate() snapshot can't rule out a frozen signal.
- tests/butlin_regression.rs (new): the always-on "structural contract
gate" -- cheap, no symthaea-backend needed, fails only on a
malformed evidence system, never on a legitimate NotDemonstrated/
Contradicted result.
- BUTLIN_EVIDENCE_TIER_DESIGN.md (new): full design rationale.
Verified in this checkout (with the real symthaea dependency restored
by the preceding commit): `cargo test -p symthaea-psych-bench
--features symthaea-backend -- butlin` 33/33 pass (1 ignored
diagnostic); `cargo test ... --lib -- wm::full` 12/12 pass; default
features still 31/31 pass; `cargo fmt --check` / `cargo clippy` clean
-- zero warnings in any file this commit touches.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
symthaea-pulse's write_butlin_pane (html.rs) and its startup summary line (main.rs) were the one real internal consumer of the old IndicatorStatus enum, which the second commit removed. Migrates both to EvidenceOutcome/SupportTier: the dashboard's 3-color dot becomes 4 tiers plus a distinct red for NotDemonstrated/Contradicted, and the startup log line reports causally/functionally-supported and observed counts instead of present/partial. Verified in this checkout, now that the first commit restored symthaea-pulse's real dependency: `cargo check -p symthaea-pulse --features symthaea-psych-bench/symthaea-backend --tests` is clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
83b04a2 to
863411c
Compare
|
@arkh-node — thank you again for the careful original scoping on issue #7. Reviewing that proposal uncovered several deeper problems in the existing
The standalone checkout now genuinely builds and tests both the default I'd especially value your review on three points:
The PR stays in draft while that review happens — no rush. |
|
@arkh-node — this is an excellent review. Thank you for independently checking the taxonomy against the paper and, more importantly, for probing the places where the new model could still quietly recreate the old failure. I checked each of your four points against the implementation. Three expose real gaps, and the fourth is only partially settled: Suite-level aggregationYou are right: the current code no longer computes a mixed-tier scalar, but the types do not make that impossible in the absolute sense. An external caller can always extract numbers and invent an average. The guarantee we can and should enforce is narrower and concrete: no first-party report, summary or serialized schema will expose a scalar that combines indicators carrying different evidence outcomes. The official summary will remain a vector/distribution of outcomes and effect estimates, not a "14/14" or mean-quality number. I'll add that as an explicit design invariant and add regression coverage for the first-party report/serialization paths rather than claiming Rust can prevent arbitrary downstream arithmetic. Architectural-constant ratchetAgreed. I do not think the existing unit test can honestly enforce the cross-commit governance rule by itself: CI has to compare the proposed claim against the base branch or a separately versioned claims manifest. I'll document the current gate as a structural-consistency check rather than overstate it as a ratchet, and carry the asymmetric rule into the claim-gate follow-up:
That belongs naturally beside the committed-baseline and claim-lane work you volunteered to take.
|
|
Yes — I'll take the baseline artifact and the CI-lane split, if it's still open. Your three corrections all land where I'd have argued for them, and one of them sharpens my own sketch: you're right that the aggregation guarantee has to be scoped to first-party reports and serialization rather than claimed as a type-level impossibility. "No first-party summary exposes a scalar combining indicators with different outcomes" is a promise that can actually be kept and tested. Proposed decomposition, three separate PRs so each stays reviewable: 1 — baseline artifact. Schema and (de)serialization only, no CI wiring. Identity block: schema version, repo commit, config hash, enabled features, seed set, machine profile. Per indicator: effect estimate with dispersion across seeds, probe quality, outcome. Loader refuses a version it doesn't understand rather than best-effort parsing it. Plus a writer that produces one from a real ablation run, and round-trip tests. 2 — integrity lane. Compares a fresh run against a committed baseline on effect sizes within tolerance bands: fails on sign flips and out-of-band drops, silent on noise. This is the lane that needs the backend feature, which is where I need your guidance — see below. 3 — claims lane and the ratchet. A claims manifest (which indicators a given publication or milestone relies on, and at what tier), plus the asymmetric governance rule enforced against the base branch: lowering is visible but allowed, raising requires an explicit revision entry. This is the one that can't live in a unit test, as you already said. Two questions before I start on 1: Verification. Where the manifest lives. Should the claims manifest be a repo file reviewed like code, or an artifact attached to a release? I lean toward in-repo — a claim about what a paper depends on should move through review with the code that produces it — but it's your governance call, not mine. If that split works for you, I'll open PR 1 first and keep it narrow. |
… data
Three rounds of adversarial review on top of the original PR questions
(aggregation, architectural ratchet, merge semantics, probe-quality gating).
Full detail in BUTLIN_EVIDENCE_TIER_DESIGN.md's "Corrections after review"
section; summary:
Round 1 (probe-quality gate): annotate_with_ablation_results() previously
trusted AblationResult.contradicted with no check the underlying probe was
interpretable. First fix attempt was circular (baseline==ablated treated as
proof of a frozen probe). Corrected: ProbeQuality now gates only on genuine
measurement problems (non-finite, no dynamic range), never on whether the
probe moved -- movement is the ablation's actual result, not a precondition
for trusting it. Added EvidenceOutcome::Inconclusive, bumped
REPORT_SCHEMA_VERSION 2->3.
Round 2/3 (evidence-boundary hardening):
- classify_ablation() is now the single canonical classifier both
ablation.rs (measurement time) and the merge (validation time) call --
annotate_with_ablation_results() rejects the whole bundle
(EvidenceMergeError::ClassificationMismatch) if AblationResult's cached
indicator_dropped/benchmark_degraded/contradicted disagree with what the
raw scores recompute to, rather than silently trusting or recomputing past
a self-contradictory row. Caught a real latent bug in this file's own test
helper (stub_ablation_result's contradicted branch used 1.2, which never
actually exceeded baseline*1.5=1.35).
- FallbackStatus{NotUsed,Used,Unknown} replaces a bare bool -- Unknown
permits provisional ablation interpretation with a mandatory disclosure
annotation, but never counts as confirmed for the stricter Observed tier.
- benchmark_measurement_is_valid() gates CausallySupported->
FunctionallySupported: an infinite/out-of-[0,1] benchmark accuracy can
trivially satisfy the degradation comparison without classify_ablation
disagreeing (so ClassificationMismatch alone doesn't catch it) --
invalid benchmark measurements now cap at CausallySupported.
- push_annotation_once() dedups the merge's own annotations so reapplying
the same bundle to an already-merged report is truly structurally
idempotent (added PartialEq to IndicatorEvidence/ButlinIndicatorReport to
actually test this, not just outcome/count equality).
- wm/full.rs: detect_consolidation_merge verifies the whole remainder
instead of stopping at the first mismatch (false-positive risk);
consolidation-boost semantics defined as max(removed, survivor);
reconcile_boosts_after_state_change fails closed (clears all boosts)
on duplicate-tick identity ambiguity rather than risking silent
misattribution.
Also fixed a real regression introduced mid-arc:
test_merge_responsive_probe_is_not_inconclusive was asserting
qualifies_as_observed() (requires confirmed FallbackStatus::NotUsed, which
ablation-derived probes can never produce) instead of the correct
qualifies_for_ablation_interpretation() gate -- it only passed before by
accident when fallback_used was a bare bool defaulting to false.
Verified independently in both the monorepo and this standalone checkout:
cargo check --features symthaea-backend --tests (0 errors, 0 warnings in
touched files); cargo test --features symthaea-backend --lib (85 passed,
0 failed, 1 pre-existing #[ignore] diagnostic) in both.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Following up on the fix above — a closer internal review since then found that version had a real problem, so I want to flag it rather than let the thread stand as "done." What was wrong with the version described above: What's landed now (
Full before/after reasoning for each item is in Verified independently in both the monorepo and this standalone checkout: Still in draft — I'd value another look at whether the revised quality gate and evidence-boundary invariants now address the original concern, especially the distinction between a genuine null result and an invalid probe. |
|
That closes rounds one through three, and the circularity catch is the good kind — you found it by re-reading your own defence, which is harder than finding it in someone else's code. Flagging it rather than letting the thread stand as "done" is the part that matters. The fourth round, if there is one, isn't a defect in this PR. It's this: Concretely — with the circularity removed, a null delta is correctly reported as a real negative finding. But "the mechanism doesn't move this indicator" and "this probe wouldn't have moved for any manipulation" are still indistinguishable from the report alone, because nothing in the pipeline establishes sensitivity independently of the experiment being interpreted. You listed targeted-ablation negative controls as future work; I'd argue the missing half is the positive control: a manipulation known a priori to move the indicator, run in the same session, whose failure to register invalidates the arm regardless of what the real ablation did. That's the only evidence of responsiveness that doesn't come from the datum under interpretation. Two smaller things, both about provenance rather than logic:
Duplicate-tick ambiguity failing closed by clearing boosts is right, but silent clearing is data loss. If it annotates ("boost state discarded: ambiguous identity at tick N"), a later reader can tell a clean run from a degraded one; if it doesn't, the degradation is invisible after the fact. Same argument as your On the baseline work — I read the existing The format already stores dispersion properly ( Also, and this is probably just an oversight: I'll open PR 1 against the existing snapshot format unless you'd rather it wait until this one lands. |
12 open individual dependency-bump PRs (leptos, iroh-gossip, ratatui, rust-toolchain, several GitHub Actions, etc.) made the repo look unattended and buried substantive work (the evidence-tier redesign in PR #30) under routine bumps. Groups patch/minor cargo and github-actions updates into one PR per ecosystem per week; major-version bumps stay ungrouped since they can be real breaking changes that deserve individual review. Also lowered open-pull-requests-limit (10->5 cargo, unset->3 actions) so the queue can't silently grow back to the same state. Existing open dependabot PRs (#13-29) aren't touched by this commit -- several are legitimate updates (including at least one, iroh-gossip, that may be security-relevant) that deserve their own merge decision rather than a blanket close.
Addresses #7. Replaces the blended-score/status model discussed there with a
proper evidence-tier system, and fixes a real WM correctness bug found while
adding the regression tests #7 asked for.
Summary
PP-2/IIT-1pairand was missing
AE-1/AE-2. Verified against Butlin et al. (2023),arXiv:2308.08708, Table 1 directly — the paper has one PP indicator and
explicitly excludes IIT ("not compatible with computational
functionalism"). Now matches the paper's real 14 indicators exactly.
IndicatorStatus(
Present/Partial/Absent, previously hardcoded toPresentregardless of score) and a
0.6*static + 0.4*runtimeblend (which let afully-dead live signal still read as "present") with:
SupportTier:ArchitecturalOnly → Observed → CausallySupported → FunctionallySupportedEvidenceOutcome:Supported(SupportTier) | NotDemonstrated | Contradicted— kept as a separate concept fromSupportTierrather than one ordered ladder, since a negative finding ("we tested
and it didn't move") and a contradicted one ("it moved the wrong way")
aren't "below" a positive tier, they're different failure modes.
architectural_score(the hand-assigned constant) andlive_score(the raw, unblended probe) reported separately, never averaged.
evaluate()snapshot can only ever produceArchitecturalOnly— one number can't rule out a frozen/fallback signal. Only
annotate_with_ablation_results()(a pure, strict, provenance-checkingmerge of real baseline-vs-ablated evidence) can produce
CausallySupported/FunctionallySupported/NotDemonstrated/Contradicted.BUTLIN_EVIDENCE_TIER_DESIGN.md.regression tests, found that the rehearsal-boost tracking in
wm::full::WorkingMemorysilently misattributed boosts to the wrong itemas soon as working memory reached capacity — not an edge case, the
ordinary steady-state case. Root cause: boost was tracked in a
position-aligned
VecDeque, resynced only on a net length change; anormal at-capacity
perceive()evicts one item and inserts one in thesame step, so length never changes and the resync never fires. Fixed by
keying boost on
(arrival_tick, rank_among_same_tick)item identityinstead of container position — correct across both plain FIFO eviction
and dream consolidation's merge. See
test_boost_survives_fifo_index_shiftand the surrounding coverage in
wm/full.rs.tests/butlin_regression.rs, theoriginal ask in Add Butlin indicator automated regression tests #7): cheap, no
symthaea-backendfeature needed, failsonly when the evidence system itself is malformed (wrong indicator count,
duplicate/unknown IDs, non-finite scores, a constant drifting below its
floor) — never on a legitimate
NotDemonstrated/Contradictedresult, since that's a real scientific finding, not a bug.
symthaea-backend'sfeature list referenced a dependency the sync tooling had stripped,
which made
cargo metadatafail for the entire workspace before thisPR — not just this crate. Restored the real dependency rather than just
neutering the feature declaration (see commit 1 for why, including why
it isn't a dependency cycle).
A provenance note on what this replaces
This repo's
mainalready contained an earlier positional-VecDequeWMboost implementation and an earlier Butlin draft (still on the old
PP-2/IIT-1taxonomy), and their content doesn't match the monorepocommit cited by this repo's last sync commit — verified directly against
that exact commit object, which contains neither. Full evidence (blob SHAs,
diffs, preserved snapshots) is documented and checksummed in the private
monorepo, referenced from a private sync-integrity tracking issue there.
This PR replaces that content wholesale with the tested monorepo source
rather than patching on top of a baseline that doesn't match its own
claimed provenance. See commit 2's message for detail.
What's verified, in this repo specifically
All of the following were run in this actual standalone checkout, not just
the monorepo:
cargo metadata --locked --no-deps: succeeds (was a hard manifest-parsefailure before commit 1 — confirmed by checking out
mainclean).cargo test -p symthaea-psych-bench(default features): 31/31 pass.cargo test -p symthaea-psych-bench --features symthaea-backend -- butlin: 33/33 pass, 1 ignored (a diagnostic, not a regression check).cargo test -p symthaea-psych-bench --features symthaea-backend --lib -- wm::full: 12/12 pass, includingtest_boost_survives_fifo_index_shift— the actual regression test for the FIFO bug this PR fixes.
cargo check --locked -p symthaea-psych-bench -p symthaea-pulse --features symthaea-psych-bench/symthaea-backend --tests: clean —Pulse compiles, including its migrated Butlin dashboard pane.
cargo fmt --check/cargo clippy(touched files): clean, zerowarnings in anything this PR changes.
repo's
mainfails almost the entire matrix independently of this PR —same root cause as the manifest break above). Systematically compared
every failing job on this PR against the equivalent job on
main;zero failures are attributable to this PR. The remaining
symthaea-brocafailures (a crate this PR never touches) are aseparate, pre-existing compile error (
MemoryBridge: Clonenotsatisfied) and should be tracked independently, not folded into this PR.
What's intentionally left as follow-up (not in this PR)
butlin_regression.rs), an evidence-integrity gate (backend-enabled,compares live effect estimates against a committed baseline), and an
explicitly opt-in claim/milestone gate (e.g. "no indicator used in a
given publication may be
Contradicted").would compare against (schema version, commit SHA, config hash, seeds,
per-indicator effect estimates and probe quality, etc.) — nontrivial
design work in its own right (portable across machines? how to treat
floating-point noise? what makes a baseline update legitimate?).
indicator's own targeted mechanism move it, or does anything breaking
move it?) — the effect-size/probe-quality types added here are meant to
make that addable later without another schema break, not to already
implement it.
@arkh-node — this directly follows up on your comment. I'd love your review
of the evidence-tier model itself, and if you're interested, the follow-up
work above (CI-lane split + baseline artifact design) is exactly the kind
of thing you originally scoped in #7 and would be a great next PR to own.