/**
 * Agent identity abstraction for Neotoma MCP write-path attribution.
 *
 * Two attribution paths contribute to a single `AgentIdentity` shape:
 *
 * 1. **AAuth (preferred)** — cryptographically verified HTTP Message Signature
 *    with an agent-token JWT (RFC 9421 + AAuth profile). Populates
 *    `publicKey`, `thumbprint`, `algorithm`, `sub`, `iss`.
 *
 * 2. **Fallback (non-AAuth)** — MCP `initialize.clientInfo` (name + version)
 *    and/or resolved OAuth `connection_id`. Populates `clientName`,
 *    `clientVersion`, `connectionId`.
 *
 * The resulting record is stamped into every durable write-path row
 * (observations, relationships, timeline events, sources, interpretations)
 * so the Inspector and audit tooling can show "who stored this" and with
 * what level of assurance.
 *
 * See docs/proposals/agent-trust-framework.md and the AAuth Neotoma
 * integration plan for the broader rationale.
 */
/**
 * @deprecated Algorithm-based heuristics no longer drive tier resolution.
 * Tier promotion to `hardware` requires a verified `cnf.attestation`
 * envelope; promotion to `operator_attested` requires the operator
 * allowlist. This helper is retained only for legacy diagnostic purposes
 * (e.g. log lines that want to flag "looks SE-ish") and MUST NOT be used
 * to set {@link AttributionTier}. Will be removed once no callers remain.
 */
export function algorithmLooksHardwareBacked(algorithm) {
    if (!algorithm)
        return false;
    const normalised = algorithm.toUpperCase();
    return normalised === "ES256" || normalised === "EDDSA";
}
/**
 * Generic clientInfo names that should NOT count as self-identification.
 * Kept lowercase; comparison is case-insensitive.
 */
const GENERIC_CLIENT_NAMES = new Set(["", "mcp", "client", "mcp-client", "unknown", "anonymous"]);
/** Normalise a self-reported client name to `undefined` when too generic. */
export function normaliseClientName(name) {
    return normaliseClientNameWithReason(name).value;
}
/**
 * Normalise a self-reported client name and also return a reason code when
 * the value was rejected. Returns `{ value: <string> }` on acceptance and
 * `{ value: undefined, reason }` otherwise. Use this in middleware / the
 * diagnostics path; the legacy {@link normaliseClientName} stays as the
 * thin compatibility shim for existing call sites.
 */
export function normaliseClientNameWithReason(name) {
    if (name === null || name === undefined)
        return { value: undefined };
    if (typeof name !== "string")
        return { value: undefined, reason: "not_a_string" };
    const trimmed = name.trim();
    if (!trimmed)
        return { value: undefined, reason: "empty" };
    if (GENERIC_CLIENT_NAMES.has(trimmed.toLowerCase())) {
        return { value: undefined, reason: "too_generic" };
    }
    return { value: trimmed };
}
/**
 * Derive the {@link AttributionTier} from the fields on an
 * {@link AgentIdentity}-in-progress. Pure function; safe to call multiple
 * times while assembling attribution.
 *
 * NOTE: this fallback ONLY returns `software` for AAuth-verified requests.
 * Promotion to `hardware` or `operator_attested` is decided by the AAuth
 * middleware, which has access to the `cnf.attestation` envelope and the
 * operator allowlist; pure derivation cannot make those calls.
 */
export function deriveAttributionTier(input) {
    const aauthVerified = !!(input.publicKey && input.thumbprint);
    if (aauthVerified) {
        return "software";
    }
    if (input.clientName) {
        return "unverified_client";
    }
    return "anonymous";
}
/**
 * Serialise an {@link AgentIdentity} into the provenance JSON shape. Omits
 * keys that are undefined so blobs stay compact.
 *
 * When an {@link ExternalActor} is provided it is attached under the
 * `external_actor` key. This keeps the two identity channels (AAuth agent
 * vs upstream artifact author) distinct in the persisted JSON.
 */
export function toAttributionProvenance(identity, externalActor) {
    if (!identity && !externalActor)
        return {};
    const out = {};
    if (identity) {
        out.attribution_tier = identity.tier;
        out.attributed_at = new Date().toISOString();
        if (identity.publicKey)
            out.agent_public_key = identity.publicKey;
        if (identity.thumbprint)
            out.agent_thumbprint = identity.thumbprint;
        if (identity.algorithm)
            out.agent_algorithm = identity.algorithm;
        if (identity.sub)
            out.agent_sub = identity.sub;
        if (identity.iss)
            out.agent_iss = identity.iss;
        if (identity.clientName)
            out.client_name = identity.clientName;
        if (identity.clientVersion)
            out.client_version = identity.clientVersion;
        if (identity.connectionId)
            out.connection_id = identity.connectionId;
    }
    if (externalActor) {
        out.external_actor = externalActor;
    }
    return out;
}
/**
 * Merge an {@link AttributionProvenance} block into an existing provenance
 * object, returning a JSON string ready for persistence. When either input
 * is empty the output preserves existing fields rather than overwriting
 * them. The merge is shallow and last-write-wins at the attribution key
 * level — which matches how Phase 1 stamps attribution (once per write).
 */
export function mergeAttributionIntoProvenance(existing, attribution) {
    let base = {};
    if (existing && typeof existing === "string") {
        try {
            const parsed = JSON.parse(existing);
            if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
                base = parsed;
            }
        }
        catch {
            // Malformed JSON — fall through to empty base so we never throw.
        }
    }
    else if (existing && typeof existing === "object" && !Array.isArray(existing)) {
        base = { ...existing };
    }
    if (Object.keys(attribution).length === 0)
        return base;
    return { ...base, ...attribution };
}
/**
 * Legacy helper retained for backwards compatibility with FU-053 callers.
 * Prefer {@link getAgentIdentityFromRequest} in new code.
 */
export function getAgentPublicKey(req) {
    if (!req)
        return null;
    const aauth = req.aauth;
    return aauth?.publicKey ?? null;
}
/**
 * Build a fresh {@link AgentIdentity} from AAuth verification result plus
 * fallback attribution inputs. Any field can be undefined; the returned
 * object's {@link AttributionTier} reflects what is actually available.
 */
export function createAgentIdentity(input) {
    const tier = input.tier ?? deriveAttributionTier(input);
    return { ...input, tier };
}
/**
 * Extract the full {@link AgentIdentity} for an Express request. The AAuth
 * middleware populates `req.aauth`; the server layer separately exposes
 * `clientInfo` / `connection_id` via the NeotomaServer session state. This
 * helper is usable in HTTP handlers that want attribution before the MCP
 * `initialize` handshake has completed.
 */
export function getAgentIdentityFromRequest(req, extra) {
    const aauth = req.aauth;
    const clientName = normaliseClientName(extra?.clientName);
    const clientVersion = extra?.clientVersion ?? undefined;
    const connectionId = extra?.connectionId ?? undefined;
    const hasAnything = !!aauth?.verified || !!clientName || !!clientVersion || !!connectionId;
    if (!hasAnything)
        return null;
    // Prefer the tier resolved by the AAuth middleware (which has the
    // attestation envelope and operator allowlist in scope) over the
    // pure-derivation fallback. `createAgentIdentity` will only run
    // `deriveAttributionTier` when `tier` is undefined.
    const decision = req
        .attributionDecision;
    const decisionTier = decision?.signature_verified ? decision.resolved_tier : undefined;
    return createAgentIdentity({
        publicKey: aauth?.publicKey,
        thumbprint: aauth?.thumbprint,
        algorithm: aauth?.algorithm,
        sub: aauth?.sub,
        iss: aauth?.iss,
        clientName,
        clientVersion: clientVersion ?? undefined,
        connectionId: connectionId ?? undefined,
        tier: decisionTier,
    });
}
/**
 * Minimal structural validation. Kept permissive because the middleware
 * already enforces the heavy lifting (signature, JWT, JWKS).
 */
export function validateAgentIdentity(identity) {
    if (!identity || typeof identity !== "object")
        return false;
    if (identity.publicKey !== undefined &&
        (typeof identity.publicKey !== "string" || identity.publicKey.length === 0)) {
        return false;
    }
    const tier = identity.tier;
    return (tier === "hardware" ||
        tier === "operator_attested" ||
        tier === "software" ||
        tier === "unverified_client" ||
        tier === "anonymous");
}
//# sourceMappingURL=agent_identity.js.map