fix(lifecycle): explain incomplete planning coverage (#797)
This commit is contained in:
1 parent
05fdfef9ea
commit
62bece4a9d
8 files changed
+100
-4
No files matched your search
@@ -6193,3 +6193,11 @@ Before: clean isolated checkout at e9271841f3af23bf2a72aa17d9d67a1ec5abe017; no
|
||||
After: personally authored three-file skills/ms-ux-improvement coordinator package, complementary to existing frontend execution guidance; final candidate-02 content manifest b93d7836ed7c70db62386192d6e1db5c4f867f841aaa7645c3c861def5a9b140. Retained 19 bounded clean-context behavioral samples / 186 raw files: six unguided baselines, seven candidate-01 samples including ready-review control, six candidate-02 samples. Candidate-01's two unnecessary frozen-method recovery conditions and already-correct control remain recorded; narrowed wording and final tests show no extra gate, without causal/statistical improvement claim. Fresh package-focused skill-creator/resource validation passed for workspace, frozen snapshot and scratch copy; patch/whitespace, arithmetic, integrity, unchanged baseline and append-only ownership checks passed. Entry 637 words (2,210 total) and one-model synthetic/procedural-scope limits retained as author concerns. Exact source, patch, hashes, commands, raw evidence and independent-review brief: /mnt/storage/scratch/mosaic-lead/skill-ux-improvement/author/run-20261105T022125Z/HANDOFF.md. Author task complete; independent source approval pending. No application suites, commits, forge/queue writes, publication, installation, runtime activation or protected actions.
|
||||
|
||||
2026-11-04 | #818 / Gitea #1845 canonical skill integration verified | Exact3 reviewed Markdown files match source hashes; canonical skill-creator validator and whitespace checks pass. Independent review APPROVE/no findings,14 focused checks and a fresh bounded forward-use handoff retained. All prior canonical and author log bytes preserved. Package is available in source by explicit path; no managed/personal installation, activation, publication or user-acceptance claim. MVP activation remains pinned to parentbd8120318, independent of this skill commit.
|
||||
|
||||
## 2026-11-05 — MVP 3 planning coverage implementation started (#797)
|
||||
|
||||
Before: pinned 05fdfef9ea1fdddf58c725c04f229ca8559fc054; live b1869a13 has five approved requirements but one singly cited task. Scope: preserve scalar task schema, surface exact uncovered IDs to PM, clarify planning versus push evidence, diagnose coder stop and report D3–D5. Local branch only; no push, merge, runtime writes or role authority changes. Queue refused in linked worktree; direct implementation charter governs this child.
|
||||
|
||||
## 2026-11-05 — MVP 3 planning coverage implementation ready for review (#797)
|
||||
|
||||
After: preserve the scalar task requirement schema and fail-closed coverage gate; incomplete advance now sends a deduplicated PM bus notice naming every uncovered id and explaining correction, task-limit reconciliation and why a push cannot clear coverage. PM contract, task_create description and planning continuation state the same rule. Five-requirement regression proves prose/push do not cover missing ids, retries do not flood, changed coverage updates the notice, and complete assigned coverage advances without pushes for the other tasks. Focused whole files: 52 pass/0 fail/0 skip; scoped/lifecycle/import/role regressions: 115 pass/0 fail/0 skip; dependency/path/closure files: 61 pass/0 fail/0 skip (228 total). Initial fixture run: 51 pass/1 fail (new test used an invalid PRD author; corrected to pm.pm). Initial path checks: 28 pass/23 fail from absent worktree node_modules; canonical dependency versions matched package.json, linked locally, then all 61 passed. Read-only .53 evidence identifies proxy-stall watchdog as coder stop cause; D2–D5 remain report-only follow-ups. Detailed commands, receipts, limitations and exact commit: /mnt/storage/scratch/mosaic-lead/mvp3/planning-gate-fix-r1-report.md. No push, merge, runtime write, role authority expansion or product-acceptance claim.
|
||||
@@ -2064,3 +2064,5 @@ are never rewritten or removed; corrections are new entries.
|
||||
|
||||
2026-11-05 | codex-ux-skill-author | Vikunja #818 UX improvement skill author handoff | Candidate-02 three-file package personally authored; package/copy/patch/arithmetic/integrity checks passed; 19 synthetic behavioral samples and all 186 raw artifacts retained with candidate-01 issue/correction and limits. Exact manifest b93d7836ed7c70db62386192d6e1db5c4f867f841aaa7645c3c861def5a9b140; evidence /mnt/storage/scratch/mosaic-lead/skill-ux-improvement/author/run-20261105T022125Z/HANDOFF.md. Independent source review pending; no commit, app suites, forge/queue writes, installation, activation or publication.
|
||||
2026-11-04 | Mos, skill integrator | #818 / Gitea #1845 | Integrated exact independently approved ms-ux-improvement candidate02 into source;3hashes/validator/whitespace pass and author histories preserved. Separate commit from MVP; its activation target staysbd8120318. No install, activation, publication or live action.
|
||||
|
||||
2026-11-05 | implementation child | #797 MVP 3 planning coverage at 05fdfef9e, branch fix/mvp3-planning-797 | scalar coverage guidance and actionable PM notice ready for review; 228 tests pass, no skips; coder proxy-stall diagnosed read-only, D3–D5 reported; queue refused linked worktree; local commit only, no push/merge/runtime writes.
|
||||
@@ -740,7 +740,14 @@ export class Broker {
|
||||
const created = events.filter(e => e.kind === 'task.created' && e.actor_role === state.pm && e.body.request === state.request &&
|
||||
e.body.project_key === state.project && e.body.requirement_snapshot === state.snapshot &&
|
||||
events.some(p => p.id === e.body.create_receipt && p.body.operation === 'tracker.create.prepared'));
|
||||
if (!created.length || state.candidate.requirements.some(r => !created.some(e => e.body.requirement === r.id))) fail('request-evidence-required');
|
||||
const uncovered = state.candidate.requirements.filter(r => !created.some(e => e.body.requirement === r.id)).map(r => r.id);
|
||||
if (uncovered.length) {
|
||||
const error = new BusError('request-evidence-required',
|
||||
`Uncovered requirement ids: ${uncovered.join(', ')}. Each task_create cites exactly one requirement id from this product snapshot; prose does not count. Check existing create receipts, then create and assign tasks for the uncovered ids within the approved task limit. If coverage would exceed that limit, ask the owner to reconcile the plan before creating more tasks. A push or review cannot clear planning coverage; retry advance_request after coverage and assignment are complete.`);
|
||||
error.planningCoverage = true;
|
||||
throw error;
|
||||
}
|
||||
if (!created.length) fail('request-evidence-required');
|
||||
const tasks = [...new Set(created.map(e => e.subject))].sort(), evidence = created.map(e => e.id);
|
||||
if (state.phase !== 'planning' && digestPrd(tasks) !== digestPrd([...state.tasks].sort())) fail('request-evidence-required');
|
||||
let changes = false;
|
||||
@@ -5448,7 +5455,20 @@ export class Broker {
|
||||
const method = REQUEST_VERBS[request.verb];
|
||||
return Promise.resolve().then(() => this.#requests[method](s, structuredClone(args)))
|
||||
.then(result => { this.#secretCheck(result); if (request.verb === 'prd.submit') this.#scheduleRequests(); return result; })
|
||||
.catch(e => { throw this.#refused(s, e instanceof BusError ? e : new BusError(e.code ?? 'request-failed'), request); });
|
||||
.catch(e => {
|
||||
// advance rolls its transaction back on refusal. Publish the actionable PM
|
||||
// message afterwards, once per revision and coverage set, through the normal
|
||||
// bus route: the sandbox tool socket only exposes the refusal code.
|
||||
if (request.verb === 'request.advance' && e instanceof BusError && e.planningCoverage) {
|
||||
const state = this.#requestJournal.readRequest(s.business, args.request);
|
||||
const body = `Planning coverage for request ${state.request}, revision ${state.revision}:\n${e.reason}`;
|
||||
this.#store.transaction(() => {
|
||||
if (!this.#store.get('SELECT 1 FROM messages WHERE business=? AND from_role=? AND to_role=? AND body=?', s.business, NOTICE_ROLE, s.role, body))
|
||||
this.#continuationMessage(state, s.role, body, this.#continuationContext(state, s.role));
|
||||
});
|
||||
}
|
||||
throw this.#refused(s, e instanceof BusError ? e : new BusError(e.code ?? 'request-failed'), request);
|
||||
});
|
||||
}
|
||||
// Row 213 (C2-08, Case 2 design 5.1 M6): a creative role has no free-text decision channel. It
|
||||
// runs before the spec-cited branch below, so a cited raise refuses too.
|
||||
|
||||
@@ -56,7 +56,7 @@ export function createContinuations({ journal, services }) {
|
||||
({ intake_propose: 'Read intake_status and intake_projects, preserve the original request intent, and propose the exact resource plan with intake_propose. If the owner selected existing, select an accessible existing mapping. The owner confirms the plan; discovery needs no task or tracker effect.',
|
||||
submit_prd: 'Read the committed spec and discovery records. Finish the product PRD and requirements with write_record, then submit_prd with their exact records commit, path, version and ids. Use request_staff for missing technical help.',
|
||||
resolve_decision: 'Read the exact PRD candidate and requirements with intake_status(path), one selected path at a time. Resolve only the cited PRD decision through resolve_decision; ask for changes if evidence is incomplete.',
|
||||
task_create: 'Read list_requirements for this project and snapshot. Check existing task/create receipts before creating or retrying. Create and assign the scoped tasks, request_staff for coder/reviewer sessions, then advance_request.',
|
||||
task_create: 'Read list_requirements for this project and snapshot. Check existing task/create receipts before creating or retrying. Each task_create cites exactly one requirement id; every approved requirement needs a cited task, even when one description claims several. Create and assign the scoped tasks within the approved task limit; if coverage would exceed it, ask the owner to reconcile the plan. Request_staff for coder/reviewer sessions, then advance_request. Planning checks coverage and assignment; a push or review cannot clear missing coverage. Read the broker message naming uncovered ids after a refusal and correct the plan before retrying.',
|
||||
push_working: 'Check the assigned tasks and existing receipts. Implement the approved requirements, push_working, and request_review for each latest commit.',
|
||||
review_verdict: 'Fetch each current review request with fetch_review, run acceptance checks, before the verdict, write a docs/*.md verification document whose entire content is raw JSON (no Markdown fences or prose): {"version":1,"task_ref":"...","review_request":"...","commit":"...","results":"..."}. Results must be a nonempty string. Return review_verdict with a raw JSON body containing exactly {"verification_record":{"path":"...","commit":"..."},"results":"..."}, using the write_record receipt and matching results.',
|
||||
advance_request: 'Check the existing task, push and review evidence, then call advance_request at the current intake_status revision. Never claim a success phase.',
|
||||
|
||||
@@ -55,6 +55,55 @@ test('wrong-role and claimed state advances fail; planning needs actual task evi
|
||||
await assert.rejects(f.call('pm', 'request.advance', { ...args, phase: 'complete' }), /invalid-request/);
|
||||
await assert.rejects(advance(f), /request-evidence-required/);
|
||||
assert.equal(f.journal.readRequest('demo', f.request).phase, 'planning');
|
||||
const notice = f.call('pm', 'message.receive').find(m => m.body.startsWith('Planning coverage for request '));
|
||||
assert.match(notice.body, /Uncovered requirement ids: REQ-POMO-1\./);
|
||||
assert.match(notice.body, /A push or review cannot clear planning coverage/);
|
||||
});
|
||||
|
||||
test('five requirements need five cited tasks; prose and push do not cover missing ids; PM can correct the refusal', async t => {
|
||||
const f = await requestFixture(t);
|
||||
for (let i = 2; i <= 5; i++) {
|
||||
const id = `REQ-POMO-${i}`;
|
||||
f.args.records_commit = f.save(`requirements/${id}.json`, JSON.stringify({ recordVersion: 1, id, title: `Timer ${i}`, text: `Acceptance ${i}`, status: 'draft' }), 'pm.pm');
|
||||
f.args.requirement_ids.push(id);
|
||||
}
|
||||
const sub = await f.submit(); f.approve(sub); await f.activate(sub);
|
||||
const state = f.journal.readRequest('demo', f.request);
|
||||
const { createDigest, createMarker } = await import('../src/broker.mjs');
|
||||
const create = i => {
|
||||
const task_ref = `vikunja:2/${40 + i}`;
|
||||
const prepared = { op: `timer-task-${i}`, request: f.request, requirement: `REQ-POMO-${i}`, project_key: 'timer', requirement_snapshot: state.snapshot,
|
||||
project: 2, creator: 3, decision: null, view: { ids: { todo: 1, review: 2, done: 3 } }, labels: [], relations: [] };
|
||||
prepared.body = { title: 'Implements REQ-POMO-1..5', description: `All five requirements.\n${createMarker(prepared)}` };
|
||||
prepared.body_digest = createDigest(prepared.body);
|
||||
const { receipt } = f.bus.broker.prepareTaskCreate(f.caps.pm, prepared);
|
||||
let fields = { ...prepared.body, project_id: 2, bucket: 1, done: false, assignees: [], due_date: null, priority: 0 };
|
||||
const snapshot = () => ({ task_ref, fields, digest: createDigest(fields), updated: new Date().toISOString() });
|
||||
f.bus.broker.recordRecoveredCreate({ receipt, task_ref, snapshot: { ...snapshot(), creator: 3 } });
|
||||
fields = { ...fields, assignees: [5] };
|
||||
f.bus.broker.recordTask({ cap: f.caps.pm, business: 'demo', snapshots: [snapshot()], events: [{ kind: 'task.assigned', subject: task_ref, body: { role: 'coder', previous: [] } }] });
|
||||
return task_ref;
|
||||
};
|
||||
const tasks = [create(1)];
|
||||
f.bus.broker.recordPush(f.caps.coder, { task_ref: tasks[0], project: 'timer', branch: 'timer', ref: 'work/vikunja-2-41', commit: 'b'.repeat(40), previous: null });
|
||||
await assert.rejects(advance(f, 'partial'), { code: 'request-evidence-required' });
|
||||
const notices = () => f.store.all("SELECT * FROM messages WHERE from_role='_bus' AND to_role='pm' AND body LIKE 'Planning coverage for request %'");
|
||||
assert.equal(notices().length, 1);
|
||||
assert.match(notices()[0].body, /Uncovered requirement ids: REQ-POMO-2, REQ-POMO-3, REQ-POMO-4, REQ-POMO-5\./);
|
||||
assert.match(notices()[0].body, /ask the owner to reconcile the plan/);
|
||||
assert.match(notices()[0].body, /A push or review cannot clear planning coverage/);
|
||||
assert.ok(f.call('pm', 'message.receive').some(m => m.id === notices()[0].id), 'notice reaches PM through the bus');
|
||||
assert.deepEqual(f.journal.readRequest('demo', f.request), state, 'refusal consumes no lifecycle revision or task evidence');
|
||||
await assert.rejects(advance(f, 'partial'), { code: 'request-evidence-required' });
|
||||
assert.equal(notices().length, 1, 'retries do not flood the same coverage notice');
|
||||
tasks.push(create(2));
|
||||
await assert.rejects(advance(f, 'partial'), { code: 'request-evidence-required' });
|
||||
assert.equal(notices().length, 2, 'changed coverage produces an updated notice');
|
||||
assert.match(notices()[1].body, /Uncovered requirement ids: REQ-POMO-3, REQ-POMO-4, REQ-POMO-5\./);
|
||||
for (let i = 3; i <= 5; i++) tasks.push(create(i));
|
||||
const next = await advance(f, 'partial');
|
||||
assert.equal(next.phase, 'implementation');
|
||||
assert.deepEqual(next.tasks, tasks, 'complete coverage and assignment advance without pushes for the other tasks');
|
||||
});
|
||||
|
||||
test('validated PRD transition acknowledges discovery and schedules planning without flooding', async t => {
|
||||
@@ -64,6 +113,11 @@ test('validated PRD transition acknowledges discovery and schedules planning wit
|
||||
await f.bus.requestLifecycle.recover();
|
||||
assert.ok(f.journal.effects('demo', f.request).some(e => e.op === discovery.op && e.stage === 'committed' && e.result.evidence));
|
||||
assert.ok(intents(f).some(e => e.result.intent.step === 'task_create'));
|
||||
const planning = intents(f).find(e => e.result.intent.step === 'task_create');
|
||||
const body = messages(f, planning.result.intent.key)[0].body;
|
||||
assert.match(body, /Each task_create cites exactly one requirement id/);
|
||||
assert.match(body, /a push or review cannot clear missing coverage/);
|
||||
assert.match(body, /approved task limit/);
|
||||
});
|
||||
|
||||
test('exact launch refusal is durable and returned by intake status, with bounded retries', async t => {
|
||||
|
||||
@@ -46,7 +46,7 @@ const PRIORITY = { type: "integer", minimum: 0, maximum: 5, description: "Priori
|
||||
|
||||
// action -> [tool name, description, properties, required]
|
||||
const TASK_TOOLS = {
|
||||
"task.create": ["task_create", "Create a tracker task. Every task cites the request it came from and a requirement id.", {
|
||||
"task.create": ["task_create", "Create a tracker task. Every task cites its request and exactly one requirement id. Planning needs a cited task for every approved requirement; descriptions claiming several do not count. Stay within the approved task limit; ask the owner to reconcile a coverage/limit conflict. A push cannot clear missing planning coverage; read the broker message naming uncovered ids after advance_request refuses.", {
|
||||
title: str("Task title, one line"),
|
||||
project: { type: "integer", minimum: 1, description: "Optional tracker board assertion; must match the confirmed request binding" },
|
||||
description: str("Task description"),
|
||||
|
||||
@@ -26,6 +26,9 @@ test("the PM gets its task verbs and the reads, and no launch", (t) => {
|
||||
const create = tools.find((x) => x.name === "task_create");
|
||||
assert.deepEqual({ ...create.parameters.properties.project, description: undefined }, { type: "integer", minimum: 1, description: undefined });
|
||||
assert.deepEqual(create.parameters.required, ["title", "request", "requirement"]);
|
||||
assert.match(create.description, /exactly one requirement id/);
|
||||
assert.match(create.description, /A push cannot clear missing planning coverage/);
|
||||
assert.match(create.description, /approved task limit/);
|
||||
assert.deepEqual(tools.filter((x) => x.parameters.properties.project).map((x) => x.name), ["intake_propose", "task_create", "read_spec", "write_record", "raise_decision"]);
|
||||
for (const tool of tools) {
|
||||
assert.equal(tool.parameters.type, "object");
|
||||
|
||||
@@ -18,6 +18,15 @@ order. You don't write the product's code and you don't review it.
|
||||
and reviewer staff from the chief, and call `advance_request` with the current
|
||||
revision and a stable operation id. It checks real task, push, review and
|
||||
closure evidence for the next fixed transition; it accepts no claimed phase.
|
||||
Planning requires a cited task for every approved requirement and coder
|
||||
assignment for each task. Each task cites exactly one requirement id;
|
||||
descriptions claiming several requirements do not count as coverage.
|
||||
On incomplete coverage, read the broker message naming the uncovered ids,
|
||||
check existing create receipts, and create and assign the missing tasks
|
||||
within the approved task limit. If coverage would exceed that limit, ask
|
||||
the owner to reconcile the plan before creating more tasks. A push or review
|
||||
cannot clear missing planning coverage. Retry `advance_request` after
|
||||
coverage and assignment are complete; push evidence is checked afterwards.
|
||||
Finish the artifacts and close each task against its latest approve verdict,
|
||||
then advance to delivery through the chief and assistant.
|
||||
|
||||
|
||||
Reference in new issue
Block a user