Files
PaperClipAI/packages
d6745ac8bd feat(server): receive and apply the Paperclip Cloud onboarding seed
Paperclip Cloud collects a mission, a first agent and a first task during
signup and pushes them into the tenant stack at activation, as
`POST /api/companies/:companyId/onboarding-seed`. Nothing served that
route, so the tenant answered 404, Cloud recorded the push as
unacknowledged, and a freshly activated stack opened on the empty
first-run wizard with the answers the customer had already given
stranded on the Cloud side.

This adds the receiver.

Contract. The body carries a content-addressed `revision` plus optional
`mission`, `agent` (`name`, `role`) and `firstTask` (`title`,
`details`). It is customer free text, so it is validated and bounded in
`packages/shared` and never read from a header — the
`x-paperclip-cloud-*` set is the trusted identity envelope, derived
server-side from host plus verified domain records, and mixing user
content into it would undermine exactly that property. A test plants a
mission on a cloud header and asserts the body wins.

Application. The mission becomes the company-level goal, the agent
becomes the company's first hire (its free-text role lands on `title`;
the structural `role` stays `ceo`, which is what the org chart and the
default-instructions lookup read), and the first task becomes an issue
in the Onboarding project assigned to that agent — the same shapes the
first-run wizard produces, so a seeded stack and a manually onboarded
one are indistinguishable afterwards. The seeded agent is created on
`claude_local`, mirroring the teams-catalog default for agents created
server-side without a human running an environment test first, and
overridable via `PAPERCLIP_ONBOARDING_SEED_ADAPTER_TYPE`.

Idempotency. Cloud retries until it gets a 2xx and treats any 2xx as
"the tenant holds this content", so the new `company_onboarding_seeds`
table records the applied revision along with the goal, agent and issue
it produced. Replaying a revision that already matches is a successful
no-op; a later revision — the customer edited their answers — updates
those three rows in place rather than creating a second agent and a
second task. The record is written last, after every other write has
landed, so a partial application cannot present itself as acknowledged.

Ordering, not eventual consistency. Everything is applied before the
200 is sent. The tests read the database immediately after the response
with no waiting and no polling, so a lazy receiver fails them on a fast
machine as well as a slow one. That matters because the redirect into
the tenant dashboard is gated on this acknowledgement.

Tested with `pnpm typecheck` (whole workspace) and
`vitest run server/src/__tests__/onboarding-seed-route.test.ts
server/src/__tests__/openapi-routes.test.ts` — 14 passing, including the
happy path, replay, revision update, project reuse, the header-injection
guard, the field bounds, and the 403 for a caller without access to the
company. The suite runs against embedded Postgres, so the new migration
is exercised too.

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 18:21:43 -07:00
..