Channels/Allowlists — Allowlist Matching
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:
- If the set is empty →
{ allowed: false } - If wildcard is present →
{ allowed: true, matchKey: "*", matchSource: "wildcard" } - Iterate candidates in order; first set membership hit wins
- 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
| Operation | Complexity | Notes |
|---|---|---|
| Compilation | O(n) | One-time cost; n = number of entries |
| Wildcard check | O(1) | Boolean flag on compiled set |
| Candidate lookup | O(k) | k = number of candidates (typically 5-10); each lookup O(1) via Set |
| Total per message | O(k) | Constant regardless of allowlist size |