symfonic.capabilities.extensions.values¶
values ¶
The vocabulary extension composition speaks (T4.2.2).
Everything here is frozen and everything here is a reading rather than a handle. That is what lets the composer be a pure function: two compositions of the same providers compare equal, so "installing this plugin changed nothing else" is an assertion rather than a claim.
Two design points worth stating rather than leaving to be inferred.
A policy verdict is tri-state. ALLOW, DENY, and ABSTAIN are
three answers, not two. A plugin that raised, or that has no opinion about this
action, has abstained; collapsing that into ALLOW is how a guard whose
backend is down reads as a guard that examined the action and approved it.
A trust tier is a string here, not the prompt compiler's enum. The capability layer does not import another capability's internals (LAY-ADR §3.4), so the tier travels as its declared name and the composition root maps it. The names are deliberately the same names, so the mapping is an identity lookup and a divergence is a failing test rather than a silent re-tiering.
ContributionKind ¶
Bases: StrEnum
The four things an extension may contribute, and there is no fifth.
ExtensionDiagnostic
dataclass
¶
ExtensionDiagnostic(extension: str, kind: ContributionKind, detail: str, subject: str = '', severity: Severity = Severity.WARNING)
One thing the composer decided, in names, for the record.
Diagnostics are how a dropped contribution stays visible. Nothing is
discarded silently: every refusal, truncation, and remap emits one of these,
and :class:~.composition.ComposedExtensions carries them alongside what
survived.
LifecyclePhase ¶
Bases: StrEnum
When a lifecycle hook runs relative to the composition that owns it.
PolicyDecision ¶
Bases: StrEnum
One contributed policy's answer about one action.
PolicyRequest
dataclass
¶
What a contributed policy is asked about: one action, once.
context is frozen at construction, all the way down: a policy answers a
question, it does not edit the question. Freezing rather than copying is
what makes that a property instead of a claim — a shallow copy leaves every
nested value writable, so a policy handed the live tool arguments could
rewrite the very call it was only allowed to veto (AS-INT-3), and the
caller's own mapping would come back mutated.
The caller's mapping is never touched: the frozen view is built over a fresh dict, so whoever passed the context still holds their own mutable one.
PolicyVerdict
dataclass
¶
One policy's answer, with the reason that makes it auditable.
Severity ¶
Bases: StrEnum
How loudly a composition diagnostic should read.
ERROR is reserved for a refusal an operator must see — a shadowed tool
name, a rejected payload — and never used for an extension declining to
contribute, which is ordinary.
freeze_context ¶
Return a recursively read-only view of value.
Mappings become :class:~types.MappingProxyType, sequences become tuples,
sets become frozensets, and every other object is passed through by
reference. Leaves are not copied on purpose: a policy context is the
caller's live data — clients, connections, large payloads — and deep-copying
it once per policy per action would be both expensive and, for anything
holding a socket, wrong. Making the structure unwritable is what the
invariant actually needs.
Source code in src/symfonic/capabilities/extensions/values.py
layer_rank ¶
Position of layer in the stable-to-volatile order.
Unknown layers sort last rather than raising: ordering is not the place to discover an invalid layer, and the contract validator has already rejected one by the time anything is sorted.