/** * 状态回写:把「本会话应该处于什么状态」推到平台。 * * 这是 [[issue-board-store]] 那个发件箱的**唯一**发送端。所有想改 issue 状态的调用方 * (开工、交付、验收、释放)都走 `projectStatus`,谁都不许直接调 `writeIssueStatus`—— * sourceSeq 的分配、串行、退避、409 对账全都只在这里实现一次。 * * ## 为什么必须有它(不是架构洁癖) * * 平台在 bind 之后给一个 **5 分钟的 activation 租约**:到期时 issue 还停在 `claimed`, * sweeper 就把它翻成 `needs_attention/claim_activate_timeout`(platform `src/store/issues.ts` * 的 sweepExpiredLeases,每 60s 扫一次)。证明「我真的在跑」的唯一方式就是回写 * `in_progress`。 * * 在这一刀之前 botmux 一个状态都不写,于是那条本该罕见的保护路径变成了**必经之路**: * 每个领取的任务 5 分钟后必然掉进需要关注,而且**回不来**——平台只放行 * `task_blocked` 的 needs_attention 恢复成 in_progress,`claim_activate_timeout` 只能 * open/reopened(都清 claim),那个群的工作就废了。所以「开工即写 in_progress」不是 * 锦上添花,是拆引信。 * * ## 409 的两种含义 * * 与 [[issue-release]] 同一套判据:撞 409 先问「平台还认不认我这个 claim」—— * - **stateRev 过期** → claim 还是我的,拿新基线重发**同一条行**(它上次没被应用, * 复用 sourceSeq 是安全的:平台只在 `<= lastSourceSeq` 时才当重复丢弃) * - **claim 已不归本机** → 平台侧这条早就结束了(force-detach/租约过期/被别人领走), * 再打多少次都一样,直接判定"已结算"让调用方收尾 */ import { type AttentionReason, type IssueStatus } from './issue-board-store.js'; import type { IssueClientResult, PlatformIssue } from '../platform/issue-client.js'; export interface StatusWriterDeps { dataDir: string; writeStatus: (issueId: string, args: { claimId: string; claimEpoch: number; sourceSeq: number; status: IssueStatus; attentionReason?: AttentionReason; expectedStateRev: number; }) => Promise>; /** 撞 409 时用来判断「平台还认不认这个 claim」。拿不到就当无法判定,不猜。 */ fetchIssue: (teamId: string, issueId: string) => Promise; now?: () => number; } export type FlushOutcome = /** 平台接受了这次回写。 */ { ok: true; applied: true; issue: PlatformIssue; } /** 平台侧这条 claim 已经不归本机了 —— 无需再发,调用方按"已结算"收尾。 */ | { ok: true; applied: false; detached: true; } /** 没有待发行(已经同步到位,或压根没排队)。 */ | { ok: false; reason: 'idle'; } /** 同一 binding 已有 inflight —— 串行约束,稍后再来。 */ | { ok: false; reason: 'busy'; } /** binding 不存在或已是终态。 */ | { ok: false; reason: 'no_binding'; } /** `permanent` = 平台明确拒绝且重试无意义,行已标 fatal 不再重投(见 isPermanentFailure)。 */ | { ok: false; reason: 'platform'; detail: string; permanent?: boolean; }; /** * 把发件箱里该 binding 的下一条待发行发出去。 * * 不排队、只发送——用于后台 pump 重投那些失败退避过的行。 */ export declare function flushNextStatus(deps: StatusWriterDeps, anchorId: string): Promise; /** * 投影一个目标状态并立刻尝试发送。 * * 排队交给 `enqueueDesiredStatus`(sourceSeq 的唯一分配入口 + 去重合并),发送交给 * `flushNextStatus`。发失败时行留在发件箱里退避,后台 pump 会接着重投——所以调用方 * 拿到 `platform` 失败也不代表这次投影丢了。 */ export declare function projectStatus(deps: StatusWriterDeps, anchorId: string, desired: IssueStatus, opts?: { attentionReason?: AttentionReason; }): Promise; //# sourceMappingURL=issue-status-writer.d.ts.map