feat(api): add spec.iconUrl to catalog resources - #604
Open
Charlesthebird wants to merge 13 commits into
Open
Conversation
Agent, MCPServer, Skill, Prompt, and Plugin gain an optional spec.iconUrl — the image a catalog UI shows for the resource. It is a first-class spec field rather than a well-known annotation so the contract is typed, discoverable in the OpenAPI schema, and validated on write. validateIconURL accepts an absolute https:// URL or a path on the serving UI's own origin, and rejects everything else. The value is rendered as an image source, so plain http:// would be blocked as mixed content, javascript:/data: would make the field an injection point, and a scheme-relative //host/path would smuggle an external host past a naive "starts with a slash" check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
Charlesthebird
marked this pull request as ready for review
July 31, 2026 20:05
Charlesthebird
enabled auto-merge
July 31, 2026 20:06
…lSpec Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
…ntract Commit bd02c96 (#605) un-gated three deployment e2e tests that had been skipped behind E2E_RUN_LOCAL_DEPLOY=1. Running them against Kubernetes surfaced three deterministic failures (identical across two CI runs): - TestApplyDeployment_HTTPIdempotent: its inline deployment YAML omitted apiVersion, so Decode rejected the document and the whole apply batch collapsed to status=failed. Add apiVersion: ar.dev/v1alpha1, matching the sibling deployment tests. This one is a real fix. - TestBatchApply_DriftRequiresForce: asserts drift rejection and `arctl apply --force`. Both were removed in #517 when the synchronous deployment service gave way to the async controller — ErrDeploymentDrift no longer exists and --force was never registered as a flag. Skipped. - TestDeploymentGet_YAMLIncludesStatus: asserts .status.phase and .status.id, neither of which exists in v1alpha1.Status, which now carries only Conditions and Details. Skipped. The two skips restore these tests to their prior state rather than asserting contracts the product no longer has. Rewriting them against the conditions-based status schema, and deciding whether drift rejection should come back, are follow-ups for the deployment controller owners. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
… Charlesthebird/iconUrlSpec Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
Charlesthebird
commented
Aug 3, 2026
Collaborator
Author
There was a problem hiding this comment.
e2e tests were failing because of this
Collaborator
Author
There was a problem hiding this comment.
These were broken in #605 and being resolved
With the apiVersion fix in place, TestApplyDeployment_HTTPIdempotent got
past the three idempotent applies (created/unchanged/unchanged) and then
failed on its final assertion: "expected exactly 1 deployment ... got 0".
The assertion decoded a response shape the list route does not return.
It expected {"deployments":[{"id","serverName"}]}, but the CRUD list
envelope is {"items":[...]} of Deployment objects, and a deployment names
its target through spec.targetRef — there is no serverName field anywhere
in the deployment API. The "deployments" key never matched, so the slice
was always nil and the count was always 0.
Decode {"items":[...]} and filter on spec.targetRef, and drop the
resourceName/resourceType query params, which are not supported filters
(unknown params are ignored, which is why the request still returned 200).
Raise limit past the default 50 so deployments left behind by other tests
in the same run cannot page this one off the first page.
Note: test/e2e/helpers.go RemoveDeploymentsByServerName decodes the same
stale shape, so it silently matches nothing and deletes no deployments.
Left alone here to avoid changing cleanup behavior suite-wide in a UI PR;
this assertion filters by target name and so is unaffected by the leak.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
Reverts the declarative_test.go changes from 92af493 and b2eb607, restoring the file to its state at 68c518b: - TestApplyDeployment_HTTPIdempotent: apiVersion re-removed from the inline deployment YAML; the list assertion goes back to the {"deployments":[{"id","serverName"}]} shape and the resourceName/resourceType query params. - TestBatchApply_DriftRequiresForce and TestDeploymentGet_YAMLIncludesStatus: t.Skip calls removed, so both run again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a first-class, typed spec.iconUrl field to the v1alpha1 catalog resource specs so UIs can display per-resource icons without relying on untyped annotations, and ensures the value is validated on write.
Changes:
- Added optional
spec.iconUrltoAgent,MCPServer,Skill,Prompt, andPluginspecs (and regenerated OpenAPI + UI client types). - Implemented shared
validateIconURLto allow only absolutehttps://URLs or root-relative paths. - Added agent-focused validation tests covering accepted/rejected
iconUrlinputs.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/lib/api/types.gen.ts | Regenerated UI API types to include iconUrl?: string across relevant spec types. |
| pkg/api/v1alpha1/validation.go | Added validateIconURL shared validator enforcing allowed URL/path formats. |
| pkg/api/v1alpha1/validation_test.go | Added tests validating Agent’s spec.iconUrl behavior. |
| pkg/api/v1alpha1/skill.go | Added IconURL field (json/yaml iconUrl) to SkillSpec with doc comment. |
| pkg/api/v1alpha1/skill_validate.go | Wired IconURL into Skill validation via validateIconURL. |
| pkg/api/v1alpha1/prompt.go | Added IconURL field (json/yaml iconUrl) to PromptSpec with doc comment. |
| pkg/api/v1alpha1/prompt_validate.go | Wired IconURL into Prompt validation via validateIconURL. |
| pkg/api/v1alpha1/plugin.go | Added IconURL field (json/yaml iconUrl) to PluginSpec with doc comment. |
| pkg/api/v1alpha1/plugin_validate.go | Wired IconURL into Plugin validation via validateIconURL. |
| pkg/api/v1alpha1/mcpserver.go | Added IconURL field (json/yaml iconUrl) to MCPServerSpec with doc comment. |
| pkg/api/v1alpha1/mcpserver_validate.go | Wired IconURL into MCPServer validation via validateIconURL. |
| pkg/api/v1alpha1/agent.go | Added IconURL field (json/yaml iconUrl) to AgentSpec with doc comment. |
| pkg/api/v1alpha1/agent_validate.go | Wired IconURL into Agent validation via validateIconURL. |
| openapi.yaml | Regenerated OpenAPI schema to include iconUrl properties for affected specs. |
| .gitignore | Added *.omc ignore entry (not directly related to API change). |
… Prompt Extract the Agent iconUrl case table into a shared iconURLCases table and reuse it for the four other kinds that wire validateIconURL, so each kind's enforcement is pinned against regressions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
A page-relative reference ("icons/x.svg", "./icons/x.svg") is served by the
UI's own origin just like a root-relative one, so treat any scheme-less,
host-less value as valid. Scheme-relative "//host/x.svg" still fails — it
carries a host and points elsewhere — as do non-https schemes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
This reverts commit a3326e9. spec.iconUrl accepts an absolute https:// URL or a root-relative path only — a page-relative reference resolves against whatever route the catalog UI happens to be on, so it is not a stable icon location. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
…lSpec Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
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.
Description
Motivation: a catalog UI needs somewhere to record the image it shows for a
resource. That was being carried as a well-known annotation, which works but
leaves the contract untyped, invisible in the OpenAPI schema, and unvalidated on
write. A spec field is the right home for it.
What changed: an optional
spec.iconUrlonAgent,MCPServer,Skill,Prompt, andPlugin— all five kinds a catalog lists, so a UI does not haveto read the icon from a different place depending on the kind.
validateIconURLaccepts an absolutehttps://URL or a path on the servingUI's own origin, and rejects everything else. The value is rendered as an image
source, which is what each rejection is about:
http://is blocked as mixed content whenever the UI is served overHTTPS, so it would silently never render
javascript:/data:would make the field an injection point//other-host/pathwould smuggle an external host past anaive "starts with a slash" check
Existing resources are unaffected — the field is optional and omitted from the
serialized spec when empty, so re-applying unchanged intent stays a no-op.
🤖 written by Claude (start)
Change Type
/kind feature
Changelog
Additional Notes
openapi.yamlandui/lib/api/types.gen.tsare regenerated, not hand-edited. The field is optional and omitted from the serialized spec when empty, so existing resources are unaffected and re-applying unchanged intent stays a no-op.The one-line
.gitignoreaddition is unrelated local-tooling noise, not part of the API change.🤖 written by Claude (end)