Skip to content

XDE-85: registry compiled-core client (JSON, semver, read bearer) - #1

Merged
remdev merged 1 commit into
mainfrom
feature/xde-85-registry-client-contract
Mar 29, 2026
Merged

XDE-85: registry compiled-core client (JSON, semver, read bearer)#1
remdev merged 1 commit into
mainfrom
feature/xde-85-registry-client-contract

Conversation

@remdev

@remdev remdev commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Align SkillDetail with canonical GET /skills/{name} from registry-api.md: versions as a map, yanked flag.
  • Unpinned ResolveInstallTarget: highest non-yanked semver (same idea as reference latestNonYanked).
  • Optional Authorization on registry GET and archive download via SKILLGET_REGISTRY_READ_TOKEN / fallback to write token (RegistryReadBearer).
  • New docs/REGISTRY_CLIENT_CONTRACT.md for CLI/integrators.
  • Dependency: golang.org/x/mod for semver sort.

Breaking

Callers unmarshaling SkillDetail from the old array/latest_version shape must use the registry JSON shape.

Paperclip: XDE-85.

- Parse GET /skills/{name} versions as map (yanked, semver keys)
- Resolve unpinned installs to highest non-yanked semver
- Send RegistryReadBearer on API GET and archive fetch when set
- Add docs/REGISTRY_CLIENT_CONTRACT.md; depend on golang.org/x/mod/semver

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Made-with: Cursor
Copilot AI review requested due to automatic review settings March 29, 2026 02:00

Copilot AI 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.

Pull request overview

Updates the Go registry client contract and implementation to match the canonical compiled-core registry API, including the new GET /skills/{name} JSON shape, “latest” resolution behavior, and optional read bearer authentication for registry/archives.

Changes:

  • Updates SkillDetail to use versions as a map keyed by version with yanked, plus timestamp fields.
  • Changes unpinned ResolveInstallTarget to select the highest non-yanked semver.
  • Adds RegistryReadBearer and applies it to registry GET requests and archive downloads; adds a client contract doc.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
types.go Aligns registry response types with canonical compiled-core shapes (versions map, yanked, timestamps).
resolve.go Implements highest-non-yanked semver selection for unpinned installs using x/mod/semver.
resolve_integration_test.go Updates integration tests to the new SkillDetail.versions map shape and new “latest” behavior.
client.go Adds optional Authorization: Bearer <read> header to registry JSON GET requests.
config.go Introduces RegistryReadBearer() with read-token override and write-token fallback.
download.go Adds optional read bearer header to archive download HTTP requests.
download_test.go Updates tests for new SkillDetail shape; adds coverage for read bearer propagation and JSON decoding.
registry_errors.go Updates 401 hint text to reference read-token behavior and env vars.
docs/REGISTRY_CLIENT_CONTRACT.md Documents the expected HTTP/JSON contract and env var behavior for integrators.
README.md Updates public docs for new “latest” resolution and read-bearer behavior; links to contract doc.
CHANGELOG.md Adds 0.1.4 entry documenting breaking JSON contract change and new behaviors.
go.mod Adds golang.org/x/mod dependency and introduces go patch + toolchain directives.
go.sum Records checksums for the new golang.org/x/mod dependency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread download.go
Comment on lines +84 to +86
if t := RegistryReadBearer(); t != "" {
req.Header.Set("Authorization", "Bearer "+t)
}

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

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

This unconditionally adds the registry bearer to whatever absolute archive_url is returned. If archive_url can point to a different host (e.g., CDN/S3 presigned URLs), this can leak read/write tokens cross-origin. Consider only attaching Authorization when the archive URL host matches the registry host (or when an explicit allowlist/opt-in env var is set).

Copilot uses AI. Check for mistakes.
Comment thread resolve.go
Comment on lines +60 to +74
var keys []string
for v, info := range d.Versions {
v = strings.TrimSpace(v)
if v == "" || info.Yanked {
continue
}
if v.Version != "" {
return v.Version
}
keys = append(keys, v)
}
return ""
if len(keys) == 0 {
return ""
}
sort.Slice(keys, func(i, j int) bool {
return semver.Compare(canonicalSemver(keys[i]), canonicalSemver(keys[j])) < 0
})
return keys[len(keys)-1]

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

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

Selecting the highest semver currently sorts all version keys (O(n log n) + alloc). Since you only need the max, this can be done in a single pass by tracking the best version with semver.Compare, which is simpler and more efficient for skills with many versions.

Copilot uses AI. Check for mistakes.
Comment thread types.go
Comment on lines 16 to 22
Data []struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Author string `json:"author,omitempty"`
LatestVersion string `json:"latest_version,omitempty"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Author string `json:"author,omitempty"`
LatestVersion string `json:"latest_version,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
} `json:"data"`

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

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

time.Time values tagged with omitempty will still be marshaled (structs are never considered empty by encoding/json), so these fields will emit "0001-..." when zero. If the intent is to omit missing timestamps to match the registry contract, use *time.Time (or remove omitempty and always send a real value).

Copilot uses AI. Check for mistakes.
Comment thread types.go
Comment on lines +31 to +36
type VersionPublicInfo struct {
Manifest json.RawMessage `json:"manifest,omitempty"`
Checksum string `json:"checksum,omitempty"`
ArchiveURL string `json:"archive_url,omitempty"`
PublishedAt time.Time `json:"published_at,omitempty"`
Yanked bool `json:"yanked,omitempty"`

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

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

Same omitempty issue here: PublishedAt time.Time will not be omitted when zero and will serialize as the year-0001 timestamp. Consider switching to *time.Time (or a custom type) if published_at is optional.

Copilot uses AI. Check for mistakes.
@remdev
remdev merged commit 34e8042 into main Mar 29, 2026
5 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.

3 participants