// Copyright 2024-2026 Tymofii Pidlisnyi. Apache-2.0 license. See LICENSE.
// ══════════════════════════════════════════════════════════════════
// Attribution Primitive — construction
// ══════════════════════════════════════════════════════════════════
// Spec §2.7 composition pipeline. Given a set of axes, an issuer DID, and
// an action tuple, produce a signed primitive. Callers hand us already-
// computed axis entries; weight derivation and hash computation of the
// underlying per-axis artifacts (AccessReceipt hashes, evaluation receipt
// hashes, delegation scope hashes, attestation hashes) is deployment
// policy per §7.1.
// ══════════════════════════════════════════════════════════════════
import { sign } from '../../crypto/keys.js';
import { canonicalHashJCS } from '../../core/canonical-jcs.js';
import { assertCanonicalTimestamp, canonicalTimestamp, envelopeBytes, } from './canonical.js';
import { buildMerkleFrame } from './merkle.js';
/** Derive action_ref from the action tuple. §1.2 / §3.4.
 *
 *  The spec pseudocode in §2.7 uses string concatenation (`agentId || type
 *  || canonical(params) || nonce`), but Theorem 1's reduction relies on
 *  canonical(T) being injective (A1). Plain string concatenation is not
 *  self-delimiting, so two distinct tuples could produce the same byte
 *  string. We adopt canonical(T) on the full tuple, which is injective by
 *  construction and matches the security argument.
 *
 *  Canonicalization uses strict RFC 8785 JCS (canonicalHashJCS), not the
 *  legacy null-stripping APS variant. ATTRIBUTION-PRIMITIVE-v1.1 §1.6
 *  pins all hashing to RFC 8785, and Theorem 1's Assumption A1
 *  (canonicalization injectivity) requires that semantically distinct
 *  action tuples produce distinct canonical bytes — which null-stripping
 *  would violate ({k:null, v:1} and {v:1} would collide). */
export function computeAttributionActionRef(action) {
    if (!action.agentId)
        throw new Error('attribution-primitive: action.agentId required');
    if (!action.actionType)
        throw new Error('attribution-primitive: action.actionType required');
    if (!action.nonce)
        throw new Error('attribution-primitive: action.nonce required');
    if (typeof action.params !== 'object' || action.params === null) {
        throw new Error('attribution-primitive: action.params must be an object');
    }
    return canonicalHashJCS({
        agentId: action.agentId,
        actionType: action.actionType,
        params: action.params,
        nonce: action.nonce,
    });
}
/** Build and sign a complete AttributionPrimitive. §2.7. */
export function constructAttributionPrimitive(params) {
    if (!params.issuer)
        throw new Error('attribution-primitive: issuer required');
    if (!params.issuerPrivateKey)
        throw new Error('attribution-primitive: issuerPrivateKey required');
    const action_ref = computeAttributionActionRef(params.action);
    const frame = buildMerkleFrame(params.axes);
    const merkle_root = frame.root.toString('hex');
    const timestamp = params.timestamp ?? canonicalTimestamp();
    assertCanonicalTimestamp(timestamp);
    const envelope = envelopeBytes({
        action_ref,
        merkle_root,
        issuer: params.issuer,
        timestamp,
    });
    const signature = sign(envelope, params.issuerPrivateKey);
    return {
        action_ref,
        axes: frame.axes,
        merkle_root,
        issuer: params.issuer,
        timestamp,
        signature,
    };
}
/** Re-sign a primitive whose axes or metadata have changed (e.g., after
 *  applying decay in axis P). Returns a fresh primitive; the input is not
 *  mutated. Caller is responsible for providing a new timestamp if the
 *  resigned receipt should be distinct under replay protection. */
export function resignAttributionPrimitive(primitive, issuerPrivateKey, opts) {
    const axes = opts?.axes ?? primitive.axes;
    const action_ref = opts?.action
        ? computeAttributionActionRef(opts.action)
        : primitive.action_ref;
    const frame = buildMerkleFrame(axes);
    const merkle_root = frame.root.toString('hex');
    const timestamp = opts?.timestamp ?? canonicalTimestamp();
    assertCanonicalTimestamp(timestamp);
    const envelope = envelopeBytes({
        action_ref,
        merkle_root,
        issuer: primitive.issuer,
        timestamp,
    });
    const signature = sign(envelope, issuerPrivateKey);
    return { action_ref, axes: frame.axes, merkle_root, issuer: primitive.issuer, timestamp, signature };
}
//# sourceMappingURL=construct.js.map