ENCLAVE Artifact API#

Context#

Previously, Capability.ENCLAVE classified a lowering as Action.ENCLOSED when its rule emitted only an in-band marker. That was not enough to prove preservation: callers received no carrier and the marker could address content that was never materialized. The contract now makes enclosure a verified artifact property while leaving every existing writer capability table unchanged.

Changes#

  • myform/degrade/artifact.py adds validated, JSON-round-trippable EnclaveRecord and ConversionArtifact models plus a targeted ArtifactRequiredError.

  • myform/degrade/registry.py lets a lowering construct a record through LoweringContext.enclave(node) and return it with RuleResult. The context snapshots the source occurrence before extension policy runs, so rule mutation cannot change the carrier.

  • myform/degrade/engine.py adds lower_with_enclaves(). It classifies an act as ENCLOSED only when a valid record exists and the final lowered tree links to that record’s deterministic carrier path. The existing lower() raises rather than discard returned records.

  • myform/session.py and myform/__init__.py add convert_artifact() while preserving the legacy return shapes for carrier-free conversions. Legacy calls raise if a carrier exists. An ENCLAVE-capable writer must parse its final output through Writer.find_enclave_markers(); repeating a carrier path as prose is not a link and fails artifact construction.

  • tests/test_enclave_artifacts.py exercises deterministic addressing, typed-node and JSON round trips, exact source retention, marker validation, strict behavior, and no-silent-loss.

Approach#

The carrier id hashes canonical finite JSON for the exact typed payload together with its source format and structural occurrence path. Its path is always enclaves/<full-sha256>.json. content_hash remains the separate position-independent semantic identity. Arbitrary token metadata outside the finite JSON domain is rejected deterministically before a record exists. The engine owns carrier admission, every ENCLOSED report row names its carrier id and path, and the artifact contains writer-recovered typed marker links, so aggregate counts and raw path substrings cannot manufacture a non-lossy result.

Risks / Edge Cases#

  • Identical content at different source occurrences has the same semantic content_hash but a distinct carrier id, so each report row remains exactly reversible and independently bound.

  • A record for another node, a non-canonical path, or a marker that points elsewhere fails explicitly.

  • Strict mode still refuses ENCLAVE; its structured error describes the actual deterministic result rather than assuming that a carrier exists.

  • Built-in writers remain carrier-free until each has marker/materialization tests of its own.