/** Transport-independent paired scaffold. No network SDK. * Implement these ports from the normative draft. Reference crypto/codec and * executable Token/Provider classes are in ../javascript/protocol.mjs. * beginVerified MUST verify signature, scoped enrolled key, fresh challenge, * actual interaction binding and current authority INSIDE a durable transaction. * Cached retries return the prior receipt; close() rolls back unless committed. */ export type Operation = "REGISTER" | "USE" | "REVOKE" | "RETRACT"; export type Envelope = Readonly<{fields: readonly string[]; signature: string}>; export interface Verified { readonly op: Operation; readonly target: string; // Full verified challenge/proof and account context are retained by Transaction. } export interface Transaction { readonly intent: Verified; cached(): Envelope | null; registerNewKey(): void; // New-key proof AND independent account authority. requireCurrentEntitlement(): void; revokeTarget(): void; retractRelationship(): void; signResult(): Envelope; commitOnce(result: Envelope): void; close(): void; } export interface Ports { challenge(op: Operation, relationship: string, target: string, trustedContext: string): Envelope; verifyProviderAndApprove(challenge: Envelope): void; signProof(challenge: Envelope): Envelope; verifyResultAndApply(result: Envelope): void; beginVerified(proof: Envelope, trustedContext: string): Transaction; // Returns a signed recovery receipt, without demanding the lost key. recoverRevoke(relationship: string, target: string, trustedContext: string): Envelope; } export class TokenSide { constructor(private readonly ports: Ports) {} respond(challenge: Envelope): Envelope { this.ports.verifyProviderAndApprove(challenge); return this.ports.signProof(challenge); } accept(receipt: Envelope): void {this.ports.verifyResultAndApply(receipt);} } export class ProviderSide { constructor(private readonly ports: Ports) {} start(op: Operation, rid: string, target: string, context: string): Envelope { return this.ports.challenge(op, rid, target, context); } finish(proof: Envelope, trustedContext: string): {receipt: Envelope; fresh: boolean} { const tx = this.ports.beginVerified(proof, trustedContext); try { const cached = tx.cached(); if (cached) return {receipt: cached, fresh: false}; switch (tx.intent.op) { case "REGISTER": tx.registerNewKey(); break; case "USE": tx.requireCurrentEntitlement(); break; case "REVOKE": tx.revokeTarget(); break; case "RETRACT": tx.retractRelationship(); break; default: throw new Error("Unsupported operation"); } const receipt = tx.signResult(); tx.commitOnce(receipt); return {receipt, fresh: true}; // Only a fresh USE may schedule one actuation. } finally {tx.close();} } lost(rid: string, target: string, trustedContext: string): Envelope { return this.ports.recoverRevoke(rid, target, trustedContext); } } export function legacyValue(value: string, approved: boolean, selected: boolean): string { if (!approved || !selected || !value) throw new Error("Local profile not approved/selected"); return value; // Renderer/driver input only. No BringPass command reaches a legacy reader. }