Skip to content

stage(plan): complete — spec + 10-step Phase 1 plan - #25

Open
craig-dt wants to merge 1 commit into
mainfrom
stage/plan
Open

stage(plan): complete — spec + 10-step Phase 1 plan#25
craig-dt wants to merge 1 commit into
mainfrom
stage/plan

Conversation

@craig-dt

Copy link
Copy Markdown
Collaborator

Stage 4. Docs only — no code.

docs/spec.md pins the how precisely enough to hand to a stranger; PLAN.md turns it into 10 Phase 1 steps, filed as #15#24. current_stage advances to scaffold.

Shape

1 toolchain+CI → 2 foundations → {3 ingest, 4 rules, 5 zeek, 6 suricata}
                              → {7 correlate, 8 labels} → 9 cli → 10 canaries

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 editcap are 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_tools tests 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 conn and ssl alongside the default TSV — so the retained artifact and the parse input cannot disagree, which two passes could not guarantee.

label_basis assigned per source, deliberately. Not by vendor: feodotracker and sslbl-c2 are ioc-destdirect, because a flow to a C2 is the malicious activity rather than a reference to one. urlhaus is ioc-nameindicator-reference. oisf/trafficid is identify and can never label. Source class is orthogonal to admission_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 -e script treat a successful run as a failure; the distinction lives in run.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 -D is 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

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
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.

Stage: Plan & Spec

1 participant