Merge latest origin/master into feat/tui-package
Bring in the one-session surface manager landed through #376 after master advanced beyond the prior merge base. Resolve the generated event-matrix overlap by regenerating from the merged TypeScript tree, preserving master's updated session declaration locations together with the TUI listener edges. Regenerate and verify the RFC index so both lifecycle updates remain represented.
This commit is contained in:
@@ -257,7 +257,7 @@ Creation announcement during session publication. A synchronous throw vetoes and
|
||||
'session/created'(this: Scoped<Session>, session: Session): void
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/index.ts:46`](../../packages/core/session/src/index.ts)
|
||||
Source: [`packages/core/session/src/index.ts:47`](../../packages/core/session/src/index.ts)
|
||||
|
||||
### `session/disposed` — emit
|
||||
|
||||
@@ -267,7 +267,7 @@ Emitted once when an announced session leaves the store, including publication r
|
||||
'session/disposed'(this: Scoped<Session>, session: Session): void
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/index.ts:56`](../../packages/core/session/src/index.ts)
|
||||
Source: [`packages/core/session/src/index.ts:57`](../../packages/core/session/src/index.ts)
|
||||
|
||||
### `session/event` — emit
|
||||
|
||||
@@ -279,7 +279,7 @@ Post-commit, fire-and-forget append feed. The listener snapshot resolves before
|
||||
|
||||
Types: [SessionEvent](../core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/session/src/index.ts:68`](../../packages/core/session/src/index.ts)
|
||||
Source: [`packages/core/session/src/index.ts:69`](../../packages/core/session/src/index.ts)
|
||||
|
||||
### `session/flush` — parallel
|
||||
|
||||
@@ -289,7 +289,7 @@ Awaited parallel durability checkpoint: every listener runs and the caller await
|
||||
'session/flush'(this: Scoped<Session>, session: Session): Promise<void> | void
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/index.ts:78`](../../packages/core/session/src/index.ts)
|
||||
Source: [`packages/core/session/src/index.ts:79`](../../packages/core/session/src/index.ts)
|
||||
|
||||
## `subagent/*`
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ list(): Session[]
|
||||
fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId): Session
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/index.ts:585`](../../packages/core/session/src/index.ts)
|
||||
Source: [`packages/core/session/src/index.ts:577`](../../packages/core/session/src/index.ts)
|
||||
|
||||
## `ctx.skills` — `SkillService`
|
||||
|
||||
|
||||
@@ -194,9 +194,20 @@ Required for `SurfaceEventType` events — every message-producing event must de
|
||||
|
||||
The same provenance distinction applies here: only `assistant/message` may carry a present empty `sourceEventSeqs`; omission does not assert that its source stream was empty.
|
||||
|
||||
### `SessionSurface` — the live readonly surface projection
|
||||
|
||||
`Session.surface` returns the session's stable `SessionSurface` view. The same incremental manager validates append candidates before commit and advances this projection from committed events; callers can observe membership and replacement generation but cannot invoke validation.
|
||||
|
||||
```ts type-equiv
|
||||
export interface SessionSurface {
|
||||
readonly nodes: readonly number[]
|
||||
readonly replaceGeneration: number
|
||||
}
|
||||
```
|
||||
|
||||
### `SurfaceFoldReplacement` and `SurfaceFoldResult` — a complete surface replay
|
||||
|
||||
`foldSurface(events)` returns detached current event sequences together with the actual sequences shadowed by each declared replacement range. `SurfaceManager` uses the same transitions for its incremental cache without retaining replacement history. Its `replaceGeneration` increments for each replacement so incremental consumers can distinguish pure tail growth from a rewrite.
|
||||
`foldSurface(events)` returns detached current event sequences together with the actual sequences shadowed by each declared replacement range. The live manager uses the same transitions without retaining replacement history. Its `replaceGeneration` increments for each committed replacement so incremental consumers can distinguish pure tail growth from a rewrite.
|
||||
|
||||
```ts type-equiv
|
||||
export interface SurfaceFoldReplacement {
|
||||
|
||||
@@ -26,10 +26,10 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||
| `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:70`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`) | [`fs-policy`](../packages/fs/fs-policy) |
|
||||
| `fs/write-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:53`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`) | [`fs-policy`](../packages/fs/fs-policy) |
|
||||
| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:40`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`invariants`](../packages/support/invariants), [`llm-replay`](../packages/support/llm-replay) |
|
||||
| `session/created` | `emit` | [`packages/core/session/src/index.ts:46`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`invariants`](../packages/support/invariants), [`jsonrpc`](../packages/ui/jsonrpc), [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:56`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| `session/event` | `emit` | [`packages/core/session/src/index.ts:68`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/ui/acp), [`invariants`](../packages/support/invariants), [`jsonrpc`](../packages/ui/jsonrpc), [`session-persistence`](../packages/session-persistence/session-persistence), [`stdio`](../packages/ui/stdio), [`token-meter`](../packages/llm/token-meter), [`tui`](../packages/ui/tui), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:78`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| `session/created` | `emit` | [`packages/core/session/src/index.ts:47`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`invariants`](../packages/support/invariants), [`jsonrpc`](../packages/ui/jsonrpc), [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:57`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| `session/event` | `emit` | [`packages/core/session/src/index.ts:69`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/ui/acp), [`invariants`](../packages/support/invariants), [`jsonrpc`](../packages/ui/jsonrpc), [`session-persistence`](../packages/session-persistence/session-persistence), [`stdio`](../packages/ui/stdio), [`token-meter`](../packages/llm/token-meter), [`tui`](../packages/ui/tui), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:79`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:112`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`jsonrpc`](../packages/ui/jsonrpc) |
|
||||
| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:86`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:92`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
|
||||
@@ -23,7 +23,6 @@ Generated by `pnpm run gen-rfc-index` from the RFC tree — never edit by hand;
|
||||
| [Prune dead public and result surface](proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md) | 2026-07-04 |
|
||||
| [Make JSON-RPC completion and transport directional](proposed/simplification/2026-07-19-make-jsonrpc-directional.md) | 2026-07-19 |
|
||||
| [Retire the standalone subagent mock package](proposed/simplification/2026-07-19-retire-subagent-mock-package.md) | 2026-07-19 |
|
||||
| [Use one surface manager per session](proposed/simplification/2026-07-19-use-one-session-surface-manager.md) | 2026-07-19 |
|
||||
|
||||
### Architecture
|
||||
|
||||
@@ -118,6 +117,7 @@ Generated by `pnpm run gen-rfc-index` from the RFC tree — never edit by hand;
|
||||
| [Drop unconsumed skill provider events](implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md) | 2026-07-12 |
|
||||
| [Prune unused web seam fields](implemented/simplification/2026-07-12-prune-unused-web-seam-fields.md) | 2026-07-12 |
|
||||
| [Simplify session-log representation](implemented/simplification/2026-07-12-simplify-session-log-representation.md) | 2026-07-12 |
|
||||
| [Use one surface manager per session](implemented/simplification/2026-07-19-use-one-session-surface-manager.md) | 2026-07-19 |
|
||||
|
||||
### Architecture
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export type SurfaceOp =
|
||||
|
||||
### SurfaceManager: delta-based, not full rebuild
|
||||
|
||||
A `SurfaceManager` class (private to `Session`) maintains one ordered `number[]` of event seqs. It tracks `_lastProcessedSeq` and processes only the new events since the last access rather than rescanning the entire log. Because the log is append-only, prior events never change; a seeded log is simply the initial suffix folded on first access. Replace locates its inclusive endpoints by array position and splices the replacement seq into that range; no link objects or seq-to-node map duplicate the order.
|
||||
A `Session` owns one `SurfaceManager` that maintains an ordered `number[]` of event seqs. The manager validates each seed or append candidate without applying it before commit, then processes only committed events since its previous synchronization rather than rescanning the entire log. `Session.surface` exposes the same manager through the readonly `SessionSurface` contract, so acceptance, derived history, compaction, and workspace context share one incremental state. Replace locates its inclusive endpoints by array position and splices the replacement seq into that range; no second manager, link objects, or seq-to-node map duplicates the order.
|
||||
|
||||
Delta processing is O(1) when no new events and O(new events) when new events arrive.
|
||||
|
||||
@@ -60,7 +60,7 @@ Every surface-eligible event must carry `surfaceOp` or it would disappear from d
|
||||
|
||||
## Consequences
|
||||
|
||||
- **`packages/core/session`**: `surface.ts` (`SurfaceManager`) maintains one ordered seq array; `SurfaceOp`/`SurfaceIntent` and the top-level session-event fields record how entries join it. `append()` requires a `SurfaceIntent` for surface events, `deriveMessages()` walks the surface as the sole derivation path, and `repair.ts` emits surface-aware closers. The seed constructor rejects a surface-eligible seed event missing its `surfaceOp` marker (see § Invariants).
|
||||
- **`packages/core/session`**: `surface.ts` (`SurfaceManager`) maintains one ordered seq array for candidate acceptance and live projection; `SessionSurface` is its readonly public view. `SurfaceOp`/`SurfaceIntent` and the top-level session-event fields record how entries join it. `append()` requires a `SurfaceIntent` for surface events, `deriveMessages()` walks the surface as the sole derivation path, and `repair.ts` emits surface-aware closers. The seed constructor rejects a surface-eligible seed event missing its `surfaceOp` marker (see § Invariants).
|
||||
- **`packages/core/agent-loop`**: All surface-capable appends pass surface opts. Chunk seqs are collected for `assistant/message` provenance; `tool/call` seqs are captured for `tool/result` provenance.
|
||||
- **`packages/session-persistence/session-persistence-sqlite`**: Two new nullable TEXT columns (`source_event_seqs`, `surface_op`) on the `events` table; `SCHEMA_VERSION` bumped (bump-and-reject, no migration).
|
||||
- **`packages/support/invariants`**: Surface-related validation rules.
|
||||
|
||||
@@ -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
|
||||
2026-07-19-use-one-session-surface-manager.md: 0e4fa14b21f5054e1ace425712506c5c060f2251
|
||||
2026-07-19-use-one-session-surface-manager.zh.md: 26126310eefaf56aa45efc428a54e65f0cf35947
|
||||
2026-07-19-use-one-session-surface-manager.md: 0c0fd70717ff4b49e8817e88592d0781d9ccbd09
|
||||
2026-07-19-use-one-session-surface-manager.zh.md: 6d11c9bb475b0ee5a568f96678d0251f1160dda3
|
||||
@@ -0,0 +1,37 @@
|
||||
# RFC: Use one surface manager per session
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-07-19-use-one-session-surface-manager.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
`Session` maintained two `SurfaceManager` instances over the same append-only event log. One validated seed and append candidates, while a second lazy instance independently folded committed events for `session.surface`, derived messages, compaction, and workspace context. Once the public surface had been read, every later event advanced duplicate node and replacement-generation state without creating a separate authority or failure boundary.
|
||||
|
||||
## Decision
|
||||
|
||||
Each `Session` owns one eagerly constructed `SurfaceManager`. Seed and append acceptance call `validateNext()` on that manager before committing an event, and `session.surface` returns the same object through this readonly contract:
|
||||
|
||||
```ts
|
||||
export interface SessionSurface {
|
||||
readonly nodes: readonly number[]
|
||||
readonly replaceGeneration: number
|
||||
}
|
||||
```
|
||||
|
||||
Candidate validation remains atomic. `validateNext()` may synchronize committed log entries, but it only plans the uncommitted candidate. The candidate enters manager state after `log.push()` and the next delta synchronization, so surface validation failures and pre-commit `internal/dispatch` vetoes leave no phantom node or replacement generation.
|
||||
|
||||
`foldSurface()` remains the detached full-log replay function for offline validation and reconstruction. It uses the same transitions and agrees with the live manager for every committed prefix without sharing mutable state.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep acceptance and projection state separate.** Separate instances appeared to isolate public reads from validation, but callers already receive borrowed surface state and the declared readonly contract prevents ordinary mutation. Duplicating the manager was not a runtime trust boundary.
|
||||
|
||||
**Recompute the public surface from the full log on every access.** This removed duplicate cached state but gave up incremental derivation and made repeated request construction scale with complete session history.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Acceptance, `session.surface`, derived messages, compaction, and workspace context observe one incremental state.
|
||||
- `Session.surface` exposes no validation method, while its object identity and borrowed readonly node array remain stable.
|
||||
- A hostile cast can still corrupt borrowed state; JavaScript callers that deliberately bypass the readonly contract remain outside the supported same-process boundary.
|
||||
- Surface, seed, dispatch-veto, request-reconstruction, compaction, and workspace-context tests exercise the shared manager and detached replay paths.
|
||||
@@ -0,0 +1,37 @@
|
||||
# RFC: 每个会话只使用一个表层管理器
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-07-19-use-one-session-surface-manager.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
`Session` 曾针对同一份仅追加事件日志维护两个 `SurfaceManager` 实例。一个实例负责校验种子事件和追加候选事件,另一个延迟创建的实例则独立折叠已提交事件,供 `session.surface`、派生消息、压缩(compaction)和工作区上下文使用。一旦读取公共表层,之后的每个事件都会推进两份重复的节点状态与替换代数状态,却没有形成独立真源或失败边界。
|
||||
|
||||
## 决策
|
||||
|
||||
每个 `Session` 主动创建并只持有一个 `SurfaceManager`。种子事件与追加事件的接纳流程在提交事件之前调用该管理器的 `validateNext()`,`session.surface` 则通过以下只读契约返回同一个对象:
|
||||
|
||||
```ts
|
||||
export interface SessionSurface {
|
||||
readonly nodes: readonly number[]
|
||||
readonly replaceGeneration: number
|
||||
}
|
||||
```
|
||||
|
||||
候选事件校验仍保持原子性。`validateNext()` 可以同步已提交的日志事件,但对尚未提交的候选事件只制定变更计划。候选事件在 `log.push()` 之后、下一次增量同步时才进入管理器状态,因此表层校验失败或提交前 `internal/dispatch` 否决都不会留下虚假节点或替换代数。
|
||||
|
||||
`foldSurface()` 仍是离线校验与重建使用的分离式完整日志回放函数。它使用相同的状态转换,并且对每个已提交前缀都与活跃管理器一致,但不共享可变状态。
|
||||
|
||||
## 备选方案
|
||||
|
||||
**继续分离接纳状态与投影视图。** 两个独立实例看似能够隔离公共读取和校验,但调用方取得的本来就是借用的表层状态,声明的只读契约会阻止普通修改。复制管理器并不能构成运行时信任边界。
|
||||
|
||||
**每次读取都根据完整日志重新计算公共表层。** 该方案能消除重复缓存状态,但会放弃增量派生,使每次请求构造都随完整会话历史增长。
|
||||
|
||||
## 影响
|
||||
|
||||
- 接纳流程、`session.surface`、派生消息、压缩和工作区上下文观察同一份增量状态。
|
||||
- `Session.surface` 不暴露校验方法,同时保持对象标识和借用的只读节点数组稳定。
|
||||
- 恶意类型断言仍可破坏借用状态;刻意绕过只读契约的 JavaScript 调用方不属于受支持的同进程边界。
|
||||
- 表层、种子、调度否决、请求重建、压缩和工作区上下文测试覆盖共享管理器与分离回放路径。
|
||||
@@ -1,43 +0,0 @@
|
||||
# RFC: Use one surface manager per session
|
||||
|
||||
Status: proposed
|
||||
|
||||
English | [中文](2026-07-19-use-one-session-surface-manager.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
`Session` maintains two `SurfaceManager` instances over the same append-only event log. `surfaceValidator` eagerly validates seed and append candidates, while the lazy `_surface` independently folds committed events for `session.surface`, derived messages, compaction, and workspace context. Once the public surface is read, every later event advances duplicate node and replacement-generation state.
|
||||
|
||||
The [session surface decision](../../implemented/architecture/2026-06-18-session-surface.md) calls for one ordered surface and one representation to validate. The second manager does not create an independent authority or protect a different failure boundary; it repeats the canonical fold and gives the two views a state-drift opportunity.
|
||||
|
||||
## Proposal
|
||||
|
||||
Keep one `SurfaceManager` per `Session`. Seed and append acceptance continue to call `validateNext()` before committing an event, and the public surface view reads `nodes` and `replaceGeneration` from that same manager.
|
||||
|
||||
Expose only the readonly surface contract from `Session.surface`; candidate validation remains owned by `Session`. Retain `foldSurface()` as the detached full-log replay function used by offline validation and reconstruction.
|
||||
|
||||
## Implementation plan
|
||||
|
||||
1. In `packages/core/session/src/surface.ts`, export a structural `SessionSurface` contract containing only readonly `nodes` and `replaceGeneration`, and make `SurfaceManager` implement it. Re-export that type from `packages/core/session/src/index.ts` so `Session.surface` no longer exposes `validateNext()` through its declaration.
|
||||
2. In `Session`, replace `surfaceValidator` and lazy `_surface` with one eagerly constructed `surfaceManager`. Route seed and append validation through that manager and return it from `get surface(): SessionSurface`; `deriveMessages()` will read the same nodes and generation. `validateNext()` may synchronize already committed log entries, but it must only plan the uncommitted candidate. The candidate reaches manager state after `log.push()` and the next delta synchronization, so rejection by surface validation or pre-commit `internal/dispatch` cannot leave phantom state.
|
||||
3. Keep `foldSurface()` and the transition functions in `surface.ts` unchanged. Compile and exercise the direct consumers in `packages/compact/compact/src/tool-pairing.ts`, `packages/compact/compact-basic/src/region.ts`, and `packages/context/workspace-context/src/state.ts`; they continue to consume only nodes and replacement generation.
|
||||
4. Extend `packages/core/session/tests/surface.spec.ts` to read the public view before an invalid candidate, prove that nodes and generation remain at the accepted prefix after rejection, append a later valid event, and compare every resulting prefix with `foldSurface()`. Add an `internal/dispatch` veto case and a type-level `SessionSurface` assertion in `session.spec.ts`, while retaining the seeded replay, delta-growth, replacement, generation, and derived-cache cases.
|
||||
5. Run the request-reconstruction, compaction tool-pairing, compaction range, and workspace-context regression suites that consume the surface. In the implementation PR, update `packages/core/session/README.md`, `docs/core-data-structures/session.md`, the implemented session-surface RFC and its Chinese counterpart, the translation record, `scripts/type-equiv.manifest.json`, and the generated RFC index before moving this RFC pair to `implemented/`.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep acceptance and projection state separate.** Separate instances appear to isolate public reads from validation, but ordinary callers already receive borrowed surface state and cannot mutate it through the declared readonly contract. A cast that mutates the returned node array already corrupts derived history; duplicating the manager is not a sound runtime trust boundary.
|
||||
|
||||
**Recompute the public surface from the full log on every access.** This removes cached duplicate state but gives up incremental derivation and makes repeated request construction scale with complete session history.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- A live `Session` owns exactly one incremental `SurfaceManager`.
|
||||
- Seed and append candidates are validated before publication with no partial surface mutation on rejection.
|
||||
- `session.surface`, derived messages, compaction, and workspace context observe the same nodes and replacement generation as the acceptance path.
|
||||
- `foldSurface()` remains available for detached replay and agrees with the live manager for every accepted prefix.
|
||||
- Session surface, seed, request reconstruction, compaction tool-pairing, and workspace-context tests pass.
|
||||
|
||||
## Risks
|
||||
|
||||
Sharing one manager makes the readonly borrowed-state contract more important because a hostile cast could corrupt both validation and projection state. The implementation should return a narrowed view and keep mutation methods inaccessible through `Session.surface`; JavaScript callers that deliberately bypass the type contract remain outside the supported same-process boundary.
|
||||
@@ -1,43 +0,0 @@
|
||||
# RFC: 每个会话只使用一个表层管理器
|
||||
|
||||
Status: proposed
|
||||
|
||||
[English](2026-07-19-use-one-session-surface-manager.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
`Session` 针对同一份仅追加事件日志维护两个 `SurfaceManager` 实例。`surfaceValidator` 主动校验种子事件与追加候选事件,延迟创建的 `_surface` 则独立折叠已提交事件,供 `session.surface`、派生消息、压缩(compaction)和工作区上下文使用。一旦读取公共表层,之后的每个事件都会推进两份重复的节点状态与替换代数状态。
|
||||
|
||||
[会话表层决策](../../implemented/architecture/2026-06-18-session-surface.md)要求系统只保留一个有序表层,并使用一种表示完成校验。第二个管理器既不形成独立真源,也不保护不同的失败边界;它只会重复规范折叠,并使两个视图可能出现状态偏差。
|
||||
|
||||
## 提案
|
||||
|
||||
每个 `Session` 只保留一个 `SurfaceManager`。种子事件与追加事件的接纳流程仍在提交事件之前调用 `validateNext()`,公共表层视图则从同一个管理器读取 `nodes` 与 `replaceGeneration`。
|
||||
|
||||
`Session.surface` 只公开只读表层契约,候选事件校验仍由 `Session` 负责。保留 `foldSurface()`,用于离线校验与重建时执行分离的完整日志回放。
|
||||
|
||||
## 实施计划
|
||||
|
||||
1. 在 `packages/core/session/src/surface.ts` 中,导出结构化的 `SessionSurface` 契约,只包含只读的 `nodes` 与 `replaceGeneration`,并让 `SurfaceManager` 实现该契约。从 `packages/core/session/src/index.ts` 重新导出这个类型,使 `Session.surface` 的声明不再暴露 `validateNext()`。
|
||||
2. 在 `Session` 中,用一个主动创建的 `surfaceManager` 替换 `surfaceValidator` 与延迟创建的 `_surface`。种子事件与追加事件都通过该管理器校验,`get surface(): SessionSurface` 返回同一个对象,`deriveMessages()` 也读取同一份节点与代数。`validateNext()` 可以同步已提交的日志事件,但对尚未提交的候选事件只能制定变更计划。候选事件在 `log.push()` 之后、下一次增量同步时才进入管理器状态,因此表层校验拒绝或提交前 `internal/dispatch` 否决都不会留下虚假状态。
|
||||
3. 保持 `foldSurface()` 与 `surface.ts` 中的状态转换函数不变。编译并验证 `packages/compact/compact/src/tool-pairing.ts`、`packages/compact/compact-basic/src/region.ts` 和 `packages/context/workspace-context/src/state.ts` 中的直接消费方;它们仍然只读取节点与替换代数。
|
||||
4. 扩展 `packages/core/session/tests/surface.spec.ts`:先读取公共视图,再提交无效候选事件,证明拒绝后节点与代数仍停留在已接纳前缀;随后追加有效事件,并把每个结果前缀与 `foldSurface()` 比较。在 `session.spec.ts` 中新增 `internal/dispatch` 否决用例与类型层面的 `SessionSurface` 断言,同时保留种子回放、增量增长、替换、代数和派生缓存用例。
|
||||
5. 运行消费表层的请求重建、压缩工具配对、压缩范围与工作区上下文回归套件。在实现 PR 中,先更新 `packages/core/session/README.md`、`docs/core-data-structures/session.md`、已实现会话表层 RFC 及其中文对应文件、翻译记录、`scripts/type-equiv.manifest.json` 和生成的 RFC 索引,再把本 RFC 双语文件移入 `implemented/`。
|
||||
|
||||
## 备选方案
|
||||
|
||||
**继续分离接纳状态与投影视图。** 两个独立实例看似能够隔离公共读取和校验,但普通调用方目前取得的就是借用的表层状态,无法通过声明的只读契约修改它。通过类型断言修改返回的节点数组,本就会破坏派生历史;复制管理器并不能构成可靠的运行时信任边界。
|
||||
|
||||
**每次读取都根据完整日志重新计算公共表层。** 该方案不再缓存重复状态,但会放弃增量派生,使每次请求构造都随完整会话历史增长。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 每个活跃 `Session` 只拥有一个增量 `SurfaceManager`。
|
||||
- 种子事件与追加候选事件都在发布前完成校验,拒绝事件时不会留下只修改一半的表层状态。
|
||||
- `session.surface`、派生消息、压缩和工作区上下文观察到的节点与替换代数,和接纳路径使用的状态完全一致。
|
||||
- `foldSurface()` 仍可用于分离回放,并且对任意已接纳前缀都与活跃管理器一致。
|
||||
- 会话表层、种子、请求重建、压缩工具配对和工作区上下文测试全部通过。
|
||||
|
||||
## 风险
|
||||
|
||||
共享一个管理器会提高只读借用状态契约的重要性,因为恶意类型断言可能同时破坏校验状态和投影视图。实现应返回收窄后的视图,避免通过 `Session.surface` 暴露修改方法;刻意绕过类型契约的 JavaScript 调用方不属于受支持的同进程边界。
|
||||
Reference in New Issue
Block a user