euthyna 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,116 @@
1
+ # Stage A: dependency audit
2
+
3
+ > This audits "what you depend on", not "your own code". Answer: **what risk does this newly introduced / upgraded dependency bring?**
4
+ >
5
+ > Prerequisite: the three non-skippable rules in `../SKILL.md`. **This stage never reads a dependency's source.**
6
+
7
+ ---
8
+
9
+ ## A.0 What this stage does and does not do
10
+
11
+ | Does | Does not |
12
+ |---|---|
13
+ | verify manifest files and lockfiles, resolve exact versions | judge whether the project can install or build (**never installs, never builds, never executes**) |
14
+ | fetch vulnerabilities, staleness, and maintenance status with deterministic tools | license compliance audit |
15
+ | translate tool output into judgments | scan the target's own source for vulnerabilities or secrets (that is Stage B / Stage C) |
16
+ | state clearly which criteria are **not evaluable** | improvise for ecosystems the collector does not support (say "not supported", do not force it) |
17
+
18
+ **This stage only reads registries, advisories, and repository metadata.**
19
+
20
+ ---
21
+
22
+ ## A.1 Why not build your own measurement tools
23
+
24
+ This framework **deliberately does not reimplement** dependency scanning. Not out of laziness:
25
+
26
+ 1. Mature tools already cover this; rewriting only introduces new risk, and the measurement logic itself must be deterministic
27
+ 2. Vulnerability data sources (OSV and others) are continuously updated; a self-maintained copy is guaranteed to go stale
28
+ 3. **This stage's value is in the judgment, not in the measurement**
29
+
30
+ So this stage's producer is an **orchestration layer**: it calls off-the-shelf tools, normalizes their output into facts, and marks what was not measured.
31
+
32
+ Available discovery sources (use whichever is installed — **audit the source before installing**):
33
+
34
+ | Purpose | Candidates |
35
+ |---|---|
36
+ | dependency vulnerabilities (with fixed versions) | any OSV-based scanner |
37
+ | lockfile and manifest parsing | built into the tool, or package-manager commands such as `npm ls` / `pnpm why` |
38
+ | introduction time (which commit added it) | `git log --oneline --follow -- <lockfile>` |
39
+
40
+ > ⚠️ **Load detection and fallback**: the process must still run to completion when **none** of these tools are installed.
41
+ > On fallback, mark the criterion "not evaluated" truthfully — **do not** fill the gap with CVE data from model memory:
42
+ > that is exactly what this framework forbids.
43
+
44
+ ---
45
+
46
+ ## A.2 Step one: confirm the inputs
47
+
48
+ **The target directory must have one of** `package.json` / `pyproject.toml` / `requirements*.txt` / `go.mod`.
49
+ If none exists, **say so and stop** — do not improvise an audit for an ecosystem the collector does not parse.
50
+
51
+ **Exactly resolvable**: `package-lock.json`, `npm-shrinkwrap.json`, `uv.lock`, `go.mod` for Go 1.17+.
52
+
53
+ **Not resolvable**: `yarn.lock`, `pnpm-lock.yaml`, `poetry.lock`.
54
+ When these are present, **say so**, fall back to the pinned or latest published version, and mark it as an approximation.
55
+
56
+ **Check the network identity**: the unauthenticated GitHub API allows only 60 requests/hour. The collector issues several requests per dependency;
57
+ unauthenticated, repository-class criteria will be broadly not evaluable — **say so, instead of silently degrading or retrying to death**.
58
+
59
+ ---
60
+
61
+ ## A.3 Three states, not two
62
+
63
+ Every dependency, every criterion, lands in exactly one of three states:
64
+
65
+ | State | Meaning |
66
+ |---|---|
67
+ | evaluated · clean | actually checked, and no problem under this criterion |
68
+ | evaluated · flagged | actually checked, and there is a problem |
69
+ | **not evaluable for a stated reason** | could not be checked — **the reason must be written down for this item** |
70
+
71
+ **A missing measurement is never a clean conclusion.**
72
+
73
+ When the collector exits non-zero, it is **refusing to report** — **quote its message verbatim; do not retry, do not work around**.
74
+ A run that measured nothing produces "not evaluable", **not** "no issues found".
75
+
76
+ ### Quoting discipline
77
+
78
+ - "no advisories" only means "none **within the evaluated scope**" — look at the coverage table before citing a clean conclusion
79
+ - **numbers must be quoted verbatim** — no re-deriving, no rounding, no dressing up
80
+ - **finding nothing is not an endorsement**
81
+
82
+ ---
83
+
84
+ ## A.4 What the model can add and the collector cannot
85
+
86
+ Measurement belongs to the tools; judgment belongs to the model. This is the value the model should add:
87
+
88
+ | Add | Caveat |
89
+ |---|---|
90
+ | which one **this reader should act on first** | order by risk, not alphabetically |
91
+ | upgrade path | patch or major? a major version means breaking changes |
92
+ | replacement candidates for deprecated dependencies | **verify the candidate actually exists in the registry before naming it**, and mark this as judgment, not measurement |
93
+ | install-script risk | can the flagged install script be avoided with `--ignore-scripts` |
94
+
95
+ ---
96
+
97
+ ## A.5 Report style
98
+
99
+ This stage's reports are often **pasted into tickets verbatim**, so:
100
+
101
+ - no person, no abbreviations, no exclamation marks
102
+ - active voice with **the matter as the subject**: write "upgrading to 1.19.0 clears all 25 advisories", not "I recommend upgrading axios"
103
+ - no intensifiers (very / significant / fortunately)
104
+ - **no guesses about motives**
105
+ - tense: audit actions in past tense, dependency state in present tense, consequences in future tense
106
+ - recommendations state only **the action and its cost** — **no naming of responsible parties**
107
+
108
+ ---
109
+
110
+ ## A.6 Handoff to Stage C
111
+
112
+ Issues reported by the dependency audit usually do **not** need Stage C — their evidence comes from vulnerability databases and is already determinate.
113
+
114
+ But there is one class of exception: **"is this vulnerability actually reachable in this project"**.
115
+ If the affected function of a dependency vulnerability has no call path here at all, that is an **assertion to be adjudicated**,
116
+ not an established fact. When you meet this, go to Stage C.
@@ -0,0 +1,129 @@
1
+ # The fact contract (adjudication-layer consumption guide)
2
+
3
+ > This is a **consumption guide**, not a design document. It answers: after receiving a fact, what can the adjudication layer infer from it, and what can it not.
4
+ >
5
+ > The full contract is in the repository's `docs/fact-contract.md`.
6
+
7
+ ---
8
+
9
+ ## What a fact looks like
10
+
11
+ ```jsonc
12
+ {
13
+ "id": "fact-007",
14
+ "kind": "history", // fact kind
15
+ "statement": "This change deleted 4 lines of code from commit ab877f9d70...",
16
+ "status": "established", // established | refuted | unknown
17
+ "evidence": { "file": "src/a.js", "line": 104, "commit": "ab877f9d70" },
18
+ "method": "command",
19
+ "command": "git blame --porcelain -L 104,104 <rev> -- src/a.js",
20
+ "confidence": "exact", // exact | approximate
21
+ "detail": { } // kind-specific structured payload
22
+ }
23
+ ```
24
+
25
+ ---
26
+
27
+ ## The three consumption rules
28
+
29
+ ### Rule 1: `unknown` is not "clean"
30
+
31
+ The three states are a hard convention:
32
+
33
+ | status | What the adjudication layer can do |
34
+ |---|---|
35
+ | `established` | May draw conclusions from it |
36
+ | `refuted` | May draw conclusions from it (in reverse) |
37
+ | **`unknown`** | **Nothing can be inferred**. It is neither support nor opposition — it is "don't know" |
38
+
39
+ Treating `unknown` as "no problem" is the only usage error in this contract that is fatal.
40
+
41
+ ### Rule 2: `approximate` may not participate in gates
42
+
43
+ Facts with `confidence: approximate` are **explicitly excluded from gate adjudication**.
44
+
45
+ This is not conservatism — it comes from practice: some tools synthesize an approximate result when "scope cannot be determined",
46
+ and explicitly mark it as **not participating in blocking even in enforcement mode**. This framework follows the same convention.
47
+
48
+ Gates only consume `exact`.
49
+
50
+ ### Rule 3: when data is missing, read the coverage table first
51
+
52
+ The report's `coverage.notEvaluated` lists **what was not measured, and why**.
53
+
54
+ **Before citing any "clean" conclusion, read this section first.**
55
+ When `notEvaluated` is non-empty, do not read it as "tested, no problem".
56
+
57
+ ---
58
+
59
+ ## What facts do **not** contain (this is design, not an omission)
60
+
61
+ | Not present | Why |
62
+ |---|---|
63
+ | Scores (score / 0–100) | Scores cannot be recomputed; they would become "using a score as a gate" |
64
+ | Severity levels | Severity is a **judgment**, not a fact |
65
+ | Fix suggestions | Same as above |
66
+ | "may" / "probably" | Weasel words mask gaps |
67
+
68
+ **Measurement stays measurement; judgment stays judgment.** If a fact carries `severity: high`,
69
+ it means the layer that produced it already made the decision for the adjudication layer — and that decision cannot be recomputed.
70
+
71
+ ---
72
+
73
+ ## How to read each fact kind
74
+
75
+ ### `history` — the origin of deleted code
76
+
77
+ | Field | How to read it |
78
+ |---|---|
79
+ | `detail.classification` | `security` / `fix` / `none`. **The classifier's two error costs are asymmetric**: missing a security commit is more dangerous than over-reporting, so it is biased wide |
80
+ | `detail.byFile` | **How many lines per file**. `blamedLines` is the total and may span multiple files — do not assume the file in the evidence contains everything |
81
+ | `detail.commitSubject` | The commit message verbatim. **Do not look only at the classification; read the message itself** |
82
+ | `command` | A directly executable reproduction command, with the real line ranges |
83
+
84
+ **Note**: `history` only says "which commit this line comes from and whether that commit looks like a security fix";
85
+ it does **not** say "deleting it is a vulnerability". The latter is the adjudication layer's business.
86
+
87
+ ### `reintroduction` — removed and added back
88
+
89
+ A non-`null` `detail.securityOrigin` means: the line's occurrence count was once changed by a commit whose message contains security keywords.
90
+ Combined with "the line does not exist in the base revision", you can infer **it was removed before and has now been added back**.
91
+
92
+ **This does not equal "this is a regression"** — it equals "someone needs to look here". The adjudication layer decides from this whether to raise priority.
93
+
94
+ ### `test_coverage` — invocation counts
95
+
96
+ ⚠️ **This kind can only falsify, never confirm.**
97
+
98
+ | Fact | What it lets you infer |
99
+ |---|---|
100
+ | Invocation count = 0 (`status: established`) | ✅ **Really never invoked**. All behavior depending on it was never exercised |
101
+ | Invocation count > 0 (`status: unknown`) | ❌ **Cannot** infer "executed". Entering a function does not equal any specific call site having run |
102
+
103
+ Reason: the underlying coverage data reports **call sites that never executed as covered** (straight-line code after a thrown exception is the classic case),
104
+ so the "covered" direction is unreliable in itself. **The producer has no "executed" output at all** — this is deliberate.
105
+
106
+ Corollary: **do not** use "non-zero count" to close a "not tested here" concern. To prove something actually executed,
107
+ only a demonstration that truly reaches that call site can do it.
108
+
109
+ ### `artifact` — artifact evidence
110
+
111
+ The report file, plus the exit code and case count of the demonstration run. **Gates 1 and 4 rely on it.**
112
+
113
+ How to read it: the exit code and case count must be **actually captured output**, not expected values.
114
+ A "pass" with 0 cases is not a pass.
115
+
116
+ ---
117
+
118
+ ## Producer exit codes
119
+
120
+ The accompanying CLI's exit codes are an external contract, usable directly in CI:
121
+
122
+ | Code | Meaning | How the adjudication layer should react |
123
+ |---|---|---|
124
+ | `0` | Measured, no security-related findings | Continue normally |
125
+ | `10` | Measured, and at least one `security`-classified fact exists | **Raise priority**; do not treat it as a conclusion directly |
126
+ | `1` | Usage error | Fix the command |
127
+ | **`2`** | **Could not measure at all** | **Must not be read as clean**. Either switch measurement methods or mark the relevant criteria as not evaluable |
128
+
129
+ The difference between `2` and `0` is the essence of why this contract exists: **failing to measure and measuring clean are two different things.**
@@ -0,0 +1,258 @@
1
+ # Fact producers: when to run them, how, and how to read the output
2
+
3
+ > This skill ships two **deterministic measurements**, provided by a zero-dependency Node CLI.
4
+ > They answer questions the model cannot compute accurately — **not** "what vulnerabilities exist".
5
+ >
6
+ > This page is the operations manual: when you must run them, how to write the commands, and which section of the output decides what conclusion you may draw.
7
+ > For what you can and cannot infer from a fact once you have it, see `fact-contract.md`.
8
+
9
+ ---
10
+
11
+ ## 1. First, where the commands live
12
+
13
+ The CLI is **not in the skill directory** — it is a separate program of this project. In order of availability:
14
+
15
+ | Situation | How to write it | Notes |
16
+ |---|---|---|
17
+ | Installed | `euthyna history …` | `euthyna` is the command name declared by the package |
18
+ | Project checkout available | `node <euthyna repo>/bin/euthyna.js history …` | the most common form |
19
+ | Neither | — | **mark the criterion as "not evaluable"**; do not substitute a manual estimate |
20
+
21
+ ### ⚠️ A real pitfall: relative paths only work inside the euthyna repository
22
+
23
+ `node bin/euthyna.js …` only works **when the current directory happens to be the euthyna repository**.
24
+ When auditing, your current directory is the **audited project**, so:
25
+
26
+ ```
27
+ Error: Cannot find module 'D:\axe-core\axe-core\bin\euthyna.js'
28
+ ```
29
+
30
+ `history` has `--repo` — pass the repository path explicitly and it runs from any directory:
31
+
32
+ ```powershell
33
+ node <euthyna repo>/bin/euthyna.js history --base <rev> --repo <audited repo>
34
+ ```
35
+
36
+ - `--repo` refers to the **audited repository**, not the euthyna path
37
+ - `--base` / `--head` both resolve relative to the **audited repository**, independent of the current directory
38
+ - the coverage command has no `--repo` — write the coverage file as an **absolute path**
39
+
40
+ Both commands were verified: run from `D:\` with `--repo` pointing at another repository, exit codes matched expectations.
41
+
42
+ ---
43
+
44
+ ## 2. `history` — the origin of deleted code
45
+
46
+ ### When you must run it
47
+
48
+ | Trigger scenario | Corresponding stage |
49
+ |---|---|
50
+ | Reviewing a change (PR / diff / commit) where **code was deleted** | Stage B |
51
+ | The user asks "did this change weaken some protection" | Stage B |
52
+ | You need to determine whether some code was "removed and added back" | Stage B |
53
+
54
+ **If the diff contains deleted lines, run it first.** This is not optional: a diff that looks "very clean" because a check was deleted
55
+ is exactly what a security regression usually looks like, and a model cannot blame hundreds of deleted lines one by one and then trace back through commit history.
56
+
57
+ ### Commands
58
+
59
+ ```powershell
60
+ # basic: which commits do this change's deleted lines come from, and do those commits look like security fixes
61
+ node <euthyna repo>/bin/euthyna.js history --base <pre-change revision> --repo <audited repo>
62
+
63
+ # additionally check for "removed and added back" lines (has a probe cap; slower than the basic version)
64
+ node <euthyna repo>/bin/euthyna.js history --base <pre-change revision> --pickaxe --repo <audited repo>
65
+
66
+ # attribute deleted lines to the commit that "originally introduced" the content (not blame's "last modifier"; has a probe cap)
67
+ node <euthyna repo>/bin/euthyna.js history --base <pre-change revision> --origins --repo <audited repo>
68
+ ```
69
+
70
+ `--base` is the **pre-change revision** (the PR's fork point); `--head` defaults to `HEAD`.
71
+ When the fork point is uncertain, use the result of `git merge-base <target branch> HEAD`.
72
+
73
+ ### Attribution semantics (two inherent approximations; established ≠ exact)
74
+
75
+ `history`'s attribution and classification each carry a **hard-coded approximation** — know them before reading the output:
76
+
77
+ 1. **Attribution: blame is "last modified", not "originally introduced".** If a security-fix line was later touched by formatting/refactoring (moved, copied), blame attributes the origin to the last commit that touched it. `--origins` uses `git log -S <line content>` to locate the commit that **originally introduced** the content; when the two differ, the fact writes out both commits (`detail.blameCommit` = last modified, `evidence.commit` = originally introduced) and states so. `--origins` only traces lines ≥ 12 characters long (for overly generic lines, `-S` would point at the first commit of the entire history, which is not the origin); when it is not enabled, the output states this under "criteria not evaluated".
78
+ 2. **Classification: look at the commit message first, then the commit diff, then the deleted line itself.** When the message is vague ("update utils") but the diff touches a dangerous API, or the deleted line is itself security code (e.g. `if (!authorized) return`), it is marked `security` — the basis is recorded in `detail.classificationBasis` (`message` / `diff` / `deleted-line`). The deliberately wide bias is intentional: missing a security commit is more dangerous than over-reporting.
79
+
80
+ ### What the output looks like (real capture)
81
+
82
+ ```
83
+ euthyna euthyna-history — attribution and security classification of deleted code
84
+ Target: D:/axe-core/axe-core
85
+ Range: 8390a5a9…..397f5fce…
86
+
87
+ Established (8)
88
+ • This change deleted 4 lines of code from commit ab877f9d70, spread across 2 files. Commit message: "fix(link-in-text-block): don't match style or script text (#3775)", classification: fix
89
+ Evidence: lib/checks/color/link-in-text-block-evaluate.js (ab877f9d70)
90
+ Reproduce: git blame --porcelain -L 104,104 -L 114,114 8390a5a9… -- lib/checks/…
91
+
92
+ ⚠ Criteria not evaluated (missing data is not clean):
93
+ • reintroduction: --pickaxe not enabled; reintroduced lines were not checked
94
+
95
+ Evaluated criteria:
96
+ • history: 105 (euthyna-history)
97
+ ```
98
+
99
+ ### How to read it
100
+
101
+ | What you see | What it means |
102
+ |---|---|
103
+ | `classification: security` under "Established" | **Raise the risk to maximum**, unless you can prove that code is no longer needed |
104
+ | `classification: fix` | Not a security fix, but **read the commit message itself** — the classifier is biased wide; do not rely on the label alone |
105
+ | `spread across N files` | The file in the evidence **does not contain all** deleted lines. Do not assume only that one file is affected |
106
+ | The "⚠ Criteria not evaluated" section at the bottom | When this section is non-empty, you **must not** read this run as "checked, no problem" |
107
+ | `Established (0)` with an empty not-evaluated section | There really are no deleted lines. This is a **completed measurement**, not a failure |
108
+
109
+ **Do not run `git blame` yourself and read commit messages by eye.** The machine adjudicates the same diff twice with identical results; a human may read it twice with different results.
110
+
111
+ ---
112
+
113
+ ## 3. `coverage` — symbol invocation counts
114
+
115
+ ### When you must run it
116
+
117
+ | Trigger scenario | Corresponding stage |
118
+ |---|---|
119
+ | The report will say "this function **has test coverage**" | Stage B / C |
120
+ | The report will say "this function **has no test coverage**" | Stage B / C |
121
+ | A conclusion depends on "that path was exercised by tests" | Stage C |
122
+
123
+ In other words: **run it before writing anything about "tested or not tested"**.
124
+
125
+ ### Commands
126
+
127
+ ```powershell
128
+ node <euthyna repo>/bin/euthyna.js coverage `
129
+ --coverage <absolute path to coverage file> `
130
+ --symbol <symbol name> --symbol <symbol name>
131
+ ```
132
+
133
+ `--symbol` is repeatable — query several at once. Add `--file` to restrict to a single file.
134
+
135
+ **Both coverage formats are read** (auto-detected, no parameters needed):
136
+
137
+ | Format | Source | Notes |
138
+ |---|---|---|
139
+ | c8 / v8-to-istanbul `coverage-final.json` | JS projects (c8 lands at `coverage/coverage-final.json` by default) | Per-file `fnMap` + `f` (invocation-count array) |
140
+ | coverage.py JSON (format 3) | Python projects (produced by `coverage json`) | Top-level `{meta, files}`; in `functions`, "function name → list of executed lines", **no invocation counts**. Functions never called are still in the list with empty `executed_lines` |
141
+
142
+ The coverage.py production commands (Python targets):
143
+
144
+ ```powershell
145
+ uv run --with coverage python -m coverage run `
146
+ --include="*/src/<package>/<file>.py" `
147
+ -m pytest <test-file> -o addopts="" -p no:randomly
148
+ uv run --with coverage python -m coverage json -o <output path>.json
149
+ ```
150
+
151
+ > Two Python-specific caveats (measured in the crewAI case study):
152
+ > 1. **Disable xdist** (`-o addopts=""`) — coverage does not collect subprocess traces by default, so with `-n auto`
153
+ > the report is empty. Override the repository's xdist defaults with `-o addopts=""`.
154
+ > 2. Restricting by file path with `--include` is more reliable than `--source` for src layouts (`--source=<package name>` may report
155
+ > `module-not-imported`).
156
+
157
+ Where the coverage file comes from: if the project produces one, use it. When the project does not, **either run its tests once with the project's own tooling to produce it, or mark the criterion as "not evaluable"**.
158
+
159
+ ### What the output looks like (real capture; one query hitting both result kinds)
160
+
161
+ ```
162
+ euthyna euthyna-coverage — symbol invocation counts (can only falsify)
163
+
164
+ Established (1)
165
+ • Symbol cleanup was never invoked in this test run (invocation count 0) — any behavior depending on it was never exercised
166
+ Evidence: …\src\handlers.js:29
167
+
168
+ Unknown (1)
169
+ • Symbol checkPermission was invoked 2 time(s), but a non-zero invocation count **cannot** prove any specific call site ran — this fact can only falsify, never confirm
170
+ Evidence: …\src\authz.js:4
171
+ ```
172
+
173
+ ### How to read it (this section is the easiest part of the whole tool to misuse)
174
+
175
+ | Fact | What it lets you infer |
176
+ |---|---|
177
+ | Count 0, `status: established` | ✅ **Really never invoked**. You may say "this path was never exercised" on that basis |
178
+ | Count > 0, `status: unknown` | ❌ **Nothing can be inferred**. Having entered the function ≠ the specific call site you asked about ran |
179
+
180
+ The reason is fully explained in `fact-contract.md`: the underlying coverage data reports **call sites that never executed as covered**
181
+ (straight-line code after a thrown exception is the classic case), so the "covered" direction is unreliable in itself.
182
+ **The producer has no "executed" output at all — this is deliberate.**
183
+
184
+ Corollary: **do not** use "non-zero count" to close a "not tested here" concern. To prove something actually executed,
185
+ only a demonstration that truly reaches that call site can do it (Gate 4 of Stage C).
186
+
187
+ ---
188
+
189
+ ## 4. Exit codes: what to do with each code
190
+
191
+ | Code | Meaning | What you should do |
192
+ |---|---|---|
193
+ | `0` | Measured, no security-related findings | Continue. **But remember to read the "Criteria not evaluated" section** |
194
+ | `10` | Measured, at least one `security`-classified fact exists | **Raise priority**, then run each one through Stage C. Do not treat it as a conclusion directly |
195
+ | `1` | Usage error | Fix the command (`--base` mistyped, empty symbol name, etc.). This is not a measurement result |
196
+ | **`2`** | **Could not measure at all** | **Must not be read as clean**. Either switch measurement methods or mark the relevant criteria as "not evaluable" |
197
+
198
+ The difference between `2` and `0` is the essence of why this contract exists: **failing to measure and measuring clean are two different things.**
199
+
200
+ ### Common situations that reach `2`
201
+
202
+ | Situation | What the output says | Handling |
203
+ |---|---|---|
204
+ | The directory is not inside a git repository | Not evaluated: not in any git repository | Switch to a real repository, or mark not evaluable |
205
+ | The coverage file does not exist | Not evaluated: `ENOENT`, tests not run | Produce coverage, or mark not evaluable |
206
+ | The symbol name is misspelled / does not exist | Symbol not found | Verify the symbol name; if it truly does not exist, state that it is outside the measurement scope |
207
+
208
+ **None of these count as "clean".** The report must write them into "Criteria not evaluated" with the reason stated.
209
+
210
+ ---
211
+
212
+ ## 5. Where they fit into the workflow
213
+
214
+ ```
215
+ Stage B (change-surface audit)
216
+ ├─ deleted lines present ────► history → the result decides whether risk is raised to maximum
217
+ ├─ regression judgment ──────► history --pickaxe → the result decides whether to treat it as a "regression"
218
+ └─ test coverage to discuss ─► coverage → can only falsify; a non-zero count is not evidence
219
+
220
+ Stage C (claim verification)
221
+ └─ conclusion depends on "tested or not" ──► coverage → it constrains the truth of Gate 4 (demonstration)
222
+ ```
223
+
224
+ **Producers give facts, not verdicts.** They say "this line comes from a security-fix commit",
225
+ **not** "deleting it is a vulnerability" — the latter is Stage C's business.
226
+
227
+ ---
228
+
229
+ ## 6. `gate` — programmatic gate validation (not a measurement)
230
+
231
+ `history` / `coverage` produce facts; `gate` **does not measure** — it checks whether an audit report's self-declaration
232
+ conforms to the 6-gate contract. The report format is in SKILL.md's "adjudication format" section.
233
+
234
+ ```powershell
235
+ node <euthyna repo>/bin/euthyna.js gate <report file>
236
+ node <euthyna repo>/bin/euthyna.js gate <report file> --verify --cwd <audited repo>
237
+ ```
238
+
239
+ | What you see | What it means |
240
+ |---|---|
241
+ | Exit code `0` | Every finding carries the evidence it should: TP has `path:L123` evidence + reproduce command + impact + all six gates passed; FP has a FAIL gate; INCONCLUSIVE has a not-evaluated gate |
242
+ | Exit code `10` | Some finding was **downgraded to "observation"** — missing evidence / missing reproduction / gates contradicting the verdict. Read the downgrade reason; fill the gap or change the conclusion to an observation |
243
+ | Exit code `2` | The report cannot be read, or contains no verifiable findings. **Not clean** |
244
+ | `--verify`'s reproduction-check ✗ | The reproduce command actually ran but did not succeed — "says it reproduces" does not count |
245
+
246
+ `gate` only validates what the report itself says; it does not audit the target repository. So it cannot replace adjudication — it only guarantees
247
+ that a conclusion without evidence is not handed over wearing the face of "established".
248
+
249
+ ---
250
+
251
+ ## 7. Known limits (do not use beyond them)
252
+
253
+ - `history`'s classifier is **biased wide**: missing a security commit is more dangerous than over-reporting, so it would rather report `fix` too. The classification is a **lead** — read the commit message yourself.
254
+ - `history`'s attribution defaults to blame's "last modified" semantics; use `--origins` for "originally introduced" (has a probe cap, and only traces lines ≥ 12 characters). Both approximations are documented in this file's "Attribution semantics" section.
255
+ - `history` only covers **deleted lines**. It does not handle newly added code (except with `--pickaxe`).
256
+ - `coverage` **can only falsify**. See above.
257
+ - `coverage` relies on invocation counts and **does not handle dynamic dispatch, reflection, or string-based calls**. Conclusions of count 0 must be discounted when such mechanisms exist.
258
+ - Neither **produces scores, severity, or fix suggestions**. That is the contract, not an omission.