Scaffold Boundary Contract
P: Defines
src/templates/<stack>/scaffold-boundary.yaml— machine-readable subtree isolation rules so multi-stack projects (src/mobile/+src/backend/+src/ai-service/) can never write outside their lane. R: Adding a new stack, debugging aboundary mismatchblock, or auditing cross-stack import discipline. S: Authoring code — read the per-stackanatomy.mdinstead. N: anatomy-contract.md, docs-system.md
Nav: Governance Index
File location
src/templates/<stack>/scaffold-boundary.yaml
One file per stack. Ships alongside stack.yaml. The aggregator merges every installed stack's boundary at cos init into the consumer project's .coding-os/scaffold-boundary.yaml.
Schema
version: 1
stack: <stack-id> # MUST equal stack.yaml::id
roots: # Subtree(s) this stack owns. Relative to project root.
- <path>/ # Trailing slash required. Conventionally under src/.
file_patterns: # Globs the stack OWNS (and only this stack may write).
- "**/*.<ext>"
imports_from: # Other-stack subtrees this stack MAY import from (read-only).
- <path>/
forbids_writing_in: # Other-stack subtrees this stack MUST NEVER write to.
- <path>/
notes: # Optional free-text.
- <one-line>
Every field is required EXCEPT notes. Empty arrays (forbids_writing_in: []) are accepted; missing keys are rejected by the parser.
Field semantics
version
Always 1 for now. Bump only if the schema changes incompatibly; old consumer projects pin their boundary file's version.
stack
The stack id from stack.yaml::id. Validation: re.fullmatch(r"[a-z0-9][a-z0-9-]{0,30}", stack). The aggregator rejects mismatches.
roots
The directory (or directories) the stack writes into. A multi-stack project has each stack in its own root under src/:
project/
src/mobile/ ← react-native root
src/backend/ ← go-fiber root
src/ai-service/ ← fastapi root
src/shared/ ← imports_from target (cross-stack types/contracts)
A stack may declare multiple roots only when it legitimately produces files in disjoint trees. Two stacks claiming the same root = aggregator rejects at cos init.
file_patterns
Globs whose match implies "this stack owns this file." enforce-skill.sh uses these to pick the matching skill on every Write/Edit. Typical:
| Stack | file_patterns |
|---|---|
| nextjs | src/frontend/**/*.{ts,tsx,js,jsx} |
| react-native | src/mobile/**/*.{ts,tsx} |
| django | src/backend/**/*.py |
| go-fiber | src/backend/**/*.go |
| fastapi | src/ai-service/**/*.py |
When two stacks could match (e.g. fastapi + django both want **/*.py), disambiguate with a more specific root prefix.
imports_from
Whitelist of subtrees this stack's source may IMPORT from. Empty list = no cross-stack imports allowed. Standard convention:
imports_from:
- src/shared/ # cross-stack types / contracts
- src/shared/types/
Avoid listing other stack roots here. If your react-native code needs go types, add a contract layer in src/shared/ instead.
forbids_writing_in
Hard list of subtrees this stack must never write to. PreToolUse Write/Edit hook blocks. Typical:
# nextjs/scaffold-boundary.yaml
forbids_writing_in:
- src/mobile/
- src/backend/
- src/ai-service/
If the agent's active stack-skill is nextjs-react and it tries to Write src/backend/api.go, the hook blocks with:
BLOCKED: stack 'nextjs' cannot write to src/backend/. Boundary defined at:
src/templates/nextjs/scaffold-boundary.yaml::forbids_writing_in
Switch to the right skill or open a multi-stack task.
Aggregation at cos init
When cos init -t nextjs,go-fiber,react-native runs, the CLI:
- Reads each stack's
scaffold-boundary.yaml. - Validates every
rootsentry is unique across all installed stacks. - Validates
imports_fromonly references subtrees that exist in the merged set ORsrc/shared/. - Writes
.coding-os/scaffold-boundary.yamlin the consumer project — a flat list of every stack's boundary.
The consumer project's hook reads .coding-os/scaffold-boundary.yaml at runtime; it does not re-read the meta-repo.
Hook integration
src/core/hooks/enforce-skill.sh (PreToolUse Write|Edit) consults the consumer's .coding-os/scaffold-boundary.yaml:
on Write/Edit of <path>:
matched_stack = first stack whose file_patterns matches <path>
active_stack = active skill's owner stack (from .coding-os/<agent>/.role)
if matched_stack and matched_stack != active_stack:
if <path> in any active stack's forbids_writing_in:
BLOCK
else:
WARN # cross-stack edit allowed but flagged
The hook is fire-and-forget for WARN, BLOCK for forbids — never silent.
Authoring rules
- One file per stack. Multi-stack rules belong in the consumer project's aggregated copy.
- Use forward-slash paths even on Windows hosts.
rootsMUST end with/and conventionally start withsrc/; file_patterns MUST be globs.- Every
forbids_writing_inentry MUST appear as arootsentry in another installed stack — otherwise the rule is unreachable.
Validation
tests/test_scaffold_boundary_contract.py runs on every PR and asserts:
- Every
src/templates/<stack>/scaffold-boundary.yamlparses as YAML. - Required keys present; types match.
stackfield equals enclosing dir name.- No two stacks share a root.
- No
forbids_writing_inentry references a path no installed stack owns.
Adding a stack
- Drop
src/templates/<stack>/scaffold-boundary.yamlper schema above. - Run
pytest tests/test_scaffold_boundary_contract.py -qto verify. - Run
make manifest-regensosrc/core/scaffold_manifest.jsonpicks the new stack up. - Run a smoke
cos init -t <stack> --debugto confirm aggregation works.