Contracts
seam-decisions.md
Contract: seam decisions the implement phases call
This is the internal interface contract between the implement phase sequence (command package) and the seams. Names are proposals; a WP may rename a function if it records the final name here and in its tracer entry. Signatures and behaviour are binding.
"Moved verbatim" means the body is unchanged except for removing printing and typer.Exit in favour of the typed error named here. That second step happens in the adjust commit (C-002).
core/dependency_graph.py
def ensure_wp_claim_preconditions(
wp_id: str,
declared_deps: Sequence[str] | Any,
work_packages: Mapping[str, Mapping[str, Any]], # reduced snapshot .work_packages
) -> None:
"""Raise WorkPackageStartRejected for an unseeded (genesis) WP, or
ValueError('dependencies_not_satisfied: ...') when a dependency is not
approved/done. Pure: no event I/O (the caller reads + reduces)."""
- The lane map is built exactly as today:
state.get("lane", Lane.GENESIS). - Provenance is threaded as today:
provenance=work_packages.
workspace/context.py
def find_wp_file(repo_root: Path, mission_slug: str, wp_id: str) -> Path: ... # moved verbatim
def resolve_lane_state_dir(repo_root: Path, mission_slug: str) -> Path: ... # was implement._resolve_lanes_dir (distinct from lanes.persistence.resolve_lanes_dir)
def resolve_mission_target_branch(mission_slug: str, repo_root: Path) -> str: ... # was resolve_feature_target_branch
- Same exceptions (
FileNotFoundErrortexts) as today. find_wp_filekeeps an__all__entry, and its now-stale dead-symbol allow-list row is removed (the symbol is live through__all__).
lanes/implement_support.py
class BaseRefUnresolved(StructuredError): base_ref: str
def resolve_base_ref(repo_root: Path, base_ref: str) -> tuple[str, str] | None: ... # origin-preferred (#4969), verbatim
# _validate_base_ref / _raise_base_ref_unresolved stay CLI-side as thin translators (tests pin typer.Exit)
def resolve_effective_base(repo_root: Path, base: str | None, resolved_workspace) -> tuple[str | None, bool]:
"""(effective_base, ignored_on_planning_lane). Raises BaseRefUnresolved."""
def resolve_execution_lane(resolved_workspace, lanes_feature_dir: Path, wp_id: str) -> tuple[LanesManifest | None, ExecutionLane | None]:
"""(None, None) for a repo-root planning workspace; raises ValueError / MissingLanesError as today."""
def refuse_repo_root_checkout_if_unavailable(repo_root, mission_slug, wp_id, resolved_workspace) -> bool: ...
def ensure_vcs_locked(feature_dir: Path) -> bool: # True when it wrote the lock; typed errors for missing / invalid meta.json
def git_stdout(repo_root: Path, args: list[str]) -> str: ... # was implement._git_stdout (moved by WP05); not lifecycle_sync._git_stdout
_BASE_REF_UNRESOLVED_MSG.
- The command keeps the tracker step text, the "--base is ignored …" warning and
- The
→ VCS locked to git in meta.jsonline keeps its order relative to the other output.
coordination/planning_commit.py (new sibling in the existing package)
def partition_files_for_commit(files: list[str]) -> tuple[list[str], list[str]]: ... # verbatim
def guard_planning_commit_partition(files: list[str], *, destination_is_coord: bool) -> None: # verbatim, raises PrimaryKindReachedCoordStagingError
def meta_json_demotion_refusal(repo_root, mission_slug, meta_path, rel_path) -> str | None: ... # verbatim (+ _read_json_at_ref)
def resolve_bookkeeping_transaction_identifiers(feature_dir, mission_slug, repo_root=None) -> BookkeepingTransactionIdentifiers # C-006 5-tuple
def feature_dir_file_paths(repo_root: Path, feature_dir: Path) -> list[str]: ... # verbatim, raises SafeCommitPathPolicyError
def planning_artifact_source_dir(repo_root: Path, feature_dir: Path, mission_slug: str) -> Path: ...
_ensure_planning_artifacts_committed_git, _commit_planning_artifacts_transaction, _run_planning_artifact_commit, the print helpers, _refuse_on_unreadable_planning_status and _planning_commit_branch.
- No
console,typerorcliimports. - The adapter
cli/commands/implement_planning_commit.pykeeps the following unchanged:
status facade
def claim_policy_metadata(shell_pid: int, agent: str) -> dict[str, Any]: ... # one definition, used by implement and workflow_executor
cli/commands/implement_claim.py
def claim_commit_paths(*, repo_root, feature_dir, wp_file, status_artifacts: Iterable[Path], routes_through_coord: bool) -> list[Path]:
"""Pure: the exact ordered path tuple the claim commit stages today (wp_file, filtered status
artifacts, meta.json if present, .kittify/config.yaml if present). #5673 changes only this."""
Placement (FR-008, research.md R-1b = B2**)
In coordination/planning_commit.py:
@dataclass(frozen=True)
class PlanningPlacement:
"""resolved=True -> ref is the context placement ref (artifact_placement.placement_ref);
resolved=False -> the WP action context did not resolve (ActionContextError); the adapter asks
the seam for the declared coordination ref *after* the structural check."""
resolved: bool
ref: CommitTarget | None # set iff resolved
def resolve_planning_placement(repo_root, *, mission_slug, wp_id) -> PlanningPlacement:
"""Context first; non-ActionContextError errors propagate unchanged (today's pre-commit gate)."""
def declared_coordination_ref(feature_dir, mission_slug, repo_root) -> str | None:
"""The seam-owned degrade (#5232 shape 2): the mission's declared coordination-branch value via
the identity cascade. No topology gate, no existence probe, never raises on a missing branch.
Equal by construction to the value today's adapter read."""
def coordination_filter(repo_root, mission_slug, placement, *, feature_dir) -> str | None:
"""resolved -> placement_coord_filter(ref); unresolved -> declared_coordination_ref(...).
Called by the adapter where today's code computed `coord_branch_for_filter` (after the
#1598 structural check)."""
outcomes as today.
it is raised today (arm b).
- The adapter's arms are keyed on
placement.resolvedand the filter value, with the same four PlacementResolutionRequiredkeeps its single remedy definition (FR-018) and is raised only where