Skip to content

feat(conformance): ADPP conformance harness — foundation (#25) - #26

Merged
CameronBrooks11 merged 7 commits into
mainfrom
feat/conformance-harness
Jun 20, 2026
Merged

CameronBrooks11 merged 7 commits into
mainfrom
feat/conformance-harness

Conversation

@CameronBrooks11

@CameronBrooks11 CameronBrooks11 commented Jun 20, 2026 •

Copy link
Copy Markdown
Member

Implements Wave 0 of the provider convergence → SDK plan: a cross-provider ADPP conformance harness that drives any provider binary through the ADPP v1 wire lifecycle and asserts compliance against docs/semantics.md (the normative spec). Scope is the foundation — a generic, versioned verifier; provider lanes and the full assertion set are staged follow-ups under #25.

Architecture: the contract owns the verifier, not its implementers

anolis-protocol is the abstract contract; it must not encode knowledge of its implementers. So the harness ships as a generic, versioned artifact in the anolis-protocol wheel that providers pull (pinned, like the C++ FetchContent of the proto) and parametrize with their own identity/config/waivers.

  • Stays here (generic): client.py (stdio + uint32_le AdppClient), spec.py (ADPP v1 constants; status codes resolved from the proto enum at runtime — the enum isn't sequential), checks.py (reusable validators), the three contract suites, the hermetic test_selftest.py, packaging ([conformance] extra + anolis-adpp-conformance console script).
  • Provider-owned (in each provider repo): a conformance.toml manifest — provider_name, has_mock_devices, [waivers] — plus the mock config and a provider.conformance CI lane. The protocol package never re-releases for a provider-specific exception.

The plugin is not a global pytest11 entry point (it loads explicitly via -p), so installing the wheel never affects unrelated pytest runs.

Three separate contracts, each with a normative source

Each suite cites its authority — so framing/executable conventions can't silently become protocol law through tests:

  1. ADPP core (test_adpp_core.py) → docs/semantics.md — messages, status, request_id correlation, capabilities, read/call semantics.
  2. Framed-stdio profile (test_framed_stdio.py) → docs/profiles/framed-stdio-v1.md (new, normative) — uint32_le framing, the 1 MiB cap, fragmentation/coalescing, Hello metadata, controlled malformed-stream behavior (incl. respond-then-exit codes).
  3. Anolis executable profile (test_executable_profile.py) → docs/profiles/anolis-executable-profile-v1.md (new, organizational) — CLI surface, WaitReady diagnostics, process hygiene. Not ADPP — the only tests a provider waiver may xfail (enforced at collection; transport/core tests are non-waivable).

The verifier cannot report green without verifying

A foundational conformance tool must fail loudly when misused:

  • Explicit modes — provider mode requires all three --provider-* args (bare/partial invocations are usage errors); --self-test runs only the hermetic lane. CI asserts each misconfiguration exits nonzero.
  • Waivers are scoped + strict — a waiver may only target an executable_profile test (rejected at collection otherwise, so it can never mask a core/transport/verifier failure); strict xfail means a fixed gap fails as XPASS.
  • Malformed input is validated by a factored helper (checks.assert_controlled_malformed): CODE_OK/CODE_UNSPECIFIED on garbage, hangs, crashes, and respond-then-crash are rejected; only a framed error response or a clean documented exit passes.
  • Self-tests exercise the real code paths — the malformed validator and the profile loader are driven against deliberately-faulty fakes, not reimplementations.

CI (hermetic — no downstream coupling)

buf lint/breaking + the verifier self-tests + the plugin-isolation regression + the misconfiguration gate, on Python 3.10 and 3.12 (3.10 exercises the tomli fallback). The protocol repo proves the verifier is sound; cross-provider verification runs in each provider's lane (it pulls this pinned wheel) — the contract's CI never downloads a provider release.

Validation

  • Hermetic self-tests: 23 pass (transport faults, the real malformed validator incl. respond-then-crash/exit, the status-presence validator, the profile loader incl. every rejection path).
  • Smoke vs released sim binary (mock mode): strengthened assertions pass; the provider manifest's --version waiver applies as a strict xfail; the unknown-signal consistency check surfaces a real sim divergence (§7.4: partial for mixed but NOT_FOUND for unknown-only) — to be fixed in sim's lane, and correctly not gating this repo's hermetic CI.

Follow-ups (staged under #25, not in this PR)

Per-provider provider.conformance lanes + conformance.toml in anolis-provider-{sim,ezo,bread}; the remaining normative assertion coverage (positive calls, arg/bounds validation, selector precedence, deadlines, typed-value/quality/timestamp); an org-level released-version compatibility matrix; the sim Python wheel pin alignment (1.1.3 → 1.2.0); Windows portability.


🔬 Please review — do not merge until you approve.

Adds a conformance/ package (shipped in the anolis-protocol wheel) that drives
any provider binary through the ADPP v1 lifecycle and asserts compliance:
- client.py: provider-agnostic stdio+uint32_le AdppClient (promoted from sim).
- profiles.py: per-provider profiles + known divergences as non-strict xfails.
- spec.py: status codes resolved from the proto enum (never hardcoded).
- plugin.py / cli.py: pytest plugin + anolis-adpp-conformance console script.
- test_conformance.py: 7 assertion groups + CLI checks.
- ADPP-CONFORMANCE.md: the executable spec.

Validated against the released sim/ezo/bread binaries (mock mode): green-as-
baseline. ezo is fully conformant; tracked divergences (xfail): sim T0.1 +
no --version; bread T0.4 + wait_ready missing init_time_ms.

Also: pyproject ships the package + [conformance] extra + entry points; CI gains
a conformance(sim) lane; handshake.proto comment fixed; Python .gitignore entries.

Refs #25.
@github-actions

github-actions Bot commented Jun 20, 2026 •

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow CI / lint (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedJun 20, 2026, 6:24 PM

…e plugin, harden verifier

Reworks the harness per review on PR #26:

Normative correctness (the harness must not contradict the spec):
- Unknown signal id: accept either fail-NOT_FOUND OR partial-results (semantics
  7.1); test consistency, not a forced NOT_FOUND. Removes the false sim waiver.
- function_id/function_name conflict: spec says MUST prefer function_id (6.2),
  so bread is conformant; removed the reversed assertion + false bread waiver.
- Unsupported version: accept FAILED_PRECONDITION or UNIMPLEMENTED (3).
- Drop the "one in-flight" claim (concurrency/out-of-order is allowed; it is a
  runtime profile, not ADPP).

Plugin isolation: removed the global pytest11 entry point (it forced Anolis
options + an autouse fixture onto every unrelated pytest run); the console
script loads the plugin via -p. Missing provider options now skip, not fail. CI
asserts an unrelated pytest suite still passes with the wheel installed.

Robustness: malformed-input checks are now crash-aware (inspect exit codes; fail
on signal death / undocumented exit / hang / over-cap response). Real monotonic
read deadline (a byte-drip can't stretch it). Response frames bounded by the
1 MiB cap. request_id correlation enforced.

Verifier self-tests: fake faulty providers (hang, signal-crash, over-cap, drip,
mid-frame close, wrong request_id, missing status) prove the harness rejects bad
providers — hermetic, run in CI.

Structure/docs: split into ADPP-core / framed-stdio / executable-profile; health
is genuinely non-gating (experimental marker, excluded by default); added
fragmentation/coalescing, include_health, typed-value, unknown-device coverage;
metadata values asserted; readiness derived from advertised metadata; ready_client
asserts wait_ready OK. ADPP-CONFORMANCE.md defers to semantics.md and is scoped as
a foundation (not "all of #25"); Linux-only declared. CI pins the sim release.

Refs #25.
@CameronBrooks11 CameronBrooks11 changed the title feat(conformance): cross-provider ADPP conformance harness (Wave 0, #25) feat(conformance): ADPP conformance harness — foundation (#25) Jun 20, 2026
@CameronBrooks11

Copy link
Copy Markdown
Member Author

Thanks for the rigorous review — it caught real problems. Pushed 5f70e8f addressing every blocking + high-severity finding. Point-by-point:

Blocking 1 — normative contradictions (verified against docs/semantics.md). You were right on all four; I had two backwards:

  • Unknown signal (§7.1): both NOT_FOUND and partial-results are conformant. Test now asserts consistency (NOT_FOUND or OK-with-the-unknown-omitted); removed the false sim waiver.
  • function_id/function_name (§6.2): MUST prefer function_id → bread is conformant; removed the reversed assertion and the false bread waiver.
  • Unsupported version (§3): now accepts FAILED_PRECONDITION or UNIMPLEMENTED.
  • Dropped "one in-flight per session" — concurrency/out-of-order is allowed (§4); it's a runtime profile, not ADPP. ADPP-CONFORMANCE.md now explicitly states semantics.md is normative, not this doc.

Blocking 2 — plugin isolation. Removed the global pytest11 entry point; the console script loads the plugin via -p. Missing provider options now skip, not fail; the autouse no longer hard-requires --profile. CI now runs an unrelated pytest suite with the wheel installed and asserts it passes (verified locally too: pytest11 eps: NONE).

Blocking 3 — crash-aware malformed input. await_outcome() inspects the exit code: a negative return code (signal death) fails, as do undocumented exits, hangs, and over-cap/malformed responses. Only a well-formed framed error response or a documented clean exit (0/2/3) passes.

Blocking 4 — deadline + response size. _read_exact uses a monotonic absolute deadline (a byte-drip can't stretch it — self-tested). Response frames are bounded by the 1 MiB cap before reading the body.

Blocking 5 — doc accuracy. Rewrote ADPP-CONFORMANCE.md: three named contracts (ADPP-core / framed-stdio / executable-profile), health is genuinely non-gating (experimental marker excluded by default), Linux-only declared, accurate "not yet covered" list, defers to semantics.md.

Blocking 6 — verifier self-tests. New test_selftest.py drives fake faulty providers (hang, signal-crash, over-cap, byte-drip, mid-frame close, wrong request_id, missing status) and asserts the harness rejects each — hermetic, runs in CI.

Blocking 7 — rescope. Retitled to "foundation"; the doc states this is not all of #25. Provider CI lanes, full assertions, version-pin alignment, Windows, and strict provider-owned waivers are explicit follow-ups (#25 stays open).

Also: request_id correlation enforced; metadata values asserted (not just keys); readiness derived from advertised metadata; ready_client asserts WaitReady OK; added fragmentation/coalescing, include_health, typed-value, unknown-device coverage; CI pins the sim release (deterministic canary) and adds the hermetic self-test lane.

Still not merging — please re-review. Remaining items I'd propose as follow-ups (not this PR): the per-provider provider.conformance lanes, the ezo mock:// config, the sim wheel version-pin, Windows portability, and strict provider-owned waiver manifests.

@CameronBrooks11
CameronBrooks11 force-pushed the feat/conformance-harness branch from e0c5686 to 6e099f2 Compare June 20, 2026 17:11
The protocol repo must not encode knowledge of its implementers. Invert
the dependency: ship a generic, versioned verifier that providers pull and
parametrize, rather than baking sim/ezo/bread into the contract.

- profiles.py: delete the hardcoded PROFILES registry; keep the
  ProviderProfile schema and add load_profile() for a provider-supplied
  TOML manifest (provider_name + has_mock_devices + [waivers]).
- plugin.py: replace --profile <name> (in-repo lookup) with
  --provider-profile <file> (provider-owned manifest); validate on load.
- ci.yml: drop the provider-sim release-binary canary (a contract ->
  implementer reverse edge); gate only the hermetic verifier self-tests.
  Cross-provider verification moves to each provider's CI lane.
- self-tests: cover load_profile (valid/defaults/invalid) hermetically.
- docs: describe the provider-owned manifest; reduce implementer mentions
  to a single "currently implemented by" line.
- pyproject: tomli fallback for Python < 3.11.
@CameronBrooks11
CameronBrooks11 force-pushed the feat/conformance-harness branch from 6e099f2 to f852cef Compare June 20, 2026 17:12
Review hardening: ensure the verifier cannot report green without
meaningfully verifying. All findings checked against docs/semantics.md
(authoritative) before changing.

- modes: provider runs require all of --provider-bin/--provider-config/
  --provider-profile; a bare or partial invocation is a usage error.
  Add --self-test for the hermetic verifier lane. CI asserts each
  misconfiguration exits nonzero.
- waivers: scope to executable_profile-marked tests only (rejected at
  collection otherwise, so a waiver can't mask a core/transport/verifier
  failure); apply as strict xfail (a fixed gap fails as XPASS).
- malformed input: factor validation into checks.assert_controlled_malformed;
  reject CODE_OK/CODE_UNSPECIFIED on garbage and respond-then-crash. Self-tests
  exercise the real helper against those fake modes.
- unknown identifiers: require CODE_NOT_FOUND for unknown function_id and
  function_name (semantics.md §8.3).
- reads: unknown-signal test now asserts the known signal survives and the
  policy is consistent across mixed/repeat/unknown-only (§7.4); default read
  restricted to OK|UNAVAILABLE; include_health requires per-device coverage
  when has_mock_devices (inventory.proto).
- contract boundary: move Hello transport-metadata assertions from ADPP core
  into the framed-stdio profile (§8). Tighten --version, fragmentation,
  coalescing, and add a trailing-garbage case; reject unknown profile keys.
- CI: add a Python 3.10 lane to exercise the tomli fallback.
)

Second-round review hardening. Each finding checked against semantics.md /
proto / release workflow before changing.

- malformed-input: a response-then-exit is validated against
  ALLOWED_MALFORMED_EXIT_CODES (undocumented positive exits now rejected, not
  just signals); the error status must be a DEFINED enum code, not any nonzero
  integer. New respond_error_then_exit_bad self-test.
- waiver scope: move test_multiple_roundtrips_stay_framed into the framed-stdio
  (non-waivable) suite — a transport-integrity test must not be waivable.
- coalescing: drive two post-handshake ListDevices instead of a repeated Hello
  (semantics.md §3 does not define a second handshake as must-succeed).
- reads: partial unknown-signal results must be a subset of requested ids
  (no unrelated inventory). Drop the non-empty CapabilitySet assertion from core
  (§6.1 requires complete, not non-empty).
- status presence: factor checks.assert_status_present (§10), wire it into the
  suite, and make the missing-status self-test exercise the real validator.
- guards prove they fired: the waiver-scope CI regression now uses real
  bin/config paths and asserts the specific diagnostic.
- normative profiles: add docs/profiles/framed-stdio-v1.md (uint32_le, 1 MiB,
  Hello metadata, malformed/exit-code behavior) and
  anolis-executable-profile-v1.md (organizational, waivable); each suite cites
  its source; semantics.md §2 points at the binding.
- docs: fix the shell example, the direct-pytest invocation (-p), stale
  strict=False comments, the §7.4 reference, and the README (no PyPI publish —
  install the GitHub-release wheel; note the conformance package + profiles).
v1.1.4/v1.2.0 predate the conformance harness, so their wheels have neither the [conformance] extra nor anolis_conformance. Install from a checkout until the first release containing it; placeholder for the release-wheel form.
@CameronBrooks11
CameronBrooks11 enabled auto-merge (squash) June 20, 2026 18:24
@CameronBrooks11
CameronBrooks11 merged commit b3f36cf into main Jun 20, 2026
7 checks passed
@CameronBrooks11
CameronBrooks11 deleted the feat/conformance-harness branch June 20, 2026 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant