Cross-repo Copilot Workspace tasks fail in a predictable way: the model invents an endpoint that “should exist,” updates one service, and leaves the other on the old contract. Spec-driven tasks flip that — OpenAPI/AsyncAPI and fixture contracts are the source of truth; acceptance criteria name the services and the verify commands; agents may only implement symbols that already appear in the spec (or in a explicitly approved spec PR).
⚡ TL;DR: Every Workspace task starts with a machine-readable contract + acceptance checklist. Generate typed clients from OpenAPI before coding. Forbid greenfield paths not in the spec. Run contract tests across service boundaries in CI. Humans merge spec changes first, then implementation tasks. Related: Cursor rules, AI PR reviewers, Express production APIs.
Task brief that survives microservice reality
## Task: Add idempotent refund endpoint
### Spec
- OpenAPI: `services/payments/openapi.yaml#/paths/@/v1/refunds`
- Event: `refund.completed.v1` in `asyncapi.yaml`
### Acceptance
- [ ] Handler matches generated types from OpenAPI
- [ ] Contract test `refunds.contract.test.ts` green against mock + sandbox
- [ ] Orders service consumes `refund.completed.v1` without schema drift
- [ ] No new paths outside the OpenAPI file in this PR
### Verify
```bash
pnpm --filter @acme/payments test:contract
pnpm --filter @acme/orders test:contract -- --grep refund
</code></pre>
<pre><code>
```markdown
❌ Bad task: "wire refunds between payments and orders somehow"
Generate clients — don’t let the model invent them
# ✅ Spec first → types → implementation
pnpm openapi-typescript services/payments/openapi.yaml -o packages/payments-client/src/types.ts
pnpm --filter @acme/payments-client build
// orders/src/refunds.ts
import type { paths } from "@acme/payments-client/types";
type RefundBody = paths["/v1/refunds"]["post"]["requestBody"]["content"]["application/json"];
export async function requestRefund(body: RefundBody) {
// ✅ Only fields that exist on the generated type
return paymentsFetch("/v1/refunds", { method: "POST", body });
}
// ❌ Model-invented client
await fetch("https://payments/api/refundOrder", { body: JSON.stringify({ order: id }) });
Boundary rules inside the Workspace prompt
Encode service ownership so the agent does not “helpfully” edit both sides of a domain boundary in one chaotic diff — or if it must, require two stacked PRs.
RULES:
- You may edit only: services/payments/** and packages/payments-client/**
- Spec PRs are separate: changes to openapi.yaml need label spec-change
- Do not add routes absent from openapi.yaml
- Prefer extending existing handlers over new micro-services
Mirror these as Cursor/agent rules (Cursor Rules for TypeScript Monorepos).
Contract tests as the merge gate
// payments/refunds.contract.test.ts
import { OpenAPIResponseValidator } from "openapi-response-validator";
import spec from "../openapi.yaml";
test("POST /v1/refunds matches response schema", async () => {
const res = await app.inject({ method: "POST", url: "/v1/refunds", payload: fixture });
const v = new OpenAPIResponseValidator({ responses: spec.paths["/v1/refunds"].post.responses });
// ✅ Fail closed on schema drift
expect(v.validateResponse(res.statusCode, res.json())).toBeUndefined();
});
AI review bots should prioritize contract and authz diffs (AI code review bots).
Cross-repo orchestration pattern
| Step | Owner | Artifact |
|---|---|---|
| 1. Spec PR | Human + agent assist | OpenAPI/AsyncAPI diff |
| 2. Generate clients | CI | types package bump |
| 3. Provider impl task | Workspace | payments PR |
| 4. Consumer impl task | Workspace | orders PR |
| 5. Dual contract CI | CI | both greens |
Never skip step 1 when the task needs a new path — that is how phantom endpoints ship.
Closing checklist
✅ Dos
– ✅ Spec + acceptance + verify commands in every task brief
– ✅ Generate typed clients before implementation
– ✅ Separate spec PRs from impl PRs
– ✅ Contract tests across provider and consumer
– ✅ Scope Workspace file allowlists per service
❌ Don’ts
– ❌ Don’t invent URLs or payload fields absent from OpenAPI
– ❌ Don’t edit both sides of a boundary without stacked PRs
– ❌ Don’t accept “temporary any” on generated types
– ❌ Don’t merge consumer before provider contract is published
– ❌ Don’t skip fixture-based contract tests
Related reading
- Cursor Rules for TypeScript Monorepos: Make AI Edits Stick
- AI Code Review Bots: IAM, Secrets, and Least-Privilege Pipelines
- Express.js Best Practices: Build Production-Ready APIs With Node.js
- LLM Coding Agents on AWS: Safe Tool Sandboxes with Lambda
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
