55The verifier watches for an in-progress AgentPhone call to one configured number. For that
66call it asks the platform which agent placed it and which sub-account owns that agent; that
77platform record is the only source of identity ([ ADR 0001] ( docs/adr/0001-trust-anchor.md ) ).
8- It then compares the owner with whoever the caller claims to represent, computes a verdict,
9- signs it, and notifies the person being called. Every step that fails, times out or is
10- ambiguous ends in ` UNVERIFIED ` ([ ADR 0002] ( docs/adr/0002-fail-closed.md ) ).
8+ As soon as the owner is known, the person being called is told whose agent it is, whether or
9+ not the agent ever says. If the agent later claims to represent someone else, a second
10+ message warns them. Every step that fails, times out or is ambiguous ends in ` UNVERIFIED `
11+ ([ ADR 0002] ( docs/adr/0002-fail-closed.md ) ).
1112
12- Only the skeleton exists so far: config, logging and a health endpoint.
13+ So far there is config, logging, a health endpoint and the verdict state machine. Nothing
14+ feeds the state machine real events yet.
1315
1416## Code map
1517
16- | Path | Role |
17- | --------------- | ----------------------------------------------------------------------- |
18- | ` src/config.ts ` | Parses and validates the environment. The only reader of ` process.env ` . |
19- | ` src/log.ts ` | One-line JSON event log, keyed by call ID. |
20- | ` src/server.ts ` | Entry point. ` node:http ` server; ` GET /health ` only for now. |
21- | ` test/ ` | ` node:test ` suites, run through ` tsx ` . |
22- | ` docs/adr/ ` | Decision records. |
18+ | Path | Role |
19+ | ---------------- | ----------------------------------------------------------------------- |
20+ | ` src/config.ts ` | Parses and validates the environment. The only reader of ` process.env ` . |
21+ | ` src/log.ts ` | One-line JSON event log, keyed by call ID. |
22+ | ` src/server.ts ` | Entry point. ` node:http ` server; ` GET /health ` only for now. |
23+ | ` src/types.ts ` | Lookup results, claims, verdict states, events and reason codes. |
24+ | ` src/verdict.ts ` | ` nextVerdict(prev, event, aliases) ` : the pure verdict state machine. |
25+ | ` test/ ` | ` node:test ` suites, run through ` tsx ` . |
26+ | ` docs/adr/ ` | Decision records. |
2327
24- Planned: the AgentPhone client, call lookup, transcript stream, claim extraction, the verdict
25- state machine, token signing and the notifier, each in its own module under ` src/ ` .
28+ Planned: the AgentPhone client, call lookup, transcript stream, claim extraction, token
29+ signing and the notifier, each in its own module under ` src/ ` .
30+
31+ ## Verdict state machine
32+
33+ Every call starts in ` IDENTIFYING ` . Events are a lookup result, a claim, a deadline (` lookup `
34+ or ` claim ` ) or an error; ` nextVerdict ` maps the current state and one event to the next state.
35+
36+ ```
37+ IDENTIFYING ──one match──▶ OWNER_KNOWN ──claim names owner─────────▶ VERIFIED
38+ │ ├──claim names someone else──▶ MISMATCH
39+ │ ├──claim deadline────────────▶ VERIFIED_NO_CLAIM
40+ │ └──error─────────────────────▶ UNVERIFIED
41+ └──none / ambiguous / error / lookup deadline──▶ UNVERIFIED
42+
43+ VERIFIED, VERIFIED_NO_CLAIM ──claim names someone else──▶ MISMATCH
44+ ```
45+
46+ - Claims heard in ` IDENTIFYING ` are kept and checked when the owner is known: one match plus
47+ any wrong claim goes straight to ` MISMATCH ` .
48+ - In ` VERIFIED ` and ` VERIFIED_NO_CLAIM ` , only a claim for someone else changes anything.
49+ - ` MISMATCH ` and ` UNVERIFIED ` are final. Events that do not apply to the current state, such
50+ as a late lookup deadline, are ignored.
51+
52+ Its invariants, each checked over every sequence of up to three events in
53+ ` test/verdict.test.ts ` :
54+
55+ 1 . ** Fail closed.** No error, timeout, ambiguity or missing call yields ` VERIFIED ` or
56+ ` VERIFIED_NO_CLAIM ` ([ ADR 0002] ( docs/adr/0002-fail-closed.md ) ).
57+ 2 . ** Monotonic.** Once ` MISMATCH ` or ` UNVERIFIED ` , the verdict never changes.
58+ 3 . ** Claims only downgrade.** A claim never creates trust: its only upward move is
59+ ` OWNER_KNOWN ` to ` VERIFIED ` , and only for the owner's name or alias
60+ ([ ADR 0003] ( docs/adr/0003-claim-only-downgrades.md ) ).
61+
62+ ### Notifications
63+
64+ The state machine only computes verdicts; the notifier (not built yet) decides what to send.
65+
66+ | Entering | Message to the person being called |
67+ | --------------- | --------------------------------------------------------------- |
68+ | ` OWNER_KNOWN ` | Disclosure: whose agent is calling, with a link to the proof |
69+ | ` MISMATCH ` | Warning: the agent claimed someone other than its owner |
70+ | Any other state | Nothing yet; whether ` UNVERIFIED ` sends a message is still open |
71+
72+ A wrong claim heard before the lookup finishes goes straight from ` IDENTIFYING ` to
73+ ` MISMATCH ` , so the notifier sends the disclosure and the warning together. For the demo, each
74+ message is a proof-page link printed to the terminal; SMS or iMessage comes later.
2675
2776## Invariants
2877
@@ -33,6 +82,8 @@ state machine, token signing and the notifier, each in its own module under `src
3382- Caller ID and anything the caller says are untrusted input. They can lower a verdict but
3483 never raise one.
3584- The verdict for a call never moves from ` MISMATCH ` or ` UNVERIFIED ` back to ` VERIFIED ` .
85+ - ` src/verdict.ts ` does no I/O and reads no clock. Timers live in the caller and arrive as
86+ deadline events.
3687
3788## Intentional oddities
3889
0 commit comments