TypeDrop

2026-04-24 Challenge

2026-04-24 Hard

Typed Permission Policy Evaluator

You're building the authorization engine for a multi-tenant SaaS platform. Raw policy documents arrive as `unknown` JSON from an admin dashboard; your engine must validate them, compile each rule into a strongly-typed decision graph, evaluate access requests against matching policies using precedence logic, and emit a discriminated-union verdict per request — with zero `any`.

Goals

  • Implement `parsePolicy` to validate raw `unknown` JSON into a fully branded, strongly-typed `Policy` using a fail-fast `Result<T, E>` monad — no `any`, no type assertions.
  • Implement `evaluateCondition` with an exhaustive discriminated-union switch that returns a typed `ConditionFailure` or `null`, covering all four condition kinds.
  • Implement `evaluate` to fetch applicable policies from the typed `PolicyStore`, select the single highest-priority matching rule, evaluate its conditions, and return the correct discriminated-union `Verdict`.
  • Implement `formatVerdict` with an exhaustive switch (guarded by a `never` check) that produces human-readable output for all four `Verdict` variants.
challenge.ts
// Key types & main evaluator signature — challenge preview

type Verdict =
  | { readonly decision: "allow";          readonly matchedRule: RuleMatch }
  | { readonly decision: "deny";           readonly matchedRule: RuleMatch }
  | { readonly decision: "deny-no-match"                                   }
  | { readonly decision: "deny-condition"; readonly matchedRule: RuleMatch;
      readonly failures: ReadonlyArray<ConditionFailure> };

type RuleMatch = {
  readonly policyId: PolicyId;
  readonly ruleId:   string;
  readonly effect:   "allow" | "deny";
  readonly priority: number;
};

// Parse raw unknown JSON into a validated, branded Policy
function parsePolicy(raw: unknown): Result<Policy, ValidationError> { … }

// Evaluate a single access request against all applicable policies
function evaluate(ctx: RequestContext, store: IPolicyStore): Verdict { … }

// Evaluate many requests, returning parallel results
function evaluateBatch(
  requests: ReadonlyArray<RequestContext>,
  store: IPolicyStore,
): ReadonlyArray<BatchResult> { … }
Hints (click to reveal)

Hints

  • For branded types without `any` or `as`, look into using a generic identity function constrained to the brand shape — `function brand<B extends Brand<string, string>>(s: string): B` won't compile; think about what *does* work at the call site under `strict: true`.
  • Your `evaluateCondition` switch must handle every `Condition` kind — add a `default: (c: never) => ...` branch and TypeScript will tell you if you've missed one.
  • In `evaluate`, collect *all* condition failures for the top rule before deciding — don't short-circuit on the first failure, so the `deny-condition` verdict includes the full `failures` array.

Or clone locally

git clone -b challenge/2026-04-24 https://github.com/niltonheck/typedrop.git