symfonic.cli.public_api¶
public_api ¶
What a scaffolded project is allowed to import from symfonic (T4.3.1).
REQ-S4.3: CLI and scaffolds generate only public APIs and target architecture
patterns. A generated project is the one piece of code the framework writes on
an adopter's behalf, so every symfonic import it emits is a promise the
framework then has to keep. This module is where that promise is written down.
Two authorities decide what "public" means; this module invents no third.
Declared public — T1.1.1's public-surface-inventory.json: a package is
public when it publishes __all__, and a name is public when it is in one.
Membership is resolved against the live package at call time rather than
copied, so a package that stops declaring a name fails the check instead of
drifting away from a transcribed list.
Sanctioned undeclared — T1.1.2's uncatalogued-triage.md: several
clusters are documented, tested, and adopter-imported while declaring nothing.
Triage gave each a verdict. A cluster marked (a) promote or
(b) compat-alias is public in substance and undeclared only in form, so the
scaffold may use it through a :data:SANCTIONED_UNDECLARED row naming that
verdict. A cluster with no such verdict gets an :data:OPEN_GAPS row instead —
pinned, not permitted.
The distinction that makes this worth enforcing: an undeclared path with no verdict is not a style problem. It is a decision the framework has not made, and shipping it inside a generated project makes that decision for them, silently, in whichever direction the next refactor happens to move the module.
OPEN_GAPS
module-attribute
¶
Empty as of the kernel-native scaffold migration.
All four rows closed the same way, and it is worth recording which way, because none of them closed by declaring the module the gap named.
- PUB-4 (
CorePreference) -- the generated domain no longer fills aDomainTemplate. It declares aDomainPersonaand a list ofGuardrail, both public onsymfonic.capabilities.prompting, so the half-public modelling API is not reached at all. - PUB-5 (
ConversationMetricsCollector) and PUB-7 (PostgresBudgetStore) -- both are now process resources the host owns and hands over, throughsymfonic.platform.observability. The scaffold names the bundle, not the internals. - PUB-6 (the metrics store's factory and its module globals) -- triaged '(c) internalize store internals', and that verdict stands: the internals stayed internal and the generated app reads the store off the bundle instead of a process global.
A new private dependency must be added back here deliberately; the suite asserts this set is neither exceeded nor stale.
OpenGap
dataclass
¶
A symbol the scaffold needs that the framework has not declared public.
Distinct from :class:UndeclaredException in the way that matters: an
exception carries a T1.1.2 verdict saying the symbol is public and merely
undeclared, so the debt is paperwork. A gap has no such verdict — or has one
pointing the other way — so the scaffold depends on a surface nobody
promised to keep.
Gaps are pinned rather than fixed because closing one means editing a
package this task holds no write authority over (T1.2.7 gives T4.3.1
src/symfonic/cli/** and its evidence directory). Pinning makes the set
non-increasing, which is the strongest claim a task can honestly make from
inside its own boundary.
PublicApiVerdict
dataclass
¶
Whether an import may be emitted, and the sentence explaining why not.
UndeclaredException
dataclass
¶
One T1.1.2 cluster the scaffold may import before it is declared.
classify_import ¶
Decide whether ref is a public import a scaffold may emit.
Pinned :data:OPEN_GAPS are deliberately not consulted here: a gap is
still a private import, and a classifier that called it public would make
the pinning a way of passing the test rather than a way of recording a
debt. The suite compares the gap set separately.
Source code in src/symfonic/cli/public_api.py
open_gap_for ¶
Return the pinned gap covering ref, or None when it is not one.