Skip to content

fix: retry local evidence recording after Hub delivery - #24

Open
imxyanua wants to merge 2 commits into
EvoMap:mainfrom
imxyanua:codex/fix-local-report-retry
Open

imxyanua wants to merge 2 commits into
EvoMap:mainfrom
imxyanua:codex/fix-local-report-retry

Conversation

@imxyanua

@imxyanua imxyanua commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

When the Hub accepts a reuse report but the local ledger write fails, sentAt makes the entry settled and permanently skips the missing local evidence.

Hub accepted + local write failed
  → retry local write only
  → keep original sentAt
  → stop after localAt is recorded

Keep terminal rejections and tier-none reports excluded, and never resend an accepted Hub report.

Evidence

  • Before: both false-return and rejected-promise ledger regressions leave the successful retry with no local record.
  • After: each writes local evidence once, preserves sentAt, performs zero additional Hub posts and stops retrying after success.
  • Reuse/outbox/file-store tests: 38 passed on Windows Node 24.21.0. All eight hosts pack; git diff --check passes. Independent code review traced delivery through the persisted outbox.
  • Initial full CI caught two headless no-request fixture failures. Request-origin tracing on both Linux Node 22/26 proved these were prior-session detached drain workers, not headless-origin traffic. Each no-write test now has its own loopback Proxy/profile; all original events, assertions, timeout and workload remain. Exact host files changed from 17/19 to 19/19 on both runtimes. Final head 77d51a7 reruns CI.
  • Tests use fixture ledgers and loopback endpoints; no real Hub/account was called.

Merge Danger

Door: two-way

No state schema changes. Existing sent entries without localAt become eligible for local-only retry.

Blast Radius: reporting

The existing eight-day outbox retention remains the retry horizon. Attempts includes local retry attempts; Hub delivery identity and rejection behavior are unchanged. Local recording and the outbox checkpoint remain separate writes: this patch does not claim exactly-once local events across a process crash or an ambiguous commit. The outbox serialization contract handles normal concurrent drains; worker-lock recovery is reviewed separately in #28.

@evomap-aiqa-bot

evomap-aiqa-bot Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Sentinel 已审核此变更,未发现已核验问题,但发现需要人工确认的风险。

GitHub CI 未通过。 此状态来自当前 CI gate,不是 Sentinel finding;请以最新 CI attempt 的日志为准。

另有 1 个需要人工确认的风险;黄色 advisory 不阻塞合并,建议开发 agent review advisory 评论并逐条确认。

开发 agent 查询详情: 调用 MCP 工具 sentinel_pr_review_context,传入 owner_repo="evomap/evolver-agent-plugin"、pr_number=24、head_sha="d59809bdcec73a5856235883ae851a41c21ecfcd",并按条目追加 finding_id 或 finding_key。

由 Sentinel 审核 · 目标提交 d59809bdcec7

@evomap-aiqa-bot

evomap-aiqa-bot Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Sentinel 保留了 1 个尚未确认的审核候选。这些黄色条目不阻塞合并;红色 P0/P1 与其他独立硬门禁不受影响。 单一猜测、缺少变更行证据或纯 checklist 的候选会统一降为 P3。
开发 agent:请 review 以下 advisory,并逐条给出“已验证无风险 / 已修复 / 转为后续事项”的结论;不要只做整体确认。

  • [P2] 本地补写缺少并发与不确定提交幂等保护 — 新增分支以 outbox 中缺失 localAt 判断是否补写,但“检查状态、调用 recordLocally、再写入 ...(truncated) · lib/reuse.js · data_integrity:finding_1

需要完整证据时,可用行末 finding_key 查询 Sentinel PR review context。

由 Sentinel 审核 · 目标提交 d59809bdcec7

@evomap-aiqa-bot

evomap-aiqa-bot Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Sentinel 已审核此变更,未发现已核验问题,但发现需要人工确认的风险。

另有 1 个需要人工确认的风险;黄色 advisory 不阻塞合并,建议开发 agent review advisory 评论并逐条确认。

开发 agent 查询详情: 调用 MCP 工具 sentinel_pr_review_context,传入 owner_repo="evomap/evolver-agent-plugin"、pr_number=24、head_sha="77d51a7095268f4b657f4d3c0fb73f4cbd0324b8",并按条目追加 finding_id 或 finding_key。

由 Sentinel 审核 · 目标提交 77d51a709526

@evomap-aiqa-bot

Copy link
Copy Markdown

Sentinel 保留了 1 个尚未确认的审核候选。这些黄色条目不阻塞合并;红色 P0/P1 与其他独立硬门禁不受影响。 单一猜测、缺少变更行证据或纯 checklist 的候选会统一降为 P3。
开发 agent:请 review 以下 advisory,并逐条给出“已验证无风险 / 已修复 / 转为后续事项”的结论;不要只做整体确认。

  • [P3] 本地补记缺少并发幂等保护,可能产生重复账本记录 — localOnly 仅依据传入 outbox 快照中的 !entry.localAt 判断。两个并发的 deliverPending 都可以读�...(truncated) · lib/reuse.js · advisory_0c55e635a1bcd367ea05f44b

需要完整证据时,可用行末 finding_key 查询 Sentinel PR review context。

由 Sentinel 审核 · 目标提交 77d51a709526

@forrestlinfeng forrestlinfeng left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review — changes requested

The fix is right: the Hub is never posted to twice (the local-only path skips attemptOf, and withAttempt keeps the existing sentAt), and rejected / tier-none reports stay excluded.

Minor

  • Retries are unbounded (8-day outbox expiry) and expensive. recordReuseLocally returns false for "evolver-core can't be found" (lib/local-ledger.js:76-77), which is permanent, and the failure isn't cached; the fallback spawns npm root -g with a 5 s timeout. So every drain, per sent entry missing its local record, repeats import + npm spawn while holding the outbox lock, for 8 days. Distinguish a permanent "no ingestor" from a transient failure, or cap local retries by attempts.
  • Idempotency gap: if ingest writes and then throws, the retry records the event twice (acknowledged in the description).
  • Predicate shape: deliverPending (lib/reuse.js:134) builds the condition from a synthetic { verdict: SENT } plus !entry.rejectedAt, which is redundant since sentAt and rejectedAt never co-occur. A named isLocalRecordPending(entry) (or letting attemptOf choose the path) would read better.
  • Scope: the codex/workbuddy hook-test isolation changes are unrelated and clean up differently (closeWhenSettled(proxy) vs proxy.close()); please move them to a separate PR.

@evomap-aiqa-bot

evomap-aiqa-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Sentinel 已审核此变更,未发现已核验问题,但发现需要人工确认的风险。

GitHub CI 未通过。 此状态来自当前 CI gate,不是 Sentinel finding;请以最新 CI attempt 的日志为准。

另有 2 个需要人工确认的风险;黄色 advisory 不阻塞合并,建议开发 agent review advisory 评论并逐条确认。

开发 agent 查询详情: 调用 MCP 工具 sentinel_pr_review_context,传入 owner_repo="evomap/evolver-agent-plugin"、pr_number=24、head_sha="eabeb3df61d4c4509a221d772cb26c55097e0b71",并按条目追加 finding_id 或 finding_key。

由 Sentinel 审核 · 目标提交 eabeb3df61d4

@evomap-aiqa-bot

Copy link
Copy Markdown

Sentinel 保留了 2 个尚未确认的审核候选。这些黄色条目不阻塞合并;红色 P0/P1 与其他独立硬门禁不受影响。 单一猜测、缺少变更行证据或纯 checklist 的候选会统一降为 P3。
开发 agent:请 review 以下 advisory,并逐条给出“已验证无风险 / 已修复 / 转为后续事项”的结论;不要只做整体确认。

  • [P3] 不确定提交会把同一账本记录追加最多三次 — 本地写入请求只包含 assetId/outcome/sessionId,没有报告 ID 或幂等键;写入异常被统一转换为 ...(truncated) · lib/reuse.js · advisory_557b73b2bf2887b4d5ce9302
  • [P3] 并发补记可能重复写入本地账本 — 两个调用都会在各自独立快照上判定 localOnly 为真,并分别异步执行 recordLocally。localAt ...(truncated) · lib/reuse.js · advisory_fdc415001e19afc9257a86e7

需要完整证据时,可用行末 finding_key 查询 Sentinel PR review context。

由 Sentinel 审核 · 目标提交 eabeb3df61d4

@evomap-aiqa-bot

evomap-aiqa-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Sentinel 已审核此变更,未发现已核验问题,但发现需要人工确认的风险。

GitHub CI 未通过。 此状态来自当前 CI gate,不是 Sentinel finding;请以最新 CI attempt 的日志为准。

另有 1 个需要人工确认的风险;黄色 advisory 不阻塞合并,建议开发 agent review advisory 评论并逐条确认。

开发 agent 查询详情: 调用 MCP 工具 sentinel_pr_review_context,传入 owner_repo="evomap/evolver-agent-plugin"、pr_number=24、head_sha="8767b36e470e8c7445035e0a9c54142af00e12a3",并按条目追加 finding_id 或 finding_key。

由 Sentinel 审核 · 目标提交 8767b36e470e

@evomap-aiqa-bot

Copy link
Copy Markdown

Sentinel 保留了 1 个尚未确认的审核候选。这些黄色条目不阻塞合并;红色 P0/P1 与其他独立硬门禁不受影响。 单一猜测、缺少变更行证据或纯 checklist 的候选会统一降为 P3。
开发 agent:请 review 以下 advisory,并逐条给出“已验证无风险 / 已修复 / 转为后续事项”的结论;不要只做整体确认。

  • [P3] 不确定提交会将同一报告写入本地账本最多三次 — recordedLocally 将所有异常折叠为 false,而请求仅包含 assetId/outcome/sessionId,没有报告 ID ��...(truncated) · lib/reuse.js · advisory_48b092344c91cf7ef39fff35

需要完整证据时,可用行末 finding_key 查询 Sentinel PR review context。

由 Sentinel 审核 · 目标提交 8767b36e470e

@evomap-aiqa-bot

evomap-aiqa-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Sentinel 已审核此变更,未发现已核验问题,但发现需要人工确认的风险。

另有 1 个需要人工确认的风险;黄色 advisory 不阻塞合并,建议开发 agent review advisory 评论并逐条确认。

开发 agent 查询详情: 调用 MCP 工具 sentinel_pr_review_context,传入 owner_repo="evomap/evolver-agent-plugin"、pr_number=24、head_sha="6a357968a250dcf5b4b94b039d7e30e833c9cbdf",并按条目追加 finding_id 或 finding_key。

由 Sentinel 审核 · 目标提交 6a357968a250

@evomap-aiqa-bot

Copy link
Copy Markdown

Sentinel 保留了 1 个尚未确认的审核候选。这些黄色条目不阻塞合并;红色 P0/P1 与其他独立硬门禁不受影响。 单一猜测、缺少变更行证据或纯 checklist 的候选会统一降为 P3。
开发 agent:请 review 以下 advisory,并逐条给出“已验证无风险 / 已修复 / 转为后续事项”的结论;不要只做整体确认。

  • [P3] 本地补记在不确定提交或并发排空时可能重复入账 — recordLocally 的请求只有 assetId/outcome/sessionId,本层没有报告 ID、幂等键或原子声明。写入�...(truncated) · lib/reuse.js · advisory_d062c3acd3c01e98bed18e56

需要完整证据时,可用行末 finding_key 查询 Sentinel PR review context。

由 Sentinel 审核 · 目标提交 6a357968a250

@forrestlinfeng forrestlinfeng left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 2 — one cleanup before merge

Round-1 items look good: local retries are capped at 3 (MAX_LOCAL_FAILURES, lib/reuse.js:93,118-120), the predicate is now a named isLocalRecordDue(entry) without the synthetic { verdict: SENT }, and the idempotency gap is documented. Keeping the hook-test isolation here is fine now that it covers all four hosts consistently.

Before merge

  • Commit history: eabeb3d reverts the hook-test change and 8767b36 re-adds it. Since this repo merges with merge commits, that churn would land on main — please squash/clean up the branch history.

Minor (can be in this PR or a follow-up)

  • lib/report-outbox.js:38 relies on isLocal being tri-state (undefined / true / false) and mutates local after building it. Building localFailures in the same expression, or having the caller pass an explicit localFailed, would make it obvious.
  • Nit: the four new local proxy variables in the hook tests shadow the module-level proxy.

@evomap-aiqa-bot

evomap-aiqa-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Sentinel 已审核此变更,未发现已核验问题,但发现需要人工确认的风险。

另有 1 个需要人工确认的风险;黄色 advisory 不阻塞合并,建议开发 agent review advisory 评论并逐条确认。

开发 agent 查询详情: 调用 MCP 工具 sentinel_pr_review_context,传入 owner_repo="evomap/evolver-agent-plugin"、pr_number=24、head_sha="8d0794f3acc7dfb2f69cf779527dc6c612330892",并按条目追加 finding_id 或 finding_key。

由 Sentinel 审核 · 目标提交 8d0794f3acc7

@evomap-aiqa-bot

Copy link
Copy Markdown

Sentinel 保留了 1 个尚未确认的审核候选。这些黄色条目不阻塞合并;红色 P0/P1 与其他独立硬门禁不受影响。 单一猜测、缺少变更行证据或纯 checklist 的候选会统一降为 P3。
开发 agent:请 review 以下 advisory,并逐条给出“已验证无风险 / 已修复 / 转为后续事项”的结论;不要只做整体确认。

  • [P2] 写入前异常被误判为不确定提交,导致本地账本永久漏记 — PR 让 recordReuseLocally 将 ingestor.ingest 的所有异常向上传播,而 recordedLocally 又把所有异常...(truncated) · lib/reuse.js · advisory_10b4e2a4c966b35968364256

需要完整证据时,可用行末 finding_key 查询 Sentinel PR review context。

由 Sentinel 审核 · 目标提交 8d0794f3acc7

@imxyanua
imxyanua force-pushed the codex/fix-local-report-retry branch from 8d0794f to d0cfddd Compare November 3, 2026 11:04
@evomap-aiqa-bot

evomap-aiqa-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Sentinel 已审核此变更,并发现 1 个已核验问题。

另有 2 个需要人工确认的风险;黄色 advisory 不阻塞合并,建议开发 agent review advisory 评论并逐条确认。

开发 agent 查询详情: 调用 MCP 工具 sentinel_pr_review_context,传入 owner_repo="evomap/evolver-agent-plugin"、pr_number=24、head_sha="d0cfddd316cda4b151f003f6773351b55d8e3f33",并按条目追加 finding_id 或 finding_key。

由 Sentinel 审核 · 目标提交 d0cfddd316cd

@evomap-aiqa-bot

Copy link
Copy Markdown

Sentinel 保留了 2 个尚未确认的审核候选。这些黄色条目不阻塞合并;红色 P0/P1 与其他独立硬门禁不受影响。 单一猜测、缺少变更行证据或纯 checklist 的候选会统一降为 P3。
开发 agent:请 review 以下 advisory,并逐条给出“已验证无风险 / 已修复 / 转为后续事项”的结论;不要只做整体确认。

  • [P2] 三次临时失败会永久丢失本地账本记录 — localFailures 达到 MAX_LOCAL_FAILURES 后,isLocalRecordDue 永久返回 false。后续即使本地账本恢�...(truncated) · lib/reuse.js · advisory_8f05970ad1d925d2a98c7229
  • [P3] 非锁超时的写入前异常会永久停止本地补记 — 本 PR 将非 LOCK_TIMEOUT 的 ingest 异常抛给 recordedLocally;后者统一写入 localUncertainAt。随��...(truncated) · lib/reuse.js · advisory_f49075f17a6759dfc0dd935f

需要完整证据时,可用行末 finding_key 查询 Sentinel PR review context。

由 Sentinel 审核 · 目标提交 d0cfddd316cd

Comment thread test/reuse.test.js
assert.equal(Object.values(sent)[0].sentAt, NOW);
assert.equal(Object.values(sent)[0].localAt, undefined);
const up = ledgers();
const recorded = await deliverPending(up, sent, NOW + 1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

锁超时测试的 mock 首次调用判断错误

🟠 严重程度:中(P2)
t.mock.method 调用 mock 实现前已计入本次调用,首次执行时 ingest.mock.callCount() 为 1 而非 0;因此 mock 不会抛出 LOCK_TIMEOUT,但测试随后断言 pending.localFailures === 1。

Finding:finding_db362c489393ca9c213c8664

@evomap-aiqa-bot

evomap-aiqa-bot Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Sentinel 已审核此变更,未发现已核验问题,但发现需要人工确认的风险。

GitHub CI 未通过。 此状态来自当前 CI gate,不是 Sentinel finding;请以最新 CI attempt 的日志为准。

另有 3 个需要人工确认的风险;黄色 advisory 不阻塞合并,建议开发 agent review advisory 评论并逐条确认。

开发 agent 查询详情: 调用 MCP 工具 sentinel_pr_review_context,传入 owner_repo="evomap/evolver-agent-plugin"、pr_number=24、head_sha="283fd25c186351e37965b7a0c4b778d7bb65c5b2",并按条目追加 finding_id 或 finding_key。

由 Sentinel 审核 · 目标提交 283fd25c1863

@evomap-aiqa-bot

Copy link
Copy Markdown

Sentinel 保留了 3 个尚未确认的审核候选。这些黄色条目不阻塞合并;红色 P0/P1 与其他独立硬门禁不受影响。 单一猜测、缺少变更行证据或纯 checklist 的候选会统一降为 P3。
开发 agent:请 review 以下 advisory,并逐条给出“已验证无风险 / 已修复 / 转为后续事项”的结论;不要只做整体确认。

  • [P2] 本地写入达到三次失败后永久漏记 — withAttempt 每次将 isLocal === false 累加到 localFailures;达到 MAX_LOCAL_FAILURES 后,isLocalRecord...(truncated) · lib/reuse.js·advisory_0ed9657c07d2bf98bc212a81`
  • [P3] 写入前异常会被永久视为“提交不确定” — 当前 PR 将除 LOCK_TIMEOUT 外的所有 ingest 异常重新抛出;recordedLocally 随后统一转换为 { lo...(truncated) · lib/local-ledger.js·advisory_0d625c4c49bc7dc37b1c77f8`
  • [P3] 写入前的非锁异常会被永久标记为“不确定提交” — recordReuseLocally 仅将 LOCK_TIMEOUT 转换为可重试的 false,其他异常继续抛出;recordedLocally ...(truncated) · lib/reuse.js · advisory_83a94e1fa0619cce8dc24cde

需要完整证据时,可用行末 finding_key 查询 Sentinel PR review context。

由 Sentinel 审核 · 目标提交 283fd25c1863

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants