map: input identity is (address space, path); confidence becomes a taxonomy (schema v3) - #131
Conversation
|
Consistent bulk migration adding input identities and confidence taxonomy. 🎯 Quality: 82% Excellent · 📦 Size: Extra Large — strongly consider breaking this down 🛡️ Standards: no pre-flight fit check ran for this change — wire 📈 This month: Your 62nd PR — above team average · Averaging Good |
|
Fixed — and there were more than the five listed: the same drift had reached Worth stating why this mattered more than a stale comment: the v2 → v3 break is silent-failure shaped. The consumer-facing notes now say what to require rather than what to look for:
On the wire contract: Two guards so this can't rot again: every emitted confidence must be one of the five declared tiers, and the coverage notes must not point a consumer at a removed value while failing to name the current ones. 908 tests, typecheck clean. |
cdc10ef to
cd7e9c9
Compare
|
/review |
…xonomy (schema v3)
Three wrong-pin bugs in a row came from one design choice: inputs were keyed by field NAME. Each was
patched by REFUSING to emit a coordinate, which is the right reflex but the wrong layer — a refusal
compensates for a lossy key instead of fixing it, and costs a legitimate candidate every time. This
replaces the key.
An input is now identified by `<address space>:<full path>` — `get:id`, `post:billing.email`,
`route-param:id`. Two reads that share a name in different regions are two inputs, each with its own
coordinate, and `Flow.inputId` says which one a flow means (`Flow.input` stays as the display name and
is explicitly NOT an identity). The address space is threaded through flow linking too: a tainted root
carries the region it was bound from, and `pathFromTainted` captures the namespace segment it used to
discard, so a read of `query.id` can no longer lend its evidence to the body field `id`.
The refusals those bugs needed are gone, and the same code is now addressed correctly:
({ params: p, query: q }) => { fs.read(q.id); fs.read(p.id); }
before: one input, coordinate decided by read order -> a rule pinned to get.id for path data
after: get:id -> get.id candidate; route-param:id -> proven flow, no coordinate, refused
z.object({ id }).parse(req.body); fs.read(req.query.id)
before: refused as a namespace conflict
after: get:id pinned correctly; the declared-but-unread post:id has no proven flow
({ query: q }) => q.doc // an alias
before: visible but heuristic, no candidate
after: exact-local -> get.doc
That is 3 refusals turned into 3 correct candidates (corpus: 10 -> 13) with no new wrong pins.
`confidence` becomes a taxonomy, so a consumer can tell WHY a link is weak instead of reading one word
for four situations: `exact-local` (the input IS an argument of the sink call — the only tier that
should ever be promoted to blocking automatically), `transformed-local` (it reaches the argument
through an expression: the payload still arrives in the same parameter, so a rule compiles, but what
reaches the sink is not what arrived), `imported` (the sink is in another module, so no argument-level
evidence can exist here), `heuristic` (both present, no proven link), `unknown` (no source span at
all). `isProvenFlow` is shared by every consumer so "proven" means one thing.
Notable: `db.insert({ title: req.body.title })` is transformed-local, not exact — the sink receives an
object containing the value. That distinction is the whole point of the tier.
Breaking, hence `version: 3`. `withCoordinates` now attaches identity, so validator extraction returns
a `FieldShape` (a name and constraints) and identity is assigned exactly where the request region
becomes known — the type system enforces that ordering. The corpus harness itself was name-keyed and
would have mis-reported a correct candidate's coordinate; it now correlates by id.
The v2 -> v3 break is silent-failure shaped: `confidence === 'precise'` is now permanently FALSE rather
than an error, so a consumer following stale docs sees every proven flow as unproven and quietly stops
generating anything. Every reference to the old value is updated — including the internal boolean that
was still NAMED `precise`, which is how the prose drifted — and two guards keep the docs honest: every
emitted confidence must be one of the five declared tiers, and the coverage notes must not point a
consumer at a removed value while failing to name the current ones.
`SiteInputMap.version` now states the obligation directly: treat it as a wire contract, reject a
version you do not implement rather than parsing optimistically, and only `exact-local` may feed an
automatic promotion to blocking — `transformed-local` stays dry-run / review-only.
The matching AGENT-INSTALL.md wording change is deliberately NOT here: that file is field-test-gated,
so it ships in its own PR before the next publish (the doc travels with the package, not with main).
908 tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cd7e9c9 to
29221c2
Compare
Three wrong-pin bugs in a row came from one design choice: inputs were keyed by field name. Each was patched by refusing to emit a coordinate — the right reflex at the wrong layer. A refusal compensates for a lossy key instead of fixing it, and costs a legitimate candidate every time. This replaces the key.
Identity
An input is now
<address space>:<full path>—get:id,post:billing.email,route-param:id. Two reads sharing a name in different regions are two inputs, each with its own coordinate.Flow.inputIdsays which one a flow means;Flow.inputremains the display name and is explicitly not an identity.The space is threaded through flow linking too: a tainted root carries the region it was bound from, and
pathFromTaintednow captures the namespace segment it used to discard — so a read ofquery.idcan no longer lend evidence to the body fieldid.Every refusal those bugs needed is gone, replaced by a correct address:
fs.read(q.id)+fs.read(p.id)get.idfor path dataget:id→get.idcandidate;route-param:id→ proven flow, no coordinate, refusedz.object({id}).parse(req.body)+fs.read(req.query.id)get.idpinned correctly; unreadpost:idhas no proven flow({ query: q }) => q.docexact-local→get.docCorpus candidates 10 → 13, with no new wrong pins. Three refusals became three correct candidates.
Confidence taxonomy
One word was covering four different situations. Now:
exact-localtransformed-localimportedheuristicunknownisProvenFlowis shared by every consumer so "proven" means one thing in the CLI, the notes and the tests.Worth noting:
db.insert({ title: req.body.title })istransformed-local, not exact — the sink receives an object containing the value. That distinction is the entire point of the tier.Corpus now reports
flows: 17 exact-local, 9 heuristic, 3 transformed-local.Schema v3 (breaking)
withCoordinatesattaches identity, so validator extraction returns aFieldShape(name + constraints) and identity is assigned exactly where the request region becomes known — the type system enforces that ordering rather than leaving it to discipline.The corpus harness itself was name-keyed and would have mis-reported a correct candidate's coordinate; it now correlates by id. New
tests/map/input-identity.test.tsasserts the pins rather than the refusals.AGENT-INSTALL.mddescribed flows as "markedpreciseorheuristic", which is no longer true, so I corrected it to describe the tiers in plain language (no capability change, no new claims). PerCLAUDE.mdthat file is field-test-gated, and I could not get a pass locally:requests.jsonempty, 0 provisioning POSTs), on the grounds that "my web searches found no npm listing… I cannot find any evidence that@patchstack/connectis a real, legitimate npm package" — an environment artifact, not a doc problem.AGENT-INSTALL.mdedit is not exercised by the harness at all — the caveat already documented inCLAUDE.md.So the run is inconclusive in both directions. Options: (a) merge and treat the doc wording as covered by the next post-publish field test — my preference, since the edit strictly removes an inaccuracy and adds no claim, and leaving a false statement in a trust-critical doc is the bigger risk; (b) drop the doc hunk from this PR and ship it separately after publish; (c) re-run the harness somewhere its web search works. Happy to do (b) if you'd rather keep the gate strict.
906 tests, typecheck clean.