fix(invariants): harden runtime contracts and gates

This commit is contained in:
Tianyi Cui
2026-07-21 00:25:38 +08:00
parent a4be4b5e52
commit 0c08e34a4a
27 files changed
+288 -111

No files matched your search

+5 -2
View File
@@ -7,6 +7,7 @@ import type { Context } from 'cordis'
import type { GenerateOptions } from '@deepseek-ai/dsh-llm'
import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants'
import { Session, SessionId, foldRequestHeader } from '@deepseek-ai/dsh-session'
import { isLoopRequest } from './request-marker.ts'
const PACKAGE_NAME = '@deepseek-ai/dsh-agent-loop'
@@ -20,9 +21,11 @@ const install: InvariantInstaller = Object.assign((ctx: Context, fail: Invariant
// Prepend prevents a short-circuiting replay listener from silencing the
// check; correctness itself comes from the sequence-bounded reconstruction.
ctx.on('llm/stream', (options: GenerateOptions, next) => {
if (options.sessionId === undefined || !Object.isFrozen(options)) return next()
if (!isLoopRequest(options)) return next()
if (!Object.isFrozen(options)) fail('a loop-built request must be frozen')
if (options.sessionId === undefined) fail('a loop-built request must carry a session id')
const session = ctx.sessions.get(options.sessionId)
if (!session) return next()
if (!session) fail(`a loop-built request must carry a live session id, got "${String(options.sessionId)}"`)
if (!Object.isFrozen(options.messages)) {
fail('a loop-built request must carry a frozen messages array')
}
+3 -2
View File
@@ -15,6 +15,7 @@ import { canonicalHeader } from '@deepseek-ai/dsh-session'
import type { Session, TurnEndReason, TurnTrigger } from '@deepseek-ai/dsh-session'
import { createTransmissionLog, recordRequestHeader } from './request-log.ts'
import type { TransmissionLog } from './request-log.ts'
import { markLoopRequest } from './request-marker.ts'
import { renderPrompt } from '@deepseek-ai/dsh-system-prompt'
import type { PromptAssembly } from '@deepseek-ai/dsh-system-prompt'
import type {} from '@deepseek-ai/dsh-tools'
@@ -619,7 +620,7 @@ async function runStep(
recordRequestHeader(session, transmission, header)
// Freeze the logged header plus boundary snapshot; the prefix precedes derived history.
const request: GenerateOptions = deepFreeze({
const request: GenerateOptions = deepFreeze(markLoopRequest({
provider: header.config.provider,
model: header.config.model,
messages: [...header.messagePrefix ?? [], ...boundaryMessages],
@@ -630,7 +631,7 @@ async function runStep(
...header.config.stop !== undefined ? { stop: header.config.stop } : {},
sessionId: session.id,
signal,
})
}))
// --- Model call (streaming-first; raw chunks are the replay record) ---
const assembler = new BlockAssembler()
@@ -0,0 +1,22 @@
/** Internal identity shared by the independently bundled loop and invariant companion. */
const LOOP_REQUEST = Symbol.for('@deepseek-ai/dsh-agent-loop/request')
/**
* Mark a request as owned by the agent loop before it is frozen.
* @param request - mutable request object being assembled by the loop.
* @returns the same request with a non-enumerable loop identity.
*/
export function markLoopRequest<T extends object>(request: T): T {
Object.defineProperty(request, LOOP_REQUEST, { value: true })
return request
}
/**
* Test whether a request carries the agent loop's internal identity.
* @param request - request observed at the LLM stream boundary.
* @returns whether the loop marked this exact request object.
*/
export function isLoopRequest(request: object): boolean {
return Reflect.get(request, LOOP_REQUEST) === true
}