fix(session): close the review gaps the boundary opened
- `SessionSummary.updatedAt`'s wire doc still said "Persisted file mtime", which stopped being true for attached sessions. - The core invariant let `session/inherited` fall through the merge-extensible default. It is core-owned, so it gets an explicit case; an unbalanced seed legally places it inside an open turn, which the relation permits. - The Agent Note claimed the boundary reaches disk via `live.pending`/ `scheduleDrain`. Verified false: the constructor append precedes `enter()`, so it never publishes on `session/event` and rides the creation seed instead. Attaching is therefore a write where none happened before — recorded, since only `load()` stays a pure read. - The deferred-index proposal asserted this change documented the cold-mtime skew on `dsh-host-apiproxy`. It did not; the README entry now exists. - `firstLiveSeq`'s firehose gap runs through its own seq, not below it. - The boundary is not always at `firstLiveSeq` (the idempotence guard), so consumers scan for the last one. - `lastActivityTime` excludes by type, so a pickup time still leaks onto a synthetic closer when a boundary ends an open turn. Documented. - Pin the fork claim end-to-end: a child inherits a still-running parent's open bracket below its own boundary, while the parent has none. Fails if the write moves back to the load path. - Fix the telemetry title that contradicted its own assertions. The `/status` call site cannot be pinned the way the other two are: the command appends its own `command/run` before rendering, so the boundary is never the log tail there. Its fixture now at least renders over a boundary-bearing log.
This commit is contained in:
@@ -1590,7 +1590,7 @@ fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId):
|
||||
|
||||
Types: [CreateSessionOptions](../core-data-structures/persistence.md) · [Session](../core-data-structures/session.md) · [SessionId](../core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/session/src/index.ts:706`](../../packages/core/session/src/index.ts)
|
||||
Source: [`packages/core/session/src/index.ts:710`](../../packages/core/session/src/index.ts)
|
||||
|
||||
## `ctx.sessionTitle` — `SessionTitleService`
|
||||
|
||||
|
||||
@@ -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/session.md
|
||||
session.md: 25fb5471e600541a3a2d5d2b67083f5b443dcd52
|
||||
session.zh.md: 9cf8c61d3c2ca27c4cdc6adf5804e59dd73b5f5e
|
||||
session.md: 03a1a3ee3350009e2f26cc52ff013643a04b5bd9
|
||||
session.zh.md: d1c676f1c9297582d9e80ed5d15af78c7ef68e52
|
||||
|
||||
@@ -97,6 +97,10 @@ interface SessionEventMap {
|
||||
* and no writer in this lifecycle produced it. Payload is empty — position
|
||||
* and `time` carry the meaning.
|
||||
*
|
||||
* Locate the LAST one rather than reading `firstLiveSeq`: a seed already
|
||||
* ending in a boundary is not re-marked, so reopening an untouched session
|
||||
* does not grow its log per pickup.
|
||||
*
|
||||
* An owner of a standalone open/close bracket (`compact/start` …
|
||||
* `compact/end`) reads it because inherited history and live work are
|
||||
* otherwise byte-identical: an unmatched opening marker below the boundary
|
||||
@@ -346,6 +350,10 @@ declare class Session {
|
||||
* `session/inherited` event at this seq, which is what a consumer reading
|
||||
* STORED history reads. Prefer this field in-process — it is exact before
|
||||
* the marker's write reaches storage.
|
||||
*
|
||||
* The marker is appended before the store attaches, so when one exists the
|
||||
* event AT this seq did not publish either: the firehose gap runs through
|
||||
* `firstLiveSeq`, not just below it.
|
||||
*/
|
||||
readonly firstLiveSeq: number;
|
||||
constructor(id: SessionId, seed?: readonly SessionEvent[], header?: SessionHeader);
|
||||
|
||||
@@ -97,6 +97,10 @@ interface SessionEventMap {
|
||||
* and no writer in this lifecycle produced it. Payload is empty — position
|
||||
* and `time` carry the meaning.
|
||||
*
|
||||
* Locate the LAST one rather than reading `firstLiveSeq`: a seed already
|
||||
* ending in a boundary is not re-marked, so reopening an untouched session
|
||||
* does not grow its log per pickup.
|
||||
*
|
||||
* An owner of a standalone open/close bracket (`compact/start` …
|
||||
* `compact/end`) reads it because inherited history and live work are
|
||||
* otherwise byte-identical: an unmatched opening marker below the boundary
|
||||
@@ -348,6 +352,10 @@ declare class Session {
|
||||
* `session/inherited` event at this seq, which is what a consumer reading
|
||||
* STORED history reads. Prefer this field in-process — it is exact before
|
||||
* the marker's write reaches storage.
|
||||
*
|
||||
* The marker is appended before the store attaches, so when one exists the
|
||||
* event AT this seq did not publish either: the firehose gap runs through
|
||||
* `firstLiveSeq`, not just below it.
|
||||
*/
|
||||
readonly firstLiveSeq: number;
|
||||
constructor(id: SessionId, seed?: readonly SessionEvent[], header?: SessionHeader);
|
||||
|
||||
@@ -78,7 +78,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
}[T]
|
||||
```
|
||||
|
||||
Sources: [`packages/core/session/src/types.ts:270`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:277`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:306`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:338`](../packages/core/session/src/types.ts)
|
||||
Sources: [`packages/core/session/src/types.ts:274`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:281`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:310`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:342`](../packages/core/session/src/types.ts)
|
||||
|
||||
## Events
|
||||
|
||||
@@ -413,6 +413,10 @@ Source: [`packages/sandbox/sandbox-policy/src/session-mode.ts:33`](../packages/s
|
||||
* and no writer in this lifecycle produced it. Payload is empty — position
|
||||
* and `time` carry the meaning.
|
||||
*
|
||||
* Locate the LAST one rather than reading `firstLiveSeq`: a seed already
|
||||
* ending in a boundary is not re-marked, so reopening an untouched session
|
||||
* does not grow its log per pickup.
|
||||
*
|
||||
* An owner of a standalone open/close bracket (`compact/start` …
|
||||
* `compact/end`) reads it because inherited history and live work are
|
||||
* otherwise byte-identical: an unmatched opening marker below the boundary
|
||||
@@ -423,7 +427,7 @@ Source: [`packages/sandbox/sandbox-policy/src/session-mode.ts:33`](../packages/s
|
||||
'session/inherited': Record<string, never>
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:266`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:270`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `session/title` — log-only
|
||||
|
||||
|
||||
Reference in New Issue
Block a user