chore(mcp): declare and enforce the FlowSource taxonomy on the chokepoint-flows cache tool - #7450
Open
ayobamiseun wants to merge 2 commits into
Open
chore(mcp): declare and enforce the FlowSource taxonomy on the chokepoint-flows cache tool#7450ayobamiseun wants to merge 2 commits into
ayobamiseun wants to merge 2 commits into
Conversation
…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
|
@ayobamiseun is attempting to deploy a commit to the World Monitor Team on Vercel. A member of the Team first needs to authorize it. |
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6113
What changed (option 1 — narrow + declare, in one change)
server/_shared/flow-source.tsowns the exhaustiveRecord<Exclude<FlowSource,'FLOW_SOURCE_UNSPECIFIED'>, true>(moved from the REST handler, so a proto change stays a compile error in every consumer), theFLOW_SOURCESset,FLOW_SOURCE_WIRE_VALUESfor schema surfaces, and a purenarrowFlowSource.get-chokepoint-status.tsimports it instead of its local copy — REST and MCP can no longer drift.get_chokepoint_status's_postFilternarrows every servedsourceonto the taxonomy before any filtering, mirroring the REST boundary'snarrowServedSources— an undeclared seeder basis reads asFLOW_SOURCE_UNSPECIFIED, never verbatim.chokepoint-flowsoutput 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 bareadditionalProperties: {type:'object'}— discoverable fromtools/list/describe_tool.hazardAlertLeveldeliberately 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
enum:land in the same change ✓ (same_postFilter/schema edit, one shared source of truth).sourceand asserts the served value is narrowed —tests/mcp-flow-source-taxonomy.test.mts:satellite-blendandnullnarrow toFLOW_SOURCE_UNSPECIFIEDon both the full-bundle andchokepoint-filtered paths, declared values pass through verbatim, numeric fields untouched, absent/null dataset tolerated, and the schema enum equals the generatedFlowSourcewire union.tests/mcp-output-schema-coverage.test.mjsstill 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
npm run typecheckand biome clean.