# AI Execution Identity Binding

URL: https://docs.nexart.io/docs/identity-binding

Bind an application-verified identity to a sealed execution record: backend implementation, V1 and V2 paths, verification checks and privacy boundaries.

Identity binding records which authenticated account your application associated with an execution and makes later changes to that association detectable. Your application authenticates the account. NexArt seals the resulting identity reference with the execution evidence.

What this proves
- The recorded identity association is hash-bound and changes to it are detectable.
- With separately verified Node signatures, the attestation is authentic and refers to the sealed record.

What this does not prove
- NexArt authenticated the user or established their legal identity.
- The person personally performed the action, the model ran as described, or the output is correct.
- The application captured every relevant action or truthfully asserted authentication.

## 1. Authenticate, bind, seal and verify

Identity binding flow. Authentication belongs to your application. The producer creates the certificate hash; local verification and Node attestation are separate operations.The authoritative field is `bundle.snapshot.identity`. A separate database column, `meta`, `context`, or an HTTP header is not a substitute. Never append identity after hashing.

## 2. Construct the identity object

Shape illustration only: replace the digest placeholder

```
import type { AiExecutionIdentity } from '@nexart/ai-execution';
const identity: AiExecutionIdentity = {
  provider: 'https://identity.example.com/',
  sub: 'opaque-provider-subject-id',
  assertionHash: 'sha256:<64 lowercase hexadecimal characters>',
  verified: true,
  // emailHash is optional; omit unless needed.
};
```

| Field | Meaning | Application responsibility |
| --- | --- | --- |
| provider | Identity provider / issuer, not model provider. | Derive from an allowlisted, verified issuer. Prefer the exact issuer URL consistently. |
| sub | Subject scoped to the issuer. | Use its verified opaque subject, not an email or display name. |
| assertionHash | Commitment to the authentication assertion. | SHA-256 of the exact raw assertion string in UTF-8; prefix lowercase hex with sha256:. |
| emailHash (optional) | Commitment to a verified email. | Document normalization if required. Never put raw email in this field. |
| verified | Your application’s authentication claim. | Set true only after server-side verification; not a NexArt verdict. |

The account anchor is `provider + sub`; a subject alone is not globally unique. Tokens rotate, so two records for one account can have different `assertionHash` values. Certificate hashes are not stable user identifiers.

## 3. Implement the authentication adapter

For a JWT-based provider, the adapter MUST enforce:

- Signature verification using keys from the configured trusted issuer, never an arbitrary token-supplied JWKS URL.
- An issuer allowlist, the correct audience, and explicitly allowed signing algorithms.
- Expiry, applicable not-before checks, and a nonempty verified subject.
- Provider-specific nonce, session, authentication-age and token-type rules where applicable.
- Authorization for the operation, in addition to authentication.

Do not interchange ID tokens and access tokens without following provider rules. Hash the exact assertion the adapter verified, not the `Bearer` prefix, reserialized decoded JSON, or a second unverified token. Bind the authenticated account to the actual request; document delegated and service-account behavior.

## 4. Choose the correct SDK path

| Record family | Identity-bearing sealing path | Verification / submission |
| --- | --- | --- |
| AI Execution V2 (recommended for new records) | createSnapshotV2 + sealCerV2, or certifyDecisionV2 with identity. | verifyCerV2; separately confirm a V2-compatible Node submission API. |
| AI Execution V1 (complete example below) | createSnapshot with identity, then sealCer. | verifyCer; optional attest sends the complete V1 CER. |

### V2: seal and verify locally

`createSnapshotV2()` accepts identity, and `certifyDecisionV2()` forwards it. When present, identity is included in the SDK-derived protected set. The example takes already verified identity and captured execution fields; it does not implement authentication.

V2 local sealing function

```
import { certifyDecisionV2, verifyCerV2 } from '@nexart/ai-execution';
import type { AiExecutionIdentity, CertifyDecisionV2Params } from '@nexart/ai-execution';

// identity MUST already be derived from server-verified claims.
// execution contains the actual recorded provider, model, input, output,
// full parameters, and explicitly declared executionRole.
export function sealIdentityBoundV2(
  identity: AiExecutionIdentity,
  execution: Omit<CertifyDecisionV2Params, 'identity'>,
) {
  const bundle = certifyDecisionV2({ ...execution, identity });
  if (!bundle.snapshot.identity) throw new Error('Identity missing');
  const result = verifyCerV2(bundle);
  if (!result.ok) throw new Error('Local V2 verification failed');
  return bundle;
}
// Use a separately confirmed V2-compatible Node submission path.
// The SDK 1.4.0 V1 attest() type is not a V2 bundle API.
```

## 5. V1: complete backend implementation

Install the source-baseline SDK

```
npm install @nexart/ai-execution@1.4.0
```

This function receives actual recorded model evidence and an authentication assertion. It does not call a model, implement JWT verification, or fabricate execution evidence. Supply the `verifyAssertion` adapter described above. The complete example intentionally uses the established V1 bundle path and protocol 1.3.0.

Authenticate first, seal locally, then attest the complete V1 CER

```
import { createHash, randomUUID } from 'node:crypto';
import {
  createSnapshot, sealCer, verifyCer, attest,
  type AiExecutionIdentity, type AiExecutionParameters,
} from '@nexart/ai-execution';

type VerifiedClaims = { issuer: string; subject: string };
type VerifyAssertion = (rawAssertion: string) => Promise<VerifiedClaims>;
type RecordedExecution = {
  provider: string;
  model: string;
  prompt: string;
  input: string | Record<string, unknown>;
  output: string | Record<string, unknown>;
  parameters: AiExecutionParameters;
};

function sha256Text(value: string): string {
  return `sha256:${createHash('sha256').update(value, 'utf8').digest('hex')}`;
}

export async function sealAndAttestIdentityBoundExecution(
  rawAssertion: string,
  execution: RecordedExecution,
  verifyAssertion: VerifyAssertion,
  nodeOptions: { nodeUrl: string; apiKey: string },
) {
  if (!rawAssertion) throw new Error('Authentication assertion required');
  // Verify the original assertion, not caller-supplied decoded claims.
  const claims = await verifyAssertion(rawAssertion);
  if (!claims.issuer || !claims.subject) {
    throw new Error('Verified issuer and subject are required');
  }
  const identity: AiExecutionIdentity = {
    provider: claims.issuer,
    sub: claims.subject,
    assertionHash: sha256Text(rawAssertion),
    verified: true,
  };
  const snapshot = createSnapshot({
    executionId: randomUUID(),
    timestamp: new Date().toISOString(),
    protocolVersion: '1.3.0',
    provider: execution.provider,
    model: execution.model,
    prompt: execution.prompt,
    input: execution.input,
    output: execution.output,
    parameters: execution.parameters,
    identity,
  });
  const bundle = sealCer(snapshot);
  const result = verifyCer(bundle);
  if (!result.ok) {
    throw new Error(`Local CER verification failed: ${JSON.stringify(result)}`);
  }
  if (!bundle.snapshot.identity) {
    throw new Error('Identity was not included in the sealed snapshot');
  }
  // Sends the complete sealed V1 CER, including snapshot.identity.
  const attestation = await attest(bundle, nodeOptions);
  if (attestation.certificateHash !== bundle.certificateHash) {
    throw new Error('Node attestation refers to a different certificate');
  }
  // Persist both artifacts separately; this function does not write storage.
  return { bundle, attestation };
}
```

Keep Node options on the backend. Separately verify receipt and applicable envelope signatures using trusted Node keys selected by key id. See Key Management and Independent Verification. This plaintext V1 example does not redact the execution input or output; use a separately confirmed confidential flow for sensitive evidence.

## 6. Preserve identity during transport and storage

The V1 SDK `attest()` helper sends the complete sealed CER as the JSON request body, not an identity-only object or a new wrapper:

V1 transport contract: placeholders only

```
POST https://node.nexart.io/api/attest
Authorization: Bearer <backend Node API key>
Content-Type: application/json

Body: the complete sealed V1 CER, including snapshot.identity
```

`sanitizeForAttestation()` removes undefined values, not a defined `snapshot.identity`. The Node API key authorizes your application’s Node access; it is not the user assertion and the bearer header does not bind user identity.

### Producer checklist

- Derive identity from server-verified claims and attach it before sealing.
- Check nested identity and local integrity before sending the entire bundle.
- Confirm attestation hash association and separately verify signatures.
- Store the original bundle with its attestation under access and retention controls.

### For a Node or ingestion layer you operate

- Accept identity at `snapshot.identity` and validate the entire CER with its family’s verifier.
- Do not strip identity through an old DTO, schema or storage allowlist; preserve rather than reconstruct or reseal the snapshot.
- Associate attestation with the original certificate hash and retain the full original CER if retrieval must include identity.
- Use indexed identity columns only as derived lookup data. Return the original nested object in authorized record/export responses.
- Apply access controls and retention; never display `verified` as an independent Node authentication verdict.

A receipt need not repeat each identity field. Its association with the correct certificate hash links it to the identity-bearing CER.

## 7. Keep trust and privacy boundaries explicit

| Layer | Responsibility | Does not establish |
| --- | --- | --- |
| Application | Verify assertion, authorize operation, construct identity. | That a sealed producer claim is automatically trustworthy. |
| SDK | Include identity in sealed snapshot and check integrity. | JWT signature verification, JWKS fetching or user authentication. |
| Node | Attest submitted record under supported protocol and policy. | That the issuer issued the claimed assertion or a person performed the execution. |
| Downstream verifier | Check integrity and separately attestation authenticity. | Recover or verify the original assertion from its hash alone. |

Independent identity verification needs a separate disclosure process for the original signed assertion, trusted issuer keys, validation policy and relevant authentication time. This identity object is not that complete proof package.

## 8. Test before production

| Test | Expected result |
| --- | --- |
| Valid assertion + explicit identity-bearing snapshot | Expected issuer, subject and assertion hash; local integrity passes. |
| Expired / invalid signature / wrong issuer / wrong audience | Reject before sealing or submission. |
| Browser-supplied verified: true | Ignore/reject; backend derives identity itself. |
| Identity-required request without assertion | Reject; never silently anonymous. |
| Change sub, provider or assertionHash after sealing | Integrity fails. |
| Add identity to a sealed anonymous CER | Integrity fails unless newly sealed; new seal changes certificate identity. |
| Sanitize and serialize | Nested identity survives; integrity passes. |
| Test Node submission and authorized retrieval/export | Original nested identity and certificate hash match. |
| Verify returned Node signatures | Trusted key verifies the expected certificate association. |
| Anonymous operation explicitly permitted | Identity omitted; ordinary integrity still passes. |

Local V1 integrity and transport test

```
import assert from 'node:assert/strict';
import { sanitizeForAttestation, verifyCer } from '@nexart/ai-execution';

// Given a V1 bundle produced with identity:
const transported = JSON.parse(JSON.stringify(sanitizeForAttestation(bundle)));
assert.deepEqual(transported.snapshot.identity, bundle.snapshot.identity);
assert.equal(verifyCer(transported).ok, true);
const tampered = JSON.parse(JSON.stringify(transported));
tampered.snapshot.identity.sub = 'different-subject';
assert.equal(verifyCer(tampered).ok, false);
```

This test checks local integrity and serialization. It does not replace the deployed Node retrieval/export or signature tests.

## 9. Frequently asked questions

### Does verified: true mean NexArt authenticated the user?

No. It records the integrating application’s assertion that authentication succeeded.

### Does verifyCer() verify the JWT?

No. It checks the recorded artifact’s integrity and applicable protocol rules, without contacting the identity provider.

### Does a hash prove who issued a token?

No. A hash commits to bytes. Issuer authenticity requires verifying the original signed assertion against trusted issuer keys and policy.

### Does the same user always produce the same certificate hash?

No. Execution evidence, timestamps, identifiers and assertion values can differ. Use the issuer/subject pair for account association, subject to privacy policy.

### Can identity be added later?

Not while preserving the certificate hash. Create a new seal and, if required, a new attestation. The old attestation does not cover modified evidence.

### Is an anonymous CER invalid?

No. Identity is optional in the SDK; your application can require it by policy.

### Does this prove a human personally initiated the model call?

Not independently. It records the application’s association. Document request authorization and any delegated or service-account behavior for stronger claims.

### What happens in a multi-step workflow?

Attach identity to each execution that requires it; grouping or chaining does not automatically propagate authentication. Project Bundles can surface embedded execution identity in their step registry, not perform a separate authentication process.

## Related documentation

- AI Execution SDK
- AI Execution V2
- Trust model
