Copilot Workspace Tasks: Spec-Driven Changes Across Microservice Boundaries

Copilot Workspace Tasks: Spec-Driven Changes Across Microservice Boundaries

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

Last updated on September 11, 2026


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply