Skip to content

fix(presentation): match refusals to the code registry and bind routes - #75

Draft
jm-rivera wants to merge 1 commit into
devfrom
fix/presentation-refusal-statuses
Draft

jm-rivera wants to merge 1 commit into
devfrom
fix/presentation-refusal-statuses

Conversation

@jm-rivera

@jm-rivera jm-rivera commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

One refusal changes on the wire. A frame store that fails to answer, a FrameUnavailable with reason io, refuses service_unavailable (503, retryable) on every Presentation route. It refused frame_unavailable at 503 on the write and read routes and at 409 on the render routes. Every other FrameUnavailable reason refuses frame_unavailable at 404, as before. Stored bytes that pass their digest check and do not decode as a frame refuse missing_rendering (404, not retryable) in _decode_frame (src/presentation_store/frame_store.py). They refused io, which a client would retry forever. A reason outside _FRAME_UNAVAILABLE_OUTCOME fails classification and answers internal_error.

DevelopmentFinanceObservatory/spec-kit#13 moves the code registry to the statuses the write routes serve (binding_invalid, constraint_violation and resource_exhausted 400, frame_unavailable 404, release_mismatch 503) and maps edit_conflict on presentation.render. The five spec-kit pins move to its commit 4f927c0. The registry holds one status per endpoint, which is why the io cause leaves frame_unavailable.

The render routes read each refusal's status from the registry on presentation.render in src/presentation_api/doors/render.py, which retires the hand-kept _RENDER_STATUS table. Component data refusals on /render/data pass through the same restatement, at the statuses they already had. _FRAME_UNAVAILABLE_OUTCOME in src/presentation_api/refusals.py names the code for each reason, and compute_presentation_capability_version hashes it, so the Presentation capability version moves.

The Presentation surface declares registry_endpoints: create and revise map to presentation.write, and the two render routes to presentation.render. The component read shares revise's path, so it runs with presentation.write bound, and its classifiers set their own statuses. The frames, component data, release and meta routes stay unbound. The Skills surface binds skills.list, skills.fetch and skills.resource, whose exemplars already agree with the registry. tests/presentation_api/test_refusal_registry.py and tests/skills_api/test_refusal_registry.py, modelled on the Graph registry test, check that every endpoint is bound and that every exemplar goes out at its registered status. All four tests fail against dev with the new pin.

The website and presentation-mcp read code and retryable from the problem body, and an io failure stays retryable under its new code, so neither needs a change.

Merge after DevelopmentFinanceObservatory/spec-kit#13. This PR and #72 both move the spec-kit pins, so whichever merges second moves them to the spec-kit release commit.

Testing: just check under CI's environment with DFO_SPEC_KIT_DIR at the pin gives 38,945 passed, 131 skipped and 5 xfailed. One order-dependent test in tests/graph_release/test_views.py fails. The file passes on its own, and #65 fixes the cause. The new tests/presentation_store/test_frame_store.py case for undecodable bytes failed before the fix.

A frame store that fails to answer, a FrameUnavailable with reason io,
refuses service_unavailable (503, retryable) on every Presentation
route. Every other FrameUnavailable reason refuses frame_unavailable at
404. Stored bytes that do not decode as a frame refuse
missing_rendering, since no retry can load them.

The render routes read each refusal's status from the code registry on
presentation.render, which retires the hand-kept _RENDER_STATUS table.
The Presentation surface binds create and revise to presentation.write
and the two render routes to presentation.render. The Skills surface
binds skills.list, skills.fetch and skills.resource.

The five spec-kit pins move to 4f927c0, which maps the Presentation
write refusals at the statuses the write routes serve and edit_conflict
on presentation.render.

tests/presentation_api/test_refusal_registry.py and
tests/skills_api/test_refusal_registry.py check that every endpoint is
bound and that every exemplar goes out at its registered status.

This branch has not been deployed

No deployments
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.

1 participant