Skip to content

Guide: generated Koerper discipline

Status: design guidance. This page is the rule set an AI-driven code generator (Claude Code, Cursor, an in-house agent) must preserve when it produces application code that talks to Gestalt. It exists so a competent generator can produce a correct Koerper without the founding team interpreting every output.

A Koerper is the mutable operational body around a Geist. See concepts/geist-and-koerper.md. When a generator produces a Koerper, the same discipline applies — it is just easier to violate by accident at scale.

These are the rules. They are short on purpose.

1. Cross the membrane through the SDK or HTTP

Section titled “1. Cross the membrane through the SDK or HTTP”

Every act that should become company reality goes through the cloud membrane. Use GestaltClient for the routes it covers. For the small set of routes that do not yet have an SDK wrapper (see ../sdk/typescript.md), call HTTP directly with the bearer token. Never reach into a database, a queue, or a connector endpoint.

Every membrane response carries a receipt (or at minimum a receipt ref). Persist it alongside the rendered fact. A list row that shows an invoice should be able to surface the invoice’s receipt.ref, outcome, reasons, and fixture flag.

refused, pending, and projected are real outcomes. Treat them as data:

switch (response.outcome) {
case "admitted":
case "verified":
case "executed":
// success
break;
case "refused":
// structured finding — render reasons and remedy hints
break;
case "pending":
// act needs more — store the pending ref, ask the user for what is missing
break;
case "projected":
// admitted into a projection, not into record
break;
case "queued":
case "failed":
// effect outbox states — see effect.intent / effect.dispatch
break;
}

Throw only on transport errors (non-2xx HTTP, network failure). A refused 200 OK is not an error.

4. Surface fixture and production-admission markers

Section titled “4. Surface fixture and production-admission markers”

Until production admission lands, every receipt carries fixture: true and most bodies carry productionAdmission: false. Show this on every surface that displays admitted state. A Koerper that hides the fixture flag is a Koerper that lies to its user.

For lists, dashboards, and detail panels, prefer the read models over reconstructing state from raw atoms:

readActiveStandings durable active standing
readActiveMandates durable active mandate
readPeriodCloseReadiness derived close readiness
readConnectorEvidenceGaps evidence gap summary
readProofHistory proof bundle manifest history

commitRecent remains the journal view; the read models are the projection-shaped views the Koerper renders against.

6. Treat local state as cache, draft, or projection

Section titled “6. Treat local state as cache, draft, or projection”

Local state is fine, as long as it is one of:

  • Cache of a recent membrane projection, invalidated by re-fetching.
  • Draft of an intent the user has not yet submitted.
  • Projection of a forked reality that has not been promoted.

Local state is never the canonical version of company truth. If a fact would be relied on by an audit, an advisor, a regulator, or a counterparty, it crosses the membrane.

7. Route sensitive effects through effect.intent

Section titled “7. Route sensitive effects through effect.intent”

External-world effects (sending an email, debiting an account, posting to a webhook, calling an API) go through effectIntent followed by effectDispatch. Always supply a stable idempotency_key derived from the user-intended action. Reuse the same key on retry.

8. Request HumanAuth presence where required

Section titled “8. Request HumanAuth presence where required”

Sensitive acts (signing, granting, revoking) require fresh HumanAuth presence. The generator should produce code that calls m13PresenceApproval and attaches the receipt ref to the proposing call. Always pass create_standing_from_presence: false.

9. Distinguish presence, session, standing, and mandate

Section titled “9. Distinguish presence, session, standing, and mandate”

These are four different things:

session a signed bearer context for a vessel
HumanAuth presence a fresh receipt that a human is at the keyboard
standing a grant of capacity to bind a subject
mandate a scoped delegation

Generated data models, render trees, and authorization checks must keep them separate. None of the four can be derived from another client-side.

A refusal is a finding. The body usually carries refusalReason, optional missingEvidence, and an optional remedyHint. Surface all three. A refusal panel without remedy is a bug.

write directly to a database
read directly from a database
hold canonical company truth in local state
hide the fixture flag
mint standing, mandate, package trust, or proof refs locally
treat HumanAuth presence as standing or mandate
treat a bearer session as standing
expose raw connector payloads
expose raw biometric material or credential bytes
expose raw register / ID payloads
flatten refused, pending, and projected into a generic error
imply production admission before the production gate is satisfied

If any of the above appears in generated code, the generator has lost the discipline. Reject the output.

Before a Koerper ships, run this sanity walk against the fixture cloud:

  1. Call tenantSelf. Confirm the response has fixture: true.
  2. Call precheckIntent for capability:issue_invoice_fixture_v1 with empty evidence. Confirm the UI renders a structured refusal with reasons and remedy hint, not a 500.
  3. Call economicInvoice with evidence_bundle:invoice_payload. Confirm the UI renders the admitted invoice with the receipt ref and the fixture marker.
  4. Call economicPeriodClose with no clearance evidence. Confirm the UI renders the economic_closure_surface_open refusal cleanly.
  5. Inspect the network panel: every authenticated call carries the bearer token; no call goes anywhere except the membrane base URL.

A Koerper that passes these five steps satisfies the floor of this guide.