Frontend checks slow down when the state worth reviewing sits behind a login, a cloud service, a native module, a model process, or a device permission. The interface may need only seconds of attention after minutes of setup.
Hraness Direct makes frontend loops faster with Carapace, its deterministic product harness. The product keeps its real interface and feature state while deterministic adapters replace slow external systems. A Carapace definition names the available worlds and evidence claims, a session owns one activated world's lifecycle, and one browser installation exposes its probe while denying unmapped application requests. The product's verifier still chooses the actions, assertions, browser driver, and evidence.


Change the composition below the behavior
A product-owned port is a small interface between product behavior and an external system. Production supplies the live implementation. The Carapace composition supplies a deterministic implementation of the same contract. Components, reducers, parsing, and navigation above that seam stay on their ordinary code paths.
A minimal product-specific layout makes that boundary visible:
product/
├── src/
│ ├── TodoApp.tsx # real interface and feature state
│ ├── todo-port.ts # product-owned semantic boundary
│ └── production.ts # live adapter and entry
└── carapace/
├── definition.ts # worlds, routes, and evidence claims
├── deterministic-port.ts # deterministic implementation
├── entry.tsx # development composition
└── verify.ts # actions, assertions, and evidenceThe filenames are illustrative. The ownership rule is fixed: production and deterministic adapters implement the same product-owned port, the real interface stays above both, and the Carapace definition, workbench, and verifier stay outside the production graph. A scenario is named initial state, route, and logical-runtime state. It is not a script of browser actions.
The public Todo example shows that boundary in code. The real TodoApp knows only the product's TodoPort. The production entry supplies browser storage. The Carapace session supplies a deterministic port:
export interface TodoItem {
readonly id: string;
readonly title: string;
readonly completed: boolean;
}
export interface TodoPort {
readonly readTodos: () => Promise<readonly TodoItem[]>;
readonly setCompleted: (
id: string,
completed: boolean,
) => Promise<readonly TodoItem[]>;
}
// Production entry
<TodoApp port={createLocalStorageTodoPort(globalThis.localStorage)} />
// Carapace workbench
<TodoApp port={props.harness.port} />The interface speaks in product terms: todos and completion. It contains no storage calls or Carapace types. Behind the port, the deterministic runtime can use a world and a logical clock without changing the component under review.
The deterministic side starts with one strict, versioned JSON world parsed from an unknown value. The session gives the validated, immutable seed to the product adapter, which can initialize its own mutable repository or event stream. Above the port, the frontend handles each response through its normal state-update path. Unknown requests fail instead of falling through to a live service. The world becomes a bounded executable model of the state needed by the interface.
Use the lowest port that preserves the behavior under review. A task interface should depend on a task repository rather than a simulated database client. A desktop renderer should depend on a renderer-safe transport rather than a simulated native protocol. If the deterministic adapter must copy the behavior named by the claim, the seam is too high and the fixture would imitate its subject instead of testing it.
Own the lifecycle once
The public API follows the same ownership model. defineCarapace from the framework-free package root validates authored scenarios and coverage claims once. createCarapaceSession from the testing entry activates one scenario and owns its world, logical clock, activity, deterministic harness, cancellation signal, probe, and reverse-order cleanup. installCarapaceBrowser from the web entry publishes that session through the canonical browser bridge and installs a fail-closed application-fetch boundary as one disposable operation.
The authored definition type keeps the default and coverage citations inside the same scenario tuple. Typed configuration assembled dynamically uses tryDefineCarapace. A genuinely unknown value uses parseCarapaceDefinition, preserving structured failure without asserting foreign data into narrow owned types.
The extracted scenario, coverage, and harness values in this entry are ordinary product-owned data and code. Carapace owns their lifecycle and browser boundary:
const definition = defineCarapace({
parseWorld: parseTodoWorld,
defaultScenario: "todos.populated",
scenarios: todoScenarios,
coverage: todoCoverage,
});
const opened = createCarapaceSession({
definition,
activation: { kind: "query", source: location.search },
create: createTodoHarness,
});
if (!opened.ok) throw new Error(opened.error.message);
const session = opened.value;
const installed = installCarapaceBrowser({ session });
if (!installed.ok) {
session.dispose();
throw new Error(installed.error.message);
}The default browser policy denies application calls through fetch. A product may allow exact URLs, record blocked calls, or disable the firewall when another checked boundary owns containment. The installation derives the probe and coverage snapshot from the session, tracks allowed and blocked fetch work in the same activity scope, and rolls back its own changes if setup fails. It registers cleanup with the session; its returned handle can remove the browser hooks earlier.
Each store reset advances the session generation. Transactions and tracked operations from an earlier generation cannot update or settle the new world. The browser reset action is a synchronous handoff, normally to a reload; it cannot return a Promise that automation could mistake for completed work. Logical time can accelerate product delays without changing browser motion. A product runner may reuse a compatible browser process or application bundle, but reuse is not a package guarantee.
Quiescence replaces sleeps
A fixed delay waits for a duration. Quiescence means that the active scenario has reached its declared readiness conditions. The session probe requires no active operations and all product-named pending counters at zero. The product verifier then checks that the generation, revision, activity totals, and counters remain unchanged for a short, bounded interval. It may separately wait for fonts, nearby images, or document stability when a claim needs visual evidence.
The probe reports violation counters and remaining scripted work outside the quiescence gate. A verifier must separately reject an unhandled request, unused required script step, console error, runtime error, leaked work token, or malformed transport value when that signal matters to the claim. Types and property tests in an agent-operated codebase help at this boundary. Strict parsers reject malformed worlds, while generated tests challenge reset, cancellation, serialization, and ordering laws across many inputs.
The browser-driver helper remains product-owned. In this illustrative Playwright check, waitForQuiescence reads the probe until those readiness conditions remain stable. It then rejects relevant violations and required remaining work:
test("completes a populated todo", async ({ page }) => {
await page.goto(
"/carapace/?__carapace_scenario=todos.populated",
);
await waitForQuiescence(page);
const todo = page.getByRole("checkbox", {
name: "Write the public guide",
});
await todo.check();
await waitForQuiescence(page);
await expect(todo).toBeChecked();
});The check needs no login, seed request, CSS selector, or fixed sleep. The named scenario supplies only the starting world and route. The product verifier supplies the checkbox action and assertion, while the port carries the interaction through real frontend state. The second waitForQuiescence call waits for that state to settle.
Quiescence does not prove that the rendered result is correct. It proves only that the readiness conditions held. The verifier must reject relevant violations and remaining work separately; semantic assertions, visual review, and direct integration tests still decide whether the result supports the claim.
Stop the claim at the substituted port
Every coverage claim names one proof mode and cites the scenarios that supply its deterministic evidence. Routes stay on scenarios, where they are executable facts, instead of being repeated as one potentially misleading representative route on a claim. A fixture claim covers real interface and product behavior through deterministic ports. A mixed claim combines that fixture evidence with a named direct adapter or service check. A direct claim requires the real host, service, browser assembly, filesystem, operating system, or device. A completed run is verified only when every kind of evidence required by its declared mode has passed.
Carry the workflow with the package
An Agent Skill is a directory of task instructions and display metadata that a compatible coding-agent runner can load. Beginning with the v0.3.0 release, an installed @cclrte/carapace package carries carapace-setup for adoption and carapace-verify for evidence review under its skills/ directory. A repository can copy or link either directory into its runner's discovery location, so the workflow stays versioned with the API and examples it describes.
Installation leaves both skills inert because agent runners do not share one discovery path. The setup skill encodes the product-port and production-exclusion sequence; the verification skill encodes quiescence, semantic assertions, coverage classification, and direct-evidence limits. They organize the work, but they do not supply evidence or promote a fixture claim.
Use Hraness Direct when external setup hides frontend state and a small product-owned port can replace that setup without copying the behavior under review. Keep direct integration and end-to-end tests when the backend, native host, browser assembly, or operating system is the subject. Carapace makes the composition deterministic; the product verifier still decides whether the resulting interface supports the claim.