Retained local attestation export

Source verified against d033b767813a8ce53f054f45585fde24d7ba016b on 2026-10-21 UTC. The re-verification command below exercises the source contracts. This document does not establish deployment, current retained-row availability or producer trust.

POST /api/evidence/v1/attestations:export retains its existing request, scopes and stellaops.attestor.bundle/1 response. It accepts authenticated attest:read or attest:create and resolves the entry inside the authenticated tenant. A caller header cannot grant another tenant’s entry. The optional archive remains the first source; an archive hit keeps its existing bytes, proof and metadata behavior.

If that archive returns no material, a recorded local entry can use the Evidence owner’s retained transparency row. The installation-wide local reader’s tenant argument is not an ownership filter: the export establishes ownership before that read. It requires one exact UUID, local backend and matching configured log URL with the stellaops URI scheme, log index, artifact SHA/kind/image/subject and bundle-hash match. No remote lookup, new report, signature, schedule, import, backfill or archive write fills a missing result.

The local JSON is parsed with duplicate-key, type, depth, encoding, payload and signature-count checks. Payload, signature and key-ID strings remain unchanged. The shared DSSE canonicalizer produces the canonical envelope; format-pinned SHA-256 must equal both the owned entry and local row before bytes are returned. This proves the recovered canonical content address. It does not assert that PostgreSQL JSONB preserved an original JSON document’s whitespace or property order.

canonicalBundle still contains base64 canonical JSON. The existing metadata map identifies the fallback:

FieldMeaning
archive.missing=trueThe optional archive remains absent.
bundle.source=local-transparencyMaterial came from the retained owner row.
bundle.hashVerified=trueCanonical SHA-256 matched both recorded hashes.
local.signatures.countNumber of retained signatures.
local.signatures.N.keyidKey ID at canonical ordinal N.
local.signatures.N.algExact original algorithm at that same ordinal.

Ordinals follow the canonicalizer’s ordinal (keyid, sig) ordering, including distinct signatures sharing one key ID. Duplicate signature tuples or conflicting associations are refused. Missing algorithms are never inferred. The canonicalizer intentionally omits alg: the algorithm metadata is a retained owner assertion, not a field authenticated by the canonical bundle digest. A later verification request must preserve that association and independently enforce enrolled trust.

The new fallback has fixed safety budgets: 8 MiB retained UTF-8 JSON/canonical envelope, at most 200 local lookups per export, and 8 MiB summed Web JSON bytes of complete successful fallback items. The last count includes base64 expansion, entry/proof fields and algorithm/key-ID metadata. It excludes archive-hit items, metadata-only unavailable items and package framing; it is not a streaming/network response ceiling. Decoded payload/signature-count limits reuse existing submission settings (defaults 2 MiB and six signatures). Nothing is truncated to fit a budget.

Unavailable fallback items have an empty canonicalBundle, archive.missing=true and local.unavailable identifying the boundary: local_backend_required, local_entry_count_invalid, local_entry_identity_mismatch, local_envelope_invalid, local_envelope_too_large, local_hash_algorithm_unavailable, local_bundle_hash_mismatch or local_export_budget_exceeded. Read faults and caller cancellation are not converted to empty success. The export neither enrolls trust nor verifies a producer.

For subsequent verification, the stored effective signer mode remains authoritative. The kms branch preserves existing HS256/384/512 secret verification. Other explicit algorithms use the existing configured verifier, requiring a nonempty recorded key ID and exact signature key binding plus the existing enrolled algorithm/provider checks. A configured registry default mode may differ from the stored effective bundle mode. This dispatch does not prove hardware custody or create trust for a Scanner key. Missing/disabled keys, unavailable providers, mismatched algorithms, invalid signatures and insufficient signature counts remain failures.

Re-verify the source contracts in a PowerShell session with the canonical targeted runner (these classes use synthetic owner ports and ephemeral purpose-selected provider material):

& ./tools/scripts/test-targeted-xunit.ps1 -Project src/Evidence/__Tests/StellaOps.Evidence.WebService.Tests/StellaOps.Evidence.WebService.Tests.csproj -Class @('*RetainedLocalEnvelopeExportTests', '*ConfiguredKmsSignatureDispatchTests') -BuildProjectReferences

The mapped-host qualification uses a separately retained sanitized driver, an exclusively reserved non-listening loopback endpoint, a nonvacuous connection-event control, strict owner ports, ephemeral DataProtection and persisted-key/private activation/outbound traps through disposal. Its synthetic authorization principal does not prove live bearer authentication. Actual Evidence adoption, retained material, configured producer/issuer trust and a qualifying owner verification receipt remain separate operational gates.