GAP (GX AI Protocol) Specification v0.1
Informative translation. The Japanese text is the normative specification. Where this translation and the Japanese text disagree, the Japanese text governs. The machine-readable schemas and test vectors are language-independent and normative in either case.
0. What this is #
A wire protocol for delivering a company's ESG data as raw activity data, signed at source, straight to an investor's AI agent over API and MCP — without passing through a PDF disclosure report.
The conventional structure:
core systems -> staff/consultants (aggregation, embellishment) -> PDF report -> analyst (re-keying) -> assessment
The GAP structure:
core systems/sensors -> [signature] -> GAP Node -> [MCP/REST] -> investor AI agent (computation and verification on the receiving side)
The three gaps in the name #
| GAP | What it is | How v0.1 closes it |
|---|---|---|
| Disclosure GAP | Divergence between disclosed figures and primary field data | Only activity data signed with the source's private key circulates, making alteration in transit mathematically detectable |
| Time GAP | Annual reporting against investors' real-time decision-making | Continuous ingest into an append-only stream (continuous auditing). Any window can be computed on demand |
| Agent GAP | Unstructured documents written for humans against the deterministic interfaces agents require | Deterministic computation and verification exposed as MCP tools. The same arguments always yield the same figure and hash |
The design in one line #
GAP does not carry emissions. It carries activity data, and the evidence that it is genuine.
Emissions are activity data times an emission factor, and the choice of factor is a judgement that varies by regime, standard and year. Letting the company settle that judgement first is where embellishment begins. GAP separates factor application out as an explicit choice on the receiving side, and asks the company for nothing but what it measured.
1. Layers #
1. Semantic data layer (standardising meaning) #
A single reading of activity data is expressed by the following payload. This is the byte string that gets signed.
{
"spec": "gap/0.1",
"source_id": "jp-demo-plant-chiba-meter-01",
"seq": 1,
"metric": "electricity_kwh",
"period_start": "2026-07-01T00:00:00Z",
"period_end": "2026-07-01T01:00:00Z",
"value": 812.4,
"unit": "kWh",
"quality": "measured",
"prev_hash": null,
"meta": { "line": "A", "tariff": "peak" }
}
Schema: reading-payload.schema.json
Rules:
- Exactly one timestamp format:
YYYY-MM-DDTHH:MM:SSZ(second precision, UTC, no milliseconds). Variation would make the signed byte string irreproducible, so millisecond precision and offset notation violate the specification. qualityis mandatory. Using estimated or corrected values is legitimate in itself, but the moment they are mixed in unlabelled, an unembellished stream becomes indistinguishable from an embellished one.seqincrements from 1 per source. A missing number remains as evidence of a deletion or a missed reading.- Zero-Embellishment Constraint: a payload whose
metric, or any key undermeta, contains a term denoting derived emissions (co2,co2e,ghg,emission,scope1|2|3,tco2,carbon_footprint) must not be accepted. GAP carries activity data only. - No unit conversion, rounding or correction may be performed in transit. The value the instrument produced is the value that travels.
2. Query and execution layer (MCP native) #
The MCP tools exposed to investor-side agents (all read-only and idempotent):
| Tool | Role |
|---|---|
list_sources | Discover primary data sources, including the granularity visible to the calling key |
get_activity_data | Raw activity data up to the permitted granularity, with provenance hashes |
list_emission_factors | Applicable factors, each with a citation URL and publication date |
compute_emissions | Activity data times an explicitly specified factor, computed deterministically |
verify_provenance | Recomputes signatures and the hash chain (returns no values) |
detect_anomalies | Statistical detection of gaps, missing seq, outliers and flatlines |
Context budget optimization
An agent's context should be spent on "the figure plus the grounds that it is correct", not on hauling raw logs.
- Responses are capped (1000 points by default); beyond that the node returns
too_many_pointsrather than silently truncating, forcing the caller to coarsen granularity. verify_provenancereturns only booleans and hashes — carrying correctness without carrying values.- Aggregated responses carry a
bucket_hash(SHA-256 over the constituent payload hashes), making it verifiable which raw data a figure was built from without revealing the values.
Where responsibility for the computation sits
compute_emissions returns no figure when the factor cannot be uniquely identified; if several
candidates match it returns factor_ambiguous with the list. This is specification, not inconvenience —
if a node could silently pick a factor, "whose judgement produced this number" would become unclear again.
3. Trust and identity layer (proof and permission) #
Canonicalization: a subset of RFC 8785. Object keys in ascending order, no whitespace, non-finite
numbers forbidden, -0 normalised to 0.
Hash: payload_hash = SHA-256(canonical_json) (lowercase hex).
Chain: prev_hash points at the preceding reading's payload_hash, forming an
append-only link; seq increases by one. Substituting or quietly deleting past readings shows up as a broken link.
Signature: Ed25519, over the UTF-8 bytes of the canonical JSON, attached as base64. The public key is handed to the node at source registration (its SHA-256 fingerprint is published).
Obligation to recompute: a node must not trust the verification flag recorded at ingest.
compute_emissions re-derives signatures and hashes immediately before computing, and if even one
reading is inconsistent it returns provenance_compromised instead of a figure. (An implementation that
trusts only the ingest-time flag will serve embellished figures while claiming they are verified, if the node's
storage is written to directly.)
Granular access control (the confidentiality boundary)
| disclosure_level | Who may read it | Granularity |
|---|---|---|
public | Every approved API key | Coarsened to min_bucket_seconds before disclosure (e.g. hourly metering disclosed daily) |
engagement | Only keys holding raw_access_grants | A grant may permit finer granularity |
restricted | Node internal only | — |
A factory's real-time electricity draw can be confidential, bearing directly on utilisation and unit cost. Rather
than forcing a choice between hiding and showing everything, GAP coarsens the time granularity while preserving
verifiability after the fact (bucket_hash). An engagement source is still visible as
existing to keys without a grant (access.granularity = "none"), because hiding it would remove the
entry point for engagement.
2. Endpoints #
| Method | Path | Auth |
|---|---|---|
| GET | /.well-known/gap.json | None (node discovery) |
| GET | /api/v1/gap/schema | None (JSON Schema bundle) |
| GET | /api/v1/gap/sources[/:id[/readings]] | API key |
| GET | /api/v1/gap/factors | API key |
| GET | /api/v1/gap/compute | API key |
| GET | /api/v1/gap/verify | API key |
| GET | /api/v1/gap/anomalies | API key |
| POST | /mcp/gap | API key (MCP Streamable HTTP) |
| GET | /api/gap/ingest/head | Node-issued ingest bearer |
| POST | /api/gap/ingest | Ingest bearer plus a per-reading Ed25519 signature |
Ingest authentication has two stages so that a leaked token still cannot rewrite the past. The token establishes who may connect; the signature establishes that the data is genuine.
3. Errors (normative) #
| code | Meaning |
|---|---|
embellishment_rejected | Payload contains derived emissions (violates the Zero-Embellishment Constraint) |
signature_invalid | Cannot be verified with the registered key |
chain_broken / seq_out_of_order | Does not connect to the head of the chain |
provenance_compromised | Stored data disagrees with its signature (computation refused) |
factor_ambiguous | The factor is not uniquely determined (candidates included) |
factor_not_found / unit_mismatch / factor_metric_mismatch | Invalid factor selection |
granularity_denied | Request finer than the permitted disclosure granularity |
grant_required / source_restricted | Outside the disclosure scope |
too_many_points / window_too_large | Response exceeds the cap (change granularity or window) |
Batch ingest is all-or-nothing. If even one reading is broken the whole batch is refused, and both
acceptances and refusals are recorded in the audit log (raw_ingest_log).
4. Reference implementation #
The reference implementation lives in a private repository, kokubee/gxceed, and is not
publicly accessible at this time. The paths below describe its structure; they are not links.
| Location | Contents |
|---|---|
workers/data-api/src/raw/provenance.ts | Canonicalization / hashing / chaining / signature verification |
workers/data-api/src/raw/ingest.ts | Signature-verifying ingest (implements the Zero-Embellishment Constraint) |
workers/data-api/src/raw/store.ts | Retrieval, aggregation, computation, verification, anomaly detection |
workers/data-api/src/mcp-gap.ts | MCP server (six tools plus a spec resource) |
workers/data-api/src/routes/gap.ts | REST bindings (same logic as MCP) |
scripts/gap/gap-sign.mjs | Signing library for the source side (no dependencies, Node 22) |
scripts/gap/demo-factory-meter.mjs | Demo data generation for a factory smart meter |
scripts/gap/e2e-local.mjs | Local end-to-end run (32 checks) |
scripts/health/gap-selftest.mjs | Canonicalization, hashing and signature vector verification |
node scripts/health/gap-selftest.mjs # self-check of the provenance layer
node scripts/gap/e2e-local.mjs # sign -> ingest -> compute -> verify -> MCP, end to end
The vectors are in test-vectors.json. The signing key is
the published RFC 8032 §7.1 TEST 1 key, deliberately a value that cannot be used in production.
5. Deliberately out of scope in v0.1 (open questions for v0.2) #
- Zero-knowledge proofs / differential privacy: confidentiality currently extends only to coarsening time granularity, and not as far as proving validity without revealing the raw data.
- Key rotation procedure:
public_key_hashis recorded on each reading so past readings can be re-verified, but the rotation protocol itself is undefined. - Cross-verification between nodes:
input_hashallows identical inputs to be matched to identical results, but there is no inter-node audit protocol. - Scope 3 / supply-chain chaining: referencing another company's node is undefined; v0.1 is closed over the sources within a single node.
- Streaming subscriptions: polling only. Push via webhook or SSE is undefined.
- Regime mapping (CSRD / SSBJ / GHG Protocol): GAP only separates activity data from factors; mapping to a given regime's line items is the recipient's responsibility.
6. The point of standardising #
If individual companies each implement their own MCP server, the integration cost on the investor side grows in proportion to the number of companies. GAP fixes only the following four things, leaving everything else to each implementer:
- The byte string that gets signed (the canonicalization rules)
- How the chain is formed (
prev_hash+seq) - The names and input/output semantics of the tools (six tools)
- The constraint that what leaves the company is activity data, not emissions
With those four in place, a single agent implementation on the investor side can connect to, verify against and compare across any company's GAP node using the same code.
Machine-readable schemas #
Each schema's $id points at this site, and $ref resolves here too. The provenance of the
distribution and the SHA-256 of each file are in manifest.json.
| File | Purpose |
|---|---|
reading-payload.schema.json | The signed unit of the protocol: one measurement period of raw activity data |
ingest-request.schema.json | Batch envelope sent from a primary source to a node |
source.schema.json | Public descriptor of a data source: what it measures, how often, under which key, at what disclosure granularity |
emission-factor.schema.json | A citable factor, kept deliberately outside the signed stream |
compute-result.schema.json | Shape of a computed figure, including everything needed to reproduce it independently |
test-vectors.json | Canonicalization, hashing, chaining and signature vectors |
Changing canonicalization, hashing or the chain rule invalidates every signature ever produced under this
version. Such a change requires a new spec value (v0.2), not an edit here.