diff --git a/README.md b/README.md index 774a898..91bd35f 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,22 @@ PCTR routes to the consequence. TTP binds the authority for the exact execution ## Under the hood: the Trust Transfer Protocol -Identity proves *who* is acting. Authorization defines *what* was assigned. **TTP establishes whether the actor is trustworthy enough for this action, right now** — before any downstream authority or execution decision is made. +**TTP is the open protocol for machine chain trust:** proving whether the chain behind an AI agent, copilot, workflow, pipeline, API, service account, or non-human identity is trustworthy enough to be relied on, before downstream authority and execution decisions occur. + +Identity proves *who* is acting. Authorization defines *what* was assigned. **TTP establishes whether the chain behind this action deserves reliance, right now** — before any downstream authority or execution decision is made. + +An *isnad* is a chain of transmission: who received what from whom, and whether each link is sound. An autonomous action has the same shape — a model acted on a prompt, handed to a tool, through a workflow, carrying authority delegated by a person no longer in the room. TTP evaluates that chain, and grades it rather than returning a boolean. See [Chain Trust Model](SPECIFICATION.md#chain-trust-model). + +| Layer | Responsibility | +| --- | --- | +| **TTP** | Machine isnad / chain trust | +| **SCIM-RE** | Runtime identity and authority schema | +| **RAP** | Authority decision engine | +| **Execution Exchange** | Downstream enforcement / control plane | +| **CortexTrace** | Evidence and trace capture | +| **ExecutionReceipts** | Cryptographic proof objects | + +TTP establishes whether a chain deserves reliance. Downstream authority systems decide whether execution may proceed. OAuth standardized delegated access. SCIM standardized identity provisioning. **TTP standardizes trust-before-execution for autonomous systems.** It is a platform-agnostic trust protocol — bring any agent, IdP, gateway, or workflow. diff --git a/SPECIFICATION.md b/SPECIFICATION.md index a8b94b1..5b44481 100644 --- a/SPECIFICATION.md +++ b/SPECIFICATION.md @@ -79,6 +79,137 @@ Blocks in the MVP: | `authority_context` | Defines action/resource context requiring a proof. | | `delegation` | Defines bounded transfer of trust or authority context. | +## Chain Trust Model + +TTP is the open protocol for **machine chain trust**: proving whether the chain behind an +AI agent, copilot, workflow, pipeline, API, service account, or non-human identity is +trustworthy enough to be relied on, before downstream authority and execution decisions +occur. + +The name is deliberate. An *isnad* is a chain of transmission — who received what from +whom, and whether each link in that chain is sound. An autonomous action has the same +shape: a model acted on a prompt, handed to a tool, invoked through a workflow, carrying +authority delegated from a person who is no longer in the room. TTP evaluates that chain. + +### Layering + +```text +TTP = machine isnad / chain-trust protocol +SCIM-RE = runtime identity + authority schema +RAP = authority decision engine +Execution Exchange = downstream enforcement / customer control plane +CortexTrace = evidence + trace capture +ExecutionReceipts = cryptographic proof objects +``` + +TTP establishes **chain trust**. SCIM-RE structures runtime identity, authority grants, +attestations and receipts. RAP makes runtime authority decisions. Execution Exchange +enforces them. CortexTrace captures execution evidence. ExecutionReceipts preserve +cryptographic proof. + +### Chain Primitives + +| Primitive | Meaning | +| --- | --- | +| `TrustChain` | Verifiable chain behind an autonomous action. | +| `ChainActor` | Human, agent, model, tool, workflow, pipeline, service account, API, or workload in the chain. | +| `TransmissionLink` | Handoff between actors, prompts, tools, workflows, systems, or authority contexts. | +| `ReliabilityProfile` | Historical and current reliability state for a chain actor. | +| `EvidenceReference` | Pointer to telemetry, receipts, approvals, runtime state, token state, code state, or external evidence. | +| `Attestation` | Verifiable proof-of-state for an actor, workflow, token, code artifact, model, tool, or environment. | +| `TrustVerifier` | Verifies signatures, issuers, proof modes, freshness, and evidence integrity. | +| `TrustValidator` | Applies decay, thresholds, scope, reliability, delegation, chain continuity, and constraints. | +| `CorroborationSet` | Multiple evidence routes supporting or contradicting the chain. | +| `LatentDefect` | Hidden defect: stale authority, concealed delegation, prompt or tool drift, policy bypass, approval bypass, missing link. | +| `TrustClassification` | Graded trust result. Not binary trust. | +| `TrustTransfer` | Bounded transfer of trust or authority context. | +| `TrustReceipt` | TTP-level proof that chain trust was evaluated. | + +`verify_isnad_chain` in the reference implementation evaluates a `TrustChain`: continuity +(every link's issuer is the prior link's subject), rooting at a trusted authority, per-link +validity, and attenuation — trust MUST NOT amplify along a chain. + +### Trust Roles + +| Role | Purpose | +| --- | --- | +| `EvidenceObserver` | Captures raw evidence from tools, workflows, runtimes, identity systems, APIs, pipelines, or agents. | +| `AttestationIssuer` | Converts evidence into signed, verifiable attestations. | +| `TrustVerifier` | Verifies attestations, signatures, issuers, proof mode, evidence integrity, and freshness. | +| `TrustValidator` | Applies trust decay, reliability history, thresholds, scope, chain continuity, delegation, and constraints. | +| `CorroborationEngine` | Compares evidence paths and detects agreement, contradiction, missing links, or latent defects. | +| `RuntimeAuthority` | Downstream role that converts validated trust into allow, deny, step-up, escalate, throttle, or constrain. | +| `ReceiptNotary` | Signs and chain-links trust validation results, runtime decisions, and execution outcomes. | +| `GovernanceReviewer` | Reviews chains, receipts, defects, and corroboration for audit, compliance, incident response, or oversight. | + +The separation is normative, and it is the point of the protocol: + +> The `TrustVerifier` MUST NOT make final execution decisions unless it is explicitly also +> acting as a downstream `RuntimeAuthority`. +> +> The `TrustValidator` MUST NOT execute protected actions. It determines whether trust +> conditions are satisfied. +> +> TTP establishes whether a chain deserves reliance. Downstream authority systems decide +> whether execution may proceed. + +### Verification Levels + +A deployment states the level it operates at. Higher levels are not merely "more secure" — +they describe how many independent parties must agree before a chain is relied upon. + +| Level | Name | Description | +| --- | --- | --- | +| `L0_LOCAL` | Local evaluator | Cleartext local parsing and evaluation, for development. | +| `L1_SINGLE_VERIFIER` | Single verifier | One verifier validates attestation, proof, issuer and freshness. | +| `L2_VERIFIER_VALIDATOR` | Verifier + validator | Verifier checks evidence; validator applies decay, thresholds, scope, reliability and policy constraints. | +| `L3_CORROBORATED_CHAIN` | Corroborated chain | Evidence references, receipts, attestations or approval records are checked for agreement and contradiction. | +| `L4_VERIFIER_SET` | Distributed verifier set | Multiple verifiers independently verify evidence and produce a quorum or weighted result. | +| `L5_VALIDATOR_QUORUM` | Validator quorum + runtime authority | Multiple validators produce a quorum-backed classification consumed by runtime authority. | +| `L6_PRIVACY_PRESERVING` | ZK / selective disclosure | Trust conditions proven without exposing raw scores, sensitive evidence, internal policies or customer data. | + +### Trust Classification + +Chain trust is graded, not binary. Each classification implies a downstream posture, which +a `RuntimeAuthority` converts into an execution response. + +| Classification | Downstream posture | +| --- | --- | +| `TRUST_ACCEPTED` | allow | +| `TRUST_ACCEPTED_WITH_CONTROLS` | constrain or throttle | +| `TRUST_REVIEW_RECOMMENDED` | step_up | +| `TRUST_CONTRADICTED` | escalate | +| `TRUST_DEFECTIVE` | escalate or deny | +| `TRUST_REJECTED` | deny | +| `TRUST_UNKNOWN` | deny or escalate | + +`TRUST_UNKNOWN` is not a neutral result. A chain nobody has evaluated is not a trustworthy +chain, and it MUST NOT be treated as one. + +### Chain Continuity + +| State | Meaning | +| --- | --- | +| `CHAIN_CONTINUOUS` | Every link is present and each link's issuer is the prior link's subject. | +| `CHAIN_MISSING_LINK` | One handoff in the chain has no evidence behind it. | +| `CHAIN_MULTI_MISSING_LINK` | More than one handoff is unevidenced. | +| `CHAIN_SUSPENDED` | A link was valid and has been suspended or revoked. | +| `CHAIN_AMBIGUOUS` | Evidence supports more than one reading of the chain. | +| `CHAIN_INFERRED` | Continuity is inferred from context rather than evidenced. | +| `CHAIN_UNKNOWN` | Continuity has not been established. | + +### Latent Defects + +A latent defect is a fault the chain does not surface on its own. Detecting one does not +by itself deny an action; it changes the classification, and the downstream authority +decides. + +`prompt_injection_suspected` · `approval_bypass` · `stale_authority` · +`unowned_identity` · `unexpected_tool_use` · `context_loss` · +`policy_version_mismatch` · `scope_inflation` · `dependency_substitution` · +`token_origin_unclear` · `chain_link_unproven` + + ## Trust Object Model ### Subject diff --git a/packages/pctr/src/decisions.mjs b/packages/pctr/src/decisions.mjs index cabfe16..4ba903b 100644 --- a/packages/pctr/src/decisions.mjs +++ b/packages/pctr/src/decisions.mjs @@ -27,6 +27,46 @@ export const PROCEEDS = { STEP_UP: false, ESCALATE: false, SUSPEND: false, DENY: false, REVOKE: false }; +// TTP grades chain trust rather than returning a boolean, and each grade implies a +// downstream posture (SPECIFICATION.md, Trust Classification). This is the mapping a +// RuntimeAuthority applies to turn a classification into one of the responses above. +// TRUST_UNKNOWN is not neutral: a chain nobody evaluated is not a trustworthy chain. +export const CLASSIFICATION_POSTURE = { + TRUST_ACCEPTED: ['KEEP', 'REROUTE'], + TRUST_ACCEPTED_WITH_CONTROLS: ['CONSTRAIN', 'THROTTLE'], + TRUST_REVIEW_RECOMMENDED: ['STEP_UP'], + TRUST_CONTRADICTED: ['ESCALATE'], + TRUST_DEFECTIVE: ['ESCALATE', 'DENY'], + TRUST_REJECTED: ['DENY'], + TRUST_UNKNOWN: ['DENY', 'ESCALATE'] +}; + +export const CHAIN_CONTINUITY = [ + 'CHAIN_CONTINUOUS', 'CHAIN_MISSING_LINK', 'CHAIN_MULTI_MISSING_LINK', + 'CHAIN_SUSPENDED', 'CHAIN_AMBIGUOUS', 'CHAIN_INFERRED', 'CHAIN_UNKNOWN' +]; + +export const LATENT_DEFECTS = [ + 'prompt_injection_suspected', 'approval_bypass', 'stale_authority', 'unowned_identity', + 'unexpected_tool_use', 'context_loss', 'policy_version_mismatch', 'scope_inflation', + 'dependency_substitution', 'token_origin_unclear', 'chain_link_unproven' +]; + +/** The response a classification permits, narrowed by what the situation allows. */ +export function postureFor(classification) { + const allowed = CLASSIFICATION_POSTURE[classification] ?? CLASSIFICATION_POSTURE.TRUST_UNKNOWN; + return { classification, allowed, proceeds: allowed.some((r) => PROCEEDS[r]) }; +} + +/** + * A response must sit inside the posture its classification permits. Answering + * TRUST_REJECTED with a reroute is how a graded protocol degrades into a boolean one + * that always says yes. + */ +export function withinPosture(response, classification) { + return (CLASSIFICATION_POSTURE[classification] ?? CLASSIFICATION_POSTURE.TRUST_UNKNOWN).includes(response); +} + /** * Decide how to respond when a route is reevaluated. Checks run most-restrictive first, * so a revoked credential is never answered with a reroute. diff --git a/packages/pctr/tests/decisions.test.mjs b/packages/pctr/tests/decisions.test.mjs index 833a483..44c3ae0 100644 --- a/packages/pctr/tests/decisions.test.mjs +++ b/packages/pctr/tests/decisions.test.mjs @@ -1,6 +1,9 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { respondToChange, reconcile, RESPONSES, isMoreRestrictive, PROCEEDS } from '../src/decisions.mjs'; +import { + respondToChange, reconcile, RESPONSES, isMoreRestrictive, PROCEEDS, + postureFor, withinPosture, CHAIN_CONTINUITY, LATENT_DEFECTS +} from '../src/decisions.mjs'; const preview = (over = {}) => ({ severity: 'HIGH', recordsAffected: 10, financialExposure: 100, ...over }); const route = (over = {}) => ({ routeId: 'r1', agents: ['a'], trustStates: [{ agentId: 'a', evidenceStale: false }], ...over }); @@ -94,3 +97,41 @@ test('reconcile refuses to answer a change with a weaker response than it warran // A stronger response is allowed to win. assert.equal(reconcile(weak, strong).response, 'SUSPEND'); }); + +test('every TTP trust classification maps to a permitted downstream posture', () => { + // SPECIFICATION.md, Trust Classification. Chain trust is graded, not binary. + const expected = { + TRUST_ACCEPTED: true, TRUST_ACCEPTED_WITH_CONTROLS: true, + TRUST_REVIEW_RECOMMENDED: false, TRUST_CONTRADICTED: false, + TRUST_DEFECTIVE: false, TRUST_REJECTED: false, TRUST_UNKNOWN: false + }; + for (const [classification, proceeds] of Object.entries(expected)) { + const posture = postureFor(classification); + assert.equal(posture.proceeds, proceeds, classification); + for (const response of posture.allowed) assert.ok(RESPONSES.includes(response), `${response} is a real response`); + } +}); + +test('an unevaluated chain is not a trustworthy chain', () => { + // TRUST_UNKNOWN must not be a neutral result, and an unrecognised classification + // must be treated as unknown rather than waved through. + assert.deepEqual(postureFor('TRUST_UNKNOWN').allowed, ['DENY', 'ESCALATE']); + assert.equal(postureFor('something-nobody-defined').proceeds, false); +}); + +test('a response may not step outside the posture its classification permits', () => { + assert.equal(withinPosture('KEEP', 'TRUST_ACCEPTED'), true); + assert.equal(withinPosture('CONSTRAIN', 'TRUST_ACCEPTED_WITH_CONTROLS'), true); + // Answering a rejected chain with a reroute is how a graded protocol turns into a + // boolean one that always says yes. + assert.equal(withinPosture('REROUTE', 'TRUST_REJECTED'), false); + assert.equal(withinPosture('KEEP', 'TRUST_UNKNOWN'), false); +}); + +test('the chain-continuity and latent-defect vocabularies match the specification', () => { + assert.equal(CHAIN_CONTINUITY.length, 7); + assert.ok(CHAIN_CONTINUITY.includes('CHAIN_MISSING_LINK')); + assert.equal(LATENT_DEFECTS.length, 11); + assert.ok(LATENT_DEFECTS.includes('approval_bypass')); + assert.ok(LATENT_DEFECTS.includes('prompt_injection_suspected')); +});