You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Revised after verifying against main (v0.2.1) — the original body cited a stale checkout. The core asymmetry stands; the "config deferred" section was wrong and is corrected below.
Problem
In an agent gate, warnings are decoration: the agent branches on the exit code, so a non-blocking finding is a finding the loop is licensed to ignore forever. Two of the strongest structural signals are non-blocking for TS on main:
TS_IMPORT_GRAPH_CYCLE is severity: "warning" (packages/validation-typescript/src/import-graph-check.ts:48,72 on main). Rust blocks cycles: rust.import-graph has defaultSeverity: "error" (packages/validation-rust/src/import-graph-check.ts:113). The TS/Rust asymmetry is not a recorded decision.
typescript.dead-code is defaultSeverity: "warning" (dead-code-check.ts:13 on main), so unused code lands freely.
Why dead-code cannot simply flip to error
The check measures absence of evidence ("no incoming CALLS graph evidence"), not proven deadness. Rust dead-code is safe as error because rustc only flags provably-dead private items and never flags pub. The TS equivalent needs entrypoint knowledge to make the same guarantee; entrypoint modeling already ships on main: automatic package.json discovery (main/exports/bin + tsconfig outDir mapping + test roots, dead-code-roots.ts) with manual deadCode.entrypoints override. The proof machinery for the "provably dead" tier exists; only the severity policy is missing.
Proposed policy
Error must mean proven out of tolerance, not suspicious. Applied:
provably dead (unexported symbol with no callers; file unreachable from configured/automatic entrypoints) -> error
introduced-mode new dead export (added in this changeset, nothing in the changeset calls it) -> error
evidence-absence on pre-existing public exports -> stays reported, labeled as evidence-absence
Example (the introduced-mode error)
Agent adds a helper and forgets to wire it in:
// utils/retry.ts (new file in this changeset)exportfunctionretryWithBackoff(fn,attempts=3){ ... }
Today: warning, exit 0, the agent ships it, unused code accumulates. After: opcore check --changed exits 1 with "you added retryWithBackoff and nothing calls it" — wire it in or delete it. This is the 20k-line-file failure mode stopped at line one.
Config note (corrected)
Main already has a policy config surface (packages/validation-policy): thresholds, lint plugins, layer rules, docs policy, Rust command gates — all via .opcore/config. What does NOT exist, deliberately or not, is per-check severity override. This issue proposes keeping it that way: severity stays vendor-owned so a green gate always means the same thing. If severity config is ever added, loosening must be surfaced in the check result and receipts, not silent.
Problem
In an agent gate, warnings are decoration: the agent branches on the exit code, so a non-blocking finding is a finding the loop is licensed to ignore forever. Two of the strongest structural signals are non-blocking for TS on main:
TS_IMPORT_GRAPH_CYCLEisseverity: "warning"(packages/validation-typescript/src/import-graph-check.ts:48,72on main). Rust blocks cycles:rust.import-graphhasdefaultSeverity: "error"(packages/validation-rust/src/import-graph-check.ts:113). The TS/Rust asymmetry is not a recorded decision.typescript.dead-codeisdefaultSeverity: "warning"(dead-code-check.ts:13on main), so unused code lands freely.Why dead-code cannot simply flip to error
The check measures absence of evidence ("no incoming CALLS graph evidence"), not proven deadness. Rust dead-code is safe as error because rustc only flags provably-dead private items and never flags
pub. The TS equivalent needs entrypoint knowledge to make the same guarantee; entrypoint modeling already ships on main: automatic package.json discovery (main/exports/bin + tsconfig outDir mapping + test roots,dead-code-roots.ts) with manualdeadCode.entrypointsoverride. The proof machinery for the "provably dead" tier exists; only the severity policy is missing.Proposed policy
Error must mean proven out of tolerance, not suspicious. Applied:
canonicalCycle()), so introduced-mode correctly limits blast radius to cycles the change created. Promotion gated on chore(policy): shadow-run strict severity policy (cycles/dead-code as error) and publish the counts #285 (shadow-run data).Example (the introduced-mode error)
Agent adds a helper and forgets to wire it in:
Today: warning, exit 0, the agent ships it, unused code accumulates. After:
opcore check --changedexits 1 with "you added retryWithBackoff and nothing calls it" — wire it in or delete it. This is the 20k-line-file failure mode stopped at line one.Config note (corrected)
Main already has a policy config surface (
packages/validation-policy): thresholds, lint plugins, layer rules, docs policy, Rust command gates — all via.opcore/config. What does NOT exist, deliberately or not, is per-check severity override. This issue proposes keeping it that way: severity stays vendor-owned so a green gate always means the same thing. If severity config is ever added, loosening must be surfaced in the check result and receipts, not silent.Depends on
Acceptance