audit: prove every permanent-refusal, plugin and generation coverage state #72
Labels
No labels
code-review
correctness
dos
performance
security
severity/high
severity/low
severity/medium
tech-debt
Kind/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Blocks
Reference
h-dv/code-index#72
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
I034 fixed a freshness barrier that read an ABSENT row as proof of exclusion, permanently bricking
changed_symbolsandreview_diff. The fix taught the WALKER stage to prove exclusion.I040 found two more doors onto the same brick, both live in production, neither filed:
dist/bricked both diff tools*.Designer.csreportedindex_stale: trueFOREVER; the barrier waited for a row that can never exist.rsdeniedchanged_symbolson EVERY call, burning 1.24 s of bounded wait each time and hiding the fresh data in the same diffThe pattern is identical every time: "will this file ever be indexed?" is answered in stages, and a prover was built for one stage while the later stages kept refusing files silently.
walker::Coveragemodels the walk;index::path_eligibilitynow models the extension gate;index::content_eligibilitynow models the content gates.Ask
Audit for any REMAINING stage that can permanently refuse a file, and check each is modelled:
classify_walkedaccepted it?parse_errorset, so it is not this class, but confirm the barrier treats it as settled rather than pendingindex.rshas a TOCTOU guard) — a file that grows past the cap mid-walknever— the failure mode in this direction is the opposite one and equally badThe generalisation worth writing down
A three-state disclosure needs every state audited, not just the alarming one. I040's review found
pending— the state nobody was worried about — was a promise ("retry, the watcher will get it") that eight classes of path could never keep.neverwas carefully proved;pendingwas assumed safe because it sounded neutral.Runtime-plugin architecture expansion
#75 adds new refusal and waiting stages, so this audit becomes a prerequisite to #78/#80 rather than a one-time walker review.
Additional doors to model:
Each state must answer two independent questions:
pending is valid only for state that can progress automatically. not_installed, approval_required, rejected and quarantined require explicit action and must never carry “retry in a moment.” A failed pending generation must not hide usable results from the previous active generation.
Acceptance now includes a registry-style stage matrix covering walk, extension/claim, content, plugin host, fact validator, generation and writer. Every terminal state has a proving component and stable reason; every transient state has a tested convergence mechanism.
audit: find the remaining "absent ⇒ never" doors — the I034 brick had threeto audit: prove every permanent-refusal, plugin and generation coverage stateindex_coveragereports "Indexed and current" for a file whose facts were refused by the validator #136The stage matrix exists. Closing — and it found a live defect on its first run.
crates/mcp-server/tests/refusal_stage_registry.rs, 14 tests, in the four sibling registries' idiom.It caught a stale wire contract immediately
Resp::stage's schema doc read"walk" | "classify" | "index"and the source stamps five. Stale for two releases — a client reading the published schema saw three of the five values it could receive. Fixed, and a gate now compares that doc line against the stages the source actually stamps.Why it cannot go stale
The population is the scan, not a list. Every
stage: "…"literal any production source stamps must have a row, and every row claiming to be stamped must be found. Each row carries aprover(machine-checked to be a real item in the crate the row names —bounding_site_registry's known escape), the(verdict, reason)pairs it emits compared against the arms in source order, and aConvergenceanswering this issue's second question.Reason families the arms compute (
WalkVerdict::code,ineligible_reason_code) are read out of the producer function and compared set-for-set, so a new door cannot arrive with an unregistered code.The two-question discipline is gated symmetrically:
Terminalmust contain no progress marker and must name the action;Automaticmust have its mechanism exist as an item and be named in the hint.Emission::Unstampedis what keeps the next door from opening quietly —plugin_host,fact_validatorandwriterreach no per-path verdict today, each records why and where its refusals surface instead, and the gate reddens the day one starts stamping.Three things this issue and my brief got wrong
write_stat_touch"always writes a row and has no refusal path". It does have one (#80 S56): a touch that would changesizeis refused. The honest verdict is better than "structurally benign" — it refuses the touch, never the row, and invalidates (mtime_ns = -1, zero hash) soclassify_hashreparses. Transient by construction.write_upsertgenuinely has zero early returns. Both are now graded structurally.index— the database stage, which is where bothpendingverdicts and the good verdict are stamped, i.e. precisely the promise-shaped states this issue exists to audit.reason_code_registrynever gradedindex_coverage's verdict reason codes at all.no_row_yet,content_refused,row_present,unclaimed_by_activationappear nowhere in it. This registry is the first thing that does.The three-state check against live states: empty, and that is a measurement
All eight live outcomes comply. No terminal hint promises progress; both automatic ones name their mechanism. The interesting one is
eligibility_unavailable— apendingthat is terminal (it needs a daemon restart) and correctly promises nothing.That is this issue's own lesson holding: "
neverwas carefully proved;pendingwas assumed safe because it sounded neutral."The gate's own control was vacuous twice, and running it is what found that
assigned_literalfirst matched onlystage: "…"at the start of a trimmed line. So the"stage": "…"JSON spelling was excluded by the matcher, and then the///prefix by the anchor — the comment strip graded nothing either time, and the control passed with the strip deleted. Widening to both spellings and to boundary-aware substring closed two real holes in the population: a stage emitted through a one-linejson!, and any stage written mid-line.Nine mutations run with
cpsnapshots and md5-verified restores, including the inversion (an unregistered new stage → red naming it), #82's shape (appending "Retry in a moment." to a terminal hint → red), a new walk door, the writer growing a refusal, and an anti-vacuity control proving the scan reaches another crate rather than being aserver.rs-only gate.Gates green in an isolated worktree at
01a478b(the shared tree was mid-edit by the #79 lane): 2946 passed / 0 failed, daemon leg 599 / 0,precision_gate7/7, baseline unmoved.Split out rather than swept
index_coveragereports"Indexed and current."for a file whose facts the validator refused.FileOverlapcarries noparse_error, so the coverage path structurally cannot see it. The barrier is correct (a row exists, so it is settled — this issue's own bullet); the tool over-promises. Needs a wire field plus both skew directions.project_overviewheadroom and a live collision with #79.activationstage's two verdicts are not exercised end-to-end through MCP — graded against source here and at daemon level byreader_epoch_e2e, but an MCP-leg probe needs a two-generation database. Worth filing.code-index://docs/reason-codesis at 3,979 of its 4,000-token cap, so the next reason code this project mints cannot be documented #184disclosure_derivation_registry.rscalls #137 an open blind spot in its header while its own body says the gap is closed and inside the gate #186code-index://docs/reason-codesis at 3,979 of its 4,000-token cap, so the next reason code this project mints cannot be documented #184