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 shape | Typical reality | Where it usually breaks |
|---|---|---|
| Many teams, many business repos | Checkout, catalog, and marketing each have a repo; cross-module work is common; few shared libraries | Peer teams: who writes the contract first, who merges first, which Spec the agent reads |
| Heavy platform or shared components | One auth SDK / base API is consumed by many business repos; breaking changes are normal | Hub-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 type | How to tell | Where OpenSpec lands (recommended, no beta required) | Who moves first |
|---|---|---|---|
| Local-only | Consequences bind only the current code repo | Local openspec/changes + local specs delta | Owning team |
| Cross-business-repos | At least two business/module repos must honor the new behavior | Write boundary behavior first in product-contracts; each code repo then opens an implementation change and cites the contract version | Contract owner merges the contract → implementing repos |
| Platform or shared dependency | You are changing a widely consumed library or service that may force consumers to change code or behavior | Write compatibility rules in platform-contracts; consumers land in waves | Platform 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):
- Demand type: local-only / cross-business-repos / platform or shared dependency
- Where the contract lives: this repo, or product-contracts / platform-contracts
- Which code repos will change (or “this repo only”)
- 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 want | Stable approach you can use today | Rough Stores beta equivalent |
|---|---|---|
| Independent planning/contract layer | Ordinary contracts git repo | Register an OpenSpec store |
| Pin a specific contract revision | Proposal + CI cite contracts@tag-or-SHA | Still your org pin—Stores does not lock revisions |
| Help agents see upstream contracts | context: / rules: in each code repo’s openspec/config.yaml | references: injects a read-only index only; does not change where commands write |
| See several folders at once | Shareable multi-root .code-workspace | workset open (usually a machine-local list—not team shared truth) |
| Health checks | CI: pin resolves; pointer present | openspec 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:
- After the contracts PR is Approved by owners on your forge, implementing repos may open implementation PRs and start coding in parallel.
- 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.
- 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. - 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.
- After the contracts PR merges (including squash), pin again to the merge commit or an annotated tag on
main, and update open implementation PRs. - 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.
- Classify as cross-business-repos; write the four lines → pass the alignment gate.
- 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.
- After approval, freeze
product-contracts@some-SHA. webandapieach open a local change: local design and tasks only; proposal headers keep pointers (see the lab chapter).- Multi-root window: contracts + involved code repos; agents read the pinned contract before coding.
- Integrate api before web usually; if field semantics change, update the contract and bump the pin first.
- TE runs product journeys against the post-merge contract revision.
- 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.
- Label it platform/shared-dependency work and attach a manual consumer list (good enough to start; later a consumers file inside platform-contracts).
- In platform-contracts, write old→new behavior, deprecation schedule, dual-run or feature flags.
- Freeze a version after approval; artifact versions must trace to that contract—no shipping the SDK before the Spec.
- Consumers open local changes in canary → batch waves, all citing the same pin.
- 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):
| Repo | Role | Typical owner |
|---|---|---|
svc-catalog | SKUs, categories, detail | Catalog team |
svc-inventory | Sellable stock, holds | Inventory / fulfillment |
svc-pricing | List price, payable after discount, tax/shipping | Pricing |
svc-promotion | Coupon validation, rules engine | Growth / promos |
svc-order | Place order, payment state, order query | Orders / checkout |
svc-gateway | Aggregated APIs for web/app | Experience / BFF |
shop-web / shop-mobile | UI | Front-end / client |
auth-sdk | Shared auth | Platform |
product-contracts | Cross-repo truth | Product + domain leads |
shop-e2e | Product automation | TE |
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.
| Question | Answer |
|---|---|
| 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-goals | No gateway field changes, no svc-promotion rule changes, no product-journey assertion changes |
Landing steps:
- Open a local change in
shop-web, e.g.openspec/changes/coupon-input-error-styles/. - State “local-only” and the four lines; no product-contracts PR and no cross-repo pin.
- Design and tasks stay on local components and CSS; a single-repo workspace is enough—do not drag
svc-orderinto the window for noise. - Merge
shop-webafter local checks. TE usually need not re-run the full checkout journey for pure styling. - 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
codeon checkout - Gateway external:
POST /checkout/couponrequest/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-contractschange. - 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.mdfiles. How this service lands the work belongs in its owndesign.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”:
| Order | Action | Gate |
|---|---|---|
| 1 | Merge contracts PR (squash OK) | Final revision, e.g. tag contracts-checkout-coupon-2026-10-27 → SHA c0ffee01 |
| 2 | Update all five impl PR pointers @9f3c1a2 → @c0ffee01 | CI: must equal post-merge revision |
| 3 | Merge svc-promotion and svc-pricing first | Local check against pinned Spec |
| 4 | Merge svc-order | Same |
| 5 | Merge svc-gateway | Same |
| 6 | Merge shop-web last | Same |
| 7 | TE runs apply-coupon in shop-e2e across the chain; suite cites contracts@c0ffee01 | Matches journey bullets |
| 8 | Archive impl repos first, then contracts; write Guides back | Archive 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.
| Question | Answer |
|---|---|
| Type | Platform / shared dependency |
| Contract home | platform-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 untouched | svc-catalog, svc-inventory, svc-promotion… (follow real dependency edges) |
| Rollout | Freeze 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
| Demand | Type | Repos roughly touched | Contracts PR + pin? | Done means |
|---|---|---|---|---|
| Coupon input styles | Local-only | shop-web only | No | Local change accepted |
| Extra display field on PDP (catalog-internal model) | Local-only | svc-catalog only | No (if public API unchanged) | Local acceptance |
| Checkout coupon code | Cross-business-repos | contracts + promotion + pricing + order + gateway + web (+ e2e) | Yes; freeze at approval, re-align after merge | Product journey covers the chain |
| App checkout follows coupons (web already shipped) | Cross-business-repos | contracts (optional client deltas) + gateway if needed + shop-mobile | Yes; cite existing or amended contract revision | App journey against the same contract |
| Breaking auth-sdk error codes | Platform / shared dependency | platform contracts + auth-sdk + gateway/web/mobile consumers in waves | Yes; plus impact list and migration window | Canary 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:
| Repo | this-repo line |
|---|---|
shop-web | coupon field UI, call gateway, render errors |
svc-gateway | public POST /checkout/coupon; orchestrate promotion/pricing/order |
svc-pricing | apply discount to payable / tax / shipping |
svc-order | persist 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):
svc-promotion,svc-pricingsvc-ordersvc-gatewayshop-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
| Step | Repo | What you do |
|---|---|---|
| Write cross-service behavior | product-contracts | /opsx-propose + cross-repo constraints; human PR; freeze SHA |
| Open impl changes | each of the five | /opsx-propose with pointer-only proposal; local design/tasks |
| Code | five repos, dependency order | /opsx-apply |
| After contracts on main | five repos | retarget final SHA, then merge PRs |
| Product verify | shop-e2e | run journey against the same pin |
| Finish | impl repos, then contracts | openspec archive … -y or /opsx-archive |
Two common mistakes:
- Pasting API field tables into
shop-webor asvc-*proposal.md(those belong in contracts specs). - Running five independent
/opsx-proposesessions 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:
| Layer | What it is | Who owns it across repos |
|---|---|---|
| Durable | Contracts specs/changes, pinned revisions, local changes | OpenSpec + git |
| Working visibility | Which folders the editor opened | Multi-root .code-workspace (or optional workset) |
| Session | Temporary understanding in the current chat | Must 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.
| Phase | Guide (before) | Sensor (after) |
|---|---|---|
| Before work | Pull/open the pinned contracts; read non-goals | Missing contracts root or SHA mismatch → block |
| During impl | Public API/event changes must update contracts first | Touched public surface without bumping the pin → fail |
| Before merge | Contract is on main; pointer is the post-merge revision | CI verifies |
| After archive | Write lessons back; prune stale rules | A 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:
| Layer | Content | Home |
|---|---|---|
| Journey spec | Human/agent-readable cross-module Given/When/Then | product-contracts (in the active change, or later under openspec/specs/). Normal OpenSpec material—no new file type required. |
| Executable suite | Automation, fixtures, environment | TE-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
- Is this local-only, cross-business-repos, or platform/shared-dependency—and are the four lines in the proposal?
- Which repo holds the contract prose? Did you freeze a revision at approval and re-align after merge?
- Do code-repo changes keep pointers only, with no copied contract body?
- Do config/rules say “read the pinned contract before coding”?
- Do TE journey specs and executable suites cite the same contract revision?
- 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
- OpenSpec Stores (beta): https://openspec.dev/docs/stores
- OpenSpec Worksets (beta): https://openspec.dev/docs/worksets
- On this site: multi-repo contract ownership, SDD truth source, Harness, ai4se-harness, three-layer stack