fix(bus): bind delivered review to task closure (#1839)

This commit is contained in:
jason.woltje committed 2026-11-04 13:31:15 -06:00
1 parent aa9b91d88b
commit 76f8d3654f
4 files changed
+58 -4

No files matched your search

+4
View File
@@ -6122,3 +6122,7 @@ Local commit only; no push or live instance/credential-file access.
2026-11-04 | GPT-6.1 Terra implementation sub-agent | #797 / Gitea #1839 lifecycle Task 7b WIP | Implemented reviewed-delivery draft, 6b completion transition, owner attachment and legacy completion handling on t3/lifecycle-task-7b; no migration. Red-first logs and focused evidence under /mnt/storage/scratch/tmp/vWMFoL/. Brief trio 22/22, harness 228/228, business 307/307, seat 239/239, webui 558/558, zero skips on those runs. Whole bus 1566/1569: channel-proof freezes the changed webui transport bytes and two Console whitelist pins omit the mandated artifact verb. Each failing file reproduced alone and passed on 647626bdd. Stopped for lead ruling on the security proof baseline; tests and changes remain uncommitted, no push.
2026-11-04 | GPT-6.1 Terra implementation sub-agent | #797 / Gitea #1839 lifecycle Task 7b | Lead authorized the Console transport proof re-pin after the earlier stop: webui bus.mjs changes only the artifact response cap to 2 MiB, all other Console responses retain 1 MiB, and the focused transport test checks both boundaries and refusal above them. Added owner-only artifact read, reviewed blob and reviewer record.write receipt binding, atomic completion and chief intent, same-artifact duplicate handling, historical complete preservation, safe attachment and evidence panel. Real task.close through the tasks adapter and fake tracker now feeds an end-to-end delivery fixture; coder push_working and reviewer fetch_review use distinct local clones. The downloaded HTML was opened in offline Chromium and matched the reviewed blob and hash. No migration; schema remains 3s. GREEN zero skips: brief 24, bus 1570, harness 228, business 307, seat 239, webui 559; logs /mnt/storage/scratch/tmp/vWMFoL/final-*. Previous bus failure and stop remain recorded above; no push or deployment.
2026-11-04 | GPT-6.1 Terra implementation sub-agent | #797 / Gitea #1839 Task 7b B1 fix round, before | On 1904c9775, review found delivery could combine the planned reviewer closure with a newer unclosed second reviewer verdict. Added two real task.close regressions: unclosed V2 must refuse without completion or chief intent, and closed planned V1 must remain deliverable after another reviewer’s V2. Both reproduced the defect before the fix; red log /mnt/storage/scratch/tmp/cLYQkO/red.log. Queue next refused linked-worktree use; canonical checkout untouched.
2026-11-04 | GPT-6.1 Terra implementation sub-agent | #797 / Gitea #1839 Task 7b B1 fix round, after | Bound delivery to the exact push, planned review, approving verdict and task.closed receipt accepted by request progress. A newer unclosed second-reviewer V2 now refuses without completion or chief intent; the closed planned-reviewer V1 still delivers. Both real-adapter regressions red before and green after (logs /mnt/storage/scratch/tmp/cLYQkO/red.log and green.log). Required gates zero skips: bus 1572/1572, harness 228/228, seat 239/239, webui 559/559; logs /mnt/storage/scratch/tmp/cLYQkO/. The first whole webui attempt had one timeout in decide-browser I1 (558/559); that file passed 9/9 alone on both this branch and 647626bdd, and the whole rerun passed. No proof pin, Console whitelist, transport, policy or schema change; no push.
+1
View File
@@ -2020,3 +2020,4 @@ are never rewritten or removed; corrections are new entries.
2026-11-04 | GPT-6.1 Terra implementation sub-agent, t3/lifecycle-task-7b | #797 / Gitea #1839 lifecycle Task 7b WIP | After lead ruling implemented and tested a delivery draft; stopped at frozen Console proof/transport hash conflict, with bus 1566/1569 and other recorded gates green; no migration, no commit, no push. Logs /mnt/storage/scratch/tmp/vWMFoL/.
2026-11-04 | GPT-6.1 Terra implementation sub-agent, t3/lifecycle-task-7b | #797 / Gitea #1839 lifecycle Task 7b | Lead-approved proof re-pin, artifact-only cap, real adapter task.close fixture and completed delivery draft. Brief 24/24, bus 1570/1570, harness 228/228, business 307/307, seat 239/239, webui 559/559; zero skips, no migration; logs /mnt/storage/scratch/tmp/vWMFoL/final-*. Local commit only, no push.
2026-11-04 | GPT-6 Luna implementation sub-agent, t3/lifecycle-task-7b-r2 | #797 / Gitea #1839 lifecycle Task 7b rebase onto 34e9af616 | Cherry-picked 1904c9775 as one local commit; unioned BUILD-LOG and SESSIONS stages 1/2/3 and retained both intake_question and deliver_request in harness tool/bundle expectations. Post-pick checks: webui bus.mjs 593d12b1582a4a3a285e0b89b32d9ee6e3ab726d, channel-proof pin 34fa64ffa17886d86243d7712fdb6f632c22b96412b642d785ddbc05fbd79692. Bus 1570/1570, harness 234/234, business 307/307, seat 239/239, zero skips. WebUI exact full runs: 562/563 with decide-browser timeout, isolated decide-browser 9/9; second run 562 passed/1 cancelled on tester-cli timeout, isolated tester-cli 2/2; final concurrency-4 run 563/563, zero skips. Logs: /mnt/storage/scratch/tmp/FcQIVV/bus.log, /mnt/storage/scratch/tmp/b3p9Ff/harness.log, /mnt/storage/scratch/tmp/YSkAka/business.log, /mnt/storage/scratch/tmp/4Qkhl5/seat.log, /mnt/storage/scratch/tmp/Zcgo8v/webui.log, /mnt/storage/scratch/tmp/n0o7aV/decide-browser.log, /mnt/storage/scratch/tmp/HIdzOF/webui.log, /mnt/storage/scratch/tmp/gBCdZz/tester-cli.log, /mnt/storage/scratch/tmp/GyMQoN/webui.log. No push.
2026-11-04 | GPT-6.1 Terra implementation sub-agent, t3/lifecycle-task-7b | #797 / Gitea #1839 Task 7b B1 fix round | Delivery now uses the exact review/verdict/closure accepted by progress; real-close V2 refusal and V1 delivery regressions red then green. Bus 1572/1572, harness 228/228, seat 239/239, webui rerun 559/559, zero skips; initial webui timeout and isolated branch/base passes recorded in BUILD-LOG. Logs /mnt/storage/scratch/tmp/cLYQkO/; local commit only, no push.
+10 -4
View File
@@ -773,10 +773,16 @@ export class Broker {
if (state.phase !== 'delivery' || !state.tasks.includes(args.task_ref)) fail('request-evidence-required');
const rows = this.#store.all('SELECT * FROM events WHERE business=? AND subject=? ORDER BY seq', state.business, args.task_ref)
.map(e => ({ ...e, body: JSON.parse(e.body) }));
const push = rows.findLast(e => e.kind === 'action.allowed' && e.body.action === 'git.push.working');
const review = rows.findLast(e => e.kind === 'review.requested' && e.seq > push.seq && e.body.commit === push.body.commit && e.body.project === state.project);
const verdict = review && rows.findLast(e => e.kind === 'review.verdict' && e.body.request === review.id && e.body.commit === push.body.commit);
if (!review || !verdict || verdict.id !== args.verdict || verdict.body.verdict !== 'approve' || verdict.actor_role !== review.body.to || verdict.actor_role === push.actor_role) fail('request-evidence-required');
// Use the same chain that #requestProgress accepted, including the specific close receipt.
// A later review by another role cannot replace the verdict that actually closed this task.
const accepted = new Set(progress.evidence);
const push = rows.findLast(e => accepted.has(e.id) && e.kind === 'action.allowed' && e.body.action === 'git.push.working');
const review = rows.findLast(e => accepted.has(e.id) && e.kind === 'review.requested');
const verdict = rows.findLast(e => accepted.has(e.id) && e.kind === 'review.verdict');
const close = rows.findLast(e => accepted.has(e.id) && e.kind === 'task.closed');
if (!push || !review || !verdict || !close || verdict.id !== args.verdict || close.body.verdict !== verdict.id ||
verdict.body.request !== review.id || verdict.body.commit !== push.body.commit || verdict.body.verdict !== 'approve' ||
verdict.actor_role !== review.body.to || verdict.actor_role === push.actor_role) fail('request-evidence-required');
const receipt = this.#store.all("SELECT * FROM events WHERE business=? AND kind='action.allowed' AND actor_role=? AND seq<? AND json_extract(body,'$.action')='record.write' ORDER BY seq", state.business, verdict.actor_role, verdict.seq)
.find(e => { const b = JSON.parse(e.body); return e.seq > review.seq && b.project === state.project && b.path === args.verification_record.path && b.commit === args.verification_record.commit; });
if (!receipt) fail('request-verification-refused');
@@ -137,3 +137,46 @@ test('real task.close through the tracker adapter supplies the closure used by d
assert.equal(d.verdict, f.verdict.verdict);
assert.equal(f.journal.readRequest('demo', f.request).phase, 'complete');
});
async function newerUnclosedReview(f) {
const { bootConfig, loadSystem } = await import('../../cli/src/config.mjs');
const config = bootConfig({ system: loadSystem({ env: f.f.env }), businessId: 'demo', env: f.f.env, warn: () => {} });
const business = config.businesses.demo;
business.roles.reviewer2 = structuredClone(business.roles.reviewer);
business.roles.reviewer2.tracker = { bot: 'bot-demo-reviewer2', botId: f.fake.user('reviewer2') };
business.launch.instances.push('reviewer2');
f.bus.broker.reloadBusiness('demo', business);
const cap = f.bus.broker.bindLaunch({ business: 'demo', role: 'reviewer2', run: 'reviewer2-run', harness: 'pi' });
f.bus.broker.request(cap, { verb: 'role.claim' });
const review = f.call('coder', 'review.request', { task_ref: f.task_ref, commit: f.commit, to: 'reviewer2' });
const results = 'Reviewer two independently passed acceptance.';
const verification = await f.bus.broker.request(cap, { verb: 'record.write', args: { project: 'timer', path: 'docs/verification2.md',
record: JSON.stringify({ version: 1, task_ref: f.task_ref, review_request: review.request, commit: f.commit, results }) } });
const verdict = f.bus.broker.request(cap, { verb: 'review.verdict', args: { task_ref: f.task_ref, request: review.request, verdict: 'approve',
body: JSON.stringify({ verification_record: { path: verification.path, commit: verification.commit }, results }) } });
return { review, verdict, verification };
}
test('refuses a newer approval whose verdict was never cited by the task closure', async t => {
const f = await ready(t, { realClose: true }), v2 = await newerUnclosedReview(f);
const closures = f.store.all("SELECT body FROM events WHERE business=? AND kind='task.closed' AND subject=?", 'demo', f.task_ref);
assert.equal(closures.length, 1);
assert.equal(JSON.parse(closures[0].body).verdict, f.verdict.verdict);
await assert.rejects(f.call('pm', 'request.deliver', { ...f.args, verdict: v2.verdict.verdict,
verification_record: { path: v2.verification.path, commit: v2.verification.commit } }), { code: 'request-evidence-required' });
assert.equal(f.journal.readRequest('demo', f.request).phase, 'delivery');
assert.equal(f.store.get("SELECT count(*) n FROM request_events WHERE business=? AND request=? AND kind='advanced' AND json_extract(body,'$.phase')='complete'", 'demo', f.request).n, 0);
assert.equal(f.journal.effects('demo', f.request).filter(e => e.kind === 'continuation' && e.stage === 'uncertain' && e.result?.intent?.step === 'send_message').length, 0);
assert.equal(f.journal.effects('demo', f.request).filter(e => e.kind === 'delivery' && e.stage === 'committed').length, 0);
});
test('delivers the closed planned review even after another reviewer gives a newer unclosed approval', async t => {
const f = await ready(t, { realClose: true }), v2 = await newerUnclosedReview(f);
const d = await f.call('pm', 'request.deliver', f.args);
assert.equal(d.review_request, f.review.request);
assert.equal(d.verdict, f.verdict.verdict);
assert.notEqual(d.verdict, v2.verdict.verdict);
assert.equal(d.verification.record_commit, f.verification.commit);
assert.equal(f.journal.readRequest('demo', f.request).phase, 'complete');
assert.equal(f.journal.effects('demo', f.request).filter(e => e.kind === 'continuation' && e.stage === 'uncertain' && e.result?.intent?.step === 'send_message').length, 1);
});