Static capability readiness v1
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 v1 / future 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 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
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. 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.