// Canonical JSON — deterministic serialization for signing
// Sorts keys alphabetically, omits null/undefined in object keys (not arrays).
// SECURITY NOTE: Null-stripping is intentional and consistent across all APS implementations
// (SDK, Gateway, Python). {a:1} and {a:1,b:null} produce the same canonical form.
// This means no security-critical field should use null as a meaningful value.
// All implementations MUST strip nulls identically. See also: canonicalizeJCS() for RFC 8785.
import { createHash } from 'node:crypto';
export function canonicalize(obj, _ancestors) {
    if (obj === null || obj === undefined)
        return 'null';
    if (obj instanceof Date)
        return JSON.stringify(obj);
    if (typeof obj !== 'object')
        return JSON.stringify(obj);
    // Cycle detection — path-scoped (only ancestors in current traversal path).
    // Shared sub-references that are not cycles MUST canonicalize successfully;
    // visit-scoped detection (tracking everything ever seen) over-rejected legitimate
    // structures like { x: leaf, y: leaf }. Output is unchanged for all cycle-free
    // inputs, so no canonical hashes change.
    const ancestors = _ancestors ?? new WeakSet();
    if (ancestors.has(obj))
        throw new Error('Circular reference detected in canonicalize()');
    ancestors.add(obj);
    let result;
    if (Array.isArray(obj)) {
        result = '[' + obj.map(item => canonicalize(item, ancestors)).join(',') + ']';
    }
    else {
        const sorted = Object.keys(obj)
            .sort()
            .filter(key => {
            const val = obj[key];
            return val !== null && val !== undefined;
        })
            .map(key => {
            const val = obj[key];
            return `${JSON.stringify(key)}:${canonicalize(val, ancestors)}`;
        });
        result = '{' + sorted.join(',') + '}';
    }
    ancestors.delete(obj);
    return result;
}
// canonicalJson — deterministic JSON serialization of an object.
// Same semantics as canonicalize() but typed to objects for cross-system
// receipt comparison (action_ref, compound_digest, etc.)
export function canonicalJson(obj) {
    return canonicalize(obj);
}
// canonicalHash — SHA-256 of canonicalJson(obj), returned as lowercase hex.
export function canonicalHash(obj) {
    return createHash('sha256').update(canonicalJson(obj)).digest('hex');
}
// normalizeTimestamp — force ISO 8601 second-precision UTC.
// Accepts any parseable timestamp; returns format: YYYY-MM-DDTHH:mm:ssZ
// Strips fractional seconds and normalizes timezone offsets to UTC.
// Thread claim (A2A#1672): action_ref timestamps are second-precision.
export function normalizeTimestamp(ts) {
    const d = new Date(ts);
    if (Number.isNaN(d.getTime())) {
        throw new Error(`normalizeTimestamp: invalid timestamp "${ts}"`);
    }
    // ISO with milliseconds: 2026-04-05T03:39:31.123Z → strip ms
    return d.toISOString().replace(/\.\d{3}Z$/, 'Z');
}
//# sourceMappingURL=canonical.js.map