nexart.iodocs

    Governed Attestation Contract (Node 0.30.1)

    Exact governed /api/attest request and response, receipt and verification-envelope signature procedures, stateless verification, persistence and visibility for Node 0.30.1.

    Every field on this page comes from Node production source and executable tests for the released Node 0.30.1 source, with @nexart/governed-execution@0.4.2 as the package contract. Placeholders in angle brackets mark dynamic values only. No fields from AI Execution CERs are included. See the source ledger.

    Two different request wrappers

    RouteAuthBody
    POST /api/attestAuthorization: Bearer <api_key>The complete sealed CER itself, top-level
    POST /v1/cer/verifyNone{ "bundle": <sealed CER> }
    MUST NOT
    Wrap the CER in { bundle } for /api/attest, or send it unwrapped to /v1/cer/verify.

    Attestation request

    PropertyContract
    Method and pathPOST /api/attest
    Content typeapplication/json
    FamilyExact bundleType: "cer.governed.execution.v1"
    CER version"0.1" or "0.2". Not the Node version, SDK version or AI protocol version.
    Logical execution identityevidence.bundle.bundleId, not threadId
    Signer selectionThe Node selects the key. Neither kid nor nodeId is sent.
    X-NexArt-VisibilityOptional; exactly public or private. hidden is not an accepted header value. Omitted: existing proof's visibility, otherwise hidden.
    X-NexArt-Readable-FieldsRejected for governed records with READABLE_FIELDS_UNSUPPORTED. Consequential-family feature only.
    POST /api/attest
    Authorization: Bearer <TEMPORARY_API_KEY>
    Content-Type: application/json
    Redacted v0.1 request body (derived from tests/fixtures/governed-static-mutual-cycle.json)
    {
      "bundleType": "cer.governed.execution.v1",
      "createdAt": "<RFC3339_CREATED_AT>",
      "version": "0.1",
      "profile": {
        "id": "cage-governance-v1",
        "version": "1",
        "contract": "urn:cage:governance:v1:attestation-bundle",
        "source": { "system": "CAGE", "profile": "provider_02" }
      },
      "evidence": {
        "bundle": {
          "bundleId": "<UUID_BUNDLE>",
          "threadId": "<CORRELATION_ID>",
          "startedAt": "<RFC3339_START>",
          "completedAt": "<RFC3339_COMPLETION>",
          "terminalPath": "happy_path",
          "steps": [
            {
              "stepId": "<UUID_STEP_1>",
              "nodeName": "execution_analyst",
              "parentStepIds": [],
              "timestampUtc": "<RFC3339_STEP_1>",
              "durationMs": 1,
              "signals": {},
              "metadata": {},
              "stateHash": "<64_LOWERCASE_HEX>"
            },
            {
              "stepId": "<UUID_STEP_2>",
              "nodeName": "evaluator",
              "parentStepIds": ["<UUID_STEP_1>"],
              "timestampUtc": "<RFC3339_STEP_2>",
              "durationMs": 1,
              "signals": {},
              "metadata": {},
              "stateHash": "<64_LOWERCASE_HEX>"
            }
          ]
        },
        "topology": {
          "nodes": ["evaluator", "execution_analyst"],
          "parentEdges": {
            "evaluator": ["execution_analyst"],
            "execution_analyst": ["evaluator"]
          },
          "terminalNode": "evaluator"
        }
      },
      "protectedSet": {
        "stabilitySchemeId": "jcs-v1",
        "protectedSetId": "nexart.governed.execution.v1.protected-set.v1",
        "protectedFields": ["bundleType", "createdAt", "version", "profile", "evidence"]
      },
      "certificateHash": "sha256:<64_LOWERCASE_HEX>"
    }

    Processing order: JSON parser, structure guard, API key, header checks, quota, family recognition, owner-scoped replay lookup, SDK verifyGovernedExecution (rejected unless ok === true), mutation and replay handling, transactional signing and persistence. The Node does not replace the submitted certificateHash.

    Source-default limits

    • HTTP JSON body: 8 MiB default, configurable via MAX_JSON_BODY up to 32 MiB.
    • Structure guard: depth 64, 100,000 total JSON nodes, 10,000 items per container (413 REQUEST_STRUCTURE_TOO_COMPLEX).
    • SDK evidence: depth 32, 4,096 array items, 4,096 object keys, 1 MiB canonical evidence.
    • CAGE SDK: 256 steps, 1,024 topology nodes, 4,096 topology edges.
    • Monthly account quota applies. /api/attest does not mount the authenticated per-minute limiter.

    These are source defaults and caps, not observations of production configuration.

    Successful response (HTTP 200)

    Redacted v0.1 response, topology omitted, TSA disabled
    {
      "ok": true,
      "bundleType": "cer.governed.execution.v1",
      "certificateHash": "sha256:<CERTIFICATE_HEX>",
      "attestationId": "<UUID_ATTESTATION>",
      "nodeRuntimeHash": "sha256:<RUNTIME_HEX>",
      "protocolVersion": "0.1",
      "issuedAt": "<RFC3339_ATTESTED_AT>",
      "kid": "<KID>",
      "attestorKeyId": "<KID>",
      "attestation": {
        "attestedAt": "<RFC3339_ATTESTED_AT>",
        "attestationId": "<UUID_ATTESTATION>",
        "bundleType": "cer.governed.execution.v1",
        "certificateHash": "sha256:<CERTIFICATE_HEX>",
        "nodeRuntimeHash": "sha256:<RUNTIME_HEX>",
        "protocolVersion": "0.1",
        "requestId": "<UUID_ATTESTATION>",
        "verified": true,
        "checks": ["governed_execution"]
      },
      "receipt": {
        "receiptVersion": "attestation.receipt.v1",
        "attestationId": "<UUID_ATTESTATION>",
        "certificateHash": "sha256:<CERTIFICATE_HEX>",
        "nodeRuntimeHash": "sha256:<RUNTIME_HEX>",
        "protocolVersion": "0.1",
        "attestedAt": "<RFC3339_ATTESTED_AT>",
        "timestampType": "node-issued",
        "nodeId": "<NODE_ID>",
        "attestorKeyId": "<KID>"
      },
      "signature": "<BASE64URL_RECEIPT_SIGNATURE>",
      "verificationEnvelope": {
        "algorithm": "Ed25519",
        "attestation": {
          "attestationId": "<UUID_ATTESTATION>",
          "attestedAt": "<RFC3339_ATTESTED_AT>",
          "kid": "<KID>",
          "nodeRuntimeHash": "sha256:<RUNTIME_HEX>",
          "protocolVersion": "0.1"
        },
        "canonicalization": "jcs",
        "envelopeType": "nexart.verification.envelope.v2",
        "kid": "<KID>",
        "scope": "whitelist",
        "signedFields": {
          "attestation": ["attestationId", "attestedAt", "kid", "nodeRuntimeHash", "protocolVersion"],
          "bundle": ["bundleType", "version", "createdAt", "profile", "evidence", "protectedSet"]
        },
        "specVersion": "v0.16.1"
      },
      "verificationEnvelopeSignature": "<BASE64URL_ENVELOPE_SIGNATURE>",
      "visibility": "hidden",
      "governedVerification": {
        "certificateIntegrity": "valid",
        "cageSchemaValidity": "valid",
        "causalGraphValidity": "valid",
        "topologyValidation": "not-supplied",
        "resourceSafety": "valid",
        "stateHashVerification": "not-performed",
        "producerAuthentication": "not-performed",
        "completeness": "not-claimed",
        "warnings": []
      },
      "verificationUrl": "https://verify.nexart.io/c/sha256%3A<CERTIFICATE_HEX>"
    }
    FieldMeaning
    oktrue
    certificateHashThe caller's sealed hash, unchanged
    attestationIdNode-generated UUID; equals attestation.requestId on the fresh path
    nodeRuntimeHashsha256:<hex> of the canonical Node runtime descriptor
    protocolVersionMirrors the CER version: "0.1" or "0.2"
    issuedAtNode attestedAt
    kid, attestorKeyIdThe selected signing key ID
    attestation.checksArray ["governed_execution"]. Not the stateless checks object.
    receipt, signatureReceipt and its base64url Ed25519 signature (procedure)
    verificationEnvelope, verificationEnvelopeSignaturev2 descriptor and projection signature (procedure)
    visibilityActual local visibility: hidden, public or private
    governedVerificationNine-key report (fields)
    verificationUrlFormatted string. Not a read-after-write publication check.
    timestampOptional; present only when a TSA is configured

    Absent from this response: the CER, top-level status, top-level nodeId (it is inside the receipt), receiptSignature, signatureEncoding, top-level signedFields (inside the envelope), persisted, published, publicRecordId and readableFields. For the cyclic-topology request above, topologyValidation is "valid".

    With a configured, successful RFC 3161 TSA, the receipt adds timestampStatus: "ok" and timestampProvider, timestampType becomes "rfc3161", and the response adds:

    {
      "timestamp": {
        "type": "rfc3161",
        "status": "ok",
        "provider": "<TSA_PROVIDER>",
        "token": "<BASE64_DER_TOKEN>"
      }
    }

    Token bytes are outside the certificate, receipt and envelope signed perimeters. No production TSA configuration is implied.

    Replay and mutation

    • An exact owner-authorized replay returns the original response with X-Idempotent-Replay: true. No new identity is minted.
    • The same bundleId with a different valid hash returns 409 EXECUTION_MUTATION_DETECTED.

    Failure responses

    HTTPCondition
    400Invalid JSON or shape, unsupported family, invalid visibility or readability headers
    400SDK rejection: { "error": "INVALID_BUNDLE", "reasonCode": "<SDK_CODE>", "details": ["<SDK_ERROR>"] }
    401Missing, invalid or inactive Bearer API key (UNAUTHORIZED)
    404Owner or privacy authorization failure, indistinguishable from a nonexistent record
    409Logical execution mutation, visibility conflict, conflicting replay
    413PAYLOAD_TOO_LARGE or REQUEST_STRUCTURE_TOO_COMPLEX
    429Monthly QUOTA_EXCEEDED; the failed-authentication limiter may also reject
    503Auth database unavailable under fail-closed metering; bounded lock or service-unavailable paths
    500Persistence, signing or internal failure. Do not assume success.

    Error shapes are branch-specific, not a uniform object.

    Receipt signature verification

    The receipt contains, in construction order:

    receiptVersion, attestationId, certificateHash, nodeRuntimeHash,
    protocolVersion, attestedAt, timestampType, nodeId, attestorKeyId
    (+ timestampStatus, timestampProvider when supplied)

    Every field actually present in the receipt is signed. There is no signed-field subset.

    bytes     = UTF8(canonicalize(receipt))     // @nexart/ai-execution toCanonicalJson
    signature = base64url(Ed25519.sign(bytes))  // URL-safe, no padding

    The canonicalizer sorts object keys, preserves array order, uses JSON string escaping and ECMAScript number serialization, emits no whitespace, rejects non-finite numbers and omits undefined members. Do not normalize Unicode, sort arrays or substitute ordinary JSON serialization.

    1. Obtain receipt and signature.
    2. Read receipt.attestorKeyId; cross-check with response kid and verificationEnvelope.attestation.kid.
    3. Resolve that exact kid in keys[] of GET /.well-known/nexart-node.json, not keys[0] or activeKid. Rotated or deprecated keys can verify older signatures.
    4. Build the Ed25519 public key from publicKeyJwk, publicKeySpkiB64 or the raw 32-byte base64url publicKey (SPKI DER prefix 302a300506032b6570032100).
    5. Apply key lifecycle policy: reject unknown or revoked keys, enforce explicit validity windows. A boot-time notBefore on the active key is not an authoritative lower bound for old records.
    6. Base64url-decode the signature and verify Ed25519 over the canonical receipt bytes.
    7. Cross-check certificateHash, attestationId, nodeRuntimeHash and protocolVersion against the CER and envelope.
    verify-receipt.ts
    import { createPublicKey, verify } from "node:crypto";
    import { toCanonicalJson } from "@nexart/ai-execution"; // the canonicalizer the Node uses
    
    const NODE = process.env.NEXART_NODE_ENDPOINT; // e.g. https://node.nexart.io
    const { receipt, signature, kid } = attestResponse;
    
    // 1-2. Key ID comes from the receipt; cross-check the response and envelope.
    const receiptKid = receipt.attestorKeyId;
    if (receiptKid !== kid || receiptKid !== attestResponse.verificationEnvelope.attestation.kid) {
      throw new Error("kid mismatch");
    }
    
    // 3. Resolve that exact kid. Never use keys[0] or activeKid by position.
    const discovery = await (await fetch(`${NODE}/.well-known/nexart-node.json`)).json();
    const key = discovery.keys.find((k) => k.kid === receiptKid);
    if (!key || key.revoked || key.status === "revoked") throw new Error("unknown or revoked key");
    
    // 4. Raw 32-byte base64url key -> SPKI DER with the Ed25519 prefix.
    const spki = Buffer.concat([
      Buffer.from("302a300506032b6570032100", "hex"),
      Buffer.from(key.publicKey, "base64url"),
    ]);
    const publicKey = createPublicKey({ key: spki, format: "der", type: "spki" });
    
    // 6. Ed25519 over UTF-8 canonical JSON of the WHOLE receipt.
    const bytes = Buffer.from(toCanonicalJson(receipt), "utf8");
    const receiptOk = verify(null, bytes, publicKey, Buffer.from(signature, "base64url"));
    if (!receiptOk) throw new Error("receipt signature invalid");
    
    // 7. Bind the receipt to the CER you hold.
    if (receipt.certificateHash !== cer.certificateHash) throw new Error("hash mismatch");
    if (receipt.protocolVersion !== cer.version) throw new Error("version mismatch");

    A valid receipt signature proves that the resolved Node key signed these receipt facts and bound the certificate hash to a Node-issued time and runtime descriptor. It does not prove state preimages, execution truth, producer authentication, completeness, public publication or independent wall-clock authority. Only a separately verified RFC 3161 token adds external time evidence.

    Verification-envelope signature verification

    Descriptor fields: algorithm (Ed25519), attestation, canonicalization (jcs), envelopeType (nexart.verification.envelope.v2), kid, scope (whitelist), signedFields, specVersion (v0.16.1, an envelope specification label, not the Node version).

    CER versionattestation fieldsbundle fields
    "0.1"attestationId, attestedAt, kid, nodeRuntimeHash, protocolVersionbundleType, version, createdAt, profile, evidence, protectedSet, then context, contextSummary if present
    "0.2"Same fiveSame six, then identity, confidentiality if present, in that order

    The signed message is the canonical document of exactly this object:

    {
      "attestation": {
        "attestationId": "<UUID>",
        "attestedAt": "<RFC3339>",
        "kid": "<KID>",
        "nodeRuntimeHash": "sha256:<HEX>",
        "protocolVersion": "0.1"
      },
      "bundle": {
        "bundleType": "cer.governed.execution.v1",
        "version": "0.1",
        "createdAt": "<RFC3339>",
        "profile": "<COPY THE COMPLETE PROFILE OBJECT>",
        "evidence": "<COPY THE COMPLETE EVIDENCE OBJECT>",
        "protectedSet": "<COPY THE COMPLETE PROTECTED-SET OBJECT>"
      }
    }

    The last three slots are the original objects, not JSON-encoded strings. The wrapper has only attestation and bundle. certificateHash, meta, receipt, signature, the rendered envelope and unknown extras are excluded.

    MUST NOT
    Verify the signature over JCS(verificationEnvelope). That is the older envelope-v1 helper, not the governed /api/attest path, and produces invalid results.
    1. Retain the submitted sealed CER. /api/attest does not return it.
    2. Read verificationEnvelope.attestation and its kid.
    3. Validate family and version and use your own expected whitelist. Do not accept the returned signedFields as authority.
    4. Reconstruct { attestation, bundle } from the five attestation fields and the required plus actually-present optional bundle fields.
    5. Canonicalize with the same rules and verify verificationEnvelopeSignature with the key for that kid under your lifecycle policy.
    6. Verify the CER with Governed SDK 0.4.2 and verify the receipt. The projection omits certificateHash, so the hash binding needs those checks.
    verify-envelope.ts
    // Reuses publicKey resolution from the receipt procedure, keyed by
    // verificationEnvelope.attestation.kid.
    const env = attestResponse.verificationEnvelope;
    
    // 3. Use YOUR expected whitelist for this family/version, not env.signedFields.
    const ATTESTATION_FIELDS = ["attestationId", "attestedAt", "kid", "nodeRuntimeHash", "protocolVersion"];
    const BUNDLE_REQUIRED = ["bundleType", "version", "createdAt", "profile", "evidence", "protectedSet"];
    const BUNDLE_OPTIONAL = cer.version === "0.2" ? ["identity", "confidentiality"] : ["context", "contextSummary"];
    
    const pick = (obj, keys) => Object.fromEntries(keys.filter((k) => k in obj).map((k) => [k, obj[k]]));
    
    // 4. Reconstruct exactly { attestation, bundle }. No other wrapper keys.
    const signable = {
      attestation: pick(env.attestation, ATTESTATION_FIELDS),
      bundle: pick(cer, [...BUNDLE_REQUIRED, ...BUNDLE_OPTIONAL]),
    };
    
    // 5. Ed25519 over UTF-8 canonical JSON of the projection, NOT of the envelope descriptor.
    const envelopeOk = verify(
      null,
      Buffer.from(toCanonicalJson(signable), "utf8"),
      publicKey,
      Buffer.from(attestResponse.verificationEnvelopeSignature, "base64url"),
    );
    if (!envelopeOk) throw new Error("envelope signature invalid");
    // 6. certificateHash is NOT in the projection: also run verifyGovernedExecution(cer)
    //    and the receipt procedure to verify the hash binding.

    For Node-side checking, attach the descriptor and signature at bundle.meta.verificationEnvelope and bundle.meta.verificationEnvelopeSignature, then send { "bundle": bundle } to /v1/cer/verify. Outer-level placement is not equivalent. The route does not verify the receipt signature for you. Node-side checking is not independent verification.

    A valid envelope signature proves the resolved Node key signed this family projection and five attestation facts. It does not authenticate the producer, validate state preimages, guarantee complete capture or certify public visibility.

    governedVerification fields

    FieldValuesAlso in stateless checks
    certificateIntegrityvalid | invalid | not-performedYes
    cageSchemaValidityvalid | invalid | not-performedYes
    causalGraphValidityvalid | invalid | not-performedYes
    topologyValidationvalid | invalid | not-suppliedYes
    resourceSafetyvalid | invalid | not-performedYes
    stateHashVerificationnot-performedNo
    producerAuthenticationnot-performedNo
    completenessnot-claimedNo
    warningsstring array, e.g. TERMINAL_PATH_UNKNOWNNo

    The object has no ok, code, errors or details. SDK codes: OK, SCHEMA_ERROR, CAUSAL_ERROR, TOPOLOGY_ERROR, CERTIFICATE_HASH_MISMATCH, CANONICALIZATION_ERROR, RESOURCE_ERROR. The Node may add INVALID_STRUCTURE and a defensive GOVERNED_VERIFICATION_FAILED. Missing SDK fields default to not-performed, not-supplied or not-claimed, never to success.

    Stateless verification: POST /v1/cer/verify

    POST /v1/cer/verify
    Content-Type: application/json
    
    { "bundle": <complete sealed CER object> }
    • No Bearer header. Public, rate-limited (source default 60/min) and structure-guarded.
    • HTTP 200 for both verified and failed on a recognized family. HTTP 400 for a missing bundle or unsupported family.
    • computedCertificateHash equals the submitted hash only on SDK success, otherwise null.
    • Does not persist, mint a receipt or envelope, or publish. It may read stored TSA evidence and key metadata.
    • checks.signature is pass, fail or skipped.
    Verified: plain v0.1 CER, no topology, signature or TSA
    {
      "status": "verified",
      "reason": "All checks passed",
      "reasonCode": "OK",
      "checks": {
        "certificateIntegrity": "valid",
        "cageSchemaValidity": "valid",
        "causalGraphValidity": "valid",
        "topologyValidation": "not-supplied",
        "resourceSafety": "valid",
        "signature": "skipped"
      },
      "certificateHash": "sha256:<CERTIFICATE_HEX>",
      "submittedCertificateHash": "sha256:<CERTIFICATE_HEX>",
      "computedCertificateHash": "sha256:<CERTIFICATE_HEX>",
      "keyUsed": null,
      "keySource": null,
      "anchorStatus": { "checked": false, "valid": true },
      "timestampStatus": { "checked": false, "valid": true },
      "timestampVerdict": "NOT_PRESENT",
      "timestampType": null,
      "timestampProvider": null,
      "timestamp": { "type": "node-issued", "provider": null, "status": "untrusted" },
      "context": null,
      "classification": "VERIFIED",
      "assurance": null,
      "relaxations": [],
      "verifiedUnder": null,
      "code": "OK",
      "classificationReason": "All checks passed",
      "contentMode": null,
      "verifiedAt": "<RFC3339_VERIFICATION_TIME>",
      "node": "<NODE_ID>",
      "protocolVersion": "0.1",
      "governedVerification": {
        "certificateIntegrity": "valid",
        "cageSchemaValidity": "valid",
        "causalGraphValidity": "valid",
        "topologyValidation": "not-supplied",
        "resourceSafety": "valid",
        "stateHashVerification": "not-performed",
        "producerAuthentication": "not-performed",
        "completeness": "not-claimed",
        "warnings": []
      }
    }

    Failed example (abridged; unlisted fields match the verified shape). The malformed stateHash was inserted before sealing a synthetic test fixture so that integrity stays valid. This describes building a test input, not a reseal workflow.

    HTTP 200, status failed
    {
      "status": "failed",
      "reason": "bundle.steps[1].stateHash: lowercase 64-character hexadecimal required",
      "reasonCode": "SCHEMA_ERROR",
      "checks": {
        "certificateIntegrity": "valid",
        "cageSchemaValidity": "invalid",
        "causalGraphValidity": "valid",
        "topologyValidation": "not-supplied",
        "resourceSafety": "valid",
        "signature": "skipped"
      },
      "certificateHash": "sha256:<CERTIFICATE_HEX>",
      "submittedCertificateHash": "sha256:<CERTIFICATE_HEX>",
      "computedCertificateHash": null,
      "classification": "FAILED",
      "code": "SCHEMA_ERROR",
      "node": "<NODE_ID>",
      "protocolVersion": "0.1",
      "governedVerification": {
        "certificateIntegrity": "valid",
        "cageSchemaValidity": "invalid",
        "causalGraphValidity": "valid",
        "topologyValidation": "not-supplied",
        "resourceSafety": "valid",
        "stateHashVerification": "not-performed",
        "producerAuthentication": "not-performed",
        "completeness": "not-claimed",
        "warnings": []
      }
    }

    Persistence versus publication

    • A successful attestation is persisted in the Node's local cer_proofs store with the cleaned CER, receipt, signatures, envelope and governedVerification. execution_id is bundleId; sdk_version is null.
    • Default visibility is hidden. Hidden, private, deleted and missing records all return indistinguishable 404s on public reads.
    • Governed records are never forwarded to external CER ingest, even when local visibility is public.
    • An authenticated owner (Bearer API key that owns the record) may call POST /api/proofs/:certificateHash/unhide (local hidden to public) or POST /api/proofs/:certificateHash/visibility with { "visibility": "public" } or "private". Visibility is Node record state (visibility_state), not a CER field, so it is outside the protected hash projection. The transition creates no new CER, receipt or signature and the record resolves locally under the same certificate hash.
    • Node 0.30.1 supports an authenticated visibility transition that does not change the protected certificate hash. This source audit does not establish the external verifier's storage or lookup implementation.

    verificationUrl is formatted, not checked. Because the verify.nexart.io backend is outside the audited repository, a successful attestation, even with public local visibility, is not a guarantee that the URL resolves.

    Route status

    RouteStatusUse
    POST /api/attestCurrentAuthenticated governed attestation
    POST /v1/cer/verifyCurrentPublic, non-persisting verification
    GET /.well-known/nexart-node.jsonCurrentKey discovery
    GET /v1/resolve/cer/:certificateHashCurrentExact-hash public resolution of local records
    GET /v1/cer/publicCurrentPublic read by certificate_hash or execution_id
    GET /api/public-cer-proofCurrent, older surfacePublic proof read; not an attestation alias
    POST /api/proofs/:certificateHash/unhideCurrentOwner hidden to public
    POST /api/proofs/:certificateHash/visibilityCurrentOwner public/private management
    POST /verifyCurrent, legacy-shapedAuthenticated render/snapshot verification; not governed
    POST /v1/cage/bundlesCurrent, separate contractNative CAGE bundle registration
    POST /api/stampGoverned unsupportedRejects governed CERs
    POST /api/stamp-from-executionCached replayNot a governed reseal
    /certifyDecisionNonexistentSDK helper name, not an HTTP route
    /verify/{hash}NonexistentNot POST /verify or the external /c/ page
    /v1/attestation/cage-governanceNonexistentNo alias

    Absence statements come from route registration in source, not production probing.

    Key discovery

    GET /.well-known/nexart-node.json (default single active key)
    {
      "nodeId": "<NODE_ID>",
      "operator": "<OPERATOR>",
      "activeKid": "<ACTIVE_KID>",
      "keys": [
        {
          "kid": "<KID>",
          "alg": "Ed25519",
          "algorithm": "Ed25519",
          "use": "sig",
          "status": "active",
          "publicKey": "<BASE64URL_32_BYTE_PUBLIC_KEY>",
          "publicKeyJwk": { "kty": "OKP", "crv": "Ed25519", "x": "<BASE64URL_PUBLIC_KEY>" },
          "publicKeySpkiB64": "<BASE64_PUBLIC_SPKI>",
          "createdAt": "<RFC3339>",
          "revoked": false,
          "notBefore": "<RFC3339>",
          "validFrom": "<RFC3339>",
          "validTo": null
        }
      ]
    }

    Additional and deprecated keys may appear with deprecatedAt, notAfter and validTo. Status is active, deprecated or revoked. Always select by the signature's kid.