scrml.dev v0.7.1
Reference › Elements

<auth>

Per-role JS-mount / code-split constraint. Wraps page subtrees whose client behaviour should mount only for specific roles. The compiler emits per-role JS bundles automatically — anonymous visitors download strictly smaller initial JS artifacts.

Not a content-secrecy control. <auth role> gates JS mount/behaviour only — it does not withhold served HTML. Gated markup ships verbatim in the HTML payload to every viewer (view-source), even under --emit-per-route. The compiler fires W-AUTH-CONTENT-NOT-GATED on every <auth role=> site. To withhold sensitive content, gate it server-side (see Semantics).

Syntax

<auth role="Admin">
  <page path="/admin" />
  ...
</auth>

role= takes a string literal, a variable reference, or a ${expr} interpolation. Closed-form predicates (variant literals, literal comma-OR, const-refs to role-set values, boolean composition of statically-known operands) ship per-role bundles via the artifact splitter. Anything else falls back to a runtime gate and emits W-AUTH-RUNTIME-FALLBACK.

Worked example

A small app with two public pages and one admin-only page. Anonymous visitors get a chunk without any of the admin subtree's atoms.

type Role:enum = { Anonymous, User, Admin }

<program auth="required" loginRedirect="/login">

  <page path="/"        />
  <page path="/about"   />

  <auth role="Admin">
    <page path="/admin"        />
    <page path="/admin/users"  />
  </auth>

</program>

The compiler analyzes the closure of each page across the three roles and emits three sets of JS chunks. Anonymous visitors load the public pages and the login redirect; the admin subtree's JS atoms (mount + wiring) are absent from their bundle. The served HTML, however, still carries the admin markup verbatim — chunk variance role-splits behaviour, not content. Per §40.9.9 the chunk variance is observable in the generated chunks.json manifest.

Semantics

  • A JS-mount gate, not a content gate. <auth role> is a code-split / mount constraint the closure analysis honors at chunk-splitting time. It is NOT a content-secrecy control and NOT a runtime check on its own — the served HTML carries the gated markup for every viewer. To withhold sensitive content, gate it server-side: put it behind a server function that resolves the authenticated role and returns/renders it only for admitted roles, so non-admitted markup never enters the response body. Request-boundary route guards (<program auth="required">) are the other server-side layer.
  • Closed-form vs runtime-fallback. Closed-form predicates ship per-role bundles. Runtime- fallback predicates (reactive reads, async server-fn calls, arbitrary ${expr}) emit W-AUTH-RUNTIME-FALLBACK and ship the gated component eagerly with a runtime gate. Per §40.9.5.
  • Roles are an enum. The role= value must resolve to a variant of an enum reachable from the entry file. Ambiguous role-enum discovery fires E-AUTH-GRAPH-002. Unknown role variants fire E-AUTH-GRAPH-003.
  • <program auth=> sets the default. <program auth="required"> gates every page that isn't otherwise marked public. If loginRedirect= isn't set AND no /login page exists, the compiler fires W-AUTH-LOGIN-MISSING.
  • Page-level inference. A <page> inside an <auth> without its own explicit auth= attribute inherits the wrapper's role. The compiler emits W-AUTH-PAGE-INFERRED info-level so the inference is auditable.
  • Redirect cross-ref check. loginRedirect= strings that don't resolve to a known route emit I-AUTH-REDIRECT-UNRESOLVED info-level. Surfaces typos and missing scaffold pages.

Errors this feature can fire

Edge cases

  • Closed-form role predicates. Boolean composition of literals (role="Admin"), literal comma-OR (role="Admin,Moderator"), or const-refs to role-set values are closed-form. Anything else — reactive reads, async calls, arbitrary ${expr} — falls back. Per §40.9.5.
  • Full interpolation grammar. role=${expr} works (S90 OQ-A3-A ratification — first-class compiler state should have the same surface as user-defined state). The closure analyzer constant- folds where it can; non-foldable cases trigger runtime fallback.
  • Channel auth is binary. <channel auth="required"> gates the WS endpoint to authenticated users only. Per-role channel partitioning rides on topic= (per the <channel> reference page).
  • Anonymous visitors. An anonymous role variant (e.g., Role.Anonymous) is treated like any other role — the chunks for anonymous visitors omit all subtrees gated to authenticated roles. Per OQ-A2-F implicit-anonymous ratification.

Related features

  • <program> auth= on <program> sets the app-wide default (required / public). Pair with loginRedirect=.
  • <page> — individual page route. Inherits the wrapping the auth element's role unless it has its own auth=. Reference page queued.
  • chunks.json — the per-route per-role manifest the compiler emits. Inspect with bun scrml build output. Documents the per-role chunk variance.
  • scrml generate auth — CLI command that scaffolds a default /login page when auth="required" is set on <program> but no login route exists.

Availability

Surface Since Notes
<auth role=> first-class element v0.3.0 (Approach A — S91) A-3 AuthGraph + per-gate classifier + redirect cross-ref
Per-route per-role chunks v0.3.0 (A-4 splitter — S91) 7 sub-phases; FNV-1a content-addressed chunk filenames
Full interpolation grammar (role=${expr}) S90 OQ-A3-A user override First-class compiler state matches user-defined state surface
scrml generate auth scaffold S94 (auth-redirect tightening) Generates /login page when auth=required and route missing

Specification

This page summarizes SPEC §40.1 (<auth> first-class element), §40.9 (AuthGraph + per-gate classifier), and §40.9.9 (worked example). The normative text lives at compiler/SPEC.md in the scrmlTS repository.

Quick-lookup anchors: SPEC-INDEX.md entries — auth-graph, per-route artifact splitter, chunk-side runtime helpers, FNV-1a content-addressing.