Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
131 changes: 131 additions & 0 deletions SPECIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions packages/pctr/src/decisions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
43 changes: 42 additions & 1 deletion packages/pctr/tests/decisions.test.mjs
Original file line number Diff line number Diff line change
@@ -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 });
Expand Down Expand Up @@ -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'));
});
Loading