Repository navigation
feat(context): add bounded checkpoints and scoped evidence access - #189
qiyuanhuakai wants to merge 70 commits into
Conversation
Plan: .omo/plans/vis-v090-unified-runtime.md
Windows process-tree cleanup could mask its primary failure: when the graceful stopWindowsProcessTree step rejected on win32, the rejection propagated before force escalation, skipping the graceful early-return and preventing the forced teardown from running. Track a forceRequired flag, catch the win32 graceful rejection to set it, and guard the graceful early-return so forced teardown always runs and the primary "Process tree did not stop" cause is preserved. Add observability-only helpers to the Kimi Web bridge and serverLive integration tests for two still-UNKNOWN-CAUSE failures. These record safe diagnostics without changing assertions or timeouts and do not claim to fix those failures.
Bound the entity/view state cache and give each session its own scoped subscription, with paged header/body retention for pins and unsynced drafts. - entity/state: add state.ts, entityCache.ts, selectionScope.ts and useRuntimeState.ts with ENTITY_LIMITS and a byte+entry memory budget - draft/pin: add draftCache.ts and pinStore.ts enforcing the 32-copy / 8 MiB resident cap with paused admission - history: add the raw history service/substrate (historyService, historyStream, historyJsonStream, historyPrivacy, historyPage, rawFrame) and the pre-decode guard - five native history producers: OpenCode, Codex, Kimi Web, DSH and ACP raw selected-history taps and drivers/transports - harness: extend harnessContract, capabilities, runtimeWire, runtimeDispatch and runtimeHost for the history capability - QA: author the task-20 happy/failure browser cases Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Brings PR #181 head 49b5b8b (Windows graceful-stop rejection escalated to forced teardown) into the task-20 forward integration branch, resolving the reviewer stacking note that #182 did not contain #181's Windows process-tree fix. Verified conflict-free by git merge-tree --write-tree (tree a2b50b2) before execution; the five files #181 changed since the divergence are not locally modified by any in-flight lane.
R1: refuse ACP session/new for a non-bound workspace before the native call and map an authorized subdirectory to its containing granted root (runtimeServices.js, acpSessions.js). R2: make ACP/OpenCode/DSH/Kimi history read() abort-aware and commit ACP history frames only at a complete newline boundary so a poll timeout releases read_busy and a split frame is never persisted as truncated (acpSessions.js createAcpHistory, openCodeHistory.js, dshHistory.js, kimiWebHistory.js). Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
R3: give the Codex raw iterator a real return() that abandons the sink, drops queued bytes and resumes the shared paused stdout, so cancelling a >4 MiB history read no longer blocks ordinary RPC on the same connection. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
R4: replace the regex-over-head/tail envelope scan with a bounded incremental JSON scanner that reads only the depth-1 result/error and scopes nextCursor to its own level, so a successful history page carrying a nested tool error is not misread as a top-level RPC failure. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
R5: branch the owned-spool-directory check on host so Windows accepts the libuv-synthesised writable mode while POSIX keeps the exact 0700/uid requirement, letting the first history spool succeed on Windows without loosening POSIX. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
R6: reconcile the visible window on member add/remove with a bounded one-page lookahead, and only for the current target+collection, so a newly delivered session reaches the view without a whole-project snapshot re-fetch. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
R8: classify finish/cancel rejections so a known-uncommitted client-admission failure keeps the handle retryable and cancellable, while an unconfirmable outcome retires the connection and reclaims the server upload slot. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
R7: install the pinned Playwright chromium (with system deps) inside the mandatory Validate job so the new IndexedDB pinStore test runs instead of failing on a missing browser. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Task 21: unified virtualizable session tree over the bounded task-20 state layer, with per-session harness badges, progressive discovery status, full-index search and child pagination, plus the registered acceptance tests and QA cases. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
…ndle B2: load SessionTree.vue through defineAsyncComponent in SidePanel.vue so its subtree moves to an async chunk, restoring the frozen entry-js budget without raising the cap. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
…tory B1: register the seven unified-tree harness-kind branch points the task-21 badge/composable introduce so the dsh branch guard passes without weakening detection. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
…rst byte R9: give the Codex raw transport an explicit cancel() that wakes a parked frame.next() directly (freeing the raw slot and dropping the abandoned frame's late bytes) and have the history stream call it before the first byte, so cancelling a Codex history read no longer queues behind the pending next() and hangs close/session switch. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
…on key R11: SessionTree.vue sent the decorated row.key (native:<parent>:<child>) to select-key, which decodeSessionKey cannot parse, and compared that same decorated key against the canonical selected key, so native rows could be neither selected nor highlighted; send and compare row.node.session.key for both session and native rows. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
…clears R10: advance the local window markers before the synchronous state.select(null) notification and gate the clear with a single-clear flag, stopping the re-entrant refreshFromState recursion (RangeError) when the selected session is deleted. R12: retain at most SEARCH_LIMITS.maxRetained (200) matches, expose hasMore/cursor with searchMore(), abort a superseded traversal through a generation fence, and clear search state on dispose, keeping the full index searchable under the task-20 bounded-resident budget. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
The renderer is served from app://index.html with contextIsolation + sandbox and no Node integration. Vite/rolldown stubs the node:-prefixed node:crypto builtin to an empty module, so createHash was undefined and App.vue's synchronous computed() activeProfileId threw 'TypeError: (0, _createHash) is not a function' during setup. That single root cause failed the no-uncaught-console-errors smoke assertion in all five Electron CI jobs (run 38317577397).
Reuse the repo's existing pure-JS synchronous DraftDigest from app/runtime/migration/draftChunks.ts instead of inventing a third implementation. SubtleCrypto is async and cannot serve a synchronous computed(), and an IPC round-trip cannot be awaited inside one either.
The digest output is byte-identical to createHash('sha256').hex.slice(0, 32), so no identity migration is needed; proven byte-identical before/after for profile:opencode:http://localhost:4096, profile:codex:ws://localhost:23004/codex and profile:acp:ws://localhost:23004 (plus four more endpoints), and pinned against Node createHash across every SHA-256 padding boundary by app/utils/legacyDigest.test.ts.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Add the connection/execution profile management service (plan task 25, GAP-5): a host-local registry for local/direct/ssh/slurm transports with stable UUIDs, opaque credentialRef and target->environment mapping, exposing the same management API to Browser and Electron through the bridge HTTP /api/v1/profiles surface.
New: shared/runtime/connectionProfile.{js,d.ts} (schema, identity derivation, secret guard), bridge/runtime/transports/{transportContract,profileStore,connectionManager}.{js,d.ts} (kind/role/capability contract, atomic JSON persistence, profile lifecycle + dial management). Additive: bridge/bridgeConfig.{js,d.ts} (defaultBridgeProfilePath / resolveBridgeProfilePath + store.profilePath) and bridge/bridgeHttpRoutes.{js,d.ts} (handleProfileHttpRequest + profileErrorStatus, every response through assertNoSecretPayload).
Acceptance criteria proven by the four new suites (28 tests): (a) create/edit/delete/reconnect preserves the environment identity, derived only from target; (b) a disabled source is never auto-enabled (connect/reconnect reject source_unavailable, only the explicit enabled action flips it); (c) deleting a connection preserves native history and owned jobs; (d) no normal payload carries a secret (secret-bearing requests rejected at the boundary and never echoed).
No live SSH/Slurm verification here (transportContract marks those live=false); task 46 owns end-to-end transport verification. The one-line server dispatch that calls handleProfileHttpRequest under /api/v1/profiles is a documented integration seam owned by the server lane; visBridgeServer.js was intentionally not edited.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Implement the recoverable Slurm job model (plan task 28, GAP-4/GAP-5): an intent UUID flows into an sbatch-parsable script and state is read back through squeue/scontrol.
- submit persists the intent and returns the explicit submitted state without waiting for ready or querying the scheduler.
- reconcile never resubmits: a lost ack / zero match becomes unconfirmed, a multi-UUID match becomes reconciling/ambiguous, and a duplicate start is refused by the single-owner store fence.
- the durable control store is a login-local, atomically-written (0600/0700, fsync+rename) hash-chained journal that rejects network filesystems (NFS/Lustre) and enforces single-writer fencing with a monotonic epoch.
- time/gres must be explicit (invalid_request/resources.{time,gres}); gres none is written verbatim.
- detach does not cancel; stop signals only owned jobs - a borrowed allocation is never signalled.
- the shared control root and the compute-node working root are independent (assertIndependentRoots rejects overlap).
- visBridgeCli gains the control-store role; vis_bridge.js binds it.
New: bridge/runtime/transports/slurmJobs.{js,d.ts}, slurmControlStore.{js,d.ts}, slurmBootstrap.{js,d.ts}; app/runtime/slurmJobs.test.ts (16 tests). Independent whole-28 gate APPROVE (24/24 checks PASS, zero blocking findings).
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Dev-only QA capture helpers for task 28 (plan convention, mirroring tasks 23/24/26): the runtime-v090 task-28 case driver and its fixture + browser shim, plus the app/dev mount page they render.
scripts/qa/runtime-v090-cases/task-28.mjs drives the real landed slurmJobs/slurmControlStore/slurmBootstrap modules against an injected scheduler edge in real headless Chromium over a real Vite server: 15 happy + 15 failure scenarios, every assertion compares a computed observed value to an explicit expected literal (check() persists {observed, expected} then assert.deepEqual). task-28-fixture.mjs supplies the deterministic scheduler fixture; task-28-browser-shim.mjs is a case-owned node:* stand-in so the real crypto runs in the Node worker, not the browser; app/dev/runtime-v090/task28.ts is the mount page.
These helpers are dev-only and are not part of the shipped app.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Task 30: the SSH/HPC connection UI and lifecycle feedback (GAP-5). Adds the five owned runtime surfaces plus the useRuntimeProfiles composable that drives them: a profile picker, host-key challenge/trust, the Slurm submit + resource and time preview, and the pending-reason / job / compute-node status readout, with detach / stop / reconnect / update actions gated on ownership. The execution target identity flows through the workspace and terminal as the profile's stable environment UUID, rendered next to the visible compute node. - ConnectionManager.vue: the entry surface - the ONE capability entry resolved as a pure function of (transports, managedTransport), a VISIBLE direct-only marker when no managed bridge exists, the profile picker, the challenge / trust dialog, the SSH/Slurm composer switch and the footer actions. - useRuntimeProfiles.ts: resolveRuntimeCapabilityEntry, the lifecycle + cancel plan (a connection cancel can never emit a scheduler signal), the consume-once secret box whose toString/toJSON projections are redacted, resolveRuntimeTargetIdentity and the exported createBridgeProfileClient. - SshHostForm.vue: SSH host form validated through the REAL strict parser; the stored credential is a reference, never a secret. - SlurmExecutionForm.vue: account/partition/cpus/memory/time/gres, ownership, and the bridge-produced resource/time preview. - ConnectionStatus.vue: pending reason / job / compute node / step / epoch / bridge instance / terminal, with the environment UUID and the node as separate identity attributes. - RemoteBridgeActions.vue: detach / stop / reconnect / update with owned-only and explicit-action gating. - useRuntimeProfiles.test.ts (25) + ConnectionManager.test.ts (22): 47 tests over the real bridge modules and real happy-dom SFC mounts. The five acceptance criteria: (1) Browser and Electron resolve ONE capability entry as a pure function of (transports, managedTransport) - the client is attribution only, never a capability source; (2) with no managed bridge the UI renders a VISIBLE direct-only marker and keeps the SSH/Slurm rows present-but-unsupported rather than absent; (3) cancelling a connection detaches a borrowed job and emits ZERO scheduler signals (stop is the only signal path and refuses borrowed); (4) a password/OTP is never cached - it is handed over once and redacted in every projection; (5) the rendered execution target is the profile's stable environment UUID, distinct from the compute node, so two environments sharing a node stay distinct and one environment survives a node move. Accepted limitation: no live SSH or live Slurm verification (task 46 owns real end-to-end). The scheduler/process edge is an owned fake of the injected Slurm lifecycle port, because the task-28 SlurmJobController has no HTTP or WS route yet; the real bridge modules, the strict parser and the real components execute. Entry bundle unchanged in reach: no production module imports the six new files (App.vue integration belongs to a later task), so the entry graph is untouched. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
QA harness for the task 30 SSH/HPC connection lifecycle: the happy/failure case module, the fixture and owned SSH/Slurm fakes, the browser shim, and the dev harness entry. Evidence capture only; no product behaviour. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
|
AI 辅助源码审查(当前 PR 未合并) 审查 head: 确认以下 3 项 P2:
范围与验证:
|
Source review on PR #183 found four issues on the Slurm compute relay; two are P1 credential failures. Each is fixed with a named RED->GREEN test. i1 [P1] reverse storage before the handshake proved the credential: dispatch() only gated the control branch on `handshaking`, so data / storageRequest / storageAck reached the login control store from a peer that had proved nothing. The unified dispatch entry now accepts only the expected handshake message before the proof, fails the channel on any other tag, and a failed handshake stops the rest of the batch. i2 [P1] the ACK was reflectable by flipping the message kind: hello and ack shared one HMAC input and the receiver never matched the ACK nonce against the challenge it issued. The handshake is now mutual and domain-separated — every leg's HMAC carries its kind, the ack binds the compute side's fresh challenge, and the login answers that challenge with a confirm. A reflected challenge, a historical ACK and a cross-connection replay all fail; both ends must prove possession of the credential. i3 the default srun step ran a bare `vis_bridge relay` the real CLI refuses, and the relay role only listened instead of forwarding stdio. The step now builds the complete binding (environment/job/bridge-instance /compute-node/socket/epoch) and a new `relay --stdio-forward` entry pipes the step's stdio to the compute-local socket, stdout carrying protocol bytes only. A test drives the real subcommand process boundary. i4 EOF or a silent peer during the handshake left `ready` unsettled, so open() hung and the spawned step was never reclaimed. A handshake timer, EOF/stream-error handling, child error/exit mirroring and an explicit close all settle `ready` now, and the step transport records the pending step before the handshake so every failure path terminates it.
…ion lifecycle Source review on 395a828 raised five P2 findings against the task-30 connection surfaces. Each is fixed with a named test that went RED before the change; none of the pre-existing assertions was weakened. 1. ConnectionManager.vue — a secret never outlives its challenge. The typed value is stamped with the challengeId it was entered against, and the input is rebuilt per challengeId. A challenge that is revoked, replaced, re-presented as another kind, or hidden by a close clears the value, so an answer can no longer pair a new challengeId with an old secret, and the OTP input (type=text) has no plaintext path to a previous password. 2. useRuntimeProfiles.ts — the real client honours the real envelope. GET /api/v1/profiles answers with a profile LIST whose entries carry neither state nor generation, and the bridge serves no inspection route (PROFILE_ACTION matches only connect|disconnect|reconnect|enabled). inspect() no longer asserts that list into a status array, which made refresh() throw "profiles is not iterable"; it reports no statuses instead of fabricating one. 3. useRuntimeProfiles.ts + task30.ts — a profile and the intent it submits are different identities. selectedSlurm resolved by comparing an intent id to a profile id, so a real submission was unresolvable and detach/stop acted on a profile id. The intent is now tracked separately with an explicit profileId -> intentId association recorded at submit time; the intent is never resolved by environmentId, so two profiles in one environment never share a job. The QA case's 12 injections that set intentId = profileId are replaced with distinct UUIDs and the real submitted intent id, plus new assertions that the two differ. 4. SlurmExecutionForm.vue — the preview shown IS the request submitted. The form compares the displayed preview field-by-field against the same normalization the submit path uses, emits preview-request on every edit so the container can answer with a bridge preview for that draft, and refuses to submit while no preview has arrived or the preview no longer describes the request. 5. useRuntimeProfiles.ts — a disconnect updates the status it was given. cancelConnection discarded the port's answer, leaving the chip reporting connected after a successful cancel. connect/reconnect/cancel now record through one helper that orders by REQUEST rather than by the reported generation, because the real connectionManager.disconnect deletes the record and answers with generation 0 while a connect answers with a monotonically increasing one — comparing those would have discarded a legitimate disconnect. Gates: npx vitest run (5857 passed), npx oxlint exit 0, npx vue-tsc --noEmit exit 0, buildArtifact cap and desktopUpdateMetadata (6/6) unchanged. vite.config.ts untouched. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Addresses the six still-open P2 findings from the third review round on PR #182 (comment 6138580284). Each is a value or state object that outlived the boundary it belonged to and was then reused as if it were current. connectionManager: a pending dial registered before openTransport was retired only on the success path, so a rejected dial's promise was reused by every later connect and the transport was never dialed again. Retire the entry on every failure path, guarded so a superseded dial cannot delete its successor's entry. sshTransport: a single hello fired immediately after spawn and an ECONNREFUSED was treated as final, so a forward whose listener appears after spawn failed while the child was still alive and was terminated. The hello timeout is now the total readiness budget and a refused connect is retried inside it while the child is alive; the shipped hello client carries the Node error code so refusal is distinguished from silence. slurmControlStore: the journal was loaded before the ownership lock was taken and never refreshed inside it, so a normal owner handoff appended from a stale sequence and forked the hash chain. Verify and reload the journal under the lock before writing. codexProcess: a bound-buffered result-first frame that outgrew the ordinary bound before its envelope id was known was attributed to the pending raw request, publishing an abandoned request's bytes into the live sink. Drain such a frame to its newline and publish it to no sink; a second unattributable frame fails the pending read instead of parking it. useRuntimeMessageSend: every accepted receipt, queued or not, became the turn owner, so cancelTurn cancelled the queue instead of stopping the running turn. Only a non-queued send becomes the owner, and that owner is retired by the turn's own completion. client/useRuntimeSessionTree: the cached index view was reused for its whole TTL with no way to invalidate it, and a parent's child scan was frozen as already-scanned, so a repeated search or refresh could not see newly indexed sessions. readIndex gains an explicit fresh option, set for a new search, an explicit refresh and the post-retry topology re-read while pagination inside one traversal keeps its view; a child scan is retried against a newer index view. Each fix is pinned by a named test that was red before the change. The entry-js budget cap and vite.config.ts are untouched.
…tep-relay Carries forward PR #182's reviewer fixes (commit 2995605, six findings across connectionManager, sshTransport, slurmControlStore, codexProcess, useRuntimeMessageSend, client and useRuntimeSessionTree) into PR #183 so the upper branch no longer lacks them. Dry-run with git merge-tree --write-tree produced tree 3b9288d, exit 0, no conflicts. Merge chosen over rebase (plan precedent #181->#182, user-approved); no force-push needed, remote fast-forwards.
…-30-connection-lifecycle Carries forward PR #183's four reviewer fixes (commit ad3a101, including the two P1 credential-boundary findings on relayHandshake domain separation and computeRelay) plus, transitively through 7af80f4, PR #182's six reviewer fixes (commit 2995605). Dry-run with git merge-tree --write-tree produced tree c4d2b57, exit 0, no conflicts. Merge chosen over rebase (plan precedent #181->#182, user-approved); no force-push needed, remote fast-forwards.
…task-31-thread-bindings Carries forward PR #184's five reviewer fixes (commit 699ac02, on ConnectionManager, useRuntimeProfiles and SlurmExecutionForm) plus, transitively through 200eb5a, PR #183's four fixes (ad3a101, incl. the two P1 credential-boundary findings) and PR #182's six fixes (2995605). This is the top of the propagation chain: tasks 32-41 descend from this branch and therefore inherit all three fix sets. Dry-run with git merge-tree --write-tree produced tree ef8a801, exit 0, no conflicts. Merge chosen over rebase (plan precedent #181->#182, user-approved); no force-push needed, remote fast-forwards.
…32-turn-timeline Carries forward PR #184's five fixes (699ac02), PR #183's four fixes (ad3a101, incl. the two P1 credential-boundary findings) and PR #182's six fixes (2995605) through the propagation chain, so this branch's head contains every fix below it. Dry-run with git merge-tree --write-tree produced tree 3170e78, exit 0, no conflicts. Merge chosen over rebase (plan precedent #181->#182, user-approved); no force-push needed, remote fast-forwards.
…-checkpoints Carries forward the whole propagated fix chain (PR #184's 699ac02, PR #183's ad3a101 incl. the two P1 credential-boundary findings, and PR #182's 2995605) so this branch's head contains every fix below it. Dry-run with git merge-tree --write-tree produced tree 36872fb, exit 0, no conflicts. Merge chosen over rebase (plan precedent #181->#182, user-approved); no force-push needed, remote fast-forwards.
…ounds
The first secret rule required the separator to sit directly after the key word,
so the ordinary JSON text {"token":"..."} never matched: the key is followed by
a closing quote, not by ':' or '='. The secret then fell through every
fixed-prefix rule and survived into the serialized checkpoint with
redactions = 0. Accept an optional quote between the key and the separator and
an escaped value, and re-emit the quote so the redacted text stays well formed.
The shrink loop could only drop while more than one entry was retained, so a
single entry that passes its own per-entry check but not with the envelope was
returned over budget (32,949 bytes against the 32,768 ceiling). Re-check the
real serialized size unconditionally and refuse with checkpoint_entry_too_large.
parseCheckpoint verified the counters were non-negative safe integers and that
the digest matched, but enforced none of the ceilings: a 70,744-byte wire
checkpoint was accepted by parseCheckpoint, checkpointService.admit,
evidenceQuery.publish and deserializeCheckpoint alike. A matching digest proves
provenance, not budget, so re-validate the entry count, the derived token and
transcript-byte totals, the per-entry text ceiling and the final serialized
size, and refuse an over-large wire string before it is parsed at all.
Tests: app/runtime/contextFabric.test.ts gains eight regression cases covering
quoted/single-quoted/spaced/escaped secret pairs, the over-matching guard, the
single-entry envelope overflow and the wire capacity re-validation.
|
AI 辅助源码复核(增量,当前 PR 未合并) 当前 head: 旧结论更新:
新增检查 context.js:628–631 要求 独立使用现有长历史测试同类输入(goal + 400 个 plan + unresolved),builder 正常生成 32,528 bytes、4,687 tokens、46 条保留/356 条丢弃的合规 checkpoint; 建议只对实际序列化 checkpoint 执行 32 KiB 上限;保留
总量检查 context.js:629–632只限制输入的 独立构造 32,004 个 ASCII 字符的 entry,按产品同一算法如实标注 entry 为 8,001 tokens,仅将 checkpoint 汇总写成 1;重算正确 entry hash/digest 后完整 wire 为 32,743 bytes。parse、deserialize、admit、publish 全部接受,随后 query.get 仍返回总量 1。也就是说,原 8,000-token 上限仍可越过,且向消费者暴露明显错误的预算统计。触发前提是 wire 提供方提交不一致计数,并非正常 builder 输出。 建议从已验证文本重新计算每条 estimatedTokens,核对/派生总和后再执行上限;同时添加“摘要正确但总和错误”和“entry 自报估算也错误”的回归测试。 验证与边界:读取了实际两文件增量、三个相关共享依赖、checkpoint/evidence 服务入口及 runtimeServices/runtimeDispatch;8 份落地源码/测试文件均核对了当前 Git blob。上述结果来自独立 Node 断言探针;脱敏只使用合成样例。未完整运行 Vitest、lint/typecheck、浏览器 QA、真实 harness、SSH/Slurm 或进程死亡恢复。生产 runtimeServices/runtimeDispatch 接线仍未新增,因此结论限定为真实模块/API 边界,不声称已经存在可由远程用户触发的线上漏洞。原已披露分页参数问题未变,不作为本次新发现。 发布前已重读 head、旧评论及 review 状态去重;当前 head 的 CI尚未终态,不把 CI 或作者验证视作本次独立复现。以上不保证不存在其他缺陷,不构成正式 APPROVE/REQUEST_CHANGES。 |
|
逐条回应(均对 head 源码独立复现,修复推送 sha 发现 1(wire 字段缺失时 JSON.stringify 丢逗号,redactSecrets 无法匹配) — 确认。复现: 发现 2(wire 上限校验漏掉 payload 字节) — 确认。复现:单条 32,498 字符的合法 entry 被接纳,产出 发现 3(class 字段无枚举校验) — 确认。复现: 验证: |
…re-trim statistic Two residual PR #189 findings on the wire re-validation in parseCheckpoint: 1. The transcriptBytes check compared the builder's PRE-TRIM statistic (the cost of every wrapped entry) against the 32 KiB serialized ceiling, so a long history that the builder had already trimmed into budget — 32,540 bytes / 4,687 tokens / 46 retained — was refused on parse, deserialize, admit and publish with invalid_request/checkpoint_transcript_bytes. transcriptBytes is now re-validated against the retained entries plus the dropped cost, which keeps the statistic honest without refusing legal checkpoints. 2. The token budget still trusted the wire's own numbers: the per-entry estimatedTokens was copied straight through and only the summary field was ceiling-checked, so an entry that really costs 8,001 tokens published with a summary of 1 was accepted end to end. Every entry now re-derives its count from its verified text with the builder's own estimator, and the summary is re-derived from those per-entry counts before the ceiling applies. checkpointService/evidenceQuery gain the .d.ts siblings the other bridge services already have so the contract tests can drive the real service boundary under vue-tsc.
|
AI 辅助源码复核(增量,当前 PR 未合并) 本次 head: 上次两项 P2 已修复:
新增 1 项确定问题: [P2] evidenceQuery 新声明与真实 reader 契约相反,会让合法 TypeScript 接线失败,并认可运行时必拒绝的返回值。 新增 evidenceQuery.d.ts:4–10把 readEvidence 的返回值声明为 独立最小 TypeScript 严格检查中,返回 string / Uint8Array 的两个实现均通过;将它们接到真实 createEvidenceQuery,publish 合法 evidence checkpoint 后调用 context.evidence.page,两者都得到 建议直接复用共享 EvidenceReader / TargetResolver,不要另造不一致的端口类型;补充“类型检查 + publish → callTool(page)”与 missing 分支测试。新增测试目前仅调用 publish,没有走到 reader,因而未覆盖这个不一致。触发边界是采用新增声明的 TS 服务调用者;没有证据表明现有生产构建已经因此失败。 验证范围:
发布前已重读 head、评论和 review 状态去重。CI 终态更新(2026-10-18 06:16 UTC):再次确认 head 未变;本 head CI已 completed/success,13 个 job 成功,Publish draft release 按流程跳过。此为 GitHub CI 状态,不替代上述独立验证,也不消除 reader 声明问题。以上不保证没有其他缺陷,也不是正式 APPROVE/REQUEST_CHANGES。 |
|
逐条回应(均已对 head 发现 1(长历史裁剪后的字节预算判断) — 确认,REAL DEFECT,已修复。 发现 2(token 汇总校验) — 确认,REAL DEFECT,已修复。 顺带说明(不属于复审新发现,但为让回归可编译):按本仓既有约定给 验证: |
…d target contracts
The new evidenceQuery.d.ts declared readEvidence as returning
string | Uint8Array, but the service hands the reader to readEvidencePage
unchanged, and that function requires { bytes: Uint8Array, totalBytes } or
null: a string or raw byte answer is refused at runtime with
invalid_request/evidence_reader, while the object reader the runtime accepts
and the null reader that yields evidence_missing were both rejected by the
declaration with TS2322. resolveTarget was likewise a loose
(...args) => unknown instead of the shared TargetResolver, so a caller could
wire a resolver whose answer parseTarget refuses.
The ports are now the shared EvidenceReader and TargetResolver types, so the
declaration, the dispatcher and the runtime agree. The publish-only call
sites are wired with type-correct ports, and a new test publishes a legal
evidence checkpoint through the real service and calls
context.evidence.page: the object reader serves the bytes, the null reader
yields evidence_missing, a moved target is refused before the reader runs,
and a string reader — the shape the old declaration accepted — is refused at
runtime.
|
AI 辅助源码增量复核(dot)。 当前 head: 上次 reader 声明 P2的类型定义已修正,实际服务的对象/null reader 路径独立验证通过。
独立验证:Node v24.19.0 直接加载精确 head 的原始服务与共享模块,6 个定向场景通过:
类型结论与执行范围分开说明:本轮是对声明直接复用共享类型的源码核对,并辅以上述真实运行时探针;当前环境没有可用 TypeScript/Vitest,因此未重新执行最小 tsc 消费者、完整 vue-tsc 或原 Vitest 套件。不能把这些未执行项目写成独立通过。实现、声明、依赖和测试共 7 份落地文件已逐项核对 Git blob。没有运行完整项目 lint/构建、浏览器 QA、真实 harness、SSH/Slurm 或进程死亡恢复。 在本次两文件增量及已验证分支内,未发现新的确定缺陷。既有工具分页参数接线缺口不在本次修改中;本结论不表示所有功能或生产接线已完成。 2026-10-19 02:50 UTC 复查:当前 head CI已 completed/failure。Validate 的 lint/type-check、tests 和 web build 均成功;失败在 Bridge installer (windows, x64) 的 Verify Windows process-tree cleanup: |
|
针对 #189(P2,evidenceQuery 声明与真实 reader 契约相反)已修复,head RED(修复前,类型检查):按共享契约正确接线( 修复( 逐条证据:
未验证(如实声明):未跑全仓 vitest、完整 lint/vue-tsc 之外的范围、浏览器 QA、真实 harness/SSH/Slurm;runtimeServices/runtimeDispatch 仍无 context 服务生产接线(与复核观察一致),本次结论限于声明边界及其测试。证据: |
…able graceful phase The Windows graceful phase of stopProcessTree ran the PowerShell CIM descendant sweep and then still waited the full grace period for the leader to exit before escalating to force. `taskkill /T` without `/F` cannot terminate a windowless console process (exit 128) and the CIM sweep excludes the root, so the graceful phase provably cannot stop a leader: the grace wait was dead time, and the sweep was redundant because the forced `taskkill /T /F` reaps the live tree anyway. That dead time plus the job's first powershell/Get-CimInstance cold start put `terminates descendants of a SIGTERM-resistant terminal` (acpTerminalManager.test.ts) past its 10s budget on the windows-x64 runner: the same blobs passed at 3333ms the day before and timed out at 10008ms. Extract taskkillProcessTree as the graceful probe (stopWindowsProcessTree keeps its taskkill-plus-sweep contract for signalProcessTree and forceStopProcessTree callers) and have stopProcessTree escalate immediately when the probe fails. The grace period is still honoured when the graceful taskkill succeeds, and the sweep still runs in the forced path for a leader that already exited, the one case taskkill cannot enumerate.
|
Windows 进程树清理测试超时的根因已定位并修复,head 推进至 根因(产品缺陷,非环境波动) 失败测试
同类 blob 的时序证据:上一次通过的 run 606(head 修复(RED-first) 新增 RED 先在 Linux 上以伪造 win32 平台 + mock windows 模块(Windows 语义已在测试注释中披露)复现缺陷:旧代码下优雅期仍调用扫描、仍烧掉 grace 期,8 个新用例失败;修复后 11/11 通过。 验证范围(如实说明)
未验证项:未在真实 Windows 上逐项测量 taskkill 各退出路径耗时;macOS/Linux 行为仅有本机与 CI 既有覆盖,无新增风险面。 |
|
AI 辅助增量复核(2026-10-19 06:44 UTC) Head: 结论:此前记录的 Windows x64 cleanup CI 超时已在当前运行消失;本轮增量未发现新的确定缺陷。
独立验证:Node v24.19.0 加载本 head 两份原始 JS,通过隔离 VM 注入假的 win32 平台及命令/子进程事件,10 个定向场景通过:taskkill 成功、128 失败、启动 error;强制参数与失败后的 sweep;sweep 失败传播;探针失败/抛错立即强制;优雅期正常退出不强制;优雅期超时升级;强制后仍不退出则拒绝。没有执行真实 taskkill、PowerShell 或进程终止,不能将这些模型测试表述为 Windows 原生验证。 外部 CI 已独立读取 job 原始日志:run 39210306512 completed/success,13 个 job 成功,draft release 跳过;执行树为
限制:本轮没有在本地执行完整 Vitest/typecheck、原生 Windows 安装或进程树测试、SSH/Slurm。CI 证实当前回归门禁通过,但不足以单独证明此前耗时上涨一定由 Defender/AMSI 冷启动造成;该精确归因未独立验证。本结论限于上述增量及已检分支,不是无缺陷保证,也不构成正式 APPROVE/REQUEST_CHANGES。 |
What this delivers
Task 33 — bounded checkpoints and scoped evidence access (plan task 33, closes GAP-6, GAP-8):
bridge/runtime/threads/checkpointService.js— 32KiB / ~8k-estimated-token checkpoints with goal/plan/decision/diff/test/artifact/unresolved sections, source hashes and byte pages; over-budget refuses (checkpoint_entry_too_large) rather than truncating; plaintext tokens cannot enter a checkpoint.bridge/runtime/threads/contextEnvelope.js— redaction and untrusted labels; injected text is carried as DATA regardless.bridge/runtime/threads/evidenceQuery.js— scope-authorized, by-page evidence reads with content-addressed hashes; cross-thread/env queries unauthorized; deleted artifacts reported unavailable, never fabricated.shared/runtime/context.js(+.d.ts) — the shared context fabric contract, incl. the MCP-compatible read-only tool server surface.app/runtime/contextFabric.test.ts— the regression suite (39 tests).Stack membership
This PR is part of a stack — it is stacked on PR #188 (task 32,
feat/v090-task-32-turn-timeline). Stack order: 31 → 32 → 33 → … → 41. Opened againstfeat/v090-wave-4-baseper the plan's delivery rule; the diff carries the not-yet-merged ancestors (PRs #182/#183/#184 and tasks 31–32).Disclosed limitations carried forward verbatim
context.evidence.pageadvertises optionaloffset/lengthin its own inputSchema (context.js:744-753) butTOOL_ARGUMENT_KEYSfreezes the accepted keys to[checkpointId, scope, hash](context.js:761). NOT blocking: the by-page capability is proven over the real product API in both realms (observed walk 0/4096/4096/8192/17 against totalBytes 8209), the gap fails CLOSED, and authorization still precedes every read (readCallsAfterUnauthorized 0 / crossThreadReads 0).src/main/host/handoff-checkpoints.js:178-207andhandoff.js:65-74do not exist in this environment; no claim rests on them; end-to-end wiring is task 46's remit.ceil(chars/4), not a real tokenizer; the budget is an inequality with large observed headroom.max_entry_chars65536 vscheckpoint_max_bytes32768; the over-budget path refuses rather than truncating.redactSecretsappends one more]on an input that already contains the placeholder — unreachable in the product flow (each raw entry is redacted exactly once at the entry boundary); excluding[would create a redaction BLIND SPOT for secrets containing[.Gate verdict
On this branch in a clean worktree:
npx vitest run app/runtime/contextFabric.test.ts39/39 passed;npx oxlintexit 0;npx vue-tsc --noEmitexit 0. Independent QA gate (fresh-eyes, re-derived every number): APPROVE / high confidence; registered invocations happy 20 scenarios / 20 assertions passed and failure 21/21 passed, both hash-bound to the delivered bytes.