Profile Escalation
Profile Escalation
Section titled “Profile Escalation”The module header declares the ceiling. The escalate block marks the exact lines that reach it.
Profiles are capability gates, not functional restrictions. But a gate that only exists at module granularity forces an ugly choice: a :service HTTP server that needs one raw-pointer call must either redeclare the whole module as :sovereign — dragging hundreds of innocent lines into the tier where everything is legal — or pay for a separate module boundary around a five-line function. Both options hide the real story.
Profile escalation is the third option. The module keeps its base profile. The header enumerates which higher profiles the module may reach. The body marks, by name, the exact bounded regions that reach them. Nothing is silent: grep -n 'escalate :' over a source tree finds every privilege boundary, and the header escalations: list finds every module that has any.
Spec anchor: SPEC-044 (Profile Escalation) — internal specification. Phase 1 scope is defined by RFC-056 (internal). Both are normative for the compiler team; this page is the user-facing contract.
The module header clause
Section titled “The module header clause”A module that escalates declares its escalation set in the header, next to the base profile:
{.profile: service, escalations: [:sovereign].}Rules:
- The
escalations:clause is optional. A module with no escalations omits it entirely:{.profile: service.} - The base profile name is bare (
service); escalation entries are colon-prefixed (:sovereign). - Every entry must rank strictly higher than the base profile.
escalations: [:core]in a:servicemodule is E4402. - Duplicate entries are E4403.
- Entries never used by any
escalatebody produce W4401 — a warning, not an error. Declared-but-unused is a style issue, not a safety issue.
The escalate block
Section titled “The escalate block”The block form is canonical for multi-statement bodies:
escalate :sovereign do // statements at the elevated profileendescalate blocks are expressions, not statements. They evaluate to the value of their final expression, exactly like do ... end blocks:
let r = escalate :sovereign do compute() endSingle-expression form
Section titled “Single-expression form”When the body is a single expression, the do ... end delimiters may be elided:
let r = escalate :compute matmul(a, b)This is purely syntactic sugar — it parses identically to escalate :compute do matmul(a, b) end. The audit token escalate :PROFILE remains literally present in both forms, so grep-based audit tooling needs no update.
The active profile and nesting
Section titled “The active profile and nesting”At any point in a module, the active profile is the base profile, raised by the innermost enclosing escalate block if any. Escalation must ascend strictly: the target of escalate :P must rank higher than the active profile at that point — not just higher than the base. This makes descending nested escalations illegal:
{.profile: service, escalations: [:cluster, :sovereign].}
func main() -> i32 do let r = escalate :sovereign do escalate :cluster do 1 end // E4402: :cluster does not ascend from :sovereign end return rendLeaving an escalate ... end block returns to the base profile automatically. Capabilities do not leak outward in the lexical scope.
The profile ladder
Section titled “The profile ladder”Escalation targets are drawn from the monotonic capability ladder:
:script ⊆ :core ⊆ :service ⊆ :cluster ⊆ :compute ⊆ :sovereignEach rung is a strict superset of the one below. :full is a stable alias of :sovereign; older spellings resolve, but new code should write the canonical names.
One implementation note worth knowing: the current compiler ranks :script and :core equally, and :cluster and :compute equally. Because escalation must ascend strictly, an escalate between two equal-rank rungs — :script to :core, or :cluster to :compute — is rejected as E4402, even though the ladder diagram orders them.
Diagnostics
Section titled “Diagnostics”All four codes are emitted in dual form: the canonical named code plus a numeric alias. (The numeric alias of W4401 is 4401, colliding with E4401’s by design — the severity letter distinguishes them.)
| Code | Severity | Cause |
|---|---|---|
E4401 E_PROFILE_ESCALATION_UNDECLARED | error | escalate :P where :P is not in the header escalations: list |
E4402 E_PROFILE_ESCALATION_DESCENDING | error | escalate :P where :P does not rank strictly above the active profile — or a header entry that does not rank above the base profile |
E4403 E_PROFILE_ESCALATION_DUPLICATE | error | the same profile twice in the escalations: list |
W4401 W_PROFILE_ESCALATION_UNUSED | warning | a header entry never used by any escalate body in the module |
E4401 — undeclared escalation
Section titled “E4401 — undeclared escalation”{.profile: service.} // no escalations declared
func main() -> i32 do let r = escalate :sovereign do 1 end // E4401 return rendThe remediation hint points at the header: add :sovereign to the escalations: list. The declaration is part of the contract — the compiler will never escalate silently.
E4402 — descending escalation
Section titled “E4402 — descending escalation”Two shapes. In the body, the target must ascend from the active profile (nested blocks count):
escalate :sovereign do escalate :cluster do 1 end // E4402 — :sovereign already includes :clusterendIn the header, every entry must ascend from the base profile:
{.profile: service, escalations: [:core].} // E4402 — :core is below :serviceThere is no de-escalate. Once you are in a high-profile context you cannot pretend to be in a lower one; factor the low-profile code into a function or module instead.
E4403 — duplicate declaration
Section titled “E4403 — duplicate declaration”{.profile: service, escalations: [:sovereign, :sovereign].} // E4403W4401 — declared but unused
Section titled “W4401 — declared but unused”{.profile: service, escalations: [:sovereign].}
func main() -> i32 do return 42 // no escalate body anywhere → W4401 on the header entryendDoes not abort the build. Remove the entry, or use it.
A complete minimal example
Section titled “A complete minimal example”This module compiles clean — declared escalation, used once, tail value returned:
{.profile: service, escalations: [:sovereign].}
func main() -> i32 do let r = escalate :sovereign do 41 + 1 end return rendReviewer experience: open the file, read the header (service + [:sovereign]), grep for escalate, find one block, audit it. The rest of the module is ordinary :service code with no hidden elevation.
Phase 1: what escalation does and does not do
Section titled “Phase 1: what escalation does and does not do”Be clear-eyed about the current state. Phase 1 escalation is audit syntax. It grants no new capabilities by itself. The per-profile validators do not consult escalate blocks yet — an operation that is illegal at your base profile is still illegal inside an escalate block today.
What Phase 1 does give you:
- A grep-visible audit surface. Every privilege boundary is literally present in the source: at the header (
escalations:) and at each site (escalate :P). No macros synthesize it, no attributes sneak it in. This is the Anti-Gadget Law applied to capability elevation. - Legality checking. The four diagnostics above enforce the declaration discipline: no undeclared escalation, no descending escalation, no duplicate declarations, no dead declarations.
- The semantic scaffolding for Phase 2. Phase 2 adds origin tags on values and per-operation gating — the rules that make escalation enforceable, where a value constructed inside an escalation carries its origin on its type and later use requires re-escalation. That work is specified in SPEC-044 but not yet implemented.
Writing escalation-correct code today is not wasted motion: it is the audit trail Phase 2 will enforce, landed early and cheaply.
Further reading
Section titled “Further reading”- Janus Profiles — the capability ladder and what each rung unlocks.
- SPEC-044 (Profile Escalation) — the normative specification (internal).
- RFC-056 (Phase 1 scope) — the implementation phasing decision (internal).