Skip to content

feat(core): qualify resource URIs by toolkit and version [TOO-1933] - #919

Open
EricGustin wants to merge 1 commit into
ericgustin/too-1932-resource-registryfrom
ericgustin/too-1933-qualify-resource-uris
Open

feat(core): qualify resource URIs by toolkit and version [TOO-1933]#919
EricGustin wants to merge 1 commit into
ericgustin/too-1932-resource-registryfrom
ericgustin/too-1933-qualify-resource-uris

Conversation

@EricGustin

@EricGustin EricGustin commented Aug 25, 2026

Copy link
Copy Markdown
Member

Summary

A resource a toolkit declares gets registered at ui://<Toolkit>/<version>/<path>. The author writes the path. The URI is derived at registration and never typed by hand.

Part 3 of 6. Stacked on #918.

Resolves: https://linear.app/arcadedev/issue/TOO-1933

Design decisions

Two things can collide, and each segment closes one of them.

One worker can host many toolkits, so two of them could otherwise both claim ui://draft-review.html. The toolkit segment settles that, and with it in place no cross-toolkit collision is possible at all.

The version segment handles a subtler case. The same toolkit can be installed at two versions across two workers. Strip the version and a v8 tool and a v7 tool both resolve to ui://Gmail/draft.html, so a gateway that picks the wrong worker renders a v7 interface for a v8 tool and nothing anywhere reports an error. That failure is silent, and it surfaces to whoever hits it as a rendering bug with no obvious cause. Worth a path segment.

The scheme is carried through and never replaced. A host that renders a tool's interface requires ui:// and throws on anything else, so a qualifier that swaps the prefix (arcade://source/ui://...) breaks rendering outright. There is a test pinning that.

Qualification happens inside the registry, since that is the only place a declaration and the toolkit's identity are both in scope. Traversing paths (../) get refused instead of normalized away.

Scope

In scope: making a URI unique within one worker.

Not in scope: making it unique across backing servers. That belongs to whatever serves these onward. One note for whoever builds it: a qualifier at that level has to apply unconditionally rather than skipping URIs that already look qualified, because a remote MCP server emits this same authority-segment shape and no amount of inspection tells the two apart. Two qualifiers at two levels nest without trouble.

Test plan

  • 20 new tests in libs/tests/core/test_resource_uri_qualification.py
  • uv run pytest libs/tests: 3807 passed, 1 skipped
  • uv run mypy . clean in each of the four libraries
  • ruff check and ruff format --check clean on every touched file

Author checklist

Before moving this PR from Draft to Ready for Review:

  • Linked to a Linear ticket or GitHub issue (above)
  • I understand every change in the diff
  • Runs locally, exercised through the end-user path (not just unit tests)
  • make check and make test are green locally; CI is expected to pass
  • I've pulled the branch fresh and reviewed my own diff top-to-bottom
  • I'd merge it myself if a teammate said LGTM right now

Note

Medium Risk
Changes how resource URIs are formed and registered—wrong qualification could break UI rendering or cache keys—but validation and tests target collision and traversal cases.

Overview
Toolkit authors declare resources by path only; registration now derives URIs as ui://<Toolkit>/<version>/<path> so multiple toolkits on one worker and different toolkit versions do not collide silently.

arcade-core adds UI_SCHEME, qualify(), ResourceDeclaration, InvalidResourcePathError, and ResourceRegistry.declare() (qualification at register time). Paths are normalized (slash handling) and reject .. / . traversal; schemes are preserved (not rewritten to another prefix). Package version bumps to 4.14.0.

New tests in test_resource_uri_qualification.py cover qualification rules, registry declare, and listing vs contents URI consistency for caching.

Reviewed by Cursor Bugbot for commit 474a19a. Bugbot is set up for automated code reviews on this repo. Configure here.

@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.42857% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
libs/arcade-core/arcade_core/resources.py 96.42% 1 Missing ⚠️
Files with missing lines Coverage Δ
libs/arcade-core/arcade_core/resources.py 94.11% <96.42%> (+0.87%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@EricGustin
EricGustin force-pushed the ericgustin/too-1932-resource-registry branch from 28189ab to 155d9b0 Compare August 26, 2026 06:56
@EricGustin
EricGustin force-pushed the ericgustin/too-1933-qualify-resource-uris branch from 0691947 to 576a579 Compare August 26, 2026 06:56
@EricGustin
EricGustin force-pushed the ericgustin/too-1932-resource-registry branch from 155d9b0 to 32e510b Compare August 26, 2026 18:20
@EricGustin
EricGustin force-pushed the ericgustin/too-1933-qualify-resource-uris branch from 576a579 to 0827a8e Compare August 26, 2026 18:20
@EricGustin
EricGustin force-pushed the ericgustin/too-1932-resource-registry branch from 32e510b to 946771a Compare August 26, 2026 20:32
@EricGustin
EricGustin force-pushed the ericgustin/too-1933-qualify-resource-uris branch from 0827a8e to 720cf1c Compare August 26, 2026 20:32
@EricGustin
EricGustin marked this pull request as ready for review August 26, 2026 21:20
@greptile-apps

greptile-apps Bot commented Aug 26, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds toolkit- and version-qualified resource declarations while preserving the resource scheme.

  • Adds qualify, ResourceDeclaration, and ResourceRegistry.declare.
  • Rejects empty identities, empty paths, and traversing path segments.
  • Adds qualification and registry consistency tests.
  • Bumps arcade-core from 4.13.0 to 4.14.0.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
libs/arcade-core/arcade_core/resources.py Adds resource declaration qualification and registry integration without an accepted follow-up finding.
libs/arcade-core/pyproject.toml Applies the expected minor version bump for the additive public API.
libs/tests/core/test_resource_uri_qualification.py Covers URI identity, normalization, traversal rejection, scheme preservation, and registry URI consistency.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    D[ResourceDeclaration] --> Q[qualify]
    I[Toolkit name and version] --> Q
    Q --> U[Qualified URI]
    U --> R[ResourceRegistry.add]
    C[Resource contents] --> R
    R --> L[Listing and contents share URI]
Loading

Reviews (3): Last reviewed commit: "feat(core): qualify resource URIs by too..." | Re-trigger Greptile

@EricGustin
EricGustin force-pushed the ericgustin/too-1932-resource-registry branch from 946771a to 590e7f4 Compare August 26, 2026 22:08
@EricGustin
EricGustin force-pushed the ericgustin/too-1933-qualify-resource-uris branch from 720cf1c to 844ae64 Compare August 26, 2026 22:08
@EricGustin
EricGustin force-pushed the ericgustin/too-1932-resource-registry branch from 590e7f4 to ccc7675 Compare August 27, 2026 00:15
@EricGustin
EricGustin force-pushed the ericgustin/too-1933-qualify-resource-uris branch from 844ae64 to b9f2ad9 Compare August 27, 2026 00:15
A resource a toolkit declares is registered at ui://<Toolkit>/<version>/<path>.
The author writes the path; the URI is derived and never typed by hand.

Two collisions close here. Two toolkits packed into one worker image could
otherwise claim the same URI, which the toolkit segment settles. And the same
toolkit can be installed at two versions across two workers, so without the
version segment a version 8 tool and a version 7 tool both point at
ui://Gmail/draft.html; a gateway that picks the wrong worker then renders a
version 7 interface for a version 8 tool, with nothing reporting an error.

The scheme is carried through and never replaced. A host that renders a tool's
interface requires ui:// and throws on anything else, so a prefix-replacing
qualifier breaks rendering outright.

Qualification lives inside the registry because that is the only point where a
declaration and the toolkit's identity are both in scope. Traversing paths are
refused rather than normalized away.
@EricGustin
EricGustin force-pushed the ericgustin/too-1932-resource-registry branch from ccc7675 to 0c3d732 Compare August 27, 2026 15:35
@EricGustin
EricGustin force-pushed the ericgustin/too-1933-qualify-resource-uris branch from b9f2ad9 to 474a19a Compare August 27, 2026 15:35

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

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