# Native asset versions, representations and closed evidence

17 September 2026. Implemented draft-3 developer-preview capability. `asset_evidence_v1` identifies signed publication evidence, not permission to access content or proof of downstream use. The [full candidate requirements](RELEASE-CANDIDATE-GATES.md) still apply.

## Stable identity and explicit publication

An asset has an origin-qualified `asset_id`, one immutable `version_id` for each revision, and one or more representation UUIDs. An event reference is exactly `{asset_id,version_id,manifest_digest,representation_id}`. `manifest_digest` hashes the original signed JSON payload bytes, not the compact signature or a parsed/reserialized projection. A publisher must use a new version ID when the manifest changes. Several representations of that version can distinguish media type, parameters, language and decoded bytes. A mutable URL alone is not an immutable version identifier.

`profiles/asset_evidence.py` validates a manifest with exactly:

```text
protocol_version, profile, origin, asset_id, version_id, published_at,
authority, representations, issuer, delegation_id, policy_digest,
publication, content_variants, derived_from
```

`protocol_version` is `1.2.0-draft.3` and `profile` is `asset_evidence_v1`. The closed representation fields are `representation_id,media_type,media_parameters,languages,decoded_length,decoded_digest`. The schema is `profiles/asset.schema.json`; runtime validation also checks strict JSON, lexical integers, canonical references and field correlations. Existing draft-2 manifests and ordinary free report envelopes are not redefined.

`publication` is the exact delegated-authority `publish_manifest` operation. Its origin, issuer, selected service/delegation, signing key/type, asset/version IDs and endpoint must correlate. The endpoint equals the version ID; the publisher's authority document must appoint `publish_assets` and the `odexa-asset-manifest+jws` signing role within the permitted asset/version prefixes and resource/action/purpose scope. Publication authority does not grant the operation's resource permissions. `sign_manifest` validates shape and signs exact bytes; its caller must obtain and check authority. The independently anchored verifier performs that scoped historical check.

The `authority` reference retains the publisher, service, revision, exact origin history URL and digest; `policy_digest` pins the corresponding native policy. These original documents travel with the evidence and must match independently retained pins. A collector/negotiator need not be the service that published the asset; the manifest's separate appointment remains authoritative for publication.

Moving content to another URL does not change its version identity when its bytes/metadata remain the same. It does not transfer an existing URL-scoped agreement or bearer grant to that new URL. The TLS reference checks this explicitly: the old token is refused at the moved path, and a newly accepted agreement permits that path.

## Encoded representations and observed bytes

`content_variants` contains closed objects `{representation_id,content_codings,content_length,content_digest}`. Codings are an ordered list of canonical tokens; identity is represented by no coding and is not an entry in this array. A decoded representation and an encoded gzip body therefore have distinct sizes and digests. A changed declared encoded variant requires a new immutable manifest/version. An unlisted coding variant can still be correlated through an observed decoded digest, but its encoded bytes have no published variant comparison.

`correlate_delivery(event, verified_closure, ...)` requires an already authenticated event. It checks the qualified manifest/representation reference and publication time. A full completed end-client gateway observation with matching metadata and decoded size/digest is `full_representation_consistent`. This remains authenticated gateway testimony. Supplying actual bytes additionally verifies their digest/count; `representation_bytes_match` becomes true only when the supplied decoded representation matches. **`full_version_delivered` and `downstream_use_verified` remain false**: locally compared bytes do not prove remote receipt or model use.

The caller supplies decoded bytes through its trusted decoding path. The verifier does not run an arbitrary decompressor or claim that a supplied compressed/decoded pair is mathematically linked merely because both match declarations. Gzip is exercised in the actual TLS fixture using its explicit trusted decoding path.

Ranges remain `range_reference_consistent` after their total length and observed metadata correlate. A slice digest cannot establish equality to the complete representation from its full hash. HEAD, no-body responses, failed/incomplete delivery, absent observed metadata and upstream hops cannot become full representation delivery. Client storage/use reports remain `referenced_only`; their signed assertions do not acquire a gateway's observation status.

## Derivation closure and independent trust

Each declared derivation is `{representation_id,source,relationship}` with `source` an exact qualified reference and relationship `copy`, `extract` or `transform`. A copy must preserve decoded length/digest and representation metadata. Other relationships are publisher assertions; closure does not establish that a transformation was computed correctly, that the source was licensed for it or that any undisclosed sources were absent. `derivation_computation_verified` remains false. Event-declared parents must match the published output links when an output reference exists, and no derivation report may predate its source publication.

The closed bundle is exactly `{protocol_version,profile,roots,manifests,documents}`. Every referenced parent must be present, correctly signed and independently anchored; unrelated extra manifests/documents, duplicate roots/manifests, immutable-version conflicts, substituted representations, future parents, self references and excessive depth fail. Object-member order cannot disguise a duplicate logical reference. The reference bounds are 32 manifests, eight derivation levels, 64 documents, 64 roots and 8 MiB encoded bundle. It never truncates a larger graph and claims complete closure.

`AssetTrust` is a trusted in-process input with `current_snapshots` by origin, exact `pinned_documents`, and optional `manifest_pins` mapping immutable version IDs to payload digests (maximum 4,096 pins). The exporter cannot bootstrap this trust with its own bundled keys/documents. A cross-origin derivation requires independently current knowledge and pins for every source origin. Optional authority-history objects reject superseded observations. Current snapshots are checked again after verification, including elapsed time.

Historical signatures are checked against the pinned publication scope/key/time. A retired signer can preserve historical evidence; its current publication authority is reported separately. Known revoked public material from current or independently pinned historical origin documents causes rejection, even after current metadata removes the old key. Key IDs cannot be silently rebound to different public material or immutable key fields. Claimed publication time plus retained origin pins establishes historical consistency, not an independently witnessed timestamp or proof that no intermediate withdrawal occurred.

## Durable catalog and exact publication bytes

`AssetCatalog(path)` in `profiles/asset_catalog.py` stores verified evidence separately from live agreement/credential databases. Its private owned SQLite file and directory reject symlinks/hard links and unrelated operational tables. Exact bundle retention, immutable version pins, manifests and context documents commit together with FULL synchronization. Exact retries recheck current trust, and new versions cannot redefine retained version IDs after restart. Verification is repeated after acquiring the write lock and before commit; late failure rolls back all new material.

```python
catalog.retain(bundle_bytes, trust=asset_trust, now=trusted_clock)
publication = catalog.get_manifest(version_id)
closed_bundle = catalog.bundle_for(exact_root_references)
```

`get_manifest` returns the original payload/JWS with `historical_only` assurance, suitable for an explicitly configured publisher route. `bundle_for` reconstructs bounded exact dependencies; its recipient must verify current independent trust again. Catalog retention does not update an origin's public routes, authorize a website change, create an agreement or transfer credentials. The actual TLS test explicitly configures the publisher's version routes and protected content adapter. The catalog is not encrypted storage or an anti-rollback mechanism against a privileged local administrator.

## Agreement evidence export and import

`profiles/asset_portability.py` adds two explicit archive profiles:

| Profile | Underlying agreement |
| --- | --- |
| `odexa-assets-free-evidence-snapshot-1` | Ordinary draft-3 free agreement |
| `odexa-assets-storage-free-evidence-snapshot-1` | Explicitly selected `storage_sessions_v1` free agreement |

They require `AssetExternalTrust`, containing the ordinary [archive trust](PORTABILITY.md) plus separate `asset_trust`. `build_bundle` requires exact `asset_bundle_bytes`, independently obtained `asset_trust`, and explicit `storage=True` for the second variant. `verify_bundle` and `AssetEvidenceArchive` likewise select the storage variant explicitly. Ordinary archive entry points still reject asset references instead of silently dropping them.

The signed inventory includes exactly one nested `asset_bundle` artifact. Its roots must equal all distinct `asset_ref` and `derived_from` references in the selected agreement's retained reports. Every ancestor is then closed transitively. Building and importing both verify asset authority/signatures and report/representation correlations. Removing a parent and re-signing the outer inventory with an authorised exporter key still fails. The outer source cut remains the one-agreement SQLite snapshot; asset dependencies come from separately retained immutable publication evidence, not a claimed atomic snapshot across all systems.

The nested asset artifact can be at most 8 MiB; other artifacts remain at most 1 MiB and the complete encoded archive remains at most 16 MiB. Report counts describe the selected retained source, not global activity or the complete asset catalog. Selected storage archives also close each exact parent write and derive storage metrics from accepted terms. Authenticated but unresolved storage claims remain unresolved.

Imports retain exact signatures, original issuers and source qualifications in a separate inert archive. Asset correlations are additional evidence, never new access or spending authority. The ordinary collector's intake authenticates the reporter and retains its assertion; it does not by itself certify the referenced asset. An internally consistent signed report with the wrong actual representation can therefore be retained as a claim while asset verification/export rejects its proposed correlation. The separately selected [paid asset/storage archive variants](PAID-PORTABILITY.md) now retain this closure together with independent payer trust and exact payment audit replay.

## Reproduce the evidence

```sh
PYTHONPATH=tests:. python -m unittest test_asset_evidence test_asset_catalog test_asset_runtime -v
python verification/build_asset_schemas.py
node verification/check_profiles.cjs /absolute/path/to/ajv profiles/fixtures/*-cases.json
```

The Python TLS tests exercise actual direct/delegated manifest publication, two content revisions, gzip, protected response writes, signed gateway reporting, a moved URL requiring a new agreement, selected storage asset references and inert archive import. Separate catalog tests cover actual SQLite restart, immutable identities, concurrent retries and rollback. Hostile cases cover revoked/rebound keys, missing/extra/substituted dependencies, source timing, graph limits and an authorised exporter omitting a parent. Structural schemas alone do not prove those semantics.

See [current verification](../verification/REPORT.md). The [independent native asset client](INDEPENDENT-ASSET-CLIENT.md) now verifies complete direct/delegated and cross-origin graph closure, original publication bytes and persistent trust against real Python HTTPS. It adds 51 native cases and nine wire methods; existing free/paid/storage paths pass the combined regressions. Observation coverage, fresh-agreement provider handover and installed tooling have separate evidence. M6 normative consolidation, complete release validation and integrated four-perspective review remain required.
