Skip to content

fix(ingest): name the event types and sources no detector reads, not unknown - #103

Merged
TAIPANBOX merged 2 commits into
mainfrom
fix/name-unread-event-types
Oct 14, 2026
Merged

TAIPANBOX merged 2 commits into
mainfrom
fix/name-unread-event-types

Conversation

@TAIPANBOX

Copy link
Copy Markdown
Owner

What and why

The bus ingest called every event type outside knownTypes "unknown". knownTypes is the set a detector reads, and it is frozen as events.consumed in compat/1.0.json. A type the bus registry names but no detector reads yet (identity_mismatch, taint_raised, unit_cap_exceeded and dependency_failed from tokenfuse, tool_call from mcp, policy_allow from wardryx) was therefore reported as unknown, although the registry names it and idryx ingests it as it is. The word was false, and the operator was told the wrong thing.

The stderr line now says what is true, and names what it counts:

N event type(s) no idryx detector reads, ingested as they are: "a", "b", ... +K more
N source(s) no idryx detector reads, ingested as they are: "typryx", ...

A source counts as read when any of its ingested events carries a type in knownTypes, judged across the whole load. Names are sorted, escaped with %q, cut at 64 bytes, and at most eight are listed, then +K more. The formatter is tokenfuse.StderrSummary, so its branches are covered in internal/ingest/tokenfuse. cmd/idryx makes one call.

Not changed: knownTypes, events.consumed, the frozen surface. No detector is added. An identity_mismatch detector is a follow-up.

Tier: T2 (user-visible stderr output).

Red first

Before the fix, TestARegisteredTypeNoDetectorReadsIsNotCalledUnknown (cmd/idryx) fails on the assertion that the registered types are not called unknown:

--- FAIL: TestARegisteredTypeNoDetectorReadsIsNotCalledUnknown
    a registered type must not be called unknown, got "idryx: tokenfuse <path>: 5 line(s) read, 0 malformed, 4 unknown event type(s)\n..."
    the four unread types must be named as ingested as they are, got "...4 unknown event type(s)..."

The formatter tests in internal/ingest/tokenfuse were red on the unfixed code at compile time (undefined: StderrSummary, rep.Sources undefined), not by assertion.

Mutants, planted one at a time in the formatter and the connector, each turned a test red, and the file was restored byte for byte:

  • bound 8 changed to 9: TestStderrSummaryBoundsAndEscapesEveryName
  • the Read tally removed: TestAnUnreadSourceIsJudgedAcrossEveryFileOfALoad
  • the merge drops Read: TestAnUnreadSourceIsJudgedAcrossEveryFileOfALoad
  • %q escaping removed: TestStderrSummaryBoundsAndEscapesEveryName
  • the 64-byte cut removed: TestStderrSummaryBoundsAndEscapesEveryName

Seven new test functions (the gate counts 374 before, 381 now). Scenarios in features/unread-is-named-not-unknown.feature, four of them, each bound to a test.

Teeth case changed

features-are-bound: no scenario left removed features/source-must-match-its-stream.feature by name. The new feature file kept scenarios alive, so that case reported TOOTHLESS. The case now removes every features/*.feature file, which is what "no scenario left in features/" means. This is a separate commit (test(gates): ...). Measured: ok (fail) with the change, TOOTHLESS without it.

Coverage (go test -cover, statements)

  • internal/ingest/tokenfuse: 96.0% before, 97.0% after. The uncovered paths were already there and are not in the changed code: verifyChain error path (85.7%), LoadWith (93.1%), parse (97.1%).
  • cmd/idryx: 40.1% before, 40.1% after. Below the 90% bar and not raised here: the change in this package is one call, which the end-to-end test covers. The formatter lives in tokenfuse for that reason.
  • Test count badge: 374 to 381, the number scripts/readme-numbers.sh measures.

Gates (local, go1.27.2; staticcheck under GOTOOLCHAIN=go1.27.1)

  • gofmt -l ., go vet ./..., go build ./...: clean.
  • go test -race -count=1 ./...: every package ok.
  • staticcheck 2026.2.1: one finding on darwin only, procPidInitIno unused (U1000) in internal/ebpfcapture/self.go, used by capture_linux.go. GOOS=linux run is clean; CI runs on Ubuntu. Not from this change.
  • go test -tags integration: compiles. Needs DATABASE_URL, not run here.
  • readme-numbers (381), detectors-complete (27), ebpf-optional, diagrams-match-detectors, compat-surface (29 frozen names), features-are-bound (17 scenarios, 17 bindings), reproducible-build: all pass.
  • govulncheck v1.7.0: no vulnerabilities. gosec v2.29.0: clean.
  • gates-have-teeth.sh: 20 cases ok, 0 failed. 4 object-matches-its-source cases NOT RUN here (they need clang 18; the ebpf job runs them).
  • preflight.sh pr: clean.

Ran it

idryx detect --load "tokenfuse:<dir>/*.ndjson" over a tokenfuse file with five types (one read, four not) and a typryx file with one valid and one malformed line:

idryx: tokenfuse <dir>/*.ndjson: 7 line(s) read, 1 malformed; 5 event type(s) no idryx detector reads, ingested as they are: "answer_given", "dependency_failed", "identity_mismatch", "taint_raised", "unit_cap_exceeded"; 1 source(s) no idryx detector reads, ingested as they are: "typryx"

Exit 0. The chain verdict line follows, as before.

NOT proven

  • No live run on the lab box, and the ebpf and integration CI jobs have not run on this branch.
  • "Read" is knownTypes membership. Two cases fall outside it. (a) A bus event whose type is spelled login, egress or mfa_challenge reaches a detector through the model constant, but is not in knownTypes, so it is named as unread. No registry producer in SPEC 6.2 emits those. (b) claimed_agent_unattested checks only whether an event has any Source; that is not counted as reading a source.
  • unrouted_egress reads scopyx by source equality. The rule here gives the same answer for scopyx's current types (web_fetch, web_blocked, both in knownTypes).
  • No detector reads identity_mismatch; that is a follow-up, not part of this change.

🤖 Generated with Claude Code

TAIPANBOX and others added 2 commits October 14, 2026 14:53
…unknown

The bus ingest report called every event type outside idryx's knownTypes
"unknown". knownTypes is the set a detector reads (frozen as events.consumed),
so a registered type that no detector reads yet, such as identity_mismatch or
dependency_failed, was reported as unknown. It is ingested as it is.

The stderr line now says what is true: N event type(s) no idryx detector
reads, ingested as they are, plus N source(s) no idryx detector reads, a
source being one whose ingested events carry no type in knownTypes across
the whole load. Names are sorted, escaped, cut at 64 bytes, and at most eight
are listed, then "+K more". The formatter is tokenfuse.StderrSummary, so its
branches are covered in that package; cmd/idryx makes one call.

knownTypes and events.consumed are unchanged. The README sentence and the
test badge (374 to 381, measured by scripts/readme-numbers.sh) follow.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
The case removed features/source-must-match-its-stream.feature by name and
expected features-are-bound.sh to say it measured nothing. A second feature
file, added for the unread-type report, kept scenarios alive, so the gate
passed on the fault it exists to catch and the teeth run reported TOOTHLESS.

The case means "no scenario left in features/", so it now removes every
features/*.feature file. Measured on this tree: the case reports ok (fail)
with the fix and TOOTHLESS without it.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
@TAIPANBOX
TAIPANBOX merged commit 1421fa2 into main Oct 14, 2026
5 checks passed
@TAIPANBOX
TAIPANBOX deleted the fix/name-unread-event-types branch October 14, 2026 13:59
@TAIPANBOX TAIPANBOX mentioned this pull request Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant