feat(eeg): add content-addressed session event references - #44
Draft
aurascoper wants to merge 3 commits into
Draft
feat(eeg): add content-addressed session event references#44aurascoper wants to merge 3 commits into
aurascoper wants to merge 3 commits into
Conversation
A dialogue turn this morning asked whether to wire a sleep detector and validate
an intervention. Both are gated, and the gates are countable:
detector SLEEP_CYCLE_DESIGN.md §21 — "5+ clean sessions through the
toolkit, no false readings"; currently 0 overnight sessions
intervention decision_registry.md entry 8 — the loop is "engineering
scaffolding, not a validated intervention"; efficacy requires
the D8 pre-registration, "non-negotiable"
The zero-throughput watchdog that turn named as the missing piece is already
built twice over — HealthSnapshot raises `eeg-zero-throughput` live, and
overnight-review detects it post-hoc against a CAPTURE_FAILED.json abort.
So this builds the thing upstream of both gates: `nc-eeg-session-event-v0`, one
record per observation, carrying the SHA-256 of the `eeg.csv` it points into
plus a sample range. It carries no signal. Hold the recording and the window is
re-derivable and `observed` recomputes to equality; without it the log says
nothing. That indirection is why one artifact serves a spectral routine today
and an encoder later — storing a latent would bind the log to whichever model
produced it, and expire with it.
**Observations only.** No stage, no efficacy, no REM (the montage has no chin
EMG — §291 R1). `session_event_contract.py` rejects a record asserting any of
them at any nesting depth, rather than leaving it to review.
Four kinds, all recomputable: clock gaps, per-channel health, band excursions
against an in-session baseline, artifact bursts. Baselines are the median over
the opening windows of the same session — a cross-session baseline would import
another night's headset fit.
`artifact_burst` is per-channel, and that was learned by running it. A
cross-channel envelope let a saturated AF7 (~900 µV against ~20 µV elsewhere)
trip the burst detector on 302 of 305 seconds, restating a fault
`channel_health_change` already reported. Per-channel thresholds drop it to 170
real transients on TP9/TP10/AF8 and zero on AF7, whose own baseline is already
saturated.
Determinism is a contract: same file, byte-identical output. `event_id` is
content-derived, `params_sha256` travels with every record so two logs are
comparable only if the rules match, and a one-sample edit changes
`recording_sha256` — invalidating references rather than silently re-pointing
them at different signal.
Standard library only. Band power via Goertzel over each band's integer bins —
O(n) per bin where a hand-rolled DFT would be O(n²) per window — because the
system interpreter on a clean runner has no numpy.
Tests/eval/test_session_events.py 20/20 pass
real recording (78,228 samples) 753 events, contract-valid
grep gate 0 forbidden names in emitted output
swift build unaffected — no Swift target added
Fixtures are synthesised in the test rather than read from Recordings/: no
eeg.csv is committed, so a test depending on one would skip vacuously.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Updated onto post-D0-R
main#43 is merged (
859feaa), so the prerequisite is satisfied. This branch wasmerged forward rather than rebased, so the reviewed commits stay as reviewed.
The effective diff is 5 files —
README.mdnever appeared here (this branchamends
Recordings/README.md), and D0-R's 11 files are now upstream rather thanin this diff.
The new ADR gate on this tree
D0-R's reference-integrity checker now runs against this branch. It passes, and
this branch adds no ADR reference of its own:
That one warning is the historical
ADR-009+recommendation indocs/reviews/, surfaced by advisory mode and deliberately preserved — notintroduced by this PR, and not an error. Hard mode is clean.
Re-verified post-merge
What this is
A content-addressed reference/index artifact — not a copied-signal dataset and not a label set. Each observation binds to
recording_sha256+ sample range +params_sha256, so later work has something reproducible to disagree about.It carries no signal. Hold the recording and the window is re-derivable and
observedrecomputes to equality; without it the log says nothing. That indirection is why one artifact serves a spectral routine today and an encoder later — storing a latent would bind the log to whichever model produced it and expire with it.What it does not claim
Detection is gated behind
SLEEP_CYCLE_DESIGN.md§21 ("5+ clean sessions through the toolkit" — currently 0). Efficacy is gated behind the D8 pre-registration (decision_registry.mdentry 8, "non-negotiable"). This is upstream of both and shortens neither — but every session run for the §21 gate now yields a structured event log for free.The validator enforces that rather than the docs asking:
clean_session_gate_credited: falseis the important one — running an extractor over a recording credits nothing toward §21. Whether a session was clean is a judgement about the recording, not about whether a script parsed it.A suppressed detector is not a clean channel
The correction that came from real data.
artifact_burstcompares a channel to its own baseline. When that baseline is already pathological the comparison is vacuous — nothing exceedsburst_sigma× a rail — so a saturated AF7 emitted zero bursts. An absent record is ambiguous between clean, no transient, and never eligible.session-events-manifest.jsonstates it instead:The validator rejects a manifest omitting any channel, because a missing entry restores the ambiguity.
Review gates
[start, start+count), non-empty, withinrecording_sample_countevent_idpins schema, recording, kind, range, params, observation — never a pathtest_same_bytes_at_a_different_path_give_identical_referencesrecording_sha256and invalidates referencestest_editing_one_sample_invalidates_every_referencetest_params_digest_is_order_independenttest_a_saturated_channel_is_reported_suppressed_not_clean*_fails_closedtestsgoertzel_powerdocs/architecture/session-events.mdEvents may overlap — the stride is shorter than the window — and are not deduplicated: two kinds firing on one window are two observations of it.
Evidence
The 750-event run is pipeline validation — the extractor runs, is deterministic, and replays. It is not evidence that 750 heuristic observations are individually correct.
Standard library only; the system interpreter on a clean runner has no numpy. Fixtures are synthesised in the test rather than read from
Recordings/, since noeeg.csvis committed and a test depending on one would skip vacuously.