# Data dictionary — `dataset.csv` (N=577)

Companion to `docs/research/0617-registry-quality-widen/dataset.csv`. Every column,
its type, its origin in the 8004scan.io `/api/v1/agents` response, and its null
semantics. Also documents two 8004scan API quirks a third party reproducing this
dataset MUST know — those quirks are the reason the sampling design (in `METHOD.md`)
looks the way it does.

## Row identity

One row per unique `(chain_id, token_id)` pair. The dataset is deduplicated on that
tuple across strata, so if the same agent surfaces in two overlapping anchor slices
it appears only once. N = 577 rows.

## Columns

| # | Column | Type | Source field | Null semantics |
|--:|---|---|---|---|
| 1 | `stratum` | string | assigned by sampler | Sampling stratum label of the form `<chain_label>@<anchor_frac>` (e.g. `ethereum@0.50`). Always populated. |
| 2 | `chain_label` | string | assigned by sampler | Human-readable chain name: `ethereum`, `base`, `bnb`, `optimism`, `arbitrum`. Always populated. |
| 3 | `anchor_frac` | float | assigned by sampler | The linear fraction (0.00 / 0.25 / 0.50 / 0.75 / 1.00) between the chain's oldest and newest `created_at` that this stratum was anchored at. Always populated. |
| 4 | `chain_id` | integer | list response `chain_id` (EIP-155 numeric chain id) | 1 (Ethereum), 8453 (Base), 56 (BNB), 10 (Optimism), 42161 (Arbitrum). Always populated. |
| 5 | `token_id` | integer (as string) | list response `token_id` | ERC-8004 identity NFT token id on that chain. Always populated. |
| 6 | `created_at` | ISO-8601 timestamp (UTC) | list response `created_at` | Timestamp the index recorded for the identity mint. Always populated in this sample (the sampler discards rows without `created_at`). |
| 7 | `name` | string | list response `name` | Display name from parsed metadata. May be a placeholder like `Agent #N`; may be empty for agents whose metadata does not surface a `name`. |
| 8 | `x402_supported` | boolean (`True`/`False`) | list response `x402_supported` | Whether the index detected x402 support in the parsed metadata. |
| 9 | `is_verified` | boolean (`True`/`False`) | list response `is_verified` | 8004scan's own verification flag for identity — 0 records true in this sample. |
| 10 | `is_endpoint_verified` | boolean (`True`/`False`) | list response `is_endpoint_verified` | 8004scan's flag for whether at least one endpoint was probed and answered — 1 record true in this sample (Arca, chain 42161, token 10). |
| 11 | `quality_score` | float | list response `quality_score` | 8004scan-computed quality score, 0–100 scale. `0.0` means "index rated it zero", not null. |
| 12 | `metadata_completeness_score` | float | list response `metadata_completeness_score` | 8004scan-computed metadata completeness, 0–100 scale. `0.0` means "index rated it zero", not null. |
| 13 | `total_score` | float | list response `total_score` | 8004scan-computed composite score, roughly 0–20 in practice. `0.0` means "index rated it zero", not null. |
| 14 | `supported_protocols` | comma-separated string | list response `supported_protocols` (an array, joined with `,`) | Empty string means the array was empty or the field was null. Common values: `A2A`, `OASF`, `Web`, `Email`, `MCP`. |
| 15 | `services_null` | boolean | derived from list response `services` | `True` iff the `services` array was null or empty. |
| 16 | `capabilities_null` | boolean | derived from list response `capabilities` | `True` iff the `capabilities` array was null or empty **as captured by 8004scan on its `last_parsed_at`**. `True` on 577/577 in the index snapshot. Task 0624 source-verified 43 rows and found 4 of the 18 that resolved to a live card carry a non-empty `capabilities` at the URL 8004scan already recorded — see `docs/research/0624-c2-provenance/README.md`. Treat this column as an index-cached hint at unknown age, not as ground truth about the agent. |
| 17 | `agent_url_null` | boolean | derived from list response `agent_url` | `True` iff `agent_url` was null or empty string **as captured by 8004scan on its `last_parsed_at`**. `True` on 577/577 in the index snapshot. Same provenance caveat as `capabilities_null` applies; Task 0624 did not source-verify `agent_url` specifically but the same one-shot-parse mechanism produces the null. |
| 18 | `a2a_endpoint_null` | boolean | derived from list response `a2a_endpoint` | `True` iff `a2a_endpoint` was null or empty string. |
| 19 | `mcp_server_null` | boolean | derived from list response `mcp_server` | `True` iff `mcp_server` was null or empty string. |
| 20 | `endpoint_less` | boolean | derived: `services_null AND capabilities_null AND agent_url_null AND a2a_endpoint_null AND mcp_server_null` | `True` iff ALL five endpoint fields are null/empty — the buyer-agent has nothing to call. |
| 21 | `callable` | boolean | derived: `NOT endpoint_less` | `True` iff at least one of the five endpoint fields carries a value. |
| 22 | `services_first` | string | derived: first URL-shaped element of the `services` array, if any | Empty string when `services_null` is `True`. |
| 23 | `capabilities_first` | string | derived: first URL-shaped element of the `capabilities` array, if any | Empty on 577/577 rows (this is the C2 held claim). |
| 24 | `agent_url_val` | string | list response `agent_url` (verbatim, empty for null) | Empty on 577/577 rows (this is the C2 held claim). |
| 25 | `a2a_endpoint_val` | string | list response `a2a_endpoint` (verbatim, empty for null) | Empty when `a2a_endpoint_null` is `True`. |
| 26 | `mcp_server_val` | string | list response `mcp_server` (verbatim, empty for null) | Empty when `mcp_server_null` is `True`. |

## Definitions used in the dataset

- **endpoint-less** — all five of `services`, `capabilities`, `agent_url`,
  `a2a_endpoint`, `mcp_server` are null or empty. A buyer-agent hitting the record
  has no URL-shaped hook to call.
- **callable** — at least one of those five fields carries a URL-shaped value. Note:
  this only says a URL is *published*; it does not say the URL resolves or answers.
  `is_endpoint_verified` is 8004scan's separate probe result (1/577 true here).
- **hard zero** — a value of exactly 0.0 on `total_score` / `quality_score` /
  `metadata_completeness_score` is a score the index assigned, not a missing field.
  These columns are never null in the dataset.
- **null on 100 %** — for `capabilities` and `agent_url`, both the `_null` boolean is
  `True` on every row of the 8004scan snapshot AND the corresponding value column
  (`capabilities_first`, `agent_url_val`) is the empty string on every row of the
  8004scan snapshot. Task 0624 falsified the simple reading of C2 by fetching each
  agent's live card at the URL 8004scan itself captured: 4 of 18 resolvable cards
  carry a non-empty `capabilities`. So "null on 100 %" is exact **for the index
  snapshot**, and not exact for the underlying agents.

## Chain distribution

| `chain_id` | `chain_label` | N |
|---:|---|---:|
| 1 | ethereum | 125 |
| 8453 | base | 125 |
| 56 | bnb | 125 |
| 10 | optimism | 77 |
| 42161 | arbitrum | 125 |
| **Total** | | **577** |

Optimism is short of 125 because the chain's total record count (531 as reported by
the index at the time of sampling) meant one of the five uniform anchor slices
overlapped nearly-empty tail regions and only yielded 2 unique rows after
deduplication. This is documented, not corrected — the resulting sample is still
uniform across time, it is just thinner at one anchor for one chain.

## API quirks a third party reproducing this MUST know

Both of these were established in task 0616 (`docs/research/0616-registry-endpoint-gap/`)
and re-relied-on for 0617. They are themselves useful citable facts about the
8004scan public API surface.

### Quirk 1 — `chain` is silently ignored; use `chain_id`

`/api/v1/agents?chain=base` returns the unfiltered global list, NOT the Base-only
list. The correct filter parameter is the EIP-155 numeric id:
`/api/v1/agents?chain_id=8453`. The `chain` string form returns HTTP 200 with a
plausible-looking response body — there is no error, so a naive scraper will report
a "Base sample" that is actually the multi-chain firehose. This is why every URL in
`summary.json.source.list_urls_used` uses `chain_id`.

### Quirk 2 — only `sort_by=created_at&sort_order=asc` is honoured

Attempted alternatives that all silently return the default (`created_at desc`)
listing with no error:

- `sort=created_at`
- `order=asc`
- `offset=N`
- `page=N`
- `skip=N`
- `start=N`

The API accepts these query parameters (HTTP 200) but does not act on them. The
canonical pair is `sort_by=created_at&sort_order=asc`; the only way to page past
the head is via the `cursor` parameter it returns. `sort_order=desc` also works
(and is the implicit default). This is why the sampling design forges base64
`cursor` payloads at target `created_at` anchors rather than using `offset` — see
`METHOD.md` § "Cursor forging".

## Companion files

- `dataset.csv` — the 577 rows
- `summary.json` — audit trail: full request URLs, cursor payloads, per-stratum age
  spread, budget used, delta vs the 0616 head-only sample
- `GRADE.md` — the three pre-pinned claims and the grade (CONFIRMED — C1 failed,
  **C2 held**, C3 failed)
- `METHOD.md` — sampling design + limitations
- `dataset.jsonld` — schema.org `Dataset` metadata block (for the planned
  `/data/...` publication page)
- `../../drafts/0617-erc8004-registry-endpoint-gap.md` — the post carrying the C2
  headline
- `../../drafts/0619-dataset-ship-spec.md` — the not-yet-executed ship spec

## License

CC BY 4.0 — see `METHOD.md` § "License".
