feat(server): construction-time warn when testController lacks resolveAccount (#1784)#1788
Conversation
…eAccount (#1784) When `createAdcpServer` is called with `testController` set but neither `resolveAccount` nor `resolveAccountFromAuth` configured, emit a one-shot logger.warn at construction. The dispatch-time sandbox gate admits requests where ctx.account is undefined, so without a resolver the only remaining check is buyer-supplied account.sandbox / context.sandbox — caller-controlled, not a trust boundary. The warn makes that silent failure mode loud once. Deliberately a warn, not a throw: storyboard-runner deployments without account scoping are legitimate; the warn message tells them to ignore it. Production bindings get the actionable signal. The check gates on resolveAccountFromAuth as well as resolveAccount, so OAuth-passthrough setups (createOAuthPassthroughResolver, the canonical Shape-B path) don't get a spurious warn. Five regression tests cover: presence/absence of testController, presence of either resolver, and that the warn fires once across construction + N requests (not per-dispatch). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…trimmed message
DX review caught that the default `logger` is `noopLogger`, which swallows
.warn — defeating the purpose of the warn for the day-one misconfig case.
Switch to dual-emit:
- `process.emitWarning(message, { type: 'AdcpServerConfigWarning',
code: 'ADCP_BRIDGE_NO_RESOLVER' })` writes to stderr by default so the
signal is visible without configured logging
- `logger.warn(message)` still fires so adopters with logging pipelines
see it through their normal channel
Storyboard runners that knowingly run without account scoping can silence
via `node --no-warnings=ADCP_BRIDGE_NO_RESOLVER`.
Message changes:
- Lead with the action ("configure resolveAccount...")
- Trim trust-model paragraph; JSDoc carries the long form
- Prefix with [adcp/createAdcpServer] for parity with sibling warns
- Add public docs URL so coding agents reading the warn from a log can
fetch the framing without an editor
New test: synchronous spy on process.emitWarning (avoids event-loop
flush timing dependencies). Verifies stderr channel fires with default
logger.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Expert review pass — three reviewersRan three parallel expert reviews (code-reviewer, security, dx). Synthesis: Convergent feedback addressed in commit e83bacd:
Security — agent stalled but partial output confirmed the M1 finding from #1786 is closed by this PR: the warn correctly targets the legacy New test for the stderr channel uses a synchronous spy on Verdict from each reviewer: ship after the DX fix lands. Done in e83bacd. CI re-running. |
Second review pass — both clearCode-reviewer and security-reviewer re-fired on commit `e83bacd` (the DX fold-in). Security: M1 fully closed by the dual-emit. Stderr content is safe (no tenant data, no auth, no payload). `--no-warnings=ADCP_BRIDGE_NO_RESOLVER` silencer is operator-scoped, not buyer-controllable. Per-construction emit cardinality is acceptable; process-level dedup would actually hide per-tenant signal in multi-tenant deployments. Ship. Code: No blockers. Two nits folded in commit 6cfff71:
Other code-review notes (all non-blocking):
CI green → admin merging. |
…n model (#1795) * docs: align bridge framing with revised single-dimension certification model Maintainer walked back the two-badge proxy-vs-state-local split from #1782 and proposed a single-dimension framing (Wire Conformance / Live Integration Verified) where every seller faces the same verifiability gap, and the bridge is one of two mechanisms for closing the seed→read loop, not a special path for one seller class. JSDoc on AdcpServerConfig.testController: - Drops "only upstream-proxy sellers" as primary framing - "Pick by where your read handlers fetch from, not by seller class" - "Either path earns wire-conformance credit; it is *not* a separate certification category" skills/build-seller-agent/SKILL.md: - New "Test surfaces" section frames the verifiability gap as universal - Names the two implementations (state-local store vs TestControllerBridge) - Hedges on certification names while #1782 settles No SDK behavior change. Marker contract, trust-boundary docs, and dual-emit warn from #1786/#1787/#1788 all stay as-is — they describe mechanism without committing to certification taxonomy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(skill): fold docs-expert feedback — tighten Test surfaces section + in-repo JSDoc link --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Closes #1784. When
createAdcpServeris called withtestControllerset but neitherresolveAccountnorresolveAccountFromAuthconfigured, emit a one-shotlogger.warnat construction.The dispatch-time sandbox gate (
src/lib/server/create-adcp-server.ts:3973-3981) admits requests wherectx.account === undefined. Without an account resolver,ctx.accountis alwaysundefined, so the only remaining check is the buyer-suppliedaccount.sandbox/context.sandboxmarker — caller-controlled, not a trust boundary. This is the gap the security review on #1786 flagged as M1; the JSDoc updates in #1786 and #1787 document it, but JSDoc doesn't fire when adopters misconfigure. This PR adds the runtime equivalent.Why a warn, not a throw
Storyboard-runner deployments without account scoping are a legitimate and intentional configuration — the runner drives state directly, no buyer authentication path needed. Failing construction would break that case. The warn is the right severity: visible in any logging setup, ignorable for runners, actionable for adopters.
OAuth-passthrough handling
The check gates on
resolveAccountFromAuthas well asresolveAccount. OAuth-passthrough setups (createOAuthPassthroughResolver, the canonical Shape-B adopter path from the v6.7 → 6.9 migration) only configure the latter — either resolver populatesctx.accountat dispatch and gives the gate its account-side teeth, so warning onresolveAccountFromAuth-only setups would be a spurious noise floor.JSDoc cross-link
TestControllerBridge(intest-controller-bridge.ts) now mentions the construction-time warn so adopters know what triggers it and how to silence it.Test plan
test/lib/seed-per-tool-wiring.test.js:testController+ neither resolvertestControlleris omittedresolveAccountis configuredresolveAccountFromAuthis configured (OAuth passthrough)tsc --noEmitcleanprettier --checkclean🤖 Generated with Claude Code