symfonic.core.prompt.blocks.validation¶
validation ¶
Construction-time validation of the declared prompt blocks.
A misconfigured block does not fail at the point it is misconfigured. It fails months later, quietly, as a per-tenant BOUNDARIES block that has been serving one global value to every tenant on the instance -- or as an operator "edit" API against a source that cannot show what the block used to say, so the edit has no undo. Neither of those raises anything at render time; both look exactly like a working configuration.
So the rules live here, and they run at construction: when a
:class:~symfonic.core.prompt.blocks.spec.PromptBlockSpec is built, and
again over the whole declared set when the framework config is built. A
rule that only fires on the request path is a rule something can be
argued into skipping; a rule that fires at construction means the process
does not start.
Two errors and one warning¶
Error -- scope pairing (§5.7, §5.8 pt2). scope is declared on the
block; scope_aware is a property of the source. Anything other than
scope='deployment' against a scope-unaware source is rejected: the
operator asked for per-tenant content from an adapter that serves one
value for the whole install, and nothing downstream can detect the
difference. A deployment-scoped block on that same source is a
legitimate configuration and stays silent -- this replaced an earlier
heuristic warning that fired on correct configurations.
Error -- operator_editable pairing (§5.9, §5.11, D14).
operator_editable=True means core exposes a write API for the block,
so the source must be able to append a revision and show its history.
The check is :func:isinstance against
:class:~symfonic.core.prompt.blocks.protocol.WritableBlockSource --
structural, not a self-reported flag. The reverse pairing is not an
error: declaring a writable source read-only is a legitimate, and safer,
configuration.
Warning -- offline safety (§5.2). A platform- or
operating-tier block backed by a non-offline-safe adapter is legal
and sometimes right, so there is no correct pairing to enforce. But D3's
narrowing -- giving up offline USER_PROFILE -- was accepted because
the authored spine would still render during a datastore outage, and
pointing IDENTITY at a database silently retracts the premise that
decision was taken on. Warning is the right instrument: it converts a
silent property loss into a loud one without rejecting a configuration
the adopter may genuinely want.
The warning is emitted once per process per offending
(block, source type) pair, following the idiom at
core/providers.py -- a long-running agent that rebuilds its config
per tenant must not reprint the same line a thousand times. The pair is
in the memo key rather than a single process-wide flag because the
message names the block: two differently-misconfigured blocks are two
different facts the adopter needs, and collapsing them would report the
first and hide the second.
What isinstance catches here is member presence only -- see
:mod:symfonic.core.prompt.blocks.protocol. A source that never
implemented append_revision is caught; one that implements it badly
is not.
WRITE_CAPABILITIES
module-attribute
¶
The write-specific members WritableBlockSource adds on top of
BlockSource -- the two history methods plus the append verb.
Named here so the rejection message can say which capability is
missing rather than only that the Protocol was not satisfied. This is
not the full member set isinstance(source, WritableBlockSource)
requires: BlockSource's own members (load, offline_safe,
scope_aware) are required too, and are checked separately by
:func:missing_write_capabilities -- see :data:_BASE_SOURCE_MEMBERS.
AuthoredSpineOfflineWarning ¶
Bases: UserWarning
An authored-tier block is backed by a source that needs the network.
Emitted at construction for a platform- or operating-tier
block whose source reports offline_safe=False. The configuration
is valid and is not rejected; what the adopter loses is the guarantee
that BOUNDARIES, IDENTITY and RULES still render when the backing
datastore is unreachable.
Adopters who have weighed that and accept it silence the category
with
warnings.filterwarnings("ignore", category=AuthoredSpineOfflineWarning).
check_operator_editable ¶
Reject operator_editable=True against a non-writable source.
operator_editable=False is never rejected, on any source:
serving a writable source read-only is a deliberate, and strictly
safer, configuration.
Source code in src/symfonic/core/prompt/blocks/validation.py
check_scope_pairing ¶
Reject a non-deployment scope against a scope-unaware source.
A deployment-scoped block is silent whatever the source does:
one value for the whole install is exactly what a scope-unaware
adapter provides, and that is a legitimate configuration rather than
a degraded one.
Source code in src/symfonic/core/prompt/blocks/validation.py
is_offline_safe ¶
Return whether source can still be read during an outage.
The memory lane is not offline-safe: it reads the datastore, and that is precisely the dependency §2.7 says the authored spine must not have. An adapter that declares nothing is treated as unsafe, so the quieter outcome is never the accidental one.
Raises:
| Type | Description |
|---|---|
TypeError
|
|
Source code in src/symfonic/core/prompt/blocks/validation.py
is_scope_aware ¶
Return whether source can serve different content per scope.
The memory lane is scope-aware by construction -- it reads the
tenant's own graph, so it cannot serve another tenant's content. Any
other source is asked for its declared scope_aware member, and a
source that does not declare one is treated as not scope-aware:
an undeclared isolation property is not an isolation guarantee.
Raises:
| Type | Description |
|---|---|
TypeError
|
|
Source code in src/symfonic/core/prompt/blocks/validation.py
missing_write_capabilities ¶
Return the WritableBlockSource members source does not present.
Empty only for a source that satisfies WritableBlockSource.
Covers the full member set, split by how "present" is checked:
- :data:
WRITE_CAPABILITIESand :data:_BASE_CALLABLE_MEMBERS(load) are methods, checked bycallable()-- a source that sets one of these to a non-callable value (e.g.load = True) is reported as missing it, even though a bareisinstance(source, WritableBlockSource)would not catch that: aruntime_checkableProtocol'sisinstanceverifies member presence, not callability, for method-shaped members. - :data:
_BASE_FLAG_MEMBERS(offline_safe,scope_aware) are data members, checked byhasattr().
Used to name the gap in the rejection message: "not writable" sends
the adopter reading Protocol source, "missing append_revision"
sends them to the one method they have to add -- and a source
presenting all three write verbs but missing a capability flag now
names that gap instead of reporting none.
Source code in src/symfonic/core/prompt/blocks/validation.py
validate_block_specs ¶
Validate every declared block against the source that serves it.
The config-level entry point: each spec already checked itself at its
own construction, and this re-checks the set as a whole so a spec
built by any other route (deserialisation, model_construct) is
caught before the framework starts.
Zero declared blocks performs no validation and emits nothing -- the feature is off, and an off feature must be indistinguishable from a release that never had it.
Source code in src/symfonic/core/prompt/blocks/validation.py
warn_if_offline_unsafe ¶
Warn once per process when an authored block needs the network.
No-op for the learned tiers: losing USER_PROFILE during an outage
costs personalisation for a turn, which is the trade D3 already
accepted. It is the authored spine -- BOUNDARIES, IDENTITY, RULES --
whose survival that decision was taken on.