# 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/.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.