Skip to content

feat(api): add spec.iconUrl to catalog resources - #604

Open
Charlesthebird wants to merge 13 commits into
mainfrom
Charlesthebird/iconUrlSpec
Open

feat(api): add spec.iconUrl to catalog resources#604
Charlesthebird wants to merge 13 commits into
mainfrom
Charlesthebird/iconUrlSpec

Conversation

@Charlesthebird

@Charlesthebird Charlesthebird commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

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.iconUrl on Agent, MCPServer, Skill,
Prompt, and Plugin — all five kinds a catalog lists, so a UI does not have
to read the icon from a different place depending on the kind.

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, which is what each rejection is about:

  • plain http:// is blocked as mixed content whenever the UI is served over
    HTTPS, so it would silently never render
  • javascript: / data: would make the field an injection point
  • a scheme-relative //other-host/path would smuggle an external host past a
    naive "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

Agent, MCPServer, Skill, Prompt, and Plugin resources now accept an optional spec.iconUrl, the image a catalog UI displays for the resource. The value must be an absolute https:// URL or a root-relative path on the serving UI's origin.

Additional Notes

openapi.yaml and ui/lib/api/types.gen.ts are 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 .gitignore addition is unrelated local-tooling noise, not part of the API change.


🤖 written by Claude (end)

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>
…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>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

e2e tests were failing because of this

@Charlesthebird Charlesthebird Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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>
Charlesthebird and others added 3 commits August 3, 2026 11:30
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.iconUrl to Agent, MCPServer, Skill, Prompt, and Plugin specs (and regenerated OpenAPI + UI client types).
  • Implemented shared validateIconURL to allow only absolute https:// URLs or root-relative paths.
  • Added agent-focused validation tests covering accepted/rejected iconUrl inputs.

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).

Comment thread pkg/api/v1alpha1/plugin_validate.go
Comment thread pkg/api/v1alpha1/mcpserver_validate.go
Comment thread pkg/api/v1alpha1/skill_validate.go
Comment thread pkg/api/v1alpha1/prompt_validate.go
… 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants