docs(server): pin the no-first-task contract on the cloud seed path

PAP-67 r17.4. #11101 added a server-owned onboarding first-task path
(`POST /issues` with `onboardingFirstTask: true`, board actor + zero
issues) that stamps `ONBOARDING_FIRST_TASK_ORIGIN_KIND` and races safely
on the partial unique index `issues_onboarding_first_task_uq`. The seed
receiver creates an unstamped issue, so if it ever created the first task
on the cloud walk the company would get a *second*, greeting-less task
rendered as a user bubble — silently, because the uq index only guards
origin-stamped rows.

The seed Cloud sends is mission-only (`agent`/`firstTask` are never
populated by the signup wizard; a paperclip-cloud `node:test` pins it),
so `firstTaskTitle` is null on the cloud path and this branch stays
inert. Document that contract at the branch it protects; the branch is
retained for the endpoint's documented body.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
TonioandPaperclip committed 2026-08-13 18:21:44 -07:00
1 parent caa0db4692
commit d8052a1742
1 file changed
+16
+16
View File
@@ -224,6 +224,22 @@ export function onboardingSeedService(db: Db) {
// 3. First task → an issue in the Onboarding project, assigned to the
// lead so the dashboard opens with work on it.
//
// No-first-task contract (PAP-67 r17.4): on the Cloud walk this branch
// never runs. The seed Cloud sends is mission-only — `agent` and
// `firstTask` are unpopulated by the signup wizard and a paperclip-cloud
// `node:test` in `src/onboarding/` pins that — so `firstTaskTitle` is
// null here and the first task stays owned by the tenant's own
// server-owned onboarding path (`POST /issues` with
// `onboardingFirstTask: true`). That path is the only one that stamps
// `ONBOARDING_FIRST_TASK_ORIGIN_KIND` and races safely on the partial
// unique index `issues_onboarding_first_task_uq`. If this receiver ever
// created the first task on the cloud walk it would produce a *second*,
// unstamped one: no agent-authored greeting, the brief rendered as a
// right-aligned user bubble, and two onboarding tasks — silently,
// because the uq index only guards origin-stamped rows. The branch is
// retained for the endpoint's documented body contract, but the
// mission-only seed is what keeps it inert on the cloud path.
let issueId = existing?.issueId ?? null;
if (firstTaskTitle) {
if (await issueStillExists(companyId, issueId)) {