Skip to content

map: input identity is (address space, path); confidence becomes a taxonomy (schema v3) - #131

Merged
patchstackdave merged 1 commit into
mainfrom
feat/map-input-identity
Aug 13, 2026
Merged

map: input identity is (address space, path); confidence becomes a taxonomy (schema v3)#131
patchstackdave merged 1 commit into
mainfrom
feat/map-input-identity

Conversation

@patchstackdave

@patchstackdave patchstackdave commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Rebased onto main after #129 and #130 merged — this branch now contains only the identity + taxonomy work (2 commits, 20 files).

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.inputId says which one a flow means; Flow.input remains 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 pathFromTainted now captures the namespace segment it used to discard — so a read of query.id can no longer lend evidence to the body field id.

Every refusal those bugs needed is gone, replaced by a correct address:

code before after
fs.read(q.id) + fs.read(p.id) one input, coordinate decided by read order → rule pinned to get.id for path data get:idget.id candidate; route-param:id → proven flow, no coordinate, refused
z.object({id}).parse(req.body) + fs.read(req.query.id) refused as a namespace conflict get.id pinned correctly; unread post:id has no proven flow
({ query: q }) => q.doc visible but heuristic exact-localget.doc

Corpus candidates 10 → 13, with no new wrong pins. Three refusals became three correct candidates.

Confidence taxonomy

One word was covering four different situations. Now:

tier meaning
exact-local the input is an argument of the sink call — the only tier that should ever be auto-promoted to blocking
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 isn't what arrived
imported the sink is in another module — no argument-level evidence can exist here
heuristic both present, no proven link
unknown no source span at all, so no evidence is possible

isProvenFlow is 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 }) is transformed-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)

withCoordinates attaches identity, so validator extraction returns a FieldShape (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.ts asserts the pins rather than the refusals.

⚠️ Field-test gate is UNMET — needs a call before merge

AGENT-INSTALL.md described flows as "marked precise or heuristic", which is no longer true, so I corrected it to describe the tiers in plain language (no capability change, no new claims). Per CLAUDE.md that file is field-test-gated, and I could not get a pass locally:

  • The agent refused before installing anything (requests.json empty, 0 provisioning POSTs), on the grounds that "my web searches found no npm listing… I cannot find any evidence that @patchstack/connect is a real, legitimate npm package" — an environment artifact, not a doc problem.
  • Independently, the fixture installs from the registry, so a local AGENT-INSTALL.md edit is not exercised by the harness at all — the caveat already documented in CLAUDE.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.

@coderbuds

coderbuds Bot commented Aug 13, 2026

Copy link
Copy Markdown

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 assess-change-fit into your coding agents to catch size before opening.

📈 This month: Your 62nd PR — above team average · Averaging Good

See how your team is trending →

@patchstackdave

Copy link
Copy Markdown
Contributor Author

Fixed — and there were more than the five listed: the same drift had reached src/map/module-graph.ts, three more comments in flows.ts, and the internal boolean was still named precise, which is how the prose rotted in the first place. That local is now proven.

Worth stating why this mattered more than a stale comment: the v2 → v3 break is silent-failure shaped. confidence === 'precise' is now permanently false rather than an error, so a consumer following the old docs sees every proven flow as unproven and quietly stops generating anything. Docs that point at a removed value are, in that situation, the actual bug.

The consumer-facing notes now say what to require rather than what to look for:

  • require exact-local or transformed-local before pinning a rule
  • identify the input by inputId — a field name can occur in more than one namespace
  • an inferred sink package is a reviewer hint, not evidence about the receiver

On the wire contract: SiteInputMap.version now states the obligation in the schema itself — reject a version you don't implement rather than parsing optimistically, and only exact-local may feed automatic promotion, with transformed-local dry-run / review-only. That's the client half. The server-side version gate and promotion policy still need building, and they're the first thing I'd write when the Phase-3 direction is picked.

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.

@patchstackdave
patchstackdave force-pushed the feat/map-input-identity branch from cdc10ef to cd7e9c9 Compare August 13, 2026 15:46
@patchstackdave

Copy link
Copy Markdown
Contributor Author

/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>
@patchstackdave
patchstackdave force-pushed the feat/map-input-identity branch from cd7e9c9 to 29221c2 Compare August 13, 2026 16:06
@patchstackdave
patchstackdave merged commit e71f6f9 into main Aug 13, 2026
5 checks passed
@patchstackdave
patchstackdave deleted the feat/map-input-identity branch August 13, 2026 16:15
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.

2 participants