An open API service indexing awesome lists of open source software.

https://github.com/agent-pattern-labs/agent-proof

Action-bound proof challenges for verifying fresh autonomous AI-agent work.
https://github.com/agent-pattern-labs/agent-proof

action-bound actionproof agent-security agent-verification ai-agent bot-detection challenge-response npm-package proof-of-work typescript

Last synced: 21 days ago
JSON representation

Action-bound proof challenges for verifying fresh autonomous AI-agent work.

Awesome Lists containing this project

README

          

# @agent-pattern-labs/actionproof

Action-bound, signed challenge-response verification for proving fresh autonomous AI-agent work.

This package creates short-lived tasks that are awkward for a human to complete quickly but straightforward for an AI agent with tool use: route finding, VM tracing, JSON state mutation, and constrained subset search. Each challenge is random, expiring, and signed, so static bots cannot replay or copy a previous answer.

![ActionProof terminal demo](media/actionproof-demo.gif)

Important limitation: no npm package can mathematically prove that a responder is an AI. This package proves narrower properties: the responder completed fresh heterogeneous computational work within your configured time window, and, when runtime attestations are required, a trusted runtime or mediator signed a receipt for the same challenge and action.

## Install

```sh
npm install @agent-pattern-labs/actionproof
```

## Library usage

```ts
import {
createChallenge,
hashActionPayload,
solvePublicTask,
verifyCapability,
verifyResponse
} from "@agent-pattern-labs/actionproof";

const secret = process.env.ACTIONPROOF_SECRET!;
const post = {
title: "Agent update",
body: "This exact post is what the agent is allowed to publish."
};

const binding = {
subject: "user_123",
action: "create_post",
resource: "POST /posts",
contentHash: hashActionPayload(post)
};

const challenge = createChallenge({
secret,
difficulty: "standard",
ttlMs: 30_000,
binding
});

// Send challenge.instructions, challenge.binding, and challenge.tasks to the agent.
// The agent must return:
// {"challengeId":"...","answers":{"task_id":"64-char lowercase sha256 hex"}}
// Trusted producer runtimes can call solvePublicTask(task) when they own the
// full autonomous path and need a deterministic local task solver.

const result = verifyResponse({
secret,
challenge,
response: agentResponse,
capabilityTtlMs: 15_000,
consumeVerification: (consumeKeys) => reserveVerificationInDatabase(consumeKeys)
});

if (!result.ok || !result.capability) {
throw new Error("not verified");
}

// On the actual POST /posts request, recompute the binding from the request.
// If content changed, this check fails.
const capabilityResult = verifyCapability({
secret,
capability: result.capability,
binding
});

if (capabilityResult.ok) {
// Atomically store capabilityResult.consumeKey as used, then publish the post.
}
```

## Runtime attestations

ActionProof can require a signed runtime attestation in addition to challenge answers. Use this when the agent runtime, MCP mediator, or egress proxy has a signing secret that is not available to the agent process or to users. The attestation is bound to the same challenge id, action binding, and content hash.

This changes the gate from "anyone with the API token and proof flow can post" to "posting also requires a receipt from an approved runtime path." It can block stolen posting tokens and direct manual API calls that do not have access to the runtime attestation key.

```ts
import {
assessAutonomousPostingAssurance,
createAutonomousActionGrant,
createAutonomousAgentSessionReceipt,
createAutonomousPolicyDecision,
createAutonomousRequestProof,
createAutonomousRuntimePolicy,
createSignerCustodyCertificate,
createStrictAutonomousDeploymentCertificate,
createStrictAutonomousDeploymentPolicy,
createStrictAutonomousActionPolicy,
createRuntimeAttestation,
createTriggerOriginProof,
hashActionPayload,
hashAutonomousPolicyDecision,
hashStrictAutonomousDeploymentManifest,
hashChallengeBinding,
auditStrictAutonomousDeploymentManifest,
verifyAutonomousAction,
verifyAutonomousPolicyDecision,
verifyAutonomousAgentSessionReceipt,
verifyAutonomousRequestProof,
verifyResponse,
verifyStrictAutonomousDeploymentCertificate,
verifyStrictAutonomousAction,
verifyStrictAutonomousDeploymentAction,
verifyStrictAutonomousDeploymentResponse,
createStrictGrantedAutonomousPostingProof,
verifyStrictGrantedAutonomousPosting,
verifyStrictGrantedAutonomousDeploymentAction,
verifyStrictGrantedAutonomousAction,
verifyStrictAutonomousResponse
} from "@agent-pattern-labs/actionproof";

const runtimeSecret = process.env.ACTIONPROOF_RUNTIME_SECRET!;

// Issued by the trusted runtime or MCP mediator, not by the agent process.
const runtimeAttestation = createRuntimeAttestation({
secret: runtimeSecret,
issuer: "profilescribe-runtime",
runtimeId: "mcp-worker-1",
challengeId: challenge.id,
binding: challenge.binding!,
mode: "autonomous",
humanInteractive: false,
trigger: {
kind: "source_event",
id: "github-release-42",
source: "github"
},
policyHash: hashActionPayload("posting-policy-v1")
});

const result = verifyResponse({
secret,
challenge,
response: agentResponse,
runtimeAttestation,
runtimeAttestationSecret: runtimeSecret,
runtimeAttestationPolicy: {
allowedIssuers: ["profilescribe-runtime"],
allowedRuntimeIds: ["mcp-worker-1"],
allowedTriggerKinds: ["source_event", "scheduled", "autonomous_policy"],
deniedTriggerKinds: ["human_request", "unknown"],
requiredMode: "autonomous",
requiredPolicyHash: hashActionPayload("posting-policy-v1")
},
capabilityTtlMs: 15_000,
consumeVerification: (consumeKeys) => reserveVerificationInDatabase(consumeKeys)
});
```

For stronger key separation, sign runtime attestations with an Ed25519 private
key and verify them with only the public key in your API:

```ts
const runtimeAttestation = createRuntimeAttestation({
privateKey: runtimeSigningPrivateKey,
keyId: "runtime-key-2026-05",
keyCustody: "external_mediator",
keyExportable: false,
issuer: "profilescribe-runtime",
audience: "profilescribe-api",
runtimeId: "mcp-worker-1",
challengeId: challenge.id,
binding: challenge.binding!,
mode: "autonomous",
humanInteractive: false,
trigger: { kind: "scheduled", id: "daily-profile-check" }
});

const result = verifyResponse({
secret,
challenge,
response: agentResponse,
runtimeAttestation,
runtimeAttestationPublicKey: runtimeSigningPublicKey,
runtimeAttestationPolicy: createAutonomousRuntimePolicy({
allowedIssuers: ["profilescribe-runtime"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["runtime-key-2026-05"],
})
});
```

If the runtime should only sign autonomous work that came from a durable
automation source, bind the attestation to a separate signed trigger receipt.
That lets your API require evidence from a scheduler, webhook mediator, or
policy engine instead of trusting the runtime's bare `trigger.kind` claim:

The origin proof should name the verification method and bind to durable
evidence. Strict policies should allow methods such as `webhook_signature`,
`scheduler_lock`, `policy_engine_decision`, `queue_claim`, or
`external_attestation`, and reject manual or self-reported evidence.

```ts
const originEvidenceHash = hashActionPayload({
deliveryId: githubWebhookEvent.deliveryId,
signature: githubWebhookSignature
});

const originProof = createTriggerOriginProof({
privateKey: sourceVerifierPrivateKey,
keyId: "github-source-key",
issuer: "profilescribe-source-verifier",
audience: "profilescribe-trigger-mediator",
kind: "source_event",
source: "github",
verificationMethod: "webhook_signature",
eventId: githubWebhookEvent.id,
evidenceId: githubWebhookEvent.deliveryId,
payloadHash: hashActionPayload(githubWebhookEvent),
bindingHash: hashChallengeBinding(challenge.binding!),
evidenceHash: originEvidenceHash
});

const triggerReceipt = createTriggerReceipt({
privateKey: triggerSigningPrivateKey,
keyId: "github-webhook-key",
issuer: "profilescribe-trigger-mediator",
audience: "profilescribe-runtime",
kind: "source_event",
payloadHash: hashActionPayload(githubWebhookEvent),
bindingHash: hashChallengeBinding(challenge.binding!),
humanInitiated: false,
source: "github",
eventId: githubWebhookEvent.id,
originProof,
originProofPublicKey: sourceVerifierPublicKey,
originProofPolicy: {
allowedIssuers: ["profilescribe-source-verifier"],
allowedAudiences: ["profilescribe-trigger-mediator"],
allowedKeyIds: ["github-source-key"],
allowedKinds: ["source_event"],
allowedSources: ["github"],
allowedVerificationMethods: ["webhook_signature"],
requireEventId: true,
requireEvidenceId: true,
requiredPayloadHash: hashActionPayload(githubWebhookEvent),
requiredBindingHash: hashChallengeBinding(challenge.binding!),
requiredEvidenceHash: originEvidenceHash
}
});

const runtimeAttestation = createRuntimeAttestation({
privateKey: runtimeSigningPrivateKey,
keyId: "runtime-key-2026-05",
keyCustody: "external_mediator",
keyExportable: false,
issuer: "profilescribe-runtime",
audience: "profilescribe-api",
runtimeId: "mcp-worker-1",
challengeId: challenge.id,
binding: challenge.binding!,
mode: "autonomous",
humanInteractive: false,
trigger: { kind: "source_event", id: githubWebhookEvent.id, source: "github" },
triggerReceipt,
triggerReceiptPublicKey: triggerSigningPublicKey,
triggerReceiptOriginProofPublicKey: sourceVerifierPublicKey,
triggerReceiptPolicy: {
allowedIssuers: ["profilescribe-trigger-mediator"],
allowedAudiences: ["profilescribe-runtime"],
allowedKeyIds: ["github-webhook-key"],
allowedKinds: ["source_event"],
allowedSources: ["github"],
requireEventId: true,
requiredPayloadHash: hashActionPayload(githubWebhookEvent),
requiredBindingHash: hashChallengeBinding(challenge.binding!),
requireOriginProof: true,
requireVerifiedOriginProof: true,
originProofPolicy: {
allowedIssuers: ["profilescribe-source-verifier"],
allowedAudiences: ["profilescribe-trigger-mediator"],
allowedKeyIds: ["github-source-key"],
allowedKinds: ["source_event"],
allowedSources: ["github"],
allowedVerificationMethods: ["webhook_signature"],
requireEventId: true,
requireEvidenceId: true,
requiredPayloadHash: hashActionPayload(githubWebhookEvent),
requiredBindingHash: hashChallengeBinding(challenge.binding!),
requiredEvidenceHash: originEvidenceHash
}
},
policyHash: hashActionPayload("posting-policy-v1"),
environmentHash: hashActionPayload("worker-image-v1")
});

const result = verifyStrictAutonomousResponse({
secret,
challenge,
response: agentResponse,
runtimeAttestation,
runtimeAttestationPublicKey: runtimeSigningPublicKey,
runtimeAttestationTriggerReceiptPublicKey: triggerSigningPublicKey,
runtimeAttestationTriggerOriginProofPublicKey: sourceVerifierPublicKey,
runtimePolicy: {
allowedIssuers: ["profilescribe-runtime"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["runtime-key-2026-05"],
maxAttestationAgeMs: 15_000,
requiredPolicyHash: hashActionPayload("posting-policy-v1"),
requiredEnvironmentHash: hashActionPayload("worker-image-v1"),
triggerReceiptPolicy: {
allowedIssuers: ["profilescribe-trigger-mediator"],
allowedKeyIds: ["github-webhook-key"],
allowedKinds: ["source_event"],
allowedSources: ["github"],
requireEventId: true,
requiredBindingHash: hashChallengeBinding(challenge.binding!),
maxReceiptAgeMs: 15_000,
requireOriginProof: true,
requireVerifiedOriginProof: true,
originProofPolicy: {
allowedIssuers: ["profilescribe-source-verifier"],
allowedKeyIds: ["github-source-key"],
allowedKinds: ["source_event"],
allowedSources: ["github"],
allowedVerificationMethods: ["webhook_signature"],
requireEventId: true,
requireEvidenceId: true,
requiredBindingHash: hashChallengeBinding(challenge.binding!),
requiredEvidenceHash: originEvidenceHash,
maxProofAgeMs: 15_000
}
}
},
signerCertificatePolicy: {
allowedIssuers: ["profilescribe-key-registry"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["key-registry-key"],
allowedKeyCustody: ["external_mediator"],
requireNonExportableKey: true,
denyAgentAccessible: true,
denyUserAccessible: true,
requireSubjectPublicKeyHash: true
},
maxChallengeAgeMs: 15_000,
capabilityTtlMs: 15_000,
consumeVerification: (consumeKeys) => reserveVerificationInDatabase(consumeKeys)
});
```

When a runtime attestation is present, the signed capability carries a runtime summary. For posting gates, verify the action with the strict autonomous helper and consume the capability in the same database transaction that publishes:

```ts
const capabilityResult = verifyStrictAutonomousAction({
secret,
capability: result.capability,
binding,
runtimePolicy: {
allowedIssuers: ["profilescribe-runtime"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["runtime-key-2026-05"],
maxAttestationAgeMs: 15_000,
requiredPolicyHash: hashActionPayload("posting-policy-v1"),
requiredEnvironmentHash: hashActionPayload("worker-image-v1"),
triggerReceiptPolicy: {
allowedIssuers: ["profilescribe-trigger-mediator"],
allowedKeyIds: ["github-webhook-key"],
allowedKinds: ["source_event"],
allowedSources: ["github"],
requireEventId: true,
requiredBindingHash: hashChallengeBinding(result.capability.binding),
maxReceiptAgeMs: 15_000,
requireOriginProof: true,
requireVerifiedOriginProof: true,
originProofPolicy: {
allowedIssuers: ["profilescribe-source-verifier"],
allowedKeyIds: ["github-source-key"],
allowedKinds: ["source_event"],
allowedSources: ["github"],
allowedVerificationMethods: ["webhook_signature"],
requireEventId: true,
requireEvidenceId: true,
requiredBindingHash: hashChallengeBinding(result.capability.binding),
requiredEvidenceHash: originEvidenceHash,
maxProofAgeMs: 15_000
}
}
},
maxCapabilityAgeMs: 15_000,
consumeCapability: (consumeKey) => reserveCapabilityInDatabase(consumeKey)
});
```

`verifyStrictAutonomousResponse` and `verifyStrictAutonomousAction` are the fail-closed helpers for production posting gates. They require strict runtime policy, full trigger receipt verification from a separate trigger issuer/key, a separately verified origin proof for the scheduler/webhook/policy event, explicit challenge and capability freshness limits, consumed challenge verification, and final capability consumption. The lower-level `verifyAutonomousAction` also requires capabilities minted from a consumed verification by default; set `requireConsumedVerification: false` only for local experiments or compatibility migrations.

For production, keep a strict deployment manifest next to the endpoint
configuration and fail deployment if it audits with any issues. This catches the
non-cryptographic gaps that a signed challenge cannot see, such as direct user
posting, bearer-token-only posting, human access to signer keys, user-invoked
runtime posting, manually minted origin events, and missing single-use database
consumption:

```ts
const deploymentCertificatePolicy = {
allowedIssuers: ["profilescribe-deployment-auditor"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["deployment-auditor-key"],
allowedDeploymentIds: ["profilescribe-posting-prod"],
maxCertificateAgeMs: 86_400_000
};

const agentSessionReceiptPolicy = {
allowedIssuers: ["profilescribe-session-controller"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["agent-session-key"],
allowedRuntimeIds: ["mcp-worker-1"],
allowedInputKinds: ["source_event"],
deniedInputKinds: ["human_prompt", "manual_shell", "unknown"],
requireChallengeId: true,
requireBindingHash: true,
requireNoHumanPrompt: true,
requireNoHumanOperator: true,
denyUserShellAccess: true,
denyUserAccessibleAgentEnvironment: true,
allowedKeyCustody: ["external_mediator"],
requireNonExportableKey: true,
requiredPolicyHash: hashActionPayload("posting-policy-v1"),
requiredEnvironmentHash: hashActionPayload("worker-image-v1"),
maxReceiptAgeMs: 15_000
};

const requestProofPolicy = {
allowedIssuers: ["profilescribe-request-mediator"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["posting-request-proof-key"],
allowedKeyCustody: ["external_mediator"],
requireNonExportableKey: true,
requireBindingHash: true,
requireCapabilityId: true,
maxProofAgeMs: 15_000
};

const deployment = {
version: "1.0",
deploymentId: "profilescribe-posting-prod",
runtimePolicy: {
allowedIssuers: ["profilescribe-runtime"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["runtime-key-2026-05"],
maxAttestationAgeMs: 15_000,
requiredPolicyHash: hashActionPayload("posting-policy-v1"),
requiredEnvironmentHash: hashActionPayload("worker-image-v1"),
triggerReceiptPolicy: {
allowedIssuers: ["profilescribe-trigger-mediator"],
allowedKeyIds: ["github-webhook-key"],
allowedKinds: ["source_event"],
allowedSources: ["github"],
requireEventId: true,
requiredBindingHash: hashChallengeBinding(challenge.binding!),
maxReceiptAgeMs: 15_000,
requireOriginProof: true,
requireVerifiedOriginProof: true,
originProofPolicy: {
allowedIssuers: ["profilescribe-source-verifier"],
allowedKeyIds: ["github-source-key"],
allowedKinds: ["source_event"],
allowedSources: ["github"],
allowedVerificationMethods: ["webhook_signature"],
requireEventId: true,
requireEvidenceId: true,
requiredBindingHash: hashChallengeBinding(challenge.binding!),
requiredEvidenceHash: originEvidenceHash,
maxProofAgeMs: 15_000
}
}
},
deploymentCertificatePolicy,
agentSessionReceiptPolicy,
requestProofPolicy,
maxChallengeAgeMs: 15_000,
capabilityTtlMs: 15_000,
maxCapabilityAgeMs: 15_000,
controls: {
proofRequiredForPost: true,
bearerTokenAloneCanPost: false,
directUserPostingAllowed: false,
deploymentCertificateRequired: true,
agentSessionReceiptRequiredForPost: true,
agentSessionSignerAccessibleToAgent: false,
agentSessionSignerAccessibleToUsers: false,
agentSessionAcceptsHumanPrompts: false,
agentSessionAllowsHumanOperators: false,
agentSessionAllowsUserShellAccess: false,
agentEnvironmentAccessibleToUsers: false,
agentSessionCanBeMintedByRuntimeSigner: false,
requestProofRequiredForPost: true,
accessTokenSenderConstrained: true,
requestProofSignerAccessibleToAgent: false,
requestProofSignerAccessibleToUsers: false,
requestProofCanBeMintedByRuntimeSigner: false,
requestProofCanBeMintedByGrantSigner: false,
requestProofCanBeMintedByPolicyDecisionSigner: false,
runtimeSignerAccessibleToAgent: false,
runtimeSignerAccessibleToUsers: false,
runtimeAcceptsInteractiveRequests: false,
runtimeCanBeInvokedByUsersForPosting: false,
triggerSignerAccessibleToAgent: false,
triggerSignerAccessibleToUsers: false,
triggerAcceptsHumanInitiatedEvents: false,
triggerCanBeMintedByRuntimeSigner: false,
originProofSignerAccessibleToAgent: false,
originProofSignerAccessibleToUsers: false,
originProofAcceptsManualEvents: false,
originProofCanBeMintedByTriggerSigner: false,
originProofCanBeMintedByRuntimeSigner: false,
signerCustodyCertificatesRequired: true,
policyDecisionRequiredForPost: true,
grantRequiresPolicyDecision: true,
policyDecisionSignerAccessibleToAgent: false,
policyDecisionSignerAccessibleToUsers: false,
policyDecisionAcceptsHumanInitiatedRequests: false,
policyDecisionCanBeMintedByRuntimeSigner: false,
policyDecisionCanBeMintedByTriggerSigner: false,
policyDecisionCanBeMintedByGrantSigner: false,
consumeVerification: true,
consumeCapability: true,
atomicConsumptionWithPost: true
}
};

const deploymentIssues = auditStrictAutonomousDeploymentManifest(deployment);
if (deploymentIssues.length > 0) {
throw new Error(`Unsafe agent posting deployment: ${deploymentIssues.join(", ")}`);
}

const strictPostingPolicy = createStrictAutonomousDeploymentPolicy(deployment);
const deploymentHash = hashStrictAutonomousDeploymentManifest(deployment);

const deploymentCertificate = createStrictAutonomousDeploymentCertificate({
privateKey: deploymentAuditorPrivateKey,
keyId: "deployment-auditor-key",
issuer: "profilescribe-deployment-auditor",
audience: "profilescribe-api",
deployment
});

const deploymentCertificateResult = verifyStrictAutonomousDeploymentCertificate({
publicKey: deploymentAuditorPublicKey,
certificate: deploymentCertificate,
deployment,
policy: deploymentCertificatePolicy
});

if (!deploymentCertificateResult.ok) {
throw new Error(`Unsafe deployment certificate: ${deploymentCertificateResult.reason}`);
}
```

The returned `strictPostingPolicy` contains the exact `runtimePolicy`,
`signerCertificatePolicy`, `deploymentCertificatePolicy`,
`agentSessionReceiptPolicy`, `requestProofPolicy`, `maxChallengeAgeMs`,
`capabilityTtlMs`, and `maxCapabilityAgeMs` values to pass into strict
verification helpers. `deploymentHash` is useful for release evidence and
deployment logs; `deploymentCertificate` turns that local manifest hash into a
signed control-plane claim from an auditor key your API pins.

You can also pass the manifest directly to the deployment wrappers so the audit
must pass before either verification step runs:

```ts
const signerCertificatePolicy = {
allowedIssuers: ["profilescribe-key-registry"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["key-registry-key"],
allowedKeyCustody: ["external_mediator"],
requireNonExportableKey: true,
denyAgentAccessible: true,
denyUserAccessible: true,
requireSubjectPublicKeyHash: true
};

const runtimeSignerCertificate = createSignerCustodyCertificate({
privateKey: keyRegistrySigningPrivateKey,
keyId: "key-registry-key",
issuer: "profilescribe-key-registry",
audience: "profilescribe-api",
subjectIssuer: "profilescribe-runtime",
subjectKeyId: "runtime-key-2026-05",
subjectPublicKey: runtimeSigningPublicKey,
purposes: ["runtime_attestation"],
keyCustody: "external_mediator",
keyExportable: false,
accessibleToAgent: false,
accessibleToUsers: false
});

const triggerSignerCertificate = createSignerCustodyCertificate({
privateKey: keyRegistrySigningPrivateKey,
keyId: "key-registry-key",
issuer: "profilescribe-key-registry",
audience: "profilescribe-api",
subjectIssuer: "profilescribe-trigger-mediator",
subjectKeyId: "github-webhook-key",
subjectPublicKey: triggerSigningPublicKey,
purposes: ["trigger_receipt"],
keyCustody: "external_mediator",
keyExportable: false,
accessibleToAgent: false,
accessibleToUsers: false
});

const originSignerCertificate = createSignerCustodyCertificate({
privateKey: keyRegistrySigningPrivateKey,
keyId: "key-registry-key",
issuer: "profilescribe-key-registry",
audience: "profilescribe-api",
subjectIssuer: "profilescribe-source-verifier",
subjectKeyId: "github-source-key",
subjectPublicKey: sourceVerifierPublicKey,
purposes: ["trigger_origin_proof"],
keyCustody: "external_mediator",
keyExportable: false,
accessibleToAgent: false,
accessibleToUsers: false
});

const requestProofSignerCertificate = createSignerCustodyCertificate({
privateKey: keyRegistrySigningPrivateKey,
keyId: "key-registry-key",
issuer: "profilescribe-key-registry",
audience: "profilescribe-api",
subjectIssuer: "profilescribe-request-mediator",
subjectKeyId: "posting-request-proof-key",
subjectPublicKey: requestProofSigningPublicKey,
purposes: ["request_proof"],
keyCustody: "external_mediator",
keyExportable: false,
accessibleToAgent: false,
accessibleToUsers: false
});

const agentSessionSignerCertificate = createSignerCustodyCertificate({
privateKey: keyRegistrySigningPrivateKey,
keyId: "key-registry-key",
issuer: "profilescribe-key-registry",
audience: "profilescribe-api",
subjectIssuer: "profilescribe-session-controller",
subjectKeyId: "agent-session-key",
subjectPublicKey: agentSessionSigningPublicKey,
purposes: ["agent_session"],
keyCustody: "external_mediator",
keyExportable: false,
accessibleToAgent: false,
accessibleToUsers: false
});

const agentSessionReceipt = createAutonomousAgentSessionReceipt({
privateKey: agentSessionSigningPrivateKey,
keyId: "agent-session-key",
keyCustody: "external_mediator",
keyExportable: false,
issuer: "profilescribe-session-controller",
audience: "profilescribe-api",
sessionId: agentRun.sessionId,
runtimeId: "mcp-worker-1",
challengeId: challenge.id,
bindingHash: hashChallengeBinding(challenge.binding!),
inputKind: "source_event",
inputSource: "github",
eventId: githubWebhookEvent.id,
humanPromptPresent: false,
humanOperatorPresent: false,
userShellAccess: false,
agentEnvironmentUserAccessible: false,
transcriptHash: agentRun.transcriptHash,
policyHash: hashActionPayload("posting-policy-v1"),
environmentHash: hashActionPayload("worker-image-v1")
});

const agentSessionResult = verifyAutonomousAgentSessionReceipt({
publicKey: agentSessionSigningPublicKey,
receipt: agentSessionReceipt,
challengeId: challenge.id,
bindingHash: hashChallengeBinding(challenge.binding!),
signerCertificate: agentSessionSignerCertificate,
signerCertificatePublicKey: keyRegistrySigningPublicKey,
signerCertificatePolicy,
requireSignerCertificate: true,
policy: agentSessionReceiptPolicy
});

if (!agentSessionResult.ok) {
throw new Error(`Unsafe agent session: ${agentSessionResult.reason}`);
}

const responseResult = verifyStrictAutonomousDeploymentResponse({
secret,
challenge,
response: agentResponse,
runtimeAttestation,
runtimeAttestationPublicKey: runtimeSigningPublicKey,
runtimeAttestationSignerCertificate: runtimeSignerCertificate,
runtimeAttestationSignerCertificatePublicKey: keyRegistrySigningPublicKey,
runtimeAttestationSignerCertificatePolicy: signerCertificatePolicy,
runtimeAttestationTriggerReceiptPublicKey: triggerSigningPublicKey,
runtimeAttestationTriggerReceiptSignerCertificate: triggerSignerCertificate,
runtimeAttestationTriggerReceiptSignerCertificatePublicKey:
keyRegistrySigningPublicKey,
runtimeAttestationTriggerReceiptSignerCertificatePolicy: signerCertificatePolicy,
runtimeAttestationTriggerOriginProofPublicKey: sourceVerifierPublicKey,
runtimeAttestationTriggerOriginProofSignerCertificate: originSignerCertificate,
runtimeAttestationTriggerOriginProofSignerCertificatePublicKey:
keyRegistrySigningPublicKey,
runtimeAttestationTriggerOriginProofSignerCertificatePolicy:
signerCertificatePolicy,
deployment,
deploymentCertificate,
deploymentCertificatePublicKey: deploymentAuditorPublicKey,
deploymentCertificatePolicy,
agentSessionReceipt,
agentSessionReceiptPublicKey: agentSessionSigningPublicKey,
agentSessionReceiptPolicy,
agentSessionReceiptSignerCertificate: agentSessionSignerCertificate,
agentSessionReceiptSignerCertificatePublicKey: keyRegistrySigningPublicKey,
agentSessionReceiptSignerCertificatePolicy: signerCertificatePolicy,
consumeVerification: (consumeKeys) => reserveVerificationInDatabase(consumeKeys)
});

const accessTokenHash = hashActionPayload(apiAccessToken);
const requestProof = createAutonomousRequestProof({
privateKey: requestProofSigningPrivateKey,
keyId: "posting-request-proof-key",
keyCustody: "external_mediator",
keyExportable: false,
issuer: "profilescribe-request-mediator",
audience: "profilescribe-api",
subject: "agent:poster",
action: "create_post",
resource: "POST /posts",
contentHash: binding.contentHash,
bindingHash: hashChallengeBinding(responseResult.capability.binding),
capabilityId: responseResult.capability.id,
accessTokenHash
});

const requestProofResult = verifyAutonomousRequestProof({
publicKey: requestProofSigningPublicKey,
proof: requestProof,
binding,
bindingHash: hashChallengeBinding(responseResult.capability.binding),
capabilityId: responseResult.capability.id,
accessTokenHash,
signerCertificate: requestProofSignerCertificate,
signerCertificatePublicKey: keyRegistrySigningPublicKey,
signerCertificatePolicy,
requireSignerCertificate: true,
policy: {
allowedIssuers: ["profilescribe-request-mediator"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["posting-request-proof-key"],
allowedKeyCustody: ["external_mediator"],
requireNonExportableKey: true,
requireBindingHash: true,
requireCapabilityId: true
}
});

if (!requestProofResult.ok) {
throw new Error(`Unsafe request proof: ${requestProofResult.reason}`);
}

const actionResult = verifyStrictAutonomousDeploymentAction({
secret,
capability: responseResult.capability,
binding,
deployment,
deploymentCertificate,
deploymentCertificatePublicKey: deploymentAuditorPublicKey,
deploymentCertificatePolicy,
requestProof,
requestProofPublicKey: requestProofSigningPublicKey,
requestProofPolicy: {
allowedIssuers: ["profilescribe-request-mediator"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["posting-request-proof-key"]
},
requestProofSignerCertificate,
requestProofSignerCertificatePublicKey: keyRegistrySigningPublicKey,
requestProofSignerCertificatePolicy: signerCertificatePolicy,
accessTokenHash,
consumeCapability: (consumeKey) => reserveCapabilityInDatabase(consumeKey)
});
```

For user-scoped posting, add a separate signed grant from your authorization
service. This is distinct from the proof chain: the proof says the autonomous
path was followed, while the grant says this subject/action/resource/runtime
path is allowed to post.

```ts
const policyDecisionSignerCertificate = createSignerCustodyCertificate({
privateKey: keyRegistrySigningPrivateKey,
keyId: "key-registry-key",
issuer: "profilescribe-key-registry",
audience: "profilescribe-api",
subjectIssuer: "profilescribe-policy",
subjectKeyId: "posting-policy-decision-key",
subjectPublicKey: policyDecisionSigningPublicKey,
purposes: ["policy_decision"],
keyCustody: "external_mediator",
keyExportable: false,
accessibleToAgent: false,
accessibleToUsers: false
});

const grantSignerCertificate = createSignerCustodyCertificate({
privateKey: keyRegistrySigningPrivateKey,
keyId: "key-registry-key",
issuer: "profilescribe-key-registry",
audience: "profilescribe-api",
subjectIssuer: "profilescribe-authz",
subjectKeyId: "posting-grant-key",
subjectPublicKey: grantSigningPublicKey,
purposes: ["action_grant"],
keyCustody: "external_mediator",
keyExportable: false,
accessibleToAgent: false,
accessibleToUsers: false
});

const policyDecision = createAutonomousPolicyDecision({
privateKey: policyDecisionSigningPrivateKey,
keyId: "posting-policy-decision-key",
keyCustody: "external_mediator",
keyExportable: false,
issuer: "profilescribe-policy",
audience: "profilescribe-api",
subject: "agent:poster",
action: "create_post",
resource: "POST /posts",
contentHash: binding.contentHash,
bindingHash: hashChallengeBinding(responseResult.capability.binding),
challengeId: responseResult.capability.challengeId,
runtimeId: "mcp-worker-1",
triggerKind: "source_event",
triggerSource: "github",
triggerEventId: githubWebhookEvent.id,
originVerificationMethod: "webhook_signature",
originEvidenceId: githubWebhookEvent.deliveryId,
originEvidenceHash,
policyHash: hashActionPayload("posting-policy-v1"),
environmentHash: hashActionPayload("worker-image-v1"),
humanInitiated: false
});

const grant = createAutonomousActionGrant({
privateKey: grantSigningPrivateKey,
keyId: "posting-grant-key",
keyCustody: "external_mediator",
keyExportable: false,
issuer: "profilescribe-authz",
audience: "profilescribe-api",
subject: "agent:poster",
actions: ["create_post"],
resources: ["POST /posts"],
runtimeIds: ["mcp-worker-1"],
triggerSources: ["github"],
triggerEventIds: [githubWebhookEvent.id],
contentHashes: [binding.contentHash],
bindingHashes: [hashChallengeBinding(responseResult.capability.binding)],
policyDecisionIds: [policyDecision.id],
policyDecisionHashes: [hashAutonomousPolicyDecision(policyDecision)!],
originVerificationMethods: ["webhook_signature"],
originEvidenceIds: [githubWebhookEvent.deliveryId],
originEvidenceHashes: [originEvidenceHash],
policyHash: hashActionPayload("posting-policy-v1"),
environmentHash: hashActionPayload("worker-image-v1")
});

const grantedActionResult = verifyStrictGrantedAutonomousDeploymentAction({
secret,
capability: responseResult.capability,
binding,
deployment,
deploymentCertificate,
deploymentCertificatePublicKey: deploymentAuditorPublicKey,
deploymentCertificatePolicy,
requestProof,
requestProofPublicKey: requestProofSigningPublicKey,
requestProofPolicy: {
allowedIssuers: ["profilescribe-request-mediator"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["posting-request-proof-key"]
},
requestProofSignerCertificate,
requestProofSignerCertificatePublicKey: keyRegistrySigningPublicKey,
requestProofSignerCertificatePolicy: signerCertificatePolicy,
accessTokenHash,
grant,
grantPublicKey: grantSigningPublicKey,
grantPolicy: {
allowedIssuers: ["profilescribe-authz"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["posting-grant-key"]
},
policyDecision,
policyDecisionPublicKey: policyDecisionSigningPublicKey,
policyDecisionPolicy: {
allowedIssuers: ["profilescribe-policy"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["posting-policy-decision-key"]
},
grantSignerCertificate,
grantSignerCertificatePublicKey: keyRegistrySigningPublicKey,
grantSignerCertificatePolicy: {
allowedIssuers: ["profilescribe-key-registry"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["key-registry-key"],
allowedKeyCustody: ["external_mediator"],
requireNonExportableKey: true,
denyAgentAccessible: true,
denyUserAccessible: true,
requireSubjectPublicKeyHash: true
},
policyDecisionSignerCertificate,
policyDecisionSignerCertificatePublicKey: keyRegistrySigningPublicKey,
policyDecisionSignerCertificatePolicy: {
allowedIssuers: ["profilescribe-key-registry"],
allowedAudiences: ["profilescribe-api"],
allowedKeyIds: ["key-registry-key"],
allowedKeyCustody: ["external_mediator"],
requireNonExportableKey: true,
denyAgentAccessible: true,
denyUserAccessible: true,
requireSubjectPublicKeyHash: true
},
consumeCapability: (consumeKey) => reserveCapabilityInDatabase(consumeKey)
});

const assurance = assessAutonomousPostingAssurance({
deployment,
verification: responseResult,
action: grantedActionResult
});

if (!assurance.ok || assurance.claim !== "controlled_autonomous_path") {
throw new Error(`Insufficient autonomous posting assurance: ${assurance.issues.join(", ")}`);
}

// This is intentionally false even when assurance.ok is true.
// The claim is controlled autonomous path evidence, not non-human identity proof.
console.assert(assurance.nonHumanIdentityProven === false);
```

For new posting gates, `verifyStrictGrantedAutonomousPosting` composes those last
steps so the API cannot forget the final assurance check. It verifies the strict
deployment response, calls `createRequestProof` after the fresh capability exists,
verifies the granted deployment action, and returns `ok: true` only for the
`controlled_autonomous_path` assurance claim.

Producer-side runtimes can use `createStrictGrantedAutonomousPostingProof` to
assemble the envelope that a posting API verifies. It creates or forwards the
deployment certificate, trigger origin proof, trigger receipt, runtime
attestation, agent-session receipt, policy decision, grant, and signer custody
certificates. It intentionally does not solve the challenge and does not create
the final sender-constrained request proof for the API bearer token; the runtime
must supply the agent's challenge response, and the protected API mediator
should still create the request proof at the final posting boundary.

```ts
const actionProof = createStrictGrantedAutonomousPostingProof({
challenge,
response: agentResponse,
binding,
deployment,
createDeploymentCertificate: {
privateKey: deploymentAuditorPrivateKey,
keyId: "deployment-auditor-key",
issuer: "profilescribe-deployment-auditor",
audience: "profilescribe-api"
},
createTriggerOriginProof: {
privateKey: sourceVerifierPrivateKey,
keyId: "github-source-key",
issuer: "profilescribe-source-verifier",
audience: "profilescribe-trigger-mediator",
kind: "source_event",
source: "github",
verificationMethod: "webhook_signature",
eventId: githubWebhookEvent.id,
evidenceId: githubWebhookEvent.deliveryId,
payloadHash: hashActionPayload(githubWebhookEvent),
evidenceHash: hashActionPayload(githubWebhookEvent.body)
},
createTriggerReceipt: {
privateKey: triggerSigningPrivateKey,
keyId: "github-webhook-key",
issuer: "profilescribe-trigger-mediator",
audience: "profilescribe-runtime",
kind: "source_event",
payloadHash: hashActionPayload(githubWebhookEvent),
source: "github",
eventId: githubWebhookEvent.id,
originProofPublicKey: sourceVerifierPublicKey,
originProofPolicy: deployment.runtimePolicy.triggerReceiptPolicy
.originProofPolicy
},
createRuntimeAttestation: {
privateKey: runtimeSigningPrivateKey,
keyId: "runtime-key-2026-05",
keyCustody: "external_mediator",
keyExportable: false,
issuer: "profilescribe-runtime",
audience: "profilescribe-api",
runtimeId: "mcp-worker-1",
trigger: { kind: "source_event", id: githubWebhookEvent.id, source: "github" },
triggerReceiptPublicKey: triggerSigningPublicKey,
triggerReceiptOriginProofPublicKey: sourceVerifierPublicKey,
triggerReceiptPolicy: deployment.runtimePolicy.triggerReceiptPolicy,
policyHash: hashActionPayload("posting-policy-v1"),
environmentHash: hashActionPayload("worker-image-v1")
},
runtimeAttestationSignerCertificate,
runtimeAttestationTriggerReceiptSignerCertificate: triggerSignerCertificate,
runtimeAttestationTriggerOriginProofSignerCertificate: originSignerCertificate,
createAgentSessionReceipt: {
privateKey: agentSessionSigningPrivateKey,
keyId: "agent-session-key",
keyCustody: "external_mediator",
keyExportable: false,
issuer: "profilescribe-session-controller",
audience: "profilescribe-api",
sessionId: agentRun.sessionId,
runtimeId: "mcp-worker-1",
inputKind: "source_event",
inputSource: "github",
eventId: githubWebhookEvent.id,
humanPromptPresent: false,
humanOperatorPresent: false,
userShellAccess: false,
agentEnvironmentUserAccessible: false,
transcriptHash: agentRun.transcriptHash,
policyHash: hashActionPayload("posting-policy-v1"),
environmentHash: hashActionPayload("worker-image-v1")
},
agentSessionReceiptSignerCertificate,
createPolicyDecision: {
privateKey: policyDecisionSigningPrivateKey,
keyId: "posting-policy-decision-key",
keyCustody: "external_mediator",
keyExportable: false,
issuer: "profilescribe-policy",
audience: "profilescribe-api"
},
policyDecisionSignerCertificate,
createGrant: {
privateKey: grantSigningPrivateKey,
keyId: "posting-grant-key",
keyCustody: "external_mediator",
keyExportable: false,
issuer: "profilescribe-authz",
audience: "profilescribe-api"
},
grantSignerCertificate
});
```

```ts
const posting = await verifyStrictGrantedAutonomousPosting({
secret,
challenge,
response,
binding,
deployment,
deploymentCertificate,
deploymentCertificatePublicKey: deploymentAuditorPublicKey,
runtimeAttestation,
runtimeAttestationPublicKey: runtimeSigningPublicKey,
runtimeAttestationTriggerReceiptPublicKey: triggerSigningPublicKey,
runtimeAttestationTriggerOriginProofPublicKey: originProofSigningPublicKey,
agentSessionReceipt,
agentSessionReceiptPublicKey: agentSessionSigningPublicKey,
grant,
grantPublicKey: grantSigningPublicKey,
policyDecision,
policyDecisionPublicKey: policyDecisionSigningPublicKey,
createRequestProof: async ({ capability, bindingHash }) => ({
requestProof: await requestProofMediator.sign({
capabilityId: capability.id,
bindingHash,
accessTokenHash,
subject: binding.subject,
action: binding.action,
resource: binding.resource,
contentHash: binding.contentHash
}),
requestProofPublicKey: requestProofSigningPublicKey,
requestProofSignerCertificate,
requestProofSignerCertificatePublicKey: keyRegistrySigningPublicKey,
accessTokenHash
}),
consumeVerification: (consumeKeys) => reserveVerificationInDatabase(consumeKeys),
consumeCapability: (consumeKey) => reserveCapabilityInPostTransaction(consumeKey)
});

if (!posting.ok) {
throw new Error(`Insufficient autonomous posting evidence: ${posting.reason}`);
}
```

Runtime attestations only mean something if the runtime signing secret is outside the agent environment and outside the user's shell. If a human can read the attestation secret, they can mint receipts. If a human can instruct the approved runtime to perform an allowed autonomous action, the runtime policy must reject that path before signing. Agent session receipts only mean something if the session controller truthfully records prompt ingress, operator presence, shell access, and environment access. Deployment certificates only mean something if the auditor private key is protected and the API pins the expected auditor public key and policy. Request proofs only protect stolen API tokens when the request-proof private key is sender-constrained and unavailable to the token thief. ActionProof verifies those signed claims; it does not independently know the user's intent.

Policy decisions add a final external control-plane check. `verifyStrictGrantedAutonomousAction` verifies the signed decision with a protected key, requires it to be non-human-initiated, binds it to the same runtime evidence and binding hash, and then only accepts grants scoped to that exact decision id/hash. Deployment certificates let an external auditor or control plane sign the exact strict manifest hash and controls hash. Agent session receipts bind capability issuance to a run that the session controller says had no human prompt, no human operator, no user shell access, and no user-accessible agent environment. Request proofs bind the final post to the API access-token hash, capability id, binding hash, and post content before the wrapper consumes the capability. Signer custody certificates let a separate key registry attest that grant, policy-decision, agent-session, and request-proof signing keys are external, non-exportable, inaccessible to agents and users, and purpose-scoped.

`assessAutonomousPostingAssurance` maps those artifacts back to the threat model. It returns `controlled_autonomous_path` only when the strict deployment audit, fresh-work verification, runtime trigger evidence, agent-session receipt, sender-constrained request proof, grant, and policy decision are all present. It always returns `nonHumanIdentityProven: false`; that field is a guardrail against treating signed path evidence as proof that no human was involved anywhere outside the trusted control points.

For posting gates, prefer Ed25519 runtime receipts, keep the private key only in the mediator, require `keyCustody` of `external_mediator` or `hardware_backed`, require non-exportable keys where your infrastructure can make that claim, and deny `human_request` and `unknown` trigger kinds. A trusted mediator should sign `source_event`, `scheduled`, or `autonomous_policy` only when the run came from its own durable automation queue rather than from an interactive user prompt.

`createAutonomousRuntimePolicy` builds those strict defaults for posting gates: autonomous mode only, no human interaction, denied `human_request` and `unknown` triggers, external or hardware-backed key custody, and non-exportable signing keys.

`createStrictAutonomousActionPolicy` adds production posting requirements on top: verifier audience allowlists, runtime key id allowlists, freshness limits, required runtime policy/environment hashes, a non-human-initiated trigger receipt claim, a required signed trigger receipt policy, and a required origin proof policy. Strict policies require the API to verify the full trigger receipt and the embedded origin proof, so the API is not only trusting a runtime-embedded summary. `auditAutonomousActionPolicy` returns issue codes when a hand-built policy is missing those safeguards.

For stolen-token and replay resistance, keep `capabilityTtlMs`, `maxCapabilityAgeMs`, `maxAttestationAgeMs`, and request-proof TTLs short. Use `consumeVerification` to atomically reserve the challenge id plus any runtime attestation and trigger receipt ids before a capability is issued, then atomically reserve the `consumeKey` returned by `verifyAutonomousAction` in the same database transaction that publishes. In strict deployment wrappers, the final consume happens only after the request proof verifies.

## CLI

```sh
export ACTIONPROOF_SECRET="replace-this-with-a-long-random-secret"

actionproof generate --difficulty standard --pretty > challenge.json
actionproof verify --challenge challenge.json --response response.json --pretty
```

You can also pass `--secret ` directly.

For action-bound posting:

```sh
CONTENT_HASH=$(printf '%s' '{"title":"Agent update","body":"..."}' | actionproof hash-payload --json)

actionproof generate \
--difficulty gauntlet \
--ttl-ms 30000 \
--subject user_123 \
--action create_post \
--resource "POST /posts" \
--content-hash "$CONTENT_HASH" \
--pretty > challenge.json

actionproof verify --challenge challenge.json --response response.json --pretty > verification.json

actionproof verify-capability \
--capability verification.json \
--subject user_123 \
--action create_post \
--resource "POST /posts" \
--content-hash "$CONTENT_HASH" \
--pretty
```

## How it works

- `createChallenge` generates a random task set and computes the expected answers server-side.
- The public challenge includes only task inputs, instructions, and a signed verification token.
- `solvePublicTask` is exported for trusted autonomous producer runtimes that need to answer public tasks locally before they sign runtime evidence.
- The token contains challenge metadata, optional action binding, a task hash, answer salts, and salted answer hashes.
- `assessAutonomousPostingAssurance` returns a threat-model report distinguishing fresh-work proof, controlled autonomous path evidence, and the explicit non-human-identity limitation.
- `verifyResponse` checks the token signature, expiry, task integrity, and submitted answer hashes.
- `createRuntimeAttestation` lets a trusted runtime or mediator sign the challenge, action binding, execution mode, and non-interactive status.
- `createTriggerOriginProof` lets a separate scheduler, webhook verifier, or policy-event verifier sign the source event before the trigger mediator signs its receipt.
- `createTriggerReceipt` lets a scheduler, webhook mediator, or policy engine sign the non-human trigger that caused the run.
- `createStrictAutonomousActionPolicy` builds a fail-closed runtime policy for production posting gates; `auditAutonomousActionPolicy` can check hand-built policies for missing controls.
- `auditStrictAutonomousDeploymentManifest` checks the endpoint-level deployment controls that cannot be proven from a challenge response alone.
- `createStrictAutonomousDeploymentCertificate` lets an external auditor or control plane sign the exact strict deployment manifest hash and controls hash.
- `verifyStrictAutonomousDeploymentResponse` and `verifyStrictAutonomousDeploymentAction` fail closed unless the deployment manifest audits cleanly and its required deployment certificate verifies before strict verification runs.
- `createSignerCustodyCertificate` lets an external key registry sign the custody, exportability, purpose, and public-key hash for runtime, trigger, origin, grant, and policy-decision signers.
- `createAutonomousAgentSessionReceipt` lets a session controller sign the run input/access record, including whether human prompts, human operators, user shells, or user-accessible agent environments were present.
- `createAutonomousRequestProof` lets a protected request mediator sign the final post request against the access-token hash, binding hash, capability id, and content hash.
- `createAutonomousPolicyDecision` lets an external policy engine sign the exact allow decision for the same binding, runtime evidence, and non-human trigger.
- `createAutonomousActionGrant` lets your authorization service sign the subject/action/resource/runtime/source scope that is allowed to post.
- `createStrictGrantedAutonomousPostingProof` assembles the producer-side posting envelope from protected runtime/session/trigger/policy/grant signers while leaving challenge solving and final API request-proof creation to their separate trust boundaries.
- `verifyStrictGrantedAutonomousPosting` composes strict response verification, request-proof creation, granted action verification, and the assurance report; it only returns `ok: true` for a controlled autonomous posting path.
- `verifyStrictGrantedAutonomousDeploymentAction` audits the deployment manifest and then requires a strict autonomous capability carrying an approved agent-session receipt, a matching signed grant, a scoped policy decision, and a sender-constrained request proof before it consumes the final post capability.
- `verifyRuntimeAttestation` checks that receipt against your approved issuers, runtime ids, policy hash, environment hash, execution mode, human-interaction rules, and optional signed trigger receipt policy.
- `verifyResponse` can call `consumeVerification` before issuing a capability so solved challenges, runtime attestations, and trigger receipts cannot be replayed within their TTL.
- `verifyStrictAutonomousResponse` and `verifyStrictAutonomousAction` wire those checks together for the verification endpoint and final posting endpoint.
- When a challenge has a binding, a successful response returns a signed capability. The capability records whether verification was consumed before issuance.
- `verifyCapability` checks that the capability is unexpired and matches the exact subject, action, resource, and content hash being submitted.
- When configured, `verifyCapability` also requires the capability to carry an approved runtime attestation summary.
- Verification is stateless; you do not need to store the generated solution in a database.

## Action-bound verification

Use action-bound verification when you want to stop a human from solving a challenge once and then manually posting something else. The verification is bound to one exact operation:

- `subject`: the user, account, API key, or agent identity.
- `action`: the operation, such as `create_post`.
- `resource`: the endpoint or destination, such as `POST /posts`.
- `contentHash`: SHA-256 of the normalized payload being authorized.
- `nonce`: generated by the challenge and returned in the signed capability.

The capability is intentionally not a login session. It is a short-lived, single-action proof. Your application should consume `capabilityResult.consumeKey` exactly once in the same database transaction that performs the protected action.

## Difficulty levels

- `lite`: fewer, smaller tasks for quick local checks.
- `standard`: default multi-task verification.
- `gauntlet`: larger randomized work set for stricter agent gates.

All levels accept `ttlMs` and `taskCount` overrides:

```ts
const challenge = createChallenge({
secret,
difficulty: "gauntlet",
ttlMs: 60_000,
taskCount: 8
});
```

## Security notes

Use a strong server-side secret and generate challenges immediately before asking the agent to solve them. Do not accept expired challenges. For high-value gates, rotate secrets, combine this with rate limiting and account reputation, consume capability keys exactly once, and consider adding private task packs that are not shipped to clients.

For human-resistant posting gates, require runtime attestations and keep the runtime attestation secret in a trusted mediator, hosted worker, hardware-backed key, or other environment the agent and user cannot read. A direct library integration cannot stop a human who controls both the agent runtime and the runtime signing key.