Documentation
    Preparing search index...

    Function evaluate

    • Evaluate a single firewall observation and return the access decision.

      PURE: no wall-clock reads (no system time APIs), no I/O, no mutation. All time comes from observation.now. The original config and state are NEVER modified — every newState is returned via immutable spread.

      1. Per-napplet policy (allow / deny / ask) — hard override for the dTag. allow → pass (bypass everything); deny → reject (block); ask → prompt. Policy returns do NOT advance any counters; newState = input state.

      2. Init-burst guard — if observation.initElapsedMs is defined and less than config.burstGuard.windowMs, the burst counter for this napplet is advanced. If the count exceeds config.burstGuard.maxOps, the burst action fires (default block). The advanced burst counter is returned in newState.

      3. Content matchersconfig.matchers are evaluated in order; the FIRST matcher whose declared conditions (opClass, kinds, size, focus, msSinceFocusGain) ALL hold fires its action. Matchers do NOT advance the token bucket.

      4. Per-napplet × op-class rate limit (config.napplets[napplet].rateLimits[opClass]) with ruleId 'rate:opclass'.

      5. Per-napplet global rate fallback (config.napplets[napplet].globalRate) for op-classes with no specific rateLimits entry. ruleId 'rate:global'.

      6. Global default rate (config.defaultRate) — applied when no napplet-specific rule exists. ruleId 'rate:default'.

      When observation.focused === false, the effective bucket capacity is tightened: effectiveCapacity = limit.capacity * config.unfocusedMultiplier. Refill rate is derived as effectiveCapacity / windowMs so the drip also tightens proportionally. The bucket KEY stays stable (napplet:opClass, no focus suffix). Because the multiplier is always > 0, an unfocused napplet's budget is reduced but never zero — focus alone NEVER hard-blocks.

      Parameters

      • config: FirewallConfig

        Immutable firewall configuration

      • state: FirewallState

        Current ephemeral counter state (never mutated)

      • observation: Observation

        Normalized observation (the sole input surface — CORE-02)

      Returns EvaluateResult

      Decision result with updated counter state

      import { evaluate, defaultConfig, createState } from '@kehto/firewall';

      const config = defaultConfig();
      const state = createState();
      const obs = {
      napplet: 'chat',
      opClass: 'relay:write',
      focused: true,
      now: injectedTimestamp, // caller supplies time; evaluate() never reads a clock
      };

      const result = evaluate(config, state, obs);
      // result.decision === 'pass'
      // result.newState has an updated token bucket for 'chat:relay:write'