Files
stack/docs/plans/2026-11-03_request-lifecycle-implementation.md

52 KiB
Raw Permalink Blame History

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.

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.