generated from john/python-template
@@ -127,7 +127,7 @@ Benchmark material containing family records remains private application data un
|
|||||||
|
|
||||||
## 6. Ownership and Change Policy
|
## 6. Ownership and Change Policy
|
||||||
|
|
||||||
1. Canonical V4 architecture, schema, requirements, and error-policy documents define how current behavior satisfies this invariant.
|
1. Canonical V6.1 architecture, schema, requirements, and error-policy documents define how current behavior satisfies this invariant.
|
||||||
2. Provider adapters own the capture of provider-boundary evidence.
|
2. Provider adapters own the capture of provider-boundary evidence.
|
||||||
3. Services own validation, persistence, retention, and export behavior.
|
3. Services own validation, persistence, retention, and export behavior.
|
||||||
4. UI pages may inspect evidence through service contracts but do not define evidence semantics.
|
4. UI pages may inspect evidence through service contracts but do not define evidence semantics.
|
||||||
|
|||||||
@@ -64,7 +64,10 @@ BASELINE_SCAN_EXCLUSIONS = frozenset(
|
|||||||
)
|
)
|
||||||
|
|
||||||
_BASELINE_DECLARATION = re.compile(r"Current Baseline:\s*V(\d+\.\d+)")
|
_BASELINE_DECLARATION = re.compile(r"Current Baseline:\s*V(\d+\.\d+)")
|
||||||
_CURRENT_VERSION_CLAIM = re.compile(r"\b(?:current|active)\s+V(\d+\.\d+)", re.IGNORECASE)
|
_BASELINE_CLAIM = re.compile(
|
||||||
|
r"\b(?:Current Baseline:\s*|current\s+|active\s+|canonical\s+)V(\d+(?:\.\d+)?)",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _declared_baseline() -> str:
|
def _declared_baseline() -> str:
|
||||||
@@ -98,12 +101,7 @@ def test_canonical_docs_declare_one_consistent_baseline():
|
|||||||
for path in _baseline_scanned_docs():
|
for path in _baseline_scanned_docs():
|
||||||
relative = path.relative_to(PROJECT_ROOT).as_posix()
|
relative = path.relative_to(PROJECT_ROOT).as_posix()
|
||||||
text = path.read_text(encoding="utf-8")
|
text = path.read_text(encoding="utf-8")
|
||||||
stale = {
|
stale = {version for version in _BASELINE_CLAIM.findall(text) if version != baseline}
|
||||||
version
|
|
||||||
for pattern in (_BASELINE_DECLARATION, _CURRENT_VERSION_CLAIM)
|
|
||||||
for version in pattern.findall(text)
|
|
||||||
if version != baseline
|
|
||||||
}
|
|
||||||
if stale:
|
if stale:
|
||||||
violations[relative] = sorted(stale)
|
violations[relative] = sorted(stale)
|
||||||
|
|
||||||
|
|||||||
+111
-28
@@ -22,6 +22,7 @@ SOURCE_DIR = PROJECT_ROOT / "src" / "transcription"
|
|||||||
|
|
||||||
# Every tree that may legitimately consume package API.
|
# Every tree that may legitimately consume package API.
|
||||||
REFERENCE_ROOTS = (SOURCE_DIR, PROJECT_ROOT / "tests", PROJECT_ROOT / "tools")
|
REFERENCE_ROOTS = (SOURCE_DIR, PROJECT_ROOT / "tests", PROJECT_ROOT / "tools")
|
||||||
|
MODULE_REFERENCE_ROOTS = (SOURCE_DIR, PROJECT_ROOT / "tools")
|
||||||
|
|
||||||
# Decorators that hand a callable to a framework registry, making the definition
|
# Decorators that hand a callable to a framework registry, making the definition
|
||||||
# reachable without any in-repo reference to its name.
|
# reachable without any in-repo reference to its name.
|
||||||
@@ -31,62 +32,70 @@ REGISTRATION_DECORATOR_PREFIXES = ("router.", "app.", "ui.page")
|
|||||||
# rationale. Removing the code means removing the entry; adding an entry means an
|
# rationale. Removing the code means removing the entry; adding an entry means an
|
||||||
# explicit decision to keep unreferenced code.
|
# explicit decision to keep unreferenced code.
|
||||||
KNOWN_ORPHANS: dict[str, str] = {
|
KNOWN_ORPHANS: dict[str, str] = {
|
||||||
"DocumentService.is_document_type_referenced": (
|
"src/transcription/services/documents.py::DocumentService.is_document_type_referenced": (
|
||||||
"Read helper currently bypassed by callers in favor of direct delete guards. "
|
"Read helper currently bypassed by callers in favor of direct delete guards. "
|
||||||
"Retained for now to preserve service API stability during cleanup."
|
"Retained for now to preserve service API stability during cleanup."
|
||||||
),
|
),
|
||||||
"DocumentService.is_tag_referenced": (
|
"src/transcription/services/documents.py::DocumentService.is_tag_referenced": (
|
||||||
"Read helper currently bypassed by callers in favor of direct delete guards. "
|
"Read helper currently bypassed by callers in favor of direct delete guards. "
|
||||||
"Retained for now to preserve service API stability during cleanup."
|
"Retained for now to preserve service API stability during cleanup."
|
||||||
),
|
),
|
||||||
"DocumentService.read_document": (
|
"src/transcription/services/documents.py::DocumentService.read_document": (
|
||||||
"Public CRUD read method currently unused by runtime routes, but retained as "
|
"Public CRUD read method currently unused by runtime routes, but retained as "
|
||||||
"part of the service API shape for downstream call sites."
|
"part of the service API shape for downstream call sites."
|
||||||
),
|
),
|
||||||
"DocumentService.read_document_type": (
|
"src/transcription/services/documents.py::DocumentService.read_document_type": (
|
||||||
"Public CRUD read method currently unused by runtime routes, but retained as "
|
"Public CRUD read method currently unused by runtime routes, but retained as "
|
||||||
"part of the service API shape for downstream call sites."
|
"part of the service API shape for downstream call sites."
|
||||||
),
|
),
|
||||||
"DocumentService.read_tag": (
|
"src/transcription/services/documents.py::DocumentService.read_tag": (
|
||||||
"Public CRUD read method currently unused by runtime routes, but retained as "
|
"Public CRUD read method currently unused by runtime routes, but retained as "
|
||||||
"part of the service API shape for downstream call sites."
|
"part of the service API shape for downstream call sites."
|
||||||
),
|
),
|
||||||
"JSONBCompat.load_dialect_impl": (
|
"src/transcription/services/jobs.py::JobService.delete_job": (
|
||||||
"SQLAlchemy type hook is invoked by ORM internals via subclass protocol rather "
|
|
||||||
"than direct in-repo references; keep this implementation method."
|
|
||||||
),
|
|
||||||
"JobService.delete_job": (
|
|
||||||
"Legacy delete method retained as a compatibility shim while guarded deletion "
|
"Legacy delete method retained as a compatibility shim while guarded deletion "
|
||||||
"flows migrate fully to delete_job_with_guardrails."
|
"flows migrate fully to delete_job_with_guardrails."
|
||||||
),
|
),
|
||||||
"JobService.update_job": (
|
"src/transcription/services/jobs.py::JobService.update_job": (
|
||||||
"Legacy update method retained for compatibility while route and workflow "
|
"Legacy update method retained for compatibility while route and workflow "
|
||||||
"callers continue converging on narrower state-transition APIs."
|
"callers continue converging on narrower state-transition APIs."
|
||||||
),
|
),
|
||||||
"PeopleService.is_person_role_referenced": (
|
"src/transcription/services/people.py::PeopleService.is_person_role_referenced": (
|
||||||
"Reference-check helper currently not called by route workflows, but kept with "
|
"Reference-check helper currently not called by route workflows, but kept with "
|
||||||
"the service surface while role-management cleanup remains in progress."
|
"the service surface while role-management cleanup remains in progress."
|
||||||
),
|
),
|
||||||
"PeopleService.read_person": (
|
"src/transcription/services/people.py::PeopleService.read_person": (
|
||||||
"Public CRUD read method currently unused by runtime routes, but retained as "
|
"Public CRUD read method currently unused by runtime routes, but retained as "
|
||||||
"part of the service API shape for downstream call sites."
|
"part of the service API shape for downstream call sites."
|
||||||
),
|
),
|
||||||
"PeopleService.read_person_role": (
|
"src/transcription/services/people.py::PeopleService.read_person_role": (
|
||||||
"Public CRUD read method currently unused by runtime routes, but retained as "
|
"Public CRUD read method currently unused by runtime routes, but retained as "
|
||||||
"part of the service API shape for downstream call sites."
|
"part of the service API shape for downstream call sites."
|
||||||
),
|
),
|
||||||
"Settings.normalize_provider_models": (
|
"src/transcription/config.py::Settings.normalize_provider_models": (
|
||||||
"Pydantic model-level validator is executed by framework hooks using decorator "
|
"Pydantic model-level validator is executed by framework hooks using decorator "
|
||||||
"registration and therefore has no direct symbolic call site."
|
"registration and therefore has no direct symbolic call site."
|
||||||
),
|
),
|
||||||
"Settings.validate_provider_models_input": (
|
"src/transcription/config.py::Settings.validate_provider_models_input": (
|
||||||
"Pydantic field validator is executed by framework hooks using decorator "
|
"Pydantic field validator is executed by framework hooks using decorator "
|
||||||
"registration and therefore has no direct symbolic call site."
|
"registration and therefore has no direct symbolic call site."
|
||||||
),
|
),
|
||||||
"SourceService.read_job_source_for_job": (
|
"src/transcription/benchmarking.py": (
|
||||||
|
"Retained as the evaluation-policy implementation for scoring preserved execution "
|
||||||
|
"attempts, even though application runtime paths do not import it directly."
|
||||||
|
),
|
||||||
|
"src/transcription/db/models.py::JSONBCompat.load_dialect_impl": (
|
||||||
|
"SQLAlchemy type hook is invoked by ORM internals via subclass protocol rather "
|
||||||
|
"than direct in-repo references; keep this implementation method."
|
||||||
|
),
|
||||||
|
"src/transcription/services/sources.py::SourceService.read_job_source_for_job": (
|
||||||
"Public read helper currently unused by runtime flows, but retained as part of "
|
"Public read helper currently unused by runtime flows, but retained as part of "
|
||||||
"the SourceService API pending endpoint consolidation."
|
"the SourceService API pending endpoint consolidation."
|
||||||
),
|
),
|
||||||
|
"src/transcription/ui/pages/tags_page.py": (
|
||||||
|
"Retained temporarily as explicitly dead code until the planned route-retirement "
|
||||||
|
"cleanup deletes the stranded module."
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -94,6 +103,22 @@ def _source_files() -> list[Path]:
|
|||||||
return sorted(SOURCE_DIR.rglob("*.py"))
|
return sorted(SOURCE_DIR.rglob("*.py"))
|
||||||
|
|
||||||
|
|
||||||
|
def _entrypoint_modules() -> set[str]:
|
||||||
|
return {
|
||||||
|
"src/transcription/app.py",
|
||||||
|
"src/transcription/__main__.py",
|
||||||
|
"src/transcription/worker_service.py",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _module_key(path: Path) -> str:
|
||||||
|
return path.relative_to(PROJECT_ROOT).as_posix()
|
||||||
|
|
||||||
|
|
||||||
|
def _leaf_definition_name(qualified_name: str) -> str:
|
||||||
|
return qualified_name.split("::", 1)[-1].split(".")[-1]
|
||||||
|
|
||||||
|
|
||||||
def _is_registered_with_framework(node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) -> bool:
|
def _is_registered_with_framework(node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) -> bool:
|
||||||
return any(ast.unparse(decorator).startswith(REGISTRATION_DECORATOR_PREFIXES) for decorator in node.decorator_list)
|
return any(ast.unparse(decorator).startswith(REGISTRATION_DECORATOR_PREFIXES) for decorator in node.decorator_list)
|
||||||
|
|
||||||
@@ -108,16 +133,15 @@ def _public_definitions() -> dict[str, str]:
|
|||||||
continue
|
continue
|
||||||
if node.name.startswith("_") or _is_registered_with_framework(node):
|
if node.name.startswith("_") or _is_registered_with_framework(node):
|
||||||
continue
|
continue
|
||||||
definitions[node.name] = f"{path.relative_to(PROJECT_ROOT).as_posix()}:{node.lineno}"
|
module = _module_key(path)
|
||||||
|
definitions[f"{module}::{node.name}"] = f"{module}:{node.lineno}"
|
||||||
if isinstance(node, ast.ClassDef):
|
if isinstance(node, ast.ClassDef):
|
||||||
for method in node.body:
|
for method in node.body:
|
||||||
if not isinstance(method, ast.FunctionDef | ast.AsyncFunctionDef):
|
if not isinstance(method, ast.FunctionDef | ast.AsyncFunctionDef):
|
||||||
continue
|
continue
|
||||||
if method.name.startswith("_"):
|
if method.name.startswith("_"):
|
||||||
continue
|
continue
|
||||||
definitions[f"{node.name}.{method.name}"] = (
|
definitions[f"{module}::{node.name}.{method.name}"] = f"{module}:{method.lineno}"
|
||||||
f"{path.relative_to(PROJECT_ROOT).as_posix()}:{method.lineno}"
|
|
||||||
)
|
|
||||||
return definitions
|
return definitions
|
||||||
|
|
||||||
|
|
||||||
@@ -146,19 +170,72 @@ def _referenced_names() -> tuple[set[str], str]:
|
|||||||
return names, "\n".join(literals)
|
return names, "\n".join(literals)
|
||||||
|
|
||||||
|
|
||||||
|
def _imported_source_modules() -> set[str]:
|
||||||
|
imported: set[str] = set()
|
||||||
|
|
||||||
|
def _mark_module_and_packages(module_path: Path) -> None:
|
||||||
|
imported.add(_module_key(module_path))
|
||||||
|
for parent in module_path.parents:
|
||||||
|
package_init = parent / "__init__.py"
|
||||||
|
if package_init.exists() and package_init.is_relative_to(SOURCE_DIR):
|
||||||
|
imported.add(_module_key(package_init))
|
||||||
|
|
||||||
|
for root in MODULE_REFERENCE_ROOTS:
|
||||||
|
for path in sorted(root.rglob("*.py")):
|
||||||
|
tree = ast.parse(path.read_text(encoding="utf-8"))
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if not isinstance(node, ast.ImportFrom) or not node.module:
|
||||||
|
continue
|
||||||
|
if node.level > 0:
|
||||||
|
anchor = path.parent
|
||||||
|
for _ in range(node.level - 1):
|
||||||
|
anchor = anchor.parent
|
||||||
|
target = anchor / Path(*node.module.split("."))
|
||||||
|
file_candidate = target.with_suffix(".py")
|
||||||
|
package_candidate = target / "__init__.py"
|
||||||
|
if file_candidate.exists():
|
||||||
|
_mark_module_and_packages(file_candidate)
|
||||||
|
elif package_candidate.exists():
|
||||||
|
_mark_module_and_packages(package_candidate)
|
||||||
|
continue
|
||||||
|
module_path = Path(*node.module.split("."))
|
||||||
|
if not module_path.parts or module_path.parts[0] != "transcription":
|
||||||
|
continue
|
||||||
|
target = SOURCE_DIR / Path(*module_path.parts[1:])
|
||||||
|
file_candidate = target.with_suffix(".py")
|
||||||
|
package_candidate = target / "__init__.py"
|
||||||
|
if file_candidate.exists():
|
||||||
|
_mark_module_and_packages(file_candidate)
|
||||||
|
elif package_candidate.exists():
|
||||||
|
_mark_module_and_packages(package_candidate)
|
||||||
|
return imported
|
||||||
|
|
||||||
|
|
||||||
|
def _orphan_modules() -> dict[str, str]:
|
||||||
|
imported = _imported_source_modules()
|
||||||
|
entrypoints = _entrypoint_modules()
|
||||||
|
return {
|
||||||
|
module: module
|
||||||
|
for path in _source_files()
|
||||||
|
if (module := _module_key(path)) not in imported and module not in entrypoints
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _orphans() -> dict[str, str]:
|
def _orphans() -> dict[str, str]:
|
||||||
definitions = _public_definitions()
|
definitions = _public_definitions()
|
||||||
names, literal_blob = _referenced_names()
|
names, literal_blob = _referenced_names()
|
||||||
|
orphan_modules = set(_orphan_modules())
|
||||||
return {
|
return {
|
||||||
name: location
|
name: location
|
||||||
for name, location in definitions.items()
|
for name, location in definitions.items()
|
||||||
|
if name.split("::", 1)[0] not in orphan_modules
|
||||||
# A definition is referenced if its name is used directly, or appears inside a
|
# A definition is referenced if its name is used directly, or appears inside a
|
||||||
# string such as "transcription.__main__:create_cli_app".
|
# string such as "transcription.__main__:create_cli_app".
|
||||||
if (
|
if (
|
||||||
name not in names
|
name not in literal_blob
|
||||||
and name not in literal_blob
|
and name.split("::", 1)[-1] not in literal_blob
|
||||||
and name.split(".")[-1] not in names
|
and _leaf_definition_name(name) not in names
|
||||||
and name.split(".")[-1] not in literal_blob
|
and _leaf_definition_name(name) not in literal_blob
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,8 +243,8 @@ def _orphans() -> dict[str, str]:
|
|||||||
def test_public_definitions_are_discovered():
|
def test_public_definitions_are_discovered():
|
||||||
"""Guard the guard: the sweep is meaningless if nothing is scanned."""
|
"""Guard the guard: the sweep is meaningless if nothing is scanned."""
|
||||||
definitions = _public_definitions()
|
definitions = _public_definitions()
|
||||||
assert len(definitions) >= 420
|
assert "src/transcription/app.py::create_app" in definitions
|
||||||
assert "create_app" in definitions
|
assert "src/transcription/services/documents.py::DocumentService.create_document" in definitions
|
||||||
|
|
||||||
|
|
||||||
def test_framework_registered_routes_are_exempt():
|
def test_framework_registered_routes_are_exempt():
|
||||||
@@ -183,9 +260,15 @@ def test_no_unexpected_orphaned_definitions():
|
|||||||
assert unexpected == {}
|
assert unexpected == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_unexpected_orphaned_modules():
|
||||||
|
"""Dead modules must be removed or recorded explicitly with a rationale."""
|
||||||
|
unexpected = {name: location for name, location in _orphan_modules().items() if name not in KNOWN_ORPHANS}
|
||||||
|
assert unexpected == {}
|
||||||
|
|
||||||
|
|
||||||
def test_known_orphans_are_still_orphaned():
|
def test_known_orphans_are_still_orphaned():
|
||||||
"""Keep the allowlist honest: an entry that regained callers must be removed."""
|
"""Keep the allowlist honest: an entry that regained callers must be removed."""
|
||||||
current = set(_orphans())
|
current = set(_orphans()) | set(_orphan_modules())
|
||||||
stale = sorted(name for name in KNOWN_ORPHANS if name not in current)
|
stale = sorted(name for name in KNOWN_ORPHANS if name not in current)
|
||||||
assert stale == [], "these definitions are referenced again; drop them from KNOWN_ORPHANS"
|
assert stale == [], "these definitions are referenced again; drop them from KNOWN_ORPHANS"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user