fix(settings): close cross-namespace, dispatch, and lifecycle races from second review
Confirmed and fixed, each with a regression test that failed first: - Concurrent writes to different namespaces lost whole sections on disk (each persist rendered the full document from a stale text): the local provider serializes render->write->rename->text-commit on one internal persist chain shared by every namespace queue. - One throwing settings/updated listener starved the rest (cordis emit stops at the first throw): commit fans out per listener via events.dispatch, contains individual failures, and rethrows the first INVARIANT-coded error only after every listener ran. - Write queues ignored fiber/service lifecycle: the base init now registers a teardown that refuses new writes and drains queued chains; queued tasks re-verify service liveness and namespace ownership before running and again before committing, so a registrant disposed mid-flight is never notified and a disposed service never commits. - Async watcher invocations could interleave (a slow stale call applied last): each watcher carries a serialized invocation chain — one call at a time, in commit order; JSDoc/doc pages state the async timing. - update/replace borrowed the caller's object until the queued task ran: inputs are structured-clone snapshotted at call time; non-cloneable plain objects reject with a typed error. - Composition guard now proves the documented fallback: the consumer uses the optional scoped-inject shape and boots both with the settings entry (hot publish) and without it (entry-config resolution, no scope). - core-data-structures index: settings.md row added to the sub-page table in core.md/core.zh.md. Both packages hold per-file 100% coverage across repeated runs.
This commit is contained in:
@@ -662,7 +662,7 @@ Committed change to one registered namespace's resolved value. Emitted after the
|
||||
|
||||
Types: [SettingsNamespace](../core-data-structures/settings.md) · [SettingsUpdateSource](../core-data-structures/settings.md)
|
||||
|
||||
Source: [`packages/settings/settings/src/index.ts:96`](../../packages/settings/settings/src/index.ts)
|
||||
Source: [`packages/settings/settings/src/index.ts:97`](../../packages/settings/settings/src/index.ts)
|
||||
|
||||
## `slash/*`
|
||||
|
||||
|
||||
@@ -1442,7 +1442,7 @@ async replace(ns: SettingsNamespace, section: object): Promise<void>
|
||||
|
||||
Types: [SettingsDescriptor](../core-data-structures/settings.md) · [SettingsNamespace](../core-data-structures/settings.md) · [SettingsRegisterOptions](../core-data-structures/settings.md) · [SettingsScope](../core-data-structures/settings.md)
|
||||
|
||||
Source: [`packages/settings/settings/src/index.ts:168`](../../packages/settings/settings/src/index.ts)
|
||||
Source: [`packages/settings/settings/src/index.ts:176`](../../packages/settings/settings/src/index.ts)
|
||||
|
||||
## `ctx.skills` — `SkillService`
|
||||
|
||||
|
||||
@@ -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/core-data-structures/core.md
|
||||
core.md: 647c7f273183e0890ef191d98ab009ad129db572
|
||||
core.zh.md: 8b0f439f09a6e6609dbe69c3056aa74d553a0943
|
||||
core.md: ca8426e6fbece18a277cc31f5e86d3058b3feb32
|
||||
core.zh.md: 6b297ca233428d38dbe022e58344e98fab7a15ad
|
||||
|
||||
@@ -24,6 +24,7 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t
|
||||
| [commands.md](commands.md) | the human-command seam: definitions, adapter discovery, direct invocation, results, and parsing views |
|
||||
| [session.md](session.md) | the full `SessionEventMap` variant catalog, `TurnTrigger`/`TurnEndReason`, `deriveMessages()`, execution enclosure, and standalone events |
|
||||
| [persistence.md](persistence.md) | the durability seam: `SessionPersistence`, JSONL + SQLite backends, `session/flush`, crash recovery, `SessionHeader` |
|
||||
| [settings.md](settings.md) | the user-settings seam: `SettingsNamespace` registration, layered resolution (defaults → composition `base` → user document), owner scopes, hot commits |
|
||||
| [session-query.md](session-query.md) | logical records, bounded exact-event reads, relationship traces, semantic filters/documents, and full-text result pages |
|
||||
| [session-title.md](session-title.md) | durable title snapshots, source provenance, and the asynchronous provider contract |
|
||||
| [system-prompt.md](system-prompt.md) | per-assembly context, tool-provider results, prompt sections, and cooperative assembly |
|
||||
|
||||
@@ -24,6 +24,7 @@ harness 是一个微内核:一个极小的核心加上众多插件。大多数
|
||||
| [commands.md](commands.md) | 人类命令 seam:定义、适配器发现、直接调用、结果与解析视图 |
|
||||
| [session.md](session.md) | 完整的 `SessionEventMap` 变体目录、`TurnTrigger`/`TurnEndReason`、`deriveMessages()`、执行封闭与独立事件 |
|
||||
| [persistence.md](persistence.md) | 持久性 seam:`SessionPersistence`、JSONL + SQLite 后端、`session/flush`、崩溃恢复、`SessionHeader` |
|
||||
| [settings.md](settings.md) | 用户设置 seam:`SettingsNamespace` 注册、分层解析(默认值 → 组合 `base` → 用户文档)、owner scope、热提交 |
|
||||
| [session-query.md](session-query.md) | 逻辑记录、有界精确事件读取、关系追踪、语义筛选器/文档与全文检索结果页 |
|
||||
| [session-title.md](session-title.md) | 持久标题快照、来源 provenance 与异步提供方契约 |
|
||||
| [system-prompt.md](system-prompt.md) | 逐次组装的上下文、工具提供方结果、提示词段落与协作式组装 |
|
||||
|
||||
@@ -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/core-data-structures/settings.md
|
||||
settings.md: 851087065c627f2390041dd6e69273e31be3917d
|
||||
settings.zh.md: 955c4fbf7c147b3d0a8be62c33c031d7bd2c4ba2
|
||||
settings.md: abbfecb35f67b27e16dffb9558a35cf368c90beb
|
||||
settings.zh.md: c746e3cc181f8347cbe634b9231cfee1b3beecd3
|
||||
|
||||
@@ -46,8 +46,9 @@ interface SettingsScope<T> {
|
||||
/** Current resolved value: schema defaults, then `base`, then the user layer. */
|
||||
get(): T
|
||||
/**
|
||||
* Observe committed changes to this namespace's resolved value. A callback
|
||||
* may be async; a rejection is contained and logged like a sync throw.
|
||||
* Observe committed changes to this namespace's resolved value. Invocations
|
||||
* of one callback run asynchronously, one at a time, in commit order; a
|
||||
* rejection is contained and logged like a sync throw.
|
||||
* @param callback - invoked after each commit with the next and previous values.
|
||||
* @returns the disposer removing this observer.
|
||||
*/
|
||||
|
||||
@@ -46,8 +46,9 @@ interface SettingsScope<T> {
|
||||
/** Current resolved value: schema defaults, then `base`, then the user layer. */
|
||||
get(): T
|
||||
/**
|
||||
* Observe committed changes to this namespace's resolved value. A callback
|
||||
* may be async; a rejection is contained and logged like a sync throw.
|
||||
* Observe committed changes to this namespace's resolved value. Invocations
|
||||
* of one callback run asynchronously, one at a time, in commit order; a
|
||||
* rejection is contained and logged like a sync throw.
|
||||
* @param callback - invoked after each commit with the next and previous values.
|
||||
* @returns the disposer removing this observer.
|
||||
*/
|
||||
|
||||
@@ -35,7 +35,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||
| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:80`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`session-persistence`](../packages/session-persistence/session-persistence), [`session-telemetry`](../packages/telemetry/session-telemetry), [`session-title`](../packages/session-title/session-title) |
|
||||
| `session/event` | `emit` | [`packages/core/session/src/index.ts:92`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), `apiproxy`, [`cli-demo`](../packages/examples/cli-demo), [`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), [`jsonrpc`](../packages/ui/jsonrpc), [`plan-mode`](../packages/plan/plan-mode), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-telemetry`](../packages/telemetry/session-telemetry), [`session-title`](../packages/session-title/session-title), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`tui`](../packages/ui/tui), [`user-approval`](../packages/ui/user-approval), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:102`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session-persistence/session-persistence), [`session-telemetry`](../packages/telemetry/session-telemetry) |
|
||||
| `settings/updated` | `emit` | [`packages/settings/settings/src/index.ts:96`](../packages/settings/settings/src/index.ts) | [`settings`](../packages/settings/settings) (`emit`) | [`settings`](../packages/settings/settings) |
|
||||
| `settings/updated` | `emit` | [`packages/settings/settings/src/index.ts:97`](../packages/settings/settings/src/index.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | [`settings`](../packages/settings/settings) |
|
||||
| `slash/input-begin-command` | `bail` | [`packages/client/ui-slash/src/types.ts:230`](../packages/client/ui-slash/src/types.ts) | - | `ui-conversation` |
|
||||
| `slash/input-consume-token` | `bail` | [`packages/client/ui-slash/src/types.ts:244`](../packages/client/ui-slash/src/types.ts) | - | `ui-conversation` |
|
||||
| `slash/input-insert-reference` | `bail` | [`packages/client/ui-slash/src/types.ts:237`](../packages/client/ui-slash/src/types.ts) | - | `ui-conversation` |
|
||||
|
||||
Reference in New Issue
Block a user