From c4d9e2edb6a58bcb8bfa2b18765094bc9d663be2 Mon Sep 17 00:00:00 2001 From: pku-xht Date: Fri, 7 Aug 2026 23:11:25 +0800 Subject: [PATCH] fix(schedule): validate restored seed logs --- docs/event-producer-consumer.md | 2 +- .../ui-conversation/src/client/contract/slots.ts | 2 +- packages/schedule/tool-schedule/src/invariant.ts | 3 +++ .../schedule/tool-schedule/tests/invariant.spec.ts | 10 ++++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 5b36725402..2538221fc5 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -30,7 +30,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `goal/changed` | `emit` | [`packages/goal/goal/src/domain.ts:114`](../packages/goal/goal/src/domain.ts) | [`goal`](../packages/goal/goal) (`emit`) | [`goal-session`](../packages/goal/goal-session) | | `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/index.ts:73`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | `apiproxy`, [`llm`](../packages/llm/llm) | | `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:62`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`llm-replay`](../packages/support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) | -| `session/created` | `emit` | [`packages/core/session/src/index.ts:74`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | `apiproxy`, [`compact`](../packages/compact/compact), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission`](../packages/interaction/permission), [`plan-mode`](../packages/plan/plan-mode), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | +| `session/created` | `emit` | [`packages/core/session/src/index.ts:74`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | `apiproxy`, [`compact`](../packages/compact/compact), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission`](../packages/interaction/permission), [`plan-mode`](../packages/plan/plan-mode), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry), [`tool-schedule`](../packages/schedule/tool-schedule), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | `session/disposed` | `emit` | [`packages/core/session/src/index.ts:84`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`session-persistence`](../packages/session/session-persistence), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title) | | `session/event` | `emit` | [`packages/core/session/src/index.ts:96`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`compact`](../packages/compact/compact), [`compact-basic`](../packages/compact/compact-basic), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/support/loader-smoke), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`workspace-context`](../packages/context/workspace-context) | | `session/flush` | `parallel` | [`packages/core/session/src/index.ts:105`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry) | diff --git a/packages/client/ui-conversation/src/client/contract/slots.ts b/packages/client/ui-conversation/src/client/contract/slots.ts index 145ccd2b95..dd2c75c56d 100644 --- a/packages/client/ui-conversation/src/client/contract/slots.ts +++ b/packages/client/ui-conversation/src/client/contract/slots.ts @@ -43,7 +43,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { 'conversation.chat.tool': { kind: 'single'; scope: 'session'; owner: ToolTreeOwnerProps } /** * The chat view's per-event presentation hole: keyed dispatch on the - * Host-provided presentation key. The durable event remains in the + * durable event type. The durable event remains in the * runtime node; a feature plugin may replace the visible JSON fallback * with a domain renderer without entering ui-conversation. */ diff --git a/packages/schedule/tool-schedule/src/invariant.ts b/packages/schedule/tool-schedule/src/invariant.ts index ee804a56ef..2e7af53e1b 100644 --- a/packages/schedule/tool-schedule/src/invariant.ts +++ b/packages/schedule/tool-schedule/src/invariant.ts @@ -32,6 +32,9 @@ const install: InvariantInstaller = Object.assign((ctx: Context, fail: Invariant for (const session of ctx.sessions.list()) { validate(session.events, session.header.seedLength ?? 0, fail) } + ctx.on('session/created', (session) => { + validate(session.events, session.header.seedLength ?? 0, fail) + }, { global: true }) ctx.on('internal/dispatch', (_mode, eventName, args) => { if (eventName !== 'session/event') return const [session, event] = args as [Session, SessionEvent] diff --git a/packages/schedule/tool-schedule/tests/invariant.spec.ts b/packages/schedule/tool-schedule/tests/invariant.spec.ts index 31f6ac8dc7..cfc536bd48 100644 --- a/packages/schedule/tool-schedule/tests/invariant.spec.ts +++ b/packages/schedule/tool-schedule/tests/invariant.spec.ts @@ -64,6 +64,16 @@ describe('Schedule package invariant', () => { await ctx.fiber.dispose() }) + it('rejects a malformed seeded session created after companion setup', async () => { + const { ctx } = await harness() + const id = SessionId('schedule-invalid-future-seed') + expect(() => ctx.sessions.create(id, { + seed: [event({ version: 9, operation: 'delete', id: 'schedule-1' }, 0)], + })).toThrow(InvariantError) + expect(ctx.sessions.get(id)).toBeUndefined() + await ctx.fiber.dispose() + }) + it('ignores inherited Schedule events before a fork seed boundary', async () => { const ctx = new Context() await ctx.plugin(SessionStore)