Просмотр исходного кода

docs(session): align projection hint ordering

pku-xht 1 месяц назад
Родитель
Сommit
b36f3d323a

+ 2 - 2
docs/subsystems/session-projection.i18n.yaml

@@ -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: ba4040e9692c4b3453b1e134a8f0654e59d51c79
-session-projection.zh.md: 7a190c586efdd44dcd659a65164b5b0a43dbe002
+session-projection.md: 87a11475bd12ed922030f08ae5d164641b95ec09
+session-projection.zh.md: 7af0cfc7e9eba696d3bf826de6b0e4067a245aaf

+ 5 - 5
docs/subsystems/session-projection.md

@@ -118,11 +118,11 @@ The persisted projection cache service. Opens the `session_projcache` domain at
 ```ts cordis-catalog
 /**
  * The zero-I/O listing read: whole values viewed straight from the stored
- * rows (version-matching keys only), each cut carried with its watermark so
- * a client value store can apply the same higher-seq-wins rule used for all
- * projection sources. The caller's header keeps unrelated lifecycles out;
- * the value remains a best-effort cached observation until a fresher cut
- * arrives.
+ * rows (version-matching keys only), with the lowest served watermark carried
+ * for later authoritative reconciliation. The caller's header keeps unrelated
+ * lifecycles out; repeated list blocks are arrival-ordered tentative hints
+ * because crash repair may lower the durable sequence; authoritative frames
+ * replace matching rows, and complete baselines replace the full set.
  * @param meta - the listed session's header (identity witness; no log read).
  * @param keys - optional projection keys required by the caller's audience.
  * @returns the cut (`asOfSeq` = lowest served-row watermark), or

+ 5 - 5
docs/subsystems/session-projection.zh.md

@@ -118,11 +118,11 @@ The persisted projection cache service. Opens the `session_projcache` domain at
 ```ts cordis-catalog
 /**
  * The zero-I/O listing read: whole values viewed straight from the stored
- * rows (version-matching keys only), each cut carried with its watermark so
- * a client value store can apply the same higher-seq-wins rule used for all
- * projection sources. The caller's header keeps unrelated lifecycles out;
- * the value remains a best-effort cached observation until a fresher cut
- * arrives.
+ * rows (version-matching keys only), with the lowest served watermark carried
+ * for later authoritative reconciliation. The caller's header keeps unrelated
+ * lifecycles out; repeated list blocks are arrival-ordered tentative hints
+ * because crash repair may lower the durable sequence; authoritative frames
+ * replace matching rows, and complete baselines replace the full set.
  * @param meta - the listed session's header (identity witness; no log read).
  * @param keys - optional projection keys required by the caller's audience.
  * @returns the cut (`asOfSeq` = lowest served-row watermark), or

+ 1 - 1
packages/extensions/tool-cordis/src/api-catalog.ts

@@ -1503,7 +1503,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
     methods: [
       {
         signature: 'cachedSnapshot( meta: SessionHeader, keys?: readonly Extract<keyof SessionProjectionMap, string>[], ): ProjectionSnapshot | undefined',
-        description: 'The zero-I/O listing read: whole values viewed straight from the stored rows (version-matching keys only), each cut carried with its watermark so a client value store can apply the same higher-seq-wins rule used for all projection sources. The caller\'s header keeps unrelated lifecycles out; the value remains a best-effort cached observation until a fresher cut arrives.',
+        description: 'The zero-I/O listing read: whole values viewed straight from the stored rows (version-matching keys only), with the lowest served watermark carried for later authoritative reconciliation. The caller\'s header keeps unrelated lifecycles out; repeated list blocks are arrival-ordered tentative hints because crash repair may lower the durable sequence; authoritative frames replace matching rows, and complete baselines replace the full set.',
         parameters: [{ name: 'meta', description: 'the listed session\'s header (identity witness; no log read).' }, { name: 'keys', description: 'optional projection keys required by the caller\'s audience.' }],
         returns: 'the cut (`asOfSeq` = lowest served-row watermark), or `undefined` when no usable row exists for this lifecycle.',
       },

+ 2 - 2
packages/session/session-projection-cache/README.i18n.yaml

@@ -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 packages/session/session-projection-cache/README.md
-README.md: 9d1619fe25075577218e95a843f752ce3cec7cfb
-README.zh.md: cd72f124865df32b8576d7f6dd3aff12a3a1cd05
+README.md: 24ca9abb3ea4403331f37b60cbd83374398cd54d
+README.zh.md: 3bca8ac80ca2e0488cfa8fada9b023505810d09e

+ 1 - 1
packages/session/session-projection-cache/README.md

@@ -58,7 +58,7 @@ Three mandatory points always write: session creation persists the seed-derived
 
 ### Reading cached values
 
-`cachedSnapshot(meta)` synchronously serves client values from the storage domain's coherent in-memory table with zero I/O. It accepts only an identity-matching record and version- and schema-matching client keys, then returns a best-effort `{ asOfSeq, values }` cut at the lowest served-row watermark; host-only rows are omitted. It returns `undefined` for an unknown id, unrelated lifecycle, absent or foreign record document, or no usable rows. The list carrier submits this value as a tentative hint to the per-Session Client projection store. Higher-sequence hints replace earlier tentative rows; the first authoritative frame replaces a tentative row regardless of sequence, and later frames require a higher sequence. A successful follow opening gives the store a complete cut: it replaces pre-opening rows and retains only authoritative frames that arrived after the opening began and are newer than that cut. A control-generation baseline also replaces its complete per-Session value, including equal-sequence and omitted keys; when it arrives during an opening at an equal or newer cut, it remains authoritative.
+`cachedSnapshot(meta)` synchronously serves client values from the storage domain's coherent in-memory table with zero I/O. It accepts only an identity-matching record and version- and schema-matching client keys, then returns a best-effort `{ asOfSeq, values }` cut at the lowest served-row watermark; host-only rows are omitted. It returns `undefined` for an unknown id, unrelated lifecycle, absent or foreign record document, or no usable rows. The list carrier submits this value as a tentative hint to the per-Session Client projection store. For each carried key, a later list block replaces an earlier tentative row by arrival order, even when crash repair lowers its durable watermark; the first authoritative frame replaces a tentative row regardless of sequence, and later frames require a higher sequence. A successful follow opening gives the store a complete cut: it replaces pre-opening rows and retains only authoritative frames that arrived after the opening began and are newer than that cut. A control-generation baseline also replaces its complete per-Session value, including equal-sequence and omitted keys; when it arrives during an opening at an equal or newer cut, it remains authoritative.
 
 `coldSnapshot(meta, events)` accepts the complete ordered log, validates every seeded row against that exact extent once, folds any required events from `init(header)`, and refreshes the record without consulting persistence. `hydratePrepared(session, meta, events)` performs the production exact-read validation for an unpublished prepared Session; if cached state is malformed or out of range, that path retries over the full supplied log from `init(header)`. Corruption in the durable event stream still fails the retry instead of producing a partial snapshot.
 

+ 1 - 1
packages/session/session-projection-cache/README.zh.md

@@ -58,7 +58,7 @@ kind: "package-reference"
 
 ### 读取缓存值
 
-`cachedSnapshot(meta)` 以零 I/O 从存储域一致的内存表同步提供客户端值。它只接受身份匹配的记录及版本和 schema 均匹配的客户端 key,再按所服务行的最低水位返回尽力而为的 `{ asOfSeq, values }` 切面;host-only 行会被省略。对于未知 id、无关生命周期、缺失或外来的记录文档,或没有可用行的情况,它返回 `undefined`。列表载体把该值作为暂定 hint 交给逐 Session 的 Client projection store。较高 sequence 的 hint 会替换较早的暂定 row;首个权威 frame 无论 sequence 如何都会替换暂定 row,后续 frame 则必须具有更高 sequence。成功的 follow opening 为 store 提供一份完整 cut:它替换 opening 前的 row,只保留 opening 开始后到达且新于该 cut 的权威 frame。control generation baseline 也会精确替换该 Session 的完整值,包括等 sequence row 与缺失 key;若它在 opening 期间以等于或新于 opening cut 的 cut 到达,它保持权威。
+`cachedSnapshot(meta)` 以零 I/O 从存储域一致的内存表同步提供客户端值。它只接受身份匹配的记录及版本和 schema 均匹配的客户端 key,再按所服务行的最低水位返回尽力而为的 `{ asOfSeq, values }` 切面;host-only 行会被省略。对于未知 id、无关生命周期、缺失或外来的记录文档,或没有可用行的情况,它返回 `undefined`。列表载体把该值作为暂定 hint 交给逐 Session 的 Client projection store。对于每个携带的 key,后到的列表 block 按到达顺序替换先前的暂定 row,即使崩溃修复使其持久水位降低;首个权威 frame 无论 sequence 如何都会替换暂定 row,后续 frame 则必须具有更高 sequence。成功的 follow opening 为 store 提供一份完整 cut:它替换 opening 前的 row,只保留 opening 开始后到达且新于该 cut 的权威 frame。control generation baseline 也会精确替换该 Session 的完整值,包括等 sequence row 与缺失 key;若它在 opening 期间以等于或新于 opening cut 的 cut 到达,它保持权威。
 
 `coldSnapshot(meta, events)` 接受完整有序日志,只以该精确范围校验一次每条 seed row,从 `init(header)` 折叠所需事件,并在不访问持久化层的情况下刷新记录。`hydratePrepared(session, meta, events)` 为尚未发布的 prepared Session 执行生产精确读取校验;若缓存状态畸形或越界,只有该路径会在所提供的完整日志上从 `init(header)` 重试。持久事件流本身若已损坏,重试仍然失败,绝不会产出部分快照。
 

+ 9 - 8
packages/session/session-projection-cache/src/index.ts

@@ -111,11 +111,11 @@ export class SessionProjectionCache extends Service {
 
   /**
    * The zero-I/O listing read: whole values viewed straight from the stored
-   * rows (version-matching keys only), each cut carried with its watermark so
-   * a client value store can apply the same higher-seq-wins rule used for all
-   * projection sources. The caller's header keeps unrelated lifecycles out;
-   * the value remains a best-effort cached observation until a fresher cut
-   * arrives.
+   * rows (version-matching keys only), with the lowest served watermark carried
+   * for later authoritative reconciliation. The caller's header keeps unrelated
+   * lifecycles out; repeated list blocks are arrival-ordered tentative hints
+   * because crash repair may lower the durable sequence; authoritative frames
+   * replace matching rows, and complete baselines replace the full set.
    * @param meta - the listed session's header (identity witness; no log read).
    * @param keys - optional projection keys required by the caller's audience.
    * @returns the cut (`asOfSeq` = lowest served-row watermark), or
@@ -130,9 +130,10 @@ export class SessionProjectionCache extends Service {
     const values = this.ctx.sessionProjections.viewCheckpoint(record.rows, keys)
     const servedKeys = Object.keys(values)
     if (servedKeys.length === 0) return undefined
-    // The block carries ONE cut: the lowest served watermark is the seq every
-    // value is at least current as of. Under-claiming is safe under
-    // higher-seq-wins; over-claiming could outrank a fresher observation.
+    // The block carries ONE cut: the lowest served watermark is the sequence
+    // through which every value is known current. The Client orders tentative
+    // list blocks by arrival so a crash-repaired lower watermark can replace an
+    // older hint; authoritative frames and baselines own later reconciliation.
     const asOfSeq = Math.min(...servedKeys.map(key => (record.rows[key] as { seq: number }).seq))
     return { asOfSeq, values }
   }