Guide: human-auth flows
Status: shape-only for cryptographic real-WebAuthn; otherwise fixture-rehearsed. See 022 gap report item 15.
This guide walks the two human-presence lanes Gestalt supports: private passkey and CPU face-match fallback. Both produce a human presence receipt without Gestalt ever seeing biometric material.
Why human presence matters
Section titled “Why human presence matters”Gestalt distinguishes:
- Identity — who someone is in the world.
- Standing — what they may bind on behalf of a company.
- Presence — that they were here, now, attesting in person.
A human presence receipt is not standing. It does not authorize anything by itself. It is consumed by other operations that require it (sensitive approvals, sensitive effect intents).
This separation is constitutional. A clever attacker who steals a session token cannot upgrade themselves to “the human is here right now.” Presence is its own thing.
Lane 1: private passkey
Section titled “Lane 1: private passkey”Step 1 — request a challenge
Section titled “Step 1 — request a challenge”const challenge = await client.humanAuthChallenge({ tenant: "tenant_node:rheinwerk_calibration", subject: "human_person:anna", relying_party_id: "gestalt.local", scopes: ["sensitive_approval"],});
console.log(challenge.body.challenge);// "human_auth_challenge:fixture_private_human_auth"console.log(challenge.body.biometricMaterialSeenByGestalt); // falseThe challenge is consumed by the user’s authenticator (WebAuthn-style). The browser / native vessel performs the passkey ceremony locally; biometric material never leaves the device.
Step 2 — verify the passkey assertion
Section titled “Step 2 — verify the passkey assertion”After the user’s authenticator signs the challenge:
const verified = await client.humanAuthVerifyPasskey({ tenant: "tenant_node:rheinwerk_calibration", challenge: challenge.body.challenge, passkey_binding: "passkey_binding:anna_platform_passkey_fixture", relying_party_id: "gestalt.local", user_verified: true, credential_id_hash: "sha256:fixture_credential_id_hash_private",});
console.log(verified.body.humanPresenceReceipt);// "human_presence_receipt:fixture_private_presence"console.log(verified.body.standingCreated); // falseconsole.log(verified.body.biometricMaterialSeenByGestalt); // falsestandingCreated: false and biometricMaterialSeenByGestalt: false
are asserted explicitly — they are part of the privacy contract.
The humanPresenceReceipt is now usable for follow-on operations.
Lane 2: CPU face-match fallback
Section titled “Lane 2: CPU face-match fallback”For environments without passkey support. Requires explicit consent.
const fallback = await client.humanAuthFaceMatch({ tenant: "tenant_node:rheinwerk_calibration", subject: "human_person:anna", consent_ref: "consent:fixture_cpu_face_match", scenario: "same-person", // | "different-person" | "low-quality" | "spoof"});
console.log(fallback.body.cpuFaceMatchReceipt);console.log(fallback.body.standingCreated); // falseconsole.log(fallback.body.rawImagesStored); // falseconsole.log(fallback.body.biometricTemplatesStored); // falseconsole.log(fallback.body.oneToManySearch); // falseThe body asserts the privacy invariants explicitly:
rawImagesStored: false— no raw face images persist.biometricTemplatesStored: false— no template storage.oneToManySearch: false— never used for 1:N search.standingCreated: false— presence does not create standing.
Without consent_ref, the call refuses with
human_auth_face_match_consent_missing. Consent is structurally
required.
Using the presence receipt
Section titled “Using the presence receipt”A presence receipt is consumed by:
authority.presenceApproval
Section titled “authority.presenceApproval”Bind presence to actor + vessel for sensitive approval:
const approval = await client.m13PresenceApproval({ tenant: "tenant_node:rheinwerk_calibration", actor: "human_person:anna", vessel: "vessel:fixture_sdk", human_presence_receipt: "human_presence_receipt:fixture_private_presence", create_standing_from_presence: false, // explicit; presence cannot create standing});
console.log(approval.body.sensitiveApprovalSatisfied); // trueconsole.log(approval.body.standingCreated); // falseconsole.log(approval.body.delegationAuthorityCreated); // falseIf you set create_standing_from_presence: true, the call refuses
with m13_human_presence_cannot_create_standing. This refusal is
load-bearing — it prevents an attacker who phished a passkey
ceremony from upgrading themselves to a Geschäftsführer.
effect.intent for sensitive effects
Section titled “effect.intent for sensitive effects”const intent = await client.effectIntent({ tenant: "tenant_node:rheinwerk_calibration", subject: "company_geist:rheinwerk_calibration", effect_kind: "send_invoice_email", adapter: "fixture_email_adapter", mode: "fire_and_record", idempotency_key: "invoice-2026-04-001-email", evidence: ["evidence_bundle:invoice_payload"], human_presence_receipt: "human_presence_receipt:fixture_private_presence",});Without the presence receipt, sensitive effect intents refuse with
effect_human_presence_required.
Replay and expiry
Section titled “Replay and expiry”// the same challenge cannot be re-usedconst replay = await client.humanAuthVerifyPasskey({ ...same args as before...});// outcome: "refused"// body.refusalReason: "human_auth_challenge_replayed"Challenges expire after a short window. Expired challenges refuse
with human_auth_challenge_expired.
Building a Koerper that does presence correctly
Section titled “Building a Koerper that does presence correctly”1. Don’t conflate session and presence
Section titled “1. Don’t conflate session and presence”A bearer session token proves who the API caller is. A presence receipt proves that a human was physically here recently. The two are different. Don’t downgrade one into the other.
2. Don’t cache presence receipts
Section titled “2. Don’t cache presence receipts”A presence receipt is a one-shot artifact. It is consumed by a specific follow-on operation. Don’t store it for re-use.
3. Surface the privacy invariants
Section titled “3. Surface the privacy invariants”Every presence-flow UI should display:
- “Gestalt did not see your face / fingerprint / biometric.”
- “This presence verification does not change your role.”
These statements are backed by the response fields
(biometricMaterialSeenByGestalt, standingCreated,
rawImagesStored).
4. Make sensitive flows require fresh presence
Section titled “4. Make sensitive flows require fresh presence”If your Koerper wraps a sensitive effect (a refund, a sensitive data export, a contract signature), require a fresh presence flow each time. Don’t reuse presence across multiple sensitive acts.
What you cannot do today
Section titled “What you cannot do today”- Real WebAuthn validation. Passkey verify uses fixture flags rather than actually validating an assertion against a registered credential.
- Real face-match. The face-match fallback returns a receipt
based on the
scenarioenum, not a real biometric pipeline. - Real session-binding. Presence cannot today be bound to a specific cloud-issued session in production.
For the gap, see 022 item 15.
Where to read next
Section titled “Where to read next”- API: human auth
- API: authority —
presenceApproval,sessionRevoke,keyRotate - API: effects — sensitive effect intents
- Glossary: HumanAuth