Skip to content

Marketplace registration - #677

Closed
SarthakJoshi18 wants to merge 50 commits into
mainfrom
marketplace-registration
Closed

SarthakJoshi18 wants to merge 50 commits into
mainfrom
marketplace-registration

Conversation

@SarthakJoshi18

@SarthakJoshi18 SarthakJoshi18 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What and why

How it was tested

Checklist

Stoyan12314 and others added 30 commits September 28, 2026 13:11
Replace the previous PRD revision.
  Package versions and package comparison are out of scope for v1; the remaining requirements are unchanged.
…place placeholder'

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…ling'

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
A skill is any folder with a SKILL.md, at any depth, so both skills/{skillName}/ and plugins/{plugin}/skills/{skillName}/ work. All other files in the folder are documents. Duplicate skill names keep the first and log a warning. Tested against clear-route/claude-skills-marketplace: 7 of 7 skills found.
Allow callers to classify GitHub and Redis outages with errors.Is
while preserving their underlying causes.

Refs: https://app.notion.com/768d125495bf82108a27013e0da17ee8
Refs: docs/adr/2026-08-28-marketplace-adr-backend.md
Add a signed delegation token row to TestControllerAuthorization. Check that the delegation reaches the policy evaluator, and that requests without a delegation header send none.

ticket: https://app.notion.com/p/3ded125495bf80fe9f7bf0f5cc9b1c34
siddhantp2003 and others added 20 commits October 5, 2026 17:02
Service calls FetchTree, then FetchSkills, but Crawler had no FetchSkills. Crawl now calls FetchTree, then FetchSkills.

ticket: https://app.notion.com/p/3ded125495bf809aa8d8ed09b1f096dc
…down

The policy bundle poll used context.Background(), so it did not stop at shutdown. main.go now passes backgroundCtx to initServer. stopBackground ends the poll and the registry watch after the server stops.

ApplyEnvOverrides requires GITHUB_APP_ID, GITHUB_APP_INSTALLATION_ID and GITHUB_APP_PRIVATE_KEY_PATH, as "Wire typed config" specifies. The config tests cover each missing variable and the policy defaults.

ticket: https://app.notion.com/p/3ded125495bf809aa8d8ed09b1f096dc
Keep the published source list and source statuses up to date.

- Add a controller for source creation, spec changes, and deletion.
- Update sources after tenant approval changes, suspension,
  reactivation, deletion, and periodic checks.
- Share one aggregator between both controllers and register the
  source controller at startup.
- Ignore source status changes to prevent repeated runs.
- Add tests for source status, approvals, removal, and failed writes.
- Test that tenant changes leave other tenants' sources unchanged.

Refs: https://app.notion.com/p/3f9d125495bf819d9525ea1a2e1e8ffd
Add repository approval and a marketplace source to the existing local
fixtures. Document the setup and required GitHub access.

Refs: https://app.notion.com/p/3f9d125495bf81bd94cbef843a5b84ec
…arketplace-registration

# Conflicts:
#	backend/internal/operator/skillmarketplacesource_controller.go
#	backend/internal/operator/skillmarketplacesource_controller_test.go
#	deploy/local/README.md
@runkora

runkora Bot commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Visual Walkthrough

This change adds skill marketplace source registration, from the CRD to the skills-service read path.

Registration components

A SkillMarketplaceSource flows through the operator into the sources ConfigMap, which the skills service reads.

flowchart TB
    src[SkillMarketplaceSource CR]
    tenant[OrbitTenant allowlist]
    subgraph operator [Operator]
        rec[Reconciler]
        agg[Aggregator]
    end
    cm[Sources ConfigMap]
    subgraph skillsService [Skills service]
        reg[Registry]
        ctl[Controller]
        svc[Service]
        crawl[Crawler]
    end
    gh[GitHub API]
    src --> rec
    rec --> agg
    tenant --> agg
    agg --> cm
    cm --> reg
    ctl --> svc
    reg --> svc
    svc --> crawl
    crawl --> gh
    classDef added fill:#1f883d,stroke:#1a7f37,stroke-width:2px,color:#ffffff
    classDef changed fill:#bf8700,stroke:#9a6700,stroke-width:2px,color:#ffffff
    classDef removed fill:#cf222e,stroke:#a40e26,stroke-width:2px,stroke-dasharray:5,color:#ffffff
    classDef context fill:transparent,stroke:#8c959f,stroke-width:1px,stroke-dasharray:3,color:#8c959f
    linkStyle default stroke:#8c959f,stroke-width:2px
    class src,rec,agg,cm,reg,ctl,svc,crawl added
    class tenant,gh context
    style operator fill:transparent,stroke:#8c959f
    style skillsService fill:transparent,stroke:#8c959f
Loading

Legend: green = added, amber = changed, red = removed, dashed grey = context

Node Code Symbols
SkillMarketplaceSource CR skillmarketplacesource_types.go SkillMarketplaceSourceSpec, repoKey, effectiveBranch
Reconciler skillmarketplacesource_controller.go Reconcile, SetupWithManager
Aggregator skillmarketplace_aggregator.go Sync, buildMarketplaceSources, updateStatuses
Sources ConfigMap marketplace.go SourcesFile, Source
Registry registry.go Watch, reload, loadSources
Controller handlers_skills_entry.go handleListSkills, handleGetSkill, respondError
Service service.go ListSkills, findSource, resolveMarketplace
Crawler crawler.go FetchTree, FetchSkills
Operator verdict paths

Only aggregated sources reach the sources file, and each source gets a status verdict.

sequenceDiagram
    participant K as Kubernetes API
    participant R as Reconciler
    participant A as Aggregator
    participant C as Sources ConfigMap
    K->>R: source created or spec changed
    R->>A: sync all sources
    A->>K: list tenants and sources
    A->>C: patch rendered sources file
    loop each source with a changed verdict
        alt namespace is system namespace
            A->>K: status Invalid, global sources
        else no active tenant
            A->>K: status Pending, tenant not active
        else name already held
            A->>K: status Invalid, duplicate name
        else repo already held
            A->>K: status Invalid, duplicate repository
        else repo not allowlisted
            A->>K: status Pending, not allowlisted
        else aggregated
            A->>K: status Aggregated
        end
    end
Loading
Node Code Symbols
Reconciler skillmarketplacesource_controller.go Reconcile, SetupWithManager
Aggregator skillmarketplace_aggregator.go Sync, buildMarketplaceSources, updateStatuses
Sources ConfigMap marketplace.go SourcesFile, Source
Skill read path

A skills request checks the cursor, the declared sources and the cache before it crawls GitHub.

sequenceDiagram
    participant C as Controller
    participant S as Service
    participant R as Registry
    participant K as Cache
    participant W as Crawler
    participant G as GitHub
    C->>S: list or get skill
    alt cursor is invalid
        S-->>C: 400 invalid cursor
    else cursor is valid
        S->>R: find source by slug
        alt slug not declared
            S-->>C: 404 marketplace not found
        else slug declared
            S->>K: read cached entries
            alt cache read fails
                S-->>C: 503 unavailable
            else cache hit
                K-->>S: sorted entries
                S-->>C: skills page
            else cache miss
                S->>W: fetch tree and skills
                W->>G: read repository files
                alt rate limited or GitHub down
                    G-->>W: failure
                    W-->>S: failure
                    S-->>C: 503 unavailable
                else crawl succeeds
                    G-->>W: file contents
                    W-->>S: skill entries
                    S->>K: store entries
                    S-->>C: skills page
                end
            end
        end
    end
Loading
Node Code Symbols
Controller handlers_skills_entry.go handleListSkills, handleGetSkill, respondError
Service service.go ListSkills, findSource, resolveMarketplace
Registry registry.go ListSources, reload
Cache cache.go GetMarketplace
Crawler crawler.go FetchTree, FetchSkills

@runkora runkora Bot 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.

Walkthrough

The pull request adds skill marketplace source registration with a skills-service, operator aggregator and frontend pages. It also renames the Terra design system package to Hades and adds sandbox environments and run scheduling.

Severity Finding Location
high Remove merge-conflict markers and duplicate index entries docs/adr/README.md:130
medium Restrict the LOCAL_TENANT_ID auth bypass to local environments backend/internal/skills/handlers_skills_entry.go:53
medium Mint the installation token under a detached context backend/internal/skills/token.go:172
medium Cap the size of fetched raw content backend/internal/platform/clients/github/client.go:336
medium Escape path segments separately in the contents URL backend/internal/platform/clients/github/client.go:317
medium Backfill not_before for runs already pending backend/cmd/orbit-agents/migrations/20261012025607_run_scheduling_queue.sql:9
medium Does searchRepoQualifier treat OR queries as repo-scoped? backend/internal/credentials/adapters/githubapp/githubapp.go:361
medium Honor skillsService.replicas, pull secrets and scheduling charts/orbit/templates/skills-service/deployment.yaml:11
medium Do not rebuild the skill slug inline frontend/src/app/(dashboard)/marketplace/[marketplace]/_components/skills-page.tsx:67
medium Revalidate the sandbox image only when it changed backend/internal/agents/environments.go:308
medium Is tenant-controlled spec.image restricted to allowed registries? backend/internal/operator/sandboximage_registry.go:58

Further minor points: setting-row.test.tsx:18 queries .text-[11.5px] but the class is text-[11px], so the absence test cannot fail; GetTree and GetRawContent errors, the nit-level test and doc mismatches in ADR 2026-10-13 (stale 8085 bullet, template comment) and docs/prd/agent-marketplace.md diverging from the base branch.

Review effort: Very high. Split into marketplace, sandbox and scheduling, and the Hades rename.
Risk: High, changes authentication bypass, stored data schema and public APIs
Coverage: 1093 of 1093 files reviewed

Where to focus

Area Why it needs a person
backend/internal/skills Confirm the auth bypass and token sharing behave safely in production.
backend/internal/credentials/adapters/githubapp Decide whether search query scoping can leak other repositories.
backend/cmd/orbit-agents/migrations Verify pending runs survive the scheduling queue migration.
charts/orbit/templates/skills-service Confirm the template should match sibling services' scheduling and replicas.
backend/internal/operator Decide which registry hosts tenants may reference.

Lower risk

Area Why
frontend/src/packages/hades Mechanical Terra to Hades rename with stories and tests.
frontend/src/app/(dashboard)/model-gateway Import path rename only.
frontend/src/app/(dashboard)/policy Import path rename only.
docs/adr Documentation and renamed ADR text.
Changes by area
Area Files Change
backend/ 220 Skills service, operator marketplace and sandbox images, run scheduling, inference gateway System One.
frontend/src/app 319 Marketplace pages, agent overview, environments wizard, Hades imports.
frontend/src/packages 285 Terra package renamed to Hades.
charts/, deploy/ 40 New skills-service chart, CRDs, sandbox mount and values.
docs/ 70 New ADRs and PRDs, renames.
.github/, scripts, build 25 CI gates, bake and version pins.
Checked

Inference gateway routes, egress gateway, trigger deliveries, Hades rename consistency.

Comment thread docs/adr/README.md
- [Hades Wizard Component](2026-08-03-terra-wizard-component.md) — `Proposed` · 2026-08-03
- [Agent Marketplace — Cache-Aside, Tenant-Scoped Redis Keys, and Resource-Aware Skills](2026-08-28-marketplace-adr-backend.md) — `Proposed` · 2026-08-28
- [Hades Wizard Component](2026-08-03-terra-wizard-component.md) — `Proposed` · 2026-08-03
>>>>>>> origin/main

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.

high docs

Remove merge-conflict markers and duplicate index entries

Problem: The index still contains >>>>>>> origin/main at lines 130 and 287. The Hades Wizard entry and the Enforcement Sidecar row are each listed twice.

Fix: Delete both markers and the duplicate entry and row.


var middleware mw.MiddlewareHandler

if localTenantID != "" {

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.

medium security

Restrict the LOCAL_TENANT_ID auth bypass to local environments

Problem: Setting LOCAL_TENANT_ID skips authentication, delegation and scope checks. Nothing limits it to local runs, so one stray env var in a deployed pod exposes every route.

Fix: Reject LOCAL_TENANT_ID in ApplyEnvOverrides unless the environment is local.

p.inflight = ch
p.mu.Unlock()

res := p.mint(ctx)

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.

medium correctness

Mint the installation token under a detached context

Problem: The mint runs under the leader caller's ctx and its result is shared. If the leader is cancelled, every waiter receives context canceled although their own contexts are live.

Fix: Mint with context.WithoutCancel plus a timeout.

return nil, statusError(resp, c.HandleErrorResponse(span, resp, ErrUnexpectedStatus))
}

data, err := io.ReadAll(resp.Body)

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.

medium security

Cap the size of fetched raw content

Problem: io.ReadAll(resp.Body) has no limit. A large file in a tenant-registered repository can exhaust skills-service memory.

Fix: Wrap the body in io.LimitReader with a maximum and return an error when it is exceeded.

// Returns an error that matches ErrUnavailable when GitHub is down or
// rate-limits the call.
func (c *Client) GetRawContent(ctx context.Context, token, org, repo, ref, path string) ([]byte, error) {
endpoint := fmt.Sprintf("%s/repos/%s/%s/contents/%s?ref=%s", c.baseURL, url.PathEscape(org), url.PathEscape(repo), url.PathEscape(path), url.QueryEscape(ref))

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.

medium correctness

Escape path segments separately in the contents URL

Problem: url.PathEscape(path) turns / into %2F, so skills/x/SKILL.md is requested as one segment. The same applies to refs such as feature/x in GetTree.

Fix: Escape each segment and join with /.

// searchRepoQualifier returns the repository name when every repo:owner/name
// term in q names the same repository of owner, and "" otherwise. A negated
// -repo: term never scopes the query.
func searchRepoQualifier(q, owner string) string {

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.

medium security

Does searchRepoQualifier treat OR queries as repo-scoped?

Problem: A query such as repo:o/a OR is:open yields the qualifier a, yet GitHub boolean search may return results from other repositories under a grant limited to one repository.. Question: return an empty qualifier when the query has OR or parentheses?

app: orbit-skills-service

spec:
replicas: 1

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.

medium correctness

Honor skillsService.replicas, pull secrets and scheduling

Problem: replicas: 1 ignores skillsService.replicas. The pod spec also omits orbit.imagePullSecrets and orbit.scheduling, which policy-service includes, so private registries and node selectors are ignored.

Fix: Use the value and add the two includes as in policy-service/deployment.yaml.

) : (
<Stack gap="snug">
{filtered.map((skill) => {
const skillSlug = skill.name

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.

medium correctness

Do not rebuild the skill slug inline

Problem: The inline regex strips _, . and non-ASCII characters, so a skill named pdf_tools links as pdftools and the detail page reports not found. marketplaceSlug duplicates similar logic.

Fix: Link with the raw skill name, which the route helper already encodes.

return nil, ErrEnvironmentNameTaken
}
env.CreatedAt = existing.CreatedAt
if err = s.validate(ctx, env); err != nil {

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.

medium correctness

Revalidate the sandbox image only when it changed

Problem: Every update re-runs validate, including the registered-image check. If the image is later removed, a description-only edit fails with 400.

Fix: Check the image and size only when those fields change.

break
}
}
parsed, err := name.ParseReference(ref, opts...)

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.

medium security

Is tenant-controlled spec.image restricted to allowed registries?

Problem: name.ParseReference receives the tenant's image reference. Question: should hosts be limited to configured registries or mirrors?

@justinwilkin
justinwilkin force-pushed the feat/skills-marketplace branch from df4a76a to c215a15 Compare November 5, 2026 04:22
@justinwilkin
justinwilkin requested a review from a team as a code owner November 5, 2026 04:22
Base automatically changed from feat/skills-marketplace to main November 5, 2026 21:02
@justinwilkin

Copy link
Copy Markdown
Member

Superseded by #225

@justinwilkin
justinwilkin deleted the marketplace-registration branch November 6, 2026 11:46
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.

4 participants