Skip to content

Static capability readiness

Retained structured declarations

readiness/structured.py is the authoritative static resolver for supported stdlib class-form TypedDict declarations. It consumes AST SourceFacts and retains StructuredDeclaration records in ReadinessReport.structured_types. Each record identifies the module, symbol and source span; the report's source and IR digests bind it to the inspected bytes. A record carries either a proven object TypeSpec or a precise refusal. This is observable evidence, including in serialized Catalog inspections, rather than a resolver cache or side channel.

The resolver proves one earlier unconditional stdlib typing import, an unchanged binding, literal requiredness and a safe class body. Only a successfully proven class span is exempt from APIZR-READY-004; every other class remains subject to the existing initialization policy. Unsupported structured inputs receive APIZR-READY-019. Supported forms and limits include the explicit inheritance, functional, forward-reference and recursion refusals. Expanded shapes are bounded to 32 levels and 4096 type nodes.

The neutral contract_types.py model adds kind="object" and canonical, name-sorted ObjectField(name, required, type) records. It rejects duplicate or invalid names, invalid field contracts and inconsistent object/non-object details, including on serialization/revalidation of constructed or copied models. contract_lowering.py lowers retained annotation expressions using these bound shapes. Interface planners consume this evidence; they never parse project source to rediscover a declaration. REST, MCP, client examples and serialized execution plans therefore share the same object contract.

interfaces/runtime.py mirrors the shape in its lightweight RuntimeType and validates required and extra fields recursively into plain dictionaries. It never reads runtime type annotations or instantiates a TypedDict class. The same reviewed runtime is copied verbatim into standalone adapters and governed workers, with updated integrity hashes. Existing scalar coercion rules remain unchanged. Return types remain descriptive (enforcement="none") and MCP tools have no outputSchema.

This is an additive v1 vocabulary extension. Historical non-object TypeSpecs omit fields, and reports without structured evidence omit structured_types; their canonical bytes remain unchanged. Current readers accept historical v1 artifacts. Older readers need an upgrade to consume the new object vocabulary. Published Catalog, Readiness, repository interface and runtime schemas include the additive definitions. Existing non-TypedDict golden contracts keep their semantics; adapter, embedded model and provenance hashes change when their implementation bytes change. No project/package version or publication changes are implied by this development feature.

Capability IR records source declarations. Readiness applies a separate, bounded policy to those declarations and matching static source evidence. Neither model executes source, imports user modules, resolves a repository or guarantees runtime success. The REST v1 consumer defines how eligible contracts are exposed; readiness itself remains independent of that target.

source → Capability IR v1 → readiness policy → readiness report
                                                |
                                  REST / MCP / runtime consumers

Version and compatibility

The policy identifier is apizr.readiness/v1, independent of apizr.capability/v1. Readiness does not add fields to the IR or change its canonical serialization, JSON Schema, golden fixture or document digests. Tests pin their baseline hashes at b82e5bf969ce27194075eb053412485941e7d0ce. Changes to readiness decisions require an explicit policy-version review.

The reviewed #266 import-guard refinement retains this v1 contract and schema: it proves one additional inert import-time statement using existing lexical facts, without adding a policy flag or changing report fields. Reports for this newly recognized source shape change; historical schema and golden bytes remain pinned. Experiment evidence grants no exception to this common rule.

The typed report references the exact IR document digest and source record. Each assessment refers to a logical python:<module>:<symbol> identity and source span. Symbols rejected by IR diagnostics still receive an assessment with in_ir=false; they never become executable IR capabilities merely to appear in a report.

States, dimensions and eligibility

  • ready: enough static contract evidence for the policy's ordinary JSON/value interface. It does not prove successful import, invocation or serialization.
  • conditional: a representable declaration has unresolved binding, dependency, initialization or input semantics.
  • unsupported: this policy lacks an adapter for the declaration.
  • ambiguous: no single coherent callable contract can be selected.

Binding, execution, inputs and outputs each contain ordered reasons and a derived state. The overall state uses precedence ambiguous > unsupported > conditional > ready. can_generate_interface is true only for an actual IR capability whose overall state is ready. It means an adapter can be described without inventing input semantics, not that execution is safe or that an adapter will execute successfully.

Unknown response schemas and absent runtime return enforcement are explicit output limitations, not blocking states. All effects are preserved from IR and remain unknown; they do not make every function ineligible. Future execution policy must decide whether those unknown effects are acceptable.

Binding and initialization policy

For an imported logical module other than __main__, the exact top-level if __name__ == "__main__": guard is inert initialization evidence. Recognition requires a single equality comparison, no else, no binding of __name__, no dynamic namespace facts and no module-level indirect attribute/subscript writes. Reversed comparisons, other operators and compound predicates remain uncertain. The original AST and source bytes are retained. Binding events, imports and dynamic-import facts inside the guard are still assessed conservatively; this rule does not promote guarded callable declarations or waive other blockers.

Module-level binding events are collected in lexical order without entering function/lambda bodies or treating class members as module names. Function binding occurs after its declaration-time expressions. Assignments, valued annotated assignments, augmented assignments, deletion, later function/class names, import aliases, loop/with/except targets, match captures and visible named expressions are considered. Annotation-only statements do not assign a value. Comprehension iteration targets do not escape their scope; visible named expressions are conservatively retained. Later events matching a function name produce conditional binding. Branches/loops are not evaluated: even a possible rebinding in dead-looking code is reported.

Conditional IR definitions and all ordinary decorators remain conditional. No runtime decorator whitelist exists. Recognized overload declarations belong to the IR analyzer and are not ordinary implementation decorators. IR duplicate and overload errors become ambiguous assessments, preserving the original diagnostics.

The initialization heuristic deliberately permits pass, docstring/constant expressions, ordinary imports, literal-only assignments and undecorated function declarations whose defaults are literal-only and annotations contain no calls. Literal-only here means constants, list/tuple/set/dict displays and unary +/- applied recursively to literal-only expressions, without unpacking. This is a bounded syntactic allowance, not proof that Python evaluation or memory allocation succeeds.

Other top-level statements produce initialization uncertainty: calls, explicit raise, nonliteral assignments, control-flow blocks and class construction. These are considered both before and after a definition, because later failure can also prevent module import from completing. Overload stubs already associated by IR are excluded from this heuristic. Definition-time calls/decorators remain visible; function bodies are not treated as module initialization.

Visible globals/locals/vars access, exec/eval or star imports make namespace binding uncertain. Dynamic imports (__import__, import_module, including visible imported aliases/attribute calls) create dependency uncertainty. Static relative or non-stdlib imports create unresolved local/namespace/external dependency reasons; no files or distributions are searched. A separately validated repository assessment can discharge a precisely proven local dependency for repository generation while retaining this entire source-local report unchanged. Imports within a candidate's body are inspected syntactically for dependency uncertainty, including nested scopes, without claiming a call graph. Ordinary stdlib imports are a bounded allowance, not a promise that their initialization succeeds. The allowed roots are frozen in apizr.readiness.stdlib.STDLIB_ROOTS, using the intersection of CPython 3.11–3.14 stdlib catalogs. The analyzer never consults the host's stdlib catalog. New, removed or otherwise unlisted roots remain unresolved even if available on the current interpreter; changing this allowance requires policy-version review.

Input and output contracts

Known syntactic primitives str, int, float, bool, None and JSON-shaped list/dict/tuple/set contracts are eligible when members are representable. A dict requires string keys; tuples may be fixed-length or tuple[T, ...]. Unsubscripted containers retain their declared shape with unconstrained members; absent annotations are unconstrained JSON contracts. No stronger validation is inferred. Union, Optional and scalar Literal members (including signed finite numbers) are classified recursively. Callable and bytes need adapters and are unsupported. Annotated retains its metadata but is conditional: v1 does not invent or discard constraint semantics. Unknown expressions, arbitrary classes, aliases and forward references are conditional, even when a class declaration exists locally.

Builtin/typing spellings are syntax conventions, not imported runtime objects. Visible bindings that replace those spellings, or attribute/subscript writes through their roots, are conditional. Direct typing imports can be recognized when uniquely bound and their symbol is not renamed. Renamed symbol imports remain conditional because IR v1 does not preserve their resolution for consumers (the boundary inconsistency tracked in issue #39). Module aliases such as t.List retain the explicit type member and remain recognizable. Arbitrary alias expressions are never evaluated. Unbound conventional typing names may describe an adapter contract, but do not establish runtime importability.

Implementation and recorded overload inputs are reviewed independently; no overload dispatch or signature merging is inferred. Defaults remain unevaluated IR expressions. Calls in definition-time expressions add initialization uncertainty. Declared output types use the same structural review for reporting; missing, unresolved and non-JSON returns are reported as an unknown response schema and never alone block interface generation. Return enforcement remains none. Sync/async functions may be eligible; generators and async generators require a future streaming adapter and are unsupported here.

Stable reason codes

Code Meaning Dimension / impact
APIZR-READY-001 Conditional module definition binding / conditional
APIZR-READY-002 Decorator may replace binding binding / conditional
APIZR-READY-003 Later name binding or deletion binding / conditional
APIZR-READY-004 Module initialization may abort execution / conditional
APIZR-READY-005 Generator requires an adapter execution / unsupported
APIZR-READY-006 Async generator requires an adapter execution / unsupported
APIZR-READY-007 Unconstrained input, no stronger semantics known inputs / ready, limitation
APIZR-READY-008 Dynamic namespace binding unresolved binding / conditional
APIZR-READY-009 Duplicate callable symbol binding / ambiguous
APIZR-READY-010 Ambiguous overload association binding / ambiguous
APIZR-READY-011 Variadic input unsupported inputs / unsupported
APIZR-READY-012 Non-JSON input needs an adapter inputs / unsupported
APIZR-READY-013 Runtime input type unresolved inputs / conditional
APIZR-READY-014 Dynamic import dependency unresolved execution / conditional
APIZR-READY-015 Local/namespace/external dependency unresolved execution / conditional
APIZR-READY-016 Response schema unknown; no return enforcement outputs / ready, limitation
APIZR-READY-017 Annotation metadata semantics unresolved inputs / conditional
APIZR-READY-018 Callable construct has no IR function contract execution / unsupported

Reasons contain code, policy-inference evidence, source line and optional parameter name. Messages are presentation, never identifiers. Reasons are uniquely ordered by line, code and parameter; assessments by capability ID.

Python API and deterministic artifacts

assess(document, source) in apizr.readiness accepts IR plus the exact Python text/bytes that produced it. For notebooks, supply the transformed Python text; its digest must match source.transformed_digest. It checks both the digest and reanalyzed declaration/diagnostic equality before assessing the source. The original notebook digest remains bound through the IR document digest.

apizr.inspection.inspect_source(source, module_name=...) and inspect_file(path, module_name=...) assemble a typed inspection result. File inspection supports one .py or .ipynb; logical identity is explicit in Python. The notebook adapter reads original bytes once and retains the exported Python as transient analysis evidence, never as an extra IR field.

Readiness canonical JSON uses sorted keys, compact separators, explicit defaults, UTF-8 and one terminal LF. Its SHA-256 digest is external. An inspection envelope contains schema_version=apizr.inspection/v1, capability_ir, ir_digest, readiness and readiness_digest. Its deterministic JSON is a presentation contract, not canonical IR bytes. No artifact contains its own digest. No paths, timestamps, random identifiers or attestation-specific fields are added.

Command line

apizr inspect pricing.py
apizr inspect pricing.py --format json
apizr inspect pricing.py --ir
apizr inspect notebook.ipynb --module-name project.pricing

Default text includes logical source identity, digests, capability states, execution form, input/output limitations, unknown effects and reason codes. --format json emits the inspection envelope. --ir emits only canonical IR bytes; the two output selectors are mutually exclusive. No output files are written implicitly. CLI identity defaults to the filename stem, normalized as a Python module name; invalid names require an explicit --module-name. Different paths with the same logical module and source produce identical machine bytes.

Exit codes are the same in every output mode:

  • 0: inspection completed, with ready and/or conditional assessments only;
  • 1: inspection completed, with unsupported/ambiguous assessments or IR errors;
  • 2: input, syntax, unsupported extension, read or notebook-conversion error.

Conditional is not an execution approval: it is nonblocking for inspection but not eligible for adapter generation under this policy. Empty sources report zero capabilities and exit 0. Ordinary user errors use stderr without a traceback.

A small CLI router recognizes inspect and delegates all historical arguments to the unchanged generation CLI. apizr --script ... and apizr --notebook ... retain their behavior; there is no mandatory generate subcommand.

Limits

This is not Python abstract interpretation, import resolution, a resource sandbox, full effect inference or a guarantee of runtime safety. Reflection, indirect aliases and arbitrary mutation cannot be resolved completely. The explicit bounded allowances and unknown effects remain visible to consumers. The REST v1 generator consumes this report without redefining eligibility. Historical issue #28 remains open for the unchanged legacy pipeline; this policy addresses the separate static-evidence problem in issue #31.