{"version":3,"sources":["../src/scope.ts"],"names":[],"mappings":"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","file":"scope.mjs","sourcesContent":["// Scope grammar, canonicalization, and sub-scope relation. See SPEC.md §7.\n//\n// A scope is <product>:<verb>(<constraint-list>).\n// Constraints are <key><op><value>, op ∈ { =, !=, <, <=, >, >=, * }.\n// Canonical form: constraints sorted by key; no whitespace.\n\nexport type ScopeOp = '=' | '!=' | '<' | '<=' | '>' | '>=' | '*';\n\nexport interface ScopeConstraint {\n    key: string;\n    op: ScopeOp;\n    /** `undefined` for the wildcard `*` op; otherwise the raw textual value (unquoted). */\n    value: string | undefined;\n    /** True if the value was supplied as a quoted string; preserved for round-trip fidelity. */\n    quoted: boolean;\n}\n\nexport interface Scope {\n    product: string;\n    verb: string;\n    constraints: ScopeConstraint[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Registered products/verbs (SPEC §7.3) and constraint keys (SPEC §7.6).\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const REGISTERED_SCOPES: Record<string, { keys: string[] }> = {\n    'lock:seal': { keys: ['recipient', 'mime', 'max_bytes'] },\n    'lock:chat': { keys: ['recipient', 'max_bytes_per_msg', 'max_msgs'] },\n    'stamp:sign': { keys: ['mime', 'max_bytes', 'content_hash_prefix'] },\n    'vote:cast': { keys: ['poll_id', 'choice'] },\n    'nostr:publish': { keys: ['kind', 'relay', 'max_bytes'] },\n    'http:request': { keys: ['origin', 'method', 'max_rps', 'max_bytes_out'] },\n    'ln:send': { keys: ['max_sats', 'node', 'max_fee_sats'] },\n    'mcp:invoke': { keys: ['server', 'tool', 'max_invocations'] },\n};\n\n/** Keys whose values are compared numerically for sub-scope ordering. */\nconst NUMERIC_KEYS = new Set<string>([\n    'max_bytes',\n    'max_bytes_per_msg',\n    'max_msgs',\n    'max_bytes_out',\n    'max_rps',\n    'max_sats',\n    'max_fee_sats',\n    'max_invocations',\n    'kind',\n]);\n\nconst IDENT_RE = /^[a-z][a-z0-9_]*$/;\nconst BARE_TOKEN_RE = /^[A-Za-z0-9_.:/@+\\-]+$/;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Parse\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ScopeParseError extends Error {\n    constructor(message: string) {\n        super(message);\n        this.name = 'ScopeParseError';\n    }\n}\n\nexport function parseScope(input: string): Scope {\n    if (typeof input !== 'string' || input.length === 0) {\n        throw new ScopeParseError('scope must be a non-empty string');\n    }\n    if (/\\s/.test(input)) {\n        throw new ScopeParseError(`scope may not contain whitespace: ${JSON.stringify(input)}`);\n    }\n\n    const colonIdx = input.indexOf(':');\n    if (colonIdx < 0) throw new ScopeParseError('scope missing \"product:verb\" separator');\n\n    const product = input.slice(0, colonIdx);\n    if (!IDENT_RE.test(product)) throw new ScopeParseError(`invalid product: ${product}`);\n\n    const rest = input.slice(colonIdx + 1);\n    const parenIdx = rest.indexOf('(');\n\n    let verb: string;\n    let constraintText = '';\n    if (parenIdx < 0) {\n        verb = rest;\n    } else {\n        verb = rest.slice(0, parenIdx);\n        if (!rest.endsWith(')')) throw new ScopeParseError('scope constraint list must end with \")\"');\n        constraintText = rest.slice(parenIdx + 1, -1);\n    }\n    if (!IDENT_RE.test(verb)) throw new ScopeParseError(`invalid verb: ${verb}`);\n\n    const constraints: ScopeConstraint[] = [];\n    if (constraintText.length > 0) {\n        for (const piece of splitTopLevelCommas(constraintText)) {\n            constraints.push(parseConstraint(piece));\n        }\n    }\n\n    // No duplicate keys.\n    const seen = new Set<string>();\n    for (const c of constraints) {\n        if (seen.has(c.key)) throw new ScopeParseError(`duplicate constraint key: ${c.key}`);\n        seen.add(c.key);\n    }\n\n    return { product, verb, constraints };\n}\n\nfunction splitTopLevelCommas(text: string): string[] {\n    const out: string[] = [];\n    let depth = 0;\n    let inQuotes = false;\n    let start = 0;\n    for (let i = 0; i < text.length; i++) {\n        const ch = text[i];\n        if (inQuotes) {\n            if (ch === '\\\\' && i + 1 < text.length) {\n                i++;\n                continue;\n            }\n            if (ch === '\"') inQuotes = false;\n            continue;\n        }\n        if (ch === '\"') {\n            inQuotes = true;\n            continue;\n        }\n        if (ch === '(') depth++;\n        else if (ch === ')') depth--;\n        else if (ch === ',' && depth === 0) {\n            out.push(text.slice(start, i));\n            start = i + 1;\n        }\n    }\n    out.push(text.slice(start));\n    return out;\n}\n\nfunction parseConstraint(piece: string): ScopeConstraint {\n    if (piece.length === 0) throw new ScopeParseError('empty constraint');\n\n    // The `*` op (wildcard) is an op with no value. Recognized by \"key=*\" form.\n    // SPEC uses \"key=*\"; we also accept \"key*\" as legacy alias.\n    // Ops in descending length so \">=\" beats \">\" and \"!=\" beats \"!\".\n    const OPS: ScopeOp[] = ['>=', '<=', '!=', '=', '>', '<'];\n\n    // Special-case wildcard: \"key=*\" or \"key*\".\n    const wildcardMatch = /^([a-z][a-z0-9_]*)(?:=\\*|\\*)$/.exec(piece);\n    if (wildcardMatch) {\n        return { key: wildcardMatch[1]!, op: '*', value: undefined, quoted: false };\n    }\n\n    for (const op of OPS) {\n        const idx = piece.indexOf(op);\n        if (idx <= 0) continue; // key must come first and be non-empty\n        const key = piece.slice(0, idx);\n        if (!IDENT_RE.test(key)) continue;\n        const raw = piece.slice(idx + op.length);\n        const { value, quoted } = parseValue(raw);\n        return { key, op, value, quoted };\n    }\n    throw new ScopeParseError(`constraint missing operator: ${piece}`);\n}\n\nfunction parseValue(raw: string): { value: string; quoted: boolean } {\n    if (raw.length === 0) throw new ScopeParseError('constraint value is empty');\n    if (raw.startsWith('\"')) {\n        if (!raw.endsWith('\"') || raw.length < 2) {\n            throw new ScopeParseError(`unterminated quoted value: ${raw}`);\n        }\n        let v = '';\n        for (let i = 1; i < raw.length - 1; i++) {\n            const ch = raw[i]!;\n            if (ch === '\\\\' && i + 1 < raw.length - 1) {\n                const next = raw[++i]!;\n                v += next;\n            } else if (ch === '\"') {\n                throw new ScopeParseError(`unescaped quote in value: ${raw}`);\n            } else {\n                v += ch;\n            }\n        }\n        return { value: v, quoted: true };\n    }\n    if (!BARE_TOKEN_RE.test(raw)) {\n        throw new ScopeParseError(`invalid bare-token value: ${JSON.stringify(raw)}`);\n    }\n    return { value: raw, quoted: false };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalize\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function canonicalizeScope(scope: Scope): string {\n    const sorted = [...scope.constraints].sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));\n    const parts = sorted.map(serializeConstraint);\n    const inner = parts.join(',');\n    return `${scope.product}:${scope.verb}${parts.length === 0 ? '' : `(${inner})`}`;\n}\n\nexport function canonicalizeScopeString(input: string): string {\n    return canonicalizeScope(parseScope(input));\n}\n\nfunction serializeConstraint(c: ScopeConstraint): string {\n    if (c.op === '*') return `${c.key}=*`;\n    const v = c.quoted ? quoteValue(c.value ?? '') : c.value ?? '';\n    return `${c.key}${c.op}${v}`;\n}\n\nfunction quoteValue(v: string): string {\n    let out = '\"';\n    for (const ch of v) {\n        if (ch === '\"' || ch === '\\\\') out += '\\\\' + ch;\n        else out += ch;\n    }\n    out += '\"';\n    return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Registry-based validation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ValidationOptions {\n    /**\n     * Strict: reject unknown products/verbs and unknown constraint keys.\n     * Permissive: accept unknown products/verbs; ignore unknown keys without treating them as wider.\n     * Default: 'strict'.\n     */\n    mode?: 'strict' | 'permissive';\n}\n\nexport function validateScope(scope: Scope, options: ValidationOptions = {}): void {\n    const mode = options.mode ?? 'strict';\n    const reg = REGISTERED_SCOPES[`${scope.product}:${scope.verb}`];\n    if (!reg) {\n        if (mode === 'strict') {\n            throw new ScopeParseError(`unregistered scope: ${scope.product}:${scope.verb}`);\n        }\n        return; // permissive: no further checks\n    }\n    const registered = new Set(reg.keys);\n    for (const c of scope.constraints) {\n        if (!registered.has(c.key)) {\n            if (mode === 'strict') {\n                throw new ScopeParseError(\n                    `unregistered constraint key for ${scope.product}:${scope.verb}: ${c.key}`\n                );\n            }\n            // permissive: ignore\n        }\n    }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Sub-scope relation (SPEC §7.4)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Is `exercised` a sub-scope of `granted`?\n * Returns true iff every constraint of `granted` admits the corresponding constraint\n * (or absence) in `exercised`, per SPEC §7.4.\n */\nexport function isSubScope(exercised: Scope, granted: Scope): boolean {\n    if (exercised.product !== granted.product) return false;\n    if (exercised.verb !== granted.verb) return false;\n\n    const exIndex = new Map<string, ScopeConstraint>();\n    for (const c of exercised.constraints) exIndex.set(c.key, c);\n\n    for (const g of granted.constraints) {\n        const ex = exIndex.get(g.key);\n        if (g.op === '*') continue; // wildcard: no requirement\n\n        if (g.op === '=') {\n            if (!ex) return false;\n            if (ex.op !== '=' || ex.value !== g.value) return false;\n            continue;\n        }\n\n        if (g.op === '!=') {\n            if (!ex) return false;\n            if (ex.op === '=' && ex.value !== g.value) continue;\n            if (ex.op === '!=' && ex.value === g.value) continue;\n            return false;\n        }\n\n        // Ordered ops: >=, <=, >, <. Exercised's implied range must be ⊆ granted's.\n        if (g.op === '<' || g.op === '<=' || g.op === '>' || g.op === '>=') {\n            if (!ex) return false;\n            if (!NUMERIC_KEYS.has(g.key)) return false;\n            if (ex.op === '*') return false;\n            if (ex.value === undefined || g.value === undefined) return false;\n            if (!rangeSubset(ex, g)) return false;\n            continue;\n        }\n    }\n    return true;\n}\n\nfunction rangeSubset(ex: ScopeConstraint, g: ScopeConstraint): boolean {\n    const exRange = opToRange(ex);\n    const gRange = opToRange(g);\n    if (!exRange || !gRange) return false;\n    return gRange.lo <= exRange.lo && exRange.hi <= gRange.hi;\n}\n\nfunction opToRange(c: ScopeConstraint): { lo: number; hi: number } | null {\n    if (c.value === undefined) return null;\n    const n = Number(c.value);\n    if (!Number.isFinite(n)) return null;\n    switch (c.op) {\n        case '=':\n            return { lo: n, hi: n };\n        case '<':\n            return { lo: -Infinity, hi: n - 1 }; // integers only\n        case '<=':\n            return { lo: -Infinity, hi: n };\n        case '>':\n            return { lo: n + 1, hi: Infinity };\n        case '>=':\n            return { lo: n, hi: Infinity };\n        default:\n            return null;\n    }\n}\n"]}