From f18d373954d2c2590db1310299305d52a35fd2fb Mon Sep 17 00:00:00 2001 From: blocksifrdev Date: Tue, 15 Sep 2026 12:59:30 -0400 Subject: [PATCH] Reframe TTP as the machine-isnad chain-trust protocol (#46) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit None of what issue #46 asked for existed: not TrustChain, ChainActor, TransmissionLink, the trust roles, the verification levels, the classification table, chain continuity, or latent defects. The isnad primitive was implemented in src/isnad.mjs but the specification never described what it was for. SPECIFICATION.md gains a Chain Trust Model section carrying all of it — the layering across TTP, SCIM-RE, RAP, Execution Exchange, CortexTrace and ExecutionReceipts; thirteen chain primitives; eight trust roles with the normative boundary language that a TrustVerifier must not make execution decisions and a TrustValidator must not execute protected actions; seven verification levels from local evaluation to privacy-preserving proof; graded classification; seven chain-continuity states; eleven latent defects. The README leads with the framing sentence the issue specified and the layer table, so the distinction is visible before the specification. Two pieces are backed by code rather than left as prose. The classification vocabulary maps onto the nine execution responses already implemented — CLASSIFICATION_POSTURE, postureFor and withinPosture — so a response cannot step outside 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. TRUST_UNKNOWN is not neutral, and an unrecognised classification is treated as unknown rather than waved through. Chain continuity and latent defects are exported vocabularies checked against the spec. 199 tests. Co-Authored-By: Claude Opus 5 --- README.md | 17 +++- SPECIFICATION.md | 131 +++++++++++++++++++++++++ packages/pctr/src/decisions.mjs | 40 ++++++++ packages/pctr/tests/decisions.test.mjs | 43 +++++++- 4 files changed, 229 insertions(+), 2 deletions(-) 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')); +});