⚠️ 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
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.
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/contractalready exports. Each is az.enum([...])or array literal whose members are exactly a contract constant's, in a file that can import it:AUTONOMY_LEVELSsrc/openapi/schemas.ts:877,packages/loopover-mcp/bin/loopover-mcp.ts:352MAINTAIN_ACTION_CLASSESpackages/loopover-mcp/bin/loopover-mcp.ts:351AGENT_ACTION_CLASSES(engine)src/openapi/schemas.ts:876PUBLIC_SURFACE_SKIP_REASONSsrc/openapi/schemas.ts:1161,src/openapi/spec.ts:2208FEASIBILITY_VERDICTSsrc/openapi/schemas.ts:2299ROTATABLE_SECRET_NAMESsrc/mcp/server.ts:609CONFIG_ADMIN_WRITE_SCOPESpackages/loopover-miner/lib/{attempt-runner,harness-submission-trigger,loop-reentry}.tsThe two in the stdio bin carry a comment explaining they cannot be imported because that file resolves
@loopover/enginethrough the published package, whose export map does not surfaceAUTONOMY_LEVELS(#6153). That reason is now stale: the bin imports@loopover/contractalready, and the contract exports both. The comment is load-bearing documentation that is no longer true, which is worse than no comment.2. Four
registerToolcall sites still pass.shape.src/mcp/server.ts:2435,:2440,:2445,:2450handAdminGetStatusInput.shape,AdminDoctorInput.shape,AdminTailLogsInput.shape, andAdminGetBackupStatusInput.shapeto the SDK. The SDK re-wraps a raw shape in a plainz.object, which discards the catchall — the defect class #9518 named, where alooseObjectoutput becomesadditionalProperties: falseand every extra field the payload carries turns into a-32602the 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
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 thePREFLIGHT_LIMITSprecedent inpackages/loopover-contract/src/limits.ts.registerToolcall sites pass the schema OBJECT, not.shape.registerToolcall 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:openapiregenerated. 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.Deliverables
AGENT_ACTION_CLASSESavailable from the contract, pinned against the engine's by a meta-test#6153"hand-synced literals, not imports" comment removed with its literalsregisterToolsites pass schema objectsregisterToolcall that passes.shape, asserted to find zero todayapps/loopover-ui/public/openapi.jsonregenerated and committedTest 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 — theAGENT_ACTION_CLASSESpin and the no-.shapeguard — 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
.shapetrap that produced #9518's-32602class 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.