# Compare declared coverage with observations

> Ask your agent to declare the services, route templates and screens read from code, then compare them with an exact window.

Canonical page: https://anectico.com/docs/investigate/coverage-map/


A coverage declaration says what your application should report. Your agent reads the code and
writes the inventory. Anectico compares it with what was observed for one project, environment,
application release and exact window. A result says **declared but not observed in this window**.
It cannot say why nothing arrived.

## Ask your agent

> "Read this application's routes, screens and service names. Show me a declaration for release
> v1.2.0 in production. Validate it first. After I approve it, save it and compare it with yesterday's
> traffic. What should be reporting and was not observed?"

Your agent uses `validate_coverage_declaration`, `get_coverage_declaration`,
`list_coverage_revisions`, `get_observed_coverage` and `compare_coverage` through
`execute_read_action`. The writes `declare_coverage` and `withdraw_coverage` go through
`execute_internal_action`: preview first, then repeat identical arguments with the confirmation
token. The CLI commands are `anectico coverage declare`, `show`, `revisions`, `observed`, `compare`
and `withdraw`. The CLI reads the file as data. It never parses or runs source code.

## The declaration file

Use JSON with exactly these three arrays. Each service or screen carries a `name`; each route
carries a `service`, `method` and `route_template`. A short `note` is optional on every entry.
Environment and release are command arguments, not fields in the file.

```json
{
  "services": [{"name": "shop-api", "note": "Public shop requests"}],
  "routes": [
    {"service": "shop-api", "method": "GET", "route_template": "/orders/{id}"},
    {"service": "shop-api", "method": "POST", "route_template": "/checkout"}
  ],
  "screens": [{"name": "Checkout"}, {"name": "/orders/{id}"}]
}
```

Read service names from the configuration that reports `service.name`. Read HTTP templates from
the router: `/orders/{id}`, `/orders/:id` and `/orders/<id>` describe a parameter. Never copy an
actual customer URL. Read mobile screen names from navigation, and web page templates from the
page router. A named mobile screen does not start with `/`.

There are at most **5,000 entries** in a **2 MiB** document and 10,000 revisions per project. Names, environment, release and
templates have at most **512 ASCII bytes**; notes have at most **200 ASCII bytes**. Operational
names start with a letter or number and use letters, numbers, `_`, `.`, `+` or `-`. Release also
accepts a bare 40- or 64-character hexadecimal software revision. Notes use ordinary letters,
numbers, spaces and limited punctuation: `. , _ : / ( ) -`.

Paths start with `/`. Static segments use letters, numbers, `_`, `.` or `-`; parameter names use
letters, numbers and `_`, starting with a letter or `_`. Empty or dot segments, query strings,
fragments, percent escapes, email addresses, UUIDs, long numbers (six or more consecutive digits)
and long hexadecimal identifiers are refused. Use a template instead. Invisible characters and
instruction sentences are refused as names. Duplicate entries, including equivalent parameter
spellings, are refused. Anectico reports the refusals and does not repair the file.

```bash
anectico coverage declare --file coverage.json --environment production --release v1.2.0 --dry-run
# Read the preview with the person. Their approval authorizes the next command.
anectico coverage declare --file coverage.json --environment production --release v1.2.0 --yes
anectico coverage show --environment production --release v1.2.0
anectico coverage compare --environment production --release v1.2.0 \
  --start 2026-10-09T00:00:00Z --end 2026-10-10T00:00:00Z
anectico coverage observed --environment production --release v1.2.0 \
  --start 2026-10-09T00:00:00Z --end 2026-10-10T00:00:00Z
```

The dry run saves no declaration and reserves no revision. It returns the proposed content and verified
principal; the declaration time is the server's commit clock. A save creates a revision. Its content stays fixed except for person erasure.
The latest is current; `show --revision 1` reads retained history and `revisions` lists metadata
newest first. A withdrawal creates an empty withdrawal revision and retains earlier revisions.
Project or workspace deletion removes the entire history.

The CLI prints `--idempotency-key` and `--expected-revision` before writing. Keep both, the same
file and all the same arguments after an uncertain response; the server replays the original
receipt without a second save. A different request with that key is refused. A stale revision
requires a new preview and approval.

Supported methods are GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, CONNECT and TRACE.
They may be supplied in either case. Release strings also accept a bare 40- or 64-character
software revision hash; identifiers in service/screen names and concrete paths remain refused.

## Read the comparison lists

| List | Meaning |
| --- | --- |
| `declared_and_observed` | A declared entry matched observations. Each match names its rule, count and first/last times. |
| `declared_not_observed` | No matching observation in a fully measured kind in this window. |
| `observed_not_declared` | An observed key matched no entry in this declaration. |
| `not_determined` | A kind could not be checked, or a declared comparison key was withheld by erasure. |
| `observed_not_determined` | Erased declaration keys prevent deciding whether these observations were declared. |

Declared entries partition into matched, not observed and not determined. Measured observed keys
partition into matched, not declared and not determined. Several equivalent observed template spellings can match
one declaration; `matched_observed_count` counts those keys separately. Unknown counts are absent,
never a synthetic zero. `not_determined_count` counts the declaration entries in unavailable kinds.

Service names match exactly. HTTP methods are compared in uppercase. A whole `{name}`, `:name` or
`<name>` segment becomes the same placeholder, and one trailing slash is removed. Web page
templates follow that path rule; mobile screen names match exactly. Nothing fuzzier is inferred.
Every match says `exact` or `normalized_method_parameters_trailing_slash`.

The window has an absolute inclusive start and exclusive end, at most **seven days**, ending no
later than now. A kind outside retained history is unavailable; the window is never silently
shortened. Each kind has a 10,000,000-row scan budget, a ten-second read budget and at most 5,000
distinct keys. A bound cut makes the whole kind `scan_limit`; it returns no partial list or count.

Routes come only from reported `http.route`. Spans that report only raw URLs, or unsafe concrete
route values, contribute to `no_route_template` and are never listed as paths. Screens come from
`$screen` / `$screen_name` or `$pageview` / `$pathname`. Sender names are bounded untrusted data.
An altered label has no exact matching entry; its separate digest key prevents truncated labels
from being merged or reused as lookup arguments.

## Permissions

Declaration reads need `tracking_plans:read` and `projects:read` to check the current project. A complete comparison also needs `query:read`, `traces:read` for
services and routes, and `analytics:read` for screens and sampling declarations. Observed-only
reads start with `query:read`. MCP reads also need `mcp:read`. A kind that lacks permission names
the missing scope and withholds its observations and counts.

Declaring needs an **owner-created API key** with `tracking_plans:write`, `tracking_plans:read`, `projects:read`,
`mcp:read` and `mcp:write`. Withdrawal needs `tracking_plans:delete` instead of the write scope.
An OAuth connection can use the read-only part. A declaration changes expected instrumentation
configuration; it does not grant telemetry access or change capture.

## Limits of the answer

Server-side capture completeness is unknown. A declaration does not prove that the code was
deployed or received traffic. Little traffic makes "not observed" weak evidence. The answer gives
the total stored spans plus screen/page events beside it when both counts are available; route
counts overlap the spans and are not added twice.

If an observation has no release, it is compared by this window alone and counted in `no_release`.
Versioned telemetry is filtered to the requested release. Reported sampling declarations are
named with their rates; they cover coarse hour overlaps and are sender claims. The current project sampling rate is named separately when `projects:read` is present; it does
not reconstruct the historical window. An unavailable sampling read is not proof that sampling
was absent. The conclusion counts observations under
these rules; withheld, failed or cut kinds make its certainty `estimated`.

There is no comparison proof page and no returned link. Read the exact window, comparison lists and
limits to the person. Say **declared but not observed in this window**, never infer a broken
application or missing instrumentation from silence.

### Screen content permissions

Observed screen names and page paths need `analytics:read` plus
`agents:content:read`, with permission from the project content policy. A valid
path shape can still contain a person's name. When either content control refuses
the read, the entire screen kind is `withheld`, with no entries or derived counts.
Comparison puts its declared screens in `not_determined`; it never concludes
that a screen was absent. The result names a missing content scope, or a policy
refusal when a broader key would not help. Your declared names and notes remain
your configuration, returned as untrusted text.

Person erasure replaces matching entry names, services, paths and notes in every revision
with `[withheld by erasure]`. Reads show `erasure_changed` and `redacted_fields`. A redacted
name or other comparison key is **withheld by erasure**, never declared but not observed.
A redacted note does not change the comparison. A later save that restores matched erased
text is refused. Shared declaration text stays out of a
person's export. Matching uses held names and identifiers with at least four letters or
digits. Nicknames, descriptions and unknown names may not match. Release and environment
names identify your software and are not searched as prose.
