fix: revert the projection-registration form per review

Per the imccyu review, the pre-existing goal, permissions, and plan units
go back to registering through the ctx.inject(['sessionProjections'], …)
child form instead of the required-inject direct register; goal returns to
zero diff (its service never reads projections). The mandatory-seam rework
for these three sites moves to a follow-up PR. New projection units keep
the required-inject direct-register form.

Regenerated catalog and subsystem docs follow the reverted service
signatures.
This commit is contained in:
_Kerman
2026-08-19 17:46:02 +08:00
parent abe572e7c4
commit 2a4f6541d6
9 changed files with 45 additions and 33 deletions
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/goal.md
goal.md: e5c8d77e7a897b097e7801f65024612d233319a9
goal.zh.md: a4c8f26173a1ed4b991beaa02de7894258f50b6d
goal.md: 676b2f49d00681ac7f05b894cda0157ebba4cfcd
goal.zh.md: 1d46bc2c9ba1f3b35026ff132d323e46ccd3bc19
+1 -1
View File
@@ -247,7 +247,7 @@ block(agent: Agent, ref: GoalRef, reason: GoalBlockReason): GoalView
Types: [Agent](core.md)
Source: [`packages/goal/goal/src/index.ts:182`](../../packages/goal/goal/src/index.ts)
Source: [`packages/goal/goal/src/index.ts:183`](../../packages/goal/goal/src/index.ts)
<a id="goal-events"></a>
+1 -1
View File
@@ -247,7 +247,7 @@ block(agent: Agent, ref: GoalRef, reason: GoalBlockReason): GoalView
Types: [Agent](core.md)
Source: [`packages/goal/goal/src/index.ts:182`](../../packages/goal/goal/src/index.ts)
Source: [`packages/goal/goal/src/index.ts:183`](../../packages/goal/goal/src/index.ts)
<a id="goal-events"></a>
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/session-projection.md
session-projection.md: a0704206904880259425b6b32b648d383d815fb2
session-projection.zh.md: 74392356d980e83b1ca281732b916f7d9e678702
session-projection.md: edc1d646c01cb95ccc090f69f3868e466c346fdf
session-projection.zh.md: 9c10737ef8b6d409155039632da86f0573c05f13
+15 -9
View File
@@ -13,6 +13,7 @@ import { agentEvents } from '@deepseek-ai/dsh-agent'
import type { Agent } from '@deepseek-ai/dsh-agent'
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
import { TypertRemoteService, Remote } from '@deepseek-ai/dsh-typert-protocol'
// Type-only: resolves ctx.sessionProjections for the optional unit child.
import type {} from '@deepseek-ai/dsh-session-projection'
import {
applyGoalEvent,
@@ -49,7 +50,7 @@ import type {
// The pure payload outlet (./types.ts, ONE home of the `goal` projection-key
// declaration) re-exported onto the package root keeps the module edge in
// the emitted index.d.ts, so aggregate programs consuming the declarations
// still receive the SessionProjectionStateMap merge.
// still receive the SessionProjectionMap merge.
export type * from './types.ts'
export type * from './domain.ts'
export { GOAL_CHANGE_VERSION, GoalError, GoalId } from './runtime.ts'
@@ -180,7 +181,7 @@ function resolveBlockReason(reason: unknown): GoalBlockReason {
/** Goal service (`ctx.goals`) backed exclusively by the owning session log. */
export class GoalService extends TypertRemoteService {
static inject = ['agents', 'sessionProjections']
static inject = ['agents']
static Config: z<Config> = z.object({
defaultMaxGoalRounds: z.number().default(256),
@@ -197,13 +198,18 @@ export class GoalService extends TypertRemoteService {
ctx.on('agent/session-start', ({ agent }) => {
this.cache(agent.session).activation = 'disarmed'
})
ctx.sessionProjections.register<'goal', GoalProjection | null>({
key: 'goal',
stateVersion: 4,
stateSchema: goalProjectionSchema,
init: () => null,
apply: applyGoalProjection,
wire: { viewSchema: goalProjectionSchema, view: state => state },
// The `goal` projection unit: last-wins fold of goal/change whole values
// (see applyGoalProjection). The unit child activates only when a
// projection registry is composed (headless assemblies stay unaffected).
ctx.inject(['sessionProjections'], (projectionCtx) => {
projectionCtx.sessionProjections.register<'goal', GoalProjection | null>({
key: 'goal',
stateSchema: goalProjectionSchema,
init: () => null,
apply: applyGoalProjection,
wire: { viewSchema: goalProjectionSchema, view: state => state },
stateVersion: 4,
})
})
}
-9
View File
@@ -4,7 +4,6 @@ import AgentRegistry, { agentEvents, Inbox } from '@deepseek-ai/dsh-agent'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { createUserMessage, HarnessError } from '@deepseek-ai/dsh-llm'
import SessionStore, { Session, SessionId, type UserMessage } from '@deepseek-ai/dsh-session'
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
import GoalService, {
GoalError,
GoalId,
@@ -61,7 +60,6 @@ function stubAgent(rawId: string, seed?: readonly import('@deepseek-ai/dsh-sessi
async function harness(config: { defaultMaxGoalRounds?: number } = {}) {
const ctx = new Context()
await ctx.plugin(AgentRegistry)
await ctx.plugin(SessionProjectionRegistry)
await ctx.plugin(GoalService, config)
const stub = stubAgent(`goal-test-${Math.random()}`)
ctx.agents.register(stub.agent)
@@ -133,7 +131,6 @@ describe('GoalService creation and replay', () => {
it('also resolves the default when constructed directly without Cordis config normalization', async () => {
const ctx = new Context()
await ctx.plugin(AgentRegistry)
await ctx.plugin(SessionProjectionRegistry)
const goals = new GoalService(ctx)
const stub = stubAgent('goal-direct-construction')
ctx.agents.register(stub.agent)
@@ -145,7 +142,6 @@ describe('GoalService creation and replay', () => {
it('rejects invalid direct configuration', async () => {
const ctx = new Context()
await ctx.plugin(AgentRegistry)
await ctx.plugin(SessionProjectionRegistry)
await expect(ctx.plugin(GoalService, { defaultMaxGoalRounds: -1 })).rejects.toThrow(expect.objectContaining({
code: 'GOAL_INVALID_MAX_ROUNDS',
}))
@@ -159,7 +155,6 @@ describe('GoalService creation and replay', () => {
const ctx = new Context()
await ctx.plugin(AgentRegistry)
await ctx.plugin(SessionProjectionRegistry)
await ctx.plugin(GoalService)
const resumed = stubAgent('seeded-goal', first.session.events)
ctx.agents.register(resumed.agent)
@@ -174,7 +169,6 @@ describe('GoalService creation and replay', () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(AgentRegistry)
await ctx.plugin(SessionProjectionRegistry)
await ctx.plugin(GoalService)
const parent = stubAgentForSession(ctx.sessions.create(SessionId('goal-fork-parent')))
ctx.agents.register(parent.agent)
@@ -221,7 +215,6 @@ describe('GoalService creation and replay', () => {
it('removes the service and its session-start listener with the providing fiber', async () => {
const ctx = new Context()
await ctx.plugin(AgentRegistry)
await ctx.plugin(SessionProjectionRegistry)
const fiber = await ctx.plugin(GoalService)
const first = ctx.goals
const stub = stubAgent('goal-hmr')
@@ -430,7 +423,6 @@ describe('GoalService mutations', () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(AgentRegistry)
await ctx.plugin(SessionProjectionRegistry)
await ctx.plugin(GoalService)
const stub = stubAgentForSession(ctx.sessions.create(SessionId('goal-reentrant-observer')))
ctx.agents.register(stub.agent)
@@ -449,7 +441,6 @@ describe('GoalService mutations', () => {
it('does not delegate goal persistence to agent injection', async () => {
const ctx = new Context()
await ctx.plugin(AgentRegistry)
await ctx.plugin(SessionProjectionRegistry)
await ctx.plugin(GoalService)
const stub = stubAgent('goal-independent-injection')
stub.agent.inject = () => { throw new Error('injection must not be called') }
+9 -1
View File
@@ -1,4 +1,12 @@
/** Goal projection behavior. */
/**
* The `goal` projection unit: mounting GoalService beside the registry
* serves the current whole goal on the history tail page with a consistent
* asOfSeq; before the first create the value is null; a clear tombstone
* returns it to null; a composition without the goal service has no `goal`
* key; unmounting drops it (HMR safety). Malformed goal-shaped events are
* ignored fail-soft (same-reference return) — strict replay validation
* belongs to the write side and foldGoal, never the projection drive.
*/
import { describe, expect, it, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
@@ -208,13 +208,17 @@ export class PermissionPresetService extends Service {
})),
currentValue: zod.string().min(1),
}) as unknown as zod.ZodType<PermissionSelect>
ctx.sessionProjections.register({
key: 'permissions',
stateVersion: 1,
stateSchema: knobStateSchema,
init: () => EMPTY_KNOBS,
apply: applyKnobEvent,
wire: { viewSchema: selectSchema, view: state => this.selectFor(state) },
// The `permissions` projection unit folds the three whole-value knob
// events; it registers through the projection registry.
ctx.inject(['sessionProjections'], (projectionCtx) => {
projectionCtx.sessionProjections.register({
key: 'permissions',
stateVersion: 1,
stateSchema: knobStateSchema,
init: () => EMPTY_KNOBS,
apply: applyKnobEvent,
wire: { viewSchema: selectSchema, view: state => this.selectFor(state) },
})
})
// The /permission command: the one write path a web client uses (the
+4 -1
View File
@@ -217,7 +217,10 @@ export class PlanModeController extends Service {
},
})
ctx.sessionProjections.register(planProjectionDefinition)
// The `plan` projection unit registers through the projection registry.
ctx.inject(['sessionProjections'], (projectionCtx) => {
projectionCtx.sessionProjections.register(planProjectionDefinition)
})
// The command child activates only when a command registry is composed.
ctx.inject(['commands'], (commandCtx) => {