Harness lab

One harness. Two domains.

The studio is treasury. This page runs the same fork, simulate and reject path over a second domain that was never given a way to act. It runs in your browser on fixture data: no model, no ArmorIQ call, nothing written.

Fork a decision

Starting point: $9,000 cash, 35.0% limit per position, $5,000 minimum cash.

What branch B changes

PositionWeight beforeWeight afterUnits
Northfield Equity Fund46.0%35.0%−110

What would be signed

0 tool steps. portfolioAdapter has no tools, so an approval would have nothing to sign and the runner nothing to run. This domain stops at the comparison.

Fixture branches through dryRun() in packages/harness/src/dry-run.ts and packages/domain-portfolio/src/adapter.ts.

Two adapters, one interface

A domain implements six methods. The harness never looks inside them, which is why leaving out the tools is enough to make a domain read-only. See the interface.

MethodTreasuryPortfolio dry run
validateActionsKnown invoices and approved vendorsKnown holdings, one trade each
simulateCash, late fees and the 14-day pathPositions, weights and cash
feasibilityNo overdraft, reserve keptNo overselling, limit and minimum cash kept
diffWhat each invoice is left owingWhich positions change
toToolStepsThe exact MCP calls ArmorIQ signsNone, so nothing can run
verifyPostconditionCash moved by what the receipts sayNothing moved at all
Executes through ArmorIQ and the MCP
Treasury only
Proposed by a model
Treasury only

Branch lifecycle

Drawn from the table the guards check. Any other move throws. A dry run reaches the marked states and no further.

  • PROPOSEDDry runcan becomeSIMULATED, INFEASIBLE, STALE
  • SIMULATEDDry runcan becomeAPPROVED, INFEASIBLE, STALE
  • APPROVEDcan becomeCOMMITTING, STALE, CANCELLED
  • COMMITTINGcan becomeCOMMITTED, PARTIALLY_COMMITTED, BLOCKED, CANCELLED
  • COMMITTEDfinal
  • PARTIALLY_COMMITTEDfinal
  • BLOCKEDfinal
  • CANCELLEDfinal
  • INFEASIBLEDry runfinal
  • STALEfinal