Skip to content

Commit 2c21656

Browse files
MajorTalclaude
andcommitted
feat(cli): surface v1.56 verification attempt detail in doctor + agent status
Pairs with gateway v1.56 (parent change: verification-no-silent-fail in run402-private). When operator email verification is 'pending' and the gateway returns email_verification.last_challenge.hint, doctor + agent status render the per-reason hint inline instead of the previous generic 'email not verified' message that gave the operator no actionable signal. - cli/lib/doctor.mjs: operator_health gap message reads from new block - cli/lib/agent.mjs: 'agent status' merges /agent/v1/operator/status email_verification block into the response (best-effort) - cli/llms-cli.txt + CHANGELOG: document the new agent-DX surface Tests: 555/555 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3180e2d commit 2c21656

4 files changed

Lines changed: 47 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
All notable changes to `@run402/sdk`, `run402` (CLI), and `run402-mcp`. Versions are kept in lockstep across the three packages in this repo. `@run402/functions` lives in the private gateway monorepo and publishes on its own cadence.
44

5+
## 2.4.0 — unreleased
6+
7+
Surfaces the v1.56 gateway verification-no-silent-fail bundle ([parent change: `verification-no-silent-fail` in run402-private](https://github.com/kychee-com/run402-private/tree/main/openspec/changes/verification-no-silent-fail)). Closes a class of UX bugs where SES auth-verdict rejections silently failed operator email verification with no signal to the operator. Additive — old clients silently ignore the new fields.
8+
9+
### Added
10+
11+
- **`run402 doctor` surfaces per-attempt verification failure detail** (`cli/lib/doctor.mjs`). When `operator_email` is `pending` and the gateway's `email_verification.last_challenge.hint` is populated, doctor renders it inline: `operator email not verified (1/5 attempts used, 4 remaining): SES reported FAIL on: spf. Fix the corresponding DNS records on <domain> and reply again. 4 more attempts remain.` — instead of the previous generic "email not verified" message that gave the operator no actionable signal.
12+
- **`run402 agent status` includes `email_verification.last_challenge` block** (`cli/lib/agent.mjs`). Best-effort fetch from `/agent/v1/operator/status` is merged into the response so a single command surfaces the full challenge state: `attempts[]` with per-reason `at`, `from_address`, `reason` (one of `trust_rejected | from_mismatch | threading_miss | code_mismatch`), `sender_trust` verdicts, plus `attempt_count`, `remaining_attempts`, and the gateway-computed `hint`. Older gateways silently keep the original response shape.
13+
14+
### Changed
15+
16+
- **Doctor's `operator_health` check is now strictly more informative** when `email_status !== "verified"`. No behavior change for already-verified operators. The threshold for "warning" status is unchanged; only the message detail improves.
17+
18+
### Out of scope (deliberate carve-out)
19+
20+
- No SDK type changes — `email_verification` is consumed dynamically because the v1.55 SDK already returns the rest of the operator-status response as `unknown`-shaped JSON pass-through, and adding strict types here would force a parallel public-repo edit on every gateway-side field addition. Future work: type the operator-status response shape end-to-end.
21+
522
## 2.3.0 — unreleased
623

724
Surfaces the v1.49 gateway image-variant pipeline ([run402#392](https://github.com/kychee-com/run402/issues/392), parent change: [`asset-image-variants` in run402-private](https://github.com/kychee-com/run402-private/tree/main/openspec/changes/asset-image-variants)). Additive, non-breaking — old clients silently ignore the new fields.

cli/lib/agent.mjs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,21 @@ async function status(args = []) {
122122
allowanceAuthHeaders("/agent/v1/contact/status");
123123

124124
try {
125-
const data = await getSdk().admin.getAgentContactStatus();
125+
const sdk = getSdk();
126+
const data = await sdk.admin.getAgentContactStatus();
127+
// v1.56: augment the response with email_verification.last_challenge from
128+
// /agent/v1/operator/status so the operator sees per-attempt status
129+
// (trust_rejected with which verdicts, attempts remaining, hint) without
130+
// a second command. Best-effort — older gateways without the v1.55+ route
131+
// skip the augment silently.
132+
try {
133+
const opStatus = await sdk.admin.getOperatorStatus();
134+
if (opStatus && opStatus.email_verification) {
135+
data.email_verification = opStatus.email_verification;
136+
}
137+
} catch {
138+
// Older gateway — keep the original response shape.
139+
}
126140
console.log(JSON.stringify(data, null, 2));
127141
} catch (err) {
128142
reportSdkError(err);

cli/lib/doctor.mjs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,25 @@ export async function run(sub, args = []) {
169169
});
170170
}
171171

172-
// 6. Operator health snapshot (v1.55).
172+
// 6. Operator health snapshot (v1.55 + v1.56 verification attempt detail).
173173
try {
174174
const sdk = getSdk();
175175
const status = await sdk.admin.getOperatorStatus();
176176
const gaps = [];
177177
if (status.operator_contact.email_status !== "verified") {
178-
gaps.push(`operator email not verified (${status.operator_contact.email_status}) — run 'run402 agent contact --email ...' then reply to the challenge`);
178+
// v1.56: prefer the structured email_verification.last_challenge.hint
179+
// over the generic "email not verified" message. The gateway computes
180+
// a per-reason remediation hint that's actionable for the operator.
181+
const ev = status.email_verification;
182+
const ch = ev?.last_challenge;
183+
if (ch && ch.hint) {
184+
const attemptsLine = ch.attempt_count > 0
185+
? ` (${ch.attempt_count}/${ch.attempt_count + ch.remaining_attempts} attempts used, ${ch.remaining_attempts} remaining)`
186+
: "";
187+
gaps.push(`operator email not verified${attemptsLine}: ${ch.hint}`);
188+
} else {
189+
gaps.push(`operator email not verified (${status.operator_contact.email_status}) — run 'run402 agent contact --email ...' then reply to the challenge`);
190+
}
179191
}
180192
if (status.operator_contact.passkey_status !== "verified") {
181193
gaps.push("operator passkey not bound — run 'run402 agent passkey enroll' after email verification");

cli/llms-cli.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1254,7 +1254,7 @@ Every invalidate returns `{ deleted, generation, host, path? }`. `generation` is
12541254
### doctor
12551255
Health + config diagnostics. Agent-DX entrypoint — agents run this first to verify environment before attempting anything else.
12561256

1257-
- `run402 doctor [--json] [--verbose]` — checks: config dir presence, allowance + rail, keystore wallet count, API base reachability, active tier + lifecycle state. Exit 0 on all-pass, 1 on any failure. `--json` emits `{ ok: boolean, checks: [{ name, status, value?, hint?, message? }] }`.
1257+
- `run402 doctor [--json] [--verbose]` — checks: config dir presence, allowance + rail, keystore wallet count, API base reachability, active tier + lifecycle state, operator health snapshot (binding state + per-attempt verification failure detail since v2.4 / gateway v1.56). Exit 0 on all-pass, 1 on any failure. `--json` emits `{ ok: boolean, checks: [{ name, status, value?, hint?, message? }] }`. When operator email verification is `pending`, the doctor surfaces the per-reason hint from `email_verification.last_challenge.hint` ("publish SPF/DMARC", "reply from registered address", "use Reply button not new message", etc.) along with `attempt_count` / `remaining_attempts` so the operator sees what to fix.
12581258

12591259
### dev
12601260
Astro dev wrapper. Loads `.env.local`, verifies `RUN402_PROJECT_ID` + `RUN402_SERVICE_KEY`, then spawns `astro dev` with the env inherited.

0 commit comments

Comments
 (0)