# What integration verification proves and what it does not

> The stages between your code and stored evidence, what a verified sample establishes, the exact limits and known gaps of the verification reads, how long to wait, and what has been run.

Canonical page: https://anectico.com/docs/reference/integration-verification-limits/


After your agent integrates feature flags or product events, three reads ask Anectico whether the
exact samples of a controlled test arrived. This page is the one place that says what their answers
establish, which limits bound them, how long to wait before trusting a negative answer, and what has
and has not been run. The request and answer fields are on the pages for each read.

| Read | REST | MCP action | CLI | Fields and states |
| --- | --- | --- | --- | --- |
| One job, one verdict | `POST /api/v1/instrumentation/verify` | `verify_integration` | `anectico doctor integration` | [Integration job verification](/docs/reference/integration-job-verification) |
| Exact product events | `POST /api/v1/instrumentation/events/verify` | `verify_integration_events` | `anectico doctor events` | [Integration event verification](/docs/reference/integration-verification) |
| Exact flag exposures | `POST /api/v1/instrumentation/flags/verify` | `verify_flag_integration` | `anectico doctor flags` | [Feature-flag exposure verification](/docs/reference/flag-integration-verification) |

All three are reads. They send nothing, change nothing, store no report, run no model and open no
proof page: `links` is always empty. Their states (`observed_match`, `mismatch`, `not_observed`,
`withheld`, `unavailable`, `ambiguous`) mean the same thing on every surface, and a REST, MCP or CLI
call with the same request returns the same answer.

## Six stages, and which one a read can establish

A test passes through six stages. Each one is evidence of itself and of nothing after it.

| Stage | What it is | Who reports it | Does a verification establish it? |
| --- | --- | --- | --- |
| Configuration | The flag, keys, project and tracking-plan revision you approved through a preview and a confirmation | The confirmation receipt | No. A receipt shows a setting exists, never that your application uses it. |
| Application execution and local outcome | Your application ran, served a branch, and its SDK or code reported success or failure | Your application's own record | No. Anectico does not see your run. Keep the clocks, message IDs and served branch from your application's record. |
| Queue acceptance | Capture answered `CAPTURE_DELIVERY_QUEUED` for the item | The capture acknowledgement | No. It does not mean the event is stored, readable, or that an identity update has finished. |
| Retained evidence | A stored event with that name, message ID and admitting source matches your selection | The verification read | Yes, for the selected samples only. |
| Identity | The `distinct_id` your application declared resolves to a person | The `identity` check, with `persons:read` | Yes, with `persons:read`. The declared ID is your application's own assertion; Anectico does not authenticate your end users. |
| Verification | Every required check of every selected sample is `observed_match` | The `verdict` | It is the sum of the checks above and no more. |

## What a verified sample proves

`verified_sample` means: for the up to 20 samples you selected, inside the window and before the
`read_cutoff` the answer states, every required check was `observed_match`. For product events that
is the event name, message ID and admitting source, the declared identity, the session if you
asked, the person the declared identity resolves to, and, with a pinned tracking-plan revision, that
the revision declares the event and the property types match. For flags it is the value, definition
version, evaluation status, fallback flag, evaluation mode and input context that your application
recorded.

It does **not** prove:

- that any other event, flag, person or code path is instrumented;
- that everything your application sent arrived. `upstream_completeness` is always `unknown`: SDK
  configuration, sampling, consent, client-side loss and an accepted intake request are not observed;
- that errors, traces, logs or replay work. They are neither required nor checked;
- that a person saw a treatment. A flag exposure is application-reported use;
- that your application authenticated its inputs. An `input_binding` is a declaration by the
  sender, never recomputed or authenticated, and a remote decision that used stored person, group,
  cohort or audience context is `context_not_established`;
- that the people in the test are real people;
- that the project's measurements are healthy. Verification checks your selected samples and nothing
  else, so another person's correct events neither help nor hide a wrong one.

## Bounds on every request

| Bound | Value |
| --- | --- |
| Event-time window | At most 1 hour, `[start, end)`, millisecond precision |
| Samples or exposures | 1 to 20, answered in the order sent |
| Property checks per sample | At most 20 |
| Whole job call | Stops after 20 seconds |
| Concurrent verifications per project | 2; a third is refused with HTTP 429 |

The product-event read behind both jobs also stops at these limits, returned in every answer under
`limits`:

| Read limit | Value |
| --- | --- |
| Rows scanned | 10,000,000 |
| Bytes scanned | 1 GiB |
| Memory | 512 MiB |
| Candidate events returned | 200 |
| Returned bytes | 4 MiB |
| Execution time | 8 seconds |

A reached limit makes every sample `unavailable` with a reason that begins `limit_`, and
`limits.limit_reached` is true. It never produces `not_observed`. Normal rate limits and agent budgets
apply to these reads; there is no separate fee.

## Two known gaps in the event read

Both are open and both are stated in every answer's `limitations`.

**The read is not narrowed by date.** It reads every retained event of the selected event names in
the project, not only those of the window's day, and keeps the ones inside the window. The cost
therefore grows with the project's history of those names, not with your test. On a local
deployment holding 100,000 retained events of the 20 selected names across 30 days, one 20-sample
verification read about 100,000 rows, 27 MB, in about 50 ms. At that row width the 1 GiB byte limit
binds near 3.9 million retained events of the selected names, before the row limit. A busy project
can therefore answer `unavailable` for an event that exists. Select fewer distinct event names per
request, and treat `unavailable` with a `limit_` reason as unknown, never as absent.

**The stored event has no source in its key.** Two sources in one project that send the same event
name, timestamp, distinct ID and message ID keep one stored event. Verifying one source can then
report `not_observed` because the other source's copy was kept. Ordinary traffic rarely collides,
because SDK message IDs are random, but a replayed fixture, a copied payload, or two servers
forwarding one upstream event can. Use a message ID that is unique to the source in every test,
and never reuse a fixture across sources. Nothing in the answer tells you a collision happened.

## How long to wait, and when to stop

Queue acceptance is not readability. In the local runs described below, stored events became
readable about 20 to 35 seconds after the application sent them; the slowest run needed its fourth
read, about 65 seconds after the exercise. How long a hosted deployment takes has not been measured,
so do not hard-code a delay.

- The first one or two reads after a test can answer `failed` because every check is
  `not_observed`. That is delay, not a defect. A `mismatch` is different: the event was read and it
  differs from what you asked for. Read the checks, not only the verdict.
- Read again with the **same** request. The runnable examples read at most four times, after waiting
  5, 10, 20 and 30 seconds in turn, and re-read only while a required check is `not_observed` or
  `unavailable`. Then they report what is unresolved. Your agent decides the schedule; Anectico does
  not poll for you.
- Do not resend an acknowledged event to make it appear, do not widen the window or change the
  selectors to get a better verdict, and do not create another resource after an ambiguous answer.
- A call that fails with 409, 429 or 503 released no answer. Retry the same request shortly.
- If a scope, the project's content policy or the selected events change while a read runs, no
  answer is released. Start a fresh read.

## Scopes decide how much of the answer you see

| Scope | Needed for | Without it |
| --- | --- | --- |
| `mcp:read` | Calling a read over MCP | The MCP call is refused. |
| `query:read` | The one-verdict job read | The job read is refused (403) before anything is read. |
| `analytics:read` | Every product-event and flag-exposure read | The read is refused (403). |
| `flags:read` | The `flags` job and the exposure read | The read is refused (403). |
| `persons:read` | The `identity` check | The check is `withheld`; a stored identity that differs from the declared one is reported only as a mismatch. |
| `tracking_plans:read` | The plan and its declarations | They are `withheld`. |
| `agents:content:read` | Property checks and recorded flag fields | Every such check is `withheld`. |

A `withheld` check never lets a sample verify. It is not a sign that the application is wrong or the
event is absent: ask for the scope, or report that part as not checked.

## Time, history and population

- Windows are event time as your application stamped it. `read_cutoff` is the receipt time after
  which nothing was read; it is not a snapshot or a delivery watermark. `effective_start_time` and
  `effective_end_time` are the part of the window the read covered after the project's readable
  history, and are empty when nothing was read.
- `population.basis` is always `explicitly_selected_application_samples`: only the samples you
  selected were checked.
- Anectico does not yet reserve a population for verification traffic. Test events are ordinary
  events in the project: they count in its measurements and in its event allocation, and they are
  retained, exported and erased like any other event. Use clearly synthetic identities such as
  `anectico-verification-user-a`, tell the person who owns the project, and never erase a person
  merely to tidy up.
- Identify and group-identify calls store no event row, so no read can select them. Their outcome
  is the acknowledgement. Verify the events that follow them.

## Jobs not covered yet

`errors`, `traces_logs`, `replay`, `surveys` and `experiments` are refused by name. For an error or
trace story use the setup check in [verify your setup](/docs/start/verify-setup).

## What has been run

On 15 October 2026 the runnable examples were driven against a local test deployment on one macOS
machine, each by a script that exercised the application like a visitor's browser and then asked
Anectico to verify the samples:

| Example | Runtime | Result |
| --- | --- | --- |
| Node checkout, remote flag decisions | Node 22.22.0, SDK built from the repository; and from a packed tarball | Product events and flags `verified_sample`; the people behind the events resolved as expected |
| Node checkout, remote flag decisions | Node 24.21.0, packed tarball | The same |
| Node checkout, local flag evaluation | Node 22.22.0, SDK built from the repository | The same |
| Ruby checkout over HTTP, no SDK | Ruby 2.6.10, standard library | The same, with session checks |

Against the verified Ruby run's own requests, a wrong selection failed as it should: another source,
identity, session, expected value or definition version each produced `failed`, and another project
was refused.

On 16 October 2026 an independent coding agent was asked, on the same local test deployment, to
integrate product events or a server-side flag into the uninstrumented Node 24 and Ruby 2.6
checkouts from scratch, four times in all. In every run, its changed application, exercised again by
the test harness as two visitors the agent had never seen, produced exactly the events and exposures
that the verification reads then confirmed: the events, the anonymous-to-identified joins, a numeric
property, the served branch and the versioned exposures, and on Node an established, agreeing
local-versus-remote comparison. None of the four counts as a qualification: each final report left
out a fact the check requires (an event name, the test people, the message IDs or the flag key). The
agents' own check of `$groups` as an event property came back missing, so account attribution was
not confirmed through event verification.

Not run: a passed qualification of the workflow by an independent agent; a hosted or shared
deployment; Ruby 3; Linux or Windows hosts; a browser; and any other language. Nothing on this page
is a promise for those. See [Ask your agent to integrate Anectico](/docs/start/ask-your-agent-to-integrate)
for what is covered and [Runnable integration examples](/docs/instrument/integration-examples) for
the code that was run.
