Skip to content

contract: close the last ten duplications and make the .shape trap impossible #9762

Description

@JSONbored

⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.

Context

#9515's premise is that no tool, schema, or enum in this repo is written down twice. Every sub-issue has landed, but a sweep of the finished surface finds the premise not yet true in two ways.

1. Ten literal duplications of values @loopover/contract already exports. Each is a z.enum([...]) or array literal whose members are exactly a contract constant's, in a file that can import it:

Contract export Duplicated at
AUTONOMY_LEVELS src/openapi/schemas.ts:877, packages/loopover-mcp/bin/loopover-mcp.ts:352
MAINTAIN_ACTION_CLASSES packages/loopover-mcp/bin/loopover-mcp.ts:351
AGENT_ACTION_CLASSES (engine) src/openapi/schemas.ts:876
PUBLIC_SURFACE_SKIP_REASONS src/openapi/schemas.ts:1161, src/openapi/spec.ts:2208
FEASIBILITY_VERDICTS src/openapi/schemas.ts:2299
ROTATABLE_SECRET_NAMES src/mcp/server.ts:609
CONFIG_ADMIN_WRITE_SCOPES packages/loopover-miner/lib/{attempt-runner,harness-submission-trigger,loop-reentry}.ts

The two in the stdio bin carry a comment explaining they cannot be imported because that file resolves @loopover/engine through the published package, whose export map does not surface AUTONOMY_LEVELS (#6153). That reason is now stale: the bin imports @loopover/contract already, and the contract exports both. The comment is load-bearing documentation that is no longer true, which is worse than no comment.

2. Four registerTool call sites still pass .shape. src/mcp/server.ts:2435, :2440, :2445, :2450 hand AdminGetStatusInput.shape, AdminDoctorInput.shape, AdminTailLogsInput.shape, and AdminGetBackupStatusInput.shape to the SDK. The SDK re-wraps a raw shape in a plain z.object, which discards the catchall — the defect class #9518 named, where a looseObject output becomes additionalProperties: false and every extra field the payload carries turns into a -32602 the caller cannot act on.

None of those four inputs declares a catchall today, so nothing is broken right now. That is exactly what makes it worth closing: the four sites are indistinguishable from the correct ones by reading, and the day one of those inputs becomes loose the failure is silent and remote.

Requirements

  • Every duplication in the table above imports the contract's constant instead of restating its members. Where the value is the ENGINE's (AGENT_ACTION_CLASSES) and the contract does not export it, add it to the contract with a meta-test pinning it against the engine's, per the PREFLIGHT_LIMITS precedent in packages/loopover-contract/src/limits.ts.
  • The stale "cannot be imported" comment in the stdio bin is deleted along with the literals it justified.
  • The four registerTool call sites pass the schema OBJECT, not .shape.
  • A guard test asserts that no registerTool call anywhere passes a .shape, so the trap cannot be reintroduced — the failure is silent and remote, so a reviewer noticing is not a control.
  • npm run ui:openapi regenerated. The document is expected to be byte-identical (the constants have the same members in the same order); if it is not, the diff is a real drift the duplication was hiding and must be committed and named in the PR body.

⚠️ Required pattern: these are IMPORT substitutions, not schema rewrites. A z.enum([...]) becomes z.enum(CONSTANT) over the identical members — it does NOT change to a different zod construct, gain or lose .optional()/.nullable(), or get reordered. If a duplication turns out not to match its contract constant exactly, that mismatch is a defect: fix it and name it in the PR body rather than reshaping the constant to fit the copy.

Deliverables

  • All ten duplications import the contract constant; no literal member list remains at any of those sites
  • AGENT_ACTION_CLASSES available from the contract, pinned against the engine's by a meta-test
  • The stdio bin's stale #6153 "hand-synced literals, not imports" comment removed with its literals
  • The four registerTool sites pass schema objects
  • A guard test failing on any registerTool call that passes .shape, asserted to find zero today
  • apps/loopover-ui/public/openapi.json regenerated and committed
  • Any member mismatch found while substituting is fixed and named in the PR body

Test Coverage Requirements

src/** is in Codecov's include and the 99% branch-counted patch gate applies. The substitutions are value-identical, so coverage comes from the suites already exercising those schemas; the two NEW assertions — the AGENT_ACTION_CLASSES pin and the no-.shape guard — are the named regression tests for this issue and must both be shown failing against the pre-fix tree.

Expected Outcome

Every enum the MCP/zod surfaces validate against has exactly one definition, and the .shape trap that produced #9518's -32602 class cannot be reintroduced without CI saying so. #9515's premise becomes true rather than aspirational.

Links & Resources

packages/loopover-contract/src/enums.ts, src/openapi/schemas.ts:876-877,1161,2299, src/openapi/spec.ts:2208, src/mcp/server.ts:609,2435-2450, packages/loopover-mcp/bin/loopover-mcp.ts:337-352. Part of #9515; the sweep that closes it.

Metadata

Metadata

Assignees

Labels

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

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions