T-017 — Interpretation
Per manthan/AGENT_PROTOCOL.md §"How to handle ambiguity", this file documents
each interpretive decision the runner had to make. Reviewers should read this
before triaging unexpected pass/fail patterns.
1. What does "exercises lib" mean for vectors the lib disagrees with?
Tension. TODO.md line 32 says T-017 "loads vectors, exercises lib". If
the library and the vectors disagree on canonical bytes (see NOTES.md GAP B),
the verifier returns a verdict that mostly reflects the disagreement, not the
property the vector was designed to test (e.g. cross-trace-replay should
demonstrate parent-trace-mismatch but observes tampered instead because
the id check fires first).
Resolution. The runner's job is to surface the disagreement, not paper
over it. Each vector's expected is compared against the verifier's
observed verdict literally; divergences are classified (known_interop_gap)
so reviewers can distinguish them from regressions, but the runner does not
attempt a "what would the verdict have been if GAP B did not exist" rewrite.
A separate tool — re-deriving envelopes through buildEnvelope so the
canonical bodies match T-003's profile by construction — would test T-003's
internal consistency, but that is not the deliverable T-017 was asked for.
Alternative considered. Patching the runner to use T-003's id rather than
the vector's id (so the trace/parent/replay logic gets exercised against an
internally-consistent envelope). Rejected: it would silently lie about
conformance with the canonical wire bytes T-001 produced.
2. Genesis envelope reconstruction: trust vector's id_hex?
Tension. SPEC §3.2 line 169 says trace == id for genesis; SPEC §3.5
lines 193-204 says id == sha256(canonical_body); T-001 INTERPRETATION §2
and T-003 INTERPRETATION §1 resolve the resulting fixed-point by
canonicalising with trace = null and only setting trace = id on the
wire.
Resolution. The runner builds genesis envelopes withtrace = vector.id_hex (the wire form). T-003's verifier internally
canonicalises with trace = null for the id check, matching the convention
established by T-001 and T-003. This is consistent with both interpretation
documents.
The runner does not independently re-derive id from the vector's
inputs; it trusts the vector's stored id_hex. This is appropriate because
the vector ships its own consistency check (re-running generate.py and
diffing). The runner's job is downstream: take the wire envelope as
specified, hand it to the verifier, observe the verdict.
3. Replay vector reconstruction
Tension. The replay-genesis vector's inputs only containsbased_on: "genesis-alpha-valid" and submission_index: 2. The runner has
to reconstruct the actual envelope to feed the verifier.
Resolution. The reconstructor recursively dereferences based_on and
returns the base vector's envelope unchanged. The replay-cache is then
pre-seeded with (from, nonce) to simulate a prior accepted submission, so
that calling verifyEnvelope with the same envelope a second time should
return replayed-nonce per SPEC §3.7 lines 224-229.
In the current state of T-003 + T-001, GAP B trips the id check before the
replay-cache check, so the observed verdict is tampered. The runner
classifies this as known_interop_gap and proceeds.
4. Tampered vector reconstruction
Tension. The tampered-genesis vector stores the wire (original) id
and signature plus the post-tamper payload. The reconstructor needs to
produce the wire envelope as it would arrive on the network: original
id+sig, mutated payload.
Resolution. The runner hard-codes the linkage to genesis-alpha-valid
because that is the only base T-001 declares. If T-001 grows additional
tampered variants, the linkage table in reconstruct.ts will need
extending; this is documented inline.
5. Verdicts T-003 does not implement
Tension. Vector 7 (lineage-cycle-birth) expectsrejected-lineage-cycle, a code introduced by T-001 INTERPRETATION §4 that
is not in SPEC §6 lines 316-333 and not implemented by T-003.
Resolution. The runner reports the vector as skip with a reason
explaining why. It does not count this as either a pass or a fail. The exit
code policy treats skips as benign. T-024 / T-026, when added, can also
register vectors with verdicts T-003 does not yet implement; the same
mechanism applies.
6. Public/private key handling
Tension. T-001 ships seeds + raw public key bytes. T-003's verifier
takes a Map<sigil, KeyObject>. There are two ways to materialise the
KeyObject: re-derive from seed (so keyPairFromSeed runs) or import from
raw bytes (so publicKeyFromRaw runs). The two paths exercise different
code; either is valid.
Resolution. The loader does both: it re-derives from seed to verify
that keyPairFromSeed → sigilFromPublicKey produces the sigil the vector
declares (catches SPEC §2.1 regressions in T-003), and then imports a
fresh KeyObject from the raw public-key bytes for the verifier's key map
(mirrors how a third party would hand a public key to the lib).
7. The runner's exit code 2
Tension. A CI system needs to distinguish "real regression" from
"known pre-existing divergence" without losing the alarm signal.
Resolution. Exit code 0 means clean (all pass or skipped on declared
coverage gap); exit code 1 means at least one genuine failure (anything not
flagged as known_interop_gap); exit code 2 means every failure is flagged
as known_interop_gap. Both 1 and 2 are non-zero so CI catches them, but
2 is distinguishable for humans glancing at a build log.
This policy is a runner-level convention, not protocol-level. A reviewer
who disagrees can change exitCode() in src/reporter.ts without touching
the rest of the runner.