Skip to content

contract: create @loopover/contract — the single zod source for tool/API schemas, with a six-tool pilot #9517

Description

@JSONbored

Context

Part of #9515 (keystone group). LoopOver's tool/API contracts live in four unshared zod sites — src/openapi/schemas.ts (~150 response schemas, spec-only), src/api/routes.ts (38 inline request schemas), src/mcp/server.ts (~116 tools' input/output shapes), packages/loopover-mcp/bin/loopover-mcp.ts (~85 shapes hand-mirroring the remote server, per their own comments, e.g. :376 "stdio mirror of the remote loopover_get_repo_onboarding_pack shape") — plus enum literals hand-copied from the engine (bin/loopover-mcp.ts:154-166: MAINTAIN_ACTION_CLASSES, MAINTAIN_AUTONOMY_LEVELS, PROPOSE_ACTION_CLASSES, "The drift this invites is real and has bitten once already") and a circular-import workaround that forces inline duplication of a REST schema (src/mcp/server.ts:262-266, fileIncidentReportShape vs postMergeIncidentReportSchema).

metagraphed proved the fix (landed as types-epics #7860 / #8068#8076): a dedicated zod schema tree with one naming convention, converted with z.toJSONSchema(…, { target: "draft-2020-12" }) at module load (no build step, nothing to regenerate), and a single listToolDefinitions() projection that every consumer derives from. This issue creates LoopOver's equivalent as a workspace package and proves it end-to-end on a pilot batch.

Decision record — recorded here, not re-litigated per PR

A new published workspace package packages/loopover-contract (@loopover/contract), not engine subpaths. Rationale: (a) the Worker src/, control-plane/, both stdio bins, packages/loopover-miner/lib, and apps/loopover-ui all need to import it — the Worker and UI deliberately do not depend on the full engine (the engine-parity:drift-check twin system exists precisely because they don't), and a zod-only leaf package keeps every bundle lean; (b) @loopover/mcp and @loopover/miner are published, so their schema dependency must be publishable too; (c) widening the engine's public export map was already considered and rejected once (bin/loopover-mcp.ts:154-157). Consequences: the package joins the release-please linked-versions lockstep group (engine,mcp,miner → add contract) and gets a test:contract-pack allowlist check mirroring scripts/check-mcp-package.ts. Overturn condition: only if the lockstep/publish overhead proves to outweigh the bundle-size cost of engine subpaths — record any reversal here first.

Requirements

  1. Package skeleton. packages/loopover-contract: zod v4 only (no other runtime deps), type: module, Workers-safe (no node builtins), subpath exports per domain (@loopover/contract/tools/<family>, @loopover/contract/enums, …). Wire into the workspace build (turbo), test:ci, release-please (lockstep group + .release-please-manifest.json + release-manifest:sync:check), and a scripts/check-contract-package.ts pack allowlist run as test:contract-pack.
  2. Schema conventions (documented in the package README, enforced by meta-test): one file per tool family; <ToolNamePascal>InputSchema / <ToolNamePascal>OutputSchema + z.infer type aliases; a shared.ts only for shapes reused 3+ times; output schemas are real object schemas — shallower than the REST response where wire-compat demands (metagraphed's documented wire-compat rule), but never bare z.unknown() at the top level.
  3. Tool metadata model. Each tool contributes a typed registry entry: name, title, description, category (existing MCP_TOOL_CATEGORIES vocabulary), MCP annotations (readOnlyHint/destructiveHint), auth (public | token | session | maintainer | operator | mcp-admin | internal), locality (remote | local-git | miner), availability (cloud | selfhost | both). The registry entry carries inputSchema/outputSchema on the entry itself — no name-keyed side maps (metagraphed residue: a typo'd key silently drops the schema).
  4. Projections. listToolDefinitions() as the single projection point (JSON Schema emitted via z.toJSONSchema at module load); buildAnthropicToolSpecs() / buildOpenAIToolSpecs() / buildAgentToolsIndex() as pure functions over it (feeds Epic: ORB maintainer chat platform — conversational command center for maintainers and repo owners (hosted-ready) #9183/Epic: hosted AMS chat platform — sign up, connect GitHub, converse with your miner #9184 grounding catalogs); an explicit McpToolDefinition interface annotation on the registry array (metagraphed's documented TS-bivariance lesson — without it heterogeneous handler types collapse).
  5. Enums single-sourced. Move MAINTAIN_ACTION_CLASSES, MAINTAIN_AUTONOMY_LEVELS, PROPOSE_ACTION_CLASSES, TEST_FRAMEWORKS, and the claim/plan status enums into @loopover/contract/enums; consumers (engine, Worker, both bins, miner) import them; the hand-copied literals and their pin-tests (test/unit/mcp-cli-maintain.test.ts) are replaced by direct imports.
  6. Pilot batch — six tools, end to end, both servers. Migrate exactly: loopover_get_repo_context, loopover_get_pr_reviewability, loopover_predict_gate, loopover_preflight_pr, loopover_local_status_structured, loopover_admin_get_config. For each: contract file with real input+output schemas; the remote server (src/mcp/server.ts) and the stdio server (packages/loopover-mcp/bin/loopover-mcp.ts) both register from the contract entry; the stdio handler for these six becomes fully typed (no any); the remote tool's placeholder output schema is replaced by the real one. The fileIncidentReportShape circular-import duplication is NOT in the pilot — it dissolves in the full migration.
  7. Tests. Registry meta-tests (unique names, description length, every entry has a compilable object outputSchema via Ajv, annotations present, category ∈ vocabulary, locality/auth/availability present); per-pilot-tool schema tests (valid + invalid input, sample output validates); 99% branch-counted patch coverage per house rule.
  8. Fix what you find (program principle 4) — any defect surfaced in the six pilot tools' handlers is fixed in the same PR series and noted in the PR body.

Non-goals

  • Migrating the remaining ~110 remote / 96 stdio tools (next issue — the batches).
  • REST route schemas and the OpenAPI seam (separate sub-issue).
  • New tools of any kind.

Deliverables

  • packages/loopover-contract published-ready package wired into build/CI/release
  • Conventions README + meta-tests enforcing them
  • Six pilot tools registered from the contract on both servers, fully typed, with real validated output schemas
  • Enums single-sourced with the hand-copies deleted
  • Decision record above kept current

Expected outcome

A schema change to a pilot tool is made in exactly one file and immediately propagates (as a compile error or Ajv failure, not silent drift) to both MCP servers and every projection — proving the mechanism the remaining batches will follow.

References

Part of #9515. Feeds #9183 / #9184 (tool catalogs) and #9282 (the UI imports response types from this package once routes migrate).

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions