Hand a project to a client
End an agency job cleanly. Eject to plain provider code, transfer the project to the client's organisation on free view-only seats, and hand over evidence.
Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
At the end of a job you give the client one of two things: code that no longer needs MainSDK, or a project they can read in their own organisation, plus evidence that the app meets the standards you agreed. This page shows the three steps and what each one needs. Plan about an hour for the eject or the transfer, and longer if the client wants an assurance bundle.
What you hand over
| What you hand over | Command | Milestone | Status |
|---|---|---|---|
| Plain provider code, no MainSDK dependency | mainsdk eject --all | 0.3 | Planned, beta. Recipes for email, pay, auth and db are proposed; the final list is not decided. |
| The project in the client's organisation, with history, policy and launch reports | mainsdk project transfer | 0.7 | Planned |
| Client people reading the project on free view-only seats | mainsdk org invite --role viewer | 0.7 | Planned |
| Signed evidence an assessor can verify | mainsdk assure, mainsdk assure verify | 0.7 | Planned, beta |
You need
- A clean git working tree and a passing test suite.
- For the transfer: a Teams organisation, and the client's organisation name.
- For the evidence: the standards or industry pack the client asked for (for example
pci-dss-4.0.1).
1. Leave MainSDK (optional)
Choose this when the client wants no dependency on MainSDK. Plan first, then run it. See Eject.
npx mainsdk eject --all --dry-run
npx mainsdk eject --allYou should see: your tests pass with the same count as before, main.yaml, the gates and every mainsdk and @mainsdk/* dependency are gone, and EJECT-REPORT.md lists each guarantee the client no longer gets. Give the client that report.
Be honest with the client about coverage. The spec says eject recipes cover the reference architecture drivers first, and the interfaces with recipes in 0.3 are still an open question. The proposal is email, pay, auth and db. Any other interface has no recipe yet, so plan to rewrite those calls by hand, or use step 2 instead.
2. Transfer the project to the client
Choose this when the client keeps using MainSDK. The transfer moves the project with its history, policy and launch reports in one step and keeps the project ID. The client's people read it on free view-only seats; client viewers never count as active developers (see Free in the app, paid in the Cloud).
npx mainsdk org invite ana@client.example --role viewer
npx mainsdk project transfer prj_01JC8Q… --to client-co --keep-maintainer--keep-maintainerkeeps one maintainer seat for your agency, so you can still fix things.--bill-addons-to clientputs any Teams add-ons on the client's invoice instead of yours.
You should see: the project in client-co with its history, the same project ID, and the client's people able to read it without a charge.
3. Hand over evidence
Run mainsdk assure against the standards the client asked for, before you eject or transfer. Running it and reading the results is free.
npx mainsdk assure --standard pci-dss-4.0.1
npx mainsdk assure verify .mainsdk/assure/latest.bundleYou should see: the failing controls with owners and fixes, then a verified signature on the bundle, which holds the results, SBOM, VEX, policy test results, drill records and exceptions. Hand the client the bundle. Whether mainsdk assure still runs after a full eject is not decided yet.
Verify
Before the handover, run the check and save its output with the evidence.
npx mainsdk check --env prodA pass exits 0. After an eject, run your test suite instead, since the check is gone with MainSDK.
If it fails
| Code | Cause | Fix |
|---|---|---|
MAIN-ASSURE-0003 | mainsdk assure pointed at a URL found a tampered /.well-known/mainsdk.json (signature invalid, exit 2). | Do not hand it over. Rebuild the record from your own repo and run assure again. |
MAIN-CONFIG-0004 | A literal secret in main.yaml, found before you hand the repo over. | Move it to a secret: reference and rotate the secret. |
Run npx mainsdk explain <code> for the cause and fix offline.
For agents
Goal: prepare this project for handover to a client.
Read: docs/guides/eject.md, docs/specs/eject.md, docs/specs/teams-plan.md, docs/specs/assure.md.
Touch only: files that eject rewrites, EJECT-REPORT.md, and .mainsdk/assure/.
Do: run mainsdk assure and keep the bundle; run mainsdk eject --all --dry-run and show the plan to the owner;
run the real eject only after the owner approves; never run project transfer without the owner.
Verify: the test suite passes with the same count, and npx mainsdk assure verify .mainsdk/assure/latest.bundle succeeds.
Never weaken, skip or loosen a check to make it pass.Next
Open questions
Was this page helpful?