Skip to content

catalog-tools: create and prioritise catalog offerings (CL-7468) - #654

Merged
TheGreatAxios merged 15 commits into
mainfrom
cl-7468-catalog-offering-tools
Sep 6, 2026
Merged

TheGreatAxios merged 15 commits into
mainfrom
cl-7468-catalog-offering-tools

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Adds three write tools to @corbits/catalog-tools: create_offering, set_offering_priority, and disable_offering, letting an agent manage the tenant's own inference-catalog offerings.
  • The new tools call @corbits/catalog-tools' own workflow-run-authenticated mirror of the tenant-admin catalog routes (packages/catalog-tools/src/routes.ts's createWorkflowCatalogAdminRoutes, mounted in apps/hub at /api/workflow-catalog-admin), resolving a model's canonical name and a provider's name against this tenant's own catalog before writing, and refusing rather than guessing when either name isn't found.
  • create_offering and disable_offering are approval: "ask"; set_offering_priority only reorders an offering already live, so it stays ungated.
  • Bumped @corbits/catalog-tools to 0.0.4 for tool-package-freshness, and bumped the workflows/assistant tool-package pin to match.
  • The model:*/model-provider:* read and model-offering:* read/create/manage grants these routes' requireGrant calls need are planted on Myra's own run principal (ASSISTANT_RUN_PRINCIPAL_GRANTS, alongside CL-7467's access-tools grants), not tenant-wide via SEED_GRANTS — a tenant-wide grant would let every seeded principal administer the catalog, not just Myra.
  • One paragraph per new tool added to the package README, updated again to describe the run-authenticated mount.
  • Shortened the offering-tools bundle id (@corbits/catalog-tools/offerings → @corbits/catalog-tools/off) so set_offering_priority's qualified wire name fits the 64-char OpenAI-compatible tool-name cap.

Hub auth wiring

A workflow child authenticates with only its sidecar bearer token and run address, never a browser session, so it cannot reach the tenant-admin catalog routes (vendor/intx/hub-api/src/routes/{models,model-providers,model-offerings}.ts), which sit behind resolveTenant and accept only a session cookie. packages/catalog-tools/src/routes.ts mounts a workflow-run-authenticated mirror at /api/workflow-catalog-admin instead: it resolves the caller's tenant/principal the same way @corbits/artifacts-hub's createWorkflowRunAuthenticator resolves every other workflow-run mount in apps/hub, and authorizes through the same createRequireGrant/idResource against the same grant store and condition registry every tenant-session route gates on. Every offering write pushes the same pushSourceUpdatesSubtree sidecar refresh the tenant-session routes push, using the same sidecarRouter/credentialCipher already wired in apps/hub/src/index.ts. The admin client (packages/catalog-tools/src/client.ts) now points at this new mount and no longer carries a tenantId — the hub resolves the tenant from the sidecar credential the same way it already does for the read-only /api/workflow-inference-catalog surface.

Those tenant-admin route handlers (createModelCatalogRoutes/createModelProviderRoutes/createModelOfferingRoutes) aren't exported from @intx/hub-api's public surface, so routes.ts reimplements only their thin handler bodies against the shared model/modelProvider/modelOffering schema and @intx/types response shapes — never the grant-store authorization itself, and never a second copy of pushSourceUpdatesSubtree.

Review follow-up

  • Re-verified that createModelCatalogRoutes/createModelProviderRoutes/createModelOfferingRoutes genuinely aren't reachable: @intx/hub-api's package.json exports map publishes only "." (src/index.ts), and that entry point never re-exports the route factories (vendor/intx/hub-api/src/routes/model-offerings.ts:87). Delegating to them outright would require either editing that exports map — vendor/intx/hub-api is vendored and never hand-edited — or a relative import reaching past a workspace package's declared surface, a pattern no other package in this repo uses. The mount stays the thin, heavily-cross-referenced reimplementation described above.
  • Dropped the tautological ?? eq(x.id, x.id) fallback after each cursor or() call in routes.ts — both branches are always defined, so or() never actually returns undefined there. Replaced it with the same never-taken ?? sql\false`fallbackvendor/intx/hub-api's own cursorCondition` uses for the identical case.
  • Added an assertion to packages/tool-registry-publish/src/pack.test.ts (which already packs and re-imports every @corbits/*-tools package's tarball) that both catalogTools and catalogOfferingTools resolve by id from the packed tool.mjs, so a bundle silently dropped from index.ts's re-exports would fail this test instead of only showing up at deploy time.

Grant scoping

Grants land on Myra's own run principal, derived the same deterministic (tenantId, deploymentId) way CL-7467's access-tools grants are (plantAssistantRunPrincipalGrants, renamed from plantAssistantAccessToolsGrants now that it plants both sets), not on the tenant's shared principal. SEED_GRANTS no longer carries the catalog-offering resource/action pairs.

Test plan

  • bunx tsc --noEmit -p packages/catalog-tools
  • bunx tsc --noEmit -p apps/hub
  • bunx tsc --noEmit -p packages/seeding
  • bun run scripts/typecheck.ts (full repo)
  • bun test packages/catalog-tools packages/seeding (103 pass)
  • bun test packages/tool-registry-publish (69 pass)
  • bun run check:structural report-error tool-package-pins tool-package-freshness

Resolves CL-7468

Covers the happy path, a 403 from the hub, and an unknown canonical
name/provider name for create_offering, set_offering_priority, and
disable_offering ahead of the implementation.
…fering

Lets an agent manage the tenant's own inference catalog offerings
through the existing tenant-admin catalog routes, resolving a model's
canonical name and a provider's name against this tenant's own catalog
before writing, and refusing rather than guessing when either name
isn't found. create_offering and disable_offering require approval;
set_offering_priority only reorders an offering already live.

Seeds the model/model-provider/model-offering read, create, and
manage grants these routes require.
@linear-code

linear-code Bot commented Sep 6, 2026

Copy link
Copy Markdown

CL-7468

A workflow child authenticates with its sidecar bearer token and run
address, never a browser session, so it cannot reach the tenant-admin
catalog routes @intx/hub-api mounts for the settings UI. This adds a
mirror at /api/workflow-catalog-admin, gated by the same grant store
and pushing the same source-update refresh on every offering write.
create_offering, set_offering_priority, and disable_offering called
the tenant-session catalog routes directly, so a workflow run's
sidecar bearer and run address 401ed against them. Point the admin
client at the new /api/workflow-catalog-admin mount instead, mounted
in apps/hub beside its own read-only inference-catalog counterpart,
and drop the tenantId the client no longer needs to carry now that
the hub resolves it from the same credential.
The pin was still at 0.0.2 after the package's version moved to 0.0.3,
and this change moves it again to 0.0.4 for the new
workflow-run-authenticated routes.
The offering-management tools' README still described them as writing
through the tenant-admin catalog routes directly; point it at the
package's own workflow-run-authenticated mirror instead.
Resolves conflicts with the newly-merged access-tools work by keeping
both routes mounted and both grant sets. The catalog-offering grants
(model:*/model-provider:*/model-offering:* read/create/manage) now
land on Myra's own run principal alongside the access-tools grants,
following the same CL-7467 pattern, instead of tenant-wide via
SEED_GRANTS.
pack.test.ts already re-imports every @corbits tool package's packed
tarball and checks it exports at least one tool factory, but a bundle
dropped from catalog-tools' index.ts re-exports would still leave that
count above zero. Assert both catalogTools and catalogOfferingTools
resolve by id from the actual packed tool.mjs the sidecar loads.
or() is called with two always-defined branches in both the /models
and /providers cursor conditions, so it never actually returns
undefined; the `?? eq(x.id, x.id)` fallback was a no-op that always
matched. Replace it with `?? sql\`false\``, the same fallback
vendor/intx/hub-api's own cursorCondition uses for the identical
never-undefined case.
@TheGreatAxios
TheGreatAxios merged commit 426b493 into main Sep 6, 2026
7 checks passed
@TheGreatAxios
TheGreatAxios deleted the cl-7468-catalog-offering-tools branch September 6, 2026 16:33
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.

1 participant