You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
⚠️ 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
@loopover/contract is a zod-only leaf: it cannot import src/, the engine, or the miner package,
so a handful of vocabularies are deliberately restated there and pinned by meta-test against
their live source. test/unit/contract-registry.test.ts:223-284 does this for AUTONOMY_LEVELS, MAINTAIN_ACTION_CLASSES, PROPOSE_ACTION_CLASSES, PUBLIC_SURFACE_SKIP_REASONS, PREFLIGHT_LIMITS, SCENARIO_LIMITS and PLAN_STEP_STATUSES. enums.ts:1-18 explains why: the
hand-copies this package replaced had drifted before, and "the drift this invites is real and has
bitten once already".
a framework the engine recognises is rejected by the tool's input schema
QUEUE_STATUSES (src/tools/miner.ts:17)
packages/loopover-miner/lib/portfolio-queue.ts:88
MinerManageStatusOutput.rows[].queueStatus (miner.ts:99) rejects a real row
CLAIM_STATUSES (src/tools/miner.ts:20)
packages/loopover-miner/lib/claim-ledger.ts:91
MinerListClaimsInput.status (miner.ts:136) rejects a filter the ledger accepts
MINER_RUN_STATES (src/tools/miner.ts:208)
RunState in packages/loopover-miner/lib/run-state.ts:8
MinerGetRunStateOutput.state / .states[].state (miner.ts:220-221) rejects a real persisted state
The three miner ones are worse than the engine one because they sit in tools/miner.ts rather than enums.ts, so they escape even the convention that shared vocabularies live in enums.ts, and two of
them are used in output schemas — a new run state or queue status makes the tool's real structuredContent fail the schema the same tool advertises. validate:mcp only notices if the cold
fixture environment happens to produce the new value.
Separately, FEASIBILITY_VERDICTS (enums.ts:68) already exists and is imported correctly at packages/loopover-contract/src/tools/local-branch.ts:27, :324 — but the identical literal is
re-inlined at packages/loopover-contract/src/tools/agent.ts:48 and :67, inside the very package
whose stated purpose is eliminating hand-copies, and again at src/openapi/schemas.ts:2392 and :2427.
Requirements
TEST_FRAMEWORKS, and the three miner vocabularies moved out of tools/miner.ts into packages/loopover-contract/src/enums.ts (where every other shared vocabulary lives), each gain a
pin in test/unit/contract-registry.test.ts's "contract enums" block, asserting array equality
against the live source module — the same shape as the existing PUBLIC_SURFACE_SKIP_REASONS pin
at :250-254.
Each pin imports the live source directly (packages/loopover-engine/src/signals/test-evidence, packages/loopover-miner/lib/portfolio-queue, packages/loopover-miner/lib/claim-ledger, packages/loopover-miner/lib/run-state) exactly as the existing pins import packages/loopover-engine/src/settings/autonomy and src/scenarios/input-model. RunState is a
type union, so the pin needs a runtime array in run-state.ts to compare against — export one
(RUN_STATES, constrained with satisfies readonly RunState[]) rather than hand-listing the
members in the test.
agent.ts:48 and :67 import FEASIBILITY_VERDICTS from ../enums.js; src/openapi/schemas.ts:2392 and :2427 import it from @loopover/contract. No inline go/raise/avoid enum literal remains in src/ or packages/.
Each moved constant keeps its exported name and is re-exported from packages/loopover-contract/src/tools/miner.ts so no downstream import path breaks.
⚠️ Required pattern: test/unit/contract-registry.test.ts:250-254 (the PUBLIC_SURFACE_SKIP_REASONS pin) and :256-261 (the PREFLIGHT_LIMITS pin) — a direct import of
the live module and an array/object equality assertion. Asserting only that the contract's list is a subset of the live one, asserting a hardcoded expected array in the test instead of importing the
source, or leaving the three miner vocabularies in tools/miner.ts, do NOT satisfy this issue.
Deliverables
QUEUE_STATUSES, CLAIM_STATUSES and MINER_RUN_STATES live in packages/loopover-contract/src/enums.ts and are re-exported from tools/miner.ts
RUN_STATES exported from packages/loopover-miner/lib/run-state.ts as a runtime array
constrained by satisfies readonly RunState[]
Four new pins in test/unit/contract-registry.test.ts (TEST_FRAMEWORKS, QUEUE_STATUSES, CLAIM_STATUSES, MINER_RUN_STATES), each asserting equality against the imported live source
No inline go/raise/avoid enum literal remains under src/ or packages/; agent.ts and src/openapi/schemas.ts import FEASIBILITY_VERDICTS
npm run typecheck and npm run validate:mcp green with no schema shape change
All Deliverables above are required in a single PR. A PR that satisfies only some of them — for
example adding the four pins while leaving the miner vocabularies in tools/miner.ts and the
feasibility-verdict literals in place — does not resolve this issue.
Test Coverage Requirements
99%+ Codecov patch coverage, branch-counted, applies: packages/loopover-contract/src/**, packages/loopover-miner/lib/** and src/** are all in coverage.include
(vitest.config.ts:55-100). The new RUN_STATES export needs a test; every changed z.enum(...)
needs an accept case and a reject case so the branch counts, and the four pins themselves are the
regression tests for the drift.
Expected Outcome
Every vocabulary @loopover/contract restates is mechanically pinned to its live source, so adding a
run state, queue status, claim status or test framework on one side fails CI instead of producing a
tool whose advertised output schema rejects its own real payload; and FEASIBILITY_VERDICTS has one
definition and no copies.
Context
@loopover/contractis a zod-only leaf: it cannot importsrc/, the engine, or the miner package,so a handful of vocabularies are deliberately restated there and pinned by meta-test against
their live source.
test/unit/contract-registry.test.ts:223-284does this forAUTONOMY_LEVELS,MAINTAIN_ACTION_CLASSES,PROPOSE_ACTION_CLASSES,PUBLIC_SURFACE_SKIP_REASONS,PREFLIGHT_LIMITS,SCENARIO_LIMITSandPLAN_STEP_STATUSES.enums.ts:1-18explains why: thehand-copies this package replaced had drifted before, and "the drift this invites is real and has
bitten once already".
Four restatements have no pin:
TEST_FRAMEWORKS(src/enums.ts:50)packages/loopover-engine/src/signals/test-evidence.ts:126QUEUE_STATUSES(src/tools/miner.ts:17)packages/loopover-miner/lib/portfolio-queue.ts:88MinerManageStatusOutput.rows[].queueStatus(miner.ts:99) rejects a real rowCLAIM_STATUSES(src/tools/miner.ts:20)packages/loopover-miner/lib/claim-ledger.ts:91MinerListClaimsInput.status(miner.ts:136) rejects a filter the ledger acceptsMINER_RUN_STATES(src/tools/miner.ts:208)RunStateinpackages/loopover-miner/lib/run-state.ts:8MinerGetRunStateOutput.state/.states[].state(miner.ts:220-221) rejects a real persisted stateThe three miner ones are worse than the engine one because they sit in
tools/miner.tsrather thanenums.ts, so they escape even the convention that shared vocabularies live inenums.ts, and two ofthem are used in output schemas — a new run state or queue status makes the tool's real
structuredContentfail the schema the same tool advertises.validate:mcponly notices if the coldfixture environment happens to produce the new value.
Separately,
FEASIBILITY_VERDICTS(enums.ts:68) already exists and is imported correctly atpackages/loopover-contract/src/tools/local-branch.ts:27, :324— but the identical literal isre-inlined at
packages/loopover-contract/src/tools/agent.ts:48and:67, inside the very packagewhose stated purpose is eliminating hand-copies, and again at
src/openapi/schemas.ts:2392and:2427.Requirements
TEST_FRAMEWORKS, and the three miner vocabularies moved out oftools/miner.tsintopackages/loopover-contract/src/enums.ts(where every other shared vocabulary lives), each gain apin in
test/unit/contract-registry.test.ts's "contract enums" block, asserting array equalityagainst the live source module — the same shape as the existing
PUBLIC_SURFACE_SKIP_REASONSpinat
:250-254.packages/loopover-engine/src/signals/test-evidence,packages/loopover-miner/lib/portfolio-queue,packages/loopover-miner/lib/claim-ledger,packages/loopover-miner/lib/run-state) exactly as the existing pins importpackages/loopover-engine/src/settings/autonomyandsrc/scenarios/input-model.RunStateis atype union, so the pin needs a runtime array in
run-state.tsto compare against — export one(
RUN_STATES, constrained withsatisfies readonly RunState[]) rather than hand-listing themembers in the test.
agent.ts:48and:67importFEASIBILITY_VERDICTSfrom../enums.js;src/openapi/schemas.ts:2392and:2427import it from@loopover/contract. No inlinego/raise/avoidenum literal remains insrc/orpackages/.packages/loopover-contract/src/tools/miner.tsso no downstream import path breaks.Deliverables
QUEUE_STATUSES,CLAIM_STATUSESandMINER_RUN_STATESlive inpackages/loopover-contract/src/enums.tsand are re-exported fromtools/miner.tsRUN_STATESexported frompackages/loopover-miner/lib/run-state.tsas a runtime arrayconstrained by
satisfies readonly RunState[]test/unit/contract-registry.test.ts(TEST_FRAMEWORKS,QUEUE_STATUSES,CLAIM_STATUSES,MINER_RUN_STATES), each asserting equality against the imported live sourcego/raise/avoidenum literal remains undersrc/orpackages/;agent.tsandsrc/openapi/schemas.tsimportFEASIBILITY_VERDICTSnpm run typecheckandnpm run validate:mcpgreen with no schema shape changeAll Deliverables above are required in a single PR. A PR that satisfies only some of them — for
example adding the four pins while leaving the miner vocabularies in
tools/miner.tsand thefeasibility-verdict literals in place — does not resolve this issue.
Test Coverage Requirements
99%+ Codecov patch coverage, branch-counted, applies:
packages/loopover-contract/src/**,packages/loopover-miner/lib/**andsrc/**are all incoverage.include(
vitest.config.ts:55-100). The newRUN_STATESexport needs a test; every changedz.enum(...)needs an accept case and a reject case so the branch counts, and the four pins themselves are the
regression tests for the drift.
Expected Outcome
Every vocabulary
@loopover/contractrestates is mechanically pinned to its live source, so adding arun state, queue status, claim status or test framework on one side fails CI instead of producing a
tool whose advertised output schema rejects its own real payload; and
FEASIBILITY_VERDICTShas onedefinition and no copies.
Links & Resources
packages/loopover-contract/src/enums.ts:1-18, 49-51, 67-69packages/loopover-contract/src/tools/miner.ts:14-20, 99, 136, 208-222packages/loopover-contract/src/tools/agent.ts:48, 67,packages/loopover-contract/src/tools/local-branch.ts:27, 324packages/loopover-engine/src/signals/test-evidence.ts:126,packages/loopover-miner/lib/portfolio-queue.ts:88,packages/loopover-miner/lib/claim-ledger.ts:91,packages/loopover-miner/lib/run-state.ts:8src/openapi/schemas.ts:2392, 2427test/unit/contract-registry.test.ts:223-284apps/loopover-miner-ui/.