Channels/Allowlists — Allowlist Matching

Focus Deep-Dive

Compiled Set Matching with Ordered Candidate Resolution

The allowlist matching engine is the core access control primitive. It compiles allowlist entries into an immutable ReadonlySet, resolves sender identity candidates in priority order, and returns a typed match result that tracks how the decision was made.

Set-Based O(1) Lookup 10+ Sources Wildcard Support

📚 What is this & why does it matter?

Every inbound message must pass through allowlist matching before reaching the agent. The engine must be fast (O(1) set lookup), flexible (10+ identity source types), and auditable (match source tracking). The compiled set pattern ensures that even with thousands of allowlist entries, matching is constant-time.

💡 Real-world analogy

Imagine airport security with a pre-approved traveler list stored in a digital database. When a passenger presents their passport, the system checks multiple identifiers in order: passport number, then name, then loyalty program ID. The first match grants access. A VIP wildcard (*) lets everyone through. The audit log records which identifier matched and from which source — critical for security review.

Contents

Type System

AllowlistMatchSource

A union type representing how a match was found:

export type AllowlistMatchSource =
  | "wildcard"    // Matched via * entry
  | "id"          // Platform numeric/string ID
  | "name"        // Display name
  | "tag"         // Platform tag (Discord discriminator)
  | "username"    // Platform username/handle
  | "prefixed-id"    // Channel-prefixed ID (telegram:123)
  | "prefixed-user"  // Channel-prefixed username
  | "prefixed-name"  // Channel-prefixed display name
  | "slug"           // Normalized slug form
  | "localpart";     // Email local part

AllowlistMatch<T>

export type AllowlistMatch<TSource extends string = AllowlistMatchSource> = {
  allowed: boolean;       // Whether the sender is allowed
  matchKey?: string;      // The actual value that matched
  matchSource?: TSource;  // How the match was found
};

CompiledAllowlist

export type CompiledAllowlist = {
  set: ReadonlySet<string>;  // Pre-compiled lookup set
  wildcard: boolean;           // Whether * is present
};

Compilation Pipeline

flowchart LR
    RAW["Raw entries
string[]"] --> FILTER["Filter empty"]
    FILTER --> SET["new Set()"]
    SET --> CHECK["Check for *"]
    CHECK --> COMPILED["CompiledAllowlist
{set, wildcard}"]
export function compileAllowlist(entries: ReadonlyArray<string>): CompiledAllowlist {
  const set = new Set(entries.filter(Boolean));
  return { set, wildcard: set.has("*") };
}

// Variant with normalization (lowercase + trim)
function compileSimpleAllowlist(entries: ReadonlyArray<string | number>): CompiledAllowlist {
  return compileAllowlist(
    entries.map((entry) => String(entry).trim().toLowerCase()).filter(Boolean),
  );
}

Resolution Algorithm

Resolution follows a strict priority order:

  1. If the set is empty → { allowed: false }
  2. If wildcard is present → { allowed: true, matchKey: "*", matchSource: "wildcard" }
  3. Iterate candidates in order; first set membership hit wins
  4. No match → { allowed: false }
flowchart TD
    START["resolveCompiledAllowlistMatch()"] --> EMPTY{"set.size === 0?"}
    EMPTY -->|Yes| DENY["{ allowed: false }"]
    EMPTY -->|No| WILD{"wildcard?"}
    WILD -->|Yes| ALLOW_WILD["{ allowed: true,
matchKey: '*',
matchSource: 'wildcard' }"]
    WILD -->|No| CANDIDATES["Iterate candidates"]
    CANDIDATES --> LOOP{"candidate.value
in set?"}
    LOOP -->|Yes| ALLOW_MATCH["{ allowed: true,
matchKey: value,
matchSource: source }"]
    LOOP -->|No more| DENY
export function resolveAllowlistCandidates<TSource extends string>(params: {
  compiledAllowlist: CompiledAllowlist;
  candidates: Array<{ value?: string; source: TSource }>;
}): AllowlistMatch<TSource> {
  for (const candidate of params.candidates) {
    if (!candidate.value) continue;
    if (params.compiledAllowlist.set.has(candidate.value)) {
      return { allowed: true, matchKey: candidate.value, matchSource: candidate.source };
    }
  }
  return { allowed: false };
}

Diagnostics

Match results can be formatted for logging:

export function formatAllowlistMatchMeta(
  match?: { matchKey?: string; matchSource?: string } | null,
): string {
  return \`matchKey=\${match?.matchKey ?? "none"} matchSource=\${match?.matchSource ?? "none"}\`;
}

Extended Resolution Variants

resolveAllowlistMatchByCandidates

Convenience wrapper that compiles and resolves in one step:

export function resolveAllowlistMatchByCandidates<TSource extends string>(params: {
  allowList: ReadonlyArray<string>;
  candidates: Array<{ value?: string; source: TSource }>;
}): AllowlistMatch<TSource> {
  return resolveCompiledAllowlistMatch({
    compiledAllowlist: compileAllowlist(params.allowList),
    candidates: params.candidates,
  });
}

Performance Characteristics

OperationComplexityNotes
CompilationO(n)One-time cost; n = number of entries
Wildcard checkO(1)Boolean flag on compiled set
Candidate lookupO(k)k = number of candidates (typically 5-10); each lookup O(1) via Set
Total per messageO(k)Constant regardless of allowlist size