Skip to content

feat(#3311): add dynamic plugin packaging and skills marketplace routes#3584

Merged
gabemontero merged 6 commits into
mainfrom
agent/3311-boost-packaging-deployment
Jun 26, 2026
Merged

feat(#3311): add dynamic plugin packaging and skills marketplace routes#3584
gabemontero merged 6 commits into
mainfrom
agent/3311-boost-packaging-deployment

Conversation

@fullsend-ai-coder

@fullsend-ai-coder fullsend-ai-coder Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Configure boost-backend, boost-backend-module-llamastack, and boost-backend-module-kagenti for RHDH dynamic plugin export using @red-hat-developer-hub/cli (rhdh-cli plugin export) and dist-dynamic to published files (tasks 6.1, 6.2).

Create dynamic-plugins-filesystem-reference.yaml and dynamic-plugins-image-reference.yaml with example configurations for deploying boost as a dynamic plugin in RHDH — local filesystem paths for development and OCI container images for production (task 6.3).

Add skills marketplace proxy routes (GET /skills, /skills/runtimes, /skills/domains) that forward requests to an external skills catalog backend configured via boost.skillsMarketplace.endpoint (task 8a.1, 8a.2). Add POST /skills/deploy for K8s manifest generation with OCI init containers (task 8c) and GET /skills/deployments/:id for deployment progress polling (task 8e). The chatEndpoint field is stored passively in deploy responses for future chat routing support (task 8d.3).

Enrich Zod schema definitions with detailed descriptions and inline .describe() annotations for all configurable keys (tasks 3.1, 3.2). Add boost.encryptionSecret field documenting the AES-256-GCM encryption secret (task 8.2 documentation).

All skills routes are gated by boost.features.skillsMarketplace feature flag and permissions. Tests cover feature gating, permission checks, deployment manifest generation, and input validation.

Add openspec section 8 (Skills Marketplace Integration) documenting design decisions and follow-up tasks for skills route improvements (runtimes from config, runtimeId resolution, manifestBuilder extraction).


Closes #3311

Post-script verification

  • Branch is not main/master (agent/3311-boost-packaging-deployment)
  • Secret scan passed (gitleaks — 709a7203bb9a6c9cd2f939d6b89e22014e3d4fa9..HEAD)
  • Pre-commit hooks passed (authoritative run on runner)
  • Tests ran inside sandbox

Configure boost-backend, boost-backend-module-llamastack, and
boost-backend-module-kagenti for RHDH dynamic plugin export (OCI)
by adding janus-cli export-dynamic scripts and dist-dynamic to
published files (tasks 6.1, 6.2).

Create dynamic-plugins.yaml with example configuration for
deploying boost as a dynamic plugin in RHDH (task 6.3).

Add skills marketplace proxy routes (GET /skills, /skills/runtimes,
/skills/domains) that forward requests to an external skills
catalog backend configured via boost.skillsMarketplace.endpoint
(task 5.1). Add POST /skills/deploy for K8s manifest generation
with OCI init containers (task 5.3) and GET /skills/deployments/:id
for deployment progress polling (task 5.4). Chat routing via
chatEndpoint is returned in the deploy response (task 5.6).

Enrich Zod schema definitions with detailed descriptions and
inline .describe() annotations for all 17 configurable keys
(tasks 3.1, 3.2). Add boost.encryptionSecret field documenting
the AES-256-GCM encryption secret (task 8.2 documentation).

All skills routes are gated by boost.features.skillsMarketplace
feature flag and permissions. Tests cover feature gating,
permission checks, deployment manifest generation, and input
validation (25 tests pass).

Closes #3311
@rhdh-gh-app

rhdh-gh-app Bot commented Jun 25, 2026

Copy link
Copy Markdown

Missing Changesets

The following package(s) are changed by this PR but do not have a changeset:

  • @red-hat-developer-hub/backstage-plugin-boost-backend-module-kagenti
  • @red-hat-developer-hub/backstage-plugin-boost-backend-module-llamastack
  • @red-hat-developer-hub/backstage-plugin-boost-backend

See CONTRIBUTING.md for more information about how to add changesets.

Changed Packages

Package Name Package Path Changeset Bump Current Version
@red-hat-developer-hub/backstage-plugin-boost-backend-module-kagenti workspaces/boost/plugins/boost-backend-module-kagenti none v0.1.0
@red-hat-developer-hub/backstage-plugin-boost-backend-module-llamastack workspaces/boost/plugins/boost-backend-module-llamastack none v0.1.0
@red-hat-developer-hub/backstage-plugin-boost-backend workspaces/boost/plugins/boost-backend none v0.1.1

@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 59.18367% with 40 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.23%. Comparing base (709a720) to head (7b6fa3b).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3584   +/-   ##
=======================================
  Coverage   54.22%   54.23%           
=======================================
  Files        2307     2308    +1     
  Lines       88341    88439   +98     
  Branches    24595    24611   +16     
=======================================
+ Hits        47907    47965   +58     
- Misses      40166    40204   +38     
- Partials      268      270    +2     
Flag Coverage Δ *Carryforward flag
adoption-insights 83.70% <ø> (ø) Carriedforward from 9092a52
ai-integrations 67.95% <ø> (ø) Carriedforward from 9092a52
app-defaults 69.79% <ø> (ø) Carriedforward from 9092a52
augment 46.39% <ø> (ø) Carriedforward from 9092a52
boost 74.35% <59.18%> (-0.61%) ⬇️
bulk-import 72.46% <ø> (ø) Carriedforward from 9092a52
cost-management 14.10% <ø> (ø) Carriedforward from 9092a52
dcm 61.79% <ø> (ø) Carriedforward from 9092a52
extensions 61.53% <ø> (ø) Carriedforward from 9092a52
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 9092a52
global-header 59.71% <ø> (ø) Carriedforward from 9092a52
homepage 49.84% <ø> (ø) Carriedforward from 9092a52
install-dynamic-plugins 56.77% <ø> (ø) Carriedforward from 9092a52
konflux 91.49% <ø> (ø) Carriedforward from 9092a52
lightspeed 68.55% <ø> (ø) Carriedforward from 9092a52
mcp-integrations 85.46% <ø> (ø) Carriedforward from 9092a52
orchestrator 38.30% <ø> (ø) Carriedforward from 9092a52
quickstart 63.76% <ø> (ø) Carriedforward from 9092a52
sandbox 79.56% <ø> (ø) Carriedforward from 9092a52
scorecard 83.96% <ø> (ø) Carriedforward from 9092a52
theme 61.26% <ø> (ø) Carriedforward from 9092a52
translations 7.25% <ø> (ø) Carriedforward from 9092a52
x2a 78.68% <ø> (ø) Carriedforward from 9092a52

*This pull request uses carry forward flags. Click here to find out more.


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 709a720...7b6fa3b. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 25, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:34 PM UTC · Completed 5:50 PM UTC
Commit: 709a720 · View workflow run →

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [Injection / Unsanitized user input in K8s manifests] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:282POST /skills/deploy takes user-supplied values (skillId, namespace, name, ociImage, resources.cpu, resources.memory) and inserts them into a K8s Deployment manifest without validation. While the manifest is only returned as JSON (not applied to a cluster) and the endpoint is gated behind boostAdminPermission, input validation should be in place before any future cluster application code is added.
    Remediation: Add input validation — validate skillId/name with RFC 1123 regex (^[a-z0-9][a-z0-9-]{0,62}$), validate namespace, validate resource quantity format.

  • [Secrets handling / encryption secret minimum length] workspaces/boost/plugins/boost-backend/src/config/schemas.ts:590boost.encryptionSecret has z.string().min(16).optional(). The description states AES-256-GCM, which requires a 256-bit (32-byte) key. A 16-character minimum is insufficient. The field is also optional, so the behavior when absent should be documented.
    Remediation: Increase minimum length to 32 characters, or require a base64-encoded 32-byte key. Document what happens when the secret is absent.

  • [Missing test coverage] workspaces/boost/plugins/boost-backend/src/skills/routes.test.ts — No success-path tests for the three proxy GET routes (GET /skills, GET /skills/runtimes, GET /skills/domains). No tests mock fetch() to verify URL construction, query parameter forwarding, non-JSON response handling, or error propagation. The PR's own tasks.md (task 8a.3) acknowledges this as a TODO.

  • [Permission middleware pattern inconsistency] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:120requireAccess checks only boostAccessPermission without an admin fallback. The established pattern in chat/routes.ts and kagenti/routes.ts checks the fine-grained permission first, then falls back to boostAdminPermission so admins always have implicit access. Without the fallback, admins who only hold boostAdminPermission would be denied access to skills read endpoints.
    Remediation: Add an admin fallback to requireAccess — after boostAccessPermission denies, perform a second permissions.authorize against boostAdminPermission.

  • [SSRF / proxy query forwarding] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:193proxyToSkillsCatalog forwards req.query parameters directly to the upstream skills catalog URL. The destination host is admin-controlled via yaml-only config, paths are hardcoded, and only GET is proxied — so the SSRF surface is limited. However, the proxy trust model should be documented.

Low

  • [Error handling gap] routes.ts:210fetch() network errors (DNS failure, connection refused) produce an opaque 500 instead of a more informative 502/503.
  • [Input validation gap] routes.ts:298skillId is used directly in K8s names and labels without RFC 1123 validation. See also: [Injection] finding above.
  • [Resources logic] routes.ts:356 — User-provided resources.cpu/memory values are used for both requests and limits, collapsing burst headroom. Defaults correctly differ (100m/500m CPU, 256Mi/512Mi memory).
  • [Upstream response passthrough] routes.ts:218 — Proxy routes forward upstream error responses unredacted, potentially exposing internal details.
  • [Auth policy] plugin.ts:400{ path: '/skills', allow: 'user-cookie' } follows the established pattern. Defense-in-depth relies on requireAdmin/requireAccess middleware.
  • [Scope creep] staged-issues.md — 496-line project planning document not mentioned in issue boost-packaging — Dynamic plugin packaging and deployment configuration (issue 15 of 15) #3311. Consider submitting separately.
  • [Scope creep] tasks.md:64 — New Section 8 (tasks 8a-8e) defines follow-up work alongside implementation.
  • [Scope mismatch] schemas.ts:67 — Removes boost.skillsMarketplace.enabled in favor of existing boost.features.skillsMarketplace. Reasonable deduplication but document the rationale.
  • [Stale task references] routes.ts:224 — Route comments reference tasks 5.1/5.3/5.4 but the PR's own tasks.md uses section 8 numbering.
  • [Tier mismatch] routes.ts:271 — Deploy endpoint accepts raw ociImage but tasks.md 8c.1 specifies runtimeId resolution. Intentional interim design.
Previous run

Review

Findings

High

  • [Container Image Injection] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:258 — POST /skills/deploy accepts user-controlled ociImage and embeds it directly into a K8s Deployment manifest (init container and main container) without any validation or allowlist check. An authenticated admin user can specify any arbitrary container image. The PR's own tasks.md (task 8c.1) plans to replace this with runtimeId resolved from server-side config, but that is not yet implemented.
    Remediation: Validate ociImage against an allowlist of trusted registries, or implement runtimeId resolution from config as planned in task 8c.1.

Medium

  • [scope-mismatch] workspaces/boost/plugins/boost-backend/src/config/schemas.ts — The PR removes boost.skillsMarketplace.enabled from boostConfigFields without incrementing BOOST_CONFIG_SCHEMA_VERSION. The functionality was consolidated into boost.features.skillsMarketplace, but stored DB values for the removed key will be silently purged on next migration. Additionally, config.d.ts still declares this field (line 100), creating a type-schema mismatch where Backstage will accept the value in YAML but the runtime schema ignores it.
    Remediation: Increment BOOST_CONFIG_SCHEMA_VERSION, update config.d.ts to remove the field, and document the migration path.

  • [scope-creep] workspaces/boost/openspec/changes/pluggable-ai-platform-architecture/tasks.md — The PR adds a new section 8 ('Skills Marketplace Integration') with 14 subtasks to the spec alongside the implementation. Adding significant spec content in the same PR as implementation bypasses spec-first review.
    Remediation: Consider splitting spec additions into a separate PR for independent review.

  • [api-report-regression] workspaces/boost/plugins/boost-backend/report.api.md:250 — The API report widens all config field description types from string literals to generic string. This is caused by moving descriptions to multi-line concatenation, which TypeScript infers as string. This affects all existing fields, not just new ones.
    Remediation: Use as const assertions or template literals to preserve literal types, or accept as intentional widening.

  • [Kubernetes Manifest Injection] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:256 — User-controlled skillId, namespace, and name from the request body are interpolated into K8s manifest metadata and labels without validation against K8s naming rules (RFC 1123). Malformed values produce invalid manifests.
    Remediation: Validate against K8s naming rules (RFC 1123 label: lowercase alphanumeric and hyphens, max 63 chars). Restrict namespace to an allowlist.

  • [Fail-Open Security Default] workspaces/boost/plugins/boost-backend/src/middleware/security.ts:74 — Pre-existing: when boost.security.mode is absent, validateSecurityMode defaults to development-only-no-auth, disabling all auth. The new skills routes inherit this behavior. If the config key is accidentally omitted in production, the entire plugin runs without auth.
    Remediation: Make boost.security.mode required or default to full.

  • [logic-error] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:329 — K8s manifest uses the same resources?.cpu / resources?.memory value for both requests and limits, with different defaults (requests: 100m/256Mi, limits: 500m/512Mi). When a user supplies a value, both collapse to the same number, losing the intentional spread.
    Remediation: Accept separate resources.requests and resources.limits in the request body, or apply a multiplier.

  • [stale-reference] workspaces/boost/openspec/changes/platform-operations-deployment/specs/runtime-config/spec.md:103 — The spec table still lists boost.skillsMarketplace.enabled as a db-overridable config field, but this PR removes it from schemas.ts.
    Remediation: Remove the boost.skillsMarketplace.enabled row from the spec.md config table.

  • [architecture-coherence] workspaces/boost/plugins/boost-backend/src/plugin.ts:323 — Skills routes read config via raw config.getOptionalBoolean()/getOptionalString(). The existing architecture uses RuntimeConfigResolver for all config reads (handles db-overridable fields and caching). Bypassing it means boost.features.skillsMarketplace cannot be toggled at runtime via the admin panel despite being db-overridable.
    Remediation: Inject RuntimeConfigResolver into skills routes, matching the pattern used by other route modules.

  • [error-handling-pattern] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:90requireAccess and requireAdmin middleware don't include the admin-permission fallback. Every other permission middleware in the codebase (mcp/routes.ts, chat/routes.ts) follows a two-step pattern: check fine-grained permission, then fall back to boostAdminPermission. An admin with boost.admin but not boost.access would be denied on read routes.
    Remediation: Add boostAdminPermission fallback matching the pattern in mcp/routes.ts.

  • [error-type-consistency] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:269 — Deploy endpoint uses raw res.status(400).json() instead of throw new InputError() from @backstage/errors. Every other route module uses InputError.
    Remediation: Replace with throw new InputError('skillId and ociImage are required').

Low

  • [SSRF via Configurable Proxy] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:164proxyToSkillsCatalog constructs a URL from yaml-only config, but Zod.url() accepts any valid URL including internal ones. Defense-in-depth suggestion.

  • [intent-mismatch] workspaces/boost/plugins/boost-backend/src/skills/routes.ts — PR body claims task 5.6 (chat routing) but no chat routing logic exists. chatEndpoint is only stored passively in the deploy response.

  • [spec-divergence] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:59 — The new section 8 in tasks.md says runtimes should come from local config (task 8b.2), yet the implementation proxies GET /skills/runtimes to the external catalog. Task 8b.2 is marked as TODO.

  • [error-handling-gap] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:178proxyToSkillsCatalog uses global fetch without a timeout or abort signal. If the external catalog is unreachable, the request hangs indefinitely.

  • [test-inadequate] workspaces/boost/plugins/boost-backend/src/skills/routes.test.ts — Test suite doesn't cover proxy GET routes (/skills, /skills/runtimes, /skills/domains) success path. Task 8a.3 in tasks.md acknowledges this.

  • [traceability] workspaces/boost/plugins/boost-backend/src/plugin.ts:321 — Inline comment references 'tasks 5.1/5.3/5.4' but these belong to 'Standalone Package Extraction'. The actual skills marketplace tasks are in the newly added section 8.

Previous run (2)

Review

Findings

High

  • [injection] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:134POST /skills/deploy takes user-supplied values (skillId, namespace, name, ociImage, resources.cpu, resources.memory) from req.body and interpolates them directly into a Kubernetes Deployment manifest without input validation beyond null checks for skillId and ociImage. skillId goes into K8s labels, env vars, and deployment name. ociImage is used as container image for both init and main containers. namespace is used as metadata.namespace. While this route requires boostAdminPermission, lack of input validation means a compromised admin account could deploy arbitrary containers or generate manifests with invalid K8s names.
    Remediation: Add strict input validation using Zod (already a dependency): validate skillId/name against K8s naming rules (RFC 1123: lowercase alphanumeric and hyphens, max 63 chars), validate namespace against K8s namespace naming rules, validate ociImage against OCI image reference format (consider a registry allowlist), validate resources.cpu and resources.memory against K8s resource quantity format. Return 400 with a clear message when validation fails.

Medium

  • [SSRF] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:90 — The proxyToSkillsCatalog function constructs a URL from the configured boost.skillsMarketplace.endpoint and forwards query parameters from req.query to the upstream. While the endpoint is yaml-only scope (requires config file access, not user-controllable), no allowlist or URL validation restricts the endpoint to external hosts only. A misconfigured endpoint pointing at an internal service could expose internal network data via the proxy.
    Remediation: Consider adding URL validation on the configured endpoint to reject private/internal IP ranges (RFC 1918, link-local, loopback) and non-HTTPS schemes.

  • [fail-open] workspaces/boost/plugins/boost-backend/src/middleware/security.ts:74 — The validateSecurityMode function defaults to development-only-no-auth when boost.security.mode is not configured. This is a fail-open default. The new skills routes (deploy endpoint generating K8s manifests, proxy routes to external services) significantly raise the impact of running without auth. Pre-existing issue, but this PR increases the blast radius.
    Remediation: Change the default security mode to full when no mode is configured, or require explicit configuration. At minimum, emit a startup error when development-only-no-auth is used with NODE_ENV=production.

  • [stale-reference] workspaces/boost/openspec/changes/platform-operations-deployment/specs/runtime-config/spec.md:103 — Stale reference to removed boost.skillsMarketplace.enabled config key. The diff removes this key from schemas.ts (replaced by boost.features.skillsMarketplace), but spec.md still lists it.
    Remediation: Remove the boost.skillsMarketplace.enabled row from the table in spec.md, or replace it with boost.features.skillsMarketplace.

  • [logic-error] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:156 — When the caller provides resources.cpu or resources.memory, the same value is used for both K8s requests and limits. The defaults correctly distinguish them (requests: 100m/256Mi, limits: 500m/512Mi), but user-provided values collapse both to the same number, defeating the purpose of separate request/limit fields.
    Remediation: Accept separate requests and limits sub-objects in the API input, or document that the provided value is applied to both.

Low

  • [test-adequacy] workspaces/boost/plugins/boost-backend/src/skills/routes.test.ts:37 — Mock permissions service returns the same AuthorizeResult for every authorize() call regardless of which permission resource is passed. Cannot distinguish between boostAccessPermission and boostAdminPermission checks — a bug swapping these would not be caught.
    Remediation: Have the mock inspect the permission argument and return ALLOW/DENY conditionally.

  • [error-handling] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:106proxyToSkillsCatalog calls fetch() without a timeout or abort signal. An unresponsive upstream could hang the request indefinitely.
    Remediation: Use AbortSignal.timeout(ms) and handle AbortError.

  • [test-adequacy] workspaces/boost/plugins/boost-backend/src/skills/routes.test.ts:1 — No tests for actual proxy behavior — requests forwarded to the configured endpoint with correct path and query parameters are not verified.
    Remediation: Add tests that mock global fetch and verify URL construction, query forwarding, and response pass-through.

  • [response-forwarding] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:109 — Proxy routes forward upstream response status codes and body directly. While JSON parsing is attempted (non-JSON is wrapped in an error object), upstream error codes are forwarded verbatim.
    Remediation: Consider mapping upstream error codes to generic errors and adding response size limits.

  • [info-disclosure] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:104 — The proxy function logs the full upstream URL including query parameters at debug level.
    Remediation: Log only the pathname portion of the URL.

Previous run (3)

Review

Findings

High

  • [logic-error] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:149new URL(path, endpoint) discards any base path component of endpoint per RFC 3986. If endpoint is https://host/api/v1, the resolved URL becomes https://host/<path>, losing /api/v1. All proxy routes silently hit the wrong backend path.
    Remediation: Use new URL(path, endpoint.endsWith('/') ? endpoint : endpoint + '/') or explicitly join the base path segments before constructing the URL.

Medium

  • [edge-case] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:169response.json() is called without error handling. If the upstream skills catalog returns non-JSON (HTML error page, 502 gateway response), this throws an unhandled rejection that crashes the request without a meaningful error message.
    Remediation: Wrap in try/catch and return a structured error response to the client.

  • [input-validation] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:240skillId and ociImage are validated only for truthiness. No format validation ensures skillId is a valid Kubernetes resource name or ociImage is a valid OCI reference. Malformed values propagate into the generated Deployment manifest, causing opaque K8s API errors at deploy time.
    Remediation: Validate skillId against the K8s DNS subdomain pattern ([a-z0-9]([a-z0-9-]*[a-z0-9])?) and ociImage against an OCI image reference regex before using them in the manifest.

  • [injection-vuln] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:146proxyToSkillsCatalog forwards all query parameters from the incoming request to the upstream endpoint via req.url without an allowlist. An attacker with user access can inject arbitrary query parameters, potentially manipulating upstream API behavior.
    Remediation: Define an explicit allowlist of permitted query parameters and filter req.query before forwarding.

  • [auth-bypass] workspaces/boost/plugins/boost-backend/src/skills/routes.tsrequireAdmin() catches NotAllowedError from the admin permission check and falls back to requireAccess() (basic user permission). This means any user with boostAccessPermission effectively has admin-equivalent access to admin-only routes. The fallback defeats the purpose of a separate admin permission.
    Remediation: Remove the fallback — if the admin permission check fails, deny the request. If the intent is graduated access, document it explicitly and rename the function to reflect the actual behavior.

  • [config-inconsistency] workspaces/boost/plugins/boost-backend/src/config/schemas.ts — Two config paths control skills marketplace enablement: boost.features.skillsMarketplace (consumed by isSkillsEnabled() in routes.ts) and boost.skillsMarketplace.enabled (defined in schema but never read). Operators will set the wrong key and wonder why the feature stays disabled.
    Remediation: Remove the unused boost.skillsMarketplace.enabled key from the schema, or migrate isSkillsEnabled() to read from the canonical path. One source of truth.

  • [missing-test] workspaces/boost/plugins/boost-backend/src/skills/routes.test.ts — The three proxy routes (GET /skills, GET /skills/runtimes, GET /skills/domains) have zero test coverage. These routes perform URL construction, query forwarding, and response proxying — all with the URL resolution bug above — and none of this behavior is verified.
    Remediation: Add tests that mock the upstream fetch and verify correct URL construction, query parameter handling, and error responses.

  • [scope-creep] workspaces/boost/plugins/boost-backend/src/config/schemas.ts — The linked issue (task 5.6) authorizes skills marketplace routes and deployment manifest generation. The PR also adds .describe() annotations to all 17 existing Zod schema fields and introduces a new boost.encryptionSecret config field — changes unrelated to the skills marketplace feature.
    Remediation: Split the schema annotation and encryption secret changes into a separate PR.

  • [stub-implementation] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:273POST /skills/deploy returns a hardcoded { id: 'deployment-id' } stub response after generating a real manifest. GET /skills/deployments/:id returns { status: 'unknown' }. Callers cannot distinguish between success and a stub, and there is no indication these are placeholders.
    Remediation: Either implement the deployment tracking or return HTTP 501 (Not Implemented) with a clear message that deployment tracking is not yet available.

  • [architectural-conflict] workspaces/boost/plugins/boost-backend/src/skills/routes.ts — The deploy route manually validates skillId and ociImage with if-checks and throws InputError, while the rest of the plugin uses Zod schemas for config validation. This inconsistency means deploy input validation doesn't benefit from Zod's type inference, error formatting, or schema composition.
    Remediation: Define a Zod schema for the deploy request body and use .parse() or .safeParse() for validation, consistent with the rest of the plugin.

  • [error-handling-idiom] workspaces/boost/plugins/boost-backend/src/skills/routes.test.ts — The test error handler maps NotAllowedError and NotFoundError but omits InputError, which is thrown by the deploy route validation. Tests for invalid deploy inputs will get a generic 500 instead of verifying the expected 400 response.
    Remediation: Add InputError → 400 mapping to the test error handler middleware.

Low

  • [injection-vuln] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:240 — User-supplied skillId is interpolated directly into Kubernetes labels and container names in the generated manifest without sanitization. While K8s API validation will reject invalid values, the error messages will be opaque.

  • [auth-policy] workspaces/boost/plugins/boost-backend/src/plugin.ts:400 — The auth policy grants user-cookie access to the entire /skills path prefix. All sub-routes inherit this policy, including admin routes, which rely on application-level permission checks rather than auth-policy-level restrictions.

  • [edge-case] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:303 — When user-provided resource values are used, requests and limits are set to the same value, which sets the pod QoS class to Guaranteed. This may not be the intended behavior for all deployments and prevents Kubernetes from bursting resources.

  • [naming-convention] workspaces/boost/plugins/boost-backend/src/skills/routes.ts — The helper fetchJson duplicates the pattern of fetch-then-parse-JSON that could use a shared utility. The function name doesn't indicate it's specifically for the skills catalog proxy.

  • [pattern-inconsistency] workspaces/boost/plugins/boost-backend/src/skills/routes.ts — Config access uses config.getOptionalString('boost.skills.endpoint') with a different key path than either of the two schema-defined paths (boost.features.skillsMarketplace and boost.skillsMarketplace.enabled), creating a third config namespace for the same feature.


Labels: PR modifies the boost workspace skills marketplace backend plugin.

Previous run (4)

Review

Reason: stale-head

The review agent reviewed commit 9b258a04ded0bee1edb83e27cfce9f8502ca953f but the PR HEAD is now 255fa97801c6daab14676fb059d882e103e9ff1e. This review was discarded to avoid approving unreviewed code.

@fullsend-ai-review

Copy link
Copy Markdown

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 25, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:54 PM UTC · Completed 6:14 PM UTC
Commit: 709a720 · View workflow run →

@fullsend-ai-review fullsend-ai-review 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.

See the review comment for full details.

Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/plugin.ts
Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.ts
@fullsend-ai-review fullsend-ai-review Bot added workspace/boost Boost workspace (Backstage AI plugin) feature labels Jun 25, 2026
- Fix URL resolution in proxyToSkillsCatalog: use URL pathname join
  instead of new URL(path, base) which discards base path per RFC 3986
- Add try/catch around response.json() for non-JSON upstream responses
- Remove unused boost.skillsMarketplace.enabled config key (the actual
  feature toggle is boost.features.skillsMarketplace)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rhdh-qodo-merge

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: Workspace boost, CI step for node 24

Failed stage: check api reports and generate API reference [❌]

Failed test name: ""

Failure summary:

The action failed during the API reports validation step because there were uncommitted/unchecked-in
API report changes:
- The workflow detected a conflict in
backstage-plugin-boost-backend/report.api.md (API Extractor output differed from what is committed).

- The job explicitly reports: “You have uncommitted changes to the public API or reports of a
package. To solve this, run yarn build:api-reports and commit all md file changes.”
- This caused
the command to exit with code 1 (##[error]Process completed with exit code 1.).

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

259:  �[93m➤�[39m YN0002: │ �[38;5;166m@red-hat-developer-hub/�[39m�[38;5;173mbackstage-plugin-boost-responses-api-toolkit�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:plugins/boost-responses-api-toolkit�[39m doesn't provide �[38;5;173mwebpack�[39m (�[38;5;111mpc962f3�[39m), requested by �[38;5;166m@backstage/�[39m�[38;5;173mcli�[39m.
260:  �[93m➤�[39m YN0002: │ �[38;5;166m@red-hat-developer-hub/�[39m�[38;5;173mbackstage-plugin-boost-toolscope�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:plugins/boost-toolscope�[39m doesn't provide �[38;5;173mwebpack�[39m (�[38;5;111mpadf779�[39m), requested by �[38;5;166m@backstage/�[39m�[38;5;173mcli�[39m.
261:  �[93m➤�[39m YN0002: │ �[38;5;166m@red-hat-developer-hub/�[39m�[38;5;173mbackstage-plugin-kagenti-entity-provider�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:plugins/kagenti-entity-provider�[39m doesn't provide �[38;5;173mwebpack�[39m (�[38;5;111mpc4ac65�[39m), requested by �[38;5;166m@backstage/�[39m�[38;5;173mcli�[39m.
262:  �[93m➤�[39m YN0002: │ �[38;5;166m@red-hat-developer-hub/�[39m�[38;5;173mbackstage-plugin-llamastack-entity-provider�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:plugins/llamastack-entity-provider�[39m doesn't provide �[38;5;173mwebpack�[39m (�[38;5;111mpea31d7�[39m), requested by �[38;5;166m@backstage/�[39m�[38;5;173mcli�[39m.
263:  �[93m➤�[39m YN0086: │ Some peer dependencies are incorrectly met by your project; run �[38;5;111myarn explain peer-requirements <hash>�[39m for details, where �[38;5;111m<hash>�[39m is the six-letter p-prefixed code.
264:  �[93m➤�[39m YN0086: │ Some peer dependencies are incorrectly met by dependencies; run �[38;5;111myarn explain peer-requirements�[39m for details.
265:  ##[endgroup]
266:  �[94m➤�[39m �[90mYN0000�[39m: └ Completed
267:  �[94m➤�[39m �[90mYN0000�[39m: ┌ Fetch step
268:  ##[group]Fetch step
269:  �[94m➤�[39m YN0013: │ �[38;5;220m2160�[39m packages were added to the project (�[38;5;160m+ 697.14 MiB�[39m).
270:  ##[endgroup]
271:  �[94m➤�[39m �[90mYN0000�[39m: └ Completed in 10s 954ms
272:  �[94m➤�[39m �[90mYN0000�[39m: ┌ Link step
273:  ##[group]Link step
274:  �[94m➤�[39m YN0007: │ �[38;5;166m@fission-ai/�[39m�[38;5;173mopenspec�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.4.1�[39m must be built because it never has been before or the last one failed
275:  �[94m➤�[39m YN0007: │ �[38;5;166m@swc/�[39m�[38;5;173mcore�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.15.40 [486b9]�[39m must be built because it never has been before or the last one failed
276:  �[94m➤�[39m YN0007: │ �[38;5;173mesbuild�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.25.12�[39m must be built because it never has been before or the last one failed
277:  �[94m➤�[39m YN0007: │ �[38;5;173mesbuild�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.23.1�[39m must be built because it never has been before or the last one failed
278:  �[94m➤�[39m YN0007: │ �[38;5;166m@nestjs/�[39m�[38;5;173mcore�[39m�[38;5;111m@�[39m�[38;5;111mnpm:11.1.21 [26b97]�[39m must be built because it never has been before or the last one failed
279:  �[94m➤�[39m YN0007: │ �[38;5;173mesbuild�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.16.17�[39m must be built because it never has been before or the last one failed
280:  �[94m➤�[39m YN0007: │ �[38;5;173mcore-js-pure�[39m�[38;5;111m@�[39m�[38;5;111mnpm:3.49.0�[39m must be built because it never has been before or the last one failed
281:  �[94m➤�[39m YN0007: │ �[38;5;173mbetter-sqlite3�[39m�[38;5;111m@�[39m�[38;5;111mnpm:12.11.1�[39m must be built because it never has been before or the last one failed
282:  �[94m➤�[39m YN0007: │ �[38;5;173mkeytar�[39m�[38;5;111m@�[39m�[38;5;111mnpm:7.9.0�[39m must be built because it never has been before or the last one failed
283:  �[94m➤�[39m YN0007: │ �[38;5;173mprotobufjs�[39m�[38;5;111m@�[39m�[38;5;111mnpm:7.6.4�[39m must be built because it never has been before or the last one failed
284:  �[94m➤�[39m YN0007: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m must be built because it never has been before or the last one failed
285:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mbetter-sqlite3�[39m�[38;5;111m@�[39m�[38;5;111mnpm:12.11.1�[39m �[31mSTDERR�[39m (node:2661) [DEP0176] DeprecationWarning: fs.R_OK is deprecated, use fs.constants.R_OK instead
...

342:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[32mSTDOUT�[39m   CXX(target) Release/obj.target/cpufeatures/src/binding.o
343:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[31mSTDERR�[39m In file included from ../src/binding.cc:1:
344:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[31mSTDERR�[39m /home/runner/.cache/node-gyp/24.17.0/include/node/node.h:1358:7: warning: cast between incompatible function types from ‘void (*)(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE)’ {aka ‘void (*)(v8::Local<v8::Object>)’} to ‘node::addon_register_func’ {aka ‘void (*)(v8::Local<v8::Object>, v8::Local<v8::Value>, void*)’} [-Wcast-function-type]
345:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[31mSTDERR�[39m  1358 |       (node::addon_register_func) (regfunc),                          \
346:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[31mSTDERR�[39m       |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
347:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[31mSTDERR�[39m /home/runner/.cache/node-gyp/24.17.0/include/node/node.h:1392:3: note: in expansion of macro ‘NODE_MODULE_X’
348:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[31mSTDERR�[39m  1392 |   NODE_MODULE_X(modname, regfunc, NULL, 0)  // NOLINT (readability/null_usage)
349:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[31mSTDERR�[39m       |   ^~~~~~~~~~~~~
350:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[31mSTDERR�[39m ../src/binding.cc:151:1: note: in expansion of macro ‘NODE_MODULE’
351:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[31mSTDERR�[39m   151 | NODE_MODULE(cpufeatures, init)
352:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[31mSTDERR�[39m       | ^~~~~~~~~~~
353:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[32mSTDOUT�[39m   SOLINK_MODULE(target) Release/obj.target/cpufeatures.node
354:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[32mSTDOUT�[39m   COPY Release/cpufeatures.node
355:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[32mSTDOUT�[39m make: Leaving directory '/home/runner/work/rhdh-plugins/rhdh-plugins/workspaces/boost/node_modules/cpu-features/build'
356:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mcpu-features�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.0.10�[39m �[31mSTDERR�[39m gyp info ok 
357:  �[94m➤�[39m YN0007: │ �[38;5;166m@openapitools/�[39m�[38;5;173mopenapi-generator-cli�[39m�[38;5;111m@�[39m�[38;5;111mnpm:2.34.0�[39m must be built because it never has been before or the last one failed
358:  �[94m➤�[39m YN0007: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m must be built because it never has been before or the last one failed
359:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[31mSTDERR�[39m gyp info it worked if it ends with ok
...

384:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[31mSTDERR�[39m gyp info spawn args   '--depth=.',
385:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[31mSTDERR�[39m gyp info spawn args   '--no-parallel',
386:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[31mSTDERR�[39m gyp info spawn args   '--generator-output',
387:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[31mSTDERR�[39m gyp info spawn args   'build',
388:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[31mSTDERR�[39m gyp info spawn args   '-Goutput_dir=.'
389:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[31mSTDERR�[39m gyp info spawn args ]
390:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[31mSTDERR�[39m gyp info spawn make
391:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[31mSTDERR�[39m gyp info spawn args [ 'BUILDTYPE=Release', '-C', 'build' ]
392:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[32mSTDOUT�[39m make: Entering directory '/home/runner/work/rhdh-plugins/rhdh-plugins/workspaces/boost/node_modules/ssh2/lib/protocol/crypto/build'
393:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[32mSTDOUT�[39m   CXX(target) Release/obj.target/sshcrypto/src/binding.o
394:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[32mSTDOUT�[39m   SOLINK_MODULE(target) Release/obj.target/sshcrypto.node
395:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[32mSTDOUT�[39m   COPY Release/sshcrypto.node
396:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[32mSTDOUT�[39m make: Leaving directory '/home/runner/work/rhdh-plugins/rhdh-plugins/workspaces/boost/node_modules/ssh2/lib/protocol/crypto/build'
397:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[31mSTDERR�[39m gyp info ok 
398:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173mssh2�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.17.0�[39m �[32mSTDOUT�[39m Succeeded in building optional crypto binding
399:  �[94m➤�[39m YN0007: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m must be built because it never has been before or the last one failed
400:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[93m➤�[39m YN0000: Yarn detected that the current workflow is executed from a public pull request. For safety the hardened mode has been enabled.
...

408:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m �[90mYN0000�[39m: ┌ Post-resolution validation
409:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m ::group::Post-resolution validation
410:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[93m➤�[39m YN0060: │ �[38;5;173mprettier�[39m is listed by your project with version �[38;5;111m3.7.4�[39m (�[38;5;111mpc2ecd8�[39m), which doesn't satisfy what �[38;5;166m@spotify/�[39m�[38;5;173mprettier-config�[39m and other dependencies request (�[38;5;37m^2.0.0�[39m).
411:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[93m➤�[39m YN0002: │ �[38;5;166m@redhat-developer/�[39m�[38;5;173mrhdh-plugins�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m doesn't provide �[38;5;166m@typescript-eslint/�[39m�[38;5;173mparser�[39m (�[38;5;111mp8d7c5c�[39m), requested by �[38;5;166m@spotify/�[39m�[38;5;173meslint-plugin�[39m.
412:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[93m➤�[39m YN0086: │ Some peer dependencies are incorrectly met by your project; run �[38;5;111myarn explain peer-requirements <hash>�[39m for details, where �[38;5;111m<hash>�[39m is the six-letter p-prefixed code.
413:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[93m➤�[39m YN0086: │ Some peer dependencies are incorrectly met by dependencies; run �[38;5;111myarn explain peer-requirements�[39m for details.
414:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m ::endgroup::
415:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m �[90mYN0000�[39m: └ Completed
416:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m �[90mYN0000�[39m: ┌ Fetch step
417:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m ::group::Fetch step
418:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m YN0013: │ �[38;5;220m642�[39m packages were added to the project (�[38;5;160m+ 272.06 MiB�[39m).
419:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m ::endgroup::
420:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m �[90mYN0000�[39m: └ Completed in 4s 129ms
421:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m �[90mYN0000�[39m: ┌ Link step
422:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m ::group::Link step
423:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m YN0007: │ �[38;5;173mesbuild�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.21.5�[39m must be built because it never has been before or the last one failed
424:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m YN0007: │ �[38;5;166m@swc/�[39m�[38;5;173mcore�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.4.13 [366d3]�[39m must be built because it never has been before or the last one failed
425:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m YN0007: │ �[38;5;173mesbuild�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.23.1�[39m must be built because it never has been before or the last one failed
426:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m YN0007: │ �[38;5;173mesbuild�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.20.2�[39m must be built because it never has been before or the last one failed
427:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m YN0007: │ �[38;5;173mcore-js-pure�[39m�[38;5;111m@�[39m�[38;5;111mnpm:3.36.1�[39m must be built because it never has been before or the last one failed
428:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m YN0007: │ �[38;5;166m@redhat-developer/�[39m�[38;5;173mrhdh-plugins�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m must be built because it never has been before or the last one failed
429:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m ::endgroup::
...

507:  *************************************************************************************
508:  The conflicting file is backstage-plugin-boost-backend/report.api.md, with the following content:
509:  ## API Report File for "@red-hat-developer-hub/backstage-plugin-boost-backend"
510:  > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
511:  ```ts
512:  import type { AgenticProvider } from '@red-hat-developer-hub/backstage-plugin-boost-common';
513:  import type { AgentRecord } from '@red-hat-developer-hub/backstage-plugin-boost-common';
514:  import type { ApprovalRequest } from '@red-hat-developer-hub/backstage-plugin-boost-common';
515:  import { BackendFeature } from '@backstage/backend-plugin-api';
516:  import type { CacheService } from '@backstage/backend-plugin-api';
517:  import type { ConversationDetails } from '@red-hat-developer-hub/backstage-plugin-boost-common';
518:  import type { ConversationMessage } from '@red-hat-developer-hub/backstage-plugin-boost-common';
519:  import type { ConversationSummary } from '@red-hat-developer-hub/backstage-plugin-boost-common';
520:  import type { DatabaseService } from '@backstage/backend-plugin-api';
521:  import type { FeedbackRecord } from '@red-hat-developer-hub/backstage-plugin-boost-common';
522:  Error: API Extractor completed with 0 errors and 1 warnings
523:  import type { HttpAuthService } from '@backstage/backend-plugin-api';
...

971:  // @public
972:  export function validateConfigValue(
973:  key: BoostConfigKey,
974:  value: unknown,
975:  ): unknown;
976:  // @public
977:  export function validateSecurityMode(
978:  mode: string | undefined,
979:  logger: LoggerService,
980:  ): SecurityMode;
981:  ```
982:  *************************************************************************************
983:  * You have uncommitted changes to the public API or reports of a package.           *
984:  * To solve this, run `yarn build:api-reports` and commit all md file changes.       *
985:  *************************************************************************************
986:  ##[error]Process completed with exit code 1.
987:  Node 20 is being deprecated. This workflow is running with Node 24 by default. If you need to temporarily use Node 20, you can set the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 25, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:30 PM UTC · Completed 6:41 PM UTC
Commit: bf40fcb · View workflow run →

@fullsend-ai-review fullsend-ai-review 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.

See the review comment for full details.

Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.test.ts
Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.test.ts
Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.ts
Comment thread workspaces/boost/plugins/boost-backend/src/skills/routes.ts
…penspec

- Replace @janus-idp/cli with @red-hat-developer-hub/cli in all three
  boost package.json files (boost-backend, module-llamastack,
  module-kagenti) and update export-dynamic scripts to use rhdh-cli
- Split dynamic-plugins.yaml into two reference files:
  dynamic-plugins-filesystem-reference.yaml (local dev paths) and
  dynamic-plugins-image-reference.yaml (OCI registry paths)
- Add skills marketplace config examples (commented out) to both files
- Add section 8 (Skills Marketplace Integration) to boost openspec
  tasks.md with design decisions from grill-me review session
- Deduplicate yarn.lock after dependency changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 25, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 11:56 PM UTC · Completed 12:13 AM UTC
Commit: 4866641 · View workflow run →

- Remove stale boost.skillsMarketplace.enabled from config.d.ts and
  runtime-config spec.md (consolidated into boost.features.skillsMarketplace)
- Use InputError instead of res.status(400) in skills deploy route
- Add InputError mapping to test error handler
- Fix task ID references in plugin.ts comment (section 8, not 5.x)
- Add issue 16 (skills route improvements) to staged-issues.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 26, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:50 AM UTC · Completed 2:07 AM UTC
Commit: 4866641 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jun 26, 2026
@gabemontero gabemontero merged commit d196266 into main Jun 26, 2026
32 of 33 checks passed
@gabemontero gabemontero deleted the agent/3311-boost-packaging-deployment branch June 26, 2026 02:12
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
5.7% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature requires-manual-review Review requires human judgment workspace/boost Boost workspace (Backstage AI plugin)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

boost-packaging — Dynamic plugin packaging and deployment configuration (issue 15 of 15)

1 participant