// ══════════════════════════════════════════════════════════════════
// Module 37: Decision Semantics & Cross-Engine Interop
// ══════════════════════════════════════════════════════════════════
// Content-addressable decisions, evaluation method classification,
// scope interpretation declaration, cross-engine decision artifacts.
//
// Motivated by cross-engine verification (kanoniv/agent-auth#2):
// Four engines, same scenario, divergent trust verdicts over shared
// structural verdicts. These functions formalize that decomposition.
// ══════════════════════════════════════════════════════════════════
import { v4 as uuidv4 } from 'uuid';
import { sign, verify } from '../crypto/keys.js';
import { canonicalize } from './canonical.js';
import { verifyActionIntent, verifyPolicyDecision } from './policy.js';
// ══════════════════════════════════════
// CONTENT HASHING
// ══════════════════════════════════════
/**
 * Minimum required identity boundary fields per artifact type.
 * The spec declares these minimums — artifacts can commit to more but not fewer.
 */
export const MINIMUM_IDENTITY_FIELDS = {
    action_intent: ['agentId', 'agentPublicKey', 'action', 'delegationId', 'intentId'],
    decision_artifact: ['artifactType', 'engine', 'intent', 'evaluation', 'semantics']
};
/**
 * Compute a content hash of an ActionIntent (unsigned fields only).
 * Uses SHA-256 of canonical JSON serialization.
 * Makes the intent content-addressable — reference by hash, not just signature.
 *
 * The identity boundary (sorted field names) is committed into the hash,
 * making the artifact self-describing: any engine can verify which fields
 * define identity without depending on external projection rules.
 */
export async function computeContentHash(intent) {
    // Extract sorted top-level field names — this IS the identity boundary
    const identityBoundary = Object.keys(intent).sort();
    // Hash includes both the content AND the boundary declaration
    const hashInput = { _identityBoundary: identityBoundary, ...intent };
    const canonical = canonicalize(hashInput);
    const hash = await sha256Hex(canonical);
    return {
        algorithm: 'sha256',
        hash,
        canonicalForm: 'canonical_json_sorted_keys',
        identityBoundary
    };
}
/**
 * Verify that a content hash matches the intent it claims to represent.
 * If identityBoundary is present, verifies the boundary was committed into the hash.
 */
export async function verifyContentHash(intent) {
    if (!intent.contentHash) {
        return { valid: false, error: 'No content hash present on intent' };
    }
    // Rebuild from unsigned fields (exclude signature and contentHash itself)
    const { signature, contentHash, ...unsigned } = intent;
    // Re-include identity boundary in hash input (same as computeContentHash)
    const identityBoundary = contentHash.identityBoundary ?? Object.keys(unsigned).sort();
    const hashInput = { _identityBoundary: identityBoundary, ...unsigned };
    const canonical = canonicalize(hashInput);
    const expected = await sha256Hex(canonical);
    if (expected !== intent.contentHash.hash) {
        return { valid: false, error: `Hash mismatch: expected ${expected}, got ${intent.contentHash.hash}` };
    }
    return { valid: true };
}
/**
 * Validate that an identity boundary meets the spec minimum for its artifact type.
 * The spec declares minimum required fields — the artifact can commit to more but not fewer.
 */
export function validateIdentityBoundary(boundary, artifactType) {
    const minimum = MINIMUM_IDENTITY_FIELDS[artifactType];
    if (!minimum)
        return { valid: true, missing: [] }; // unknown type = no minimum
    const missing = minimum.filter(f => !boundary.includes(f));
    return { valid: missing.length === 0, missing };
}
// ══════════════════════════════════════
// CONTENT-ADDRESSABLE INTENT CREATION
// ══════════════════════════════════════
/**
 * Create an ActionIntent with a content hash embedded.
 * The hash is computed over the unsigned, unhashed intent,
 * then included in the object before signing.
 * This means the signature covers the hash — binding content identity to signer identity.
 */
export async function createContentAddressableIntent(opts) {
    // Build the unsigned intent (without signature or contentHash)
    const unsigned = {
        intentId: 'intent_' + uuidv4().slice(0, 12),
        agentId: opts.agentId,
        agentPublicKey: opts.agentPublicKey,
        delegationId: opts.delegationId,
        action: opts.action,
        context: opts.context,
        createdAt: new Date().toISOString()
    };
    // Compute content hash over unsigned fields
    const contentHash = await computeContentHash(unsigned);
    // Now sign the intent INCLUDING the content hash
    const withHash = { ...unsigned, contentHash };
    const signature = sign(canonicalize(withHash), opts.privateKey);
    return { ...withHash, signature };
}
// ══════════════════════════════════════
// EVALUATION METHOD CLASSIFICATION
// ══════════════════════════════════════
/**
 * Classify the evaluation method of a PolicyDecision.
 * If the decision already has evaluationMethod set, returns it.
 * Otherwise infers from the principles evaluated.
 */
export function classifyEvaluationMethod(decision) {
    if (decision.evaluationMethod)
        return decision.evaluationMethod;
    // Principles F-001 through F-005 are deterministic (scope, expiry, registration)
    // Principles F-006 (Non-Deception) and F-007 (Proportionality) require model reasoning
    const hasModelDependent = decision.principlesEvaluated.some(p => {
        const num = parseInt(p.principleId.replace('F-', ''), 10);
        return num >= 6 && p.status !== 'not_applicable';
    });
    const hasDeterministic = decision.principlesEvaluated.some(p => {
        const num = parseInt(p.principleId.replace('F-', ''), 10);
        return num <= 5 && p.status !== 'not_applicable';
    });
    if (hasModelDependent && hasDeterministic)
        return 'hybrid';
    if (hasModelDependent)
        return 'model_dependent';
    return 'deterministic';
}
// ══════════════════════════════════════
// DECISION SEMANTICS DECOMPOSITION
// ══════════════════════════════════════
/**
 * Decompose a PolicyDecision into structural vs trust components.
 * Structural: F-001 through F-005 (scope, delegation, registration)
 * Trust: F-006, F-007, and any reputation/behavioral checks
 */
export function decomposeDecision(decision) {
    // Split principles into structural (F-001..F-005) and trust (F-006+)
    const structural = decision.principlesEvaluated.filter(p => {
        const num = parseInt(p.principleId.replace('F-', ''), 10);
        return num <= 5;
    });
    const trust = decision.principlesEvaluated.filter(p => {
        const num = parseInt(p.principleId.replace('F-', ''), 10);
        return num >= 6 && p.status !== 'not_applicable';
    });
    const structuralFailed = structural.some(p => p.status === 'fail');
    const trustFailed = trust.some(p => p.status === 'fail');
    const hasTrustLayer = trust.length > 0;
    const structuralVerdict = structuralFailed ? 'deny' : 'permit';
    const trustVerdict = hasTrustLayer
        ? (trustFailed ? 'deny' : 'permit')
        : null;
    // Detect override pattern: when trust overrides a structural permit
    const hasOverride = structuralVerdict === 'permit' && trustVerdict === 'deny';
    const evaluationMethod = classifyEvaluationMethod(decision);
    const reproducibility = evaluationMethod === 'deterministic'
        ? 'structural_by_any_engine'
        : evaluationMethod === 'model_dependent'
            ? 'trust_by_originating_engine_only'
            : 'structural_by_any_engine, trust_by_originating_engine_only';
    return {
        structuralVerdict,
        trustVerdict,
        override: hasOverride ? {
            active: true,
            phase: 'trust_threshold',
            wouldHaveBeen: 'permit'
        } : undefined,
        finalVerdictRule: hasTrustLayer ? 'structural AND trust' : 'structural only',
        reproducibility
    };
}
// ══════════════════════════════════════
// DECISION ARTIFACT CREATION
// ══════════════════════════════════════
/**
 * Create a cross-engine decision artifact from an intent + decision pair.
 * Bundles the pre-execution decision with its semantic decomposition
 * into a single verifiable, content-addressable object.
 */
export async function createDecisionArtifact(opts) {
    // Compute or use existing content hash
    let contentHash;
    if (opts.intent.contentHash) {
        contentHash = opts.intent.contentHash;
    }
    else {
        const { signature, ...unsigned } = opts.intent;
        contentHash = await computeContentHash(unsigned);
    }
    const semantics = decomposeDecision(opts.decision);
    const evaluationMethod = classifyEvaluationMethod(opts.decision);
    const artifact = {
        artifactId: 'dart_' + uuidv4().slice(0, 12),
        artifactType: 'decision',
        version: opts.version ?? '1.0.0',
        engine: opts.engine,
        timestamp: new Date().toISOString(),
        intent: {
            intentId: opts.intent.intentId,
            agentId: opts.intent.agentId,
            action: {
                type: opts.intent.action.type,
                target: opts.intent.action.target,
                scopeRequired: opts.intent.action.scopeRequired
            },
            contentHash
        },
        evaluation: {
            verdict: opts.decision.verdict,
            evaluationMethod,
            principlesChecked: opts.decision.principlesEvaluated.map(p => p.principleId),
            evaluatorId: opts.decision.evaluatorId,
            decisionId: opts.decision.decisionId
        },
        semantics,
        proof: {
            intentSignature: opts.intent.signature,
            decisionSignature: opts.decision.signature
        }
    };
    // Sign the entire artifact
    const artifactSignature = sign(canonicalize(artifact), opts.signerPrivateKey);
    return {
        ...artifact,
        proof: {
            ...artifact.proof,
            artifactSignature
        }
    };
}
// ══════════════════════════════════════
// DECISION ARTIFACT VERIFICATION
// ══════════════════════════════════════
/**
 * Verify all cryptographic properties of a decision artifact.
 * Checks: content hash, intent signature, decision signature, artifact signature.
 */
export async function verifyDecisionArtifact(artifact, keys, originalIntent, originalDecision) {
    const errors = [];
    // 1. Verify content hash
    let contentHashValid = false;
    if (artifact.intent.contentHash) {
        const { signature, contentHash, ...unsigned } = originalIntent;
        // Re-include identity boundary in hash input (same as computeContentHash)
        const identityBoundary = contentHash?.identityBoundary ?? Object.keys(unsigned).sort();
        const hashInput = { _identityBoundary: identityBoundary, ...unsigned };
        const expectedHash = await sha256Hex(canonicalize(hashInput));
        contentHashValid = expectedHash === artifact.intent.contentHash.hash;
        if (!contentHashValid) {
            errors.push('Content hash mismatch');
        }
    }
    // 2. Verify intent signature
    const intentCheck = verifyActionIntent(originalIntent);
    const intentSignatureValid = intentCheck.valid;
    if (!intentSignatureValid) {
        errors.push(`Intent signature invalid: ${intentCheck.errors.join(', ')}`);
    }
    // 3. Verify decision signature
    const decisionCheck = verifyPolicyDecision(originalDecision);
    const decisionSignatureValid = decisionCheck.valid;
    if (!decisionSignatureValid) {
        errors.push(`Decision signature invalid: ${decisionCheck.errors.join(', ')}`);
    }
    // 4. Verify artifact envelope signature
    const { proof, ...artifactBody } = artifact;
    const bodyWithPartialProof = {
        ...artifactBody,
        proof: {
            intentSignature: proof.intentSignature,
            decisionSignature: proof.decisionSignature
        }
    };
    const artifactSignatureValid = verify(canonicalize(bodyWithPartialProof), proof.artifactSignature, keys.artifactSignerPublicKey);
    if (!artifactSignatureValid) {
        errors.push('Artifact envelope signature invalid');
    }
    return {
        valid: errors.length === 0,
        contentHashValid,
        intentSignatureValid,
        decisionSignatureValid,
        artifactSignatureValid,
        errors
    };
}
// ══════════════════════════════════════
// SCOPE INTERPRETATION HELPERS
// ══════════════════════════════════════
/**
 * Get the effective scope interpretation for a delegation.
 * Defaults to 'hierarchical' — APS's native scope matching.
 */
export function getEffectiveScopeInterpretation(delegation) {
    return delegation.scopeInterpretation ?? 'hierarchical';
}
// ══════════════════════════════════════
// SHA-256 HELPER
// ══════════════════════════════════════
export async function sha256Hex(data) {
    // Use Web Crypto API (available in Node.js 18+ and browsers)
    const encoder = new TextEncoder();
    const buffer = encoder.encode(data);
    const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
//# sourceMappingURL=decision-semantics.js.map