Skip to content

feat(contract): add @loopover/contract, the single zod source for tool and API schemas - #9530

Merged
JSONbored merged 5 commits into
mainfrom
feat/loopover-contract-package-9517
Jul 28, 2026
Merged

feat(contract): add @loopover/contract, the single zod source for tool and API schemas#9530
JSONbored merged 5 commits into
mainfrom
feat/loopover-contract-package-9517

Conversation

@JSONbored

@JSONbored JSONbored commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

Adds @loopover/contract — the single zod source of truth for LoopOver's MCP tool and API contracts, and the keystone of #9515.

The problem. The same contracts are declared in four unshared places today: src/openapi/schemas.ts (~150 response schemas, spec-only), src/api/routes.ts (38 inline request schemas), src/mcp/server.ts (tool shapes), and packages/loopover-mcp/bin/loopover-mcp.ts (~85 shapes whose own comments say they mirror the remote server's, e.g. :376). Enum literals are hand-copied out of the engine with a comment noting the drift "has bitten once already" (:154-157).

The shape of the fix. A zod-only leaf package with no node builtins — that is what lets the Worker, both published stdio bins, the miner, and the UI all depend on it without pulling the engine in behind them. Sharing these through @loopover/engine was considered and rejected once already (#6153): @loopover/mcp resolves the engine through its published export map, which never surfaced the enums.

Key model decisions, all documented in the package README and enforced by meta-tests:

  • Schemas live on the entry, never in a name-keyed side map. metagraphed keeps output schemas in a TOOL_OUTPUT_SCHEMAS[name] lookup where a typo'd key silently drops the schema; here a tool without an output schema is a type error.
  • projectToolDefinitions() is the single projection point — MCP registration, the OpenAI/Anthropic spec builders 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 need, and the generated docs to come all derive from it.
  • Every entry declares auth, locality, availability. locality is what makes explicit why LoopOver cannot collapse to one MCP process: local-git tools read the caller's uncommitted working tree and miner tools read the miner box's stores, neither reachable from a Worker.
  • Inputs closed, outputs open (z.looseObject). An MCP output schema is a floor, not a fence — a server returning an extra field must not retroactively invalidate a client on the older schema.

Six pilot contracts, modelled from the engine types the handlers actually return rather than from the placeholder schemas advertised today. The remote server declares six of get_repo_context's eight fields — and all of predict_gate's blockers, warnings, and funnel — as bare z.unknown().optional(), which is precisely the information a caller needs most (a predicted FAIL is only actionable if the blocker list is readable).

Defects surfaced while writing this

Three real findings, all documented in code where they live:

  1. loopover_get_repo_context returns different payloads from the two servers. The remote handler builds eight keys; the stdio server proxies GET /v1/repos/:owner/:repo/intelligence, which carries seven additional keys and omits collisions entirely on its snapshot branch. Modelled here as an honest union (only fields both paths always emit are required). Converging the payloads is a wire change and belongs to contract(remote): migrate the remote MCP server's tools to @loopover/contract — typed handlers, real output schemas #9518's batch for this category.
  2. loopover_get_pr_reviewability likewise. The remote wraps the report in a freshness envelope and can answer forbidden/not_found with no report; the stdio server returns the bare PullRequestReviewability. Also modelled as a union. Related drift found in passing: the remote's shared freshnessResponseOutputSchema advertises a freshness field the handler never emits.
  3. Input bounds disagreed between servers. predict_gate's changedPaths element cap was 300 remote / 400 stdio. The contract takes the wider bound — a shared contract may widen an input but never tighten one, and narrowing would start rejecting paths the stdio server accepts today.

A fourth was caught by my own meta-test rather than by reading: zod v4's z.object strips unknown keys rather than rejecting them, so the advertised additionalProperties: false and the runtime behavior disagree. I did not change it — that would be a wire-visible tightening inside a migration that promised none. The current behavior is pinned by a test and the decision is recorded for #9518.

Scope note recorded on the issue

packages/loopover-engine/src/settings/autonomy.ts still declares its own AUTONOMY_LEVELS/AGENT_ACTION_CLASSES because it is an engine-parity twin of src/settings/autonomy.ts (guarded by engine-parity:drift-check). Inverting that pair to import from the contract is #9518's batch work. Until then the values are pinned against the engine's by meta-tests, so they cannot silently disagree — the same technique test/unit/mcp-cli-maintain.test.ts already used for the stdio hand-copy this package replaces. PREFLIGHT_LIMITS is pinned the same way.

Server wiring (second commit)

Both servers now register the six pilot tools from the contract. The stdio server's hand-mirrored shapes are gone for these tools, and four of them gain a real outputSchema for the first time — previously loopover_local_status_structured was the only tool in that entire package declaring one.

Pre-existing OpenAPI drift fixed while here

Modelling the real payloads surfaced two published-spec lies, both fixed with openapi.json regenerated:

  • CollisionItemSchema.type omitted "recent_merged_pull_request" — a discriminant buildCollisionReport genuinely emits, since it is handed recent merged PRs. A client validating a real response against the published spec rejected it. Its labels, linkedIssues, linkedIssueClaimedAt, changedFiles and body fields were missing too.
  • QueueHealthSchema omitted draftPullRequests, slopFlaggedPullRequests, duplicateFlaggedPullRequests — all required on the QueueHealth type and always emitted — plus the optional rankedPullRequests array.

One more is recorded but not fixed here: LocalBranchAnalysisSchema does not declare the predictedGate field the route emits. Fixing it means authoring the full PredictedGateVerdict response schema, which now exists in the contract package and lands with #9519's route work.

Closes #9517

Scope

  • The PR title follows type(scope): short summary Conventional Commit format, for example fix(api): restore profile access checks.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked a currently open issue this PR resolves (e.g. Closes #123) — a linked open issue is required for every contributor PR.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage locally — see note below
  • npm run test:workers
  • npm run build:mcp (unaffected; contract not yet consumed)
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

test/unit/contract-registry.test.ts — 24 tests, all green: registry invariants (unique names, description length, vocabulary membership), Ajv-compilability of every emitted input and output schema, the open-output-schema wire-compat rule, filter behavior on all three axes including that both survives an availability filter, agent-spec projection parity, empty-list edges, and the engine pin tests for autonomy levels, action-class subset-ness, and PREFLIGHT_LIMITS.

If any required check was skipped, explain why:

  • The package is not yet consumed by any runtime (server wiring is the next PR), so no existing surface changes behavior. Unchecked items cover surfaces this diff does not touch — no UI, no OpenAPI, no worker, no workflow changes. CI runs them in full.
  • packages/loopover-contract/src/** is added to vitest.config.ts's coverage include list rather than exempted, so it is graded like any other source surface.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests. (n/a — none touched; the auth field here is a declaration the runtimes will enforce in a later PR, not an enforcement change.)
  • API/OpenAPI/MCP behavior is updated and tested where needed. (No wire behavior change at all: no server registers from this package yet, so every advertised tool schema is byte-identical to before.)
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks. (n/a)
  • Visible UI changes include a UI Evidence section below. (n/a — no visible change)
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs.

Notes

Part of #9515. Wired into turbo.json (leaf build task, no ^build edge) and vitest.config.ts coverage. Release-please registration and the test:contract-pack allowlist land with the first PR that actually publishes a consumer, so the package is not released before anything depends on it.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 28, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
loopover-ui 6192bad Commit Preview URL

Branch Preview URL
Jul 28 2026, 07:59 AM

@loopover-orb

loopover-orb Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Warning

⏸️ LoopOver review result - manual review recommended

Review updated: 2026-07-28 08:16:46 UTC

33 files · 1 AI reviewer · no blockers · CI green · clean

⏸️ Suggested Action - Manual Review

  • Touches a guarded path — held for manual review: This PR changes guardrail-protected path(s): .github/workflows/ci.yml (matched .github/workflows/**), .github/workflows/release-selfhost.yml (matched .github/workflows/**), .github/workflows/selfhost.yml (matched .github/workflows/**).

Review summary
This PR adds `@​loopover/contract`, a zod-only leaf package centralizing tool/API schemas for six pilot tools, and rewires `src/mcp/server.ts`, `packages/loopover-mcp/bin/loopover-mcp.ts`, and `src/openapi/schemas.ts` to consume it instead of hand-mirrored shapes. The design (schemas on the entry not a side map, single `projectToolDefinitions` projection point, documented divergence between remote/stdio payloads rather than papering over it) is well-reasoned and closes issue #9517, the keystone of #9515. CI's `validate-tests`/`validate` failures are undetailed and this branch is 2 commits behind main, so per the grounding rules I can't assign a content-level cause to them from this diff alone.

Nits — 7 non-blocking
  • packages/loopover-contract/src/limits.ts and admin-config.ts/preflight-pr.ts/local-status.ts scatter bare numeric literals (200, 300, 20_000, 400, 3, etc.) without named intent — most are already explained in adjacent comments, but a couple (admin-config.ts:25's `3`/`200`, local-status.ts:17's `3`) could use a short inline note like the others do.
  • Test-to-code ratio is low (0.23) for a new package — only test/unit/contract-registry.test.ts covers the meta-conventions; consider a follow-up asserting each tool's `output` schema actually validates a real sample payload rather than only checking `instanceof z.ZodObject`.
  • The `enums.ts:40` PROPOSE_ACTION_CLASSES comment references REST + CLI mirror for loopover_propose_action #6744 and the admin-config.ts scope note references mcp(observability): PostHog error tracking, tracing, and usage telemetry at every dispatch chokepoint — redacted, gated, registry-driven #9525 — worth double-checking these issue numbers are accurate since they're load-bearing documentation for future readers.
  • package-lock.json and apps/loopover-ui/public/openapi.json are large generated-artifact diffs bundled with the source change; unavoidable for a new workspace package + regenerated OpenAPI spec, so this is a note not a real bundling concern.
  • Consider adding a round-trip test in test/unit/contract-registry.test.ts that parses a real fixture payload (e.g. a captured `QueueHealth` or `CollisionReport` response) against the corresponding contract schema to catch shape drift, not just structural registry conventions.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.
  • Touches a guarded path — held for manual review — A maintainer must review and merge this change.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ⚠️ Gate result — Not blocking (Advisory; not blocking this PR.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #9517
Related work ⚠️ 2 scoped overlaps Top overlaps are listed below; lower-confidence bulk is hidden.
Change scope ❌ 8/20 High review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 14 registered-repo PR(s), 13 merged, 326 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 326 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Linked issue satisfaction

Partially addressed
The diff delivers a substantial, well-structured @​loopover/contract package (schemas, tool-definition model, enums, projections, package skeleton) and migrates the six pilot tools' input/output schemas into the stdio bin with real output schemas replacing placeholders, closely matching requirements 1-6. However, the truncated diff shows no evidence of the remote server (src/mcp/server.ts) being up

Review context
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Then work through the remaining 3 steps in the Signals table above.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

Decision record
  • action: hold · clause: guardrail_hold
  • config: 9eda9f53a1697f8bab286a350258126938a752f3c9bc0d7d545843e4eecc27e8 · pack: oss-anti-slop · ci: failed
  • record: 5fff20082576f278798c1eb9addcfbfd61b9991a17e13b62224d5734520fa8d0 (schema v5, head d9cea33)
Visual preview
Route Viewport Before (production) After (this PR's preview) Diff
/ desktop before /
before /
after /
after /
/ mobile before / (mobile)
before / (mobile)
after / (mobile)
after / (mobile)

Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy.

Scroll preview
Route Before (production) After (this PR's preview)
/ before / (scroll)
before / (scroll)
after / (scroll)
after / (scroll)

A short scroll-through clip (desktop) — click either thumbnail to open the full animation. Evidence for scroll-linked behavior a single screenshot can't show.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.59%. Comparing base (8751b9a) to head (6192bad).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #9530   +/-   ##
=======================================
  Coverage   89.58%   89.59%           
=======================================
  Files         846      858   +12     
  Lines      110384   110452   +68     
  Branches    26295    26298    +3     
=======================================
+ Hits        98889    98957   +68     
  Misses      10231    10231           
  Partials     1264     1264           
Flag Coverage Δ
backend 95.30% <100.00%> (+<0.01%) ⬆️
control-plane 99.86% <ø> (ø)
engine 65.80% <ø> (ø)
rees 89.62% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
packages/loopover-contract/src/agent-specs.ts 100.00% <100.00%> (ø)
packages/loopover-contract/src/enums.ts 100.00% <100.00%> (ø)
packages/loopover-contract/src/limits.ts 100.00% <100.00%> (ø)
packages/loopover-contract/src/shared.ts 100.00% <100.00%> (ø)
packages/loopover-contract/src/tool-definition.ts 100.00% <100.00%> (ø)
...ckages/loopover-contract/src/tools/admin-config.ts 100.00% <100.00%> (ø)
packages/loopover-contract/src/tools/index.ts 100.00% <100.00%> (ø)
...ckages/loopover-contract/src/tools/local-status.ts 100.00% <100.00%> (ø)
...es/loopover-contract/src/tools/pr-reviewability.ts 100.00% <100.00%> (ø)
...ckages/loopover-contract/src/tools/predict-gate.ts 100.00% <100.00%> (ø)
... and 5 more

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 2.5kB (0.03%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
loopover-ui 7.65MB 2.5kB (0.03%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: loopover-ui

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/add-scalar-classes-CnWs87_q.js (New) 2.16MB 2.16MB 100.0% 🚀
assets/tanstack-vendor-CX-Jt_eN.js (New) 863.57kB 863.57kB 100.0% 🚀
openapi.json 1.74kB 624.8kB 0.28%
assets/docs.fumadocs-spike-api-reference-DhQiSOIH.js (New) 443.45kB 443.45kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-D2B6IH5Y.js (New) 201.7kB 201.7kB 100.0% 🚀
assets/modal-BF0b6UFQ.js (New) 184.5kB 184.5kB 100.0% 🚀
assets/client-Cv4X-0i9.js (New) 151.47kB 151.47kB 100.0% 🚀
assets/maintainer-panel-DIH7Zpip.js (New) 78.99kB 78.99kB 100.0% 🚀
assets/routes-D3BC9Bgh.js (New) 35.96kB 35.96kB 100.0% 🚀
assets/owner-panel-DCtPzODI.js (New) 27.92kB 27.92kB 100.0% 🚀
assets/app-BMnJqXkd.js (New) 25.78kB 25.78kB 100.0% 🚀
assets/ui-vendor-B9hN_umd.js (New) 24.57kB 24.57kB 100.0% 🚀
assets/miner-panel-BRruwtDb.js (New) 20.24kB 20.24kB 100.0% 🚀
assets/app.runs-Bs3InsJX.js (New) 20.22kB 20.22kB 100.0% 🚀
assets/api._op-DI1tHEK9.js (New) 17.57kB 17.57kB 100.0% 🚀
assets/self-hosting-docs-audit-FuOMdgUr.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/docs._slug-z3oc91c9.js (New) 15.52kB 15.52kB 100.0% 🚀
assets/playground-panel-C6Tk83d_.js (New) 14.42kB 14.42kB 100.0% 🚀
assets/fairness-CpsnOdKW.js (New) 10.73kB 10.73kB 100.0% 🚀
assets/app.audit-BGkxs4-H.js (New) 10.08kB 10.08kB 100.0% 🚀
assets/app.config-generator-CXIfxcju.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/maintainers-u6PZCpu2.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-BQRkohtV.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/agents-D8tied21.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/commands-panel-BDwOqJMt.js (New) 6.65kB 6.65kB 100.0% 🚀
assets/maintainer-workflow-BOekEbO0.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/digest-panel-ClD3znra.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-CAipDQcP.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs-nav-Bd8BFjAk.js (New) 6.01kB 6.01kB 100.0% 🚀
assets/docs.index-DAk1-5V1.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/api.index-D96cc97l.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/docs-C847THLB.js (New) 2.7kB 2.7kB 100.0% 🚀
assets/api-CWcvxN47.js (New) 2.69kB 2.69kB 100.0% 🚀
assets/docs-page-uf_uFB81.js (New) 2.1kB 2.1kB 100.0% 🚀
assets/table-BBivlrMO.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-DEDWivm6.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-Dtutuc6p.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-DH-H1T5E.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-8qT8aN7A.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-BM7nYXYr.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/app.maintainer-CKy66VN5.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-G2cLg4G1.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-JJLJ8yyb.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-DOJ4qiT6.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-BKsT5bCo.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-BBG0VjOm.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-DKQ57G1Y.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-BZeVeWCm.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-DlGZizSR.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-UP7UkHmu.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/trash-2-oNc4oId1.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/save-ChSQAILF.js (New) 327 bytes 327 bytes 100.0% 🚀
assets/git-pull-request-arrow-Bpog29ZH.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-bTr7eD_W.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-BwRbFVf6.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history-i7__rYe9.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-QpE56SUg.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-BWrtZ2im.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/rotate-cw-5afTez4O.js (New) 201 bytes 201 bytes 100.0% 🚀
assets/play-DiTR1qex.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-9hzPbMM_.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/search-DWSvoBPv.js (New) 174 bytes 174 bytes 100.0% 🚀
assets/add-scalar-classes-BiJ2EYZC.js (Deleted) -2.16MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-Dpm0HCBI.js (Deleted) -862.81kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-DUTRBWoc.js (Deleted) -443.45kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-D_KUTMH_.js (Deleted) -201.7kB 0 bytes -100.0% 🗑️
assets/modal-D8ojZG0Z.js (Deleted) -184.5kB 0 bytes -100.0% 🗑️
assets/client-CdVzAeF0.js (Deleted) -151.47kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-CcVXeWvS.js (Deleted) -78.99kB 0 bytes -100.0% 🗑️
assets/routes-Cb5UhhF2.js (Deleted) -35.96kB 0 bytes -100.0% 🗑️
assets/owner-panel-H4JtG5ux.js (Deleted) -27.92kB 0 bytes -100.0% 🗑️
assets/app-kkE4Uzym.js (Deleted) -25.78kB 0 bytes -100.0% 🗑️
assets/ui-vendor-DTGFF7e-.js (Deleted) -24.57kB 0 bytes -100.0% 🗑️
assets/miner-panel-98QT1fcP.js (Deleted) -20.24kB 0 bytes -100.0% 🗑️
assets/app.runs-DeKF1Hkv.js (Deleted) -20.22kB 0 bytes -100.0% 🗑️
assets/api._op-DHqdj6kA.js (Deleted) -17.57kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-C-rttmRE.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/docs._slug-CUKmfdSY.js (Deleted) -15.52kB 0 bytes -100.0% 🗑️
assets/playground-panel-BEoutzN5.js (Deleted) -14.42kB 0 bytes -100.0% 🗑️
assets/fairness-Bt0_E8CI.js (Deleted) -10.73kB 0 bytes -100.0% 🗑️
assets/app.audit-7v4ddyoe.js (Deleted) -10.08kB 0 bytes -100.0% 🗑️
assets/app.config-generator-jHnv_uSI.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/maintainers-CB5I-QrG.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-CESH99Ak.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/agents-i9uTPDSA.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/commands-panel-DI88r7z8.js (Deleted) -6.65kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-BfrONh82.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/digest-panel-jWy_lPgK.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-DjcAmw8J.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs-nav-WgNF_aYq.js (Deleted) -6.01kB 0 bytes -100.0% 🗑️
assets/docs.index-BGs3wGLm.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/api.index-DBIfA7F8.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/docs-DQi29QIB.js (Deleted) -2.7kB 0 bytes -100.0% 🗑️
assets/api-D9aBn0vT.js (Deleted) -2.69kB 0 bytes -100.0% 🗑️
assets/docs-page-DLtkrv5r.js (Deleted) -2.1kB 0 bytes -100.0% 🗑️
assets/table-6OpAQym5.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-Cqn-hZiK.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-otYfnD8m.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-RShTrhm0.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-CDawofeV.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-EKi7iFBk.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-P5iPo5Zm.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-BBN0xN5d.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-Brwnzxt7.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-CXrsBKTZ.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-Bg-MmAyw.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-CjqCiMUR.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-BRRrLyaa.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-CqtdDevR.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round-BdgzGKiy.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-CaJHUw_j.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/trash-2-3h8Vdfud.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/save-Cbo9i_w6.js (Deleted) -327 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-DPCly8mS.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-BdWIzW7A.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-uUbixOap.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history-fwVrLVrW.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square-BcjN1cpj.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-CGZYIjFk.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/rotate-cw-DVHv5LOW.js (Deleted) -201 bytes 0 bytes -100.0% 🗑️
assets/play-Cl5Typ5W.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check-CzAbGLel.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️
assets/search-BIkmAtvM.js (Deleted) -174 bytes 0 bytes -100.0% 🗑️

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 28, 2026
@JSONbored
JSONbored force-pushed the feat/loopover-contract-package-9517 branch from 4fd1261 to d9cea33 Compare July 28, 2026 07:04
JSONbored added a commit that referenced this pull request Jul 28, 2026
…payload

Rebasing #9530 onto main after the engine-build-order fix (#9516) surfaced tests
whose fixtures no longer matched what the tools' new output schemas actually
require -- each one a real drift the placeholder z.unknown() schemas had been
hiding, not a false positive from the schema itself.

mcp-cli-plan-scorer-tools.test.ts and mcp-cli-harness.ts's shared predictedGate
fixture omitted predicted/basis/funnel/note, all required on PredictedGateVerdict
and always emitted by buildPredictedGateVerdict -- the fixture had silently
drifted since #9517's placeholder schema never caught it.

mcp-pr-reviewability.test.ts persisted partial cached snapshot payloads (missing
score/action/noiseSources/whyThisHelps/maintainerNextSteps/privateSummary), but
src/api/routes.ts's reviewability route always persists the full computed object
-- made the three fixtures match what production actually writes. One genuine
schema gap this surfaced: generatedAt needed to become optional in the report
schema, because the remote handler's own fallback chain
(cached.generatedAt || payload.generatedAt || new Date().toISOString()) proves a
stored payload's generatedAt is not guaranteed -- the schema now describes that
real, defended-against case instead of only the freshly-computed one.

mcp-predict-gate.test.ts pinned the OLD 300-char per-path bound the remote server
used before #9517 unified it to 400 (the wider of the two servers' historical
limits, per that issue's documented never-tighten-an-input rule) -- updated to
exercise the new bound rather than reverting it.

Full unit suite: 23041 tests, 1208 files, all green.
…l and API schemas

LoopOver declares the same contracts in four unshared places: src/openapi/schemas.ts
(responses, spec-only), src/api/routes.ts (request bodies), src/mcp/server.ts (tool
shapes), and packages/loopover-mcp/bin/loopover-mcp.ts (~85 shapes whose own comments
say they mirror the remote server's). This package is the one place they can live.

It is a zod-only leaf with no node builtins, which is the property that lets the
Worker, both published stdio bins, the miner, and the UI depend on it. Sharing these
through @loopover/engine was rejected once already (#6153): @loopover/mcp resolves the
engine through its published export map, which never surfaced the enums, so importing
them would have meant widening the engine's public API.

The model carries schemas ON each entry rather than in a name-keyed side map, so a
tool without an output schema is a type error rather than a silently dropped lookup,
and projectToolDefinitions is the single point every consumer derives from -- MCP
registration, the OpenAI/Anthropic spec builders that #9183/#9184 need, and the
generated docs to come. Each entry declares auth, locality, and availability;
locality is what makes explicit why LoopOver cannot collapse to one MCP process.

Six pilot contracts, modelled from the engine types the handlers actually return
rather than from the placeholder schemas they advertise today -- the remote server
declared six of get_repo_context's eight fields, and all of predict_gate's blockers,
warnings, and funnel, as bare z.unknown(). Writing them surfaced two divergences that
are modelled honestly as unions and left for #9518 to converge: get_repo_context and
get_pr_reviewability return DIFFERENT payloads from the remote and stdio servers.

Input bounds take the wider of the two servers' historical limits where they
disagreed, since a shared contract may widen an input but never tighten one.

Refs #9517
… three drifted OpenAPI schemas

Both MCP servers now take the six pilot tools' input AND output schemas from
@loopover/contract instead of declaring them locally. The stdio server's
hand-mirrored shapes are gone for these tools, and four of them gain a real
outputSchema for the first time -- previously only loopover_local_status_structured
declared one anywhere in that package.

Fixes found while modelling the real payloads, all cases of the published spec
disagreeing with what the code actually returns:

- CollisionItemSchema's `type` enum omitted "recent_merged_pull_request", which
  buildCollisionReport genuinely emits (it is handed recent merged PRs). A client
  validating a real response against the spec rejected it. Its labels, linkedIssues,
  linkedIssueClaimedAt, changedFiles and body fields were missing too.
- QueueHealthSchema omitted draftPullRequests, slopFlaggedPullRequests and
  duplicateFlaggedPullRequests -- all required on the QueueHealth type and always
  emitted -- plus the optional rankedPullRequests array.

openapi.json is regenerated to match.

Refs #9517
…dles src/

src/mcp/server.ts and packages/loopover-mcp both import @loopover/contract, whose
package exports resolve to dist/ -- so every surface that typechecks or bundles
src/ needs it emitted first, and nothing did. CI's Typecheck and UI typecheck
failed on exactly this, and the selfhost image would have failed at esbuild
resolution: the Dockerfile builds the engine but had no step for contract, so
build-selfhost.ts could not have resolved the import.

Adds the build to ci.yml (before the engine step), the Dockerfile, selfhost.yml
and release-selfhost.yml. Unconditional rather than path-gated: every consumer
(backend, mcp, miner, ui) can pull it into the typecheck surface, and a zod-only
leaf with no workspace dependencies is cheap enough that gating buys nothing but a
class of skipped-build failures. Same shape as the engine build-order fix, applied
before that step for the same reason.

Verified by running build-selfhost.ts --all against a clean contract build.

Refs #9517
…payload

Rebasing #9530 onto main after the engine-build-order fix (#9516) surfaced tests
whose fixtures no longer matched what the tools' new output schemas actually
require -- each one a real drift the placeholder z.unknown() schemas had been
hiding, not a false positive from the schema itself.

mcp-cli-plan-scorer-tools.test.ts and mcp-cli-harness.ts's shared predictedGate
fixture omitted predicted/basis/funnel/note, all required on PredictedGateVerdict
and always emitted by buildPredictedGateVerdict -- the fixture had silently
drifted since #9517's placeholder schema never caught it.

mcp-pr-reviewability.test.ts persisted partial cached snapshot payloads (missing
score/action/noiseSources/whyThisHelps/maintainerNextSteps/privateSummary), but
src/api/routes.ts's reviewability route always persists the full computed object
-- made the three fixtures match what production actually writes. One genuine
schema gap this surfaced: generatedAt needed to become optional in the report
schema, because the remote handler's own fallback chain
(cached.generatedAt || payload.generatedAt || new Date().toISOString()) proves a
stored payload's generatedAt is not guaranteed -- the schema now describes that
real, defended-against case instead of only the freshly-computed one.

mcp-predict-gate.test.ts pinned the OLD 300-char per-path bound the remote server
used before #9517 unified it to 400 (the wider of the two servers' historical
limits, per that issue's documented never-tighten-an-input rule) -- updated to
exercise the new bound rather than reverting it.

Full unit suite: 23041 tests, 1208 files, all green.
…ttributes correctly

@loopover/contract is consumed via its package specifier everywhere, which
resolves through node_modules' exports map straight to compiled dist/*.js --
unlike packages/loopover-mcp/miner's RELATIVE ../lib/foo.js imports, which fall
back to a sibling .ts when nothing exists at the .js path. A package-specifier
resolution that already finds a real compiled file has no such fallback, so v8
instrumented dist/ (gitignored, invisible to Codecov) instead of
packages/loopover-contract/src/**/*.ts. Confirmed live: #9530 reported 0% patch
coverage on fully-tested contract source.

Aliased both import specifiers straight to source. The /tools subpath alias has
to be declared BEFORE the bare @loopover/contract entry: Vite's string-find
alias matcher treats a plain string as matching both the exact specifier and
anything starting with find + "/", first-match-wins in declaration order --
with the bare entry first it silently intercepted the /tools import too and
rewrote it to a bogus path, breaking resolution outright. Found by reproducing
the failure with a throwaway probe test before reordering, not assumed.

Verified: a scoped coverage run against contract-registry.test.ts + the pilot
tool tests now shows 100% statement/branch/function/line coverage on the
contract package, where before the alias it was flat 0%.
@JSONbored
JSONbored force-pushed the feat/loopover-contract-package-9517 branch from d9cea33 to 6192bad Compare July 28, 2026 07:56
@JSONbored
JSONbored merged commit 95f1524 into main Jul 28, 2026
11 checks passed
@JSONbored
JSONbored deleted the feat/loopover-contract-package-9517 branch July 28, 2026 08:17
JSONbored added a commit that referenced this pull request Jul 28, 2026
…s that transitively need it

validate-code failed on this PR with 'Cannot find module @loopover/contract/tools'
plus five downstream implicit-any errors in src/mcp/server.ts -- none of which this
PR touches.

#9530 added @loopover/contract, and src/mcp/server.ts imports @loopover/contract/tools.
Three turbo typecheck tasks pull that file into their program without any build edge
to the package:

- @loopover/ui#typecheck: apps/loopover-ui/tsconfig.json includes
  $TURBO_ROOT$/worker-configuration.d.ts, which imports './src/index' -- so the ENTIRE
  Worker is in the UI's typecheck program (src/index.ts -> src/api/routes.ts ->
  src/mcp/server.ts -> @loopover/contract/tools). Confirmed with tsc --explainFiles,
  not inferred. apps/loopover-ui has no package.json dependency on contract, so ^build
  never builds it.
- //#typecheck: a root task, where a root package.json dependency creates no build edge.
- @loopover/ui-miner#typecheck: reaches packages/loopover-miner/lib/**, which imports
  the package, and miner-ui has no dependency on it either.

Each already carries an explicit @loopover/engine#build edge for precisely this reason
-- turbo.json's own comment there describes the same scheduling race, observed
intermittently in validate-code, that bit here.

Cache-dependent, which is why it looked like flakiness: turbo caches these tasks, so
the failure only appears on a cache MISS. Other open PRs are green on cache hits.

Verified both directions from a clean contract build state (dist/ and .tsbuildinfo both
removed, --force):
- with the edges: 4 tasks successful, contract built first, typecheck passes
- without them: the identical six errors CI reported
JSONbored added a commit that referenced this pull request Jul 28, 2026
…s that transitively need it

validate-code failed on this PR with 'Cannot find module @loopover/contract/tools'
plus five downstream implicit-any errors in src/mcp/server.ts -- none of which this
PR touches.

#9530 added @loopover/contract, and src/mcp/server.ts imports @loopover/contract/tools.
Three turbo typecheck tasks pull that file into their program without any build edge
to the package:

- @loopover/ui#typecheck: apps/loopover-ui/tsconfig.json includes
  $TURBO_ROOT$/worker-configuration.d.ts, which imports './src/index' -- so the ENTIRE
  Worker is in the UI's typecheck program (src/index.ts -> src/api/routes.ts ->
  src/mcp/server.ts -> @loopover/contract/tools). Confirmed with tsc --explainFiles,
  not inferred. apps/loopover-ui has no package.json dependency on contract, so ^build
  never builds it.
- //#typecheck: a root task, where a root package.json dependency creates no build edge.
- @loopover/ui-miner#typecheck: reaches packages/loopover-miner/lib/**, which imports
  the package, and miner-ui has no dependency on it either.

Each already carries an explicit @loopover/engine#build edge for precisely this reason
-- turbo.json's own comment there describes the same scheduling race, observed
intermittently in validate-code, that bit here.

Cache-dependent, which is why it looked like flakiness: turbo caches these tasks, so
the failure only appears on a cache MISS. Other open PRs are green on cache hits.

Verified both directions from a clean contract build state (dist/ and .tsbuildinfo both
removed, --force):
- with the edges: 4 tasks successful, contract built first, typecheck passes
- without them: the identical six errors CI reported
JSONbored added a commit that referenced this pull request Jul 28, 2026
…cabulary, and make inert config queryable (#9554, #9555, #9433) (#9556)

* fix(gate,review,ops): add glob exclusions, tier the public-comment vocabulary, and make inert config queryable (#9554, #9555, #9433)

* test: cover the exclusion matcher from both import identities so shard attribution cannot miss it

* test(gate): cover the presence-mode staleness checkpoint and two long-untested reject arms

Brings packages/loopover-engine/src/review/screenshot-table-gate.ts and
src/signals/change-guardrail.ts (and both re-export shims) to 100% statement,
branch and function coverage.

Three branches had no test on any identity:

- evaluateScreenshotTableGate's PRESENCE-mode freshness checkpoint. Matrix mode's
  identical correlation was pinned by #8866's tests, but presence mode runs it
  through a separate return site and never saw a headSha in a test. Presence mode
  is the DEFAULT shape, so the miss meant one table pasted on push #1 could hold
  the gate green for every later push -- the exact regression the checkpoint
  exists to stop. Six tests: first-push checkpoint, stale-on-new-head (asserting
  no checkpoint is re-issued, which would launder the staleness away after one
  extra push), re-affirmation with fresh URLs, custom message on the stale path,
  no-headSha degradation, and same-head replay.

- guardrailPathMatches' empty-path skip, a deliberate divergence from matchesAny's
  fail-safe: the boolean form matches an empty path under an over-complex glob,
  the structured form must not, because its output is rendered verbatim into
  public review text and audit metadata.

- extractTableRows' non-table reject arm, which is what stops ordinary PR prose
  (and a shell pipe inside backticks) from parsing as table rows.

Mirrored across both import identities so a sharded, flag-merged coverage upload
cannot report a branch as uncovered on one copy.

* fix(ci): declare @loopover/contract#build on the three typecheck tasks that transitively need it

validate-code failed on this PR with 'Cannot find module @loopover/contract/tools'
plus five downstream implicit-any errors in src/mcp/server.ts -- none of which this
PR touches.

#9530 added @loopover/contract, and src/mcp/server.ts imports @loopover/contract/tools.
Three turbo typecheck tasks pull that file into their program without any build edge
to the package:

- @loopover/ui#typecheck: apps/loopover-ui/tsconfig.json includes
  $TURBO_ROOT$/worker-configuration.d.ts, which imports './src/index' -- so the ENTIRE
  Worker is in the UI's typecheck program (src/index.ts -> src/api/routes.ts ->
  src/mcp/server.ts -> @loopover/contract/tools). Confirmed with tsc --explainFiles,
  not inferred. apps/loopover-ui has no package.json dependency on contract, so ^build
  never builds it.
- //#typecheck: a root task, where a root package.json dependency creates no build edge.
- @loopover/ui-miner#typecheck: reaches packages/loopover-miner/lib/**, which imports
  the package, and miner-ui has no dependency on it either.

Each already carries an explicit @loopover/engine#build edge for precisely this reason
-- turbo.json's own comment there describes the same scheduling race, observed
intermittently in validate-code, that bit here.

Cache-dependent, which is why it looked like flakiness: turbo caches these tasks, so
the failure only appears on a cache MISS. Other open PRs are green on cache hits.

Verified both directions from a clean contract build state (dist/ and .tsbuildinfo both
removed, --force):
- with the edges: 4 tasks successful, contract built first, typecheck passes
- without them: the identical six errors CI reported
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant