feat(kernel): resilience layers — breaker + injected observability + WebBotAuth signing (Phase A slice 2) - #34
Conversation
…WebBotAuth signing (Phase A slice 2) Fill the inert slice-1 resilience seams with concrete, opt-in implementations. All three layers are inert by default — a client constructed without a `resilience` config behaves exactly like the slice-1 SDK wrapper. - Circuit breaker: opossum-backed `createCircuitBreaker` implementing the CircuitBreakerLike seam (errorThresholdPercentage 50 / resetTimeout 30000 / rollingCountTimeout 60000 / volumeThreshold 5). `WaveKernel.run()` guards the SDK call path through the breaker when configured; open trips a CircuitOpenError. State transitions emit breadcrumbs via an injected hook. - Observability: no hard Sentry/Next dep. The seam accepts injected captureError / captureBreadcrumb hooks (service: 'kernel' intent), so non-Next hosts can wire their own reporter. - WebBotAuth signing: `createWebBotAuthSigner` implementing the SignerLike seam — RFC-9421 Ed25519 request signing via @noble/ed25519, wired into the SDK's fetch path. Fail-open: any signing error is captured and the request proceeds unsigned. Opt-in (only when a key is configured). New deps: opossum ^10, @noble/ed25519 ^3, @types/opossum ^8 (dev). Tests: 12 -> 26 (breaker, signer, client resilience wiring). type-check/test/build all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_e5e485e1-6f3a-4cb1-b1cf-5956b0fb3f21) |
|
📝 WalkthroughWalkthroughAdds opt-in opossum circuit breaking and RFC-9421 WebBotAuth signing to the TypeScript Kernel SDK, expands resilience contracts and exports, wires both capabilities into ChangesKernel resilience
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant WaveKernel
participant OpossumCircuitBreaker
participant WebBotAuthSigner
participant KernelSDK
participant Fetch
Caller->>WaveKernel: run(action)
WaveKernel->>OpossumCircuitBreaker: fire(action)
OpossumCircuitBreaker-->>WaveKernel: result or circuit-open error
KernelSDK->>WebBotAuthSigner: sign(request)
WebBotAuthSigner-->>KernelSDK: signature headers
KernelSDK->>Fetch: send signed request
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
Warning Review ran into problems🔥 ProblemsThese MCP integrations need to be re-authenticated in the Integrations settings: Sentry Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@sdk-typescript/packages/kernel/package.json`:
- Around line 69-71: Update the kernel package’s engines declaration and
corresponding CI/runtime baseline to require Node 22 or newer, matching the
requirements of `@noble/ed25519` and opossum; keep the dependency versions
unchanged.
In `@sdk-typescript/packages/kernel/src/circuit-breaker.ts`:
- Around line 77-103: Isolate each captureBreadcrumb invocation registered on
this.breaker so a throwing breadcrumb sink cannot escape the synchronous open,
halfOpen, or close lifecycle listener or alter breaker behavior. Catch and
suppress hook errors while preserving the existing breaker transitions and
breadcrumb payloads. Add a regression test covering a throwing captureBreadcrumb
during a lifecycle event, including timer-driven transitions if supported by the
existing test setup.
In `@sdk-typescript/packages/kernel/src/web-bot-auth.ts`:
- Around line 56-58: Defer string key conversion from the constructor into the
fail-open signing path so malformed hex cannot throw during WebBotAuthSigner
construction. Update the signing method to convert and use its local privateKey
when calling ed25519.signAsync, preserving graceful failure without blocking
requests.
- Around line 166-174: Wire WebBotAuthSigner.sign() failures to the shared
resilience.captureError hook so fail-open signing errors are observable,
preferably by having WaveKernel inject that hook when constructing the signer.
In sdk-typescript/packages/kernel/src/web-bot-auth.ts lines 166-174, update the
signer’s error path; in lines 177-194, update the JSDoc example to pass the same
captureError function to createWebBotAuthSigner and resilience.captureError. In
sdk-typescript/packages/kernel/src/client.ts lines 99-131, ensure WaveKernel
provides the shared hook to the signer, or explicitly document the remaining
limitation if the signer type cannot support injection.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: e6f92d27-5415-4b01-bdf0-0c91e7f1d636
⛔ Files ignored due to path filters (1)
sdk-typescript/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (9)
sdk-typescript/packages/kernel/package.jsonsdk-typescript/packages/kernel/src/__tests__/circuit-breaker.test.tssdk-typescript/packages/kernel/src/__tests__/client.test.tssdk-typescript/packages/kernel/src/__tests__/web-bot-auth.test.tssdk-typescript/packages/kernel/src/circuit-breaker.tssdk-typescript/packages/kernel/src/client.tssdk-typescript/packages/kernel/src/index.tssdk-typescript/packages/kernel/src/resilience.tssdk-typescript/packages/kernel/src/web-bot-auth.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
- GitHub Check: semgrep-cloud-platform/scan
- GitHub Check: Cursor Approval Agent: Pull Request Router and Approver
🧰 Additional context used
🔍 Remote MCP
Additional review context
-
Opossum’s
fire()returns aPromise; the breaker emitsreject,timeout,failure,open,close, andhalfOpen, transitions tohalfOpenafterresetTimeout, and uses a rolling stats window governed byrollingCountTimeoutandrollingCountBuckets. The docs also showvolumeThresholdcan block opening until enough requests exist, and list defaults oftimeout=10000,resetTimeout=30000,rollingCountTimeout=10000,rollingCountBuckets=10,errorThresholdPercentage=50,volumeThreshold=0. (nodeshift.dev) -
@noble/ed25519v3 docs say the main APIs work onUint8Array, withsignAsync/verifyAsync/getPublicKeyAsyncavailable; the v3 release notes explicitly say string hex inputs are prohibited. (npmjs.com) -
Review check: if
WebBotAuthConfig.privateKeyaccepts strings, the implementation should convert them to bytes before signing, or it will conflict with the current@noble/ed25519API expectations. (npmjs.com)
🔇 Additional comments (8)
sdk-typescript/packages/kernel/src/circuit-breaker.ts (1)
16-75: LGTM!Also applies to: 106-144
sdk-typescript/packages/kernel/src/__tests__/client.test.ts (2)
10-13: LGTM!Also applies to: 65-84, 96-101
86-94: 🎯 Functional CorrectnessNo issue here — hex keys are decoded to bytes before
signAsync, and the signing path is already covered insdk-typescript/packages/kernel/src/__tests__/web-bot-auth.test.ts.> Likely an incorrect or invalid review comment.sdk-typescript/packages/kernel/src/resilience.ts (1)
2-53: LGTM!Also applies to: 63-65, 80-92
sdk-typescript/packages/kernel/src/index.ts (1)
43-65: LGTM!sdk-typescript/packages/kernel/src/web-bot-auth.ts (1)
60-81: LGTM! RFC-9421 signature-base construction, base64/base64url helpers, and nonce generation are internally consistent (the exactparamsstring used for signing is the one emitted inSignature-Input), and match the@noble/ed25519v3 API (signAsync,etc.hexToBytes).Also applies to: 94-165
sdk-typescript/packages/kernel/src/__tests__/web-bot-auth.test.ts (1)
1-102: LGTM!sdk-typescript/packages/kernel/src/client.ts (1)
22-28: LGTM!run()correctly runs the action directly when no breaker is configured (unchanged behavior) and reports breaker errors via the injected hook before rethrowing; the conditionalfetchwiring preserves the SDK default when no signer is configured.Also applies to: 44-46, 62-76, 78-97
| "@noble/ed25519": "^3.1.0", | ||
| "@onkernel/sdk": "^0.78.0", | ||
| "opossum": "^10.0.0" |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd -HI -a '^(package\.json|\.nvmrc|\.node-version|\.tool-versions)$' . \
-x sh -c 'echo "== $1"; rg -n "\"engines\"|\"node\"|node-version" "$1" || true' sh {}
rg -n -g '*.{yml,yaml,json}' 'node-version|setup-node|NODE_VERSION|engines' . || trueRepository: wave-av/sdks
Length of output: 20928
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package.json (root and kernel)"
sed -n '1,120p' sdk-typescript/package.json | cat -n
echo
sed -n '1,120p' sdk-typescript/packages/kernel/package.json | cat -n
echo
echo "== lockfile entries for `@noble/ed25519` and opossum"
python3 - <<'PY'
from pathlib import Path
p = Path('sdk-typescript/pnpm-lock.yaml')
text = p.read_text()
for needle in ['`@noble/ed25519`', 'opossum']:
print(f"\n## {needle}")
idx = 0
while True:
idx = text.find(needle, idx)
if idx == -1:
break
start = text.rfind('\n', 0, idx)
end = text.find('\n', idx)
print(text[max(0, start-250):min(len(text), end+400)])
print("\n---")
idx += len(needle)
PYRepository: wave-av/sdks
Length of output: 9458
🏁 Script executed:
#!/bin/bash
set -euo pipefail
for needle in '`@noble/ed25519`' 'opossum'; do
echo "===== $needle ====="
rg -n -C 4 --fixed-strings "$needle" sdk-typescript/pnpm-lock.yaml
echo
doneRepository: wave-av/sdks
Length of output: 2788
🌐 Web query:
npm @noble/ed25519 3.1.0 engines node opossum 10.0.0 engines node
💡 Result:
The Node.js engine requirements for the specified package versions are as follows: - @noble/ed25519 version 3.1.0 requires Node.js v20.19 or later [1][2][3]. - opossum version 10.0.0 requires Node.js v22 or later (Node.js >= 22) [4][5]. This version introduced a breaking change by removing support for Node.js 20 [6][7].
Citations:
- 1: https://registry.npmjs.org/@noble/ed25519
- 2: https://jsr.io/@noble/ed25519
- 3: https://npmx.dev/package/@noble/ed25519/v/3.1.0
- 4: https://registry.npmjs.org/opossum
- 5: https://github.com/nodeshift/opossum
- 6: https://github.com/nodeshift/opossum/releases/tag/v10.0.0
- 7: https://github.com/nodeshift/opossum/blob/main/CHANGELOG.md
🌐 Web query:
site:npmjs.com opossum 10.0.0 engines node
💡 Result:
The opossum npm package, which serves as a circuit breaker for Node.js, specifies its Node.js engine requirement as ">=22" in its metadata [1]. This means that the package is intended for use in environments running Node.js version 22 or newer [1]. Users should ensure their Node.js environment meets this requirement to avoid potential compatibility issues during installation or execution [2][3].
Citations:
- 1: https://www.npmjs.com/package/opossum
- 2: https://docs.npmjs.com/cli/v10/configuring-npm/package-json/
- 3: https://docs.npmjs.com/cli/v10/using-npm/developers/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1426,1440p' sdk-typescript/pnpm-lock.yaml
echo '---'
sed -n '2210,2225p' sdk-typescript/pnpm-lock.yaml
echo '---'
sed -n '2374,2386p' sdk-typescript/pnpm-lock.yamlRepository: wave-av/sdks
Length of output: 1943
Raise the kernel Node engine floor. sdk-typescript/packages/kernel/package.json:65-71 still says node: >=18.0.0, but @noble/ed25519@3.1.0 needs Node 20.19+ and opossum@10.0.0 needs Node 22+, so this package no longer matches its declared runtime support. Either pin older compatible versions or bump the documented engines/CI baseline.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@sdk-typescript/packages/kernel/package.json` around lines 69 - 71, Update the
kernel package’s engines declaration and corresponding CI/runtime baseline to
require Node 22 or newer, matching the requirements of `@noble/ed25519` and
opossum; keep the dependency versions unchanged.
| const breadcrumb = options.captureBreadcrumb; | ||
| if (breadcrumb) { | ||
| this.breaker.on('open', () => | ||
| breadcrumb({ | ||
| category: this.serviceName, | ||
| message: 'Circuit breaker opened', | ||
| level: 'warning', | ||
| data: { state: 'open' }, | ||
| }), | ||
| ); | ||
| this.breaker.on('halfOpen', () => | ||
| breadcrumb({ | ||
| category: this.serviceName, | ||
| message: 'Circuit breaker half-open', | ||
| level: 'info', | ||
| data: { state: 'halfOpen' }, | ||
| }), | ||
| ); | ||
| this.breaker.on('close', () => | ||
| breadcrumb({ | ||
| category: this.serviceName, | ||
| message: 'Circuit breaker closed', | ||
| level: 'info', | ||
| data: { state: 'close' }, | ||
| }), | ||
| ); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Do not let breadcrumb sinks alter breaker behavior.
A throwing captureBreadcrumb escapes these synchronous lifecycle listeners, potentially replacing the action error or crashing on timer-driven transitions. Opossum emits these lifecycle events, and Node EventEmitter listeners run synchronously. (nodeshift.dev)
Proposed fix
const breadcrumb = options.captureBreadcrumb;
if (breadcrumb) {
+ const capture = (event: Parameters<CaptureBreadcrumb>[0]) => {
+ try {
+ void Promise.resolve(breadcrumb(event)).catch(() => undefined);
+ } catch {
+ // Observability must not affect circuit operation.
+ }
+ };
this.breaker.on('open', () =>
- breadcrumb({
+ capture({
category: this.serviceName,
message: 'Circuit breaker opened',
level: 'warning',
data: { state: 'open' },
}),
);
this.breaker.on('halfOpen', () =>
- breadcrumb({
+ capture({
category: this.serviceName,
message: 'Circuit breaker half-open',
level: 'info',
data: { state: 'halfOpen' },
}),
);
this.breaker.on('close', () =>
- breadcrumb({
+ capture({
category: this.serviceName,
message: 'Circuit breaker closed',
level: 'info',
data: { state: 'close' },Add a regression test with a throwing breadcrumb hook.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const breadcrumb = options.captureBreadcrumb; | |
| if (breadcrumb) { | |
| this.breaker.on('open', () => | |
| breadcrumb({ | |
| category: this.serviceName, | |
| message: 'Circuit breaker opened', | |
| level: 'warning', | |
| data: { state: 'open' }, | |
| }), | |
| ); | |
| this.breaker.on('halfOpen', () => | |
| breadcrumb({ | |
| category: this.serviceName, | |
| message: 'Circuit breaker half-open', | |
| level: 'info', | |
| data: { state: 'halfOpen' }, | |
| }), | |
| ); | |
| this.breaker.on('close', () => | |
| breadcrumb({ | |
| category: this.serviceName, | |
| message: 'Circuit breaker closed', | |
| level: 'info', | |
| data: { state: 'close' }, | |
| }), | |
| ); | |
| } | |
| const breadcrumb = options.captureBreadcrumb; | |
| if (breadcrumb) { | |
| const capture = (event: Parameters<CaptureBreadcrumb>[0]) => { | |
| try { | |
| void Promise.resolve(breadcrumb(event)).catch(() => undefined); | |
| } catch { | |
| // Observability must not affect circuit operation. | |
| } | |
| }; | |
| this.breaker.on('open', () => | |
| capture({ | |
| category: this.serviceName, | |
| message: 'Circuit breaker opened', | |
| level: 'warning', | |
| data: { state: 'open' }, | |
| }), | |
| ); | |
| this.breaker.on('halfOpen', () => | |
| capture({ | |
| category: this.serviceName, | |
| message: 'Circuit breaker half-open', | |
| level: 'info', | |
| data: { state: 'halfOpen' }, | |
| }), | |
| ); | |
| this.breaker.on('close', () => | |
| capture({ | |
| category: this.serviceName, | |
| message: 'Circuit breaker closed', | |
| level: 'info', | |
| data: { state: 'close' }, | |
| }), | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@sdk-typescript/packages/kernel/src/circuit-breaker.ts` around lines 77 - 103,
Isolate each captureBreadcrumb invocation registered on this.breaker so a
throwing breadcrumb sink cannot escape the synchronous open, halfOpen, or close
lifecycle listener or alter breaker behavior. Catch and suppress hook errors
while preserving the existing breaker transitions and breadcrumb payloads. Add a
regression test covering a throwing captureBreadcrumb during a lifecycle event,
including timer-driven transitions if supported by the existing test setup.
| function toBytes(key: Uint8Array | string): Uint8Array { | ||
| return typeof key === 'string' ? ed25519.etc.hexToBytes(key) : key; | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Constructor can throw synchronously, breaking the fail-open guarantee.
toBytes() calls ed25519.etc.hexToBytes() directly in the constructor (Line 87), outside any try/catch. A malformed hex string (odd length, non-hex chars) throws synchronously from new WebBotAuthSigner(...)/createWebBotAuthSigner(...) — i.e. at signer construction time, before any request or sign() call. This contradicts the file's own contract: "Signing is OPT-IN... and FAILS OPEN... A signing failure must never block a request." A bad key (e.g. malformed env var) crashes app init instead of degrading gracefully.
🛡️ Proposed fix — defer key conversion into the fail-open path
export class WebBotAuthSigner implements SignerLike {
- private readonly privateKey: Uint8Array;
-
- constructor(private readonly config: WebBotAuthConfig) {
- this.privateKey = toBytes(config.privateKey);
- }
+ constructor(private readonly config: WebBotAuthConfig) {}
async sign(input: {
method: string;
url: string;
headers: Record<string, string>;
}): Promise<Record<string, string>> {
try {
+ const privateKey = toBytes(this.config.privateKey);
const url = new URL(input.url);And use the local privateKey in the ed25519.signAsync(...) call instead of this.privateKey.
Also applies to: 83-88
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@sdk-typescript/packages/kernel/src/web-bot-auth.ts` around lines 56 - 58,
Defer string key conversion from the constructor into the fail-open signing path
so malformed hex cannot throw during WebBotAuthSigner construction. Update the
signing method to convert and use its local privateKey when calling
ed25519.signAsync, preserving graceful failure without blocking requests.
| return headers; | ||
| } catch (error) { | ||
| this.config.captureError?.(error, { | ||
| service: 'kernel', | ||
| component: 'web-bot-auth', | ||
| }); | ||
| return {}; | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
WebBotAuthConfig.captureError and ResilienceHooks.captureError are unwired — signing failures can go unreported. WebBotAuthSigner.sign() only reports errors through its own this.config.captureError (a hook set on WebBotAuthConfig), which is entirely separate from the resilience.captureError hook WaveKernel exposes. The documented integration path never connects the two, so a signing failure is silently swallowed with no observability, contradicting the PR's stated goal that signing "reports errors through the injected hook."
sdk-typescript/packages/kernel/src/web-bot-auth.ts#L166-L174: this is the root cause — the signer's fail-open catch has no path to the sharedresilience.captureErrorhook.sdk-typescript/packages/kernel/src/web-bot-auth.ts#L177-L194: update the JSDoc example to also pass a sharedcaptureErrorintocreateWebBotAuthSigner(...)(the same function passed toresilience.captureError), so the recommended usage doesn't silently drop errors.sdk-typescript/packages/kernel/src/client.ts#L99-L131: this catch'sresilience.captureErrorcall cannot observeWebBotAuthSigner's internal signing failures (only its own method/URL extraction errors here); note this limitation or haveWaveKernelinjectresilience.captureErrorinto the signer at construction if the signer type supports it.
📍 Affects 2 files
sdk-typescript/packages/kernel/src/web-bot-auth.ts#L166-L174(this comment)sdk-typescript/packages/kernel/src/web-bot-auth.ts#L177-L194sdk-typescript/packages/kernel/src/client.ts#L99-L131
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@sdk-typescript/packages/kernel/src/web-bot-auth.ts` around lines 166 - 174,
Wire WebBotAuthSigner.sign() failures to the shared resilience.captureError hook
so fail-open signing errors are observable, preferably by having WaveKernel
inject that hook when constructing the signer. In
sdk-typescript/packages/kernel/src/web-bot-auth.ts lines 166-174, update the
signer’s error path; in lines 177-194, update the JSDoc example to pass the same
captureError function to createWebBotAuthSigner and resilience.captureError. In
sdk-typescript/packages/kernel/src/client.ts lines 99-131, ensure WaveKernel
provides the shared hook to the signer, or explicitly document the remaining
limitation if the signer type cannot support injection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| signatureInput: string, | ||
| label = 'sig1', | ||
| ): string { | ||
| const params = signatureInput.replace(new RegExp(`^${label}=`), ''); |
There was a problem hiding this comment.
Semgrep identified an issue in your code:
RegExp() called with a label function argument, this might allow an attacker to cause a Regular Expression Denial-of-Service (ReDoS) within your application as RegExP blocks the main thread. For this reason, it is recommended to use hardcoded regexes instead. If your regex is run on user-controlled input, consider performing input validation or use a regex checking/sanitization library such as https://www.npmjs.com/package/recheck to verify that the regex does not appear vulnerable to ReDoS.
Dataflow graph
flowchart LR
classDef invis fill:white, stroke: none
classDef default fill:#e7f5ff, color:#1c7fd6, stroke: none
subgraph File0["<b>sdk-typescript/packages/kernel/src/__tests__/web-bot-auth.test.ts</b>"]
direction LR
%% Source
subgraph Source
direction LR
v0["<a href=https://github.com/wave-av/sdks/blob/7eab4af663bb1151efc7c9d1f8fbafa8894b86c5/sdk-typescript/packages/kernel/src/__tests__/web-bot-auth.test.ts#L21 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 21] label</a>"]
end
%% Intermediate
subgraph Traces0[Traces]
direction TB
v2["<a href=https://github.com/wave-av/sdks/blob/7eab4af663bb1151efc7c9d1f8fbafa8894b86c5/sdk-typescript/packages/kernel/src/__tests__/web-bot-auth.test.ts#L21 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 21] label</a>"]
v3["<a href=https://github.com/wave-av/sdks/blob/7eab4af663bb1151efc7c9d1f8fbafa8894b86c5/sdk-typescript/packages/kernel/src/__tests__/web-bot-auth.test.ts#L23 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 23] `</a>"]
end
v2 --> v3
%% Sink
subgraph Sink
direction LR
v1["<a href=https://github.com/wave-av/sdks/blob/7eab4af663bb1151efc7c9d1f8fbafa8894b86c5/sdk-typescript/packages/kernel/src/__tests__/web-bot-auth.test.ts#L23 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 23] new RegExp(`^${label}=`)</a>"]
end
end
%% Class Assignment
Source:::invis
Sink:::invis
Traces0:::invis
File0:::invis
%% Connections
Source --> Traces0
Traces0 --> Sink
To resolve this comment:
🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.
💬 Ignore this finding
Reply with Semgrep commands to ignore this finding.
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by detect-non-literal-regexp.
You can view more details about this finding in the Semgrep AppSec Platform.


Fills the inert slice-1 resilience seams in
@wave-av/kernelwith concrete, opt-in, inert-by-default implementations. A client constructed without aresilienceconfig behaves exactly like the slice-1 SDK wrapper — existing callers are unaffected.Layers
1. Circuit breaker (
createCircuitBreaker, opossum-backed)CircuitBreakerLikeseam. Params match the WAVE reference:errorThresholdPercentage 50/resetTimeout 30000/rollingCountTimeout 60000/volumeThreshold 5.WaveKernel.run(action)routes SDK actions through it when configured; when open it rejects fast withCircuitOpenError.timeout: false) — the SDK already owns request timeouts.2. Observability capture (injected, no hard dep)
@sentry/nextjs/ Next dependency — this SDK is consumed by non-Next hosts too.captureError/captureBreadcrumbhooks the consumer wires to their own reporter. Breaker open/halfOpen/close transitions emit breadcrumbs (category: 'kernel');run()errors go throughcaptureErrortaggedservice: 'kernel'.3. WebBotAuth signing (
createWebBotAuthSigner)SignerLikeseam: RFC-9421 HTTP Message Signatures with Ed25519 via@noble/ed25519(lightweight, maintained, runs on Node/browser/edge — no framework dep).fetch(the SDK exposes afetchoption), so it adapts to the@onkernel/sdkcall path rather than transplanting a fetch layer.Deps added
opossum ^10,@noble/ed25519 ^3(runtime);@types/opossum ^8(dev).Test coverage
12 → 26 tests: circuit-breaker (pass-through, error propagation, trip →
CircuitOpenError+ breadcrumb, serviceName naming), WebBotAuth (header shape,Signature-Agentcoverage, signature verifies against reconstructed base, fail-open), and client resilience wiring (inertrun(), breaker routing +captureError, signer construction).pnpm --filter @wave-av/kernel type-check/test/buildall exit 0.dist/cleaned before commit.🤖 Generated with Claude Code
Note
Medium Risk
Introduces request signing with private keys and changes failure/shedding behavior when callers opt into the breaker; fail-open signing limits blast radius but misconfiguration could send unsigned or fast-failing traffic.
Overview
Implements the previously inert resilience seams in
@wave-av/kernelwith concrete, opt-in layers. Clients withoutresilienceconfig behave like before.Circuit breaker: Adds
createCircuitBreaker(opossum) with WAVE-aligned defaults, open/half-open/close breadcrumbs via injectedcaptureBreadcrumb, andCircuitOpenErrorwhen shedding.WaveKernel.run()runs SDK actions through the breaker when configured.WebBotAuth: Adds
createWebBotAuthSigner(RFC-9421 Ed25519 via@noble/ed25519). When a signer is passed inresilience, the client wraps the SDKfetchto attach signature headers; signing fails open (unsigned request + optionalcaptureError).Observability: Formalizes
CaptureError/CaptureBreadcrumbonResilienceHooks(no hard Sentry dep). Breaker errors fromrun()and signing failures can be reported through injected hooks.Public exports and vitest coverage are expanded for breaker, signer, and client wiring. Runtime deps:
opossum,@noble/ed25519.Reviewed by Cursor Bugbot for commit bdf0897. Configure here.
Summary by cubic
Adds opt-in resilience to
@wave-av/kernel: a shared circuit breaker, injected observability hooks, and WebBotAuth request signing. Defaults keep behavior unchanged when no resilience config is provided.New Features
createCircuitBreaker(opossum):WaveKernel.run()routes calls through it, fast-fails when open, and emits breadcrumbs. Defaults: errorThresholdPercentage 50, resetTimeout 30s, rollingCountTimeout 60s, volumeThreshold 5; breaker timeout disabled.captureErrorandcaptureBreadcrumbhooks, no hard@sentry/nextjsdep; errors are taggedservice: 'kernel'.createWebBotAuthSigner(@noble/ed25519): RFC-9421 headers, wired through the SDKfetchoption, fail-open on errors; optionalSignature-Agentis covered.@pathis path-only and@queryis now included in the default covered components.Dependencies
opossum,@noble/ed25519; dev:@types/opossum.Written for commit 7eab4af. Summary will update on new commits.