Porting api.forsale
Every api.forsale route, matched to the operation the generated sales brand serves it with, adapted to a new shape, or decided, and each of the sales kernel's 76 behavior tests matched to the line that carries it. Then the kernel's money path and api.forsale's own probe suite, passing against the generated brand.
sales's wordsdeal is a jobaccount is a subjectgig is a taskcloser is a memberseller is a clientseller is a buyer
api.forsale was built by hand: a deal state machine, a seller's mandate naming who works each role, a gate that refuses a close below the floor, and settlement by role. The sales example generates the same business from three files. This page holds the two side by side, the acceptance test for Money and Outcome.
| Measure | Of | Result | Read it |
|---|---|---|---|
The sales kernel's behavior tests: core, gig-result, gig-lifecycle, sdk and demo, at 52fa49b | 76 tests | 59 ported as they are · 10 adapted · 7 decided | Its behavior tests |
| api.forsale's routes, the kernel's and the front door's | 22 routes | 12 served as its clients call them · 6 adapted · 4 decided | Route by route |
| The kernel's money path, its numbers unchanged | 10 rows | 10 passed · 0 failed | The suite |
| api.forsale's own probe suite | 10 probes | 10 passed · 8 unchanged, 2 rewritten for a door that sells | The suite |
The kernel's money path, then api.forsale's probes. Two probes expected the door's answers from before it sold, a free price and a stub OFFER, and read the brand's own now:
npx services.dev verify --suite https://api.forsale/verify # api.forsale's own probe suite, two probes rewritten for a door that sellsapi-forsale@2 · 10 probes · from https://api.forsale/verify · against http://localhost:8787
✓ keyless-ok GET /deals 200 OK
✓ known-empty-1 GET /deals?stage=none 200 EMPTY
✓ known-empty-2 GET /deals?role=none 200 EMPTY
✓ known-forbidden-1 GET /deals?scope=tenant 403 BLOCKED
✓ known-forbidden-2 GET /deals?scope=settlement 403 BLOCKED
✓ pricing GET /pricing 200 · model outcome: 5% of the closed value at won · rewritten from model free
✓ offer GET /offer 402 OFFER · orders with a scoped payment token · rewritten from offer-stub
✓ card GET /.well-known/agents.json 200
✓ openapi GET /openapi.json 200
✓ llms GET /llms.txt 200
10 passed · 0 failed · 8 probes unchanged, 2 rewritten from the door's answers before it soldport.ts reads each api.forsale route, finds the operation that serves it, and calls the running brand. A resource's .md, .json and .html twins count as one route.
| Mark | Means |
|---|---|
| ✓ | The generated operation serves the route as api.forsale's clients call it |
| adapted | The operation exists with another shape, and the row says what a client changes |
| decision | Another generated surface serves it, or it stays the brand's own, and the row says which |
node -e "import('./port.ts')" # each api.forsale route: its mark, the operation, and what the sales brand answered✓ POST /deals service.order 402 OFFER
✓ GET /deals/:id job.get 404 EMPTY not_found
decision POST /deals/:id/advance step.propose a generated surface · the gate advances the graph and no caller drives it: AI proposes each step, and a closer proposes in their own session
✓ POST /deals/:id/signals job.signal 404 EMPTY not_found
adapted POST /deals/:id/gig-result task.propose · task.complete · task.release POST /gigs/:id/propose, /negotiate, /complete and /release, in the closer's own session on closers.deals: one call per act, never a callback the seller's key could send
adapted GET /deals/:id/settlement job.ledger GET /owner/deals/:id: the owner's read, with the ledger; the seller reads the charge on GET /deals/:id
adapted POST /deals/:id/reengage job.again POST /deals/:id/again: a new deal with from: on its record, from any ended deal
adapted POST /sell service.order POST /deals, then GET /deals/:id with wait: one order, and the same answer whether AI closes or a closer does
✓ GET /deals job.list 200 EMPTY
adapted GET /gigs task.feed GET /gigs on closers.deals, in a member's session: the teasers, with pay, before a claim
✓ GET /offer service.offer 402 OFFER
✓ GET /pricing brand.pricing 200 application/json
✓ GET /openapi.json brand.openapi 200 application/json
✓ GET /llms.txt brand.llms 200 text/markdown
✓ GET /.well-known/agents.json brand.card 200 application/json
✓ GET /index.json brand.home 200 application/json
✓ POST /mcp brand.mcp 200 initialize
✓ GET /mcp brand.mcp 405 · POST only, as MCP over HTTP asks
adapted POST /api/waitlist service.waitlist POST /deal/waitlist { email }: one waitlist per Service, which a not_live answer names
decision GET /icp.json public/icp.json kept as a brand route · a file of the brand's own in public/, served as it is
| Kernel | Decision | Why |
|---|---|---|
POST /deals/:id/advance | never a call: the gate | AI proposes each step, and a closer proposes in their own session; no caller drives the graph |
| A commission set per deal, clamped to the seller's cap | the closer's 10% is the brand's, in one line | A gig's pay is fixed at post, and a closer reads one rate on every teaser |
/, /health and POST /demo/reset | the brand's own routes, and test mode | A test key and test clocks are the sandbox, so nothing needs resetting |
| The setter's and nurturer's fees, charged to the seller | funded from api.forsale's 5% | The seller pays nothing on effort: who funds the setter |
| The one-way door: a deal a person touched stays a person's for every role | a closer's span, and her first call on a revived step | Each step's escalate is its own, so a deal a setter booked under humans_from still meets with AI. An order that wants a person on every role sets setter: 'person' with humans_from: 0 |
flows.ts reads each of the kernel's tests and names the line that carries it:
node -e "import('./flows.ts')" # the sales kernel at 52fa49b: 76 tests in core, gig-result, gig-lifecycle, sdk, demo76 tests · 59 ✓ · 10 adapted · 7 decision
## ✓ · 59
## adapted · 10
## decision · 7The adapted rows, whole:
$ node -e "import('./flows.ts')" -- --mark adapted
core#the one-way door: a human-track deal stays human for every subsequent role
→ deal.ts roles.closer.span, revive: { first_call: '24h' } · a closer's span stays hers from the meeting to the end, and a revived step comes back to her first; a deal a setter booked under humans_from still meets with AI, since each step's escalate is its own, so an order that wants a person on every role sets setter: 'person' with humans_from: 0
core#fees settled at gig fulfillment appear as a line item in the all-in economics
→ deal.ts roles.setter.pay · the setter's $250 is api.forsale's cost, debited from its balance at the held meeting, so the seller pays nothing on effort; from: 'buyer' runs the kernel's way
gig-result#refuses to re-engage a deal that is not closed_lost
→ POST /deals/:id/again · again takes any ended deal, a won one for a renewal; a live deal answers not_terminal
sdk#createDeal throws GateRefusedError with the violations payload on an out-of-bounds mandate
→ the SDK · BLOCKED comes back as a typed value, policy_invalid with its fill list, not a thrown error
sdk#getDeal throws NotFoundError for an unknown id
→ the SDK · not_found, as a typed value
sdk#settlement throws ConflictError on a deal that is not closed_won
→ the deal's ledger · a lost deal reads +$0.00; there is no settlement call to refuse
port.ts is a list: each route of the brand you're porting, the operation that serves it, and its mark. The run above calls every row against the generated brand and prints what it answered, so a ✓ is an answer, never a claim. flows.ts does the same for the brand's tests. One row of each mark:
export default [
{ route: 'POST /deals/:id/signals', op: 'job.signal', mark: '✓' },
{ route: 'POST /deals/:id/reengage', op: 'job.again', mark: 'adapted', moved: 'POST /deals/:id/again: a new deal with from: on its record, from any ended deal' },
{ route: 'POST /deals/:id/advance', op: 'step.propose', mark: 'decision', decision: 'a generated surface', why: 'the gate advances the graph and no caller drives it: AI proposes each step, and a closer proposes in their own session' },
] as const satisfies readonly Port[]What the brand gets shows the same operations on every surface.