Use when authoring or discovering any cross-domain edge — `import`, `from X import`, `require(...)`, `export * from`, `export { X } from`, dynamic loader (`importlib`, `await import()`, reflection), DI binding, event-bus `subscribe`/`publish`/`emit`, path alias, barrel, facade, re-export, fixture/seed/migration reaching another domain — whose source and target sit in different top-level directories in `domain-map.contract.json` and whose edge is absent from `dependency-rules.contract.json#allowed`; also when `.law/bin/check-coupling` surfaces a violation; also when `contradiction_map` lists an edge this task would extend; also when grep/trace during refactor hits a cross-domain edge. Anchors: CONSTITUTION.md §6, §11.
Every cross-domain edge the agent introduces must already appear in .law/contracts/dependency-rules.contract.json#allowed. The default stance is deny. An edge absent from allowed is forbidden, even when the import looks local, harmless, or one-line. Structural intent — "module A depends on module B" — is what the contract governs; the syntactic shape (static import, dynamic loader, re-export, facade, event bus, DI binding) does not dilute that intent. The same discipline binds discovery: when the agent finds a forbidden edge already in the tree — by reading, tracing, grepping, or running .law/bin/check-coupling — the edge becomes the task. Halt, record in contradiction_map, surface, block. CONSTITUTION.md §6 binds the edge; §11 binds the enforcement posture ("Correct violations at the point of discovery. 'We'll fix it later' is not an acceptable response.").
Fire the instant any of the following is about to be written:
from ... import ... line across domains in Python/TS/Go/Rust/Swift/etc.export * from, export { X } from) or barrel file that surfaces a symbol owned by another domainawait import(), require() at runtime, importlib.import_module, System.import, class-loader reflection)subscribe, publish, emit, or on that crosses domains@/featureB/... used from featureA/) added to tsconfig.json, jsconfig.json, package.json#imports, pyproject.toml, or bundler configFire the instant any of the following is observed:
dependency-rules.contract.json#allowed../<other-domain>/... path.law/bin/check-coupling (v1.4 real dep-edge walker) surfaces a violation on the current branch.law/context/current-system.json#contradiction_map already lists an edge the current task would extend by adding new callers or new data flowBefore introducing any cross-domain edge:
domain-map.contract.json.source -> target in dependency-rules.contract.json#allowed.dependency-rules.contract.json with recorded justification (per §9 mutation rules), or raise the edge as a blocker in .law/context/pending-questions.json.On discovering an existing forbidden edge, halt the current task, record the edge in .law/context/current-system.json#contradiction_map with source_domain, target_domain, file, line, evidence_or_judgment: "evidence", blocks_task, and resolution (remove | amend | raised-block), and raise it to the user naming source, target, file, line, and the blocked task. Do not silently extend forbidden edges; do not TODO-comment-and-proceed. Run .law/bin/check-coupling and .law/bin/validate-contracts after recording; block on non-zero exit.
from -> to pair is absent from dependency-rules.contract.json#alloweddomain-map.contract.json using the actual resolved file path, not the alias label.law/context/pending-questions.json.law/context/current-system.json#contradiction_map with class dep-edge-violation, evidence_or_judgment: "evidence", and evidence pointing at file + line.law/bin/check-coupling to confirm the violation is real and to find siblings.law/context/pending-questions.json when resolution requires human inputdependency-rules.contract.json#allowed only through the §9 mutation path, co-locating the edge entry with the code change and stamping last_validated_at.law/bin/validate-contracts and .law/bin/check-counts after any amendment or contradiction_map update and block on non-zero exitallowed mid-task to unblock the current importshared/, common/, utils/, lib/, core/, or any other generic containerawait import(), importlib, or reflection to defer the edge past the contract checkTODO, FIXME, XXX, HACK, or deferred-fix comment promising to amend the contract or remove the edge "later".law/bin/validate-contracts, .law/bin/check-coupling, or .law/bin/check-counts with || true, --no-verify, or silent skipdependency-rules.contract.json#allowed to legitimize a discovered edge.law/context/current-system.json#contradiction_map because "someone else will record it"| Rationalization the agent whispers | Why it is still forbidden |
|---|---|
| "It's a one-line import, the overhead of amending is disproportionate." | §6: default stance is deny. Line count is not a carve-out. Halt and amend or raise. |
"I'll wrap it in a facade under shared/ so nothing directly imports across." | §6 reverse-indirection clause: structural intent matters. The facade is the edge. |
"A re-export through index.ts keeps the call sites clean." | A barrel that forwards a cross-domain symbol is the cross-domain edge. Classify by resolved target. |
| "The event bus is allowed, so publishing a domain-B payload from domain A is fine." | The bus is the transport. The edge is A -> B payload ownership. Routing does not dissolve it. |
| "DI binds it at runtime; the source file never names domain B." | The binding declaration is the edge. container.register(B) from A/ fails the check. |
"await import() is dynamic, so the static analyzer won't catch it." | The contract governs intent, not tooling coverage. Halt on the semantic edge. |
| "Tests need to reach across domains to set up fixtures." | Test files belong to a domain. Cross-domain test imports need an entry in allowed or a declared test-only edge. |
"The alias @/shared/* makes this look local." | Resolve the alias to the real path; classify by that path. The alias is not a loophole. |
"I'll add the edge to allowed and amend the commit message." | Amendment requires the §9 mutation path, not a silent append. Record rationale and validate. |
| "It's temporary — I'll remove it after the spike." | Temporary edges calcify. Raise the blocker; do not type the import. |
| "The target already imports from my domain, so the edge already exists in reverse." | cycles_forbidden: true. An existing reverse edge makes this forward edge worse, not permitted. |
| "Nobody has defined domains yet, so nothing is cross-domain." | Halt. Surface the gap. Proceeding before domain-map.contract.json is populated violates §8 mode obligations. |
"I'll import the DB driver directly (pg, prisma, etc.) — it's a library, not the persistence domain." | Third-party packages that semantically are the forbidden target count as crossing the edge. The rule binds to structural intent, not the package name. §6 'structural intent matters' applies. Halt. |
| "Health-check is infrastructure — exempt from layer rules by nature." | No CONSTITUTION article exempts diagnostic or infrastructure code from layer rules. If an edge is forbidden, it is forbidden for infrastructure too. §6 admits no infrastructure carve-out. |
| "Make it a middleware — middleware is framework-level plumbing, not a domain module." | Middleware that calls across a forbidden edge violates §6 identically to any other call site. Framework position is not a layering escape. Classify by resolved target, not by hook name. |
"Pull the connection pool from config/ — config/ isn't a declared domain." | Generic containers (config/, runtime/, bootstrap/) laundering forbidden edges is forbidden by §5's generic-container rule and §6's 'structural intent matters' clause. The edge is where the pool is wired, not where the folder sits. |
| "It's a read-only probe with no side effects — a pure read can't create architectural coupling." | Coupling is about dependency direction, not mutation. A read-only call from the higher layer to the lower layer is the same cross-layer dependency as a write. §6 binds reads and writes identically. |
| "The violation was already there. Not my problem." | §11: correct violations at the point of discovery. The discoverer owns the surface-and-record step. Ownership is not transferred by the absence of authorship. |
"I'll add a TODO and fix it later." | §11 forbids TODO-and-proceed explicitly. "Later" never arrives. Halt, record, surface, block. |
| "My change adds no new import — it only uses the existing one." | Adding a caller, consumer, subclass, or data-flow dependency is silently extending the edge. §6 governs structural intent, not import-line authorship. |
| "The test suite passes, so the violation must be intentional." | Runtime success is evidence of what runs, not evidence of what the contract permits. §6 default stance is deny. Contracts declare intent; code is evidence, not permission. |
| "It's a legacy import, grandfathered by age." | No grandfather clause exists. §11 applies at point of discovery regardless of the edge's age. Age is not an amendment. |
"I'll silently refactor around it — move the call through a new helper in shared/." | Reverse-indirection is explicitly forbidden (dependency-rules.contract.json#rules.reverse_indirection_forbidden). A facade in a neutral directory is the edge, re-labelled. |
| "It's only on one call path and nobody will notice." | Visibility is not the enforcement criterion. The edge is in the tree; §11 fires on discovery regardless of traffic. |
| "The contradiction_map already lists something similar, so this is a duplicate entry." | New discovery sites, new callers, and new extension paths are distinct entries. Add the row; do not suppress it. |
| "The task is urgent. Recording this blocks me." | §11: the violation is the task now. Urgency is not a carve-out. Raise the block and surface the trade-off to the user. |
Halt and re-check if any of these appear:
allowed authored in the same diff as the import that needed it, without an Amendment log rowshared/, common/, utils/, lib/, or core/ that imports from a feature domainindex.ts, __init__.py, mod.rs) that re-exports symbols from multiple domains, or symbols owned by a foreign domainXAdapter, XGateway, XBridge, XFacade added under a neutral directory that forwards calls into a foreign domainuser.created, billing.charged) subscribed to from an unrelated domaintsconfig.json/pyproject.toml path alias added in the same diff as the first import using it, or resolving to a forbidden target// eslint-disable, # noqa, @ts-ignore, or # type: ignore on a cross-domain import line.law/bin/validate-contracts, .law/bin/check-coupling, or .law/bin/check-counts invoked with || true, --no-verify, or skipped in CIdependency-rules.contract.json#allowed.law/context/current-system.json#contradiction_map lists the edge and the current task adds a new caller of the same forbidden symbol.law/bin/* output reports a cross-domain violation that is not yet in contradiction_mappg import isn't really crossing the edge because it's a library"config/ — it isn't a declared domain"Primary: .law/contracts/dependency-rules.contract.json (fields: rules.default_stance, rules.cycles_forbidden, rules.reverse_indirection_forbidden, allowed[], forbidden[], agent_directives.before_adding_import, agent_directives.on_discovering_existing_forbidden_edge).
Cross-reference: .law/contracts/domain-map.contract.json (fields: domains[], rules.every_runtime_module_has_exactly_one_domain, rules.generic_containers_require_explicit_domain_entry, rules.forbidden_default_names_without_justification).
Record target: .law/context/current-system.json#contradiction_map — append one entry per discovered edge with source_domain, target_domain, file, line, evidence_or_judgment: "evidence", blocks_task, and resolution (remove | amend | raised-block).
Block target: .law/context/pending-questions.json — append one blocker when resolution requires human input.
Enforcement template: .law/templates/check-dep-direction.example.mjs — adapt the classifyLayer function to the project's path conventions; the contract-reading and violation-reporting scaffold stays fixed.
Validators: .law/bin/validate-contracts, .law/bin/check-coupling (v1.4 real import-graph walker), .law/bin/check-counts. Run after every amendment or contradiction_map update; block on non-zero exit.
Constitution anchor: CONSTITUTION.md §6 (Dependency law), §9 (Mutation rules), §11 (Enforcement).