c8 documentation

Configure & deploy

Operator book

Highly available RKE2/K3s, EKS, AKS, GKE & on-prem deployment.

This path takes a junior platform engineer from an ontology-grounded TriG bundle to an autoscaling c8 cluster. The checked-in chart has an explicit RKE2 profile; K3s, EKS, AKS, GKE and other on-prem clusters use the schema-validated generic profile plus provider-specific identity, storage, ingress and node-autoscaling overlays.

Open the complete Helm chart reference

01 · start

The deployment sequence

  1. 01Prepare RKE2/K3s, EKS, AKS, GKE or on-prem Kubernetes, capacity controllers and external dependencies.
  2. 02Build and digest-pin the Rust services and HermiT adapter.
  3. 03Create responsibility-specific node pools and reservations.
  4. 04Author and validate ontology-grounded TriG plus its graph catalog.
  5. 05Install CRDs, platform chart, then workload chart.
  6. 06Upload the source, submit ingestion, certify and publish a snapshot.
  7. 07Run exact SPARQL and verify metrics, authorization and fail-closed behavior.
  8. 08Qualify scaling, retries, node loss, backup, restore and rollback.

02 · data lifecycle

From prepared TriG to linked answers

c8 creates and operates the linked-data environment; it does not invent the links. Your source must already contain RDF subjects, predicates, objects, named graphs and any OWL axioms you expect the reasoner to use.

StepRouteWhat it does
1PUT /v1/datasets/{datasetId}Creates the logical, tenant-scoped database namespace.
2PUT /v1/datasets/{datasetId}/sources/{sourceId}Streams and verifies an immutable RDF/OWL TriG source.
3POST /v1/datasets/{datasetId}/ingestionsStarts asynchronous compilation of a checksum-addressed bundle.
4GET /v1/jobs/{operationId}Polls compilation, qualification and artifact state. There is no completion callback.
5POST /v1/datasets/{datasetId}/snapshots/{snapshotId}/publishAtomically activates a qualified immutable snapshot.
6GET/POST /v1/datasets/{datasetId}/sparqlRuns standard SPARQL Protocol queries and negotiates standard result formats.
7POST /v1/datasets/{datasetId}/queryReturns the result plus snapshot, routing, completeness and optional proof/hydration evidence.

What ingestion materializes

Compilation preserves the original links while producing immutable RDF graph representations, named-graph catalogs, IRI/GUID dictionaries, graph-capability indexes, class/property extents, OWL signatures and datatype policy, pinned import bundles, exact-reasoning inputs, distributed plans, Arrow/Parquet data, GUID locators, provenance/checksum manifests and serving/completeness certificates.

03 · API map

Use the semantic routes before the physical routes

/sparql

Standard interface

SELECT/ASK return SPARQL JSON, XML, CSV or TSV; CONSTRUCT/DESCRIBE return Turtle, N-Triples or RDF/XML.

/query

c8 evidence interface

Adds active snapshot, authorized graph-set hash, execution mode, completeness, routing, proofs and optional enterprise payload.

/sparql/direct/validate

OWL legality

Reports whether graph patterns are legal under OWL 2 Direct Semantics; it does not answer the query.

/sparql/direct/route

Reasoning route

Chooses certified index, certified finite closure or exact HermiT. Unknown coverage routes to HermiT.

/locate

Physical locator

Maps semantic entity GUIDs to authorized physical Parquet locations.

/hydrate

Physical hydration

Retrieves bounded payload columns such as measurements, descriptions or timestamps after semantic selection.

The candidate contains 32 explicit REST operations plus two local Swagger asset handlers across control, query, fragment/shuffle, locator, hydration and exact-reasoner roles. Synchronous requests return directly; ingestion is the asynchronous exception and is observed by polling.

04 · platform

Prerequisites you must supply

Cluster

Kubernetes 1.33+ per the checked-in chart, Metrics Server, an enforcing CNI, a default StorageClass and enough allocatable whole-core, memory and local-NVMe capacity.

Capacity

A provider-backed Cluster Autoscaler or node provisioner, plus Kueue. KEDA is used only for separately owned queue jobs; it does not replace node capacity.

Durable services

PostgreSQL for catalog truth and S3-compatible object storage for immutable artifacts. c8 workers must not discover state by listing buckets.

Security

Private registry, digest-pinned images, external secret management, graph-scoped bearer identities, NetworkPolicy enforcement and service-mesh/internal mTLS.

# Build gates before image publication
python3 scripts/structural_validate.py --root .
cargo fmt --all --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features
mvn --batch-mode --no-transfer-progress -f adapters/hermit-reasoner/pom.xml clean package
helm lint charts/ngkg-platform
helm lint charts/ngkg-workloads

05 · topology

One responsibility per worker pool

Keep batch, query, fragment, hydration and reasoner shapes separate. Required anti-affinity turns a new replica into an unambiguous node-pool demand signal instead of packing it onto an already saturated node.

Pool label / taintWorkOwnerWarm minimum guidance
semantic-projectionTriG plan and projection completionsOperator + KueueMay scale from zero if cold start is acceptable
semantic-artifact-buildArrow/Parquet and semantic sidecarsOperator + KueueMay scale from zero
index-buildReducers, finalizers, serving-root barrierOperator + KueueMay scale from zero
reasoningReference compile and HermiT qualificationOperator + KueueKeep warm if ingestion SLO requires it
online-reasoningExact Direct-BGP partitionsHPARepository default: 2
sparql-query-processingSPARQL coordinator and query cacheHPARepository default: 3
sparql-fragment-processingFragments and sparse joinsHPARepository default: 3
parquet-hydrationGUID-directed row-group hydrationHPARepository default: 2

06 · source data

What your TriG upload must contain

  • UTF-8 RDF 1.1 TriG sent as application/trig; charset=utf-8.
  • At least one named graph. Every graph name must be an absolute IRI; blank-node graph names are rejected.
  • One ontology-grounded */semkg graph per subdomain containing the asserted ontology module and asserted instance data required by that domain.
  • A declaration for every named graph in graphCatalog. Undeclared graphs fail compilation.
  • At least one authorization label on every query-visible or reasoning-visible graph.
  • OWL 2 DL-compliant ontology documents with complete locally pinned imports, supported datatypes and globally consistent merged semantics.
  • Stable entity IRIs that remain the identity authority across TriG, dictionaries, GUID locators and Parquet hydration.

07 · ontology model

ABox, TBox, RBox and “CBox” requirements

TBox

Classes and conceptual structure: owl:Class, rdfs:subClassOf, equivalent classes, restrictions and domain/range consequences.

ABox

Named individuals and assertions: rdf:type, object-property facts and well-typed datatype-property values.

RBox

Property semantics: subproperties, inverse properties, characteristics, domains/ranges, transitivity and OWL 2 property chains.

CBox-like constraints

Disjointness, keys, qualified cardinality, some/all values, functional or inverse-functional properties and negative assertions.

08 · graph layout

Use named graphs as connected subdomains

https://c8-next-generation.io/<scope>/<subdomain>/semkg
https://c8-next-generation.io/<scope>/<subdomain>/closure
https://c8-next-generation.io/<scope>/<subdomain>/provenance

# Examples
https://c8-next-generation.io/prod/customer/semkg
https://c8-next-generation.io/prod/order/semkg
https://c8-next-generation.io/prod/fulfillment/semkg

semkg is the required asserted ontology/data module. It is the only role admitted to exact ontology assembly after graph authorization.

closure is an optional selected finite materialization used as derived acceleration. It can never represent every OWL 2 DL consequence and is not asserted input to the exact reasoner.

provenance carries evidence metadata and is also excluded from exact ontology assembly.

The physical TriG default graph is preserved and counted, but c8 keeps it outside the query/reasoning-visible union-default service dataset. Put production subdomain content in named graphs. Repeating an IRI unions its triples under TriG semantics.

09 · logical qualification

Pin imports, validate profile, control datatypes

  • Each ontology document has exactly one owl:Ontology header.
  • owl:versionIRI and owl:imports declarations attach to that header.
  • Every import resolves to one operator-pinned local document and SHA-256. No network import discovery occurs in the trusted reasoning boundary.
  • The merged set must pass OWLAPI OWL 2 DL profile qualification and HermiT global consistency.
  • The checked-in datatype policy currently publishes 31 supported datatype IRIs. Unsupported datatypes or invalid lexical forms fail compilation.
  • An inconsistent ontology may emit bounded diagnostic evidence, but it cannot publish a normal snapshot or return a partial semantic answer.

10 · access policy

Declare graph roles and visibility

Graph identity is never inferred from filenames. The immutable compile bundle carries a catalog entry for every named graph. The runtime resolves authorization before constructing the active dataset, then recomputes graph-set and active-dataset hashes.

"graphCatalog": [
  {
    "graphIri": "https://c8-next-generation.io/prod/customer/semkg",
    "role": "semkg",
    "authorizationLabels": ["domain:customer", "env:prod"],
    "queryVisible": true,
    "reasoningVisible": true
  },
  {
    "graphIri": "https://c8-next-generation.io/prod/order/semkg",
    "role": "semkg",
    "authorizationLabels": ["domain:order", "env:prod"],
    "queryVisible": true,
    "reasoningVisible": true
  },
  {
    "graphIri": "https://c8-next-generation.io/prod/order/closure",
    "role": "closure",
    "authorizationLabels": ["domain:order", "env:prod"],
    "queryVisible": false,
    "reasoningVisible": false
  }
]

11 · authoring example

A reasoning-ready, cross-subdomain TriG skeleton

This abbreviated file demonstrates the required shape. Expand it with your own domain ontologies, facts and pinned ontology documents; do not copy the example identifiers into production.

@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl:  <http://www.w3.org/2002/07/owl#> .
@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .
@prefix cust: <https://example.org/ontology/customer#> .
@prefix ord:  <https://example.org/ontology/order#> .
@prefix id:   <https://example.org/id/> .

GRAPH <https://c8-next-generation.io/prod/customer/semkg> {
  <https://example.org/ontology/customer>
    a owl:Ontology ;
    owl:versionIRI <https://example.org/ontology/customer/1.0.0> .

  # TBox — classes and hierarchy
  cust:Person a owl:Class .
  cust:Customer a owl:Class ; rdfs:subClassOf cust:Person .
  cust:SystemActor a owl:Class .

  # CBox-like model constraints (not a separate OWL 2 box)
  cust:Customer owl:disjointWith cust:SystemActor .

  # RBox — object/data properties and characteristics
  cust:hasAccount a owl:ObjectProperty ;
    rdfs:domain cust:Customer ;
    rdfs:range cust:Account ;
    owl:inverseOf cust:accountOwnedBy .
  cust:accountOwnedBy a owl:ObjectProperty, owl:FunctionalProperty .
  cust:customerId a owl:DatatypeProperty, owl:FunctionalProperty ;
    rdfs:domain cust:Customer ; rdfs:range xsd:string .

  # TBox restriction and key
  cust:Customer rdfs:subClassOf [
    a owl:Restriction ;
    owl:onProperty cust:hasAccount ;
    owl:someValuesFrom cust:Account
  ] ; owl:hasKey ( cust:customerId ) .

  # ABox — individuals and assertions
  id:customer-C001 a cust:Customer ;
    cust:customerId "C001"^^xsd:string ;
    cust:hasAccount id:account-A001 .
  id:account-A001 a cust:Account .
}

GRAPH <https://c8-next-generation.io/prod/order/semkg> {
  <https://example.org/ontology/order>
    a owl:Ontology ;
    owl:versionIRI <https://example.org/ontology/order/1.0.0> ;
    owl:imports <https://example.org/ontology/customer/1.0.0> .

  ord:Order a owl:Class .
  ord:OrderLine a owl:Class .
  ord:Product a owl:Class .

  ord:hasLine a owl:ObjectProperty ;
    rdfs:domain ord:Order ; rdfs:range ord:OrderLine .
  ord:lineProduct a owl:ObjectProperty, owl:FunctionalProperty ;
    rdfs:domain ord:OrderLine ; rdfs:range ord:Product .
  ord:purchasedProduct a owl:ObjectProperty ;
    owl:propertyChainAxiom ( ord:hasLine ord:lineProduct ) .

  id:order-O100 a ord:Order ;
    ord:hasLine id:line-O100-1 .
  id:line-O100-1 a ord:OrderLine ;
    ord:lineProduct id:product-P42 .
  id:product-P42 a ord:Product .
}

# Optional selected materialization. Treat as derived acceleration,
# never as asserted ontology input to exact HermiT reasoning.
GRAPH <https://c8-next-generation.io/prod/order/closure> {
  id:order-O100 ord:purchasedProduct id:product-P42 .
}

12 · infrastructure

Configure responsibility pools before Helm

Every autoscaled worker node joins with exactly one c8 responsibility label and taint. Use immutable machine templates and measured reservations. The example below is RKE2 syntax; express the same labels, taints, CPU policy and reservations in K3s, EKS managed node groups/Karpenter, AKS VMSS pools, GKE node pools or your on-prem provisioner.

node-label:
  - ngkg.io/workload=sparql-fragment-processing
node-taint:
  - ngkg.io/workload=sparql-fragment-processing:NoSchedule
kubelet-arg:
  - cpu-manager-policy=static
  - topology-manager-policy=restricted
  - system-reserved=cpu=2,memory=4Gi,ephemeral-storage=20Gi
  - kube-reserved=cpu=1,memory=2Gi,ephemeral-storage=10Gi

Install a Kubernetes-minor-compatible, digest-pinned node autoscaler outside the c8 release. On RKE2 the checked-in profile expects Cluster Autoscaler with the Rancher provider; managed-cloud and generic clusters supply their own provider integration. c8 Helm values express application demand, not cloud credentials.

13 · Helm

Create an approved values overlay

Start from the checked-in schemas, replace every repository, digest, Secret and private CIDR, then tune resources from measured allocatable capacity. Never use the sample numbers as universal sizing.

images:
  query:     {repository: registry.example/c8-online, digest: sha256:<reviewed>}
  fragment:  {repository: registry.example/c8-online, digest: sha256:<reviewed>}
  locator:   {repository: registry.example/c8-online, digest: sha256:<reviewed>}
  hydration: {repository: registry.example/c8-online, digest: sha256:<reviewed>}
  reasoner:  {repository: registry.example/c8-reasoner, digest: sha256:<reviewed>}

onlineServing:
  databaseSecret: c8-database
  authTokensSecret: c8-auth-tokens
  tenantAdmissionSecret: c8-tenant-admission
  objectStoreCredentialsSecret: c8-object-store
  artifactStoreBaseUrl: s3://c8-artifacts/prod

onlineReasoning:
  enabled: true
  sharedWorkspaceClaim: c8-reasoner-work
  sharedTokenSecret: c8-reasoner-token
  adapterSha256: <sha256-of-ngkg-hermit-adapter.jar>

hpcRuntime:
  guaranteedQos: true
  nodeSaturationTargetPercent: 80
  cpuManagerPolicyRequired: static
  topologyManagerPolicyRequired: restricted
  localNvme: {requiredForSpill: true}

networking:
  defaultDenyNetworkPolicies: true
  tlsMode: external-service-mesh-required
  dependencyCidrs:
    - 10.40.0.0/24 # private PostgreSQL endpoint
    - 10.41.0.0/24 # private S3/MinIO endpoint

14 · installation

Install CRDs, platform and workloads in order

python3 scripts/validate_helm_values.py   charts/ngkg-workloads/values.yaml   --overlay approved-rke2-values.yaml

helm upgrade --install c8-crds charts/ngkg-crds   --namespace c8 --create-namespace

helm upgrade --install c8-platform charts/ngkg-platform   --namespace c8 --values approved-platform-values.yaml   --wait --rollback-on-failure

helm upgrade --install c8-workloads charts/ngkg-workloads   --namespace c8 --values approved-rke2-values.yaml   --wait --rollback-on-failure

CRD upgrades require separate review because Helm does not manage CRD schema upgrades like ordinary templates. The platform migration hook uses a separately privileged migration database identity; runtime services use restricted identities.

15 · publish data

Create, upload, ingest and publish

Use non-nil UUIDs, a policy version that matches your projection bundle, and a SHA-256 over the exact uploaded bytes.

export C8_API=https://c8.example.com
export C8_TOKEN=<bearer-token>
export DATASET_ID=<uuid>
export SOURCE_ID=<uuid>
export SNAPSHOT_ID=<uuid>
export IDENTITY_NAMESPACE=<uuid>

# 1. Create the tenant-scoped dataset
curl -fsS -X PUT "$C8_API/v1/datasets/$DATASET_ID"   -H "Authorization: Bearer $C8_TOKEN"   -H "Content-Type: application/json"   --data '{"identityNamespace":"'"$IDENTITY_NAMESPACE"'","policyVersion":"urn:c8:projection:v1"}'

# 2. Upload the exact UTF-8 TriG bytes
TRIG_SHA256=$(sha256sum dataset.trig | awk '{print $1}')
curl -fsS -X PUT "$C8_API/v1/datasets/$DATASET_ID/sources/$SOURCE_ID"   -H "Authorization: Bearer $C8_TOKEN"   -H "Content-Type: application/trig; charset=utf-8"   -H "X-NGKG-Content-SHA256: $TRIG_SHA256"   --data-binary @dataset.trig

# 3. Stage the checksum-addressed compile bundle with scripts/stage_reference_bundle.py.
# It must include source, ontology bundle, graphCatalog, projection policy,
# pinned reasoner inputs, limits and independently expected certified queries.

# 4. Submit immutable ingestion work
curl -fsS -X POST "$C8_API/v1/datasets/$DATASET_ID/ingestions"   -H "Authorization: Bearer $C8_TOKEN"   -H "Idempotency-Key: ingest-$SNAPSHOT_ID"   -H "Content-Type: application/json"   --data @ingestion-request.json
{
  "bundleObjectKey": "bundles/<checksum>/compile-bundle.json",
  "bundleSha256": "<64-lowercase-hex>",
  "parentSnapshotId": null,
  "targetSnapshotId": "<snapshot-uuid>",
  "publicationPolicy": "manual",
  "resourceProfile": "distributed-hpc-v1"
}

Poll GET /v1/jobs/{operationId}. When certification succeeds, publish with the snapshot route and the expected parent snapshot. Publication uses compare-and-swap, so a stale predecessor cannot replace newer truth.

16 · source sizing

Know the direct-upload boundary

The current endpoint is streaming and memory-bounded, but it still proxies one long HTTP upload through an API pod, writes the entire source to local scratch, parses it, uploads it to object storage and verifies the remote object. It accepts only raw, uncompressed UTF-8 TriG—no ZIP, gzip, zstd, CSV, Parquet or JSON-LD source body.

100 GiB

maximum TriG

128 GiB

scratch volume

1

concurrent upload

2 billion

maximum quads

10,000

named graphs

8 MiB

multipart buffer

8

multipart concurrency

64 MiB

single-upload threshold

Two proposed—not implemented—large-source paths

Register existing cloud objects

Freeze exact bucket, key, version ID, byte length and SHA-256; mount or stream the objects read-only; then fan out validation and compilation without proxying the source through an API pod.

Direct multipart upload

Authenticate an upload session, issue short-lived presigned URLs, complete object storage, then register a checksum-bound source manifest.

For either path, package very large datasets as independently valid 1–8 GiB TriG shards and parse them through indexed Kubernetes jobs. Blank-node scope and graph IRIs must be explicit in the manifest. Neither cloud-object registration nor the proposed /source-uploads routes exist in Phase 40.13.9.

17 · target architecture

Target: versioned cloud TriG to complete distributed answers

Precise standards target: distributed SPARQL 1.1 query execution under the OWL 2 Direct Semantics entailment regime over a validated OWL 2 DL ontology snapshot.

  1. 01Register existing cloud-hosted TriG objects.
  2. 02Mount or stream each object read-only.
  3. 03Freeze the exact object versions, lengths and checksums.
  4. 04Parse and partition RDF across cores and nodes.
  5. 05Preserve authorized named graphs and c8 union-default semantics.
  6. 06Build GUID dictionaries, Parquet data, adjacency and semantic indexes.
  7. 07Assemble authorized asserted */semkg graphs plus pinned imports only.
  8. 08Validate the merged ontology as OWL 2 DL and check consistency.
  9. 09Compute a certified distributed finite closure.
  10. 10Route uncovered entailment patterns to exact HermiT.
  11. 11Execute SPARQL algebra and graph traversal across worker nodes.
  12. 12Merge and validate every required partition.
  13. 13Return the result only when completeness is proven.

Example cross-plane query

SELECT ?shipment ?country
WHERE {
  ?shipment a <https://example.org/ontology/Shipment> ;
            <https://example.org/ontology/locatedAt> ?location .

  ?location <https://example.org/ontology/locatedIn>+ ?country .
}

This can combine distributed OWL class/property entailment, exact property-path traversal, partitioned cross-graph joins, HermiT fallback for unknown coverage, scalar differential verification during qualification and optional Parquet hydration.

A complete response proves its context

SPARQL resultsnapshot hashgraph-set hashexecution-plan hashentailment regimeproof/support IDsreasoning certificatepartition evidencecomplete: true

The coordinator must not claim completeness when any worker partition is missing, the ontology is inconsistent, a Direct-Semantics BGP is illegal, a checksum differs, unknown index/closure coverage cannot reach HermiT, a required operation times out, or distributed and scalar reference results disagree.

18 · acceptance

Verify the ownership and scaling chain

kubectl -n c8 get deploy,statefulset,pods
kubectl get resourceflavors.kueue.x-k8s.io
kubectl -n c8 get localqueues.kueue.x-k8s.io
kubectl get nodes -L ngkg.io/workload
kubectl -n kube-system logs deploy/cluster-autoscaler --since=10m
kubectl -n c8 get hpa
kubectl -n c8 get networkpolicy

Sustain each online role below and above its 80% target. Confirm the HPA adds a pod, anti-affinity makes it pending when the pool is full, and Cluster Autoscaler grows only the matching Rancher pool. Then confirm scale-down waits for active spill/frontier/checkpoint gauges to drain.

19 · use the service

Run the first exact SPARQL request

curl -fsS -X POST "$C8_API/v1/datasets/$DATASET_ID/sparql"   -H "Authorization: Bearer $C8_TOKEN"   -H "Content-Type: application/sparql-query"   -H "Accept: application/sparql-results+json"   --data-binary 'SELECT ?customer ?product WHERE {
    ?order <https://example.org/ontology/order#purchasedProduct> ?product .
    ?order <https://example.org/ontology/order#placedBy> ?customer .
  }'

The query role applies bearer identity, graph authorization, SPARQL dataset precedence and active-snapshot checks before planning. Exact entailment requests are rebuilt only from authorized semkg graphs plus pinned imports—never closure or provenance graphs.

20 · product boundary

What c8 is—and what it is not

c8 builds an immutable, queryable and reasoner-enabled linked RDF/OWL environment from already aligned TriG. It preserves entity IRIs and graph relationships, derives authorized consequences when the ontology supports them, and can hydrate semantic GUID results with enterprise payload columns.

Inside c8

  • RDF/OWL validation and deterministic compilation
  • Named-graph authorization and immutable snapshots
  • SPARQL evaluation and exact OWL reasoning
  • Physical location and optional payload hydration

Separate upstream work

  • CSV or source-schema mapping
  • Text-to-OWL generation
  • Vocabulary alignment to BFO, CCO or domain ontologies
  • Automatic sameAs, closeMatch or alignment assertions

It is also not yet a complete public Web Linked Data publishing server. Dereferenceable entity routes, per-resource content negotiation and Link headers, Graph Store Protocol, secured SERVICE federation, provenance/proof retrieval and stable public URI/caching policy remain to be completed.

21 · operations

What fails closed

Missing or duplicate partitions
Bad artifact or adapter checksum
Unauthorized or undeclared graph
Unsupported datatype or invalid lexical value
OWL 2 DL profile violation
Globally inconsistent ontology
Exceeded row/frontier/spill/time ceiling
Worker loss without complete recovery evidence

These conditions return an error or block snapshot publication. They never return a successful partial answer.

22 · release

Production qualification still required

  • Native Cargo formatting, compilation, Clippy and complete workspace tests.
  • Maven HermiT adapter tests and deterministic/offline release builds.
  • Helm lint, deterministic rendering, install/upgrade/rollback/uninstall.
  • Activation of distributed algebra and property-path worker transports with scalar equality.
  • Full applicable W3C suites plus Apache Jena differential correctness and performance.
  • Multinode autoscaling, skew, hot vertices, NVMe pressure, failure injection, checkpoint recovery and soak tests.
  • Penetration, tenant isolation, SBOM, license/CVE, reproducible image and signed-release evidence.