ADR 0013: Keep named application profiles in extension packages

Copy Markdown View Source
  • Status: Accepted
  • Date: 2026-10-08
  • Updated: 2026-10-09

Context

RFC 9421 assigns application coverage, algorithm, freshness, and trust decisions to profiles. Named application profiles need that shared machinery without making application contracts part of the framework-independent core or allowing extensions to impersonate core names.

Decision

Named application profiles live in extension packages against the documented RequestSeal.Profile surface: valid_signature_input?/1, dictionaries/2, preflight/4, and verify_label/6. This surface is unstable; pin RequestSeal exactly. Extensions select and enforce their public source-specific predicates before returning success and own any trusted principal attribution. The core pipeline enforces the explicitly supplied generic policy.

An extension profile map has at most eight atom keys and a required name: {package, kind} with atom values and a package distinct from :request_seal, :rfc9421, and :web_bot_auth. The extension module defines that name as a compile-time constant, never from wire data. Bare atom names and the core package namespace reject with :invalid_profile at the :input layer. Other values are limited to atoms, integers from -999,999,999,999,999 through 999,999,999,999,999, or binaries of at most 256 bytes. The generic verifier refuses both :profile and :principal options with the same bounded error. Core named profiles retain their own acceptance predicates.

A profile stamp records selection, not proof that source-specific rules ran. Consumers must allowlist trusted profiles and verification implementations. The Ash adapter retains its closed core profile allowlist. Replay commitments receive the selected profile so callers can bind claims to their security scope. Cryptographic validity and principal attribution grant no authorization.

Internal dependencies

Undocumented helpers are not extension API. Extensions carry their own JOSE support, message validation, source URL/origin validation, and replay composition. RequestSeal.Custody.run/2 is a documented public boundary for operation deadlines and cancellation. RequestSeal.Replay.Claim.valid?/1 exposes bounded claim validation. JWS verification and JWE encryption/decryption document their explicit policy and callback shapes; these primitives establish no application profile enforcement or authorization. Generic signing uses public RequestSeal.JOSE.JWS.sign_protected/4 for caller-serialized protected bytes. RequestSeal.Discovery.Source.directory_id?/1 is publicly documented.

Consequences

Extensions share lossless field parsing, coverage, freshness, cryptography, content checks, and replay while independently maintaining named application profiles. Their public requirements must retain cited public provenance under ADR 0011. Undocumented internals do not become part of this public surface.

Acceptance

  • Core-owned names, bare atom names, non-atom keys, oversized maps, and invalid metadata values reject.
  • A package-namespaced profile verifies selected bytes and stamps the result.
  • Ash rejects an extension result before invoking caller bindings.
  • Caller commitments can separate the same nonce by profile name and reject duplicates.