Data Model: Integration/Core Boundary Sets and Allowlist

Mission: integration-boundary-01KW0PBE ADR: architecture/adrs/2026-05-11-1-defer-391-structural-extraction-from-3-2-x.md


CORE Set

Modules that implement canonical mission lifecycle and governance logic. These modules MUST NOT import from the INTEGRATION set.

PackageRoot pathDescription
coresrc/specify_cli/core/Mission creation, contract gate, dependency graph
statussrc/specify_cli/status/State machine, event log, adapter registry
readinesssrc/specify_cli/readiness/Readiness checks and coordinator
invocationsrc/specify_cli/invocation/Op lifecycle propagation and registry

Enforcement: tests/architectural/test_integration_boundary.py scans all four directories with Path.rglob("*.py") and ast.walk. Any new .py file added to these directories is automatically covered without any test change (FR-001, C-008).

Path constants in test (sanity-checked to prevent vacuous passes):

CORE_PACKAGES = [
    SRC / "specify_cli" / "core",
    SRC / "specify_cli" / "status",
    SRC / "specify_cli" / "readiness",
    SRC / "specify_cli" / "invocation",
]

INTEGRATION Set

Modules that connect spec-kitty to external systems. These modules MAY import from the CORE set (allowed direction).

PackageRoot pathDescription
orchestrator_apisrc/specify_cli/orchestrator_api/Orchestrator HTTP client
syncsrc/specify_cli/sync/Real-time SaaS sync, WebSocket, OfflineQueue
trackersrc/specify_cli/tracker/Issue-tracker origin binding
saassrc/specify_cli/saas/SaaS-specific feature flags and rollout
saas_clientsrc/specify_cli/saas_client/SaaS REST client

Pattern constant in test:

INTEGRATION_PREFIXES = [
    "specify_cli.orchestrator_api",
    "specify_cli.sync",
    "specify_cli.tracker",
    "specify_cli.saas",
    "specify_cli.saas_client",
]

Out-of-Scope Modules (C-004)

The following modules are NOT classified in either set for this mission. Their import patterns are NOT checked by test_integration_boundary.py.

PackageRoot pathReason for exclusion
coordinationsrc/specify_cli/coordination/Deferred (C-004)
lanessrc/specify_cli/lanes/Deferred (C-004)
runtimesrc/specify_cli/runtime/ and src/runtime/Deferred (C-004)

Allowlist Exemptions

The enforcement test includes a controlled allowlist. Each entry permits one specific (source_module, imported_module) pair and carries a written rationale.

Exemption 1 — Leak #2 (the only entry)

FieldValue
Source modulesrc/specify_cli/readiness/coordinator.py
Imported modulespecify_cli.saas.rollout
Import formLazy function-body: from specify_cli.saas.rollout import is_saas_sync_enabled at line 237
Rationalesaas/rollout.py acts as a shared-config module (shared-config v1). is_saas_sync_enabled is a pure feature-flag read with no side effects; it is not a structural SaaS dependency. The flag will be relocated to a core/kernel config module in a follow-up mission. Exempted until that relocation lands.
Follow-upPlanned relocation of is_saas_sync_enabled to a core config module (no issue number yet; tracked in architecture/ docs).

No other exemptions exist after WP01–WP03 fix all three leaks. If a new exemption is needed, it must be added via an explicit edit to test_integration_boundary.py with a written rationale, not by broadening the allowlist entry above.


New Modules Introduced

src/specify_cli/invocation/adapters.py

Registry for sync-routing resolver and SaaS-client factory, following the status/adapters.py idiom exactly.

_sync_routing_resolver: Callable[[Path], bool | None] | None
_saas_client_factory:   Callable[[Path], Any | None]  | None

register_sync_routing_resolver(fn)  → idempotent by qualified name
register_saas_client_factory(fn)    → idempotent by qualified name
resolve_sync_routing(path: Path) → bool | None   # None = no preference registered
get_saas_client(path: Path) → Any | None          # None = no client available
reset_adapters()                    → test-only

Invariants:

  • Non-raising: both dispatch functions catch all exceptions and return None on error.
  • Idempotent: re-registration of same qualified name replaces existing entry.
  • No third-party imports (C-007).
  • No imports from INTEGRATION set (C-007 / one-directional rule).

src/specify_cli/core/adapters.py

Registry for the pending-origin consumer, exposing a result-bearing callable slot (needed because origin_binding_* fields in MissionCreationResult must be preserved for CLI output).

PendingOriginConsumer = Callable[
    [Path, Path, dict[str, Any]],
    tuple[bool, bool, str | None, dict[str, Any]]
]
# Signature: (repo_root, feature_dir, meta) -> (attempted, succeeded, error_msg, updated_meta)

_origin_consumer: PendingOriginConsumer | None

register_pending_origin_consumer(fn: PendingOriginConsumer)  → idempotent
consume_pending_origin(repo_root, feature_dir, meta) → tuple[bool, bool, str|None, dict]
  # Returns (False, False, None, meta) when no consumer is registered (no-op default)
reset_origin_consumer()  → test-only

Invariants:

  • Non-raising: failure in the consumer is caught; returns (True, False, str(exc), meta).
  • No imports from INTEGRATION set.

src/specify_cli/tracker/origin_consumer.py

Concrete implementation of PendingOriginConsumer. Contains the logic extracted from core/mission_creation.py::_consume_pending_origin_if_present verbatim. Registered in tracker/__init__.py startup hook.

def consume_pending_origin_impl(
    repo_root: Path,
    feature_dir: Path,
    meta: dict[str, Any],
) -> tuple[bool, bool, str | None, dict[str, Any]]:
    """Implementation of PendingOriginConsumer — moves tracker imports here."""
    from specify_cli.tracker.origin import OriginBindingError, bind_mission_origin
    from specify_cli.tracker.origin_models import OriginCandidate
    from specify_cli.tracker.ticket_context import clear_pending_origin, read_pending_origin
    # ... (extracted logic, identical behaviour)

Adapter Registry Comparison

RegistryModuleHandler typeRegistration siteNon-raisingIdempotent
Dossier-syncstatus/adapters.pyDossierSyncHandlersync/__init__.pyYesYes
SaaS fanoutstatus/adapters.pySaasFanOutHandlersync/__init__.pyYesYes
Lifecycle SaaS fanoutstatus/adapters.pyLifecycleSaasFanOutHandlersync/__init__.pyYesYes
Sync-routing resolverinvocation/adapters.py`Callable[[Path], boolNone]`sync/__init__.pyYes
SaaS-client factoryinvocation/adapters.py`Callable[[Path], AnyNone]`sync/__init__.pyYes
Pending-origin consumercore/adapters.pyPendingOriginConsumertracker/__init__.pyYesYes