Skip to content

symfonic.devtools.ownership

ownership

Task-and-module ownership manifest tooling (T1.2.7).

Loads the rolling ownership manifest (.agent/team/framework-refactor/evidence/T1.2.7/ownership-manifest.toml) and validates that the wave being scheduled has gate-approved, exact, non-overlapping write ownership. Run python -m symfonic.devtools.ownership from the repository root, or rely on tests/architecture/test_ownership_manifest.py inside the pytest gate.

ManifestError

Bases: ValueError

The manifest file is structurally invalid.

OwnershipManifest dataclass

OwnershipManifest(path: Path, authoritative_waves: tuple[str, ...], scheduled_wave: str, waves: dict[str, tuple[str, ...]], tasks: dict[str, TaskRow], plan: str = '')

The parsed rolling manifest: wave rosters plus per-task rows.

OwnershipViolation dataclass

OwnershipViolation(rule: str, message: str, tasks: tuple[str, ...] = tuple())

One validation failure; tasks lists every implicated task ID.

TaskRow dataclass

TaskRow(task_id: str, wave: str, status: str, title: str = '', agent: str = '', complete: bool = False, current: tuple[str, ...] = (), target: tuple[str, ...] = (), exclusive_write: tuple[str, ...] = (), shared_read: tuple[str, ...] = (), split_points: tuple[str, ...] = (), handoff: tuple[str, ...] = ())

One manifest row: what a plan task may read, write, and hand off.

exclusive_write globs are the only paths the task may create or modify; shared_read names the read-only contracts it consumes; handoff lists the task IDs whose outputs it builds on (the plan's depends_on edges, which also order tasks for the overlap check). Rows with status advisory are speculative sketches for later waves and are never treated as authoritative.

load_manifest

load_manifest(path: Path) -> OwnershipManifest

Parse path into an :class:OwnershipManifest, or raise ManifestError.

Source code in src/symfonic/devtools/ownership/loader.py
def load_manifest(path: Path) -> OwnershipManifest:
    """Parse *path* into an :class:`OwnershipManifest`, or raise ManifestError."""
    with open(path, "rb") as handle:
        try:
            data = tomllib.load(handle)
        except tomllib.TOMLDecodeError as exc:
            raise ManifestError(f"{path}: invalid TOML: {exc}") from exc
    _check_keys(data, _TOP_KEYS, str(path))
    header = data.get("manifest")
    if not isinstance(header, dict):
        raise ManifestError(f"{path}: missing [manifest] table")
    _check_keys(header, _HEADER_KEYS, "manifest")
    scheduled = header.get("scheduled_wave")
    if not isinstance(scheduled, str) or not scheduled:
        raise ManifestError("manifest: 'scheduled_wave' is required")
    authoritative = _str_list(
        header.get("authoritative_waves"), "manifest.authoritative_waves"
    )
    raw_waves = data.get("waves")
    if not isinstance(raw_waves, dict) or not raw_waves:
        raise ManifestError(f"{path}: missing [waves] table")
    waves = {name: _str_list(roster, f"waves.{name}") for name, roster in raw_waves.items()}
    raw_tasks = data.get("tasks")
    if not isinstance(raw_tasks, dict) or not raw_tasks:
        raise ManifestError(f"{path}: missing [tasks.*] tables")
    tasks = {task_id: _parse_row(task_id, row) for task_id, row in raw_tasks.items()}
    return OwnershipManifest(
        path=path,
        authoritative_waves=authoritative,
        scheduled_wave=scheduled,
        waves=waves,
        tasks=tasks,
        plan=str(header.get("plan", "")),
    )

patterns_overlap

patterns_overlap(a: str, b: str) -> bool

True when glob patterns a and b could both match some path.

Source code in src/symfonic/devtools/ownership/overlap.py
def patterns_overlap(a: str, b: str) -> bool:
    """True when glob patterns *a* and *b* could both match some path."""
    return _overlap(_segments(a), _segments(b))

validate_manifest

validate_manifest(manifest: OwnershipManifest, wave: str | None = None) -> list[OwnershipViolation]

Validate manifest for scheduling wave (default: its scheduled wave).

Source code in src/symfonic/devtools/ownership/validate.py
def validate_manifest(
    manifest: OwnershipManifest, wave: str | None = None
) -> list[OwnershipViolation]:
    """Validate *manifest* for scheduling *wave* (default: its scheduled wave)."""
    scheduled = wave or manifest.scheduled_wave
    violations: list[OwnershipViolation] = []
    violations.extend(_referential_rules(manifest))
    violations.extend(_authority_rules(manifest))
    violations.extend(_scheduled_wave_rules(manifest, scheduled))
    violations.extend(_overlap_rules(manifest))
    return violations