Skip to content

Answer 404 when revoking a function from a component that does not exist - #675

Merged
davidmckayv merged 1 commit into
CopilotKit:mainfrom
asasemahmed:fix/component-revoke-function-404
Oct 2, 2026
Merged

davidmckayv merged 1 commit into
CopilotKit:mainfrom
asasemahmed:fix/component-revoke-function-404

Conversation

@asasemahmed

Copy link
Copy Markdown
Contributor

What this changes

DELETE /api/components/:name/functions/:function calls store.revokeFunction, which was the only
one of the four component grant writes that did not ask whether the component exists: grant,
revoke and grantFunction all call requireComponent first, and the routes for the other three
catch ComponentNotFoundError and answer 404. revokeFunction just deleted, so against a name
nobody has it deleted zero rows, the route answered { "revoked": true }, and component.function_revoked
was written for a component that was never there.

revokeFunction now calls requireComponent and the route answers 404 with the store's own message,
as POST /:name/functions does.

The principle #616 records, that taking something away is always allowed so a dead row stays
removable by hand, does not apply here. component_functions.component_name references
components.name with ON DELETE CASCADE, so a function grant cannot outlive its component and a
revoke against a missing one can only ever delete nothing.

Where it runs

  • New state that outlives a request? None.
  • What happens on the second replica? The same: one extra read of components before the delete.
  • Anything serialised? No. A component removed between the check and the delete leaves nothing to delete, as before.
  • Anything fanned out to a browser? No.
  • New listener, port, or schedule? No.

Boundary and audit

  • Every acting call still goes through the gateway: grants are not acting calls, and how a granted function is used is unchanged.
  • New refusals and new failures each write a row: the 404 writes none, like the 404s on the sibling routes. What changes is that a row is no longer written for something that did not happen.
  • Nothing new is trusted from the client.

Changelog

  • An entry in CHANGELOG.md under Unreleased.

Proof

  • component-grants-skills-validation.test.ts: a revoke against a component that does not exist
    answers 404 with "No component is called nothing." and records no audit row; against one that
    does, it revokes, answers { revoked: true } and records component.function_revoked.
  • component-store.integration.test.ts, beside the existing "withholding a component that does not
    exist is an error, not a silent row": revoking a function from a missing component throws
    ComponentNotFoundError.
  • Linux, against Postgres: those two files and sandboxed-components.integration.test.ts, 51 pass.
    Against main, the two new cases fail.
  • bun run format:check, bun run lint, bun run typecheck: clean.

DELETE /api/components/:name/functions/:function was the one grant
write that did not check the component exists. Against a name nobody
has, it deleted nothing, answered revoked: true and wrote a
component.function_revoked row naming a component that was never
there. It now answers 404 and writes nothing, as granting a function
and withholding a component already do. A function grant cannot
outlive its component, so no stored row becomes unremovable.
@davidmckayv
davidmckayv enabled auto-merge (squash) October 2, 2026 03:55
@davidmckayv
davidmckayv merged commit 734d916 into CopilotKit:main Oct 2, 2026
30 of 34 checks passed
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