# Independent native asset graph verification

18 September 2026. The native Node implementation independently checks the complete `asset_evidence_v1` derivation graph against Python-produced signatures and real HTTPS publication. It imports no Python validator, crypto code or subprocess. This supplies the remaining independent asset-client evidence for M5. The M6 consolidated review edition and complete current-source checks are now prepared; integrated four-perspective review and release operations remain required; the public protocol stays a developer preview.

## Components and trust

- [asset-verifier.mjs](../verification/asset-verifier.mjs) validates closed manifests, representations, publication operations, native policy, signed authority and complete graph dependencies. It reuses independently written Node JSON, ES256, URL and authority-shape primitives. The authority-shape helper now permits explicit historical validation without weakening its default current-time checks.
- [asset-client.mjs](../verification/asset-client.mjs) is a credential-free reference HTTPS client. Its configured origins and separately retained pins supply trust. It fetches the bundle, independently observes each configured origin, verifies the graph, retrieves the original signed publications from their version URLs, and verifies again under fresh origin observations. Optional byte probes compare explicitly configured public fixture resources.
- The private local journal retains exact origin documents, origin revisions, policy lineage and verified immutable version pins across fresh processes. It is an operator-local trust store, not a portable protocol evidence export. The client uses an exclusive lock, atomic replacement and file/directory synchronization. A crashed process can leave a lock: an operator must establish that no process still owns it before removing it. There is no automatic age-based takeover.

The pure verifier accepts trusted in-process `currentSnapshots`, `pinnedDocuments` and optional `manifestPins`. Snapshots must come from an independent trusted acquisition adapter; a caller must never construct them by promoting bundled documents. The HTTPS client creates these inputs itself from configured origins and its private journal. Its origin reads require verified TLS, exact routes and HTTP 200. Authority declares `Cache-Control: no-store`; both origin requests send explicit revalidation headers and reject Age/cache warnings. Observation time is captured before acquisition, and retrieval beyond five seconds fails before entering retained history. Redirects, ambiguous response metadata/framing, unexpected media/coding, unavailable endpoints, oversize bodies and expired acquisition freshness fail. All requests are GETs without Authorization or Cookie headers.

Authority observation occurs before and after public-version acquisition and again after optional byte comparison. A valid newly observed origin revision is retained even when the graph then fails, so a later old revision cannot hide an observed withdrawal or compromise. Previously pinned revoked public material remains disqualifying after the current origin removes the key or gives the same material another key ID. Historical active signatures can survive later retirement or current authority expiry, but their current publication authority is reported separately. This is historical consistency, not an independently witnessed signing timestamp.

## Exactly what is checked

Every root and parent reference binds asset ID, immutable version ID, exact signed payload digest and representation UUID. Every publisher has separately observed current origin knowledge and independently pinned historical authority/policy bytes. The verifier checks publication capability, direct versus explicit delegated service, issuer, key role and lifecycle, endpoint, resource/action/purpose scope and asset/version prefixes at the asserted publication time.

The declared graph must close completely: missing or extra documents, missing or unreachable manifests, duplicate logical references, conflicting immutable versions, future parents, self-reference, cycles, absent representations and altered copy metadata/length/digest are rejected. All declared edges of every reached manifest are checked, including edges belonging to another representation of the same version. Limits match the native profile: 32 manifests, eight derivation levels, 64 documents, 64 roots and 8 MiB bundle bytes. Shared subgraphs are revisited only when reached at a greater depth; verification does not expand every duplicate path exponentially.

`compareAssetBytes` separately checks supplied decoded bytes, optional encoded bytes and observed representation metadata. The HTTPS adapter implements only identity and gzip decoding, with an 8 MiB decoded-body bound. A published gzip variant has its own encoded length/digest. An unlisted encoding variant remains unconfirmed even when supplied decoded bytes match. A range cannot match a complete representation merely by supplying a slice hash.

Successful results always preserve:

```json
{
  "resource_permission_granted": false,
  "downstream_use_verified": false,
  "derivation_computation_verified": false
}
```

Local byte comparison does not prove remote receipt, downstream model use, that a transform was computed correctly, that all actual sources were disclosed, or that the source was licensed for that use. Publication authority does not turn a policy prohibition into access permission.

## Run the client

Use Node 24 or a compatible runtime supporting the native APIs used here. No npm dependency is required. Run from this source checkout:

```sh
node verification/asset-client.mjs \
  --config /private/operator/asset-config.json \
  --state /private/operator/asset-journal.json
```

The journal's parent directory must be owned by the operator with mode `0700`; configuration and existing journal files must be owned regular files with mode `0600`, one hard link and no symlink. The configured CA file is a separately supplied public trust anchor. The JSON configuration has these exact fields:

| Field | Input |
| --- | --- |
| `origins` | Explicit allowlist of independently trusted publisher origins; at most 32 |
| `bundle_url` | Operator-selected HTTPS URL for the asset bundle on one configured origin |
| `expected_roots` | Nonempty list of exact qualified root references expected by the caller |
| `ca_file` | Local path to the CA certificate used to authenticate these origins |
| `document_pins` | Object mapping exact digest to base64url original historical origin document bytes, independently provisioned; may be empty for current publication |
| `manifest_pins` | Previously trusted immutable version URL to payload digest mapping; may initially be empty |
| `byte_probes` | Optional list, or `[]`; each entry has exactly `url`, `reference` and `metadata` (`media_type`, `media_parameters`, `languages`) |

The journal's origin set cannot silently change. Use a separate explicitly configured journal when changing the trust domain. Incoming bundles cannot add origins, keys or history pins to the configuration. Retained origin-document and version limits are 512 and 4,096 respectively; exceeding a limit fails rather than silently dropping compromise or version history.

The `/asset-evidence.json` and `/asset-bytes/*` routes used by tests are explicitly configured fixture routes, not new required protocol discovery paths. The client deliberately verifies live original version URLs; its availability requirement is additional to the pure offline graph verifier. It is an evidence-interoperability reference, not an Agent Core crawler, access-negotiation client, universal decoder, protected-content gateway or verifier of every agreement/archive envelope. Those capabilities retain their separate documented implementations and tests. Private journal ownership does not protect against a privileged local administrator replacing the journal.

## Reproduce and inspect the evidence

```sh
node --test verification/test_asset_verifier.mjs
ODEXA_NODE="$(command -v node)" \
  ODEXA_ASSET_NODE_REPORT_DIR=verification/local-asset-node-run \
  python -m unittest discover -s tests -p test_asset_node.py -v
```

The [51 native semantic cases](../verification/asset-node-semantic-tests.txt) cover independent signatures/trust, direct/delegated and cross-origin graphs, exact payload bytes, complete closure and representation comparison. The [complete seven-suite Node regression](../verification/asset-node-regressions.txt) passes **191 tests**, including the previous 140 free/paid/storage/duty checks.

The [final wire regression](../verification/asset-node-wire-regressions.txt) passes **21 Python harness methods**: nine new asset methods, eight paid-client methods and four existing free/storage methods. The [asset harness](../tests/test_asset_node.py) invokes **22 fresh native Node processes** across nine saved scenario reports. All nine scenarios exercise real certificate-verified HTTPS; expected failures are deliberate hostile cases, not failed tests. The cross-origin case is also accepted independently by the Python graph verifier before Node verification. Reports retain outcomes, not client credentials or private keys. The older eight-method `asset-node-wire-tests.txt` is intermediate evidence before the immutable-version restart case and added same-revision mutation check; the final 21-method run is authoritative for this checkpoint. Overlapping counts are not additive.

The [compatibility inventory](../verification/asset-node-checks.json) records exact source/evidence hashes and unchanged Python runtime/schema/installed-wheel bytes. No wire schema or public release changed. At that M5 checkpoint the last complete Python suite was the historical M2 run of 571 methods. The subsequent M6 run below establishes the current complete-suite result; requested independent review and release operations remain.

See [native asset contract](ASSET-EVIDENCE.md), [completion plan](RELEASE-COMPLETION-PLAN.md) and [verification report](../verification/REPORT.md).

## M6 consolidation correction

The [complete M6 Python run](../verification/m6-python-tests.txt) passes 625 methods, including all ten asset wire methods and 24 fresh asset-client processes. The added slow/cached-origin test verifies that observation time is captured before retrieval, acquisitions over five seconds are refused and cache warnings/age do not enter trusted history. The final M6 [Node run](../verification/m6-node-tests.txt) still passes 191 cases. Earlier M5 counts above describe that earlier checkpoint; the [release audit](RELEASE-AUDIT.md) is current.
