Sessions
A session associates workflow runs with a particular user. Use this API primitive to run document extraction and verification workflows for that user, share data between runs and track their progress.
For KYC or KYB, configure the document tasks and checks your process needs. The session holds the execution context; the workflows perform the document work. Sessions can also include related participants, such as a business’s representatives or beneficial owners.
Key Concepts
- Session configurations: Reusable blueprints that define which workflows, bindings, verification settings, and defaults a session uses.
- Sequential execution: Workflows run in order. The user must complete workflow 1 before starting workflow 2.
- Participants: Sessions can include a subject plus related parties such as beneficial owners, controllers, directors, or representatives.
- Cross-workflow data flow: Output from earlier workflows is available as input to later ones (e.g., validate POA name matches ID name).
- Checks and risk: Sessions can include provider-backed check workflows and native risk scoring before final resolution.
- Resolution: Sessions can be auto-resolved (approved/rejected) based on validation results, or manually resolved by a partner.
- End-user access: Each session gets a unique access token that your frontend can turn into the collection URL for end-users.
Test and live environments
API keys select their own environment. A test key can create and access test configurations, sessions and workflow runs; a live key can access live resources. Create private workflow definitions and configurations in the same environment as the session. Both environments may use the same slugs. Public catalog definitions are shared; each run keeps the caller’s environment.
Dashboard JWT requests default to live. Use X-Korala-Mode: test, or the SDK’s
korala.sessions.inMode('test') and korala.workflows.inMode('test') views, to
select test mode. These views leave the original client unchanged. A header
cannot override an API key’s mode.
Session links inherit the session’s environment. You cannot move an existing
session between environments. Responses expose sandbox, and risk evidence
imports require a subject in the same organization and environment.
Test mode isolates records and webhook routing. It does not mock OCR, model or check providers, or make their services free. Configure provider test credentials or mock providers before running a test workflow.
Session Configurations
Before creating sessions, set up a configuration that defines the session blueprint. Configurations are org-scoped and reusable.
import { KoralaClient } from '@korala/api-client';
const korala = new KoralaClient({
apiKeyId: 'your-api-key-id',
apiSecret: 'your-api-secret',
});
// Create a configuration
const config = await korala.sessions.createConfiguration({
name: 'KYB Standard',
slug: 'kyb-standard',
description: 'Business onboarding with participant-aware verification',
workflowBindings: [
{
workflowSlug: 'id-document',
participantRoles: ['representative'],
outputKey: 'representative_id',
},
{
workflowSlug: 'proof-of-address',
participantRoles: ['representative'],
outputKey: 'representative_poa',
},
{
workflowSlug: 'pep-check',
participantRoles: ['beneficial_owner'],
outputKey: 'ubo_pep_check',
},
],
subjectKind: 'business',
isVerification: true,
autoResolve: false,
finalManualReviewRequired: true,
defaultExpiresInMinutes: 1440,
defaultRedirectUrl: 'https://yourapp.com/verification/complete',
});Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
name | string | required | Display name |
slug | string | required | URL-safe identifier (unique per org) |
workflowSlugs | string[] | — | Ordered list of workflow slugs for simple sequential sessions |
workflowBindings | object[] | — | Participant-aware workflow definitions with per-step context and output keys |
subjectKind | `individual \ | business` | individual |
isVerification | boolean | true | Enable resolution (approved/rejected) |
autoResolve | boolean | true | Auto-resolve based on validation results |
finalManualReviewRequired | boolean | false | Force partner review before final resolution |
defaultExpiresInMinutes | number | 1440 | Default session expiration |
resubmissionMinimumWindowMinutes | integer | 1440 | Time a resubmission leaves the person at least before the session expires (0 to 43,200; 0 never extends). See Resubmission and expiry |
defaultRedirectUrl | string | — | Default redirect after completion |
Managing Configurations
// List all configurations
const configs = await korala.sessions.listConfigurations();
// Get a specific configuration
const config = await korala.sessions.getConfiguration('kyc-standard');
// Update a configuration
await korala.sessions.updateConfiguration('kyc-standard', {
defaultExpiresInMinutes: 2880,
});
// Delete a configuration
await korala.sessions.deleteConfiguration('kyc-standard');Creating a Session
Each session starts from a configuration:
const session = await korala.sessions.create({
externalUserId: 'org_abc123',
configurationSlug: 'kyb-standard',
participants: [
{ role: 'subject', displayName: 'Acme Corp' },
{ role: 'beneficial_owner', displayName: 'Jane Smith' },
{ role: 'representative', displayName: 'John Smith' },
],
context: {
partnerName: 'Acme Corp',
applicationType: 'merchant_onboarding',
},
metadata: {
applicationId: 'app_001',
},
// Override config defaults if needed
expiresInMinutes: 720,
redirectUrl: 'https://yourapp.com/custom-redirect',
});
// Share the access URL with the end-user
const accessUrl = `https://yourapp.korala.ai/v/${session.accessToken}`;Use the session detail endpoint when you need full case state, including participants, workflow instances, checks, and risk summaries.
Session Lifecycle
| Status | Description |
|---|---|
created | Session created, user hasn’t started yet |
in_progress | User is completing workflows |
pending_review | A workflow is paused for human review |
completed | All workflows finished without failures |
expired | The person did not finish before expiresAt (a session in review never expires) |
failed | A workflow failed with no retries remaining |
Resolution
When all workflows complete, the session may auto-resolve if enabled in the configuration and nothing requires manual review:
- Approved: All workflows passed validation
- Rejected: One or more workflows failed validation
For KYB or other review-heavy flows, the session can move into pending_review until a partner makes the final decision.
Automatic decisions
A verification configuration with autoResolve on decides on its own:
- Rejected only when a check you set to
blockingMode: "block"returns a definite result: a clear face mismatch fromaws_rekognition(below its mismatch threshold), a sanctions match fromkorala_screening, a Face Liveness confidence fromaws_rekognition_livenessbelow thedeclineConfidenceyou set, or, on aduplicate_documentrisk step, an identity document already approved for another user in your organization. The public nameskorala_face_matchandkorala_livenessare the same checks: a step configured with either can be set toblockand declines exactly likeaws_rekognitionoraws_rekognition_liveness. Every other check, and every one of these set tomanual_review, goes to review instead. Nothing blocks by default. - Review whenever a check is not clear, a result is uncertain, or any
evidence is missing or failed. Missing evidence holds the session in review
even when another check would decline it: a check step that recorded no
check, a check that errored, or a sanctions screening that was incomplete
(a stale or attention-flagged list, entries dropped from a list, capped
candidates). A sanctions match declines only on a complete screening. A
face mismatch is review, never a decline, when a capture problem (a
capture.*code: an unreadable, expired, unsupported or one-sided document) was found anywhere in the session’s current attempts, in any workflow: for example, a proof-of-address upload that could not be read holds a face mismatch in the identity workflow, since the capture may be what failed. A PEP and adverse media never decline, nor does liveness without your own decline threshold. - Approved when every check is clear and nothing needs review, with or without a validate step.
Saving a workflow with block on a check that can never decline (document
authenticity, adverse media, a vision-model face match or liveness check, the
local liveness model, your own HTTP check) is refused.
A korala_liveness step’s thresholds are now checked when the workflow is
saved, as aws_rekognition_liveness ones always were: an existing workflow
whose korala_liveness thresholds are invalid gets a 400 on its next
update until they are fixed.
Liveness declines only below a threshold you choose. Korala sets no
default and has not measured where genuine people and attacks separate for
your users. Below clearConfidence (90 unless you set it) a session goes to
review; below declineConfidence it is declined. A threshold set too high
declines genuine people: someone in bad light or on an old camera scores low
too. Start low, for example 10, and raise it only on your own reviewed
results.
{
"type": "check",
"config": {
"kind": "active_liveness",
"provider": "aws_rekognition_liveness",
"blockingMode": "block",
"providerConfig": { "clearConfidence": 90, "declineConfidence": 10 }
}
}declineConfidence must be below clearConfidence; without it, block is
refused.
Each session carries a decision:
{
"outcome": "rejected",
"decidedBy": "auto",
"decidedAt": "2026-10-12T09:30:00.000Z",
"rulesVersion": "korala-decisions-v3",
"reasonCodesVersion": "korala-reasons-v1",
"codes": ["biometric.face_mismatch"],
"decidingCheckIds": ["8b0d…"],
"findings": [
{ "code": "biometric.face_mismatch", "effect": "decline", "checkId": "8b0d…" }
],
"attempts": [
{ "sessionWorkflowId": "c41e…", "workflowRunId": "5f2a…", "attempt": 1 }
]
}decidedBy is auto, reviewer (someone in the dashboard) or partner
(your integration through the API). codes are Korala’s reason codes, in the
families document, biometric, duplicate, screening, capture and
data: the decline codes of a rejection (plus
duplicate.identity_changed_between_attempts where the person changed
between attempts, which never declines itself; its finding stays review),
or the codes holding a session in review. Route on them rather than on resolutionReason, which is for people.
The record holds codes and ids only, never personal data, and is included in
session webhooks.
Partners can override the resolution at any time before the session expires:
await korala.sessions.resolve(sessionId, {
resolution: 'approved',
reason: 'Manually verified after review',
});An expired session stays expired: resolving it answers 409 with
reason: "session_expired" (see Resubmission and
expiry for what counts as expired). A session in
pending_review never expires, so you can decide it after its deadline.
Listing Sessions
const sessions = await korala.sessions.list({
externalUserId: 'usr_abc123',
status: 'completed',
page: 1,
limit: 10,
});Session Webhooks
Subscribe to these events in your webhook configuration:
| Event | Description |
|---|---|
session_completed | All workflows in the session finished. status says whether it is completed or waiting in pending_review |
session_resolved | A resolution was set or changed, automatically (resolvedBy: "auto") or by your team. With resolution: null and decision: null, a resubmission withdrew the earlier decision |
session_review_required | The session entered pending_review: a workflow paused for review, or its results need a decision from you |
session_resubmission_requested | Korala asked the person to redo one or more workflows because of a capture problem; resubmission.workflows lists each with its attempts left, resubmission.sessionWorkflowId is the earliest and resubmission.attemptsRemaining the fewest left; expiresAt is the session’s expiry after the resubmission extended it, or null |
session_expired | The session expired: the person did not finish (created or in_progress) by expiresAt, or you expired it. A session in review never expires |
session_failed | Session failed (no retries remaining) |
session_workflow_completed | Individual workflow within session completed |
session_workflow_failed | Individual workflow within session failed |
Every session event is recorded in the same database transaction as the
change it reports, then sent within a few seconds. Each change is announced
once; a later, different outcome is a new event. For example, an automatic
approval that a late check sends back to review is followed by
session_review_required with resolution: null, and a later automatic
rejection by a second session_resolved. The same outcome judged again with
different decision.codes is announced too: session_resolved for a resolved
session, session_review_required for one in review.
session_completed, session_resolved, session_review_required and
session_workflow_completed carry the session’s sessionId,
externalUserId, status, resolution, resolvedBy, riskScore,
riskLevel, sandbox and decision (the decision record: codes, ids and
versions), and no personal data: fetch the session for its
results, including its riskSummary, which session_resolved no longer
carries. A resolution set by your team also carries the reason you gave
and overridden. session_workflow_completed also carries
sessionWorkflowId and workflowSlug, the session workflow that finished.
session_expired carries sessionId and externalUserId.
session_workflow_failed carries sessionId, externalUserId,
errorMessage, workflowSlug, attemptsRemaining and expiresAt (the
session’s expiry, extended when you are asked to retry the step, or null
for a session with no expiry); session_failed
carries sessionId, externalUserId, errorMessage and failedWorkflow.
errorMessage in session_failed and session_workflow_failed, and reason
in session_resolved, can be free text your own reviewers wrote when they
rejected a step or resolved the session.
Every session event’s data carries version, a number that grows with each
change Korala announces for that session. Events of one change share it.
Numbers may skip, and an event without version is older than any event
with one. The session itself carries the same number as eventVersion.
Delivery is at least once, and events can arrive out of order. Verify the
signature and deduplicate on eventId. Compare version with the newest you
have seen for the session, or with the session’s eventVersion: the higher
one wins, and an event with a lower version is older news. Read the session
before acting on it.
Resubmission
Automatic
When the only problems in the session are ones the person can fix, Korala asks them to redo the workflow as soon as it finishes or its capture step fails, without waiting for the rest of the session:
| Code | When |
|---|---|
capture.document_expired | The document has expired |
capture.document_unsupported | The document or file type is not accepted |
capture.image_unreadable | The document could not be read |
capture.side_missing | A side of the document is missing |
capture.file_too_large | The file is larger than the step accepts |
capture.document_no_face | No face was found on the identity document photo |
capture.selfie_no_face | No face was found in the selfie |
capture.selfie_multiple_faces | More than one face was found in the selfie |
capture.selfie_too_dark | The selfie face is darker than the step’s minSelfieBrightness |
capture.selfie_blurry | The selfie face is less sharp than the step’s minSelfieSharpness |
The selfie and portrait codes come only from Amazon Rekognition’s own face
detection on an aws_rekognition face match whose similarity was uncertain
or could not be computed. A clear match is never inspected, a clear mismatch
stays biometric.face_mismatch, and an uncertain similarity with one usable
face stays biometric.face_uncertain. The selfie is asked for in the
workflow that captured it; a document with no face asks for the identity
document again, and the face match that read it. These codes never decline,
even on a face match set to block. minSelfieBrightness and
minSelfieSharpness (0 to 100, in the step’s providerConfig) have no
default; without them only the face counts apply. An unknown key, or a
value outside 0 to 100, in an aws_rekognition face match’s
providerConfig is refused with 400 when the workflow is saved.
A biometric verdict that was not clear on an earlier attempt carries forward: a face mismatch (declined or not), an uncertain face match, or Face Liveness below its clear threshold or a spoof. A result from your own HTTP check or a vision model that is not clear, an error included, carries forward too, whatever its metadata says. An error from Korala’s own face comparison or liveness check, or one of their results that compared nothing (no face found, an unusable image, a liveness session missing, failed, expired or unfinished), does not carry. A result from any participant of the session holds the whole session.
The person may still retake, but a later clear result goes to review with
biometric.earlier_attempt_not_clear (in codes, the earlier check in
decidingCheckIds) instead of approving; a definite decline still declines.
What clears it is the approval in force, yours or a reviewer’s in the
dashboard, and only for results from before that approval. Nothing else
does: not your resubmission of any workflow, not a rejection (a rejection
after an approval brings back what that approval had cleared), and not
reopening with reopen: true (a result written after the approval, which
nobody has seen, still holds). A retake for more than one face, darkness or
blur happens only beside an uncertain similarity, so it always ends in
review or a decline; a retake for no face can still approve. A resubmit
decision’s codes are what is asked for; its findings can also hold this
review finding. These rules are rulesVersion: "korala-decisions-v3".
The workflow goes to resubmit_required with these codes in
resubmissionReasons, the session stays in_progress, its decision has
outcome: "resubmit", and you receive session_resubmission_requested (and
no session_resolved: nothing was decided or withdrawn). The
person sees why on the session link and tries again there. If anything else
in the session needs review (a PEP, an uncertain face match, a risk finding,
a validation result, missing evidence), the session goes to review instead,
and a definite decline still declines.
Every workflow with a capture problem of its own is asked for again, with its own reasons, together with the later ones that read its result (see “On request” below for what reading means); the rest keep their result. A failure with no reason the person can act on is retried with a plain “We couldn’t finish this step. Please try it again.”
The decision record and resubmissionReasons stay while the person redoes
the workflow, as the record of why they were asked; the next decision
replaces the record, and the workflow’s next completion clears its reasons.
A file Korala could not read back from its own storage is retried on
Korala’s side. If it still cannot be read, the session goes to
pending_review with data.evidence_unavailable: the attempt is not
counted against the person and the session does not fail, so your team can
resubmit it. A file replaced with more bytes after it was confirmed is the
person’s file, and is refused as capture.file_too_large.
Each workflow allows maxAttempts attempts (3 unless the configuration’s
workflow binding sets 1 to 20). At the limit the session goes to
pending_review at once, even if later workflows remain, with
data.attempts_exhausted beside every finding: never a decline, and never
failed for a capture problem. Automatic resubmission needs a session
configuration that is a verification flow with autoResolve on; otherwise,
and for sessions created without a configuration, you decide.
Resubmission and expiry
A resubmission moves the session’s expiresAt out so the person has time
to redo the step: to now plus the configuration’s
resubmissionMinimumWindowMinutes (a day unless you set it), or leaves it
where it is when that is later. It never brings an expiry closer. This
applies to every resubmission, Korala’s automatic one and yours, a reopen
included, and to every release of a session from pending_review back to
in_progress with workflows still to run: a review step approved, or an
async check (such as a face match) that finishes. A session with no expiry
keeps none. The window is read from the configuration each time, so
changing it also changes what sessions already under way get the next time
their work resumes.
A session has expired when its status is expired, or when the person had
not finished (created, in_progress) by expiresAt. A session in
pending_review never expires: the person has finished, and only the
review is late. An expired session stays expired. Resolving or
resubmitting it answers 409 with reason: "session_expired" (reopen
does not change that). Create a new session to ask the person again. With
the API client, read error.reason (or error.code) on the KoralaError.
A session the person finished (in review, completed, or decided by your
team, your integration or Korala, at any time) can be resubmitted or
reopened after expiresAt, and it gets the window from now. A review step
in a workflow (POST /workflows/runs/:id/review) can likewise be approved
or rejected after the deadline while the session is in review; it answers
409 with reason: "session_expired" only once the session has expired.
An approval that puts the session back in progress (a step that is not the
workflow’s last, or the last one with other workflows still to run) moves
expiresAt out by the window, as a resubmission does, so the remaining
steps run. No webhook announces it; read the session.
With a window of 0 nothing is extended, so past expiresAt the person
would have no time at all. A resubmit, a reopen, or a review approval that
would put the session back in progress then answers the same 409 with
reason: "session_expired", and nothing changes. An approval of a
workflow’s last step is refused only when Korala can tell beforehand that
it would put the session back in progress: with a check that is not clear
on the session, it may ask for a redo instead, so the approval is taken. A
review rejection is
still taken: when the workflow has attempts left, the person is asked
again with no extra time, so the session expires. A session Korala itself
puts back in progress (an async check that finishes, a retry) keeps its
deadline. Past expiresAt it is then marked expired within about a
minute, and session_expired is sent.
A session whose person never finishes (created or in_progress past
expiresAt) is marked expired on its own within about a minute of its
deadline, whether or not anyone opens its link, and session_expired is
sent once. Lists show it as expired from then on.
session_resubmission_requested carries expiresAt: the session’s expiry
after the extension, as an ISO 8601 time, or null for a session with no
expiry. It can be in the past when Korala judged the step after the
person’s deadline: the session has expired, and the person will not be
asked again. session_workflow_failed carries expiresAt too, so a
failed step you are asked to retry (where you decide) shows its extended
expiry. The session itself (GET /sessions/:id, or the response of your
resubmit request) shows the expiry as well.
On request
If a workflow fails or the user must redo it, request resubmission:
await korala.sessions.resubmit(sessionId, sessionWorkflowId);This resets the workflow and the later workflows that read its result: a
face match that compares the selfie with the ID’s portrait, a check that
compares the name on a proof of address with the ID, or any step that reads
the whole session (validation, review, risk, screening, your own HTTP check).
Later workflows that do not read it keep their result, so redoing a proof of
address that comes after the identity steps does not make the person redo
their identity verification. A redo of the identity document does reset a
later proof of address whose validation or name check reads the identity:
that proof of address was checked against it. When Korala cannot tell
whether a workflow reads it (including a workflow whose steps or schemas
you changed after it ran; renaming it does not count), it resets it. Reset workflows get their attempts back (attemptCount
returns to 0): being reset because an earlier step changed is not the
person’s try at them.
The decision is made again from the new attempt, and risk summaries are recomputed from the updated evidence.
Attempts
The organisation’s session responses (GET, create, resolve, resubmit) list
every attempt at each workflow in workflows[].attempts, ordered by
attemptNumber and then any attempt not yet numbered by startedAt, the
current one included:
{
"workflowRunId": "5f2a…",
"attemptNumber": 1,
"status": "completed",
"startedAt": "2026-10-12T09:20:00.000Z",
"completedAt": "2026-10-12T09:21:10.000Z",
"resubmissionReasons": ["capture.image_unreadable"],
"documentChoice": { "country": "DE", "type": "passport" },
"checkIds": ["8b0d…"]
}checkIds join to session.checks; an earlier attempt’s checks are the ones
with supersededAt set. resubmissionReasons is why Korala asked for that
attempt again, and stays on the attempt after the workflow’s own
resubmissionReasons clears. attemptNumber follows the order the runs
were claimed and is never rewritten (decisions cite it as
decision.attempts[].attempt). A run an older
version of Korala started during an upgrade is numbered when the next run
of that workflow starts or the session is next decided, so it can be
numbered after a newer run and the numbers need not follow startedAt;
sort by startedAt if you need start order. Until then it has
attemptNumber: null: Korala never shows a number that would change
later. If an older version made the session’s last decision, earlier runs
can stay unnumbered, and that decision cites the older version’s attempt
counter. A decision cites the stored number; in the one case where a run
was never numbered (it should not happen), it cites attempt: null. When
Korala decides automatically, it then records data.attempt_unnumbered:
an approval or a resubmission goes to review instead, with the code in
codes; a review gains the code in codes; beside a definite decline the
code sits in findings, not in codes, and the decline stands. Where
Korala makes no automatic decision (not a verification flow, or
autoResolve off with nothing to review), there is no decision record at
all. A reviewer’s or your own decision stands and simply cites null. The number keeps counting when a workflow is reset
because an earlier one changed, while its attemptCount (the cap) starts
again. The person’s session link never shows attempts.
On a new attempt the person chooses their document again, among the types
the binding’s documentTypes accepts: an unreadable passport can be followed
by an ID card.
A different person between attempts goes to review. When a later
attempt’s identity document shows a different person from an earlier one,
the session goes to review with duplicate.identity_changed_between_attempts.
The switch itself never declines; a definite decline from a check set to
block still declines, with the switch recorded among decision.codes. It
stops an automatic resubmission. A switch needs two disagreements, and any of three
signals is enough:
name_and_date_of_birth: the name and the date of birth both differ;document_number: another document (number and issuing country) whose name differs while the date of birth is not shown to agree (unread on either);document_and_date_of_birth: another document with another date of birth, even under the same name (two people of one name).
A renewed passport or an ID card for the same person is not a switch, and
neither is the same document re-read with one misread field. A date of birth
misread on a renewed document does go to review. A name changed on a renewal
with the same date of birth is unknown. The decision records
identityAcrossAttempts: { "status": "same" | "changed" | "unknown", "signals": [...] }, where unknown means the attempts could not be
compared (a field either could not read, or a name that changed alone),
never that it was the same person; it does not hold the session. Attempts
from before this was recorded have no resubmissionReasons.
A session your team or your integration already decided keeps its decision:
resubmitting it answers 409 unless you reopen it explicitly (a reviewer in
the dashboard can reopen it too, after confirming):
await korala.sessions.resubmit(sessionId, sessionWorkflowId, { reopen: true });A resubmission that withdraws a decision sends exactly one
session_resolved with resolution: null, decision: null and
status: "in_progress", in the same transaction: treat the earlier decision
as no longer standing and wait for the next one. That covers a decision by
your team or integration (reopened with reopen: true), an automatic
approval or rejection, and a session waiting in pending_review, whose
review decision is withdrawn too even though it had no resolution.
Link a session to a risk customer
Pass the Korala risk subject ID alongside your existing customer reference:
const session = await client.sessions.create({
configurationSlug: 'kyc-standard',
externalUserId: 'customer-123',
subjectId: customer.id,
});
const history = await client.sessions.list({ subjectId: customer.id });The subject must already exist in the same organization and environment. Its
external reference must equal externalUserId, and its type must match the
session configuration. Korala rejects mismatches; it never matches customers by
name or email. Several sessions can link to one subject. The optional link is
fixed at creation and returned in session list/detail responses. Existing
sessions remain unlinked. Linking does not import evidence, run an assessment,
or approve the customer.
Record the journey that prompted verification
Alice joins purchase B and needs to repeat KYC. Link her new session to that registered context so you can find the verifications started for B:
const session = await client.sessions.create({
configurationSlug: 'kyc-standard',
externalUserId: 'customer-123',
subjectId: customer.id,
originContextId: purchaseB.id,
});
const history = await client.sessions.list({ originContextId: purchaseB.id });Register the context and add the customer as a participant first; see
Risk contexts. The customer, context and session must
belong to the same organization and environment. Without a linked customer or
context membership, Korala rejects the request. Session list and detail responses
include originContextId, or null when no origin was linked. The link is fixed
at creation; putting a context ID in metadata or session context does not
create it.
Origin records why a verification started. It does not make all its results exclusive to that purchase. General policies can still consume reusable native verification evidence through their configured bindings. Recording the origin does not approve Alice, run an assessment, or enable context-scoped policy evaluation. Context evaluation and policy composition are not available yet.