# Custody Record Validator Validates the non-secret platform-root custody record defined by `canon/schemas/custody-record_v0.1.schema.json` (NK-ADR-0017, status accepted; NK-WP-0045-T02/T03). Offline and read-only. ```bash uv run tools/custody-record/validate.py tools/custody-record/fixtures/valid/*.json uv run pytest tools/custody-record/tests ``` Beyond the schema it checks: one active grant per exact `(issuer, subject)`, revision ordering and supersession links inside a record, append-only successors across reads (`successor_errors`: an older revision is a stale read, a removed entry is not a revocation), and secret-looking values. `observe_root_grant` is the consumer rule from ADR-0017 section 3: a grant only for tenant `platform`, from a valid record read at most 15 minutes ago, otherwise no grant (fail closed). Observations include `expires_at`, derived from the actual read time and the record's cache bound, so downstream consumers can honor bounds below 900 seconds. Pass the last trusted document as `previous_record` to `observe_root_grant` to reject rollback or same-revision mutation. New custody actions cannot be inserted into already observed revisions. Read/check times must be timezone aware; future document/grant timestamps yield no grant. The CLI emits only acceptance or error counts, because schema diagnostics can contain rejected secret values. Library error strings are internal diagnostics and must not be logged against untrusted inputs. Runtime owners must also authenticate the source, bind the expected record ID, retain the revision baseline, and re-read for sensitive writes. See the [approved read contract](../../docs/contracts/root-grant-read-contract_v0.1.md) for delivery requirements and cross-repo acceptance receipts. Fixtures: `valid/` covers grant, revocation and supersession; `invalid/` covers projection-only grants, tenant-admin implication, other tenants, secret-bearing fields and values, username keys, an over-long cache bound, duplicate active grants and an entry both revoked and superseded.