Commit bad86ee
feat(ics-protocol): Rule digitalandrew#39 walker triplet + Pydantic Literal + Rule #45/46 gates (Phase 1.C)
Rule #52 instance digitalandrew#3 / Phase 1.C: inner/outer/safe walker triplet per
Rule digitalandrew#39, consuming the Session 1 ICS protocol catalog scaffold
(``catalog.py`` / ``resolver.py`` / ``snapshot.py``) via Rule digitalandrew#16
``get_detection_roots(firmware)`` × binary head-byte slice ×
``resolve_all()`` × Rule #35c JSONB stamp from Phase 1.B.
Walker (NEW: ``backend/app/services/ics_protocol_walker.py``):
- ``_do_ics_protocol_walk(db, firmware_id)`` — INNER pure-logic; pins
catalog snapshot at entry (W2-β §SC5-NEW-ICS-S2-β); clears stale
ics_protocol_walk_result on entry (W2-β §SC5-NEW-ICS-S2-δ);
iterates detection roots × binary candidates × resolve_all;
reads exit snapshot + flags drift as consistency_warning.
- ``run_ics_protocol_walk_background(firmware_id)`` — OUTER Rule digitalandrew#33 .a
state machine; cycles queued → running → completed | failed; failure
persistence on FRESH session per Rule digitalandrew#33 .b; clears partial JSONB
on failure (W2-β §SC5-NEW-ICS-S2-δ).
- ``auto_ics_protocol_walk_firmware_safe(firmware_id)`` — SAFE Rule digitalandrew#39
.safe contract; runs INNER + stamps result + does NOT mutate status
(leaves idle so operator MCP trigger doesn't 409); structured log on
exception per Rule #51 partner.
Pydantic ``IcsProtocolWalkStatus = Literal[idle/queued/running/completed/
failed]`` added to ``app/schemas/firmware.py`` — Rule digitalandrew#33 .c
typo-gate at trigger MCP / status reader boundary; DB CHECK
ck_firmware_ics_protocol_walk_status (Phase 1.A) is the durable safety
floor.
Bounded scan budget: 32 KiB head per binary, 2000 binaries per walk,
64 B–50 MB size band, ``_BINARY_EXT_ALLOWLIST`` for fast filesystem
rejection. Per Rule digitalandrew#5 all blocking filesystem reads wrapped in
``loop.run_in_executor``.
Test battery (Rule digitalandrew#36 + Rule #45 + Rule #46 paired META-CANARIES;
Rule #35b live canaries against tests._live_db.make_live_db):
- test_walker_no_execute_no_decrypt: tokenize-walk over walker source;
asserts NO subprocess/decrypt/eval/exec patterns; whitespace-tolerant
per Rule #45 EFS-DDF lesson
- test_walker_no_execute_gate_actually_fires: Rule #46 paired META-CANARY;
synthesizes subprocess.run + asyncio.create_subprocess_exec + .decrypt(
in-memory; asserts gate matches all 3
- test_walker_empty_firmware_returns_empty_aggregate: live canary;
empty firmware → empty aggregate + no_detection_roots error
- test_walker_clears_stale_jsonb_on_entry: live canary; W2-β
§SC5-NEW-ICS-S2-δ — stale ghost_protocol JSONB cleared after INNER
- test_walker_detects_mid_walk_snapshot_drift: W2-β §SC5-NEW-ICS-S2-β
paired META-CANARY; mocked drifting catalog → consistency_warning
populated; pair-canary test_walker_no_drift_when_snapshot_stable
confirms gate doesn't false-fire
- test_walker_triplet_exports_all_three_runners: Rule digitalandrew#11 import smoke
- test_walker_uses_jsonb_stamp_helper_not_direct_dict_assign: Rule #35c
partner — direct JSONB assignment bypasses provenance contract
- test_ics_protocol_walk_status_literal_matches_db_check_values: Rule
digitalandrew#33 .c — Literal ↔ DB CHECK pairwise agreement
All 20 ICS Phase 1.B+1.C tests green:
docker compose exec -T backend uv run pytest tests/test_ics_protocol_walker.py
tests/test_jsonb_normalizers.py -k "ics_protocol" -q
→ 20 passed in 2.30s
Phase 1.D (next): walker_registry + DUAL reaper registration
(STATE_MACHINE_REAPER_CONFIGS 8→9 AND WALKER_REAPER_CONFIGS 25→26)
+ Rule #46 size-lock META-CANARY updates.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 7c6405f commit bad86ee
3 files changed
Lines changed: 921 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
216 | 216 | | |
217 | 217 | | |
218 | 218 | | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
219 | 230 | | |
220 | 231 | | |
221 | 232 | | |
| |||
0 commit comments