fix(review): harden web replay verification

Validate replay sidecars and cross-copy failure facts, make browser console tripwires and macOS temp paths deterministic, and wait for asynchronous TUI resume details. Keep the owning docs, translations, and generated catalog aligned.
This commit is contained in:
Tianyi Cui
2026-07-26 22:38:33 +08:00
parent b67ecc45fc
commit e2882f486b
20 changed files with 310 additions and 132 deletions

View File

@@ -47,13 +47,10 @@ export function markLlmAdapterFailure(
const error = value instanceof Error
? value as Error & { code?: string }
: new HarnessError(String(value), 'UNKNOWN', { cause: value })
// The own `failure` data property is the serializable boundary contract:
// validated field-by-field and cross-checked against the error's own code,
// then honored on ANY Error — an instanceof gate here would drop the facts
// exactly when class identity is lost (a second copy of this package in
// the process, e.g. a source-plane test harness over a lib-plane boot).
// Cross-package copies preserve own data but not class identity. Trust the
// carried facts only when both own properties agree after validation.
const carried = ownFailureSnapshot(error)
const failure = carried !== undefined && carried.code === foreignErrorCode(error) ? carried : Object.freeze({
const failure = carried !== undefined && carried.code === ownErrorCode(error) ? carried : Object.freeze({
message: errorMessage(error),
code: harnessErrorCode(error),
})
@@ -61,13 +58,12 @@ export function markLlmAdapterFailure(
return error
}
/** Read a foreign error's `code` for the cross-check without letting an SDK accessor replace the primary failure. */
function foreignErrorCode(error: Error & { code?: string }): unknown {
/** Read a foreign error's own data-backed `code` without invoking accessors. */
function ownErrorCode(error: Error): unknown {
try {
return error.code
} catch (_sdkCodeGetter) {
// An unreadable code cannot confirm the carried facts describe this
// error; the caller falls back to the normalized snapshot.
const descriptor = Object.getOwnPropertyDescriptor(error, 'code')
return descriptor !== undefined && 'value' in descriptor ? descriptor.value : undefined
} catch (_sdkPropertyTrap) {
return undefined
}
}

View File

@@ -291,6 +291,34 @@ describe('LlmService', () => {
expect(facts).not.toBe(carried)
})
it('keeps validated failure facts across package copies with matching own codes', async () => {
const original = Object.assign(new Error('provider busy'), {
code: 'RATE_LIMIT',
failure: {
message: 'provider busy',
code: 'RATE_LIMIT',
status: 429,
providerRetryAfterMs: 1_500,
requestId: 'req-cross-copy',
},
})
const ctx = new Context()
await ctx.plugin(LlmService)
ctx.llm.registerAdapter(['test-provider'], new ThrowingAdapter(original))
const stream = ctx.llm.stream({ provider: 'test-provider', model: 'test-model', messages: [] })
await expect((async () => {
for await (const _chunk of stream) { /* drain */ }
})()).rejects.toBe(original)
expect(llmFailureOf(stream, original)).toEqual({
message: 'provider busy',
code: 'RATE_LIMIT',
status: 429,
providerRetryAfterMs: 1_500,
requestId: 'req-cross-copy',
})
})
it('keeps an unknown SDK Error exact without trusting its private code or accessors', async () => {
const original = Object.assign(new Error('socket closed'), { code: 'ECONNRESET' })
Object.defineProperty(original, 'failure', {
@@ -324,10 +352,7 @@ describe('LlmService', () => {
expect(llmFailureOf(stream, original)).toEqual({ message: 'LLM adapter failed', code: 'UNKNOWN' })
})
it('keeps an SDK Error exact when a valid failure payload rides a hostile code accessor', async () => {
// The carried-facts cross-check reads error.code; a throwing accessor
// there must fall back to the normalized snapshot instead of replacing
// the original adapter error with the accessor exception.
it('keeps an SDK Error exact without trusting accessor-backed carried facts', async () => {
const original = Object.assign(new Error('busy'), {
failure: { message: 'busy', code: 'SERVER', status: 503 },
})
@@ -345,6 +370,46 @@ describe('LlmService', () => {
expect(llmFailureOf(stream, original)).toEqual({ message: 'busy', code: 'UNKNOWN' })
})
it('does not trust carried facts matched only by an inherited code', async () => {
class InheritedCodeError extends Error {
get code(): string { return 'SERVER' }
}
const original = Object.assign(new InheritedCodeError('busy'), {
failure: { message: 'busy', code: 'SERVER', status: 503 },
})
const ctx = new Context()
await ctx.plugin(LlmService)
ctx.llm.registerAdapter(['test-provider'], new ThrowingAdapter(original))
const stream = ctx.llm.stream({ provider: 'test-provider', model: 'test-model', messages: [] })
await expect((async () => {
for await (const _chunk of stream) { /* drain */ }
})()).rejects.toBe(original)
expect(llmFailureOf(stream, original)).toEqual({ message: 'busy', code: 'UNKNOWN' })
})
it('keeps an SDK Error exact when code descriptor inspection is trapped', async () => {
const target = Object.assign(new Error('busy'), {
code: 'SERVER',
failure: { message: 'busy', code: 'SERVER', status: 503 },
})
const original = new Proxy(target, {
getOwnPropertyDescriptor(value, property) {
if (property === 'code') throw new Error('SDK code descriptor trap')
return Reflect.getOwnPropertyDescriptor(value, property)
},
})
const ctx = new Context()
await ctx.plugin(LlmService)
ctx.llm.registerAdapter(['test-provider'], new ThrowingAdapter(original))
const stream = ctx.llm.stream({ provider: 'test-provider', model: 'test-model', messages: [] })
await expect((async () => {
for await (const _chunk of stream) { /* drain */ }
})()).rejects.toBe(original)
expect(llmFailureOf(stream, original)).toEqual({ message: 'busy', code: 'UNKNOWN' })
})
it('falls back safely when SDK objects trap failure inspection or expose malformed facts', async () => {
const propertyTrap = new Proxy(new HarnessError('descriptor trapped', 'SERVER'), {
getOwnPropertyDescriptor(target, property) {