Skip to content

tracking(runtime-host): condition-driven Session waits and wake-ups #5926

Description

@chinawch007

This issue follows Discussion #5920: condition-driven waiting and wake-up and tracks its seven implementation slices. The discussion explains the motivation, architecture, and boundaries; this issue turns them into separately reviewable, testable deliveries. It does not imply approval of every design detail.

Motivation

Waiting for CI, background tests, PR collaboration, or external data can currently involve repeated model-driven status checks or an execution held open by a long-running tool call. Goal's timed waiting path can also start new check Turns while nothing changes, spending iterations intended for actual work.

The objective is to let the agent register an explicit condition, let Host observe it independently, and continue through existing admission only when it resolves, becomes invalid, expires, or produces an actionable exception. This reduces unnecessary model participation, releases execution occupied solely by waiting, and unifies cancellation, deadlines, verification, and result delivery.

Target and boundaries

  • Initially support ShellRun, GitHub CI, PR/review events, and external-file readiness.
  • Support standard events and deterministic data predicates expressed through bounded templates—not arbitrary natural-language predicates, semantic watchers, or generated observer scripts.
  • Reuse Goal continuation, root-Turn admission, and execution persistence authority. Do not add a second execution lifecycle or independent wake database.
  • Preserve existing product rules for continuation, confirmation, and replay after interruption. Restoring a wait does not restart an old process or grant new permissions.
  • Registration, observation, waking, and concrete actions remain separately authorized. Model arguments cannot attest that the user granted permission.
  • Use the Goal path as the first registration/continuation consumer. Ordinary Session follow-ups require explicit ownership and admission contracts rather than assumed coverage.

PR 1: wait contracts and persistence foundations

Objective: turn “which task is waiting for which condition” into identified, versioned, recoverable state rather than prompt text and in-memory intent.

Changes:

  • Define provider-neutral records for Session/Goal control identity, resource, condition type/version, deadline, and result correlation.
  • Add bounded validation, state transitions, CAS updates, and pagination rather than unrestricted JSON or unbounded scans.
  • Add SQLite persistence, an independent memory reference implementation, and root-lease-scoped access inside the existing execution consistency domain.
  • Initially allow one effective, undelivered wait per Session. Stable wait and delivery keys support later deduplication without creating a second continuation queue.
  • Integrate archive/removal/operational-state cleanup and workflow schema compatibility tests.

Acceptance:

  • Reopening retains records; duplicate creation and racing updates cannot overwrite newer state.
  • Stale requests cannot reopen cancelled, expired, or resolved records.
  • Providers agree on behavior; closed or invalid leases cannot continue accessing the store.
  • No new watchers, tools, model calls, or Goal scheduling behavior.

A local implementation handoff is available in the PR 1 plan. Remove this relative link before posting, or replace it with the actual attached plan URL.

PR 2: Host observation and condition management

Objective: observe valid waits and produce explicit results without invoking the work model.

Changes:

  • Define adapter contracts for initial reads, notifications/polling, cancellation, and reconciliation.
  • Implement a Host wait manager with TTL, frequency limits, backoff, and source-unavailability handling.
  • Cover conditions satisfied before registration; notifications and current-state checks enter the same evaluation path.
  • Persist observation progress, stable event identity, and bounded evidence, distinguishing pending, satisfied, invalidated, and unknown.
  • Validate with fake providers, a controlled clock, and real persistence; do not introduce production GitHub credentials or start execution directly.

Acceptance:

  • Unchanged observations do not call the work model; never-satisfied conditions expire.
  • Duplicate notifications, read/subscribe races, restart, and throttling do not cause missed results or unbounded observation.
  • Restart preserves absolute deadlines; query errors are not satisfaction.

PR 3: execution bridge, tool yield, and Goal integration

Objective: make registration and result delivery safe boundaries for yielding and admitting follow-up work.

Changes:

  • Add the high-level wait tool's runtime control path. Host binds trusted identity and permissions, then ends current execution correctly rather than merely asking the model to wait.
  • Gate Goal continuation so legacy waiting timers and evaluator failures cannot bypass an explicit wait.
  • Use existing durable continuation and root-Turn admission with typed provenance; handle busy admission, pause, cancellation, Goal replacement, and archive.
  • Address deduplication and crash windows in one delivery/admission protocol. A recorded result must not be confused with an admitted execution.
  • Integrate normal settlement and token budgets. When idle waits stop consuming work iterations, TTL, observation limits, and per-subscription wake bounds must already be effective.
  • Reconcile existing execution after restart under current recovery policy; do not redispatch old operations with unknown effects.

Acceptance:

  • Real admission plus a fake model proves: register → current execution ends → no model calls without change → result arrives → one effective successor.
  • Pause, cancellation, busy admission, and crash injection do not cause uncontrolled or duplicate continuation.
  • Keep the production tool hidden until a real source is ready; do not expand task authorization.

PR 4: ShellRun terminal-state adapter

Objective: ship the first usable “start a long test and continue handling its result” flow.

Changes:

  • Register waits on authorized background ShellRuns using stable task identity and durable terminal facts.
  • Map completion, failure, cancellation, and orphaned state explicitly. A notification triggers verification rather than replacing it.
  • Preserve existing cleanup callback responsibilities. Already-completed tasks can be read immediately without awaiting another notification.
  • Expose the tool, minimal wait status, and cancellation; verify supported executor and Code Mode boundaries.

Acceptance:

  • Results return to the correct Session; repeated completion does not duplicate execution.
  • Tasks without registered waits do not automatically invoke models.
  • Knowing a task ID does not expose user-owned terminals or other task scopes.
  • Restart follows task facts and existing policy; commands with unknown effects are not automatically restarted.

PR 5: GitHub CI

Objective: observe remote CI through the same protocol without requiring Webhook setup.

Changes:

  • Add explicitly authorized GitHub connections and background API observation.
  • Bind repository, head SHA, and run/attempt; define specific-run and declared-check-set conditions separately.
  • Handle success, failure, cancellation, credential loss, throttling, and offline reconciliation.
  • Filter stale versions and irrelevant changes while bounding observation frequency.

Acceptance:

  • Deliver current-target results without waking on stale state, duplicate reads, or query errors.
  • Adding GitHub as the second source does not add GitHub-specific core fields or another execution queue.
  • “This run completed” cannot stand in for “all required PR checks are satisfied.”

PR 6: PR/review collaboration events

Objective: reuse GitHub integration for merge, closure, and explicit review conditions.

Changes:

  • Add merged, closed, review-arrival, and identity/revision-bound approval conditions.
  • Handle closure without merge, dismissed approval, new commits, and late reviews.
  • Deliver comments/review content as data for subsequent analysis, not watcher-level semantic judgments or new authorization.

Acceptance:

  • Distinguish review arrival, receiving approval, and satisfying current merge requirements.
  • Historical or dismissed reviews do not become current facts.
  • Different GitHub notifications satisfying one one-shot wait still produce one effective successor.

PR 7: external files and deterministic data readiness

Objective: continue after a user or another program delivers data, without guessed sleep intervals.

Changes:

  • Combine filesystem notifications and scans for already-satisfied conditions and missed notifications.
  • Initially support completion markers, manifest/checksum sets, and a small set of bounded assertions such as required columns in an explicit CSV set.
  • Validate path scope, replacement, and resource identity; preserve pending, invalidated, and unknown.
  • Let models fill supported template parameters, not submit arbitrary observer programs.

Acceptance:

  • Partial writes, replacement, notification loss, and restart are reconciled against a defined readiness contract.
  • File creation is not completed writing; directory stability is not complete delivery.
  • Report uncertainty instead of substituting an approximate signal for the user's requirement.

Merge order and shared acceptance

PR 1 contracts/storage → PR 2 observation → PR 3 execution bridge → PR 4 ShellRun
                                                                     ├→ PR 5 CI → PR 6 PR/review
                                                                     └→ PR 7 files

PRs 1–3 are reviewable separately but must not create separate execution authorities. PRs 3 and 4 may be combined based on size. Foundations can land before exposure; cancellation, deadlines, deduplication, permissions, and restart safety must accompany the first usable slice rather than become post-release patches.

Overall completion:

  • Unchanged external state does not repeatedly invoke the work model.
  • Waiting does not keep an execution occupied solely for querying.
  • Matching, delivery, admission, and goal achievement are separately explainable.
  • Duplicate, stale, cancelled, and invalid events cannot create incorrect successors.
  • Interruption between result and admission follows existing recovery rules without silent loss or blind replay.
  • All four sources use the common protocol with source-specific identity, permission, and fault tests.
  • Work accounting and waiting bounds are separated while real model execution remains budgeted.
中文(点击展开) 本 issue 由 [Discussion #5920:条件驱动的等待与唤醒](https://github.com//discussions/5920) 引申而来,用于跟踪讨论中的七个实施 PR。讨论提供问题背景、架构及边界;本 issue 将其整理成可逐项评审、验证和交付的修改计划,不代表全部设计已经获得批准。

为什么做

等待 CI、后台测试、PR 协作状态或外部数据时,当前任务可能反复让模型查询状态,或让同一个执行长时间等待工具返回。Goal 的定时 waiting 路径也可能在外部没有变化时启动新的检查 Turn,消耗原本用于实际工作的轮次。

我们希望让 Agent 登记明确的等待条件,由 Host 独立观察,只有条件满足、失效、超时或出现需要处理的异常时,才经现有执行接纳继续原任务。这可以减少无效模型参与、释放纯等待占用的执行位置,并统一期限、取消、状态核验和结果交付。

目标与边界

  • 短期支持 ShellRun、GitHub CI、PR/review 事件和外部文件就绪。
  • 支持已有标准事件,以及受支持模板能够确定性验证的通用数据条件;不做任意自然语言条件、语义 watcher 或模型生成的观察脚本。
  • 复用现有 Goal continuation、root-Turn admission 和执行存储权威,不新增第二套执行生命周期或独立 wake 数据库。
  • 中断后是否继续、是否需要确认、是否允许重跑沿用现有业务逻辑。恢复等待不等于重启旧进程,也不增加原任务权限。
  • 订阅、观察、唤醒和实际动作分别受权限约束。模型不能用参数声明“用户已经授权”。
  • 首批登记和续跑消费者从 Goal 路径接入。普通 Session 的独立 follow-up 不自动视为已覆盖,需有明确所有权与接纳契约。

PR 1:等待契约与持久化基础

**目标:**将“哪个任务正在等待哪个条件”变成有身份、有版本、可更新且可恢复的事实,而不是只留在提示词和内存中。

修改内容:

  • 定义 provider-neutral 的等待记录:所属 Session/Goal 控制身份、来源和资源、条件类型与版本、截止时间及结果关联键。
  • 定义有界的记录校验、等待状态转换、CAS 更新和有界分页,避免开放任意 JSON 或无上限全量扫描。
  • 在现有执行存储一致性域中增加 SQLite 实现、内存参考实现和受 root lease 约束的访问 facade。
  • 首版限制一个 Session 同时只有一个未交付的有效等待;稳定 wait ID 与交付关联键用于后续去重,不创建第二个 continuation 队列。
  • 为归档、删除和 operational-state purge 接入清理;更新 workflow schema 和迁移兼容测试。

验收:

  • 记录可重开读取,重复创建和并发更新不会覆盖较新状态。
  • 取消、过期和已记录结果的状态不会被旧请求重新打开。
  • 两种 provider 行为一致,关闭或失效 lease 不允许继续访问。
  • 没有新增 watcher、工具、模型调用或 Goal 调度行为。

详细执行方案另附本地计划:PR 1 实施计划。发布 issue 时可移除这一相对链接,或改为实际附上的计划地址。

PR 2:Host 观测与条件管理

**目标:**在不调用工作模型的情况下,持续观察仍然有效的等待,并形成明确结果。

修改内容:

  • 定义来源适配器的首次读取、通知/轮询、取消和重新校准契约。
  • 实现 Host 等待管理器,对有效记录安排观测,处理 TTL、频率限制、退避和来源失联。
  • 首次检查覆盖“登记前条件已经满足”;通知与当前事实核验进入同一个判断路径。
  • 保存观测进度、稳定事件身份和有界证据,区分 pending、satisfied、invalidated 与 unknown。
  • 使用 fake provider、可控时钟和真实存储验证行为,暂不接入生产 GitHub 凭据,也不直接创建执行。

验收:

  • 无变化时不调用工作模型;永远不满足的条件会按期限结束。
  • 重复通知、读/订阅竞态、重启和限流不会造成漏结果或无限观测。
  • 重启不重置绝对截止时间,查询失败不被当作条件满足。

PR 3:执行桥接、工具让出与 Goal 整合

**目标:**让“登记等待”和“结果到来”分别成为安全的执行让出与后续接纳边界。

修改内容:

  • 提供高层等待工具的运行时控制接口。Host 绑定可信身份和权限;登记成功后正确结束当前执行,而不是只给模型一句“请等待”。
  • 将有效等待与 Goal 控制状态连接,阻止旧 waiting timer 或 evaluator 失败路径绕过等待。
  • 通过现有持久化 continuation 和 root-Turn admission 安排后续执行,携带事件来源身份,处理 busy、暂停、取消、Goal 替换和归档。
  • 在同一份交付/接纳协议中处理去重和崩溃窗口,不将“结果已记录”误判为“执行已安排”。
  • 接入正常结算与 token 预算。取消空等待对工作轮次的消耗时,同时启用 TTL、观测限制和每订阅唤醒上限。
  • 重启后核验已有执行并沿用既有恢复策略,不盲目重派结果未知的旧操作。

验收:

  • 通过真实接纳路径与 fake model 验证:登记 → 当前执行结束 → 无变化时无模型调用 → 结果到达 → 一次有效后继。
  • 暂停、取消、繁忙和不同崩溃位置的测试均不产生失控续跑或重复执行。
  • 未接入真实来源前不开放生产工具;不扩展原任务授权。

PR 4:ShellRun 终态接入

**目标:**交付“启动长测试,完成后继续处理结果”的首个用户可用闭环。

修改内容:

  • 允许等待有权访问的后台 ShellRun;使用稳定任务身份和持久化终态。
  • 将完成、失败、取消和 orphaned 等事实转换为明确结果。完成通知只作为触发,不能代替对可靠记录的核验。
  • 保留原有资源清理回调职责。已经完成的任务可立即读取,不依赖再收到一次通知。
  • 开放等待工具、最小等待展示和取消入口,并验证支持的 executor / Code Mode 边界。

验收:

  • 结果回到正确 Session,重复完成通知不重复执行。
  • 没有登记等待的任务不会一律唤醒模型。
  • 用户终端及其他任务范围不因知道 task ID 就可读取。
  • 重启按任务事实与原有策略处理,不自动重启未知副作用的命令。

PR 5:GitHub CI

**目标:**在不要求用户配置 Webhook 的情况下,用同一协议跟踪远端 CI。

修改内容:

  • 增加明确授权的 GitHub 连接与后台 API 观测。
  • 绑定 repository、head SHA、run/attempt;分别定义单 run 终态和明确检查集合的条件。
  • 处理成功、失败、取消、凭据失效、限流和离线补查。
  • 过滤旧 SHA、旧 attempt 和与订阅无关的变化,维护受控的观测频率。

验收:

  • 当前目标结果可交付,旧状态、重复读取和查询失败不会误唤醒。
  • GitHub 作为第二种来源,不要求向核心 schema 增加 GitHub 专属字段或新建执行队列。
  • “run 完成”不能冒充“PR 所有 required checks 已满足”。

PR 6:PR/review 协作事件

**目标:**复用 GitHub 接入,支持合并、关闭及明确的评审条件。

修改内容:

  • 增加 merged、closed、review 到达及指定身份/版本下的审批条件。
  • 处理关闭但未合并、审批撤销、PR 新提交和迟到 review。
  • 将评论、review 正文作为后续分析的数据,不由 watcher 判断模糊语义,也不视为新授权。

验收:

  • 区分 review 到达、收到 approval 和当前合并要求满足。
  • 旧 review 或已撤销审批不能作为当前事实。
  • 多种 GitHub 通知满足同一 one-shot 等待时只产生一个有效后继。

PR 7:外部文件与确定性数据就绪

**目标:**支持用户或其他程序交付数据后继续处理,不依靠猜测式 sleep。

修改内容:

  • 接入文件系统通知和当前状态扫描,覆盖登记前已完成与漏通知。
  • 首批模板包括完成标记、manifest 文件清单/校验,以及少量有界数据断言,例如明确 CSV 集合具有指定列。
  • 校验路径访问范围、文件替换和资源身份;保留 pending、invalidated 和 unknown 的区别。
  • 模型只填写受支持模板参数,不提交任意观察程序。

验收:

  • 部分写入、替换、通知丢失与重启后都能按明确就绪协议核验。
  • 文件创建不等于写完,目录稳定不等于全部文件已交付。
  • 无法确定时明确说明,不把近似信号当作用户要求已经满足。

合并顺序与共同验收

PR 1 契约/存储 → PR 2 观测 → PR 3 执行桥接 → PR 4 ShellRun
                                                   ├→ PR 5 CI → PR 6 PR/review
                                                   └→ PR 7 文件就绪

PR 1–3 可分别评审,但不能各自引入一套运行状态权威。PR 3 与 PR 4 可按体量合并。基础代码未开放前可以逐步建设;首个用户闭环必须同时具备取消、期限、去重、权限和恢复安全,不能将这些留作上线后补丁。

总体完成条件:

  • 外部无变化时,不反复启动工作模型。
  • 等待不会继续占用仅为查询而存在的执行。
  • 条件匹配、等待交付、执行接纳和目标完成分别可解释。
  • 重复、过期、取消或失效事件不能产生错误后继。
  • 结果与接纳之间的中断按现有恢复规则处理,不静默遗忘或盲目重跑。
  • 四类来源通过共同协议接入,且具备各自的身份、权限和故障测试。
  • 工作轮次与等待限制合理分离,实际模型执行仍正常计量。

Drafted with assistance from Maka, based on the published discussion and code inspection. This is an implementation tracking proposal, not a completed feature or approval of every design choice.

Activity

  1. self-assigned this
    on Oct 2, 2026
  2. chinawch007 commented on Oct 2, 2026

    @chinawch007
    ContributorAuthor

    PR 1 implementation plan: event-wait contracts and dormant persistence

    • Source: Discussion #5920.
    • Inspected baseline: cae4a93ec842f053422aad615708ff18bbba02e2.
    • Status: a concrete handoff plan for an implementation Session. The new types, tables, and APIs below are design choices specified by this plan—not existing repository functionality or a claim of maintainer approval.
    • Sole deliverable: wait records can be correctly created, read, concurrently updated, reopened, and cleaned up within existing execution persistence. Production runtime behavior remains unchanged.

    0. Task brief to give directly to an implementation Session

    Implement PR 1 as defined here, not the entire event-wake system. First check the branch and changes on main, then use the file inventory to add core contracts, SQLite/memory persistence, and an execution-group facade. Cover contracts, migration, leases, cleanup, and provider parity with tests. Only tests invoke the new behavior: do not register tools or watchers, or change Goal accounting, waiting, automatic recovery, or model-call paths. Preserve existing untracked documents. Commit the implementation and report validation results; do not push or create a PR without separate authorization. If the design cannot fit the existing consistency boundary, report the concrete conflict rather than bypassing it with an independent database or execution queue.

    Suggested branch: feat/event-wait-persistence.

    Suggested PR title: feat(storage): add dormant event-wait authority.

    1. Preflight and scope freeze

    Before editing code, run:

    git status --short
    git branch --show-current
    git rev-parse HEAD
    git fetch --no-tags upstream main
    git diff --stat cae4a93ec842f053422aad615708ff18bbba02e2..upstream/main -- \
      packages/core/src/goal.ts \
      packages/storage/src/execution-persistence-provider.ts \
      packages/storage/src/execution-stores.ts \
      packages/storage/src/goal-authority.ts \
      packages/storage/src/sqlite-workflow-schema.ts \
      packages/storage/src/test-only

    Create a separate branch or worktree from the latest main at implementation time. Do not mix this feature into the earlier fix branch, and preserve existing tracked and untracked user changes. If the inspected baseline is unavailable, inspect the relevant files rather than guessing.

    Record the actual baseline, Node/npm versions, dependency state, and workflow schema version. This plan's baseline uses workflow schema 12, so the proposed next version is 13. If main has advanced, use the next workflow version at that time; do not reuse an occupied number.

    In scope

    • Core types, strict decoding, and state-transition validation.
    • A provider-neutral wait record with a CAS revision.
    • A stable result-delivery correlation key, without creating execution.
    • Bounded queries and a storage constraint allowing one undelivered wait per Session.
    • SQLite persistence within the existing consistency domain and an independent memory reference implementation.
    • Execution-group/root-lease-scoped access, lifecycle management, and archive/removal cleanup.
    • Migration, provider parity, fault, and no-side-effect tests.

    Explicitly out of scope

    • No WaitForEvent tool, tool-catalog changes, or model-prompt changes.
    • No timers, polling, filesystem watchers, or external connections.
    • No GitHub authentication or Webhooks.
    • No changes to GoalAuthorityRecord, GoalState statuses, waiting backoff, iterations, or token budgets.
    • No new SessionStatus or wire-protocol fields.
    • No creation of pending Goal continuations, Turns, Runs, or RuntimeEvents.
    • No automatic execution recovery, command redispatch, subscription UI, or notifications.
    • No generic receipt inbox, independent wake queue, or independent database.

    Actual event-intake deduplication belongs to PR 2. Atomic linkage between a wait result and successor admission belongs to PR 3. This PR supplies stable identity, CAS, and the foundation for committing one immutable result to a wait; it must not claim exactly-once wake-up.

    2. Current code facts and reuse points

    File / entry point Current fact Use in this PR
    packages/core/src/goal.ts GoalControlLease carries Goal ID and generation; pendingContinuation already exists Reuse control-identity types/validation rules without changing Goal storage
    packages/storage/src/goal-authority.ts Existing CAS authority, lease facade, and backend-close patterns Use as a structural reference, but do not copy an access path with implicit Local fallback
    packages/storage/src/execution-persistence-provider.ts ExecutionPersistence is an indivisible consistency domain Add eventWaitStore; do not open separate storage in Host
    packages/storage/src/local-execution-persistence.ts Opens/closes execution stores as one group Add the SQLite backend using the same pattern, including partial-open cleanup
    packages/storage/src/execution-stores.ts The authenticated execution group wraps the provider and owns active operations, close, and leases Add a controlled facade managed with the group
    packages/storage/src/sqlite-workflow-schema.ts Baseline workflow schema is 12; workflow tables are built declaratively Add the table and advance to 13 using this module's migration pattern
    packages/storage/src/operational-state-store.ts Shared runtime.sqlite connection, transactions, and schema-scope registry Reuse acquireOperationalStateDatabase and the workflow scope
    packages/storage/src/operational-target-schema.ts Builds and strictly validates the target structure from schema builders Verify that the new table/indexes are covered by target validation and upgrade tests
    packages/storage/src/test-only/memory-execution-persistence.ts Independent memory reference provider, without SQLite Add maps and transactions within the same memory authority
    sqlite-session-metadata-store.ts / conversation-operational-state.ts Archive, retirement, and purge already clean up Goal authority Clean up waits within the same transaction boundaries

    Do not use the SQLITE_RUNTIME_SCHEMA_VERSION numbering for this change. The current runtime schema is 20, but this adds a workflow table. Unless implementation demonstrates a separate need, leave the runtime schema and its PRAGMA user_version numbering unchanged.

    The core/storage package exports are explicit, not ./*. Add the necessary exports for new modules without exposing every internal constructor as a product API.

    3. Minimal v1 contract

    Add packages/core/src/event-wait.ts. Follow the closed-object validation style in record-schema.ts: decode inputs from unknown, rather than accepting external data through type assertions.

    Suggested exports:

    export const EVENT_WAIT_SCHEMA_VERSION = 1 as const;
    export type EventWaitRecord = EventWaitBase & EventWaitLifecycle;
    export type EventWaitStatus = 'waiting' | 'resolved' | 'cancelled' | 'expired';
    export function decodeEventWaitRecord(value: unknown): EventWaitRecord;
    export function assertEventWaitTransition(
      previous: EventWaitRecord,
      next: EventWaitRecord,
    ): void;
    export function eventWaitDeliveryKey(waitId: string): string;

    Use the following record semantics. Types may be split to match repository style, but do not add executor, model-call, or arbitrary-action fields:

    type EventWaitJsonValue =
      | null
      | boolean
      | number
      | string
      | readonly EventWaitJsonValue[]
      | { readonly [key: string]: EventWaitJsonValue };
    
    interface EventWaitBase {
      readonly schemaVersion: 1;
      readonly waitId: string;
      readonly sessionId: string;
      readonly goalControlLease: GoalControlLease;
      readonly sourceTurnId: string;
      readonly sourceToolCallId: string;
      readonly resource: {
        readonly providerId: string;
        readonly connectionId: string | null;
        readonly resourceType: string;
        readonly resourceId: string;
      };
      readonly condition: {
        readonly typeId: string;
        readonly version: number;
        readonly parameters: Readonly<Record<string, EventWaitJsonValue>>;
      };
      readonly deliveryKey: string;
      readonly createdAt: number;
      readonly updatedAt: number;
      readonly deadlineAt: number;
    }
    
    interface EventWaitResolution {
      readonly outcome: 'satisfied' | 'invalidated' | 'source_unavailable';
      readonly receiptKey: string;
      readonly observedAt: number;
      readonly evidenceRefs: readonly string[];
    }
    
    type EventWaitLifecycle =
      | { readonly status: 'waiting' }
      | {
          readonly status: 'resolved';
          readonly resolvedAt: number;
          readonly resolution: EventWaitResolution;
        }
      | {
          readonly status: 'cancelled';
          readonly cancelledAt: number;
          readonly reason: string;
          readonly priorResolution?: {
            readonly resolvedAt: number;
            readonly resolution: EventWaitResolution;
          };
        }
      | { readonly status: 'expired'; readonly expiredAt: number };

    3.1 Reasons for this restricted shape

    • Goal is the only v1 consumer. Use existing Goal control identity instead of adding unimplemented owner variants. Identity in the record is correlation, not self-authenticating permission; Host must later verify it against current control authority.
    • Sources remain provider-neutral: no GitHub-, file-, or ShellRun-specific fields.
    • resource and condition are persisted descriptions. PR 1 does not interpret their business semantics; PRs 2/3 must validate types and parameters through registered adapters.
    • deliveryKey is fixed to event-wait/${waitId}: immutable and reconstructible, not evidence of existing execution. PR 3 will use it to link to existing continuation/admission rather than another execution queue.
    • resolved means the result was recorded. It does not mean a model was called or a successor was admitted.
    • Do not introduce consumed, running, or delivered. Without the actual admission transaction, a standalone markDelivered() must not pretend delivery completed. PR 3 will add linkage/release rules around the real cross-domain transaction.
    • receiptKey identifies this wait's result. It is not a provider-wide receipt deduplication table; different subscriptions may observe the same external fact.

    3.2 Validation rules and explicit limits

    Export and test the following proposed limits. They are internal safety bounds for this plan, not default product TTLs:

    Field / value Validation
    Wait/Session/Goal/Turn/connection IDs Follow existing ID constraints; suggested wait ID: 1–128 ASCII letters, digits, _, or -; connection may be null
    provider/resourceType/condition typeId Non-empty ASCII type identifier; allow dots, underscores, and hyphens; at most 128 bytes
    Opaque resourceId Non-empty UTF-8, at most 2 KiB; reject NUL/control characters
    sourceToolCallId Non-empty, at most 512 bytes; follow existing tool-call identity rules
    parameters Plain-object root; JSON values only; at most 8 KiB, depth 8, and 1024 nodes
    resolution.receiptKey Non-empty, at most 512 bytes, no control characters
    evidenceRefs At most 8 strings, each at most 1 KiB; validate bounds only, without resolving references
    Cancellation reason Non-empty, at most 1 KiB
    Entire record At most 32 KiB when JSON-serialized
    Times, versions, generation Finite safe integers; nonnegative times; validate versions/generation under existing identity conventions

    Additional rules:

    • Reject unknown fields at the top level and in every fixed shape. Keys within parameters are bounded data, not arbitrary top-level extensions.
    • Reject cycles, sparse arrays, undefined, NaN, Infinity, Date, custom prototypes, and other values outside the explicit JSON contract. Do not rely on JSON.stringify silently dropping fields.
    • Require createdAt <= updatedAt and deadlineAt > createdAt.
    • State-specific timestamps must be within [createdAt, updatedAt]; require expiredAt >= deadlineAt. observedAt is the Host observation time, not the provider's original event timestamp.
    • deliveryKey must equal the derived value.
    • Validate goalControlLease structurally, without claiming that the record proves current Goal authorization. PR 3 checks current validity in the trusted Host control path.
    • Do not add credential, command, model-prompt, or userAuthorized fields. Parameter validation is not a secret scanner; later adapters must still prevent credentials being stored in parameters.

    3.3 State-transition matrix

    Previous state Permitted next state Constraint
    Absent waiting The only creation path; direct insertion of resolved is forbidden
    waiting resolved Commit one complete resolution; do not start execution
    waiting cancelled Store an explicit cancellation reason
    waiting expired The original deadline has been reached; caller-initiated, without a background timer
    resolved cancelled Permit revocation before delivery, retaining the original resolution in priorResolution
    Any existing state Identical record With the correct expected revision, return a no-op without incrementing revision
    Any state Change resource, condition, ownership, creation time, or deadline Forbidden; a new condition requires a new wait ID
    resolved/cancelled/expired waiting or a different resolved record Forbidden

    Apart from identical records, do not accept meaningless writes such as “waiting → waiting with only a heartbeat update.” PR 2 can add cursor/observation fields based on a real source requirement; do not prebuild a universal extension mechanism in PR 1.

    Reject transitions not listed in the table. For example, do not edit the reason of a cancelled record or replace a resolved result while claiming it is the same delivery.

    4. Storage API and CAS behavior

    Add packages/storage/src/event-wait-authority.ts, following Goal authority's repository/facade layering. Add eventWaitStore to the execution storage group.

    interface EventWaitSnapshot {
      readonly authorityRevision: number; // Initially 0
      readonly record: EventWaitRecord;
    }
    
    interface CommitEventWaitInput {
      readonly sessionId: string;
      readonly waitId: string;
      readonly expectedAuthorityRevision: number | null; // null means create only
      readonly record: EventWaitRecord; // No record:null deletion entry point
    }
    
    type CommitEventWaitResult =
      | { readonly kind: 'committed'; readonly snapshot: EventWaitSnapshot }
      | {
          readonly kind: 'revision_conflict';
          readonly actualAuthorityRevision: number | null;
        }
      | { readonly kind: 'active_wait_conflict'; readonly waitId: string }
      | { readonly kind: 'session_unavailable' };
    
    interface EventWaitPage {
      readonly items: readonly EventWaitSnapshot[];
      readonly nextCursor: string | null;
    }

    The repository provides read, listSession, listPending, commit, and close:

    read({sessionId, waitId})
    listSession({sessionId, afterWaitId?, limit})
    listPending({afterWaitId?, limit})
    commit(input)
    close()
    
    • limit is required and must be 1–200. Use ascending ASCII waitId keyset pagination; read limit+1 to determine nextCursor. Do not implement an offset-based full scan.
    • listPending returns only waiting and resolved for future restart reconciliation. It is not an execution queue or permission to start work.
    • Pagination guarantees stable key ordering, not a database-wide snapshot across concurrent writes. The future observer must account for rescanning and deduplication.
    • read matches both Session and wait ID. A wait ID alone must not expose another Session's record.
    • Return isolated copies from reads; mutating a returned object must not alter committed state.
    • The backend contract may return values or Promises. The authenticated facade returns Promises, matching existing provider style.

    4.1 Commit sequence

    1. Strictly decode input and verify that input IDs agree with the record.
    2. In the backend's same write transaction, verify that the Session exists, is not archived, and has no tombstone.
    3. Read the existing wait. If the wait ID belongs to another Session, reject the identity mismatch; do not transfer ownership.
    4. Compare expected revision. On mismatch, return revision_conflict without writing.
    5. Require waiting on creation; validate immutable fields and the transition matrix on update.
    6. If the record is identical and expected revision is correct, return the current snapshot without incrementing revision.
    7. Check whether another wait occupies this Session's active slot.
    8. Atomically write state and revision. Roll back on failure and return an isolated snapshot on success.

    Creation retries do not need a special “already-created” branch. A second expected=null for the same wait ID returns a revision conflict; the caller reads the existing record and decides what to do without creating another wait. Do not generate a new wait ID merely to retry.

    The PR 1 active slot is status IN ('waiting', 'resolved'). A resolved wait retains the slot until cancellation or a real delivery transaction introduced by PR 3 releases it. With no production consumer in this PR, do not add a fake consumption API that releases the slot independently of actual admission.

    Storage validates shape and Session liveness within the transaction. Whether the Goal generation is currently valid, the tool is authorized, and the adapter actually observed the condition belongs to PRs 3/2. Host must recheck these later; a low-level write lease is not model permission.

    5. SQLite schema and migration

    Add the table to sqlite-workflow-schema.ts, preserving its declarative schema-building style. The following DDL defines the target structure; apply repository formatting and validation conventions when implementing it:

    CREATE TABLE IF NOT EXISTS workflow_event_waits (
      wait_id TEXT PRIMARY KEY,
      session_id TEXT NOT NULL,
      authority_revision INTEGER NOT NULL CHECK (authority_revision >= 0),
      status TEXT NOT NULL
        CHECK (status IN ('waiting', 'resolved', 'cancelled', 'expired')),
      delivery_key TEXT NOT NULL UNIQUE,
      deadline_at INTEGER NOT NULL CHECK (deadline_at >= 0),
      record_json TEXT NOT NULL,
      FOREIGN KEY (session_id) REFERENCES session_metadata(session_id)
        ON DELETE CASCADE
    );
    
    CREATE INDEX IF NOT EXISTS workflow_event_waits_by_session
      ON workflow_event_waits(session_id, wait_id);
    
    CREATE INDEX IF NOT EXISTS workflow_event_waits_pending
      ON workflow_event_waits(wait_id)
      WHERE status IN ('waiting', 'resolved');
    
    CREATE UNIQUE INDEX IF NOT EXISTS workflow_event_waits_one_active_session
      ON workflow_event_waits(session_id)
      WHERE status IN ('waiting', 'resolved');

    Implementation requirements:

    • Acquire a lease on the existing runtime.sqlite via acquireOperationalStateDatabase(root). Do not create another file or hold a DatabaseSync outside composition.
    • Use existing transaction('write', ...) for Session validation, CAS, slot checking, and writing together. Unique indexes are the durable backstop; an in-process Map is not sufficient.
    • Parameterize SQL. Do not interpolate opaque resource IDs or JSON.
    • On read, strictly decode and verify scalar/JSON consistency for wait, Session, status, deliveryKey, and deadline. Do not duplicate authority_revision in record JSON; validate it separately as a nonnegative safe integer. Fail closed on corruption instead of repairing it into waiting or success.
    • Initial revision is 0; each valid change increments it by 1. Do not mutate the caller's object to construct a result.
    • Advance workflow scope from 12 to 13, or the next version at implementation time. Preserve existing runtime/core_execution/session_metadata and other scope versions.
    • operational-target-schema.ts builds target DDL through schema builders. Check whether it already covers the new table, then change only necessary assertions. Do not weaken schema-integrity checks to make tests pass.
    • The new build must open a workflow-12 root while retaining existing data. Older builds must reject workflow 13 through existing newer-scope handling rather than write a downgrade.
    • Do not add a shared provider-wide receipt table. resolution.receiptKey and active-slot/CAS tests are not a complete event-deduplication system.

    6. ExecutionPersistence wiring and memory reference implementation

    6.1 Integration points

    1. execution-persistence-provider.ts: add readonly eventWaitStore: EventWaitAuthorityRepository.
    2. local-execution-persistence.ts: open the SQLite store, add it to the reverse-order close stack, and return it with the group. Partial-open failures must close it too.
    3. execution-stores.ts: add eventWaitStore to InteractiveExecutionStoresWriter; wrap every operation through existing lease/active-operation handling.
    4. test-only/memory-execution-persistence.ts: construct the memory wait store using the same MemoryExecutionAuthority and return it through the existing scoped proxy.

    6.2 Facade requirements

    • Add the InteractiveEventWaitAuthorityWriter brand and authenticateInteractiveEventWaitAuthorityWriter.
    • Follow goal-authority.ts so fake objects fail authentication and invalid leases or access after group close are rejected.
    • Facade construction must use a repository explicitly supplied by the execution group. Do not add a product accessor that defaults to SQLite, silently opening Local storage under a memory/custom provider.
    • The group owns backend closure. Closing a facade only revokes that facade; do not close a shared backend twice.
    • Reuse the execution group's in-flight waiting and close-failure semantics. Do not catch an error and silently continue using a partially closed store.
    • A custom provider missing the new port should fail type checking or fail explicitly. Do not make the port optional or automatically supply a Local implementation.

    6.3 Memory implementation

    Add test-only/memory-event-wait-authority.ts, using rows<EventWaitSnapshot>(state, 'eventWaits').

    • MemoryExecutionAuthority.read/write supplies copies and copy-on-write transactions.
    • Share core decoders/transition validators with SQLite, but implement Map operations independently without calling SQLite.
    • Check existence and archive state using existing memory Session headers/tombstones.
    • Use the same ordering as ASCII SQLite BINARY, not locale-sensitive ordering.
    • Support existing beforeCommit / afterCommit fault hooks, using a stable operation name such as eventWait.commit.
    • State explicitly that the memory provider retains facts only for the provider object's process lifetime, not across process loss.

    7. Archive, removal, copy, and backup boundaries

    7.1 SQLite

    • deleteGoalAuthorities in sqlite-session-metadata-store.ts currently serves archive and batch retirement. Extend/rename it as an explicit Session workflow-authority cleanup helper, deleting waits in the same transaction.
    • Preserve the databaseLease guard for standalone metadata stores without workflow schema so their existing tests do not break.
    • Hard removal is covered by FK cascade and existing retirement paths. Test single-Session removal, batch removal, and mixed removal/archive.
    • Add explicit wait-table cleanup to conversation-operational-state.ts::purge. This path may retain the Session header, so FK cascade alone is insufficient.
    • Unarchive does not recreate removed waits or automatically start any work.

    7.2 Memory

    • In memory-execution-session.ts, delete waits by record.sessionId within remove, archive, and batch-retirement transactions.
    • In memory-execution-persistence.ts::purgeConversationOperationalState, clean up by snapshot.record.sessionId. Do not accidentally use a generic loop that understands only flat records and misses nested snapshots.
    • Cleanup and other Session changes belong to the same authority.write and roll back together on failure.

    7.3 Moving data

    • A full database backup of the same State Root retains wait records through existing operational backup/schema compatibility paths.
    • Session copy, fork, and import must not automatically copy active subscriptions, which could make one event resume two tasks. Do not add fields to the Session transfer protocol in this PR.
    • Check that these paths copy declared business data rather than wildcard-copying the new table. If active waits would be copied, fix that specific path and add a test.
    • These are operational-state lifecycle rules. Do not alter conversation history or user files, and do not interpret deleting a wait as terminating the underlying task.

    8. File inventory

    Add

    File Responsibility
    packages/core/src/event-wait.ts Types, decoder, limits, deliveryKey, transition validation
    packages/core/src/__tests__/event-wait.test.ts Valid/invalid core contracts and transitions
    packages/storage/src/event-wait-authority.ts Repository types, SQLite implementation, controlled facade; split a private SQLite implementation if needed for size
    packages/storage/src/test-only/memory-event-wait-authority.ts Independent memory backend
    packages/storage/src/__tests__/event-wait-authority.test.ts Store, lease, reopen, CAS, and migration-related tests

    Modify

    File Change
    packages/core/package.json Export ./event-wait using the existing format
    packages/storage/package.json Export ./event-wait-authority if other workspaces need its types; do not expose raw DB handles
    execution-persistence-provider.ts Required eventWaitStore port
    local-execution-persistence.ts Open/close
    execution-stores.ts Authenticated group facade and close/error paths
    sqlite-workflow-schema.ts Table, indexes, workflow schema bump
    sqlite-session-metadata-store.ts Atomic archive/retirement cleanup
    conversation-operational-state.ts Purge cleanup
    test-only/memory-execution-persistence.ts Provider integration and purge
    test-only/memory-execution-session.ts Memory archive/retirement cleanup
    __tests__/execution-provider-conformance.test.ts Shared provider scenarios and close/fault boundaries
    __tests__/operational-state-store.test.ts Upgrade from old workflow scope, rejection of future versions, target schema
    __tests__/sqlite-workflow-store.test.ts New workflow structure and preservation of existing data
    __tests__/sqlite-session-metadata-store.test.ts Cleanup and standalone behavior
    __tests__/operational-state-backup.test.ts Wait readability after full-root backup/restore
    __tests__/session-copy-cleanup.test.ts Where fixtures allow, verify that copies do not carry active waits

    Paths without a directory prefix above are under packages/storage/src/. Also search the repository for every test fixture constructing ExecutionPersistence and supply the required port. Make type adaptations only; do not expand production functionality.

    9. Required tests

    A. Core contract

    • Valid waiting/resolved/cancelled/expired records.
    • Unknown schemaVersion or fields, missing required fields, invalid IDs/generation/version.
    • Negative or fractional times, a deadline not later than creation, premature expiry.
    • Parameter depth/node/byte limits, NaN, undefined, sparse arrays, cycles, non-plain objects.
    • Evidence/reason/resourceId bounds and a tampered deliveryKey.
    • Attempts to change Session/Goal/resource/condition/source-call identity.
    • Terminal records cannot reopen; resolved results cannot be replaced.
    • resolved→cancelled retains the original result; waiting→cancelled does not invent one.

    B. Shared provider behavior

    Run at least the following against both Local and memory, not just one backend:

    • Write/read/list records belonging to a genuinely created active Session.
    • Initial revision=0; valid changes increment it.
    • Two writers using the same expected revision allow only one actual mutation.
    • Duplicate create returns revision conflict rather than creating a second record.
    • A second active wait in one Session returns active_wait_conflict; other Sessions are unaffected.
    • cancelled/expired release the slot; resolved does not release it early.
    • Identical commit with correct revision is a no-op; stale revision still conflicts.
    • Pending pagination covers both states, filters terminal rows, does not repeat records across pages, and validates limit boundaries.
    • Mutating read results or input objects does not change committed facts.
    • Absent, archived, and removed Sessions reject writes.
    • Test descriptions explicitly acknowledge that external truth and current Goal authorization are not verified here; storage tests are not authorization tests.

    C. Lease and provider boundaries

    • Invalid leases, fake branded writers, and access after group close.
    • Facade close does not close the shared backend again.
    • Partial-open cleanup; close failure does not authorize a fresh Local fallback.
    • Memory/custom providers do not open local SQLite, and the facade does not leak raw repository/SQL capabilities.
    • Reads, lists, and writes all participate in existing in-flight lifecycle management.

    D. Durability, migration, and cleanup

    • Local close/reopen retains records, revisions, and results exactly.
    • Two store instances share operational authority; CAS does not depend on either instance's cache.
    • Upgrade from a genuine workflow-12 structure/registry: the new table is initially absent, and existing Goal/Session data survives. Do not fake migration by changing only a version number after creating the new table.
    • Repeated opens are idempotent; future workflow scope is rejected without modifying the database.
    • Corrupt record_json or disagreement between indexed columns and JSON fails closed.
    • Archive, batch retirement, removal, and purge clean waits without affecting another Session.
    • Unarchive does not restore records; a stale commit after deletion cannot recreate a wait for an absent Session.
    • Same-root backup/restore preserves waits; Session copies do not carry active subscriptions.

    E. Faults and unchanged runtime behavior

    • A memory beforeCommit exception leaves state unchanged. An afterCommit exception represents a lost acknowledgement: rereading finds the single committed result, and retry does not duplicate creation.
    • Cleanup transaction failure cannot leave partial results such as an archived Session with a still-effective wait.
    • Fixtures call storage only, not a model, Host admission, or provider network.
    • Existing Goal authority, Session retirement, provider conformance, and Runtime Host builds do not regress.

    Full successor recovery across real process crashes belongs to PR 3. Passing store-reopen tests in this PR must not be presented as completed runtime automatic recovery.

    10. Recommended implementation sequence

    1. Confirm the baseline and schema. Record them in the PR work notes without editing existing user documents.
    2. Add core tests and types first. Fix shapes, bounds, and transitions so backends do not interpret them differently.
    3. Add workflow DDL and Local store tests. Verify CAS, slots, reopen, and transitions before completing the facade.
    4. Implement memory storage and run the same scenarios. Do not share SQLite operations or use Local fallback to manufacture parity.
    5. Wire ExecutionPersistence and the authenticated group. Update required ports/fixtures and verify close/partial-open failure behavior.
    6. Complete cleanup and migration coverage. Archive, retirement, purge, backup/copy tests precede any production consumer.
    7. Run focused and workspace validation. Preserve original failures and rerun results rather than silently dismissing environmental failures.
    8. Review the diff. Confirm no watchers, tool registration, Goal behavior changes, or hidden permission modes.
    9. Commit. Use the repository's required generative-tool attribution; do not push or create a PR without user authorization.

    11. Validation commands

    Use the Node/npm toolchain declared by package.json for the implementation environment. If dependencies are incomplete, use the standard repository installation process. Do not update unrelated lockfile dependencies for this PR.

    During development:

    npm --workspace @maka/core run build
    npm --workspace @maka/storage run build
    
    node --test packages/core/dist/__tests__/event-wait.test.js
    node --test --test-concurrency=2 \
      packages/storage/dist/__tests__/event-wait-authority.test.js \
      packages/storage/dist/__tests__/execution-provider-conformance.test.js \
      packages/storage/dist/__tests__/goal-authority.test.js \
      packages/storage/dist/__tests__/operational-state-store.test.js \
      packages/storage/dist/__tests__/sqlite-workflow-store.test.js \
      packages/storage/dist/__tests__/sqlite-session-metadata-store.test.js \
      packages/storage/dist/__tests__/operational-state-backup.test.js \
      packages/storage/dist/__tests__/session-copy-cleanup.test.js

    If tests require a package cwd, run equivalent commands from that workspace. Update commands when filenames change; do not quietly omit tests.

    Before committing, at minimum:

    npm run build:test
    npm run typecheck
    npm run lint
    npm run format:check
    git diff --check

    Also run the full core/storage suites. If machine resources allow, use npm run test:dist. If known excessive-concurrency issues occur, rerun relevant workspaces with node --test --test-concurrency=4 'dist/**/*.test.js', retaining both the initial failures and rerun results. Verify Runtime Host and Desktop consumers compile; a storage-only build is insufficient.

    Follow repository contribution requirements for ASF headers. If pre-existing untracked user documents make the checkout-wide scan fail, do not edit them to remove the noise. Check this PR's changed/staged files and report the distinction.

    Without Host wire-protocol changes, do not mechanically bump the compatibility epoch merely because functionality was added; workflow compatibility belongs to storage migration. If implementation touches the protocol, first explain why the scope expanded, then validate with the existing guard.

    12. Acceptance checklist and handoff output

    • The new contract is closed and bounded, accepting no arbitrary commands or authorization claims.
    • Wait ID, ownership, condition, and deliveryKey are immutable; transitions have one validation implementation.
    • SQLite and memory pass shared behavior tests covering CAS, slots, cleanup, and closure.
    • Workflow migration preserves old-root data and fails closed on future versions.
    • The new store is used through the execution group, without another database or implicit provider fallback.
    • Archive/remove/purge/backup/copy boundaries are tested.
    • Goal behavior, model calls, tool registration, SessionStatus, and the wire protocol are unchanged.
    • resolved is not treated as execution already scheduled; actual delivery atomicity is explicitly left to PR 3.
    • Existing edits/untracked documents are preserved and the diff contains only necessary PR files.

    The implementation Session should finish by reporting:

    1. Actual baseline and commit hash.
    2. Changed files and any necessary deviations from this plan.
    3. A short explanation of contracts, schema, API, and cleanup semantics.
    4. Commands run and actual results, including failures, baseline reproduction, and checks not run.
    5. The PR 2/3 handoff: how to list waits requiring reconciliation, submit results, and identify capabilities not yet implemented.

    Stop and explain these situations rather than expanding the PR

    • Wait and Session cleanup cannot be kept consistent within the existing execution consistency domain.
    • Storage tests require changing model execution, Goal accounting, or recovery policy.
    • Provider or migration infrastructure has changed materially and this plan's versions/paths no longer apply.
    • An independent “delivered” marker would conceal the absence of a real continuation-admission transaction.
    • Tests pass only by bypassing permissions, weakening schema validation, or silently falling back to Local.

    These indicate a boundary that needs discussion, not a reason to implement the entire event-wake system inside PR 1.


    This plan is based on targeted code inspection. Its new design still requires code review. Preparing this document did not modify production code or establish that the proposed tests have run.

    中文(点击展开) # PR 1 执行计划:事件等待契约与未启用的持久化基础
    • 来源:Discussion #5920。
    • 核查基线:cae4a93ec842f053422aad615708ff18bbba02e2。
    • 性质:可交接给实现 Session 的具体计划。以下新增类型、表和 API 是本计划给出的实现选择,不是仓库中已经存在的功能,也不表示已获维护者批准。
    • 唯一交付目标:等待记录能在既有执行存储中正确创建、读取、并发更新、重开和清理;生产运行行为不变。

    0. 可直接交给实现 Session 的任务说明

    实现本文定义的 PR 1,不实现整套事件唤醒。先确认分支及 main 漂移,再按文件清单新增核心契约、SQLite/内存存储和执行组 facade,补足契约、迁移、lease、清理和 provider parity 测试。所有行为只由测试调用,不注册新工具或 watcher,不修改 Goal 的计费、waiting、自动恢复和模型调用路径。不要覆盖工作区已有的未跟踪文档。完成后提交代码和验证结果;除非另有授权,不 push 或创建 PR。若发现设计无法在现有一致性边界中实现,报告具体冲突,不用独立数据库或另一个执行队列绕过。

    建议分支名:feat/event-wait-persistence。

    建议 PR 标题:feat(storage): add dormant event-wait authority。

    1. 开始前检查与范围冻结

    在开始改代码前执行:

    git status --short
    git branch --show-current
    git rev-parse HEAD
    git fetch --no-tags upstream main
    git diff --stat cae4a93ec842f053422aad615708ff18bbba02e2..upstream/main -- \
      packages/core/src/goal.ts \
      packages/storage/src/execution-persistence-provider.ts \
      packages/storage/src/execution-stores.ts \
      packages/storage/src/goal-authority.ts \
      packages/storage/src/sqlite-workflow-schema.ts \
      packages/storage/src/test-only

    从实施时最新 main 建立独立分支或 worktree,不在此前修复 PR 的分支上混入本功能;保留用户现有 tracked/untracked 修改。若基线已不可用,以文件级核查代替猜测。

    记录实际基线、Node/npm 版本、依赖状态和 workflow schema 版本。本文基线为 workflow 12,所以计划新增版本 13;若 main 已前进,使用当时的下一个 workflow 版本,不抢占已使用编号。

    本 PR 做什么

    • 核心类型、严格 decoder、状态转换校验。
    • provider-neutral 的一条等待记录及 CAS revision。
    • 稳定的结果交付关联键,不创建执行。
    • 有界查询与一个 Session 一条未交付等待的存储约束。
    • 现有 SQLite consistency domain 内的存储与独立 memory reference 实现。
    • 经过执行组/root lease 的访问、生命周期管理、归档与删除清理。
    • migration、provider parity、故障与无副作用测试。

    本 PR 明确不做什么

    • 不新增 WaitForEvent 工具,不改工具目录或模型提示词。
    • 不启动计时器、轮询、文件监听或任何外部连接。
    • 不接 GitHub 认证或 Webhook。
    • 不改 GoalAuthorityRecord、GoalState 状态、waiting 退避、iterations、token 预算。
    • 不增加 SessionStatus,不增加 wire protocol 字段。
    • 不创建 pending Goal continuation、Turn、Run 或 RuntimeEvent。
    • 不实现自动恢复执行、重派命令、订阅 UI 或通知。
    • 不做通用 receipt 收件箱、独立 wake queue 或独立数据库。

    实际“事件接收去重”留给 PR 2;“等待结果与后继接纳的原子关联”留给 PR 3。本 PR 只提供稳定身份、CAS 和同一条等待只能提交一个不可变结果的基础,不能宣称已实现 exactly-once 唤醒。

    2. 当前代码事实与复用点

    文件/入口 当前事实 本 PR 如何使用
    packages/core/src/goal.ts GoalControlLease 含 Goal ID 和 generation;已有 pendingContinuation 引用控制身份的类型/校验规则,不改 Goal 存储结构
    packages/storage/src/goal-authority.ts CAS authority、lease facade、backend close 的现有范式 参考结构,但不要复制一个带隐式 Local fallback 的新访问路径
    packages/storage/src/execution-persistence-provider.ts ExecutionPersistence 是不可分的一致性域 增加 eventWaitStore,不在 Host 另开存储
    packages/storage/src/local-execution-persistence.ts 统一打开/关闭各执行存储 按相同模式接入 SQLite backend,保持部分打开失败的清理
    packages/storage/src/execution-stores.ts authenticated execution group 包装 provider,管理 active operations、close、lease 增加受控 facade,与整个组共同管理
    packages/storage/src/sqlite-workflow-schema.ts 基线 workflow schema 为 12;声明式建立 workflow 表 新增表并升到 13,沿用该模块迁移方式
    packages/storage/src/operational-state-store.ts runtime.sqlite 共享连接、事务、schema scope registry 复用 acquireOperationalStateDatabase 和 workflow scope
    packages/storage/src/operational-target-schema.ts 用各 schema 构建目标结构并严格校验 确认新增表/索引被目标结构和旧库升级测试覆盖
    packages/storage/src/test-only/memory-execution-persistence.ts 独立内存参考 provider,不依赖 SQLite 加入同一 memory authority 的 map 和事务
    sqlite-session-metadata-store.ts / conversation-operational-state.ts 归档、退役和 purge 已清理 Goal authority 在相同事务边界清理等待

    不要误用 SQLITE_RUNTIME_SCHEMA_VERSION 的编号。 当前 runtime schema 为 20,但此次新增的是 workflow 表;除非实际实现证明另有必要,不修改 runtime schema 或 PRAGMA user_version 的 runtime 编号。

    core/storage 的 package exports 是显式列举,并非 ./*。新增模块需要对应 export;不必将所有内部构造函数暴露为产品 API。

    3. 最小 v1 契约

    新增 packages/core/src/event-wait.ts。使用现有 record-schema.ts 的封闭对象校验风格,输入按 unknown 解码,不靠类型断言接受外部数据。

    建议导出:

    export const EVENT_WAIT_SCHEMA_VERSION = 1 as const;
    export type EventWaitRecord = EventWaitBase & EventWaitLifecycle;
    export type EventWaitStatus = 'waiting' | 'resolved' | 'cancelled' | 'expired';
    export function decodeEventWaitRecord(value: unknown): EventWaitRecord;
    export function assertEventWaitTransition(
      previous: EventWaitRecord,
      next: EventWaitRecord,
    ): void;
    export function eventWaitDeliveryKey(waitId: string): string;

    记录结构采用下述语义;可以按仓库风格拆类型,但不擅自增加 executor、模型调用或任意动作字段:

    type EventWaitJsonValue =
      | null
      | boolean
      | number
      | string
      | readonly EventWaitJsonValue[]
      | { readonly [key: string]: EventWaitJsonValue };
    
    interface EventWaitBase {
      readonly schemaVersion: 1;
      readonly waitId: string;
      readonly sessionId: string;
      readonly goalControlLease: GoalControlLease;
      readonly sourceTurnId: string;
      readonly sourceToolCallId: string;
      readonly resource: {
        readonly providerId: string;
        readonly connectionId: string | null;
        readonly resourceType: string;
        readonly resourceId: string;
      };
      readonly condition: {
        readonly typeId: string;
        readonly version: number;
        readonly parameters: Readonly<Record<string, EventWaitJsonValue>>;
      };
      readonly deliveryKey: string;
      readonly createdAt: number;
      readonly updatedAt: number;
      readonly deadlineAt: number;
    }
    
    interface EventWaitResolution {
      readonly outcome: 'satisfied' | 'invalidated' | 'source_unavailable';
      readonly receiptKey: string;
      readonly observedAt: number;
      readonly evidenceRefs: readonly string[];
    }
    
    type EventWaitLifecycle =
      | { readonly status: 'waiting' }
      | {
          readonly status: 'resolved';
          readonly resolvedAt: number;
          readonly resolution: EventWaitResolution;
        }
      | {
          readonly status: 'cancelled';
          readonly cancelledAt: number;
          readonly reason: string;
          readonly priorResolution?: {
            readonly resolvedAt: number;
            readonly resolution: EventWaitResolution;
          };
        }
      | { readonly status: 'expired'; readonly expiredAt: number };

    3.1 为什么这样收窄

    • v1 的消费者先限定为 Goal,使用现有 Goal 控制身份,不引入多个尚无实现的 owner variant。记录中的身份仅供关联,不能自证授权;后续 Host 必须核验它确实匹配当前控制权威。
    • 来源仍是 provider-neutral:没有 GitHub、文件或 ShellRun 专属字段。
    • resource 与 condition 是持久化描述,PR 1 不解释其业务语义;PR 2/3 必须通过注册的 adapter 校验类型与参数。
    • deliveryKey 固定为 event-wait/${waitId},不可变且可重建,不表示已有执行。它将供 PR 3 关联既有 continuation/admission,不另存一个运行队列。
    • resolved 表示结果已记录,不表示模型已被调用或后继已经接纳。
    • 不引入 consumed、running、delivered 状态;没有真实接纳事务时,不允许调用一个单独的 markDelivered() 假装交付完成。PR 3 根据实际跨域事务补充交付关联/释放规则。
    • receiptKey 只关联这条等待的结果。它不是全 provider 的 receipt 去重表;不同订阅可以观察同一外部事实。

    3.2 校验规则和明确上限

    建议将以下上限导出并测试。它们是本计划的内部安全边界,不是产品默认 TTL:

    内容 校验
    wait/session/Goal/Turn/connection 等 ID 沿用现有 ID 约束;wait ID 建议 1–128 个 ASCII 字母、数字、_、-;connection 可为 null
    provider/resourceType/condition typeId 非空 ASCII 类型标识,允许点、下划线、连字符,最多 128 字节
    opaque resourceId 非空 UTF-8,最多 2 KiB,拒绝 NUL/控制字符
    sourceToolCallId 非空、最多 512 字节,遵循现有工具调用身份规则
    parameters 根为普通对象;只允许 JSON 值,最多 8 KiB、深度 8、节点数 1024
    resolution.receiptKey 非空、最多 512 字节,不含控制字符
    evidenceRefs 最多 8 个字符串,每个最多 1 KiB;这里只校验边界,不读取引用
    取消 reason 非空、最多 1 KiB
    全记录 JSON 序列化后最多 32 KiB
    时间、版本、generation 有限、安全整数;时间非负;版本与 generation 按既有身份约定校验

    额外规则:

    • 顶层及每个固定形状拒绝未知字段;parameters 的 key 是有界数据,不是任意顶层扩展点。
    • 拒绝循环、稀疏数组、undefined、NaN、Infinity、Date、自定义 prototype 等非明确 JSON 值;校验不能靠 JSON.stringify 静默删字段。
    • createdAt <= updatedAt,deadlineAt > createdAt。
    • 状态专属时间应在 [createdAt, updatedAt];expiredAt >= deadlineAt。observedAt 是 Host 的观察时刻,不是外部事件原始发生时间。
    • deliveryKey 必须等于派生函数结果。
    • 核验 goalControlLease 结构,但不声称记录本身证明 Goal 当前仍授权;当前有效性由 PR 3 在可信 Host 控制路径核验。
    • 不增加 credential、命令、模型 prompt 或 userAuthorized 字段。参数不是 secret scanner;后续 adapter 仍须防止把凭据写进参数。

    3.3 状态转换矩阵

    原状态 允许的新状态 约束
    不存在 waiting 只允许这样创建,不允许直接插入 resolved
    waiting resolved 提交一个完整 resolution;不会启动执行
    waiting cancelled 保存明确取消原因
    waiting expired 时间达到原截止值;由调用方发起,不设后台 timer
    resolved cancelled 允许交付前撤销,但必须保留原 resolution 到 priorResolution
    任意已存在状态 完全相同记录 expected revision 正确时返回 no-op,不增加 revision
    任意状态 更改资源、条件、归属、创建时间或截止时间 禁止;新条件必须创建新 wait ID
    resolved/cancelled/expired waiting,或另一份 resolved 禁止

    除完全相同记录外,不允许“waiting → waiting 只更新心跳”之类无实际语义写入。游标与观测进度由 PR 2 按真实来源需求扩展,不在 PR 1 预建万能字段。

    表中未列出的转换一律拒绝;例如不得修改已取消记录的原因,也不得更新已 resolved 的结果再宣称是同一份交付。

    4. 存储 API 与 CAS 行为

    新增 packages/storage/src/event-wait-authority.ts,参考 Goal authority 的 repository/facade 分层。新增 eventWaitStore 属性到执行存储组。

    interface EventWaitSnapshot {
      readonly authorityRevision: number; // 首次为 0
      readonly record: EventWaitRecord;
    }
    
    interface CommitEventWaitInput {
      readonly sessionId: string;
      readonly waitId: string;
      readonly expectedAuthorityRevision: number | null; // null 仅表示创建
      readonly record: EventWaitRecord; // 不提供 record:null 删除入口
    }
    
    type CommitEventWaitResult =
      | { readonly kind: 'committed'; readonly snapshot: EventWaitSnapshot }
      | {
          readonly kind: 'revision_conflict';
          readonly actualAuthorityRevision: number | null;
        }
      | { readonly kind: 'active_wait_conflict'; readonly waitId: string }
      | { readonly kind: 'session_unavailable' };
    
    interface EventWaitPage {
      readonly items: readonly EventWaitSnapshot[];
      readonly nextCursor: string | null;
    }

    repository 提供 read、listSession、listPending、commit、close:

    read({sessionId, waitId})
    listSession({sessionId, afterWaitId?, limit})
    listPending({afterWaitId?, limit})
    commit(input)
    close()
    
    • limit 必填,范围 1–200。按 ASCII waitId 升序 keyset 分页;查 limit+1 决定 nextCursor,不使用 offset 全量扫描。
    • listPending 只返回 waiting 和 resolved,用于未来重启校准;这个查询不是执行队列或启动授权。
    • 分页保证稳定键次序,不承诺跨并发写入的全库快照。未来观测管理器需要处理反复扫描和去重。
    • read 使用 Session 和 wait ID 双条件,不能用一个 wait ID 泄漏其他 Session 的记录。
    • 所有读返回隔离副本;修改读结果不得修改已提交状态。
    • 外部契约可以返回同步值或 Promise,authenticated facade 统一为 Promise,保持现有 provider 风格。

    4.1 一次 commit 的顺序

    1. 严格解码并检查入参 ID 与 record 一致。
    2. 在 backend 的同一个写事务中,检查 Session 存在、未归档、未被 tombstone 标记。
    3. 读取当前 wait。wait ID 已属于别的 Session 时拒绝 identity mismatch,不迁移归属。
    4. 比较 expected revision:不一致返回 revision_conflict,不写入。
    5. 创建时要求 waiting;更新时核验不可变字段和状态矩阵。
    6. 完全一致且 expected revision 正确时返回当前 snapshot,不增加 revision。
    7. 检查该 Session 其他 wait 是否占用 active slot。
    8. 原子写入新的状态与 revision;失败回滚,返回隔离 snapshot。

    创建重试不要求特殊“already-created”分支:同一个 wait ID 的第二次 expected=null 返回 revision conflict,调用方读取已有记录再决定,不产生第二条等待。不要为重试生成新 wait ID。

    PR 1 的 active slot 是 status IN ('waiting', 'resolved')。resolved 保持占用直到取消,或未来 PR 3 通过真正的交付事务释放。首 PR 无生产消费者,因此不添加一个会脱离真实接纳而提前释放 slot 的假消费 API。

    存储只能核验结构和事务内的 Session 生存状态;Goal generation 是否仍有效、工具是否获授权及 adapter 是否真实观察到条件,是 PR 3/PR 2 的责任。后续 Host 必须重新核验,不能把低层 write lease 当作模型权限。

    5. SQLite schema 与迁移

    在 sqlite-workflow-schema.ts 增加表,保持已有声明式 schema 构建方式。以下 DDL 是目标结构,实施时按现有缩进/校验约定写入:

    CREATE TABLE IF NOT EXISTS workflow_event_waits (
      wait_id TEXT PRIMARY KEY,
      session_id TEXT NOT NULL,
      authority_revision INTEGER NOT NULL CHECK (authority_revision >= 0),
      status TEXT NOT NULL
        CHECK (status IN ('waiting', 'resolved', 'cancelled', 'expired')),
      delivery_key TEXT NOT NULL UNIQUE,
      deadline_at INTEGER NOT NULL CHECK (deadline_at >= 0),
      record_json TEXT NOT NULL,
      FOREIGN KEY (session_id) REFERENCES session_metadata(session_id)
        ON DELETE CASCADE
    );
    
    CREATE INDEX IF NOT EXISTS workflow_event_waits_by_session
      ON workflow_event_waits(session_id, wait_id);
    
    CREATE INDEX IF NOT EXISTS workflow_event_waits_pending
      ON workflow_event_waits(wait_id)
      WHERE status IN ('waiting', 'resolved');
    
    CREATE UNIQUE INDEX IF NOT EXISTS workflow_event_waits_one_active_session
      ON workflow_event_waits(session_id)
      WHERE status IN ('waiting', 'resolved');

    实现要求:

    • 用 acquireOperationalStateDatabase(root) 取得现有 runtime.sqlite 的 lease;不创建新文件、不直接持有绕开 composition 的 DatabaseSync。
    • 写入使用既有 transaction('write', ...),Session 校验、CAS、slot 检查和写入都在事务内。唯一索引是底线,不只靠进程内 Map。
    • SQL 均使用参数,不拼接 opaque resource ID 或 JSON 内容。
    • 读取时严格 decoder + scalar/JSON 一致性校验:wait/session/status/deliveryKey/deadline 不允许互相矛盾;authority_revision 不重复存入 record JSON,单独校验为非负安全整数。损坏记录应 fail closed,不补成 waiting 或 success。
    • 首次创建记录 revision=0,之后合法变更加 1;不能修改 caller 对象作为返回结果。
    • workflow scope 12→13(或实施时下一个版本)。保留 runtime/core_execution/session_metadata 等 scope 的既有版本。
    • operational-target-schema.ts 通过 schema builder 构造目标 DDL,先验证是否自动覆盖新表,再只改真正需要的断言。不要放宽 schema 完整性检查来让测试通过。
    • 新程序可打开旧 workflow 12 根并保留数据;旧版本面对 workflow 13 应按现有 newer-scope 机制拒绝,不能降级写入。
    • 本 PR 不新增共享的全 provider receipt 表。resolution.receiptKey 与 active-slot/CAS 测试不能被宣传成完整事件去重系统。

    6. ExecutionPersistence 接线与内存参考实现

    6.1 接线位置

    1. execution-persistence-provider.ts:增加 readonly eventWaitStore: EventWaitAuthorityRepository。
    2. local-execution-persistence.ts:打开 SQLite store,加入统一逆序 close 栈,并随整个 group 返回;部分打开失败也会 close。
    3. execution-stores.ts:在 InteractiveExecutionStoresWriter 增加 eventWaitStore;所有方法包装进既有 lease/active-operation 路径。
    4. test-only/memory-execution-persistence.ts:用同一 MemoryExecutionAuthority 创建 memory wait store,经过现有 scoped proxy 返回。

    6.2 Facade 要求

    • 新建 InteractiveEventWaitAuthorityWriter 品牌和 authenticateInteractiveEventWaitAuthorityWriter。
    • 参考 goal-authority.ts,保证 fake object 无法通过 authenticate,失效 lease 与 group close 后的调用被拒绝。
    • facade 创建必须使用执行组显式传入的 repository。不提供默认回落到 SQLite 的新产品 accessor,避免 memory/custom provider 下偷偷打开 Local 存储。
    • group close 拥有 backend,facade close 只撤销该 facade;不能把共享 backend 重复关闭。
    • 复用执行组的 in-flight 等待和关闭失败语义;不要 catch 后静默继续使用部分关闭的 store。
    • 自定义 provider 缺少新增端口时应通过类型检查或明确失败发现,不做可选端口和自动补 Local 实现。

    6.3 内存实现

    建议新增 test-only/memory-event-wait-authority.ts,使用 rows<EventWaitSnapshot>(state, 'eventWaits')。

    • MemoryExecutionAuthority.read/write 提供副本与 copy-on-write 事务。
    • commit 与 SQLite 复用 core decoder/transition validator,但独立实现 Map 操作,不调用 SQLite。
    • 用现有 memory Session headers/tombstones 检查存在和归档状态。
    • 字符串排序采用与 ASCII SQLite BINARY 相同的次序,不依赖 locale 排序。
    • 支持现有 beforeCommit / afterCommit 故障钩子,操作名固定如 eventWait.commit。
    • 明确 memory provider 仅在 provider 对象进程生命周期内保留状态,不声称跨进程 durable。

    7. 归档、删除、复制与备份边界

    7.1 SQLite

    • sqlite-session-metadata-store.ts 中当前 deleteGoalAuthorities 用于归档和批量退役。将其扩展/重命名为明确的 Session workflow authority 清理 helper,在同一事务删除等待记录。
    • 保留 standalone metadata store 没有 workflow schema 时的 databaseLease guard,不使原独立存储测试失败。
    • 硬删除由 FK cascade 和现有退役路径保证清理;补测试覆盖单 Session remove、批量删除及“部分删除、部分归档”。
    • conversation-operational-state.ts::purge 增加显式等待表清理;该路径可能保留 Session header,不能只靠 FK。
    • 解除归档不重新创建被清理的等待,不自动启动任何工作。

    7.2 Memory

    • 在 memory-execution-session.ts 的 remove、archive 和批量 retirement 事务中,按 record.sessionId 删除 wait。
    • 在 memory-execution-persistence.ts::purgeConversationOperationalState 中按 snapshot.record.sessionId 清理;不要误用只适用于扁平 record 的通用循环而漏删。
    • 清理与其他 Session 状态变更属于同一次 authority.write,故障时共同回滚。

    7.3 数据移动

    • 同一 State Root 的完整数据库备份应保留等待记录,沿用已有 operational backup/schema 兼容路径。
    • Session 复制、fork、导入不自动复制活跃订阅,避免一个事件恢复两个不同任务。本 PR 不给现有 Session transfer 协议添加新字段。
    • 核对这些路径只复制已声明的业务数据,而不是新增表后被通配复制。若发现活跃等待会被复制,修正该具体路径并增加测试。
    • 这是操作态的生命周期规则,不改聊天历史或用户文件,也不把删除等待误当成终止底层任务。

    8. 文件清单

    新增

    文件 职责
    packages/core/src/event-wait.ts 类型、decoder、上限、deliveryKey、转换校验
    packages/core/src/__tests__/event-wait.test.ts 核心合法/非法契约与转换测试
    packages/storage/src/event-wait-authority.ts repository types、SQLite 实现、受控 facade;如过大可拆 SQLite 私有实现
    packages/storage/src/test-only/memory-event-wait-authority.ts 独立 memory backend
    packages/storage/src/__tests__/event-wait-authority.test.ts store、lease、重开、CAS、迁移相关测试

    修改

    文件 修改内容
    packages/core/package.json 增加 ./event-wait 导出,沿用现有 export 格式
    packages/storage/package.json 如其他 workspace 需要类型,增加 ./event-wait-authority;不公开原始 DB handles
    execution-persistence-provider.ts required eventWaitStore port
    local-execution-persistence.ts open/close
    execution-stores.ts authenticated group facade 与 close/error paths
    sqlite-workflow-schema.ts 表、索引、workflow schema bump
    sqlite-session-metadata-store.ts 归档/退役的原子清理
    conversation-operational-state.ts purge 清理
    test-only/memory-execution-persistence.ts provider 接入、purge
    test-only/memory-execution-session.ts memory 归档/退役清理
    __tests__/execution-provider-conformance.test.ts 两种 provider 共用场景与关闭/故障边界
    __tests__/operational-state-store.test.ts 旧 workflow scope 升级、新版本拒绝及目标 schema
    __tests__/sqlite-workflow-store.test.ts 新 workflow 结构及原数据不受影响
    __tests__/sqlite-session-metadata-store.test.ts 清理与 standalone 行为
    __tests__/operational-state-backup.test.ts 全根备份与恢复记录可读
    __tests__/session-copy-cleanup.test.ts 如现有夹具可覆盖,验证复制不带活跃等待

    以上省略目录前缀的文件位于 packages/storage/src/。此外全仓搜索所有构造 ExecutionPersistence 的测试夹具,补齐 required port;只做类型适配,不扩展生产功能。

    9. 必须完成的测试

    A. Core contract

    • waiting/resolved/cancelled/expired 各状态合法样例。
    • 未知 schemaVersion、未知字段、缺失必填、错误 ID/generation/version。
    • 时间负数、非整数、deadline 不晚于创建、提前 expired。
    • parameters 深度/节点/字节边界,NaN、undefined、稀疏数组、循环与非普通对象。
    • evidence/reason/resourceId 边界和 deliveryKey 篡改。
    • 不允许替换 wait 所属 Session/Goal/资源/条件/源调用身份。
    • terminal 不可重开;resolved 不能更换结果。
    • resolved→cancelled 必须保留原结果,waiting→cancelled 不伪造结果。

    B. Provider 共同行为

    至少把以下用例分别运行于 Local 和 memory,而不是只测一个 backend:

    • 在真实创建的 active Session 下写入、read、按 Session 列表。
    • 初始 revision=0,合法更新递增。
    • 两个 writer 使用同一 expected revision,只允许一个实际变更。
    • 重复 create 返回 revision conflict;不生成第二份记录。
    • 一个 Session 的第二条 active wait 返回 active_wait_conflict;其他 Session 不受影响。
    • cancelled/expired 释放 slot;resolved 不提前释放。
    • 正确 revision 下同值提交是 no-op,旧 revision 仍冲突。
    • 两种 pending 状态分页、有终态混入时过滤,跨页不重复,边界 limit 校验。
    • 读结果与写入对象的外部修改不影响已提交事实。
    • 不存在、archived、removed Session 不接受写入。
    • 不验证外部条件真假或现时 Goal 授权的测试说明必须明确,避免误把 store 测试当授权测试。

    C. Lease 与 provider 边界

    • invalid lease、fake branded writer、group close 后访问。
    • facade close 不重复关闭 shared backend。
    • partial open failure 清理;close failure 不授权新 Local fallback。
    • memory/custom provider 不打开本地 SQLite,且新增 facade 不泄漏 raw repository/SQL 能力。
    • read/list/write 均通过既有 in-flight 生命周期。

    D. 持久性、迁移、清理

    • Local 关闭后重开,记录、revision、结果完全一致。
    • 两个 store instance 使用同一 operational authority,CAS 不依赖各自内存 cache。
    • 从真正的 workflow 12 结构/registry 升级:没有新表、保留原 Goal/Session 数据;不要仅修改版本号却预先创建新表的伪迁移测试。
    • 重复打开幂等;future workflow scope 拒绝且不修改原库。
    • 手动破坏 record_json 或索引列/JSON 对应关系时 fail closed。
    • archive、批量退役、remove、purge 都清理等待且不影响其他 Session。
    • 解除归档不恢复记录;删除后 stale commit 不能在不存在 Session 下重建等待。
    • 同根 backup/restore 保留等待;Session copy 不附带活跃订阅。

    E. 故障与无行为变化

    • memory beforeCommit 抛错,状态不变;afterCommit 抛错代表 lost acknowledgement,重读能找到唯一提交,重试不重复创建。
    • 清理事务失败时不留下“Session 已归档,但等待仍有效”等部分结果。
    • fixture 只调用存储,未调用 model、Host admission 或 provider network。
    • 现有 Goal authority、Session retirement、provider conformance 与 Runtime Host build 不回归。

    其中真实进程 crash 的完整后继恢复测试属于 PR 3;本 PR 不以“重开 store 测试通过”冒称运行时自动恢复已完成。

    10. 建议执行顺序

    1. 确认基线和 schema。 写入 PR 工作记录,不动现有用户文档。
    2. 先加 core 测试及类型。 固定形状、边界和状态矩阵,避免 backend 各自解释。
    3. 加入 workflow DDL 与 Local store 测试。 先验证 CAS、slot、重开和合法转换,再补 facade。
    4. 实现 memory store 并跑同一组场景。 不共享 SQLite 操作或以 Local fallback 让 parity 假通过。
    5. 接入 ExecutionPersistence 与 authenticated group。 更新 required ports 和夹具,检查 close/部分打开失败。
    6. 补清理与迁移。 归档、退役、purge、backup/copy 测试要在开放任何生产消费者前完成。
    7. 运行聚焦与 workspace 验证。 保留失败原文和重跑结果,不把环境失败简单吞掉。
    8. 审查 diff。 确认没有 watcher、工具注册、Goal 行为修改或隐藏权限模式。
    9. 提交。 使用项目要求的生成式工具 attribution;未经用户授权不 push 或创建 PR。

    11. 验证命令

    根据实施环境使用 package.json 声明的 Node/npm 工具链。已有依赖若不完整,先执行仓库标准安装;不要为了本 PR 更新锁文件中的无关依赖。

    开发时:

    npm --workspace @maka/core run build
    npm --workspace @maka/storage run build
    
    node --test packages/core/dist/__tests__/event-wait.test.js
    node --test --test-concurrency=2 \
      packages/storage/dist/__tests__/event-wait-authority.test.js \
      packages/storage/dist/__tests__/execution-provider-conformance.test.js \
      packages/storage/dist/__tests__/goal-authority.test.js \
      packages/storage/dist/__tests__/operational-state-store.test.js \
      packages/storage/dist/__tests__/sqlite-workflow-store.test.js \
      packages/storage/dist/__tests__/sqlite-session-metadata-store.test.js \
      packages/storage/dist/__tests__/operational-state-backup.test.js \
      packages/storage/dist/__tests__/session-copy-cleanup.test.js

    如测试依赖 package cwd,从对应 workspace 执行等价命令。新增文件名改变时同步调整命令,不悄悄省略测试。

    提交前至少:

    npm run build:test
    npm run typecheck
    npm run lint
    npm run format:check
    git diff --check

    并运行 core/storage 完整 suite。机器资源允许时使用 npm run test:dist;若遇到已知过高并发问题,可以对相关 workspace 用 node --test --test-concurrency=4 'dist/**/*.test.js' 重跑,并保留首轮失败与重跑事实。确认 Runtime Host 及 Desktop 消费方编译,不能只通过 storage 自己的 build。

    ASF header 检查遵循仓库贡献要求。若 checkout 中存在用户未跟踪的旧文档导致全目录检查失败,不修改那些文件来消除噪声;检查本 PR changed/staged 文件并如实说明。

    不修改 Host wire protocol 的情况下,本 PR 不应为了“新增功能”机械 bump compatibility epoch;workflow schema 的兼容性由 storage migration 管理。若实际改动碰到协议,先解释为何越界,再按现有 guard 验证。

    12. 验收清单与交接输出

    • 新契约封闭且有界,不接收任意命令或授权声明。
    • wait ID、归属、条件和 deliveryKey 不可变;状态转换有唯一实现。
    • SQLite 与 memory 有同一套行为测试,包括 CAS、slot、清理和关闭。
    • workflow migration 正确,旧根数据保留,未来版本 fail closed。
    • 新 store 只能随执行组使用,未引入独立数据库或隐式 provider fallback。
    • archive/remove/purge/backup/copy 边界已测试。
    • 没有改变 Goal 行为、模型调用、工具注册、SessionStatus 或 wire protocol。
    • 记录“resolved”未被当作已经安排执行;实际交付原子性明确留给 PR 3。
    • 现有修改/未跟踪文档未被覆盖,diff 只含本 PR 必要文件。

    实现 Session 最终应输出:

    1. 实际基线与提交 hash。
    2. 文件变更列表及任何对本计划的必要偏离。
    3. 契约、schema、API、清理语义的简短说明。
    4. 执行过的验证命令与真实结果,包含失败、基线复现或未运行项。
    5. PR 2/3 接口交接:如何列出待校准等待、提交结果,以及哪些能力尚未实现。

    遇到以下情况应先停下来说明,而不是扩大 PR

    • 无法在现有 execution consistency domain 内保证等待与 Session 清理一致性。
    • 需要修改实际模型执行、Goal 计费或恢复策略才能让存储测试成立。
    • 发现 provider 或迁移主干发生实质变化,本文版本/文件路径不再适用。
    • 需要用一个独立“已交付”标记掩盖不存在的 continuation 接纳事务。
    • 测试只能靠跳过权限、放宽 schema 校验或隐式落回 Local 才能通过。

    这些问题意味着边界需要重新讨论,而不是应在 PR 1 内实现整套事件唤醒。


    计划依据为定向代码核查,新增设计仍需代码评审。该文档没有修改生产代码,也没有声明相关测试已经运行。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requesttrackingTracking or umbrella issue

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions