Skip to content

chore(mcp): declare and enforce the FlowSource taxonomy on the chokepoint-flows cache tool - #7450

Open
ayobamiseun wants to merge 2 commits into
koala73:mainfrom
ayobamiseun:chore/mcp-flow-source-taxonomy
Open

chore(mcp): declare and enforce the FlowSource taxonomy on the chokepoint-flows cache tool#7450
ayobamiseun wants to merge 2 commits into
koala73:mainfrom
ayobamiseun:chore/mcp-flow-source-taxonomy

Conversation

@ayobamiseun

Copy link
Copy Markdown
Contributor

Fixes #6113

What changed (option 1 — narrow + declare, in one change)

  • One taxonomy module: server/_shared/flow-source.ts owns the exhaustive Record<Exclude<FlowSource,'FLOW_SOURCE_UNSPECIFIED'>, true> (moved from the REST handler, so a proto change stays a compile error in every consumer), the FLOW_SOURCES set, FLOW_SOURCE_WIRE_VALUES for schema surfaces, and a pure narrowFlowSource. get-chokepoint-status.ts imports it instead of its local copy — REST and MCP can no longer drift.
  • Enforced: get_chokepoint_status's _postFilter narrows every served source onto the taxonomy before any filtering, mirroring the REST boundary's narrowServedSources — an undeclared seeder basis reads as FLOW_SOURCE_UNSPECIFIED, never verbatim.
  • Declared: the chokepoint-flows output schema now carries the enum (built from the shared wire-value list, not a literal copy) plus the real value fields (currentMbd, baselineMbd, flowRatio, disrupted, hazard fields) instead of bare additionalProperties: {type:'object'} — discoverable from tools/list/describe_tool.
  • hazardAlertLevel deliberately stays a raw nullable string with a comment pointing at chore(supply-chain): FlowEstimate.hazard_alert_level cannot become an enum — sebuf v0.11.1 drops an empty (sebuf.http.enum_value) #6106: its closed enum is blocked on sebuf codegen for the REST twin, and the issue's own note says the two surfaces should adopt it together.

Acceptance mapping

  • Narrowing and enum: land in the same change ✓ (same _postFilter/schema edit, one shared source of truth).
  • A test drives the tool with an out-of-taxonomy source and asserts the served value is narrowedtests/mcp-flow-source-taxonomy.test.mts: satellite-blend and null narrow to FLOW_SOURCE_UNSPECIFIED on both the full-bundle and chokepoint-filtered paths, declared values pass through verbatim, numeric fields untouched, absent/null dataset tolerated, and the schema enum equals the generated FlowSource wire union.
  • tests/mcp-output-schema-coverage.test.mjs still passes and the fixture exercises the new shape ✓ — the captured fixture's full flow objects now validate against declared properties + enum rather than a bare object (38/38).

Verification

  • Failing-first: 3 of 4 new tests fail on main (the absent-dataset control passes). Mutation check: making the narrowing a no-op turns 2 red.
  • Sibling suites green: analysis cache tools + portwatch freshness parity (110/110), chokepoint suites (56/56). npm run typecheck and biome clean.

…oint-flows cache tool

get_chokepoint_status is a cache tool: it serves energy:chokepoint-flows:v1
straight from Upstash, so the FlowSource work koala73#6101 shipped on the REST
path (closed enum in OpenAPI/TS, toFlowSource narrowing at the boundary)
never reached the one surface built specifically for agents. The output
schema declared bare additionalProperties objects — an agent calling
tools/list saw nothing about the taxonomy — and an undeclared seeder
value reached MCP clients verbatim.

Option 1 from the issue, with the declaration and the served bytes
moving together per the contract-gate design note:

- server/_shared/flow-source.ts now owns the taxonomy — the exhaustive
  Record over non-UNSPECIFIED FlowSource members (a proto change is a
  compile error in every consumer), the FLOW_SOURCES set, the wire-value
  list for schema surfaces, and a pure narrowFlowSource. The REST
  handler imports it instead of its local copy.
- The MCP tool's _postFilter narrows every served source onto the
  taxonomy before any filtering, and the output schema declares the
  enum plus the real flow-value fields (currentMbd, baselineMbd,
  flowRatio, disrupted, hazard fields) instead of a bare object — so
  the schema-coverage fixture exercises the shape rather than passing
  trivially. hazardAlertLevel stays a raw string by design: its closed
  enum is blocked on sebuf codegen for the REST twin (koala73#6106) and the
  two surfaces should adopt it together.

Fixes koala73#6113
@vercel

vercel Bot commented Aug 31, 2026

Copy link
Copy Markdown

@ayobamiseun is attempting to deploy a commit to the World Monitor Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added the trust:safe Brin: contributor trust score safe label Aug 31, 2026
…mber set

Review follow-ups on koala73#7450 (koala73#6113).

- Consolidate the narrowers. `toFlowSource` in get-chokepoint-status.ts kept a
  byte-identical copy of the predicate while importing only the member Set, so
  the PR shared the taxonomy's DATA but not its DECISION — and the axis that
  drifts in practice is the predicate (case-folding, trimming, a legacy alias),
  not the member list. It now delegates to `narrowFlowSource` and keeps only the
  REST-side warn-once. `FLOW_SOURCES` loses its last external consumer and
  becomes module-private so a third hand-rolled predicate cannot appear.

- Narrow entries that omit `source` entirely. The MCP guard was
  `'source' in entry`, so an entry without the key was served with the field
  missing while the REST twin emits FLOW_SOURCE_UNSPECIFIED for the same blob —
  a latent divergence on the exact axis this work closes.
  tests/chokepoint-flow-source-taxonomy.test.mts already pins that case on REST.

- Add the served-value gate koala73#6113's acceptance asks for. The existing tests call
  `_postFilter` directly and assert on the mutated INPUT; dispatch.ts serves the
  RETURN. Every assertion now reads the returned object, plus a real tools/call
  round trip through api/mcp.ts that asserts the bytes a client receives carry
  no source outside the declared enum. Also covers the `dataset` branch (where
  selectDatasets builds a new object) and null/non-object entries — a throw
  there is silent, since dispatch.ts falls back to the raw un-narrowed blob.

- Record the two hazard-field decisions instead of implying a constraint. The
  closed hazard enum is DEFERRED for parity with the REST twin, not blocked:
  koala73#6113 notes hand-authored JSON Schema can express it here today. Nullability
  likewise diverges from REST deliberately — REST coerces null to '' only
  because the proto field is non-nullable.

tests/mcp.test.mjs's label-walk fixture gains an in-taxonomy `source` so it
keeps testing labelling rather than failing on the newly added narrowed field.

Verified: typecheck:api clean; lint:boundaries clean; 7/7 new taxonomy tests,
10/10 REST taxonomy, 34/34 output-schema coverage, 177/177 mcp, 149/149
resources + portwatch parity.

Claude-Session: https://claude.ai/code/session_017Gc5oYkEZTf5v5rktyuYX6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

trust:safe Brin: contributor trust score safe

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(mcp): get_chokepoint_status serves chokepoint-flows raw — the FlowSource taxonomy is neither declared nor enforced on the MCP surface

2 participants