Repository navigation
feat(registry): register the export and MCP refusal codes - #37
Merged
Merged
Conversation
Adds the datasets.exports and datasets.export_link endpoints for the dfo-apis export routes, with provenance_unavailable and licence_not_redistributable. Adds the mcp_refusal channel and mcp.tools endpoint for refusals an MCP tool result carries, with tool_not_found, frame_ref_incomplete, frame_not_held, frame_expired and rolling_reanchor_unavailable.
A prepare that names only a spec_id resolves its newest version and can rebuild it, so datasets.exports carries the 21 codes datasets.definition answers for a rebuild, at the same statuses.
…sets.exports entities_unbound and resolution_ambiguous are catalogue entries no rebuild raises.
Definitions of frame_not_held, frame_expired, rolling_reanchor_unavailable and provenance_unavailable now state the conditions dfo-ai and dfo-apis serve them for, and provenance_unavailable takes the fix_request action. The mcp.tools endpoint and the mcp_refusal channel require each other in the schema. Eight codes only an MCP tool result carries join the registry, and rate_limited and transient_unavailable share their spelling with the proxy table, so the proxy check refuses only codes that reach a problem document or a no_data reason. datasets.export_link registers identity_quarantined, as datasets.files does.
…face mcp.tools now holds every code data-mcp, analysis-mcp, presentation-mcp and the combined server write, so a mapping states where a code appears. presentation-mcp also forwards platform codes it does not write itself. A proxy code may share a spelling only with a code whose every mapping is mcp_refusal, and rate_limited and transient_unavailable are worded to hold on both surfaces. provenance_unavailable takes the stop action, since a recipe root that cannot be cited survives a rebuild. Definitions name no store mechanics, and the README states every field of the mcp_refusal envelope.
jm-rivera
added a commit
to DevelopmentFinanceObservatory/ai
that referenced
this pull request
Oct 30, 2026
…ears, one refusal envelope and export_data (#124) The public MCP server at `/mcp` lists 16 tools at 18,018 o200k tokens, down from 19 tools at 27,054. It adds `export_data`, takes a bare `spec_id` wherever a frame goes, rolls windows over relative years, and gives every refusal one envelope whose `fix` names only arguments the tool accepts. ## Surface - `compare` takes `operation` (`difference` or `sum`) and `share_of` takes `operation` (`share` or `quotient`). Each form runs today's `sum_of` or `quotient_of` path with the same frame id, digest and refusal codes. A share refused `cross_frame_unit_mismatch` between two plain units sets `fix` to `operation: "quotient"`. - `get_skill` takes `path`, `offset` and `digest`, and with a `path` returns what `get_skill_file` returns. - `sum_of`, `quotient_of`, `get_skill_file` and `resolve_anchor` stay on the per-server mounts the Studio harness calls, and leave the public list. A call to one of the first three refuses `tool_not_found` with a `fix` naming the merged tool and the argument to set. - `common.declaration` applies the declaration rules to every tool at registration. It removes `$comment`, empty `required` and the null branch of a property that states `default: null`, inlines every definition, and keeps each sentence once. It refuses a schema whose references it cannot inline. - Each child server declares the public shape of the tools whose public form differs (`PUBLIC_SHAPES`): paragraphs the public description adds, properties it hides and arguments it forces. The combined server applies them to every tool alike. A public call that sends a hidden, unforced argument refuses it as unknown; a forced argument the caller sends is accepted and overwritten. - The public `get_data` forces `place_types` and asks the dfo-apis mint for `enrichment: "default"`, so every public frame carries the type and party columns of the roles its version holds; dfo-ai reads the columns from the rendering the mint served. `rank`, `aggregate` and `share_of` take `place_level`, sent to dfo-apis as a filter step under the verb. - A test bounds the public list in o200k tokens (`_PUBLIC_LIST_TOKEN_BOUND`), and another holds every public schema to the declaration rules. A test holds every code the servers write to its `mcp.tools` mapping in the pinned registry. ## Frames - Frame arguments are typed objects: `spec_id` required, `content_version` optional. A bare `spec_` id reads the newest version, the one the dfo-apis analysis runner's Spec root reads: the verbs send it as a Spec root and record the pair it names; `describe_frame`, `write_component` and `export_data` send it to the dfo-apis routes that resolve it. The public server registers a bare id's stored definition first, so an id fresh from `search` works on all four paths. A bare id of any other kind refuses `frame_ref_incomplete`. - On the public server a verb rebuilds every lost input the runner names (a `spec_` frame by fetching it again in the rendering its mint record states, an `ops_` frame by replaying its recipe with `latest: true`) and retries; `describe_frame` and `write_component` create do the same for a 404 `frame_unavailable` whose `cause.code` is `unknown`, `expired` or `ops_unsupported`, and each reply names the pair asked for and the one read. `common.materialisation.rendering` answers a frame's rendering for all three. A frame whose rendering cannot be established, and in presentation-mcp a summed or cut frame, refuses `rendering_not_recorded` with a `fix` naming `get_data`, since dfo-apis has no route that reads a Spec at a rendering outside an analysis tree. A bare read records the pair it resolved. The harness mounts keep `frame_not_held` and `frame_unavailable`, so Studio runs its own replay. A hidden `rebuild_lost_frames` argument, forced true by `PUBLIC_SHAPES`, carries the difference; `common.derived_replay` holds the one replay call. ## Rolling periods - `growth` and `rank` window bounds and the `filter` threshold value take `{"anchor": "latest", "offset": n}` beside a four-digit year. dfo-apis resolves the latest year from the tree, and the digest names it and the years it resolved. - A public call whose tree holds an `ops_` input sends `rolling: "tree"`, so dfo-apis rolls those inputs' periods over the whole tree; the digest names any input computed again. A derived input with no stored recipe refuses `rolling_reanchor_unavailable`. The harness sends no `rolling` field and keeps its own populations. ## Refusals - presentation-mcp refusals carry `code`, `retry_mode`, `message` and `fix` in `structuredContent` and the same facts in `content`, with `isError`, as data-mcp and analysis-mcp do. Advice moves into the message; `_meta` keeps the assembler's refusal for the harness, so Studio's refusal is unchanged. - `request_invalid` carries `fix` `{tool, problems, accepted}` on every server, with dfo-apis pointers rebased onto the tool's own arguments. Tests sample every refusal code of data-mcp, analysis-mcp and presentation-mcp and check each `fix` against the tool's declared schema. ## export_data `export_data(frame_ref, format, bundle)` prepares the file through dfo-apis `POST /api/datasets/exports`, forwarding the caller, and returns `https://developmentfinance.org/api/datasets/exports/{export_id}`. That link answers 302 to a five-minute signed URL and does not expire. The result and its digest carry the file's name, size, rows, columns, SHA-256, citation and licences. A `get_data` frame exports the stored version its mint record names, and the result names both. A summed or broken-down `get_data` frame refuses `capability_unavailable` and names the `aggregate` route, because the export route takes no groupby. A frame with no mint record refuses `rendering_not_recorded`. A frame dfo-apis no longer holds refuses `frame_unavailable` with the call that registers it again; an export never moves to another version by itself. ## Order 1. Merge DevelopmentFinanceObservatory/spec-kit#37 with a merge commit, which registers the new codes and keeps `2dd2400`, the commit this PR and DevelopmentFinanceObservatory/apis#194 pin, in spec-kit's dev history. A squash merge drops it, and both PRs then need re-pinning to the squash commit. 2. Merge apis#194 and let it deploy to dev. Its analysis route accepts `rolling: "tree"`, and its export and bare-`spec_id` routes are what `export_data`, `describe_frame` and `write_component` call, and its `cause.code` on `frame_unavailable` is what presentation-mcp's rebuild reads; dev dfo-apis refuses the `rolling` field until then. 3. Merge this PR.
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.
Registers the refusal codes for the frame export routes in dfo-apis and for the public MCP tools in dfo-ai.
datasets.exportsisPOST /api/datasets/exports, which prepares a file.datasets.export_linkisGET /api/datasets/exports/{export_id}, which answers 302.provenance_unavailable(422) andlicence_not_redistributable(403) are new. Existing codes those routes return gain the two endpoints, withover_ceilingat 422 as on every otherdatasets.*endpoint.spec_idresolves its newest version and can rebuild it, sodatasets.exportsalso carries the 19 refusals a rebuild raises ondatasets.definition, at the same statuses (for examplespec_not_exportable409,upstream_version_changed503,timeout504).mcp_refusalis a new channel with no HTTP status, andmcp.toolsis its endpoint: a refusal an MCP tool result carries. Every code data-mcp, analysis-mcp, presentation-mcp and the combined server write maps there, 67 in all. Thirteen sit there alone, with no client-contract action rows, since no HTTP client receives them from the kit's routes.rate_limitedandtransient_unavailableare among them and share their spelling with proxy codes, and their definitions hold on both surfaces.datasets.export_linkalso carriesidentity_quarantinedat 500, asdatasets.filesdoes.mcp.toolsandmcp_refusalrequire each other, andvalidate_registry.pyaccepts the new channel.validate_client_contract.pylets a proxy code share a spelling only with a code whose every mapping ismcp_refusal. No retry mode changes.DevelopmentFinanceObservatory/apis#194 and DevelopmentFinanceObservatory/ai#124 pin this branch's head (
2dd2400); merge this with a merge commit so that commit stays on dev; a squash merge means re-pinning both. The branch includes dev's5132aa0.