Skip to content

Cap a name-collision edge, the fabrication provenance cannot see - #74

Open
inth3shadows wants to merge 2 commits into
mainfrom
fix/fabricated-edge-hardening
Open

Cap a name-collision edge, the fabrication provenance cannot see#74
inth3shadows wants to merge 2 commits into
mainfrom
fix/fabricated-edge-hardening

Conversation

@inth3shadows

Copy link
Copy Markdown
Owner

provenance marks an edge codegraph synthesized. It says nothing about an edge codegraph extracted and then resolved wrong, which arrives with provenance = NULL and confidence = 0.9 — this repo's top trust tier. A wrong edge there silently corrupts the one thing testgraph answers.

Reproduced against the installed codegraph 1.5.0 bundle (codegraph #66) — a project with a module-level append, and rows["k"].append(2) extracted as a bare append and matched onto it:

build_map (unrelated.py) --calls--> append (ledger.py)
{"confidence":0.9,"resolvedBy":"exact-match","refName":"append"}

The fix

metadata.resolvedBy is the discriminator, and it was already in the index — just unread. import / qualified-name rest on evidence in the source; exact-match rests on a bare name.

exact-match onto a name more than one symbol has is now capped at AMBIGUOUS_NAME_MATCH_CONFIDENCE = 0.5, below LOW_CONFIDENCE, so it flags.

The ambiguity filter is load-bearing. On the codegraph repo's own index:

calls edges                                     29,440
  resolvedBy = exact-match                      16,569  (56.3%)
  ...whose target NAME is ambiguous              8,407  (28.6%)

Capping all exact-match would flag 56% of the graph and make VERIFY MANUALLY mean nothing. An exact-match on a unique name had nothing to get wrong, so it is left alone.

An unrecognized or missing resolvedBy is scored exactly as before: a codegraph change can cost us this protection, but can never invent a flag.

It does not flood the flag

That was the risk worth measuring — 4,106 capped edges sounded like a lot. Journey-level effect, 300 random single-symbol changes per repo, counting journeys that go from trusted to flagged:

repo journey-selections newly flagged
honeyslate 324 0 (0.0%)
signedintake 261 8 (3.1%)
testgraph 66 0 (0.0%)

max over paths of (min over edges) absorbs nearly all of it: one import-resolved route in leaves a journey untouched. The edge count was the wrong unit to reason about.

Also

verify_manually was a bare boolean — a flag nobody can act on is noise. It now carries a reason: synthesized edge, name collision, or a low confidence the index itself reported.

Verification

  • python -m unittest discover -s tests270 tests, OK
  • New build_name_collision_fixture lives in its own in-memory db so the existing count-sensitive tests are untouched. It pins: the ambiguous exact-match is capped; an import-resolved edge to the same target is not; an exact-match on a unique name is not; a missing resolvedBy is not; and the cap tiers stay numerically distinct (weak_edge_reason reads the cap back off the value).

What this does not do

testgraph now downgrades a fabricated edge; it does not stop one being written. The codegraph-side fix is separate (colbymchenry/codegraph#1704, plus the kernel mirror on our fork at v1.6.0-em.2), and it does not reach these indexes until the binary that writes them is updated.

…annot see

`provenance` marks an edge codegraph SYNTHESIZED. It says nothing about an edge
codegraph extracted and then resolved WRONG, which arrives with
provenance = NULL and confidence = 0.9 — our top trust tier. Reproduced against
the installed 1.5.0 bundle (codegraph #66): a project with a module-level
`append`, and `rows["k"].append(2)` matched onto it as

    {"confidence":0.9,"resolvedBy":"exact-match","refName":"append"}

an edge that does not exist, arriving as one we fully trust. A wrong edge here
corrupts the one thing testgraph answers.

`metadata.resolvedBy` is the discriminator and was already in the index, unread.
`import` / `qualified-name` rest on evidence in the source; `exact-match` rests
on a bare name. Capped to 0.5 — below LOW_CONFIDENCE, so it flags — but ONLY
when the target's name is shared by more than one symbol. On the codegraph
repo's index, 16,569 of 29,440 calls edges are exact-match, so capping all of
them would flag 56% of the graph and make VERIFY MANUALLY meaningless; the
ambiguity filter narrows it to 8,407, and an exact-match on a unique name had
nothing to get wrong.

An unrecognized or missing `resolvedBy` scores exactly as before: a codegraph
change can cost us this protection, never invent a flag.

`verify_manually` was a bare boolean; a flag nobody can act on is noise. It now
carries the reason — synthesized edge, name collision, or a low confidence the
index itself reported.

Measured, because the worry was flooding the flag. 300 random single-symbol
changes per repo, counting journeys that go trusted -> flagged:

    honeyslate      324 journey-selections    0 newly flagged   (0.0%)
    signedintake    261 journey-selections    8 newly flagged   (3.1%)
    testgraph        66 journey-selections    0 newly flagged   (0.0%)

`max over paths of (min over edges)` absorbs nearly all of it — one
import-resolved route in leaves a journey untouched. Edge count was the wrong
unit to reason about.

270 tests, OK.
…are `reason`

Four defects from the dual review of this PR.

**The reason was inferred from the number, and the number is not unique to a
cap.** `0.5` is a tier real indexes report directly — `LOW_CONFIDENCE`'s own
comment says so. On honeyslate all 37 reach-kind edges at 0.5 target a name that
is unique in the index, so every "name-collision" label was false: 11 of 11 at
journey level, and it reproduced on this repo's own fixture, where `mid_a`
carries a plain {"confidence":0.5} and no `resolvedBy` at all. The test meant to
defend this only compared the two cap constants to each other, which cannot see
a collision with an index-reported value.

`impacted_closure(..., with_reasons=True)` now carries a cap token through the
CTE beside `conf`; each hop keeps the token of whichever cap produced the value
that survived its `min`, a tie going to the edge as the more specific fact.
Termination is unaffected — the token comes from a four-element set, so the
(id, conf, cap) triple space is still finite. Measured after: 33 flagged entries
on honeyslate, all correctly `metadata`, and zero name-collision labels on a
target that is not ambiguous.

**The ambiguity set was rebuilt per call.** It is an index-wide GROUP BY (~80 ms
on a 17k-node index) and `export.build_map` runs a closure per node, so it was
quadratic — 2.4-2.7x on the map builds, projecting ~21 minutes of identical
redundant work on a large index. TEMP tables are per-connection, so the table's
presence is the cache key and needs no bookkeeping; `refresh_ambiguous_names` is
the escape hatch for a connection that outlives a re-index, and stale data there
can only misgrade a cap, never change closure membership. honeyslate build_map:
0.89s -> 0.59s against 0.53s on main.

**`reason` meant two things.** It already meant "why this row is listed at all"
on the bare degrade rows, and two tests read its ABSENCE as proof a journey was
genuinely selected by the closure. The cap explanation is now `weak_reason`, so
that discriminator stays true.

**The select half had no coverage.** Deleting the attachment used to leave the
suite green; it now fails.

276 tests, OK.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant