Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,26 +83,34 @@ checker walks the AST of every changed `.py` file under each rule's
`scope_root` and emits `layering-rule-violation` for any `import` or
`from … import` of the forbidden top-level package or its submodules.

### 2. 600-line cap on non-test source files
### 2. 500-line cap on non-test source files

No non-test, non-generated `.py` file under `implementations/python/packages/`
shall exceed 600 lines, except for paths listed in
shall exceed 500 lines, except for paths listed in
`tools/policy/oversized_allowlist.yaml`.

The 500-line value matches SonarCloud's S104 threshold, so a file either
passes both the repo policy and the Sonar quality gate or fails both; there
is no longer a 500–599 band that is repo-compliant but blocks the
`dev → main` integration PR. (The cap was originally 600; see Amendments.)

The allowlist is the inverse of the goal: each entry is technical debt to
be drained. Each child PR of #3 splits its file and removes the entry.
The tracker closes when the allowlist is empty.
be drained. A split PR splits its file and removes the entry. The
modularity tracker closes when the allowlist is empty.

The fixed reference set — the 14 paths over the cap when this ADR landed —
is a *code constant*, `_ADR015_INITIAL_OVERSIZED_FILES` in
The fixed reference set — the paths over the cap when the current cap was
adopted — is a *code constant*, `_ADR015_INITIAL_OVERSIZED_FILES` in
`tools/policy/repo_policy.py`, **not** config in `adr_policy.yaml`. Keeping
the locked set in PR-editable config would let one PR satisfy "the allowlist
only shrinks" by editing the allowlist and the locked set together; pinning
it in the policy module means adding a 15th entry requires a diff to the
it in the policy module means adding an entry requires a diff to the
checker code, which PR review scrutinises as policy rather than as data
noise. The allowlist must be a subset of that constant — no new entry may be
added; a file over the cap that wasn't one of the initial set must be split,
not allow-listed. Enforcement:
added; a file over the cap that wasn't one of the reference set must be
split, not allow-listed. Lowering the cap re-baselines both the constant and
the allowlist to the files over the new cap at that time (a code-constant
diff, PR-scrutinised the same way); the shrink-only rule then continues from
the new baseline. Enforcement:

- `_check_oversized` — emits `oversized-source-file` for an over-cap,
non-allowlisted changed file.
Expand Down Expand Up @@ -170,14 +178,14 @@ defensively. That would be a separate ADR.
- The dependency between `aces_sdl` and `aces_processor` is a DAG,
enforced by CI.
- Each subsequent file split inherits the layering invariant for free.
- The cap creates a forcing function: a file that wants to grow past 600
- The cap creates a forcing function: a file that wants to grow past 500
lines must justify the split or the allowlist entry.
- The allowlist is self-documenting debt; its size measures remaining
modularity work.

### Negative

- Fourteen subsequent PRs are needed to drain the allowlist.
- A subsequent PR is needed to drain each allowlist entry.
- `docs/api/processor-semantics.rst` is rescoped to planner only; a new
`docs/api/sdl-semantics.rst` documents the moved modules. Bookmarks to
the processor-semantics page still resolve, to a smaller page.
Expand All @@ -188,6 +196,12 @@ defensively. That would be a separate ADR.
re-introduce a layering bug the current rule doesn't capture. Mitigation:
each new package introduction should consider whether it needs its own
`layering_rules` entry.
- The 600-line cap is conservative; some cohesive modules may hit it. The
- The 500-line cap is conservative; some cohesive modules may hit it. The
allowlist gives an explicit, visible escape hatch. The cap can be tuned
later if it proves too aggressive.

## Amendments

| Date | Commit/PR | Summary |
|------|-----------|---------|
| 2026-07-12 | #561 | Lowered the non-test source-file cap from 600 to 500 lines to align with SonarCloud's S104 threshold, closing the 500–599 band where a file was repo-compliant but failed the Sonar quality gate at the `dev → main` integration PR (#545). Re-baselined `_ADR015_INITIAL_OVERSIZED_FILES` and `tools/policy/oversized_allowlist.yaml` to the files over the 500-line cap as of this amendment; the shrink-only rule continues from that new baseline. |
6 changes: 5 additions & 1 deletion docs/decisions/adrs/adr-index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ adrs:
pin: 940ce80b1f1d94e3efb4a9f56cb9147d09c9a9dc1453a4977c7a5e194238d708
- id: ADR-015
path: docs/decisions/adrs/adr-015-sdl-processor-layering-and-source-file-size-cap.md
pin: 1e6000c3405e84765a993d326a8d97822e7b28a6714463bcd4d1550951f20ec7
pin: bc5038164e535c4d506962e27cee07e720f5c8a77cb17d018dedb3dba927936f
amendments:
- date: 2026-07-12
ref: "#561"
summary: "Lowered the non-test source-file cap from 600 to 500 lines to align with SonarCloud S104; re-baselined _ADR015_INITIAL_OVERSIZED_FILES and the oversized allowlist to the files over 500 lines at amendment time."
- id: ADR-016
path: docs/decisions/adrs/adr-016-semantic-layer-scope-and-coverage-model.md
pin: ff9a71be7f6d427b88ff26317da36e14b6cc5943418cd941df88dca29033449b
Expand Down
6 changes: 3 additions & 3 deletions implementations/python/tests/test_repo_policy_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ def test_module_boundaries_reject_runtime_importing_sdl_semantics(tmp_path: Path
assert [f.rule_id for f in failures] == ["module-boundary-import"]


# ── ADR-015: 600-line source-file cap ───────────────────────────────────
# ── ADR-015: 500-line source-file cap ───────────────────────────────────

# A path that is in _ADR015_INITIAL_OVERSIZED_FILES (the code constant in
# tools/policy/repo_policy.py), so the allowlist-subset (drain) check passes
Expand Down Expand Up @@ -629,7 +629,7 @@ def test_allowlist_entry_not_in_locked_set_is_rejected(tmp_path: Path) -> None:

def test_allowlist_subset_of_locked_set_passes(tmp_path: Path) -> None:
repo_root = setup_policy_repo(tmp_path)
# A strict subset of the 14 initial oversized entries — the drained state.
# A strict subset of the initial oversized entries — the drained state.
write_text(repo_root / "tools" / "policy" / "oversized_allowlist.yaml", f"files:\n - {_LOCKED_PATH}\n")
write_text(repo_root / _LOCKED_PATH, "x = 1\n" * 700)

Expand Down Expand Up @@ -752,7 +752,7 @@ def test_stale_allowlist_entry_below_cap_is_rejected(tmp_path: Path) -> None:
even though the file's deletion/shrink isn't in the changed set."""
repo_root = setup_policy_repo(tmp_path)
write_text(repo_root / "tools" / "policy" / "oversized_allowlist.yaml", f"files:\n - {_LOCKED_PATH}\n")
write_text(repo_root / _LOCKED_PATH, "x = 1\n" * 100) # well under the 600-line cap
write_text(repo_root / _LOCKED_PATH, "x = 1\n" * 100) # well under the 500-line cap

failures = evaluate_repo_policy(
repo_root,
Expand Down
11 changes: 6 additions & 5 deletions tools/policy/adr_policy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -236,16 +236,17 @@ module_boundaries:
allowed_top_level_imports:
- aces_sdl

# ADR-015: non-test source files under packages/ cap at 600 lines.
# The allowlist (oversized_allowlist.yaml) lists the files that were over
# the cap when ADR-015 landed; each split PR (child of issue #3) removes
# its entry. The allowlist must stay a subset of the *code* constant
# ADR-015: non-test source files under packages/ cap at 500 lines (aligned
# with SonarCloud S104; lowered from 600 per the 2026-07-12 amendment / #561).
# The allowlist (oversized_allowlist.yaml) lists the files that were over the
# cap when the current cap was adopted; each split PR removes its entry. The
# allowlist must stay a subset of the *code* constant
# _ADR015_INITIAL_OVERSIZED_FILES in tools/policy/repo_policy.py (the fixed
# reference set) — entries can be drained but not added. The locked set is
# intentionally NOT mirrored here: keeping it as config the same PR can edit
# would make the "shrink only" rule self-satisfiable.
oversized_source_files:
line_cap: 600
line_cap: 500
allowlist_path: tools/policy/oversized_allowlist.yaml
scope_roots:
- implementations/python/packages
Expand Down
37 changes: 28 additions & 9 deletions tools/policy/oversized_allowlist.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
# Files over the 600-line cap (tools/policy/adr_policy.yaml :: oversized_source_files).
# Files over the 500-line cap (tools/policy/adr_policy.yaml :: oversized_source_files).
# Each entry is a path relative to the repository root and must be one of the
# files in _ADR015_INITIAL_OVERSIZED_FILES (tools/policy/repo_policy.py) — the
# fixed reference set. The allowlist may only shrink: each split PR (a child of
# issue #3) removes its file once it's been split below the cap. The modularity
# tracker (#3) closes when this list is empty. Do NOT add new entries — split
# the file instead. (See ADR-015.)
# fixed reference set. The allowlist may only shrink: each split PR removes its
# file once it's been split below the cap. The modularity tracker closes when
# this list is empty. Do NOT add new entries — split the file instead. This set
# was re-baselined to the 500-line cap on 2026-07-12 (#561); see ADR-015 §2 and
# its Amendments section.
files:
- implementations/python/packages/aces_processor/models.py
- implementations/python/packages/aces_processor/compiler.py
- implementations/python/packages/aces_backend_libvirt/drivers/libvirt.py
- implementations/python/packages/aces_backend_libvirt/realization.py
- implementations/python/packages/aces_backend_libvirt/techvault_native.py
- implementations/python/packages/aces_backend_stubs/stubs.py
- implementations/python/packages/aces_conformance/conformance.py
- implementations/python/packages/aces_contracts/contracts.py
- implementations/python/packages/aces_contracts/workflow.py
- implementations/python/packages/aces_mcp/tools/authoring.py
- implementations/python/packages/aces_mcp/tools/inspection.py
- implementations/python/packages/aces_mcp/tools/reference.py
- implementations/python/packages/aces_operations/_evidence_run_artifact.py
- implementations/python/packages/aces_processor/compiler.py
- implementations/python/packages/aces_processor/models.py
- implementations/python/packages/aces_processor/planner.py
- implementations/python/packages/aces_conformance/conformance.py
- implementations/python/packages/aces_runtime/control_plane_api.py
- implementations/python/packages/aces_runtime/control_plane.py
- implementations/python/packages/aces_runtime/workflow_result_contract_checks.py
- implementations/python/packages/aces_sdl/composition.py
- implementations/python/packages/aces_sdl/module_registry.py
- implementations/python/packages/aces_mcp/tools/authoring.py
- implementations/python/packages/aces_sdl/orchestration.py
- implementations/python/packages/aces_sdl/participant_behavior.py
- implementations/python/packages/aces_sdl/runtime_mail_service.py
- implementations/python/packages/aces_sdl/runtime_security_monitoring.py
- implementations/python/packages/aces_sdl/semantics/objective_semantics.py
- implementations/python/packages/aces_sdl/semantics/participant_behavior.py
56 changes: 35 additions & 21 deletions tools/policy/repo_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,41 +114,55 @@ def structural_runner(input_document: dict) -> list[PolicyFailure]:


# ──────────────────────────────────────────────────────────────────────────
# ADR-015: SDL→processor layering rule + 600-line source-file cap.
# ADR-015: SDL→processor layering rule + 500-line source-file cap.
#
# These two gates catch unintentional regressions in normal contributions:
# a developer who accidentally writes `import aces_processor` in `aces_sdl/`,
# or one who pushes a >600-line file, or a split PR that forgets to drain
# or one who pushes a >500-line file, or a split PR that forgets to drain
# its allowlist entry. The policy and its YAML config are PR-mutable; PR
# review (not this code) defends against deliberate weakening. See ADR-015.
# ──────────────────────────────────────────────────────────────────────────

_POLICY_CONFIG_PATH = "tools/policy/adr_policy.yaml"

# The set of source files that were over the 600-line cap when ADR-015
# landed. The size-cap allowlist (tools/policy/oversized_allowlist.yaml) may
# only ever be a SUBSET of this set: entries are removed as child PRs of #3
# split their file, and no new entry may be added. This is a code constant —
# not config in adr_policy.yaml — so that "the allowlist only shrinks" is
# enforced against a fixed reference rather than against an input the same PR
# can edit. Adding a 15th oversized file therefore requires a diff to this
# module, which PR review scrutinises as policy, not as data noise.
# The set of source files that were over the source-file cap when the current
# cap was adopted. The cap was originally 600 lines (ADR-015 landing); it was
# lowered to 500 on 2026-07-12 (#561, ADR-015 Amendments), and this reference
# set was re-baselined to the files over 500 lines at that time. The size-cap
# allowlist (tools/policy/oversized_allowlist.yaml) may only ever be a SUBSET
# of this set: entries are removed as split PRs shrink their file, and no new
# entry may be added. This is a code constant — not config in adr_policy.yaml —
# so that "the allowlist only shrinks" is enforced against a fixed reference
# rather than against an input the same PR can edit. Adding an oversized file
# therefore requires a diff to this module, which PR review scrutinises as
# policy, not as data noise.
_ADR015_INITIAL_OVERSIZED_FILES: frozenset[str] = frozenset(
{
"implementations/python/packages/aces_processor/models.py",
"implementations/python/packages/aces_processor/manager.py",
"implementations/python/packages/aces_processor/compiler.py",
"implementations/python/packages/aces_sdl/validator.py",
"implementations/python/packages/aces_contracts/contracts.py",
"implementations/python/packages/aces_processor/planner.py",
"implementations/python/packages/aces_processor/control_plane.py",
"implementations/python/packages/aces_conformance/conformance.py",
"implementations/python/packages/aces_backend_libvirt/drivers/libvirt.py",
"implementations/python/packages/aces_backend_libvirt/realization.py",
"implementations/python/packages/aces_backend_libvirt/techvault_native.py",
"implementations/python/packages/aces_backend_stubs/stubs.py",
"implementations/python/packages/aces_sdl/module_registry.py",
"implementations/python/packages/aces_processor/control_plane_api.py",
"implementations/python/packages/aces_conformance/conformance.py",
"implementations/python/packages/aces_contracts/contracts.py",
"implementations/python/packages/aces_contracts/workflow.py",
"implementations/python/packages/aces_mcp/tools/authoring.py",
"implementations/python/packages/aces_mcp/tools/inspection.py",
"implementations/python/packages/aces_mcp/tools/reference.py",
"implementations/python/packages/aces_operations/_evidence_run_artifact.py",
"implementations/python/packages/aces_processor/compiler.py",
"implementations/python/packages/aces_processor/models.py",
"implementations/python/packages/aces_processor/planner.py",
"implementations/python/packages/aces_runtime/control_plane.py",
"implementations/python/packages/aces_runtime/control_plane_api.py",
"implementations/python/packages/aces_runtime/workflow_result_contract_checks.py",
"implementations/python/packages/aces_sdl/composition.py",
"implementations/python/packages/aces_sdl/module_registry.py",
"implementations/python/packages/aces_sdl/orchestration.py",
"implementations/python/packages/aces_sdl/participant_behavior.py",
"implementations/python/packages/aces_sdl/runtime_mail_service.py",
"implementations/python/packages/aces_sdl/runtime_security_monitoring.py",
"implementations/python/packages/aces_sdl/semantics/objective_semantics.py",
"implementations/python/packages/aces_sdl/semantics/participant_behavior.py",
}
)

Expand Down Expand Up @@ -505,7 +519,7 @@ def _check_drain(allowlist: frozenset[str], allowlist_path: str) -> list[PolicyF
"oversized-allowlist-locked",
(
f"'{path}' is in {allowlist_path} but is not one of the files that were over the "
"600-line cap when ADR-015 landed (_ADR015_INITIAL_OVERSIZED_FILES in "
"source-file cap when it was adopted (_ADR015_INITIAL_OVERSIZED_FILES in "
"tools/policy/repo_policy.py); the allowlist may only shrink — split the file instead of adding it"
),
allowlist_path,
Expand Down
Loading