Skip to content

Commit 0a14239

Browse files
committed
feat(phase-14): implement TA-P14-003 stable pull cursor
1 parent 1f20207 commit 0a14239

16 files changed

+1025
-120
lines changed

docs/implementation/phase-14/README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# TelAgent v1 Phase 14 执行产出(产品聚焦与缺陷收敛)
22

3-
- 文档版本:v1.0
3+
- 文档版本:v1.1
44
- 状态:Phase 14 执行中
55
- 最后更新:2026-03-03
66

@@ -17,7 +17,7 @@ Phase 14 回归 P2P 应用核心体验,聚焦“可用性、正确性、收敛
1717
| --- | --- | --- |
1818
| TA-P14-001 | DONE | 阶段边界重置(产品聚焦) |
1919
| TA-P14-002 | DONE | 删除 Web 运维面板,保留核心聊天流程 |
20-
| TA-P14-003 | TODO | 消息拉取稳定游标改造(替代 offset 风险) |
20+
| TA-P14-003 | DONE | 消息拉取稳定游标改造(替代 offset 风险) |
2121
| TA-P14-004 | TODO | direct 会话参与方与访问约束强化 |
2222
| TA-P14-005 | TODO | TS/Python SDK 核心行为收敛与错误语义统一 |
2323
| TA-P14-006 | TODO | 回归验证与 Gate 收口 |
@@ -26,6 +26,11 @@ Phase 14 回归 P2P 应用核心体验,聚焦“可用性、正确性、收敛
2626

2727
- `ta-p14-001-phase14-product-focus-boundary-2026-03-03.md`
2828
- `ta-p14-002-web-ops-panel-removal-2026-03-03.md`
29+
- `ta-p14-003-stable-pull-cursor-2026-03-03.md`
2930
- `logs/2026-03-03-p14-web-build.txt`
3031
- `logs/2026-03-03-p14-web-ops-removal-check.txt`
32+
- `logs/2026-03-03-p14-node-build.txt`
33+
- `logs/2026-03-03-p14-node-test.txt`
34+
- `logs/2026-03-03-p14-stable-pull-cursor-check-run.txt`
3135
- `manifests/2026-03-03-p14-web-ops-removal-check.json`
36+
- `manifests/2026-03-03-p14-stable-pull-cursor-check.json`
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
> @telagent/node@0.1.0 build /Users/xiasenhai/Workspace/OpenClaw/telagent/packages/node
3+
> tsc -p tsconfig.json
4+
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
2+
> @telagent/node@0.1.0 pretest /Users/xiasenhai/Workspace/OpenClaw/telagent/packages/node
3+
> pnpm build
4+
5+
6+
> @telagent/node@0.1.0 build /Users/xiasenhai/Workspace/OpenClaw/telagent/packages/node
7+
> tsc -p tsconfig.json
8+
9+
10+
> @telagent/node@0.1.0 test /Users/xiasenhai/Workspace/OpenClaw/telagent/packages/node
11+
> node --test dist/*.test.js dist/**/*.test.js
12+
13+
✔ created response returns data envelope and Location header (193.075308ms)
14+
✔ list response returns paginated envelope shape (84.709043ms)
15+
✔ validation errors use RFC7807 shape and problem+json content type (89.958675ms)
16+
✔ node audit snapshot exports de-sensitized envelope and links.self (38.350977ms)
17+
✔ node audit snapshot rejects invalid query with RFC7807 response (48.846122ms)
18+
✔ TA-P12-003 revoked DID event isolates session and rejects message send with RFC7807 (13.799364ms)
19+
✔ TA-P12-004 node metrics exposes federation DLQ replay burn-rate section (3.517423ms)
20+
✔ not found uses RFC7807 shape (2.96806ms)
21+
✔ identities and groups endpoints are accessible with expected status codes (15.003963ms)
22+
✔ messages, attachments and federation endpoints are accessible (24.993854ms)
23+
✔ routes only serve /api/v1/* prefix (283.585335ms)
24+
✔ identity endpoint responds with data envelope (27.620449ms)
25+
✔ mailbox store defaults to sqlite backend (11.563549ms)
26+
✔ mailbox store parses postgres backend config (0.844643ms)
27+
✔ postgres backend requires connection url (0.684561ms)
28+
✔ mailbox backend rejects unsupported value (0.323177ms)
29+
✔ federation protocol defaults to v1 and supports self version (2.770112ms)
30+
✔ federation supported protocols auto-include self version (0.428246ms)
31+
✔ federation replay protection defaults are applied (0.377544ms)
32+
✔ federation replay protection accepts custom values (28.017496ms)
33+
✔ federation replay protection requires positive integer values (2.171158ms)
34+
✔ domain proof config defaults to enforced mode (0.861294ms)
35+
✔ domain proof config accepts report-only mode and custom values (0.418474ms)
36+
✔ domain proof mode rejects unsupported value (0.413848ms)
37+
✔ domain proof numeric settings require positive integers (0.37303ms)
38+
✔ federation pinning defaults to disabled mode (0.385626ms)
39+
✔ federation pinning parses current/next keys and cutover timestamp (0.529049ms)
40+
✔ federation pinning rejects invalid mode (0.380009ms)
41+
✔ federation pinning enabled requires key mappings (0.364314ms)
42+
✔ federation pinning map requires domain=keys format (0.405227ms)
43+
✔ federation SLO automation config defaults are applied (0.321265ms)
44+
✔ federation SLO automation config accepts custom values (0.320538ms)
45+
✔ federation SLO burn-rate thresholds require positive values (0.314032ms)
46+
✔ finalityDepth only materializes finalized blocks (47.864606ms)
47+
✔ reorg rollback replays canonical events and restores deterministic view (17.99717ms)
48+
✔ TA-P4-009 E2E main path: create -> invite -> accept -> group chat (text/image/file) (169.58791ms)
49+
✔ TA-P4-010 E2E offline 24h pull keeps dedupe and per-conversation order (163.01412ms)
50+
✔ TA-P14-003 E2E pull cursor stays stable when cleanup happens between pages (12.19717ms)
51+
✔ TA-P4-006 init-upload sanitizes filename and emits attachment objectKey (2.303927ms)
52+
✔ TA-P4-006 complete-upload enforces manifest and checksum integrity (0.735654ms)
53+
✔ TA-P4-006 complete-upload is idempotent and rejects checksum divergence (0.44287ms)
54+
✔ TA-P4-006 expired upload sessions are cleaned and cannot be completed (0.253666ms)
55+
✔ TA-P11-003 accepts valid domain proof challenge and canonical hash (17.979588ms)
56+
✔ TA-P11-003 rejects illegal domain challenge on malformed domain (0.887872ms)
57+
✔ TA-P11-003 rejects when canonical domainProofHash mismatches payload (55.257025ms)
58+
✔ TA-P11-003 rotates challenge nonce near expiry and accepts renewed domain proof (5.704762ms)
59+
✔ TA-P11-003 report-only mode returns warning without blocking create flow (0.441505ms)
60+
✔ TA-P4-007 federation envelopes support idempotent retries (2.171735ms)
61+
✔ TA-P4-007 federation auth token is enforced when configured (0.563806ms)
62+
✔ TA-P4-007 federation rate limit rejects burst traffic (0.2998ms)
63+
✔ TA-P4-008 group-state sync enforces domain consistency (0.349322ms)
64+
✔ TA-P8-002 group-state sync rejects stale stateVersion and records resilience counters (0.762015ms)
65+
✔ TA-P8-002 group-state sync detects split-brain on same stateVersion with different state (0.342534ms)
66+
✔ TA-P9-002 federation accepts compatible protocol versions and tracks usage stats (11.902944ms)
67+
✔ TA-P9-002 federation rejects unsupported protocol versions (0.263646ms)
68+
✔ TA-P11-004 federation pinning enforces sourceKeyId with current/next rotation (0.645247ms)
69+
✔ TA-P11-004 federation pinning report-only mode allows traffic but records warnings (0.332472ms)
70+
✔ TA-P11-005 federation DLQ captures failures and replays in sequence order (8.366413ms)
71+
✔ TA-P4-008 node-info publishes domain and federation security policy (0.239805ms)
72+
✔ TA-P13-005 federation replay applies backoff and opens circuit on repeated failures (0.86748ms)
73+
✔ TA-P13-005 federation replay protection validates backoff range (0.171509ms)
74+
✔ TA-P12-004 federation SLO runOnce auto-replays DLQ and records burn-rate metrics (4.456233ms)
75+
✔ TA-P12-004 federation SLO scheduler periodically replays DLQ (1102.179825ms)
76+
✔ assertSufficient throws INSUFFICIENT_GAS_TOKEN_BALANCE when native balance is not enough (1.795618ms)
77+
✔ TA-P11-006 rotate key keeps old key usable in grace window then expires (1.865939ms)
78+
✔ TA-P11-006 revoke and recover lifecycle is verifiable (0.560927ms)
79+
✔ TA-P11-006 rejects invalid did and malformed key id (0.409711ms)
80+
✔ TA-P12-007 orchestrator supports staged key rotation with rollback recovery (2.975709ms)
81+
✔ TA-P4-002 sequence allocator keeps per-conversation monotonic order (4.021015ms)
82+
✔ TA-P4-003 dedupe keeps idempotent writes for same envelopeId (0.841859ms)
83+
✔ TA-P4-003 duplicate envelopeId with different payload is rejected (0.875514ms)
84+
✔ TA-P4-004 cleanupExpired removes expired envelopes and releases dedupe key (1.004798ms)
85+
✔ TA-P14-003 conversation pull cursor stays stable after cleanup between pages (2.544622ms)
86+
✔ TA-P14-003 global pull cursor is keyset token and survives cleanup drift (0.933764ms)
87+
✔ TA-P4-005 provisional envelopes are retracted when group is reorged back (0.929405ms)
88+
✔ TA-P4-005 send is rejected when group chain state is REORGED_BACK (0.402014ms)
89+
✔ TA-P12-002 buildAuditSnapshot exports hashed retraction samples (1.06ms)
90+
✔ TA-P12-002 buildAuditSnapshot normalizes sample and scan bounds (0.394715ms)
91+
✔ TA-P12-003 revoked DID event isolates related sessions and evicts active sessions (1.526647ms)
92+
✔ TA-P12-003 buildAuditSnapshot includes revocation isolation evidence (0.508865ms)
93+
✔ TA-P6-001 mailbox persists messages and seq after service restart (21.84485ms)
94+
✔ TA-P11-006 message send validates signal/mls key lifecycle status (2.317835ms)
95+
✔ TA-P11-007 revoked DID cannot continue sending new messages (0.53192ms)
96+
✔ TA-P5-002 monitoring snapshot normalizes dynamic route segments and records counters (2.822989ms)
97+
✔ TA-P5-002 monitoring emits warning/critical alerts when thresholds are exceeded (0.505919ms)
98+
✔ TA-P12-004 federation DLQ burn-rate alert is emitted and tracked (1.033452ms)
99+
ℹ tests 86
100+
ℹ suites 0
101+
ℹ pass 86
102+
ℹ fail 0
103+
ℹ cancelled 0
104+
ℹ skipped 0
105+
ℹ todo 0
106+
ℹ duration_ms 2496.051154
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[TA-P14-003] conversationCursorStableAfterCleanup=true
2+
[TA-P14-003] globalCursorTokenFormat=true
3+
[TA-P14-003] globalCursorStableAfterCleanup=true
4+
[TA-P14-003] legacyOffsetRejectedInGlobalPull=true code=VALIDATION_ERROR
5+
[TA-P14-003] decision=PASS
6+
[TA-P14-003] output=/Users/xiasenhai/Workspace/OpenClaw/telagent/docs/implementation/phase-14/manifests/2026-03-03-p14-stable-pull-cursor-check.json
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"phase": "Phase 14",
3+
"taskId": "TA-P14-003",
4+
"generatedAt": "2026-03-03T14:41:43.610Z",
5+
"summary": {
6+
"conversationCursorStableAfterCleanup": true,
7+
"globalCursorTokenFormat": true,
8+
"globalCursorStableAfterCleanup": true,
9+
"legacyOffsetRejectedInGlobalPull": true,
10+
"rejectedErrorCode": "VALIDATION_ERROR"
11+
},
12+
"decision": "PASS",
13+
"details": {
14+
"conversation": {
15+
"page1EnvelopeIds": [
16+
"p14-conv-1",
17+
"p14-conv-2"
18+
],
19+
"page1NextCursor": "2",
20+
"page2EnvelopeIds": [
21+
"p14-conv-3"
22+
],
23+
"page2NextCursor": null
24+
},
25+
"global": {
26+
"page1EnvelopeIds": [
27+
"p14-global-1",
28+
"p14-global-2"
29+
],
30+
"page1NextCursor": "g1.eyJzZW50QXRNcyI6MTc3MjU4MjUwMDEwMCwiY29udmVyc2F0aW9uSWQiOiJkaXJlY3Q6cDE0LWdsb2JhbC1iIiwic2VxIjoiMSIsImVudmVsb3BlSWQiOiJwMTQtZ2xvYmFsLTIifQ",
31+
"page2EnvelopeIds": [
32+
"p14-global-3"
33+
],
34+
"page2NextCursor": null
35+
},
36+
"globalLegacyCursorRejection": {
37+
"rejectedErrorCode": "VALIDATION_ERROR"
38+
}
39+
}
40+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# TA-P14-003 消息拉取稳定游标改造(替代 offset 风险)(2026-03-03)
2+
3+
- Task ID:TA-P14-003
4+
- 阶段:Phase 14
5+
- 状态:DONE
6+
- 负责人角色:Backend + QA
7+
8+
## 1. 目标
9+
10+
`GET /api/v1/messages/pull``offset cursor` 升级为稳定 `keyset cursor`,消除“分页过程中发生清理/撤回后出现跳项或重复”的风险。
11+
12+
## 2. 实现摘要
13+
14+
1. `conversation_id` 维度拉取:
15+
- 游标语义切换为 `afterSeq`(最后一条消息的 `seq`)。
16+
- 响应游标继续使用字符串数值(如 `"2"`),兼容既有调用。
17+
- 查询语义为 `seq > afterSeq`,不再依赖 offset。
18+
2. 全局拉取(不带 `conversation_id`):
19+
- 使用 `g1.<base64url-json>` 的 keyset 游标,包含:
20+
- `sentAtMs`
21+
- `conversationId`
22+
- `seq`
23+
- `envelopeId`
24+
- 查询语义按 `(sent_at_ms, conversation_id, seq, envelope_id)` 递增推进。
25+
3. 全局拉取不再接受 legacy 数字 offset 光标:
26+
- 返回 `VALIDATION_ERROR`,由 API 统一转换为 RFC7807。
27+
4. SQLite/Postgres 仓储统一改造为 keyset 查询,并新增 pull 游标索引。
28+
29+
## 3. 变更文件
30+
31+
- `packages/node/src/services/message-service.ts`
32+
- `packages/node/src/storage/mailbox-store.ts`
33+
- `packages/node/src/storage/message-repository.ts`
34+
- `packages/node/src/storage/postgres-message-repository.ts`
35+
- `packages/node/src/services/message-service.test.ts`
36+
- `packages/node/src/phase4-e2e.test.ts`
37+
- `packages/node/scripts/run-phase14-stable-pull-cursor-check.ts`
38+
39+
## 4. 验证
40+
41+
1. 单测/E2E 新增:
42+
- `TA-P14-003 conversation pull cursor stays stable after cleanup between pages`
43+
- `TA-P14-003 global pull cursor is keyset token and survives cleanup drift`
44+
- `TA-P14-003 E2E pull cursor stays stable when cleanup happens between pages`
45+
2. 专项脚本:
46+
- `packages/node/scripts/run-phase14-stable-pull-cursor-check.ts`
47+
3. 构建与测试:
48+
- `corepack pnpm --filter @telagent/node build`
49+
- `corepack pnpm --filter @telagent/node test`
50+
51+
## 5. 证据
52+
53+
- 构建日志:`docs/implementation/phase-14/logs/2026-03-03-p14-node-build.txt`
54+
- 测试日志:`docs/implementation/phase-14/logs/2026-03-03-p14-node-test.txt`
55+
- 专项检查日志:`docs/implementation/phase-14/logs/2026-03-03-p14-stable-pull-cursor-check-run.txt`
56+
- 机读清单:`docs/implementation/phase-14/manifests/2026-03-03-p14-stable-pull-cursor-check.json`

docs/implementation/telagent-v1-iteration-board.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,9 @@
223223
- 已完成:`TA-P13-007`(Phase 13 Gate 评审与收口)
224224
- 已完成:`TA-P14-001`(Phase 14 产品聚焦边界冻结)
225225
- 已完成:`TA-P14-002`(默认 Web 运维面板下线,回归核心聊天流程)
226+
- 已完成:`TA-P14-003`(消息拉取稳定游标改造,修复 cleanup/retraction 导致分页跳项风险)
226227
- 已规划:`TA-P15-001`(WebApp 工业级规划总纲冻结)
227-
- 下一批 Ready:执行 `TA-P14-003`(消息拉取稳定游标)与 `TA-P14-004`(direct 会话访问控制)。
228+
- 下一批 Ready:执行 `TA-P14-004`(direct 会话访问控制)与 `TA-P14-005`(SDK 行为收敛)。
228229

229230
## 4.2 Blockers(2026-03-03 更新)
230231

@@ -412,7 +413,7 @@
412413

413414
- `TA-P14-001`:DONE(产品聚焦边界冻结,见 `docs/implementation/phase-14/ta-p14-001-phase14-product-focus-boundary-2026-03-03.md`)。
414415
- `TA-P14-002`:DONE(默认 Web 运维面板下线,见 `docs/implementation/phase-14/ta-p14-002-web-ops-panel-removal-2026-03-03.md`)。
415-
- `TA-P14-003`TODO(消息拉取稳定游标改造)。
416+
- `TA-P14-003`DONE(消息拉取稳定游标改造,见 `docs/implementation/phase-14/ta-p14-003-stable-pull-cursor-2026-03-03.md`)。
416417
- `TA-P14-004`:TODO(direct 会话参与方访问控制)。
417418
- `TA-P14-005`:TODO(TS/Python SDK 行为收敛)。
418419
- `TA-P14-006`:TODO(Phase 14 Gate 收口)。

docs/implementation/telagent-v1-task-breakdown.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ flowchart LR
147147
| TA-P13-007 | Phase 13 | Phase 13 Gate 评审与收口 | TL + QA | 0.5 | TA-P13-002, TA-P13-003, TA-P13-004, TA-P13-005, TA-P13-006 | gate 结论文档 | Phase 13 正式关闭 | DONE |
148148
| TA-P14-001 | Phase 14 | 冻结产品聚焦边界(回归核心 P2P 应用) | TL + BE + FE + QA | 0.5 | TA-P13-007 | boundary decision doc | Phase 14 范围与 Phase 15 分工冻结 | DONE |
149149
| TA-P14-002 | Phase 14 | 删除默认 Web 运维面板,保留核心聊天流程 | Frontend | 1 | TA-P14-001 | web app cleanup + build log | 默认界面仅保留核心链路入口,构建通过 | DONE |
150-
| TA-P14-003 | Phase 14 | 消息拉取稳定游标改造(替代 offset 风险) | Backend + QA | 1.5 | TA-P14-001 | pull cursor upgrade + tests | 清理/撤回场景下分页稳定无重复/跳项 | TODO |
150+
| TA-P14-003 | Phase 14 | 消息拉取稳定游标改造(替代 offset 风险) | Backend + QA | 1.5 | TA-P14-001 | pull cursor upgrade + tests | 清理/撤回场景下分页稳定无重复/跳项 | DONE |
151151
| TA-P14-004 | Phase 14 | direct 会话访问控制强化(参与方约束) | Backend + Security | 1.5 | TA-P14-001 | direct ACL guard + tests | 非参与方消息写入被拒绝并返回 RFC7807 | TODO |
152152
| TA-P14-005 | Phase 14 | TS/Python SDK 核心行为收敛 | DX + Backend + QA | 1 | TA-P14-003, TA-P14-004 | sdk parity extension + checks | 参数、错误语义、返回结构一致 | TODO |
153153
| TA-P14-006 | Phase 14 | Phase 14 Gate 评审与收口 | TL + QA | 0.5 | TA-P14-002, TA-P14-003, TA-P14-004, TA-P14-005 | gate 结论文档 | Phase 14 正式关闭 | TODO |
@@ -356,8 +356,8 @@ flowchart LR
356356
| --- | --- | --- | --- | --- |
357357
| TA-P14-001 | DONE | `docs/implementation/phase-14/ta-p14-001-phase14-product-focus-boundary-2026-03-03.md`, `docs/implementation/phase-14/README.md` || 进入 `TA-P14-002`(删除默认 Web 运维面板) |
358358
| TA-P14-002 | DONE | `docs/implementation/phase-14/ta-p14-002-web-ops-panel-removal-2026-03-03.md`, `packages/web/src/index.html`, `packages/web/src/main.js` || 进入 `TA-P14-003`(消息拉取稳定游标改造) |
359-
| TA-P14-003 | TODO | `docs/implementation/phase-14/README.md` || 实施 pull cursor 稳定化改造并补齐测试 |
360-
| TA-P14-004 | TODO | `docs/implementation/phase-14/README.md` || 实施 direct 会话参与方访问控制 |
359+
| TA-P14-003 | DONE | `docs/implementation/phase-14/ta-p14-003-stable-pull-cursor-2026-03-03.md`, `packages/node/src/services/message-service.ts`, `packages/node/src/storage/message-repository.ts`, `packages/node/src/storage/postgres-message-repository.ts`, `packages/node/src/services/message-service.test.ts`, `packages/node/src/phase4-e2e.test.ts`, `packages/node/scripts/run-phase14-stable-pull-cursor-check.ts`, `docs/implementation/phase-14/logs/2026-03-03-p14-node-build.txt`, `docs/implementation/phase-14/logs/2026-03-03-p14-node-test.txt`, `docs/implementation/phase-14/logs/2026-03-03-p14-stable-pull-cursor-check-run.txt`, `docs/implementation/phase-14/manifests/2026-03-03-p14-stable-pull-cursor-check.json` || 进入 `TA-P14-004`(direct 会话参与方访问控制) |
360+
| TA-P14-004 | TODO | `docs/implementation/phase-14/README.md` || 实施 direct 会话参与方访问控制并补齐测试 |
361361
| TA-P14-005 | TODO | `docs/implementation/phase-14/README.md` || TS/Python SDK 行为与错误语义收敛 |
362362
| TA-P14-006 | TODO | `docs/implementation/phase-14/README.md` || 阶段回归与 Gate 收口 |
363363

0 commit comments

Comments
 (0)