You already decided who owns the contract—and cross-repo work still blows up. An agent in web “reasonably invents” an API; api ships against a hallway agreement; a platform SDK ships a major before anyone writes the Spec. The failure is rarely ignorance of multi-repo contract ownership. It is the lack of a change protocol you can read aloud and follow, and of a way to pin durable context while people and agents execute per repo and per session.

Thesis in one line: OpenSpec holds cross-repo contracts and change memory that stay true; User Harness forces read-back, checks, and write-back during local execution—so cross-repo implementation does not lose context or drift.

Audience: developers and testing engineers (TE). After reading, you should be able to run one local-only change and one cross-business-repo change end to end. Tech leads can follow topology and gates; they are not the primary reader. No new product branding—OpenSpec is a mature SDD open-source framework; this is how to collaborate with it at scale.

This article avoids shorthand codes. If you have seen labels like “L0/L1” or “H2” elsewhere, they are only nicknames for the demand types and the “parallel after approval, align before merge” rule below. While reading, trust the plain sentences.

First: which product pain are you in?

Many orgs run both shapes; the failure modes differ:

Product shapeTypical realityWhere it usually breaks
Many teams, many business reposCheckout, catalog, and marketing each have a repo; cross-module work is common; few shared librariesPeer teams: who writes the contract first, who merges first, which Spec the agent reads
Heavy platform or shared componentsOne auth SDK / base API is consumed by many business repos; breaking changes are normalHub-and-spoke: impact surface, migration window, waved consumer upgrades

Do not invent two methodologies. One change pipeline is enough—the only forks are whether you need a separate platform contracts repo, and whether you need an impact list and waves. The fictional Acme lab later contrasts both pains in one workspace.

The shared pipeline:

Classify the demand (local-only / cross-business-repos / platform dependency)
  → Decide which repo holds the contract and which change to open
  → Pin the contract with a git tag or SHA
  → Each code repo implements locally and cites that pin
  → Integrate and release (especially for platform work)
  → Product journeys: TE verifies against the pinned contract
  → Archive and write lessons back into Harness rules

Same claim as SDD as the source of truth: when agents generate code, what stays stable is human-maintained Spec. Across repos, Spec must also be citable and versioned. OpenSpec remains the durable Spec layer in the delivery stack—see the three-layer stack.

Three kinds of demand: choose a path in about a minute

Start with the prequel’s question: do these behaviors or constraints require people in other repos to comply? Then ask whether the impact is peer collaboration across business repos, or a hub change that forces many consumers to move.

Demand typeHow to tellWhere OpenSpec lands (recommended, no beta required)Who moves first
Local-onlyConsequences bind only the current code repoLocal openspec/changes + local specs deltaOwning team
Cross-business-reposAt least two business/module repos must honor the new behaviorWrite boundary behavior first in product-contracts; each code repo then opens an implementation change and cites the contract versionContract owner merges the contract → implementing repos
Platform or shared dependencyYou are changing a widely consumed library or service that may force consumers to change code or behaviorWrite compatibility rules in platform-contracts; consumers land in wavesPlatform owner proposes → impact review → consumers in batches

Both “cross-business-repos” and “platform dependency” can span git repos. The difference is collaboration shape: the first is usually peer (web + API for checkout); the second is hub-and-spoke (one SDK moves, many apps follow).

Before broad coding, put four lines in the proposal (this is the Harness “align before you build” gate—called an alignment gate on this site):

  1. Demand type: local-only / cross-business-repos / platform or shared dependency
  2. Where the contract lives: this repo, or product-contracts / platform-contracts
  3. Which code repos will change (or “this repo only”)
  4. Non-goals—what you will not do, so scope does not quietly expand mid-session

Missing any line → do not let an agent rewrite half the tree yet.

Default topology: ordinary git repos are enough; Stores beta is optional

product-contracts/     # product contracts: boundary behavior, cross-client journeys
platform-contracts/    # platform contracts: compatibility + migration window (split out when the platform hurts)
web/  api/  mobile/ …  # business repos: local openspec/ only describes how this repo lands the work

If the team is small and almost never ships “upgrade the shared SDK or everyone breaks,” you can start with one contracts repo and separate product vs platform with folders and CODEOWNERS. Same protocol; fewer remotes.

OpenSpec Stores and references / worksets are still beta: treat them as an optional accelerator for the same protocol, not a prerequisite. Keep the split clear:

  • What the tool can do: manage specs and changes under an openspec/ tree; Stores can host planning in its own repo and reference it by name.
  • What the tool will not do for you: auto git pull; lock “you must use this SHA”; run the TE’s product suites.

Docs also say Stores do no sync by design. A stale contracts checkout is a stale source of truth. So version pins are git tag/SHA + CI/Harness checks, not a references: line that merely indexes upstream specs.

Capability you wantStable approach you can use todayRough Stores beta equivalent
Independent planning/contract layerOrdinary contracts git repoRegister an OpenSpec store
Pin a specific contract revisionProposal + CI cite contracts@tag-or-SHAStill your org pin—Stores does not lock revisions
Help agents see upstream contractscontext: / rules: in each code repo’s openspec/config.yamlreferences: injects a read-only index only; does not change where commands write
See several folders at onceShareable multi-root .code-workspaceworkset open (usually a machine-local list—not team shared truth)
Health checksCI: pin resolves; pointer presentopenspec doctor / context

Bootstrap each code repo with stable config, for example:

schema: spec-driven
context: |
  Cross-repo behavior lives in product-contracts.
  Before coding: read the frozen contracts@SHA cited in this change.
  Do not copy contract body into this repo.
rules:
  proposal:
    - State demand type, contracts pin, impl repos, non-goals
    - For cross-repo or platform work, keep pointers only in this repo

That is what makes “contract body only in contracts; pointers in code repos” executable—otherwise agents only see the current repo.

How changes queue: parallel after approval, align before merge to main

Recommended default rhythm in plain language:

  1. After the contracts PR is Approved by owners on your forge, implementing repos may open implementation PRs and start coding in parallel.
  2. Before any implementation PR merges to main, the contracts PR must already be merged, and every implementation PR must cite the final post-merge contract version.

Faster than “merge the contract before anyone types,” safer than “we’ll ship on a hallway handshake.”

How to pin versions (or parallel work aims at a moving target)

OpenSpec does not lock versions; git does.

  1. The moment the contracts PR is Approved, freeze an immutable revision: e.g. product-contracts@9f3c1a2 (head SHA at approval) or a prerelease tag on that SHA. Do not pin a floating branch tip.
  2. Every implementation proposal must cite that frozen revision. If someone pushes another semantic commit to the still-open contracts PR, re-approve and bump the freeze, and update every implementation PR.
  3. After the contracts PR merges (including squash), pin again to the merge commit or an annotated tag on main, and update open implementation PRs.
  4. CI blocks merges when contracts is already on main but an implementation PR still cites the old “approved SHA.”

One sentence: freeze a revision at approval; re-align to the final revision after merge.

Cross-business-repos path (e.g. checkout coupons)

Fictional Acme shop: checkout needs coupons; both web and api must change.

  1. Classify as cross-business-repos; write the four lines → pass the alignment gate.
  2. Open a change in product-contracts: boundary behavior, error semantics, journey acceptance bullets—do not dump front-end and back-end task lists into the contracts repo.
  3. After approval, freeze product-contracts@some-SHA.
  4. web and api each open a local change: local design and tasks only; proposal headers keep pointers (see the lab chapter).
  5. Multi-root window: contracts + involved code repos; agents read the pinned contract before coding.
  6. Integrate api before web usually; if field semantics change, update the contract and bump the pin first.
  7. TE runs product journeys against the post-merge contract revision.
  8. Archive after implementers land: code-repo archives first, then contracts; write lessons back into Guides.

Minimal pointer stub in a code-repo proposal:

- demand: cross-business-repos
- contracts: product-contracts@<sha-or-tag>
- change: <path-or-id in the contracts repo>
- impl-repos: [web, api]
- non-goals: <link to the non-goals section in the contracts change>
- prs: [contracts-PR, …]
- journeys: [<id> → e2e suite path]

Remember: behavior that must stay true across repos has one prose home in the contracts repo; code repos cite it—they do not paste a second summary. Summaries drift.

Platform or shared-dependency path (e.g. breaking auth-sdk)

Versus cross-business-repos, platform work usually adds only three things: who is hit (impact surface), how long old behavior lasts (migration window), and who moves first (waves)—not a second SDD.

  1. Label it platform/shared-dependency work and attach a manual consumer list (good enough to start; later a consumers file inside platform-contracts).
  2. In platform-contracts, write old→new behavior, deprecation schedule, dual-run or feature flags.
  3. Freeze a version after approval; artifact versions must trace to that contract—no shipping the SDK before the Spec.
  4. Consumers open local changes in canary → batch waves, all citing the same pin.
  5. Write down window-close rules: critical product journeys green, or a signed exception list.

When the platform is heavy, tighten gates: inside the window, canary consumer journeys plus platform smoke; close the window only after full critical journeys (or exceptions).

Simulated landing: walk the Acme microservice map

Principles above; directories, changes, and PR order below. Acme is split like a typical commerce set of services—not a two-repo toy demo, but a map you can compare to a real split. First the workspace, then local-only, cross-microservice, and shared-SDK work.

Workspace: contracts plus many business services

Ordinary git repos are enough. One repo per business service (or monorepo packages—same rule: cross-boundary behavior still lives in the contracts repo).

~/acme/
  # —— contracts & verification ——
  product-contracts/          # product contracts: behavior that stays true across services
  platform-contracts/         # platform contracts: auth-sdk compatibility notes
  shop-e2e/                   # TE product suites (black-box across services)

  # —— clients ——
  shop-web/                   # web storefront
  shop-mobile/                # app (optional in case 2; used in the contrast table)

  # —— business microservices (one repo per service) ——
  svc-gateway/                # BFF / API gateway: aggregates checkout reads/writes
  svc-catalog/                # catalog
  svc-inventory/              # inventory
  svc-pricing/                # pricing, tax, shipping
  svc-promotion/              # coupons & promo rules
  svc-order/                  # orders & checkout state machine
  svc-notification/           # SMS / email / push

  # —— platform shared dependency ——
  auth-sdk/                   # auth session & error codes (used by gateway/web/mobile/…)

  acme-shop.code-workspace    # multi-root window: tick only repos for this change

Rough ownership (map to your own teams):

RepoRoleTypical owner
svc-catalogSKUs, categories, detailCatalog team
svc-inventorySellable stock, holdsInventory / fulfillment
svc-pricingList price, payable after discount, tax/shippingPricing
svc-promotionCoupon validation, rules engineGrowth / promos
svc-orderPlace order, payment state, order queryOrders / checkout
svc-gatewayAggregated APIs for web/appExperience / BFF
shop-web / shop-mobileUIFront-end / client
auth-sdkShared authPlatform
product-contractsCross-repo truthProduct + domain leads
shop-e2eProduct automationTE

Do not open every folder every time. For checkout coupons, tick contracts plus services you will touch:

{
  "folders": [
    { "path": "product-contracts", "name": "product-contracts" },
    { "path": "svc-promotion", "name": "svc-promotion" },
    { "path": "svc-pricing", "name": "svc-pricing" },
    { "path": "svc-order", "name": "svc-order" },
    { "path": "svc-gateway", "name": "svc-gateway" },
    { "path": "shop-web", "name": "shop-web" }
  ]
}

Case 1 — Local-only: coupon input error styles

Demand: Restyle the checkout coupon field error state. Do not change validation rules, API fields, or error codes. Leave the surrounding microservices alone.

QuestionAnswer
Must other repos comply?No → local-only
Where does the contract live?No new cross-repo behavior; stay in shop-web
Who changes code?shop-web only
Non-goalsNo gateway field changes, no svc-promotion rule changes, no product-journey assertion changes

Landing steps:

  1. Open a local change in shop-web, e.g. openspec/changes/coupon-input-error-styles/.
  2. State “local-only” and the four lines; no product-contracts PR and no cross-repo pin.
  3. Design and tasks stay on local components and CSS; a single-repo workspace is enough—do not drag svc-order into the window for noise.
  4. Merge shop-web after local checks. TE usually need not re-run the full checkout journey for pure styling.
  5. Archive the local change.

Done: local Spec/change acceptance. Do not open an empty contracts PR to look formal, and do not “also tweak” a microservice.

Case 2 — Cross-microservices: apply a coupon at checkout

Demand: User enters a coupon on checkout. Call chain roughly:

shop-web
  → svc-gateway (aggregates checkout data + “apply coupon”)
      → svc-promotion (validate code, discount lines)
      → svc-pricing (apply discount to payable amount)
      → svc-order (persist discount on the in-progress checkout/order)

shop-mobile, svc-catalog, svc-inventory, and svc-notification stay out (non-goals). Success/failure meaning must match from gateway to web; promotion / pricing / order fields must not drift. Classic cross-business-repos.

Step 1: Contract prose only in product-contracts

product-contracts/
  openspec/
    changes/
      add-checkout-coupon/
        proposal.md
        specs/            # or delta: cross-service boundary behavior + errors

Illustrative proposal:

## Demand type
Cross-business-repos — all listed repos must honor the same boundary behavior

## Implementation repos
- shop-web
- svc-gateway
- svc-promotion
- svc-pricing
- svc-order

## Non-goals
- No shop-mobile (follow in a later iteration)
- No svc-catalog / svc-inventory / svc-notification
- No auth-sdk / platform-contracts changes
- No gift cards, no “recommended coupons” list

## Product journey
- id: checkout-apply-coupon
- suite: shop-e2e/tests/checkout/apply-coupon.spec.ts

Body holds only what must stay true across services, for example:

  • User action: submit coupon code on checkout
  • Gateway external: POST /checkout/coupon request/response shape
  • Service agreements (sync or events—pick one and write it): how pricing consumes promotion discount lines; how order persists discount
  • Failures: user-visible meaning of COUPON_NOT_FOUND / COUPON_EXPIRED / COUPON_NOT_APPLICABLE (web and gateway agree)
  • Inventory/catalog: no hold-logic change because of coupons in this change

Open the contracts PR → relevant domain leads Approve → freeze e.g. product-contracts@9f3c1a2.

Step 2: Pointer-only implementation changes in every touched repo

After approval, five implementation repos may open PRs in parallel. One rule must be exact:

  • Contract prose lives only in the product-contracts change.
  • Each of the five implementation repos also has a proposal.md, but it is a pointer: same frozen contract revision, same contracts change path, full impl-repo list, plus this repo’s responsibility.
  • Do not copy API fields, error meanings, or cross-service agreements into these five proposal.md files. How this service lands the work belongs in its own design.md / tasks.md.

Spell out every tree; annotate every proposal.md as pointers-only:

shop-web/
  openspec/
    config.yaml
    changes/
      add-checkout-coupon/
        proposal.md    # pointers only; no copied contract body
        design.md      # coupon field UI, call gateway, render errors
        tasks.md

svc-gateway/
  openspec/
    config.yaml
    changes/
      add-checkout-coupon/
        proposal.md    # pointers only; no copied contract body
        design.md      # public POST /checkout/coupon; orchestrate downstream
        tasks.md

svc-promotion/
  openspec/
    config.yaml
    changes/
      add-checkout-coupon/
        proposal.md    # pointers only; no copied contract body
        design.md      # validate code; return discount lines
        tasks.md

svc-pricing/
  openspec/
    config.yaml
    changes/
      add-checkout-coupon/
        proposal.md    # pointers only; no copied contract body
        design.md      # apply discount to payable / tax / shipping
        tasks.md

svc-order/
  openspec/
    config.yaml
    changes/
      add-checkout-coupon/
        proposal.md    # pointers only; no copied contract body
        design.md      # persist discount on in-progress checkout/order
        tasks.md

The five proposal.md files align field-for-field. Shared: contract revision, contracts change, impl-repo list, non-goals/journey pointers. Different: only this-repo.

shop-web/openspec/changes/add-checkout-coupon/proposal.md:

- demand: cross-business-repos
- contracts: product-contracts@9f3c1a2
- change: openspec/changes/add-checkout-coupon
- impl-repos: [shop-web, svc-gateway, svc-promotion, svc-pricing, svc-order]
- this-repo: shop-web = coupon field UI, call gateway, render errors
- non-goals: (see the contracts change; do not copy that prose here)
- prs: [acme/product-contracts#84]
- journeys: [checkout-apply-coupon → shop-e2e/tests/checkout/apply-coupon.spec.ts]

svc-gateway/.../proposal.md: same, except
this-repo: svc-gateway = public POST /checkout/coupon; orchestrate promotion/pricing/order.

svc-promotion/.../proposal.md: same, except
this-repo: svc-promotion = validate code and return discount lines.

svc-pricing/.../proposal.md: same, except
this-repo: svc-pricing = apply discount to payable / tax / shipping.

svc-order/.../proposal.md: same, except
this-repo: svc-order = persist discount on in-progress checkout/order.

Multi-root window: contracts plus the one or two services you are editing. Rule: fetch and verify SHA 9f3c1a2 before coding; do not invent neighbor fields from an old DTO.

Step 3: Merge order, final pin, TE, archive

With more services, merge by dependency direction, not “whoever finished first”:

OrderActionGate
1Merge contracts PR (squash OK)Final revision, e.g. tag contracts-checkout-coupon-2026-10-27 → SHA c0ffee01
2Update all five impl PR pointers @9f3c1a2 → @c0ffee01CI: must equal post-merge revision
3Merge svc-promotion and svc-pricing firstLocal check against pinned Spec
4Merge svc-orderSame
5Merge svc-gatewaySame
6Merge shop-web lastSame
7TE runs apply-coupon in shop-e2e across the chain; suite cites contracts@c0ffee01Matches journey bullets
8Archive impl repos first, then contracts; write Guides backArchive write-back

svc-catalog, svc-inventory, and shop-mobile have no impl PRs—on purpose, via non-goals. When the app follows later, open a new change citing the same (or updated) contract revision.

If contract semantics change between Approve and merge: re-approve, bump the freeze, update all five impl PRs. More repos make a moving target more expensive.

Case 3 (contrast) — Shared dependency: breaking auth-sdk error codes

Demand: auth-sdk replaces a batch of login error codes; old codes deprecated. Consumers include more than shop-web: svc-gateway, shop-mobile, and any service that links the SDK directly.

QuestionAnswer
TypePlatform / shared dependency
Contract homeplatform-contracts (compat notes, migration window, old→new code table)
Impact list (manual to start)auth-sdk, svc-gateway, shop-web, shop-mobile; add svc-order if it links the SDK
Can stay untouchedsvc-catalog, svc-inventory, svc-promotion… (follow real dependency edges)
RolloutFreeze platform contract → publish versioned SDK → canary gateway+web → batch mobile → TE login journeys → close window

Follow the earlier “platform or shared dependency” section: artifact versions must trace to the platform contract; canary journeys must pass inside the window.

Five demands on the same map

DemandTypeRepos roughly touchedContracts PR + pin?Done means
Coupon input stylesLocal-onlyshop-web onlyNoLocal change accepted
Extra display field on PDP (catalog-internal model)Local-onlysvc-catalog onlyNo (if public API unchanged)Local acceptance
Checkout coupon codeCross-business-reposcontracts + promotion + pricing + order + gateway + web (+ e2e)Yes; freeze at approval, re-align after mergeProduct journey covers the chain
App checkout follows coupons (web already shipped)Cross-business-reposcontracts (optional client deltas) + gateway if needed + shop-mobileYes; cite existing or amended contract revisionApp journey against the same contract
Breaking auth-sdk error codesPlatform / shared dependencyplatform contracts + auth-sdk + gateway/web/mobile consumers in wavesYes; plus impact list and migration windowCanary login journeys; close on critical journeys or signed exceptions

The middle rows matter: “catalog-internal only” and “checkout coupon across five or six repos” can both live in the same microservice estate. The difference is not how many repos exist—it is whether consequences spill into other repos.

Order of work is unchanged: classify → local-only stays local; cross-business or platform work puts contract prose and a frozen revision in the right contracts repo first → implement with pointers → TE verifies one revision → archive. Stores later only adds registration/indexing—the pin and PR order stay the same.

Cross-repo demands: OpenSpec commands and prompts, step by step

The map is above. This section focuses on cross-business-repos (checkout coupons) and spells out what you type in the terminal and in the AI chat.

Default is the stable track: each repo has its own openspec/; product-contracts is an ordinary git repo—no Stores beta required. Commands follow the current OPSX workflow. In Cursor the slash form is often /opsx-propose (hyphen); in Claude Code-style tools it is often /opsx:propose. Same intent—use whatever openspec init / openspec update generated in your project. Stable-track commands used below (init / new change / status / instructions / validate / archive) exist on common installs; if your CLI has no store / workset / doctor, you are likely on an older build without Multi-repo (beta)—this protocol does not need them; upgrade @fission-ai/openspec when you want those.

0. Initialize once per repo

Run once in the contracts repo and in every code repo you will change (skip if already done):

cd ~/acme/product-contracts && openspec init --tools cursor
cd ~/acme/svc-promotion && openspec init --tools cursor
# same for gateway / pricing / order / shop-web

Implementation-repo openspec/config.yaml must require “read the pinned contract first; proposals keep pointers only”—see the earlier example. Without that, agents happily paste a second contract body into local proposal.md.

Open a multi-root window with product-contracts plus the one or two service repos you are editing.

1. Start the change in product-contracts (prose only here)

cd ~/acme/product-contracts
git checkout -b change/add-checkout-coupon

Path A (recommended—one chat turn for planning artifacts):

In Cursor on the product-contracts workspace:

/opsx-propose add-checkout-coupon

This is a cross-business-repos change. Write planning artifacts under these constraints:

Demand: apply a coupon code at checkout.
Call chain: shop-web → svc-gateway → svc-promotion → svc-pricing → svc-order.
Impl repos must include: shop-web, svc-gateway, svc-promotion, svc-pricing, svc-order.
Explicit non-goals: shop-mobile, svc-catalog, svc-inventory, svc-notification, auth-sdk.

proposal.md must state: demand type = cross-business-repos, impl-repo list, non-goals, journey id checkout-apply-coupon.
specs cover only cross-service boundary behavior, error meanings, gateway public shape, and how promotion/pricing/order hand off.
Do not describe per-repo class splits or table schemas—those belong in each impl repo’s design/tasks.
design.md may include a cross-service sequence, but must not become a five-repo task dump.
If tasks.md exists in contracts, keep only coordination tasks (review, freeze pin, notify impl repos)—not service code tasks.

Path B (CLI scaffold, then AI fills artifacts):

cd ~/acme/product-contracts
openspec new change add-checkout-coupon
openspec status --change add-checkout-coupon
openspec instructions proposal --change add-checkout-coupon

Paste instructions output plus the constraints above into the agent so it writes proposal.md, then specs / design / tasks by dependency. That is the safest Path B on the core profile.

Default openspec init installs the core workflows (propose / explore / apply / archive). /opsx-continue and /opsx-ff are expanded commands: run openspec config profile, select them, then openspec update in the repo before they appear as slash commands. Only after that:

/opsx-continue add-checkout-coupon   # one planning artifact at a time
/opsx-ff add-checkout-coupon         # all planning artifacts at once (when the picture is clear)

Check:

openspec status --change add-checkout-coupon
openspec validate add-checkout-coupon

After human Approve of the contracts PR, freeze the revision immediately (SHA at approval) for every impl proposal:

git rev-parse HEAD   # e.g. 9f3c1a2

After contracts merge (squash OK), take the final SHA (e.g. c0ffee01) and update every open impl proposal before merging those PRs.

2. Open pointer-only changes in each implementation repo

Same flow in all five repos; only the directory and this-repo line change. Example for svc-promotion:

cd ~/acme/svc-promotion
git checkout -b change/add-checkout-coupon

Prompt (stress pointers):

/opsx-propose add-checkout-coupon

This repo is an implementation repo, not the contract host.
Read and obey the product contract first:
  repo: ~/acme/product-contracts
  change: openspec/changes/add-checkout-coupon
  frozen pin: product-contracts@9f3c1a2
  (verify with git -C ~/acme/product-contracts show 9f3c1a2:openspec/changes/add-checkout-coupon/proposal.md)

Local proposal.md may contain pointer fields only—do not copy API field tables, error meanings, or cross-service agreements.
Required fields:
- demand: cross-business-repos
- contracts: product-contracts@9f3c1a2
- change: openspec/changes/add-checkout-coupon
- impl-repos: [shop-web, svc-gateway, svc-promotion, svc-pricing, svc-order]
- this-repo: svc-promotion = validate code and return discount lines
- non-goals: see contracts change (pointer only)
- prs / journeys: match the contracts change

design.md / tasks.md cover svc-promotion only.
Do not edit shop-web, gateway, pricing, or order in this session.

Swap this-repo for the others:

Repothis-repo line
shop-webcoupon field UI, call gateway, render errors
svc-gatewaypublic POST /checkout/coupon; orchestrate promotion/pricing/order
svc-pricingapply discount to payable / tax / shipping
svc-orderpersist discount on in-progress checkout/order

CLI alternative:

cd ~/acme/svc-promotion
openspec new change add-checkout-coupon
openspec status --change add-checkout-coupon

Then have the agent follow openspec instructions proposal --change add-checkout-coupon, while your prompt still insists on pointers-only.

Each impl repo should contain:

<repo>/openspec/changes/add-checkout-coupon/
  proposal.md   # pointers
  design.md     # local design
  tasks.md      # local tasks
  (plus specs if the schema requires them)

3. Implement in dependency order

When planning artifacts are ready and the contract pin is frozen:

/opsx-apply add-checkout-coupon

Suggested order (same as merge order):

  1. svc-promotion, svc-pricing
  2. svc-order
  3. svc-gateway
  4. shop-web

If the contract is wrong mid-flight: change product-contracts first (e.g. /opsx-update add-checkout-coupon), re-approve, bump the freeze, update all five contracts: pins, then resume /opsx-apply. Do not “fix” cross-service semantics only inside one service repo.

Handy checks:

openspec status --change add-checkout-coupon
openspec show add-checkout-coupon

4. After contracts merge, retarget the final pin

# in product-contracts, after merge
git rev-parse HEAD   # final SHA, e.g. c0ffee01

In every open impl proposal, change:

contracts: product-contracts@9f3c1a2

to:

contracts: product-contracts@c0ffee01

CI should block merges that still cite the pre-merge SHA once contracts is on main.

5. TE verification and archive

TE runs checkout-apply-coupon in shop-e2e with metadata contracts@c0ffee01.

Archive implementation repos first, then contracts:

cd ~/acme/svc-promotion && openspec archive add-checkout-coupon -y
# same for pricing / order / gateway / shop-web

cd ~/acme/product-contracts && openspec archive add-checkout-coupon -y

Or /opsx-archive add-checkout-coupon in chat. Impl repos may use --skip-specs when there is no local delta to merge; contracts usually merges deltas. Check openspec archive --help.

6. Cheat sheet: which repo, what you type

StepRepoWhat you do
Write cross-service behaviorproduct-contracts/opsx-propose + cross-repo constraints; human PR; freeze SHA
Open impl changeseach of the five/opsx-propose with pointer-only proposal; local design/tasks
Codefive repos, dependency order/opsx-apply
After contracts on mainfive reposretarget final SHA, then merge PRs
Product verifyshop-e2erun journey against the same pin
Finishimpl repos, then contractsopenspec archive … -y or /opsx-archive

Two common mistakes:

  1. Pasting API field tables into shop-web or a svc-* proposal.md (those belong in contracts specs).
  2. Running five independent /opsx-propose sessions that invent five conflicting cross-service truths. There is one cross-service truth: product-contracts.

Harness: seeing folders is not a pinned context

Lost cross-repo context is often blamed on “the model forgets.” A more useful split:

LayerWhat it isWho owns it across repos
DurableContracts specs/changes, pinned revisions, local changesOpenSpec + git
Working visibilityWhich folders the editor openedMulti-root .code-workspace (or optional workset)
SessionTemporary understanding in the current chatMust not be the cross-repo source of truth

Multi-root windows only fix visibility. Anti-drift needs: a frozen contract revision + forced durable read-back + CI/rules. Same idea as Harness Engineering Guides/Sensors and ai4se-harness “align → verify against Spec → archive write-back.” You do not need the full framework on day one: a few clear rules + one or two CI checks are enough to start.

PhaseGuide (before)Sensor (after)
Before workPull/open the pinned contracts; read non-goalsMissing contracts root or SHA mismatch → block
During implPublic API/event changes must update contracts firstTouched public surface without bumping the pin → fail
Before mergeContract is on main; pointer is the post-merge revisionCI verifies
After archiveWrite lessons back; prune stale rulesA manual checklist is fine at first

When you switch code repos mid-work: for cross-business-repos demands, both sides re-read the same product contract revision; for platform work, also carry whether you are in the canary or batch wave and whether the migration window is still open.

Product done: journey spec Ă— executable suite

“Both repos’ unit tests are green” is not product done. Split acceptance into two layers:

LayerContentHome
Journey specHuman/agent-readable cross-module Given/When/Thenproduct-contracts (in the active change, or later under openspec/specs/). Normal OpenSpec material—no new file type required.
Executable suiteAutomation, fixtures, environmentTE-owned e2e repo

Require bidirectional references: the journey cites suite path/id; the suite cites contracts@revision and journey id. Missing either → fail the release gate. OpenSpec does not run the suite—CI and TE engineering do.

To avoid copying the same journey twice:

  • User-facing product journeys live only in product-contracts
  • platform-contracts holds compatibility/migration journeys only
  • Product changes cite a platform contract revision when needed—they do not fork the text

Many-business-repo products usually need one product e2e repo; heavy platforms add consumer smoke waved by impact. Broader E2E practice: AI4SE E2E best practices.

Anti-patterns

  • Burying cross-repo Spec in one “main” business repo
  • Copying the same contract into every repo and hoping review aligns them
  • Dumping implementation task lists into the contracts repo
  • Treating multi-root windows / worksets as ownership or a version pin
  • Shipping code and contract on a hallway handshake with no frozen revision
  • Treating per-repo unit green as product done
  • Publishing platform artifacts before the compatibility write-up
  • Believing Stores beta is required before you can start this protocol

Checklist before you start typing

  1. Is this local-only, cross-business-repos, or platform/shared-dependency—and are the four lines in the proposal?
  2. Which repo holds the contract prose? Did you freeze a revision at approval and re-align after merge?
  3. Do code-repo changes keep pointers only, with no copied contract body?
  4. Do config/rules say “read the pinned contract before coding”?
  5. Do TE journey specs and executable suites cite the same contract revision?
  6. For platform work: are impact surface, migration window, waves, and close criteria written down?

You can refine directories later. Run the change order first, and pin context on the durable layer with Harness. Stores is optional—an ordinary contracts repo is enough to start today.

References