265 lines
52 KiB
Markdown
265 lines
52 KiB
Markdown
# Request Lifecycle Implementation Plan
|
||
|
||
> **For agentic workers:** Use `superpowers:executing-plans` to implement this plan task by task. Steps use checkbox syntax. This planning assignment authorizes no implementation, delegates or review runs; a later implementer follows the lead's execution assignment.
|
||
|
||
**Goal:** Carry an unattached user request through project selection, discovery, approved product requirements, real implementation, independent review and an openable artifact without user message relay.
|
||
|
||
**Architecture:** Extend the existing broker's append-only SQLite evidence and project records, with a small fixed lifecycle reducer and durable effect receipts. Reuse the configured software factory's repository and installed tracker board, existing role sessions, local working repositories and review protocol. There is no new workflow service, reusable creation mandate or general authorization framework.
|
||
|
||
**Tech Stack:** Existing Node.js ESM, `node:sqlite`, local Git, Vikunja adapter, typed harness tools and signed-in Console. Preserve the pinned harness and existing sandbox/processing/launch controls.
|
||
|
||
**Spec:** `docs/plans/2026-11-03_request-to-product-design.md`, stages 2–4; owner continuation and this assignment narrow the first supported route. Base: `7e849a7b0b635d34950a5c5c2e4b78c4238fe651`. Tracking context: Vikunja #797 / Gitea #1839. The separate `2026-11-03_request-intake-prd-addendum.md` is proposed exact implementation text, not an assertion that a new PRD version was approved.
|
||
|
||
## Purpose and boundary
|
||
|
||
This is a builder check and executable implementation plan, not evidence of delivered product behavior. Stage 1 is independently owned: consume `decision.preview.console`, `decision.resolve.console` and `/api/decisions/:id/confirmation|resolve` after integration. Do not redesign its option transport, grant summaries or confirmation. Intake-specific forms and reads belong here; generic decision buttons do not.
|
||
|
||
The first supported new product reuses a user-selected, already declared factory code source and an already installed tracker board in that business's configured tracker tree. It creates a distinct business project key, separate local code checkout, separate local records repository, chat-project association and local working repository. Sharing a tracker board or upstream does **not** share product requirement authority. Product tasks explicitly name their product and approved snapshot. This uses the design's authorized-resource reuse route and avoids requiring new external repositories, accounts, tracker projects or broad service credentials for an offline HTML result.
|
||
|
||
A resource plan displays the source project/commit, upstream and existing fork state, tracker root/selected board, paths, roles, mode and exact original intent before confirmation. Choosing autonomous or guided and confirming that displayed plan authorizes that one bootstrap. Reusing an existing project has a picker and confirmation bound to its current mapping. Unsupported resources produce a precise setup wait; silently substituting Stack requirements or a different board is forbidden.
|
||
|
||
## Global invariants
|
||
|
||
- Discovery, PRD drafting and their decisions have no fabricated task or requirement. Pre-task decisions use existing `blocking: false`; the intake journal owns the wait.
|
||
- Preserve the original `human.input` event and message bytes. That is a current technical identifier; new prose uses “user.” Identifier migration belongs to #798 / Gitea #1840.
|
||
- Only authenticated owner controls can select a mode, approve bootstrap, confirm generated intent or change mode. First support is the existing business owner Console identity; delegated-user creation is not implied.
|
||
- Original intent becomes `spec/north-star.md` only through the protected executor of the exact user-confirmed plan. Agent assumptions stay in agent-authored PRD records. Existing `writeHumanFile`/`business.human`/`confirmed` identifiers remain compatible.
|
||
- Broker reads and tracker creation use **one** project-scoped approved snapshot. Draft records, a JSON `status: accepted`, a bare requirement ID or a chat project alone confer no authority.
|
||
- Autonomous PRD approval uses a distinct configured technical arbiter, initially CTO; guided approval uses the user. Bind the decision to exact PRD/requirement content, current spec and mode revision. No PM self-approval.
|
||
- Existing role grants, messaging graph, processing state, launch revocation, quotas, tool limits, credential scopes and all hard waits remain enforced. A new `project.bootstrap` action stays user-gated; do not add it to `ROUTED` or any role authority list.
|
||
- No protected push, remote publication, merge, deployment, external message or spend is needed to deliver the reviewed local HTML. Such later actions keep their existing gates.
|
||
- Effects reserve an operation key before execution; a retry reconciles evidence. Append corrections and supersessions; never rewrite run records, events, decisions, prior snapshots or receipts.
|
||
- Managed product paths are under the configured `dataRoot`; reject links, overlaps, collisions and unrelated partial contents. Existing projects keep their paths. No new repository-root files.
|
||
- The original planning assignment changed only its two planning artifacts. A later execution assignment authorizes only the named task and its checks; it does not authorize production setup or unrelated authority edits. The controller records integration and session evidence centrally.
|
||
|
||
## Review focus
|
||
|
||
1. Two requests sharing a tracker board or the same `REQ-*` spelling must never borrow one another's product authority (Task 5).
|
||
2. A restart after an external task create but before its receipt must recover the existing task, including a subsequently closed task, without a second POST (Tasks 5 and 8).
|
||
3. A valid selection followed by a changed organization, intent or mode must not execute stale bootstrap/approval work (Tasks 2 and 4).
|
||
4. A delivered message followed by a dead role session must not strand the continuation; launch refusal must stay visible and resume only through allowed launch paths (Tasks 6 and 8).
|
||
5. An approved commit followed by a new push, or an artifact containing links/traversal, must never export unreviewed or host-local bytes (Task 7).
|
||
|
||
## Builder findings and first-route decisions
|
||
|
||
| Existing seam at base | Consequence and chosen integration |
|
||
| --- | --- |
|
||
| `broker.mjs` `project.create` only inserts a chat grouping with optional `repo` | Create/associate the business mapping and records/code resources separately; retain `project.create` semantics. |
|
||
| `organizationWriter().write` checks `organization-projects-protected` before and after serialization | Add a dedicated exact-add operation; do not introduce an escape flag into ordinary organization writes. |
|
||
| `runtime.mjs` `fixedAtStart`/`sameFixed` rejects changed roots on reload | Admit only completed, receipt-backed managed project additions; retain restart/refusal behavior for arbitrary roots/credential/bot changes. |
|
||
| `cli/src/config.mjs` `trackerFor` refuses two project files with `tracker.project` | Keep the single factory tracker root. Product `.mosaic/project.json` omits `tracker.project`; the approved resource binding selects an installed board from that root's tree. |
|
||
| `tasks/src/verbs.mjs` and `broker.mjs` separately call `loadRequirements()` | Replace product lookups with a shared resolver. Keep the static loader only for the explicitly bound legacy Stack catalogue. |
|
||
| `staff.request` requires `task_ref`; PM has no launch authority | Add an intake alternative to that typed request, still PM → chief. Before a PM is running, use assistant → chief and the existing launch tool. |
|
||
| `typedTools` gates requirements on tracker/task authority and freezes project enums at launch | Expose scoped discovery reads without a fabricated task; new code/review sessions must be built after project activation. |
|
||
| `task.create` documents that a lost create answer cannot currently be reread | Add durable operation markers and reconciliation before claiming lifecycle idempotency. |
|
||
| `checkPush`/`recordPush` do not bind a task's product requirement scope to `push.project` | Add that binding for scoped tasks before accepting bundle bytes and at record time. |
|
||
| `project` records accept direct `docs/NAME.md`, not `docs/prd/NAME.md` | Product PRDs use `docs/prd-VERSION.md`; do not reuse the static catalogue's path schema. |
|
||
| Gitea grants can provision identities/forks, but role Vikunja scopes cannot create projects | Reuse the installed tracker board and existing roles for v1. Do not invent a Vikunja provisioning capability. |
|
||
|
||
The configured factory is a selected declared project, not a hard-coded `stack` key. For new products clone its local code source at the displayed commit into `<dataRoot>/products/<business>/<project>/code`, preserve history, and write a local bootstrap commit changing only `.mosaic/project.json` to the new ID and explicitly displayed permitted project vars. Do not copy role overrides or broaden limits from the template. The artifact lives at `products/<project>/pomodoro.html` in the product clone; the factory checkout is never edited. Records default to `<dataRoot>/projects/<business>/<project>` and have no remote. Working storage remains `workingRepo(dataRoot,business,project)`.
|
||
|
||
Reuse the factory's validated upstream URL in the new mapping. Existing actor forks may be reused only after the existing identity/grant checks; a new product key does not inherit `publish` rights. Local `push_working` and `fetch_review` need no new remote grant. If remote publication is later requested, the chief uses the existing Gitea `credential.mint` path for the product scope and a separate `git.push.remote` approval for the exact commit. No remote provisioning is a hidden dependency of the first offline delivery.
|
||
|
||
## Producer/consumer contract
|
||
|
||
All shapes below are proposed strict, versioned interfaces. Reject extra keys at public boundaries. IDs use existing repository validators; hashes are SHA-256, Git commits use the existing full-OID validator. Broker-derived identity is never an agent argument. `request` is the original `human.input` event ID, not the chat message ID.
|
||
|
||
```text
|
||
ResourcePlan = {
|
||
version:1, business, request, project, name, chat_project:null|id,
|
||
mode:"autonomous"|"guided", organization_revision,
|
||
source:{project, commit, remote:null|httpsURL,
|
||
forks:[{instance,url,grant:null|id,state:"verified"|"not-granted"}]},
|
||
code_root, records_root,
|
||
tracker:{root:number, project:number},
|
||
staff:{pm, technical_reviewer, coder, reviewer, chief},
|
||
intent:{text, sha256, source_message, generated:boolean},
|
||
project_vars:{...validated project-layer vars, no tracker.project},
|
||
existing:boolean
|
||
}
|
||
PlanBinding = {plan:ResourcePlan, digest, decision, choice}
|
||
RequestState = {
|
||
version:1, request, business, revision:number, source_message,
|
||
thread:null|id, session:null|id, owner, pm,
|
||
phase:"selection"|"bootstrap"|"discovery"|"prd-review"|"planning"|
|
||
"implementation"|"review"|"delivery"|"delivered"|"cancelled",
|
||
project:null|key, chat_project:null|id, mode:null|"autonomous"|"guided",
|
||
mode_revision:number, plan_digest:null|hash, spec_commit:null|oid,
|
||
snapshot:null|id, tasks:[task_ref], delivery:null|id,
|
||
question:null|{id,text,options:[{key,text}],answer:null|event_id},
|
||
candidate:null|PrdCandidate, selection:null|{decision,bindings:[PlanBinding]},
|
||
route:null|"existing",
|
||
wait:null|{kind:"decision"|"setup"|"launch"|"effect",
|
||
code, subject, resume_phase, observed_revision}
|
||
}
|
||
PrdCandidate = {
|
||
version:1, request, project, prd_version:"1.0", records_commit,
|
||
prd_path:"docs/prd-1.0.md", prd_blob,
|
||
requirements:[{id,title,path,blob}], spec_commit, mode_revision
|
||
}
|
||
ApprovedSnapshot = {
|
||
version:1, id, business, project, prd_version, records_commit,
|
||
prd_path, prd_blob, spec_commit, requirements:[{id,title,text,path,blob}],
|
||
authors:[{definition,instance,commit}],
|
||
approval:{decision, resolution_seq, by, via}, mode_revision,
|
||
origin:"product"
|
||
}
|
||
BuiltinSnapshot = {
|
||
origin:"builtin", id, business, project, prd, version,
|
||
requirements:[{id,title}], catalog_sha256
|
||
}
|
||
CatalogSnapshot = ApprovedSnapshot | BuiltinSnapshot
|
||
TaskScope = {project_key, requirement_snapshot, requirement}
|
||
EffectReceipt = {
|
||
version:1, business, request, op, kind, digest,
|
||
stage:"prepared"|"committed"|"uncertain"|"refused",
|
||
result:null|object, code:null|string
|
||
}
|
||
Delivery = {
|
||
version:1, id, business, request, project, snapshot,
|
||
task_ref, commit, review_request, verdict,
|
||
artifact:{path,blob,sha256,bytes,mime:"text/html"},
|
||
verification:{record_path,record_commit}, message
|
||
}
|
||
```
|
||
|
||
`request_events` stores `{seq,business,request,revision,kind,actor_role,actor_run,at,body}` with unique `(business,request,revision)` and append-only triggers. `body` is a validated transition payload; `readRequest` reduces it into `RequestState`. Creation is unique per source event. `request_effects` stores the `EffectReceipt` fields plus sequence/time, unique `(business,request,op,stage)`; an operation's digest/kind cannot change. `request_snapshots` stores immutable `ApprovedSnapshot` JSON keyed by ID, and `request_snapshot_events` appends activation/revocation with project, revision, snapshot and cause. `request_project_events` stores `{seq,business,project,revision,kind,at,body}` with unique `(business,project,revision)` and append-only triggers. It holds shared project mode changes and attachment evidence. Mode writes compare the displayed project mode revision as well as the request revision in one transaction; another attached request cannot race a stale mode write through. These are operational records, not system configuration. All indexes/projections are rebuildable. Never repurpose mutable `status` files as authority.
|
||
|
||
Only fixed effect kinds are supported: `bootstrap`, `prd-submit`, `prd-activate`, `task-create`, `continuation`, `delivery`. Intermediate bootstrap steps have deterministic keys under their parent's op. No agent-facing “execute arbitrary effect” API.
|
||
|
||
## Task 1: Durable pre-task intake and selection boundary (stage 2)
|
||
|
||
**Files:** Create `packages/bus/src/request-lifecycle.mjs`, `packages/bus/src/request-store.mjs`, `packages/bus/src/migrations/3n-3o.sql`, `packages/bus/tests/request-intake.test.mjs`. Modify `packages/bus/schema.sql`, `packages/bus/src/store.mjs`, `packages/bus/src/broker.mjs`, `packages/bus/src/runtime.mjs`, `packages/business/src/vocabulary.mjs`.
|
||
|
||
**Interfaces:** `createRequestStore(store)` returns `readRequest(business,request)`, `appendRequest({business,request,expectedRevision,kind,body,actor})`, `prepareEffect(receipt)` and `finishEffect(receipt)`. These are host-only. `createRequestLifecycle({journal,services})` returns `open(identity,args)`, `get(identity,args)`, `resume(business,request)` and `recover()`. Broker verbs: `request.open({request})`, `request.get({request})`, `request.projects({request})`, and PM-only `request.propose({request,revision,op,source_project,project,name,tracker_project})`. Opening captures intake; proposing validates a concrete resource plan and creates the mode decision. The PM chooses a sensible source/name from declared resources; the user sees and confirms them before any bootstrap effect. PM may open a reachable user-origin request through existing `message.send` authority; owner may read its own request. Opening twice returns the same state.
|
||
|
||
- [ ] **Red:** Add `opens_without_task_or_requirement`, `duplicate_open_is_same_request`, `rejects_foreign_input`, `non_owner_cannot_select` and `reopen_store_reduces_same_state`. Assert no `task.created` and null `task_ref`/`requirement_ref` on intake decisions.
|
||
- [ ] **Red verification:** Run `node --test packages/bus/tests/request-intake.test.mjs`; new exports/verbs must fail before implementation.
|
||
- [ ] **Green:** Add append-only tables, offline migration and schema digest/version updates using existing migration checks. Keep earlier migrations immutable; if another integrated change takes `3o`, rebase this single new migration rather than editing its predecessor.
|
||
- [ ] **Green:** Add `project.bootstrap` to both closed `ACTIONS` lists (`business/src/vocabulary.mjs` and `bus/src/broker.mjs`) only; update their equality/closed-vocabulary tests, never either `ROUTED` list. `request.propose` builds the three exact keys `autonomous`, `guided`, `existing` in a user-routed nonblocking decision, with `approvalChoice: "autonomous"`. This decision is an enumerated selection: guided/existing are valid selections, not denials inferred from `approvalChoice`. Its lifecycle executor uses the immutable choice-to-plan bindings, never generic action consumption. Before opening it, prepare and validate the displayed resource proposals; if prerequisites are missing, open one setup wait instead. Cancellation uses existing withdrawal, creates no resources, and retains the source request.
|
||
- [ ] **Green:** For `existing`, return only accessible software project mappings; exclude library projects, unknown tracker associations and another business. A picker stages a candidate binding; a subsequent ordinary decision confirms that exact association. Existing matching requirements still require an explicit scope choice by PM; no text matching silently establishes authorization.
|
||
- [ ] **Verification:** Run `node --test packages/bus/tests/request-intake.test.mjs packages/bus/tests/schema.test.mjs packages/bus/tests/store.test.mjs packages/business/tests/hard-waits.test.mjs`. Expect zero failures; the hard-wait test intentionally gains only `project.bootstrap`.
|
||
- [ ] **Commit:** Commit this task's named implementation/tests only after green checks, when the execution assignment authorizes local commits.
|
||
|
||
## Task 2: Protected project bootstrap that becomes usable at runtime (stage 2)
|
||
|
||
**Files:** Create `packages/business/src/project-bootstrap.mjs`, `packages/bus/src/request-bootstrap.mjs`, `packages/business/tests/project-bootstrap.test.mjs`, `packages/bus/tests/request-bootstrap.test.mjs`. Modify `packages/business/src/organization.mjs`, `packages/project/src/index.mjs`, `packages/bus/src/runtime.mjs`, `packages/bus/src/broker.mjs`, `packages/cli/src/config.mjs`, `packages/cli/src/launcher.mjs`, `packages/bus/src/process.mjs`, `packages/seat/src/working.mjs`. Test existing `packages/business/tests/library-projects-protected.test.mjs`, `packages/project/tests/init-failure.test.mjs`, `packages/cli/tests/launcher.test.mjs`.
|
||
|
||
**Interfaces:** `prepareBootstrap({business,request,sourceProject,project,name,mode,dataRoot,organizationRevision,tracker,staff,intent}) -> Promise<ResourcePlan>` in `request-bootstrap.mjs`; `project-bootstrap.mjs` validates the exact mapping/path addition for the organization writer. `executeBootstrap({binding,journal,services}) -> Promise<{project,chat_project,spec_commit,organization_revision}>`. `organizationWriter().addProject(id,{revision,project,entry,receipt},actor)` is a new narrow host method; `entry` is only `{root,repo,remote?}`. `activateManagedProject({business,project,receipt,organization_revision}) -> Promise<void>` is a runtime-only operation. Host IPC `request.admission({business,project}) -> {ready,revision,code}` exposes readiness to the launcher through `process.mjs`; scope it to the host business and fail closed if unavailable. `initializeWorking({dataRoot,business,project,root,env?}) -> Promise<{repo,commit}>` is a new host-only export in `seat/src/working.mjs`, using its existing repository lock and private safe `seed` helper, with no work ref or push authority. Bootstrap calls it after the code seed commit and journals the returned root commit. Existing `initProject`, `writeHumanFile` and `project.create` remain compatible.
|
||
|
||
- [ ] **Red:** Test `bootstrap_connects_all_resources`: inspect the actual organization file, code `.mosaic/project.json`, records HEAD/spec author, chat association, tracker tree/board, working-repo seed and next launch's project list. Test stale organization/plan, changed source commit, symlink ancestor, path overlap, preexisting unrelated data and partial initialization. Assert every ordinary organization writer still rejects project changes.
|
||
- [ ] **Red verification:** Run `node --test packages/business/tests/project-bootstrap.test.mjs packages/bus/tests/request-bootstrap.test.mjs`.
|
||
- [ ] **Green:** Under the existing organization lock/receipt discipline, factor a private exact-add writer used only by `addProject`; reject replacement/removal, library entries, unrelated key changes and a mismatched plan receipt. Compare both input and serialized document. A plan with a stale revision requires a new preview/confirmation, never silent rebasing.
|
||
- [ ] **Green:** Journal path reservations, clone/seed, mapping addition, records init, exact-intent save, chat association and runtime activation separately. `initProject` currently requires a declared project: prepare the code seed, publish the reserved mapping, then initialize records under that mapping. Mark it pending in broker admission until every step passes. For interrupted local Git effects compare deterministic paths, stored before/after commits and exact bytes; retain ambiguous contents and return `bootstrap-uncertain`. Do not remove unrelated paths or rewind commits.
|
||
- [ ] **Green:** Save original displayed intent through `writeHumanFile` as the authenticated owner recorded by the decision; generated wording requires its own exact preview/confirmation. Record confirmation event and resulting spec commit. Keep `writeRecord` unable to write `spec/`.
|
||
- [ ] **Green:** Preserve the single tracker root: the product file omits `tracker.project`; persist its selected board in the immutable plan and admission projection. `trackerFor` must ignore reserved managed additions until committed and verify product IDs after completion, including after a fresh boot. Never create a second tracker adapter per product or fall back to the root board on failure.
|
||
- [ ] **Green:** Extend runtime reload only for receipt-backed additive roots within `<dataRoot>/products`, already excluded from secret locations by the Credentials data-root fence. Revalidate the full business, unchanged credentials/bots/libraries/roles, prior mappings and completed plan. Admit the mapping to broker only after readiness; rebuild readiness from receipts on restart. Launcher already rereads business in `check`; additionally refuse a reserved/unready product from cloning until admitted. Existing arbitrary reload changes still answer `restart: true` or refuse. Existing sessions receive no new project tool enum; Task 6 schedules a fresh suitable coder/reviewer session.
|
||
- [ ] **Verification:** Run the new tests plus `node --test packages/business/tests/library-projects-protected.test.mjs packages/project/tests/init-failure.test.mjs packages/cli/tests/launcher.test.mjs`. Kill/reopen the fixture after mapping write and after spec commit; recovery must reach the same mapping/spec exactly once, without changing factory files.
|
||
- [ ] **Commit:** Commit this bounded bootstrap path after green checks. Do not execute bootstrap on any running instance.
|
||
|
||
## Task 3: Intake Console controls and guided answers (stage 2)
|
||
|
||
**Files:** Create `packages/webui/src/request-http.mjs`, `packages/webui/src/public/requests.js`, `packages/webui/tests/request-http.test.mjs`, `packages/webui/tests/request-browser.test.mjs`. Modify `packages/webui/src/serve.mjs`, `packages/webui/src/console-bus.mjs`, `packages/webui/src/bus.mjs`, `packages/webui/src/public/index.html`, `packages/webui/src/public/chat.js`, `packages/webui/tests/browser.test.mjs`, `packages/bus/src/request-lifecycle.mjs`, `packages/bus/src/broker.mjs`.
|
||
|
||
**Interfaces:** `requestHTTP(req,res,{path,requestRead,requestWrite,session,origin})` handles `GET /api/requests/:request`, `GET /api/requests/:request/projects`, and `POST /api/requests/:request/answer|attach|mode`. The reads call `request.get.console` and `request.projects.console`; the writes call owner-only broker verbs `request.answer.console({request,revision,question,op,text})`, `request.attach.console({request,revision,project,op})`, `request.mode.console({request,revision,mode_revision,mode,op})`. All return `RequestState`; authenticated owner reads also support `GET /api/requests/:request/draft` calling `request.draft.console({request})` and returning the candidate PRD text and full requirement records at its bound commit. That read is produced by Task 4 and is not a generic file-read endpoint. Attachment stages a confirmation decision, not a mapping write. `request.question({request,revision,op,text,options?})` is PM-only and records an outstanding question ID; text answers are not PRD approvals.
|
||
|
||
- [ ] **Red:** Test signed-in owner selection, authorized picker, guided free-text answer and stale revision. Assert denied origins, expired sessions, readers/testers/granted nonowners cannot mutate intake. No generic decision endpoint/schema changes are part of this task.
|
||
- [ ] **Red verification:** Run `node --test packages/webui/tests/request-http.test.mjs packages/webui/tests/request-browser.test.mjs`.
|
||
- [ ] **Green:** Mount the intake panel from the source chat request. Render server-selected plans and waiting reason as escaped data; use Stage 1's authoritative decision options and existing confirmation endpoints for decisions. New text/attach/mode writes enforce the same signed-in owner, same-origin, writable-Console and bounded-body rules. Extend `CONSOLE_VERBS` with only these new `.console` verbs, using `consoleCall` and its existing verified `(provider,iss,sub)` transport, never an owner CLI proxy selected by client input. Check `s.console` and the configured owner explicitly; the existing `#human` CLI guard rejects Console capabilities and must not be weakened. Agent intake reads keep their unsuffixed names. Mode change is an explicit user control with expected revision; it invalidates pending autonomous approval/dispatch admissions at the next action.
|
||
- [ ] **Green:** Guided answers become attributed `human.input` events linked to question and request, with duplicate op protection. They do not silently rewrite the original spec. Persist answers; enqueue PM continuation after commit. “Cancel” withdraws the selection or appends cancellation; an already-created project remains evidence, never auto-deleted.
|
||
- [ ] **Verification:** Run the two new suites and `node --test packages/webui/tests/browser.test.mjs`. Update its exact script-list expectation for `requests.js`. Test reload after lost answer response: read the stored answer/op first, no blind repost. Existing grant summaries and confirmation regressions remain green after Stage 1 integration.
|
||
- [ ] **Commit:** Commit intake-specific UI and tests; do not fold generic Console decision work into this commit.
|
||
|
||
## Task 4: Exact-version product PRD approval and activation (stage 3)
|
||
|
||
**Files:** Create `packages/project/src/prd.mjs`, `packages/bus/src/requirement-catalog.mjs`, `packages/project/tests/prd.test.mjs`, `packages/bus/tests/prd-activation.test.mjs`. Modify `packages/bus/src/request-lifecycle.mjs`, `packages/bus/src/broker.mjs`, `packages/bus/src/runtime.mjs`, `packages/project/src/index.mjs`, `packages/project/src/read.mjs`, `packages/webui/src/request-http.mjs`, `packages/webui/src/public/requests.js`.
|
||
|
||
**Interfaces:** `readPrdCandidate({business,project,dataRoot,request,prd_version,records_commit,prd_path,requirement_ids,mode_revision}) -> Promise<PrdCandidate>` reads bounded committed regular blobs using existing Git safety rules. `prd.submit({request,revision,op,prd_version,records_commit,prd_path,requirement_ids}) -> {candidate,decision}` is PM-only, requires `record.write`, and internally raises the existing `prd.approve` action. `createRequirementCatalog({journal,readSpec,builtinBinding})` returns `resolve({business,project,snapshot?}) -> Promise<CatalogSnapshot>` and `activate({request,decision}) -> Promise<ApprovedSnapshot>`; the latter is host-only. `Broker.requirementsFor(cap,{project_key,requirement_snapshot?,requirement?}) -> Promise<CatalogSnapshot>` is the shared, authenticated consumer used in Task 5.
|
||
|
||
- [ ] **Red:** Test autonomous PM → configured CTO approval, PM/self-role denial, guided user approval, changed draft, changed requirement blob, stale spec, changed mode, revoked snapshot, wrong business and forged `status: accepted`. Assert no task exists before activation.
|
||
- [ ] **Red verification:** Run `node --test packages/project/tests/prd.test.mjs packages/bus/tests/prd-activation.test.mjs`.
|
||
- [ ] **Green:** Bound this first route to 64 requirements, 32 KiB PRD text, 16 KiB per requirement text and 256 KiB aggregate candidate content; reject oversized candidates before decision creation. PM writes `docs/prd-1.0.md` and `requirements/REQ-POMO-1.json` etc. with existing `record.write`; validate strict requirement records, IDs/titles, unique paths and exact content at one reachable records commit. Candidate hashes are server-read, never trusted agent assertions. At submission and activation, compare each selected path at current HEAD with its candidate blob; unrelated record commits do not invalidate it, but an edited selected draft requires a new submission. Read requirement JSON via a private committed-blob helper; do not broaden the public spec editor's file access.
|
||
- [ ] **Green:** In autonomous mode raise `prd.approve` with `domain: "technical"`, current confirmed spec citation and target bound to the candidate; require a distinct authorized arbiter instance/definition. Missing or self-routing technical arbiter becomes a setup wait, not self-approval. Guided mode deliberately raises the same gated action without a routing spec citation so it goes to the user; its separate candidate binding still includes and revalidates the current spec. Display exact draft, requirements and version through the request panel before approval.
|
||
- [ ] **Green:** After resolution, activation rereads the immutable candidate, current intent, mode and role authority (not whether the approving run is still alive) and checks matching approving choice/resolution. Append the snapshot and activation receipt atomically with the continuation. A changed candidate requires a new decision. Repeated activation returns the same ID. A mode/spec change revokes outstanding admissions; prior task evidence remains immutable. Owner `request.mode.console` and existing spec-save notices trigger this invalidation. Support owner-only `prd.revoke.console({project,snapshot,revision,reason,op})` for explicit revocation through `POST /api/requests/:request/revoke`, with the same Console controls and whitelist integration as Task 3; pending work waits for replacement authority.
|
||
- [ ] **Verification:** Run both new suites and `node --test packages/project/tests/read.test.mjs packages/project/tests/human-file.test.mjs packages/business/tests/spec-review-contracts.test.mjs`. Fresh runtime must resolve the same approved snapshot solely from committed records and journal evidence. Preserve existing decision closure/notification behavior.
|
||
- [ ] **Commit:** Commit activation/catalogue code and tests after green checks. Do not claim the Stack PRD amendment itself has already been approved.
|
||
|
||
## Task 5: One scoped requirement validator and recoverable tracker creates (stage 3)
|
||
|
||
**Files:** Create `packages/tasks/src/create-recovery.mjs`, `packages/tasks/tests/scoped-requirements.test.mjs`, `packages/tasks/tests/create-recovery.test.mjs`, `packages/bus/tests/scoped-push.test.mjs`. Modify `packages/tasks/src/verbs.mjs`, `packages/tasks/src/adapter.mjs`, `packages/tasks/src/startup.mjs`, `packages/tasks/src/sync.mjs`, `packages/bus/src/broker.mjs`, `packages/cli/src/config.mjs`, `packages/cli/src/git-socket.mjs`, `packages/bus/src/process.mjs`, `packages/bus/src/runtime.mjs`.
|
||
|
||
**Interfaces:** Broker `requirements({project_key?,requirement_snapshot?})` and host method `requirementsFor` both use Task 4's catalog. Add optional `{project_key,requirement_snapshot,op}` to existing `task.create`; product callers require all three plus existing `request` and `requirement`. Existing numeric `project` remains the tracker board ID, distinct from `project_key`. `reconcileCreate({ctx,receipt}) -> Promise<{state:"committed",task_ref}|{state:"uncertain",code}>` reads through sync authority, never writes as sync. Adapter exposes host-only `reconcileCreates(business)` and `inspectProject(business,project)` for startup/recovery/admission. Add host-only `Broker.recordRecoveredCreate({receipt,task_ref,snapshot})`: verify the prepared identity/body/target and write the missing task event once, with original attribution. Never expose this method as a socket verb.
|
||
|
||
- [ ] **Red:** Test two products on one board with the same requirement ID but different snapshots; draft/unapproved/revoked/wrong-project/wrong-board/ambiguous requests; omission by product callers; and explicitly bound legacy Stack compatibility. Assert broker and task creation return/accept the same exact snapshot, including its version and content commit.
|
||
- [ ] **Red:** Test lost response after tracker create, restart before receipt, duplicate PM calls, failure during label/assignment follow-up, a closed matching task, edited marker, duplicate marker and complete scan failure. Assert at most one create POST after an uncertain result; a negative/partial scan alone never authorizes another POST.
|
||
- [ ] **Red verification:** Run `node --test packages/tasks/tests/scoped-requirements.test.mjs packages/tasks/tests/create-recovery.test.mjs packages/bus/tests/scoped-push.test.mjs`.
|
||
- [ ] **Green:** Remove the private product ID set from `verbs`; call `Broker.requirementsFor` both when planning and immediately before create admission. Resolve tracker board from the request's confirmed binding, recheck installed tree membership and role/session authority, and append a prepared op before network I/O. Record `TaskScope`, source PRD version/commit, spec commit and op in `task.created` evidence. Task filtering/trail must distinguish product keys when IDs collide.
|
||
- [ ] **Green:** Bind the built-in catalogue only to a startup-validated Stack project, passed explicitly by `bootConfig` as `{business,project,tracker_project}`. Confirm its code project identity and repository-owned Stack PRD/catalogue relationship; do not infer it from the first project, any `REQ-*` prefix or the board number. Unscoped legacy calls preserve the existing `{prd,version,requirements}` response and may use that binding only when their source request has no intake record or conflicting product binding. Any intake request, including selection/discovery, requires an approved product snapshot before task creation. The internal builtin wrapper records the baked catalogue version/hash and never fabricates a product approval decision. New product calls never fall back. Keep `packages/tasks/requirements.json` and its existing approved-PRD consistency test unchanged in this task.
|
||
- [ ] **Green:** Use a stable visible description suffix `Mosaic operation: <op>; request: <request>; scope: <project_key>; snapshot: <snapshot>` added by the adapter. Journal the complete create-body digest and target board. On uncertainty, scan all pages including completed tasks with the sync reader; adopt exactly one matching marker only after validating board, creator bot and original create fields. A changed/ambiguous/missing marker stays `task-create-uncertain` with a reconciliation wait. Resume labels/relations through existing read-before-write checks. Record recovered create attribution from the original prepared identity, never from a fabricated live capability.
|
||
- [ ] **Green:** Add product binding to git admission: extend `checkPush(cap,task_ref,project)` and pass `project` from the git socket through `process.mjs`/runtime before reading bundle bytes; repeat in `recordPush`. A scoped task can push/review/export only from its bound project. Preserve legacy recorded task handling without inventing product scope for old evidence.
|
||
- [ ] **Verification:** Run new tests plus `node --test packages/tasks/tests/requirements.test.mjs packages/tasks/tests/verbs.test.mjs packages/tasks/tests/tree.test.mjs packages/tasks/tests/adapter.test.mjs packages/cli/tests/git-socket.test.mjs`. Exercise an actual file-backed Store close/reopen and persistent fake tracker; an in-memory reducer replay is not a restart test.
|
||
- [ ] **Commit:** Commit scoped validation and create recovery only after all listed checks pass.
|
||
|
||
## Task 6: Typed role continuations through implementation and review (stage 3)
|
||
|
||
**Files:** Create `packages/bus/tests/request-continuation.test.mjs`, `packages/harness/tests/request-tools.test.mjs`, `packages/cli/tests/request-launch.test.mjs`. Modify `packages/bus/src/request-lifecycle.mjs`, `packages/bus/src/broker.mjs`, `packages/bus/src/runtime.mjs`, `packages/cli/src/launcher.mjs`, `packages/bus/src/process.mjs`, `packages/harness/src/tools.mjs`, `packages/harness/src/runner.mjs`, `packages/seat/src/sandbox.mjs`, and duty contracts `roles/pm.md`, `roles/chief-of-staff.md`, `roles/cto.md`, `roles/coder.md`, `roles/reviewer.md`, `roles/assistant.md` in the later implementation only.
|
||
|
||
**Interfaces:** Extend `staff.request`/`request_staff` to exactly one of `{task_ref,...}` or `{request,...}`, keeping `{roles,note?,decision?}` and PM-only `message.send` checks. Add typed tools `intake_open`, `intake_status`, `intake_projects`, `intake_propose`, `intake_question`, `submit_prd`, `advance_request` mapping to the already specified verbs. Add `request.advance({request,revision,op}) -> RequestState` as PM-only: it verifies evidence for the next fixed transition, never accepts a claimed success state. Lifecycle `resume` records one durable continuation intent per `(request,revision,target,step)`; runtime `recover` sweeps unfinished intents on startup and after relevant events, plus a bounded 30-second runtime reconciliation pass for missed notifications. It processes only these fixed request stages, deduplicates by durable continuation key and does not add another scheduler service.
|
||
|
||
- [ ] **Red:** Test pre-task staff request, correct chief launch, wrong-role refusal, actual PM/CTO/coder/reviewer tool manifests, absent tracker during discovery, a running role with an old project enum, revoked launch, full limits and a dead role after message delivery. Existing assistant message restrictions must still reject direct PM/reviewer coordination.
|
||
- [ ] **Red verification:** Run `node --test packages/bus/tests/request-continuation.test.mjs packages/harness/tests/request-tools.test.mjs packages/cli/tests/request-launch.test.mjs`.
|
||
- [ ] **Green:** Preserve assistant → chief → PM intake and chief → assistant delivery. A PM uses discovery records and scoped catalog tools without creating a task; after activation it creates/assigns the task using Task 5 and requests coder/reviewer staff. Chief launches existing configured roles under the normal `launch` tool. Do not hire a PM/CTO/reviewer through the empty chief `hireable` list or add PM `role.launch` authority.
|
||
- [ ] **Green:** Continuations name source request/revision, current phase, evidence IDs and exact next typed operation. Decision closure, approved snapshot, task/create/review evidence, session end, successful launch and cleared prerequisites trigger reconciliation. Append continuation intent and outgoing message atomically; link acknowledgement to a validated transition/effect, not `message.read`. On a dead run with no progress, deliver the same continuation key to its successor and recheck existing effect receipts. Persist attempt/refusal state; avoid message floods and busy retry loops.
|
||
- [ ] **Green:** If the chief or worker is unavailable, store the exact existing launch refusal and surface it in the intake panel. A new relevant prerequisite revision/session event retries through the same chief path. No synthetic unlimited capability, direct privileged worker spawn or user command relay. A role that lacks the new product in its session manifest waits for its current turn to drain; chief uses its existing permitted end/relaunch path for that role, never interrupting unrelated active work. The next launch uses admitted mappings and real `ensureCheckouts`. Use Task 2's host-only `request.admission` read for readiness.
|
||
- [ ] **Green:** Update typed schemas with product snapshot fields and intake alternatives; expose reads for eligible PM/CTO before task existence and writes only to existing authorized roles. Ensure runner/MCP dispatch and sandbox module mounts include the new imported modules without mounting credentials/configuration. Update role duty prose to describe discovery, exact approval, resume and artifact completion; no `.json` authority grants are needed on this first route.
|
||
- [ ] **Verification:** Run new tests plus `node --test packages/harness/tests/tools.test.mjs packages/harness/tests/mcp-server.test.mjs packages/harness/tests/pi-session.test.mjs packages/business/tests/workflow-roles.test.mjs packages/business/tests/chief-of-staff.test.mjs`. Inspect manifests for both configured supported harnesses; no generic broker escape tool.
|
||
- [ ] **Commit:** Commit continuation/tool/contract updates after green checks and the normal execution review gate. This planning task makes none of those edits.
|
||
|
||
## Task 7: Export a reviewed artifact and deliver its evidence (stage 4)
|
||
|
||
**Files:** Create `packages/seat/src/artifact.mjs`, `packages/seat/tests/artifact.test.mjs`, `packages/bus/tests/request-delivery.test.mjs`, `packages/webui/tests/artifact-http.test.mjs`. Modify `packages/bus/src/request-lifecycle.mjs`, `packages/bus/src/runtime.mjs`, `packages/bus/src/broker.mjs`, `packages/webui/src/request-http.mjs`, `packages/webui/src/public/requests.js`, `packages/webui/src/bus.mjs`, `packages/harness/src/tools.mjs`.
|
||
|
||
**Interfaces:** `readReviewedArtifact({repo,commit,path,maxBytes}) -> Promise<{bytes,blob,sha256,mime}>` accepts a trusted working-repo path and a regular committed blob, max 1 MiB for this route. PM typed `deliver_request` → `request.deliver({request,revision,op,task_ref,verdict,path,verification_record}) -> Delivery`; `verification_record` is `{path,commit}` in the records repo. Host resolves all other evidence. `GET /api/requests/:request/artifact/:delivery` returns a protected attachment from broker-authorized delivery evidence. Broker read `request.artifact.console({request,delivery}) -> {mime,filename,base64,sha256}` is owner-only and bounded to 1 MiB decoded. Add it to `CONSOLE_VERBS`; set `consoleCall`'s output cap to 2 MiB for this verb only (its current 1 MiB cap cannot hold the base64 payload), below the existing bus Client 4 MiB response cap; the HTTP layer decodes bytes and enforces request ownership again. It is not a harness tool.
|
||
|
||
- [ ] **Red:** Test approved artifact bytes, stale verdict after newer push, changes verdict, wrong-project task, path traversal/symlink/submodule/oversize, forged verification record and duplicate export/notification. Assert exact blob/hash, task scope, review request and commit equality.
|
||
- [ ] **Red verification:** Run `node --test packages/seat/tests/artifact.test.mjs packages/bus/tests/request-delivery.test.mjs packages/webui/tests/artifact-http.test.mjs`.
|
||
- [ ] **Green:** Coder builds/tests the actual file, commits locally in its sandbox clone and calls `push_working`. A distinct reviewer fetches the exact work commit with `fetch_review`, runs the acceptance checks, writes an attributed verification record through `record.write` and emits `review_verdict`. PM closes with that verdict through existing `task.close`; `request.deliver` requires both closed-task evidence and latest approving review, and confirms the verification record cites the same commit/results and was committed through `record.write` by the reviewing role, proven by the broker action receipt rather than text claiming a reviewer identity.
|
||
- [ ] **Green:** Export only the committed working-repo blob through bounded safe Git operations, never a workspace path. Append delivery evidence and chief notification intent atomically. Link it to the original thread/session; chief and assistant relay automatically through existing messaging rules. Delivery read remains available even if the assistant is temporarily unavailable, but the outstanding notification stays visible and recoverable. Exactly one delivery ID/message intent per op.
|
||
- [ ] **Green:** Serve HTML as a download (`Content-Disposition: attachment`, `nosniff`, private/no-store); never execute product HTML inside the privileged Console origin or relax its CSP. The downloaded file runs locally offline. The evidence panel links PRD/snapshot, task, code commit, review and verification records. It labels reviewed delivery separately from landing/deployment.
|
||
- [ ] **Verification:** Run the three suites; open the downloaded file in the isolated acceptance browser with networking disabled, then confirm its bytes match the reviewed Git blob. Existing `task.close` refuses stale/changes verdicts as before.
|
||
- [ ] **Commit:** Commit artifact delivery only after green checks.
|
||
|
||
## Task 8: Three-path integration, crash recovery and honest acceptance (stage 4)
|
||
|
||
**Files:** Create `packages/cli/tests/request-lifecycle.test.mjs`, `packages/cli/tests/fixtures/request-agent.mjs`, `packages/cli/tests/fixtures/pomodoro-request.json`, `packages/webui/tests/request-lifecycle-browser.test.mjs`, `packages/cli/live/request-lifecycle.mjs`. Reuse `packages/tasks/src/fake.mjs`, the file-backed broker runtime, real project Git helpers, launcher/session fixtures and `packages/webui/tests/browser.mjs`. Do not reuse `tasks/tests/world.mjs`'s permissive PM launch grant as shipped authority.
|
||
|
||
**Interfaces:** Fixture request file is `{source_event,source_message,text,source_sha256,provenance}`. The integration fixture exposes `start()`, `kill(point)`, `restart()` and `evidence()` over the same on-disk instance and persistent fake service. Scripted role adapters invoke the real typed tools and create real commits; they do not insert successful lifecycle records directly. Live runner takes an explicitly named isolated instance and evidence output directory, validates resources/owner authorization first, and never defaults to .53/.54 or production.
|
||
|
||
- [ ] **Red:** `autonomous_original_request_delivers_offline_html`: submit the original request, select/confirm autonomous once, then let role adapters run through PRD approval, real tracker task, code/test/commit, independent role review and export. Assert zero user message relays, shell commands and task creation before snapshot activation.
|
||
- [ ] **Red:** `guided_answers_and_exact_approval`: answer a short question via Console, reject a draft, revise, approve exact content and deliver. `attach_existing_and_revise_missing_requirement`: attach to an authorized existing product; test both matching approved snapshot and discovery/revision when none fits; create no extra project. Also test cancellation and mode change during approval.
|
||
- [ ] **Red verification:** Run `node --test packages/cli/tests/request-lifecycle.test.mjs packages/webui/tests/request-lifecycle-browser.test.mjs`.
|
||
- [ ] **Green:** Build the fixture with actual shipped role definitions and valid isolated organization/tracker credentials; fake only external services and model responses. Use real task field validation, local repositories, git socket/bundles, typed manifests, launcher admission, separate role/run identities and signed-in HTTP/browser actions. The coder fixture writes working HTML, not a README describing how the user should build it.
|
||
- [ ] **Green:** Crash a separate process after selection resolution, mapping write, spec commit, PRD resolution, tracker POST, working push, review verdict and export-before-notification. Restart the actual broker/runtime against the same Store/repositories/fake-service data. Assert identical IDs/commits, one project/task/artifact, unchanged historical rows and automatic continuation. Include uncertainty where the remote outcome cannot be proven: a visible wait is correct; a duplicate write or success claim is not.
|
||
- [ ] **Green:** Check proposed product defaults only after their product PRD approval: 25-minute work, 5-minute break, start/pause/reset, phase-end cue, no network/external assets. Browser tests control time to verify transitions, pause/resume/reset and cue after a user gesture. Assert downloaded HTML is self-contained and usable offline. Those defaults are agent proposals, not invented prior owner requirements.
|
||
- [ ] **Verification:** Run `node --test packages/bus/tests/*.test.mjs packages/tasks/tests/*.test.mjs packages/project/tests/*.test.mjs packages/business/tests/*.test.mjs packages/cli/tests/*.test.mjs packages/harness/tests/*.test.mjs packages/seat/tests/*.test.mjs packages/webui/tests/*.test.mjs` in the repository's supported Node/browser environment. Record failures, skips and environment limitations explicitly; isolate a flaky/preexisting failure before claiming an unaffected suite green. Document/reference/whitespace checks also run before each final integration commit.
|
||
- [ ] **Verification:** Run the separate live runner only in a later explicitly authorized isolated configured instance with actual model credentials/role routes. It must use the same production code and real independent role sessions. Retain request, activation, task, commit, test, review and artifact receipts. Fixture success demonstrates integration/recovery, not live model quality or deployment readiness. No production setup or external resource creation is authorized by this planning assignment.
|
||
- [ ] **Commit:** Commit the acceptance harness and evidence references after applicable checks. Report fixture and live results separately; if live prerequisites are absent, report the exact remaining setup gate instead of marking stage 4 fully accepted.
|
||
|
||
## Protected setup, policy deltas and handoff
|
||
|
||
No `.json` role expansion is necessary for the selected first route: PM already writes records/tasks; CTO resolves technical decisions; coder pushes local working refs; reviewer issues verdicts; chief launches; assistant relays. The unavoidable implementation boundary is a new user-gated `project.bootstrap` action plus its narrow executor and protected project-map writer. The PRD addendum explicitly states that routing and scope. Other new verbs are bounded implementations of existing record/message/task/PRD authority or authenticated owner controls, not new permission grants. Reviewed duty-contract edits and corresponding schema/tool tests are still necessary.
|
||
|
||
Missing model account/route, PM/CTO/reviewer role, tracker sync credential or installed board is a real foundation setup refusal. The supported route reuses those declared resources; it must not create new service accounts by pretending a project-mode answer granted `account.create` or `credential.mint`. Show `{service,instance,missing_scope_or_resource,decision,can_execute,resume_condition}` without secrets. For existing Gitea grant needs, use the chief's established `credential.mint` request and concrete Console confirmation; existing bounded grant scope may apply only where already configured. For missing Vikunja token issuance, existing `credentials.issue` accepts a user-provided token through the protected credentials control; it is **not** an automatic project/token provisioner. If no installed board exists, state `tracker-install` and that the current executor cannot provision it. That is a protected foundation setup blocker, not delivery completion. Do not ask the user to edit files, forward role messages or run decision commands as the normal lifecycle.
|
||
|
||
Implement in Task order except that Task 3 may follow Task 4 while waiting for Stage 1 integration. Tasks 4/5 share the catalog contract; integrate together before enabling product task creation. Keep the feature unavailable until bootstrap admission, scoped validation and typed continuations all exist; no prompt-only partial release. The shortest end-to-end milestone is one product, one scoped task, one code commit/review and one downloaded HTML artifact. Multiple products share the same machinery; reusable mandates, automatic external project provisioning and broader artifact types are deferred.
|
||
|
||
The checked-in design names the original pomodoro request but does not preserve its verbatim text/source event. The lead must supply a read-only export of that source for the final acceptance fixture; do not relabel a reconstructed prompt as the original. Fixture development can use an explicitly labeled representative request meanwhile. No other builder choice requires a fresh owner decision: source project and board are selected from actual configured resources in the displayed initial plan. Exact PRD amendment adoption remains a separate versioned approval record; this document is not that record.
|
||
|
||
Lead provenance update, 2026-11-03: the owner's original incident quotation is
|
||
preserved at `/mnt/storage/scratch/tmp/mosaic-797-original-request-provenance.json`.
|
||
It contains the exact pomodoro wording, the original input/message IDs reported
|
||
by the owner, a SHA-256 of the wording and the source conversation item. Use it
|
||
for the original-wording acceptance fixture and preserve its provenance fields.
|
||
It is an owner-supplied quotation, not an exported runtime database row or live
|
||
acceptance evidence. Fixture and later live-run reports must keep that distinction.
|