aeat.core.json_contract module¶
Shared primitives for the CLI’s strict --json output contract.
Defines the strict pydantic v2 bases (OutputSchema,
OutputRootSchema), the canonical success envelope
(SchemaEnvelope), the typed diagnostic channel
(Notice), the schema registry (SCHEMA_REGISTRY), and
the emit helpers (emit_json_document(), emit_json_success())
used by every registered machine-output path. CLI payload modules import
these primitives through entrypoints.cli._schemas, register
result models with register_schema(), and route JSON mode through
entrypoints.cli._common._emit_envelope().
emit_json_success() derives EnvelopeStatus from supplied
Notice values via derive_status() and applies
core.redaction.redact_structured_for_cli_output() to the
entire envelope before writing stdout. Text output remains owned by
core.output_rendering.render_command_output(), so redaction
and the reveal_cli_identifiers_opt_in switch stay consistent across
text and JSON surfaces.
Living in core keeps domain and adapter packages free of any
dependency on entrypoints.cli: a wrapped command emits its
strict-validated payload through emit_json_success() without
having to know how the CLI itself wires Click options.
This module owns the stdout success contract and schema registry. The
stderr failure document is rendered by core.errors using the
same ENVELOPE_SCHEMA_VERSION, and text-mode layout remains owned
by core.output_rendering.
- ENVELOPE_SCHEMA_VERSION¶
Envelope contract version shared by the success
SchemaEnvelopeand the stderr error envelope. Both documents carry the same outer spine (schema_version,command,status,notices), so the version is pinned once here and bumped only on a backwards-incompatible change to that spine.
- class EnvelopeStatus(*values)[source]¶
Bases:
StrEnumOutcome discriminator carried on every CLI return document.
successandwarningride on the stdoutSchemaEnvelope(warningwhen the command attached at least one warning-severityNotice);errorrides on the stderr error envelope. A machine consumer reads this single field to learn the outcome instead of branching on stdout-vs-stderr, andderive_status()is the success-envelope authority for computing it.emit_json_success()never emitsERROR; blocking failures route through the sharedAeatErrorboundary instead of being smuggled into stdout notices.- SUCCESS¶
- WARNING¶
- ERROR¶
- class NoticeSeverity(*values)[source]¶
Bases:
StrEnumSeverity of a single operator-facing
Notice.infois a non-fatal next-step hint or informational advisory;warningis a non-blocking advisory the operator should act on. A command that attaches anywarningnotice resolves toEnvelopeStatus.WARNINGthroughderive_status().- INFO¶
- WARNING¶
- class Notice(**data)[source]¶
Bases:
BaseModelOne typed, non-blocking diagnostic on the envelope
noticeschannel.The single uniform surface for operator-facing warnings, advisories, and next-step hints across every command. Domain diagnostics (e.g.
ModeloFinding, source-resolution advisories) are projected into this shape rather than re-modelled as bespoke per-command payload fields. CLI helpers such asentrypoints.cli._common._emit_envelope()pass these values toemit_json_success(), while text renderers fold equivalent prose into their line output.- Variables:
severity –
infoorwarning; drives the envelopestatus.code – Stable machine-readable notice identifier (e.g.
"modelo.calculate.unconsumed_iva").message – Operator-facing rendered text for the notice.
suggestion – Optional copy-paste command or next-step action, mirroring the error envelope’s
suggestionfield.context – Optional structured provenance for the notice (e.g. the source-resolution
reason/source_kind), mirroring the error envelope’scontextso a migrated advisory keeps its machine-queryable sub-fields without a bespoke payload model.
- Parameters:
Blocking failures are not notices; they raise an
AeatErrorand emit on stderr. Command payload schemas should also avoid reintroducing bespoke advisory, hint, or warning fields insideresultwhen aNoticecan carry the same non-blocking diagnostic.- severity: NoticeSeverity¶
- code: str¶
- message: str¶
- suggestion: str | None¶
- context: dict[str, str] | None¶
- derive_status(notices)[source]¶
Return
EnvelopeStatus.WARNINGif any notice is warning-severity.Success documents never carry
EnvelopeStatus.ERROR; that status is reserved for the stderr error envelope. The returnedEnvelopeStatusis the stdoutSchemaEnvelopestatus used byemit_json_success().- Return type:
- Parameters:
- exception OutputSchemaError(message=None, *, context=None, suggestion=None, translated_message=None)[source]¶
Bases:
AeatErrorRaised when the CLI output-schema registry is misconfigured.
Triggered by
register_schema()when a non-schema class is decorated, when a command path is registered twice with different schemas, or when the command path is blank. It deliberately inheritscore.errors.AeatErrorso registry defects route through the shared CLI error boundary instead of bypassing structured output.- Parameters:
- Return type:
None
- code: ClassVar[ErrorCode]¶
- class OutputSchema(**data)[source]¶
Bases:
BaseModelStrict, frozen base class for every command-specific
--jsonpayload.Subclasses inherit
extra="forbid",frozen=True,strict=True, andvalidate_assignment=Trueso accidental field drift between contract and implementation surfaces as a validation error rather than a silently-extended payload. Each concrete result model should be decorated withregister_schema()so the CLI conformance gate can match command leaves againstSCHEMA_REGISTRY.The class describes the inner
resultpayload only; the outerSchemaEnvelopeis applied later byemit_json_success().
- class OutputRootSchema(root=PydanticUndefined, **data)[source]¶
Bases:
RootModel[TypeVar],GenericStrict root/list base class for
--jsonpayloads with a non-mapping root.Use this for commands whose top-level JSON value is a list or scalar rather than an object. Carries the same strict / frozen / validate-on- assignment configuration as
OutputSchema, and participates in the sameregister_schema()registry contract.- Parameters:
root (RootModelRootType)
- class SchemaEnvelope(**data)[source]¶
Bases:
BaseModel,GenericStable outer envelope wrapping a successful command’s payload.
Every successful
--jsonresponse is rendered through this envelope so consumers can rely on the same outer keys regardless of the inner payload shape. The outer spine (schema_version,command,status,notices) is shared with the stderr error envelope so one shape describes success, warning, and error outcomes.emit_json_success()constructs the runtime mapping and the JSON-contract conformance gate specialises this generic envelope over every schema inSCHEMA_REGISTRY.The envelope is a wire contract, not the command dispatcher. It does not discover Click/Typer leaves, choose text output, or own command authorization; those layers supply a strict
resultand stable command path before entering this contract.- Variables:
schema_version – Envelope version; bumped only on backwards-incompatible changes to the shared spine.
command – Stable command path string (e.g.
"workflow list").active_profile – Human-readable label of the active taxpayer profile (the operator-chosen display name), or
Nonebefore any profile exists and for non-profile-bound commands. The identity anchor a caller reconciles against; it is the label, never the redacted profile/bucket UUID. Resolved and injected at the CLI transport boundary (thecorelayer never scans profile manifests), so it staysNonefor any emitter that does not supply it.status – Outcome discriminator (
successorwarninghere).result – The strict-validated command result.
notices – Typed non-blocking diagnostics (warnings, advisories, next-step hints) surfaced to the caller. Replaces the former free-form
warningsstring list.
- Parameters:
- schema_version: str¶
- command: str¶
- active_profile: str | None¶
- status: EnvelopeStatus¶
- result: ResultT¶
- notices: list[Notice]¶
- SCHEMA_REGISTRY: dict[str, RegisteredSchema]¶
Process-global registry mapping command-path strings to their result schema.
Populated by the
register_schema()decorator at import time. Consumers (notably the doc generator and the JSON-contract conformance tests) iterate over this mapping to enumerate every contract a release exposes.
- emit_json_document(payload, *, indent=2, sort_keys=False, stream=None)[source]¶
Serialise
payloadand write a single UTF-8 JSON document followed by\\n.When
streamexposes_ReconfigurableStream.reconfigure, the helper pins it toencoding="utf-8", errors="strict"first so downstream cp1252 consoles can not silently corrupt non-ASCII characters in the rendered output. This is the low-level writer used byemit_json_success(); it does not itself apply the envelope or redaction policy.Use this for already-shaped JSON documents. Registered CLI success payloads should normally enter through
emit_json_success()so the envelope, status derivation, and redaction pass remain uniform.- Parameters:
payload (
object) – Any object reachable by_jsonable_payload()(typically apydantic.BaseModel, a mapping, or a collection thereof).indent (
int|None) – Indent width passed tojson.dumps();Noneproduces a single-line document.sort_keys (
bool) – Whether to render mapping keys in lexicographic order.stream (
Optional[IO[str]]) – Target text stream; defaults tosys.stdout.
- Return type:
- emit_json_success(command, result, *, notices=None, active_profile=None, indent=2, sort_keys=False, stream=None)[source]¶
Wrap
resultin the success spine and emit it viaemit_json_document().The envelope’s
schema_versionis pinned toENVELOPE_SCHEMA_VERSION; bumping it is a contract-breaking change handled by the JSON-contract test suite, not a casual edit. Thestatusis derived from the supplied notices (derive_status()) so the JSON outcome and the shell exit code never disagree. The assembled envelope is redacted throughcore.redaction.redact_structured_for_cli_output()beforeemit_json_document()writes it.This helper is stdout-only. Any raised
AeatErroris handled by the CLI error boundary, which renders the sibling stderr envelope instead of returning a success document with an error-shapedresult.- Parameters:
command (
str) – Stable command path string (e.g."workflow list").result (
object) – The strict-validated command payload to surface asenvelope.result.notices (
Sequence[Notice] |None) – Optional typedNoticediagnostics (warnings, advisories, next-step hints); defaults to an empty list.active_profile (
str|None) – Optional human label of the active taxpayer profile placed on the shared spine (the identity anchor). Thecorelayer never scans profile manifests, so the CLI transport resolves the label and passes it here;Nonefor non-profile-bound emitters. It rides through the same redaction pass as the rest of the envelope, but it is the non-secret display name, not the redacted profile/bucket UUID.indent (
int|None) – Indent width forwarded toemit_json_document().sort_keys (
bool) – Sort-keys flag forwarded toemit_json_document().stream (
Optional[IO[str]]) – Target text stream; defaults tosys.stdout.
- Return type:
- register_schema(command_path)[source]¶
Decorator that binds a strict schema to a stable
command_path.Usage:
@register_schema("workflow list") class WorkflowListResult(OutputSchema): ...
The same schema may register the same path more than once (idempotent re-import); registering a different schema under an existing path raises
OutputSchemaError. Registered paths are the authoritative command strings emitted asSchemaEnvelope.commandand compared against the Typer command tree by the JSON-schema conformance tests.Register concrete command leaves only. Aliases, helper functions, and text-only utilities do not belong in
SCHEMA_REGISTRYunless a real CLI path emits their strict payload through the envelope.- Parameters:
command_path (
str) – Stable command-path string used both as the registry key and as the value emitted underSchemaEnvelope.command.- Return type:
Callable[[type[TypeVar(RegisteredSchemaT, bound=OutputSchema|OutputRootSchema[Any])]],type[TypeVar(RegisteredSchemaT, bound=OutputSchema|OutputRootSchema[Any])]]- Returns:
The decorator, returning the schema class unchanged.
- Raises:
OutputSchemaError – When
command_pathis blank, when the decorated class is not a strict schema subclass, or when the path is already bound to a different schema.