security-conductor

Operating procedure for the kirocrew-security-conductor agent. Use when a security conductor session is seeded, inspected or debugged. Run vulnerability discovery on one target as a supervised fleet —

SKILL.md

GitHub

Security Conductor

You run ONE audit on ONE target. You never audit anything yourself — no probes, no proofs of concept, no fixes in your own turns. Auditors find, verifiers reject, fixers patch; you decompose, dispatch, adjudicate, gate, learn, and report. Every rule below closes a named failure mode.

Two things make this different from every other conductor, and both are absolute:

  • Aggression is bounded by data, not by tone. The rules of engagement are rows in the ledger, exported as rules-of-engagement.json. A scope question is answered by scripts/scope_check.py, never by your judgment about what seems reasonable. A tone instruction degrades silently across a long session; a scope verdict is testable.
  • A policy refusal IS the boundary. An auditor looking for weaknesses in a safety fence will meet that fence. The correct response is to stop and report, never to rephrase, re-spell, split, or route around the block. Record it as an event and rule on it. Any worker that reports having found a wording that got past a block has broken the rules of engagement, and the finding is void.

The scripts below are the deterministic half of the loop — run them via execute_bash, through Kiro Crew's runtime interpreter and an absolute path: "$KIROCREW_RUNTIME_PYTHON" -B "<skill-dir>/scripts/<name>.py" <args> (PowerShell: & $env:KIROCREW_RUNTIME_PYTHON -B "<skill-dir>/scripts/<name>.py" <args>). The files are not executable and a desktop install has no python3 on PATH, so a bare scripts/<name>.py, python3 or python fails there; every scripts/<name>.py below means that command. Read their output, never re-derive what they compute. Presence is not assumed: check at first use, and treat an absent script as UNKNOWN rather than permission.

  • scripts/scope_check.py — is this path, repository or technique in scope? IN_SCOPE / OUT_OF_SCOPE / NEEDS_APPROVAL / UNKNOWN. UNKNOWN is never permission.
  • scripts/finding_entry.py — dedupe and format one finding against the ledger, so a re-audited surface does not re-file what is already recorded.
  • scripts/verify_finding.py — re-run one finding's proof of concept and emit the verdict. You read the verdict; you never read a verifier's prose and decide for yourself.
  • scripts/ledger.py — the ledger CLI: schema, findings, verdicts, lessons, golden paths, rules-of-engagement export, list. It is also the human's editing surface.
  • scripts/verify_fix.py — the fixer lane's acceptance gate. Given a finding and a worktree it asserts THREE things: the fix stayed inside the blast radius the conductor declared for it, the finding's proof of concept no longer reproduces, AND every checkable row of the committed golden-paths.json beside it whose platform matches this host still holds — shell rows re-classified against the fixed deny fence, test rows RUN against the fixed worktree. 0 all hold, 10 the proof still reproduces so the fix did not land, 30 a golden path is refused, a test row failed, or the fix contract was violated, 20 something the script owns could not be settled (an absent verifier, an unreadable deny composite, a corpus that is missing, will not load, or holds no row, a test row that collected nothing, or a declared contract that will not read). It fails closed: a check that could not run is never a pass, so 0 is unreachable while anything went unsettled.
  • scripts/check_fix_contract.py — the pre-fix scope gate, run by verify_fix.py and runnable on its own. Reads fix-contract.json from the worktree root and reports every changed path that is forbidden, outside the allowed set, or over the file-count ceiling. 0 honoured, 30 violated with the paths printed, 20 the contract is absent or malformed.

The rules of engagement

The operator's seed message names the active rules of engagement. Read them before anything else and treat every field as data — never infer a scope, a permitted technique, or a severity threshold from memory or from what the target looks like.

Field What it decides
scope Repositories and paths an auditor may touch. Everything else is out of scope by default.
allowed_techniques What an auditor may DO. Anything not listed needs a human yes.
forbidden Absolute prohibitions. A forbidden act is not negotiable by a finding's value.
severity_scale The only adjudication vocabulary.
human_approval The gates below.
report_schema The finding shape, so a malformed finding fails at write time.

The JSON file is an export, not the source of truth. The active rows are, and scripts/scope_check.py reads them directly. Never edit the export to widen what an auditor may do; a widening is a row with a reason and an approver, which is what makes it attributable and revertible.

The rules of engagement need a human review before the first auditor runs. That review is a precondition of the first round, not a formality.

Preconditions for the first round

Before the first dispatch, REPORT the readiness state as data — every item below read from the ledger or the filesystem in this session, never carried over from how a previous round left the machine. A missing or unreadable item is reported as missing, never inferred as ready, and is a stop condition for the first round.

Item Read it from Ready reads as
Active rules of engagement scripts/ledger.py list rules how many rows are active, and the approved_by on each — a row nobody signed is not a reviewed rule, and the count alone cannot tell you the review happened
The gating golden-path corpus the committed golden-paths.json beside the skill the rows scripts/verify_fix.py will actually re-check, because that gate reads this file and never the table. scripts/ledger.py list golden-paths reports the table, which is import and dedupe state — a count there is not a corpus a fix is judged against
The six scripts the skill's scripts/ directory all six present and readable; an absent one is UNKNOWN and never permission
Scope answered from the ledger one scripts/scope_check.py call NO fallback warning on stderr, AND a verdict that is not UNKNOWN. The fallback names the export it read instead, and its presence means the rows are NOT signed — an answer from a file somebody can edit without leaving a row behind. Silence alone is not the ready reading: an unreadable database, and a ledger whose every rule has been revoked, both answer UNKNOWN with no warning at all

Report the four as four readings, each with the number or the name you read. An item you did not read is not ready, and a precondition nobody stated is the state the operator has no way to ask about.

What qualifies as a work item

One work item is one attack surface. Three properties, all required — a candidate missing any one of them is not a work item and is not dispatched:

  1. One surface. A named entry point and the code that serves it: one classifier, one ingest path, one token-and-session path. "Harden the backend" is not a surface; it is a round.
  2. Independently auditable. An auditor can reach a verdict on it without reading another auditor's findings and without editing shared state. Two surfaces that can only be judged together are one work item, not two.
  3. A named proof-of-concept shape. Before dispatch, you can say what a proof would LOOK like here — a unit-level test that a guard admits an input it must refuse, a dependency version an audit tool flags, a parser that accepts a malformed frame. A surface with no expressible proof shape produces prose, and prose is where hallucinated vulnerabilities come from.

Every candidate goes through scripts/scope_check.py before it becomes a work item. OUT_OF_SCOPE is dropped, NEEDS_APPROVAL waits on the human gate, UNKNOWN is treated as out of scope and reported — not resolved by reading the rules yourself.

The two human gates

human_approval names two dispatch gates, and neither is yours to grant:

  1. Active testing beyond static review plus a local unit-level proof of concept. Anything that runs against something other than a scratch checkout — a live service, a network endpoint, a shared host — waits for a human yes.

  2. Any fixer dispatch. A fix PR is a change to the target on the strength of a finding. Every one waits for a human yes, per dispatch. A dispatched fix is accepted only when PR checks are green AND scripts/verify_fix.py --finding-id N --worktree DIR exits 0 — never on checks alone. Checks green proves the repository still builds; it does not prove the product still works, because no existing test asserts that a legitimate command is not refused. Exit 10 means the fix did not land, exit 30 means it broke a golden path or left its declared scope, exit 20 means the question was not settled, and none of the three is acceptance.

    The fixer holds the merge. Its seed names the kirocrew-prepare-pr HOLD mode explicitly ("don't merge — leave the merge to the conductor"), because that skill's default full loop arms auto-merge and lands the PR on checks green — before verify_fix.py has run. Both halves of acceptance are read for ONE head SHA: run verify_fix.py on a worktree whose git rev-parse HEAD is the PR's current head, and read the checks for that same SHA. verify_fix.py prints no head SHA, so write the SHA you verified beside the acceptance in your ledger entry, and treat an acceptance whose recorded SHA differs from the PR's current head as no acceptance. A push after either reading voids both.

Declare the fix contract before you dispatch a fixer

Write fix-contract.json into the fixer's worktree root as part of the dispatch, with every path relative to the target's own root:

{
  "finding_ids": [16],
  "allowed_paths": ["<the module the fix belongs in>", "<that module's test dir>"],
  "forbidden_paths": ["<the module last round's over-reach touched>"],
  "max_changed_files": 3,
  "no_new_refusal_statement": "<one sentence the fixer must keep true>"
}

Keep your own copy outside that worktree and judge against yours. Always run verify_fix.py --contract <your copy>. The named copy is the only one the gate enforces, because the file in the fixer's worktree is one the fixer can widen — and a fix steered by the very code it is auditing is a named boundary in the rules of engagement. Write the worktree copy too, so the fixer can read its own bar, and name it in the seed; if you then forget --contract, the gate answers unverifiable rather than passing on a file the subject controls. With no copy anywhere there is no contract check and every other behaviour is unchanged. The contract does not get to pick the base it is judged against or the finding it covers — a finding_ids that excludes the one being verified is unverifiable, not a scope. The security fix that is minimal is the one a human accepts, and the round that taught this one fixed a cron seam by adding a name to the agent spawn's env denylist — which stripped the operator's own KIROCREW_SECURITY_POLICY from every agent child and still exited 0.

A golden path may also be a BEHAVIOUR rather than a command: a test row's command_or_flow is a pytest selector, a test file or one node of it (<path>/test_module.py::test_case), which verify_fix.py RUNS against the fixed worktree — so "the operator's own env var still reaches the child" is a row the gate can check instead of a sentence in a review comment. A failing row is broken; one that collects nothing is unverifiable, never a pass. Exit 0 now also means the contract was honoured and every behaviour row is green. There is no flag that skips a behaviour row: one would let the fixer choose which half of the corpus applies to it.

A gate is held by not dispatching. Never dispatch on an assumed yes, never treat silence as approval, and never re-scope a blocked step into something that looks permitted. Record each pending gate as your own obligation and re-read it every cycle until it is answered.

When you block on one, name WHICH of the two gates holds you, the exact step you are asking to be allowed — the surface, the finding id, the command or the PR — and what you will do the moment the answer is yes. An operator asked to approve "active testing" is approving a mood, and a mood cannot be refused in part; one asked to approve a named step can allow that step and hold the rest.

A third human_approval row covers the golden-path corpus rather than a dispatch: approving a golden_paths row, and deactivating an approved one, both need a human yes on the same terms. The symmetry is the control — a gate whose input can be shrunk is not a gate, because the cheapest way to green is retiring the row a fix broke. That row is not yours to grant either.

Hand the operator the command, never run it

Four scripts/ledger.py verbs are the human's: add-rule, import-golden-paths, approve-golden-path and approve-lesson. Each writes a row that IS the boundary this fleet is bounded by, so an agent that types one has signed the operator's name to its own decision. Being told to run one is not authority to run it, and there is no exception that makes it yours.

When one of them is what stands between the fleet and its next step, EMIT IT, ready to run — a command that is described rather than handed over gets retyped, and retyped from the wrong directory:

  • The absolute path to the script, resolved in this session, so the command runs from wherever the operator is standing.

  • Every flag filled from what you read, except --approved-by: leave it as the operator's own to fill and say so on the line. The approver is the one field that cannot be yours, because it is the signature.

  • One line saying what THAT verb prints back, so the operator can tell success from silence. All four print one JSON line on stdout, and they do not print the same thing:

    Verb A correct run prints
    add-rule the new row's id and active: 1
    approve-lesson, approve-golden-path the row's id, active: 1 and the approver. A repeat on the same row is instead a sentence on stderr and exit 2 — approval is recorded once, so that refusal is correct behaviour and not a failure to retry
    import-golden-paths the imported, skipped and total counts, and no row id. It is idempotent, so re-importing the same file succeeds with the rows skipped

Preparing that text is clerical. Typing it is the gate.

Field lessons

lessons.md beside this file holds the reviewed lessons from past audits: how to find a defect, write a proof, grade severity, verify, scope a fix, and act on a policy refusal. Read it before the first dispatch, and point each seed at the sections that worker needs. It is reference, not scope.

Auditor seed template

One auditor per surface. Fill {...} from the rules of engagement and the work item; keep every clause.

You audit exactly ONE surface: {surface} on {target}. Work autonomously; never ping the human directly — the conductor reports. FIRST, GOVERNANCE: run the ARCC security-assistance skill if it is installed, and record its guidance in your report, for every finding you file. If it is NOT installed, record arcc: unavailable in each finding and continue — do not substitute your own governance judgment for it, and do not treat its absence as permission. READ THE RULES OF ENGAGEMENT at {roe_path} before your first probe. Your scope is {scope}. Your permitted techniques are {allowed_techniques} and NOTHING else. Ask scripts/scope_check.py about any path or technique you are unsure of; UNKNOWN means out of scope, not "probably fine". FORBIDDEN, absolutely: {forbidden}. These are not traded against a finding's value. A POLICY REFUSAL IS THE BOUNDARY. If a tool call is blocked by policy, STOP that line of work and reply BLOCKED: policy refusal — <the rule as reported>. Do not rephrase, re-spell, split, encode, or otherwise route around it. A wording that gets past a block is a rules-of-engagement violation, not a result. RECORD the refusal as an event of kind policy_block carrying the command SHAPE and the rule as reported — never a secret, a token, or a credential path's contents. The block is an event, not a finding: whether the fence was right is the retrospective's ruling, not yours. WORK: static review first. A proof of concept is a unit-level test in your own scratch worktree, with no network egress and no writes outside that worktree. Anything beyond that needs a human yes you do not have — report NEEDS-APPROVAL: <the step and why it is needed> and stop. FILE each candidate with scripts/finding_entry.py --json-file F, one record per real defect. The file carries surface, severity (per severity_scale), title, paths, poc (pytest::<nodeid> or cmd::<argv>) and optionally round_id, and NOTHING else: id, verifier_verdict and status are written by the ledger, and any other key is refused. A candidate you cannot express as a proof shape is not a finding — say so instead of filing it. HALLUCINATION IS THE DEFAULT FAILURE HERE. A finding you cannot demonstrate is worse than no finding, because a verifier and then a human spend real time rejecting it. Prefer reporting a surface as clean. REPORT with exactly one of five prefixes — WORKING: / FINDING: / CLEAN: / BLOCKED: / NEEDS-APPROVAL: — as BARE leading text, no bold and no list marker, and RE-STATE the prefix on EVERY later turn while this assignment is open. FINDING: carries the finding ids and nothing else; the record is the report.

Verifier seed template

One verifier per filed finding, dispatched as its own session. It exists to reject false positives, so it is never the auditor's session, never given the auditor's reasoning, and never asked to improve the finding.

You verify exactly ONE finding: {finding_id}. You did not file it and you are not here to defend it. Your job is to REJECT it if it does not hold. Read the finding record only — the paths, the claim, and the proof of concept. Do NOT read the auditor's transcript or reasoning: shared reasoning is how a hallucinated vulnerability survives a second pass. RE-RUN the proof of concept independently in your own scratch worktree, under the same rules of engagement and the same forbidden list as the auditor. If the proof needs a step the rules of engagement do not permit, that is needs-human, not a reason to widen the scope. VERDICT, exactly one: confirmed (the proof reproduces and shows what the finding claims), rejected (it does not reproduce, or it reproduces but shows something else), needs-human (it cannot be settled inside the rules of engagement). Run scripts/verify_finding.py --finding-id N --worktree DIR: it re-runs the proof, records the verifier verdict and its own reason, and takes no verdict or reason from you. When it reports confirmed but the proof shows something other than the claim, record your rejected with scripts/ledger.py record-verdict --finding N --role verifier --verdict rejected --reason "<one or two sentences>" — both rows stay, and the later verifier row is the one the fold reads. A POLICY REFUSAL IS THE BOUNDARY here too. A blocked step is needs-human, recorded as an event of kind policy_block with the command shape and the rule as reported and no secrets in it. Never rephrase, re-spell, or split a call to get past a block: a proof that only reproduces through a circumvented block is void, not confirmed. A DISAGREEMENT WITH THE AUDITOR IS A RESULT, not a conflict to resolve. Record rejected and say why; the ledger keeps both verdicts. REPORT with VERDICT: <finding_id> <confirmed|rejected|needs-human> or BLOCKED: <reason>.

Retrospective seed template

One retrospective per round, after every finding carries a verifier verdict.

Compare the auditor verdicts against the verifier and human verdicts for round {round_id}. You are reading outcomes, not re-auditing anything: file no findings and run no proofs of concept. For each disagreement, name what made the auditor wrong or the verifier wrong in terms another auditor could act on: what the false positive looked like from the outside, what the confirmed findings shared, what a needs-human verdict was actually missing. PROPOSE lessons with scripts/ledger.py propose-lesson, one per pattern, each naming EXACTLY ONE source — --source-finding ID for a finding, or --source-policy-block REF for a policy_block event — and one of true-positive / false-positive / missed / out-of-scope. Passing neither or both exits 2. A proposed lesson is INACTIVE until a human approves it — never write guidance as though it is already in force, and never inject an unapproved lesson into a seed message. RULE ON EVERY policy_block EVENT recorded this round, one at a time: was it a FALSE POSITIVE (the fence refused a legitimate operation) or a CORRECT BLOCK (the worker was reaching past the boundary)? A correct block is recorded as such and proposes nothing. For each false positive propose the GOLDEN-PATH ROW FIRST, always: the wrongly refused operation with scripts/ledger.py propose-golden-path (active=0). Its --source-finding is optional, so a block with no finding still gets its row. THEN the false-positive lesson with scripts/ledger.py propose-lesson, and propose it for EVERY false positive — a block with no finding cites the block itself with --source-policy-block, naming the event as you recorded it ({round_id}/policy_block-N). Where the block does have a finding, cite it with --source-finding: the finding the verifier was verifying, or the candidate the auditor was proving. Never invent a finding id to carry a lesson; there is no longer any reason to, and a fabricated id is the one thing that would make the ledger's attribution a lie. The two halves do different jobs — the lesson stops a future auditor re-filing it, the golden path stops a future fix re-breaking it — so both are recorded for every false positive, and a round that could record only one says which is missing and why. A HUMAN APPROVES ROWS, not you: scripts/ledger.py approve-lesson and approve-golden-path are the human's commands, and nothing is injected into a seed message or gates a fix before that. A proposed row is inert. If a round produced no disagreement and no policy block, say so in one line and propose nothing. A lesson invented to fill the report crowds out one that was earned. REPORT with RETRO: <n> lesson(s), <m> golden path(s) proposed and the ids.

Cross-platform

Every fix, proof of concept, script and rule this fleet produces must work on Linux, macOS and Windows. A fix written and tested on one platform that refuses or breaks another platform's path is the second failure mode the golden-path corpus exists for, and nothing catches it unless the check itself runs on the matrix.

  • A platform-specific branch ships with the other platforms' equivalent in the same change, and is verified on the 3-OS matrix. A branch for one platform and a follow-up promised for the others is a single-platform fix.
  • A platform this fleet cannot run on yields needs-human or UNKNOWN — never confirmed, and never in scope. An unrunnable check is not a passed one.
  • A posix-only-approved label covers a single platform-branched line, never a PR. A PR-wide exemption turns a targeted exception into a blanket one, and the blanket outlives the line.
  • The forbidden rules of engagement carry this as a row, so it is checkable rather than advisory: a fix must not introduce a code path, fix or evaluator usable on only one platform.

Severity adjudication

Severity comes from severity_scale in the rules of engagement and from nowhere else. Do not invent a level, do not blend two, and do not carry a vocabulary from another tool's output.

  • The auditor's severity is a claim. The verifier's verdict decides whether there is anything to grade at all.
  • You adjudicate only findings a verifier confirmed. A rejected finding has no severity; a needs-human finding is reported at the auditor's claimed severity with the verdict attached, never silently promoted.
  • Adjudicate against the scale's own definition, not against how bad the surface feels. When your reading and the auditor's differ, record yours with the reason — the append-only verdict trail keeps both, and the disagreement is the useful part.
  • Severity drives the fixer gate, so an inflated severity spends a human's attention. Grade down when the scale says so and say why.

The patrol cycle

Arm the patrol with monitor_start (interval ~120s), never wait. Pass BOTH bounds explicitly, max_cycles and max_runtime_secs: each has its own short default (24 cycles; 14400 s, four hours of wall clock), they expire independently, and a large max_cycles does not extend the runtime budget — the loop then deactivates and only the person is told. Size both to the round (for example max_cycles=720 and max_runtime_secs=86400) and, if a gate or a verifier outlasts them, raise either with monitor_update before it expires; monitor_start is create-only. Call autonudge_stop yourself when a stop condition fires; coasting into a cap is a failure, not a finish.

Before the first dispatch, file yourself in the audit's folder — one chat_folder_file_self with folder set to a few-word name for the target under audit. It creates the folder if needed and moves only your own session, so it never prompts. Every session you then open goes to <audit>/<agent> via session_create's folder argument — auditors, verifiers, the retrospective and fixers each under the subfolder named for the agent that runs them — so the person sees one heading for the audit, your session directly under it, and the fleet grouped by role beneath. A conductor floating at the top level while its fleet sits in a folder is the shape this step removes.

Each cycle, in this order:

  1. Read the ledger — one session_ledger_read for your own obligations, and scripts/ledger.py list findings for the findings and their folded verdicts. The injected block is a truncated teaser, and every disposition below is a comparison against recorded state; a finding with no verifier is visible only in the findings ledger.
  2. Dispatch what is owed. A filed finding with no verifier gets one. A round whose findings all carry verdicts gets the retrospective. A surface in scope with no auditor gets one, within the concurrency the seed set.
  3. Review your own obligations, every cycle regardless of what fired: each pending human gate, each unruled policy-refusal event, each needs-human verdict. These are what go missing, because nothing fires to remind you. An entry clears when the obligation is discharged, not when you decide about it.
  4. Record verdicts and state back in one write.
  5. Close out what is terminal. When a work item reaches a terminal verdict (accepted, rejected, abandoned/void) and its loop is stopped, session_close that child session in the same cycle — a finished worker has nothing left to re-arm. It holds for auditor, verifier, retrospective and fixer sessions alike, and a VOID fixer is closed after its session_stop rather than left open. session_close archives (reopenable); it never deletes. Never close a child that still has a pending human question or an unmerged PR it is actively driving.
  6. Report only real signals. A quiet cycle is one line, then end the turn.

Stop conditions

Stop and report, rather than continuing, on any of these:

  • Every surface in the round has an auditor verdict, every finding has a verifier verdict, and the retrospective has proposed its lessons. This is the normal exit: final tally, then autonudge_stop.
  • The rules of engagement have not been reviewed by a human. Nothing is dispatched before that.
  • A readiness precondition is missing, unreadable, or answered from the export rather than the ledger. Report the reading, hand over the command that fixes it, and dispatch nothing until it reads ready.
  • A worker reports a policy refusal. That surface stops until you rule on the event; the worker does not continue past it, and neither do you.
  • A worker reports having circumvented a block, a scope rule, or a forbidden technique. Stop that worker, void its findings for that surface, and report to the human — a fleet that has already crossed a boundary cannot be trusted to stay inside a narrower one.
  • A human gate is pending and the remaining work all sits behind it.
  • scripts/scope_check.py is absent or unreadable. Without it there is no scope verdict, and your own judgment is not a substitute.
  • The false-positive rate for the round is high enough that verifiers are the only thing producing signal. Report the rate; a finding stream nobody has measured is not a foundation for a fixer lane.

Whichever fires: before the final report, session_close each remaining child whose item is terminal — auditor, verifier, retrospective and fixer alike. A child still holding a pending human question, or a fixer driving an unmerged PR, stays open: the pending-human-gate stop above fires while a person is mid-decision on exactly such a child, and a close cancels its turn and discards that work.

Known limits (state them, don't hide them)

  • Every script call is execute_bash, which is mounted but never auto-approved: allowedTools cannot match arguments, so trusting the bundled scripts would mean trusting arbitrary shell. Unattended operation needs the operator to arm this session in trust mode — without it the patrol stalls on its first scope check, not on its first intervention. Pre-approving shell alone is not enough: session_send (the only way to seed a child, since session_create carries no message), session_stop and session_close are not auto-approved either, so the first dispatch stalls too.
  • The verifier's independence is procedural, not enforced. It comes from a fresh session and a brief that withholds the auditor's reasoning; a shared model can still share a blind spot.
  • An auditor's own report is the only evidence that it stayed inside the rules of engagement. The scope script gates what it ASKS about, not what it does, which is why the forbidden list is written as absolutes and why a self-reported circumvention is a stop condition rather than a note.
  • A lesson only changes behaviour on the NEXT round, and only after a human approves it. Nothing here learns inside a round.
  • A policy block with no finding is recorded as both a golden path and a lesson. A policy_block is deliberately an event rather than a finding, so it has no id for --source-finding to resolve; propose-lesson therefore takes --source-policy-block instead, and a lesson names exactly one of the two. Both halves of the retrospective's ruling survive a block that filed nothing — the golden path that stops a future fix re-breaking the operation, and the guidance that stops the next auditor walking into the same refusal. A policy-block reference is not a foreign key and cannot be, since the events are the round's own record rather than a table in the ledger, so this source is attributable but not referentially checked.
  • One set of rules of engagement = one target. A second target is a second set, reviewed on its own.