stage(plan): complete — spec + 10-step Phase 1 plan - #25
Open
craig-dt wants to merge 1 commit into
Open
Conversation
Part A grilled the spec to the point a stranger could build it; Part B turned it into 10 numbered Phase 1 steps, filed as #15-#24. Spec decisions settled with Craig: - One module per pipeline stage, plus a shared models.py so steps 3-6 can be built in parallel without importing one another. - Zeek emits BOTH formats from a SINGLE pass — a bundled Zeek script adds JSON filters for conn and ssl alongside the default TSV, so the retained artifact and the parse input physically cannot disagree. - label_basis derived from per-source class. Assigned deliberately rather than by vendor: feodotracker and sslbl-c2 are ioc-dest (a flow to a C2 IS the malicious activity, so 'direct'), urlhaus is ioc-name ('reference'), trafficid is identify and can never label. Source class is orthogonal to admission_basis, so label_basis carries information admission_basis does not. - argparse, TOML config: zero runtime dependencies, which matters for a tool whose output trains models. - Single labels.json object; project-local .flabel/rules/<id> snapshots, content-addressed by sha256 of the sorted rule file. - Exit codes 0/1/2/3 with partial input staying 0 — a non-zero exit on a truncated capture would make every set -e script treat a successful run as a failure. The distinction lives in run.input_status. - Zeek parses conn.log + ssl.log only; everything else retained unparsed. - Plan covers Phase 1 only. Phase 2 depends on the reachability spike, and planning against an unverified assumption is the review's Critical 1. TESTING: tools real, network stubbed. Zeek, Suricata and editcap are hard test dependencies invoked for real — no mocks, because a mock encodes our assumptions about tool behaviour, which is what needs verifying. CLAUDE.md's 'never call external APIs in tests' still holds and is drawn at the right boundary: rule-feed endpoints and the PANW device are never contacted. That decision reorders the plan: the toolchain container is STEP 1, not scaffolding for later, because nothing is testable until CI can run the tools. CI also fails if zero requires_tools tests executed, so a skipped integration suite can never look like a passing one. Spec also pins the things that make Goal 2 real: canonical sort keys, sorted JSON keys, one timestamp format, and an exact list of the only fields excluded from a reproducibility comparison. Closes #3
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.
Stage 4. Docs only — no code.
docs/spec.mdpins the how precisely enough to hand to a stranger;PLAN.mdturns it into 10 Phase 1 steps, filed as #15–#24.current_stageadvances toscaffold.Shape
Parallel groups: {3,4,5,6} and {7,8} — disjoint files, safe for 2–3 worktrees.
The decision that reordered everything
Testing is tools real, network stubbed: Zeek, Suricata and
editcapare hard test dependencies invoked for real, with no mocks — because a mock encodes our assumptions about tool behaviour, which is exactly what needs verifying.CLAUDE.md's "never call external APIs in tests" still holds, drawn at the right boundary: rule-feed endpoints and the PANW device are never contacted.Consequence: the toolchain container is step 1, not later scaffolding, because nothing is testable until CI can run the tools. CI also fails if zero
requires_toolstests executed, so a skipped integration suite can never masquerade as a passing one.Spec highlights
Zeek emits both formats from one pass. A bundled Zeek script adds JSON filters for
connandsslalongside the default TSV — so the retained artifact and the parse input cannot disagree, which two passes could not guarantee.label_basisassigned per source, deliberately. Not by vendor:feodotrackerandsslbl-c2areioc-dest→direct, because a flow to a C2 is the malicious activity rather than a reference to one.urlhausisioc-name→indicator-reference.oisf/trafficidisidentifyand can never label. Source class is orthogonal toadmission_basis, so the field carries information the other doesn't — worth checking, since a purely vendor-based rule would have made them redundant.Zero runtime dependencies — argparse plus
tomllib. Snapshots are content-addressed by sha256 of a source-sorted rule file, so fetch order cannot affect the id.Exit codes: partial input stays 0. A non-zero exit on a truncated capture would make every ordinary
set -escript treat a successful run as a failure; the distinction lives inrun.input_status.Canonical output is fully pinned — sort keys, sorted JSON keys, one timestamp format, and an exact list of the only fields excluded from a reproducibility comparison. Goal 2 stops being aspirational.
Step 5 carries a determinism regression test that fails if
zeek -Dis ever dropped — locking in the verified spike-3 finding.Scope
Phase 1 only. Phase 2 depends on the reachability spike you deferred, and planning against an unverified assumption is precisely the review's Critical 1.
Closes #3
🤖 Generated with Claude Code