23 KiB
Module Size & Complexity
This chapter owns deterministic line/function/byte gates, their debt manifest, and paydown by simplifying in place rather than extracting passthroughs. Its readability invariants cover hot-reader projections, the source-complete decision pipeline, continuation authority, UI disposers, and embedded-surface geometry/refresh. Each rule bounds reading cost and names its enforcement or absence.
P7 makes context fit a maintenance constraint, not a line-count aesthetic.
- Python modules everywhere (including
tests/anddevtools/) and first-partyweb/**/*.jsmodules (includingweb/tests/; vendored/minified excluded) target roughly 1000 lines. The deterministic hard gates read exact-path debt from the manifest: 1600 lines per module (ouroboros/size_ratchet_manifest.py::GIANT_PATHS), 200,000 UTF-8 bytes per module (BYTE_DEBT, shrink-only) and the exact-current 1001–1500 band (BAND_PATHS; a new or re-entered path requires a nonblank rationale) — all three apply to Python and JavaScript alike — and 300 lines per non-grandfathered function (FUNCTION_DEBT, exact(path, qualname)keys), which sees the runtime-Python function inventory only (the iterator skipstests/,devtools/, JavaScript andFUNCTION_COUNT_EXCLUDED_FILES). A stale or newly oversized entry fails. Regenerate withscripts/regenerate_size_ratchet.py; it validates the rendered candidate before writing and refuses an unmerged index with a typed error. Counts are taken on strict-UTF-8, POSIX-LF-normalized text, so checkout policy cannot change the inventory; the one shared iterator and the manifest mechanics: ARCHITECTURE §6 "Review stack". - Paying down a size cap: a change that runs into a band, the hard cap, the
function-size gate or the byte debt pays it down by SIMPLIFYING where the
change lives — simpler control/data flow and interfaces, dead code and
duplicates removed, an existing SSOT reused, prose made compact and legible —
so the module reads BETTER afterwards. Extracting a helper, a passthrough
wrapper or a neighbour module is the LAST resort, and a paydown only when the
new unit is a natural boundary that would stay correct with the parent far
below the cap: its own reason-to-change, an explicit contract, a real caller.
A cap-driven bucket, a one-caller passthrough, or bytes bought by deleting
contract-bearing comments, docstrings, messages or tests is a defect, not
paydown — report the conflict instead (BIBLE P7 «first simplify what
exists»). Enforcement: Repo Commit Checklist item 31
size_cap_paydown, advisory when applicable. - Methods above 150 lines and more than eight parameters are decomposition signals (BIBLE P7, CHECKLISTS item 2(c)), not deterministic gates; existing baseline debt is not retroactively a failing tree.
- Runtime Python function/method count stays under
ouroboros/review.py::MAX_TOTAL_FUNCTIONS(the same runtime-only iterator; the module gates include tests/devtools) — a high-water alarm with ample headroom, raised only with a one-line campaign rationale in the same commit (the current 10500 ceiling came with the exact budget-pause lifecycle, owner-approved within 10%, after that change's own single-caller inlines). - Enforcement: the OFFICIAL repository's CI runs the dedicated
size_ratchetpytest lane as a blocking step (OURO_SIZE_RATCHET_BASE_REFnames the event base; lane placement and base fallback: ARCHITECTURE §8 "CI topology"). Local surfaces never block on size: the default pytest lanes exclude the marker, andcheck_worktree_readinessandcodebase_healthreport the samevalidate_size_ratchetfindings as "official CI will enforce" warnings. Both readouts also show capacity from the same inventory and current limits; readiness passes it separately from warnings and focuses on touched paths. Registered debt and omitted rows are labelled; a nearly full valid module remains admissible. Why a locally evolved fork is never trapped by inherited debt (no committed-history replay): ARCHITECTURE §6 "Review stack".
Pragmatic SOLID
SOLID is a direction for making changes legible to future agents, not a demand for classes or extra framework surface:
- SRP — Single Responsibility Principle: one coherent reason and one clear authority for a unit to change.
- OCP — Open/Closed Principle: extend an existing stable seam that preserves the contract instead of rewriting unrelated callers.
- LSP — Liskov Substitution Principle: an implementation or backend preserves the caller-visible behavior of the contract it implements.
- ISP — Interface Segregation Principle: consumers depend only on the capabilities they actually use, not a broad convenience interface.
- DIP — Dependency Inversion Principle: policy depends on small, host-owned contracts, not provider-specific or concrete details.
Apply them pragmatically: they require no class hierarchy, DI container, numeric score, AST analyzer, or new review pass. A SOLID or minimalism finding must name the exact symbol or authority, the concrete duplication or coupling, and a smaller alternative that still satisfies the contract. Diff size, line count, and file count alone are not findings. Enforcement: review-only — CHECKLISTS item 2(d) scores these rules in commit review.
Shared behavior and data-flow changes
When changing shared behavior or data flow, identify the owning authority, the
identity and scope of its facts, and the affected producers and consumers,
including unchanged consumers whose inputs or assumptions the change alters.
Preserve the promised semantics across the relevant live and recovery paths at
comparable freshness, and make legitimate scope or freshness differences
explicit. Verify preservation with falsifiable checks at real consumer
boundaries, not only helper outputs or matching field names, selecting the
paths from the change and its dependencies rather than a fixed inventory of
surfaces or events. Enforcement: review-only through the existing scope-review
cross_module_bugs and implicit_contracts items; no separate gate.
Invariant: Projection over replay (hot readers of growing stores)
A reader that runs per INTERACTION — an HTTP request, a WS/SSE message, a poll tick, a task turn — must not replay a growing store to produce its answer. Interactive read cost is O(response), through a maintained projection, a cursor, rotation or a bounded tail — never a full-history scan filtered down to the answer.
- Evaluate the whole operation as the project grows. For a changed data reader, weigh growth in history, object count and project size, including nested repetition, cold caches and concurrent users of shared resources. A once-per-boot or explicit-owner scan is still allowed; its cost belongs to the whole operation, not separately to every child, file or lookup it visits. Where growth can materially hurt responsiveness, show evidence at a representative scale on the affected path. First remove redundant work or reuse a validated view within one operation; add a projection, cache or other mechanism only when that simpler change is insufficient. A batch names its observation boundary, the next batch refreshes it, and unknown evidence never becomes an empty answer. This is advisory reasoning, not a universal time limit, mandatory heavy benchmark for every PR, or a new approval gate.
- Storage-agnostic. A full-table read filtered in code IS a replay (a
SELECT *narrowed in Python, a whole JSONL file parsed for its tail), including unbounded collections INSIDE snapshot/state files. - Passive GET. Read handlers perform no NEW steady-state durable writes.
Exactly two named exceptions exist: (1) substrate-owned integrity repair
under the substrate's own lock (the usage-ledger torn-tail quarantine in
ouroboros/usage_ledger.py), and (2) one-time idempotent migrations guarded by a durable watermark (the legacy usage import). Anything else that materializes state on a GET is a mutation hiding on a read path. - House precedents — reuse these shapes: archive-aware chat log rotation
(
supervisor/state.py::rotate_chat_log_if_needed); the compactcontainment_faults.jsonlprojection maintained beside an unbounded event log (ouroboros/delegate_custody.py); the process-local custody row memo behinddelegate_custody.custody_rows(ouroboros/delegate_custody_memo.py: an ordered inode/size/mtime fingerprint of the rotated chain prefix, only appended bytes folded, a refold on any doubt, a bypass while unreadable — it bounds the warm read, not the cold fold, so a durable compact projection stays the next step); the bounded filtered tail readerouroboros/jsonl_tail.py(doubling live tail, three newest archives, coverage facts) for history endpoints and the per-task recent-activity sections alike; the fingerprint-keyed render cache inouroboros/_usage_rows_memo.py, held while its input is unchanged and invalidated only by advance/refold, never by TTL; thegateway/task_list_scan.pystat-invalidated result memo and the task-event SSE v2 cursor discipline, whose rules are stated once in ARCHITECTURE §3 "Chat and Projects".
Enforcement: Repo Commit Checklist item 24 (advisory) triggers on diffs that
change data readers, startup/shutdown or other batch operations, or an
endpoint/poller/subscription/timer; the deterministic runtime tripwire is
agent_startup_checks.py::hot_store_growth_notes, surfaced by
context_health.py::build_health_invariants, with thresholds justified in
ouroboros/context_budget.py. A change that introduces a new append-only store
read on an interactive path enrolls that store in that threshold table, with a
justified constant, in the same commit — an unenrolled hot store is invisible
to the tripwire. Retained execution drives under both
state/headless_tasks and task_drives are enrolled by direct-child count at
context_budget.RETAINED_EXECUTION_DRIVES_WARN_COUNT; startup never
recursively sizes those trees.
Invariant: Source-complete decision pipeline
Every new or changed continuity surface is reviewed as one narrow chain:
producer → canonical full source → bounded projection → consumer → decision → retention/GC.
- The producer records the complete event or artifact before it publishes a projection or wakeup. The canonical source owns identity, order, bytes and integrity state; a cache or hot index is never a second authority.
- A bounded projection names what it omitted and carries a source reference
that the same actor can resolve through an existing reader.
source_completeis a coverage fact, not a permission to infer missing material. - Every over-limit tool result persists its exact source; there is no per-tool
exemption, because the DECIDER must be able to resolve what the actor could
page. A bounded row whose exact source is durable and referenced is an
omission for the acceptance panel. If its primary handle fails, a verified
matching redacted tool projection may recover the recorded pre-truncation
result through the existing source writer. Preserve complete inline evidence
on publication failure; a later cut or whole-row omission must not reuse the failed handle or the
original partial corpus. Only
source_unavailable— no actor-resolvable source at all — is an unresolved partial that withholds dispatch. Anapi_chatacceptance reviewer has no tools and cannot resolverepo_diff_source_ref, so a criterion that depends on the unseen part of the diff is at mostpartial. - A consumer that can authorize PASS, a destructive rewrite, or replacement of
a full contract materializes the named source first. A known
partialmarker and an unverified claim that some host might retrieve more are not equivalent: the latter is not actor-attested coverage and cannot release the decision. - Retention and GC are part of the chain. Anything referenced by a canonical result, review, identity decision, or project summary is retained or promoted before its execution root can be collected; an unavailable legacy source is an explicit gap, never silently treated as complete.
Control-plane distrust is metadata, not a data-plane operation. Paid model output is evidence until a typed validity predicate fails. Actual profile, route or subject mismatches, invalid output contracts and delivery failures may affect review authority; window sizing and reading diagnostics alone may not (BIBLE P3). Neither case blanks, rewrites or relabels the artifact or its original cause.
Enforcement: CHECKLISTS item 25 source_completeness (critical when
applicable) scores the chain in commit review; the presentation-adapter
contracts below are pinned by the named web tests.
Review presentation adapters
web/modules/review_presentation.js (grouping, identity, ordering, typed
presentation state), ouroboros/review_execution_projection.py (the bounded
cross-domain executions[] wire), web/modules/review_dom_patch.js (keyed
in-place DOM reconciliation) and web/modules/harness_presentation.js
(harness identity marks and labels) are pure read-side presentation: they
never author, mutate or feed back canonical verdict, lifecycle, routing,
attention or enforcement authority, and never infer that a requested route
executed. Admission is source-complete — an incomplete row is omitted, never
guessed from chat, repository, timestamps, model, tool name or activity. Reuse
the existing chat-history, task-detail and canonical physical-attempt readers;
add no review ledger, endpoint, persisted UI state, cost copy or enforcement
layer. Money presentation and the folded-group bounds follow ARCHITECTURE §3
"Chat and Projects"; compact review rows copy or sum no money. Enforcement:
web/tests/review_presentation.test.js and
web/tests/harness_presentation.test.js pin these contracts; module headers
carry the per-module contracts.
Context and growth matrix
| Store / surface | Complete producer and source | Interactive projection / consumer | Growth and retention proof |
|---|---|---|---|
| Chat and biography | Canonical logs/chat.jsonl, rotated generations, and dialogue blocks |
Main/Project context and archive-aware chat_history |
Rotation/archive readers carry generation/gap coverage; blocks are the compression path, not a deletion of the horizon |
| Plan/review evidence | Exact task-artifact/observability bodies and reviewer route/thread receipts | Bounded review hot index, obligations, and latest-wave status | Exact artifact refs and candidate SHA bind the decision; index rotation cannot certify a missing or partial wave |
| Skill-review root tasks | Per-skill state/skills/<name>/review_history.jsonl; skill_review_runner._append_terminal_history projects terminal identities to state/skill_review_root_tasks.jsonl |
skill_readiness._skill_names_from_review_history reads a bounded newest-first suffix for acceptance |
Derived index is append-only and idempotent by root/task/outcome identity; SKILL_REVIEW_ROOT_TASKS_WARN_BYTES warns at 20 MB |
| Task/project execution | Canonical task result plus promoted child artifacts and summaries | Status cards, terminal rows, and Main/Project summary projections | Canonical promotion precedes child-drive GC; disposable task scratch follows the unified retention owner |
Invariant: Continuation authority and bounded Main projection
Continuation is an explicit relation, not an inferred chat-memory feature: the
router contract requires predecessor_task_id ("" = fresh; omission or
null is a typed refusal before any lookup, enqueue, or provider spend), and
Main receives only a defensive provider projection of the predecessor authority
— never a raw head/tail slice, an invented summary, or a mutation of the
canonical result (the contract and the projection rules: ARCHITECTURE §1
"CLI / Headless Boundary"). The authored continuation narrative is written at
the result owner together with its exact get_task_result(include_authority=True)
source; the projection thresholds only the closed raw keys result and
final_answer, at context_budget.PREDECESSOR_RESULT_INLINE_CHARS.
The startup injection is a bounded continuation ENVELOPE, not a body copy,
minted by the one producer contracts.task_contract.bounded_continuation_envelope:
the predecessor's contract core inherits without its nested
predecessor_authority, every field is whole-or-pointer against one strict
serialized budget (a preview carries full_chars plus a named source_ref;
previous_task_id keeps the chain walkable), and durable task_results bodies
stay the untouched SSOT. Disclosed: the bound is per-field, so a pathological
row can still exceed the wire budget — the refusal is typed and loud rather
than a silent $0. No hop cap exists anywhere: depth belongs to the mind, the
floor only keeps bodies off the wire.
Provider context overflow is a typed recovery fact: after the useful reclaim
and one strictly-smaller same-route retry, a final context_overflow skips the
provider-unavailable/forced-provider path, keeps
execution_status=infra_failed and reason_code=llm_api_error, and records
the typed acceptance bypass and failure.error_kind; ordinary provider outages
keep their existing recovery behavior. Enforcement:
tests/test_continuation_context_authority.py.
Invariant: notifications ring for live events only
Owner-facing notification POLICY is docs/DESIGN.md §9 — one canonical
section, never re-derived here. The engineering rules are:
The subscription is CLIENT-level and must never move into a chat instance. An
instance dies with its room — closing a Project panel disposes its ws.on
handlers — so a notifier wired inside one is silent in exactly the case
notifications exist for: the owner left and the room is closed.
notifications.js::attach() takes one subscription on the shared socket in
app.js, and chat.js holds no notification code.
Only live frames reach it; history and reconnect backfill run through the instances' own readers, which never call it. That boundary — not a persisted ledger — is what makes replay safe, so no notification state survives a reload. The room gate is the client's owner-visible chat set: the hidden partition, A2A ids and unknown chats are refused.
One ending is one key per task: the task_done log frame, the authored summary
and the turn's ordinary reply all collapse together, and a direct turn's ending
is the ordinary-reply category rather than a finished task. Lineage comes from
the delegation facts frames carry, because the terminal frame has none — a child
must not reach the owner's banner.
Classification and delivery gating are pure over one frame and stored preferences,
testable without a DOM or socket. Client-local preferences have no s- field,
are excluded from the settings-dirty tracker, never reach /api/settings or
prompt to discard unsaved settings (tests/test_notifications_static.py asserts
these causes, not just effects). Delivery degrades instead of disappearing;
the status line identifies this client's surface. Feature-detect the optional
desktop bridge per call at delivery: its result is capability evidence, not a
banner/delivery claim. It may raise the existing window and request one system
sound; no scheduler, persistence or background process. Importance adds no host
field, text heuristic or second model call.
Invariant: UI resources carry a disposer
Every long-lived acquisition in web/ returns or records a disposer, and a UI
instance owns a destroy() that releases everything the instance acquired: WS
subscriptions (ws.on(...)), document/window event listeners, observers
(ResizeObserver, MutationObserver, IntersectionObserver), timers,
requestAnimationFrame loops, and EventSource/streaming connections.
An instance that can be closed, hidden, or replaced (project chat panels are
the canonical case) must be destroyable without leaving any acquisition
behind. It may survive being hidden only under an explicit, owner-visible
retention reason — a project chat with pending work, a widget card the owner
set to Keep running — and even then it owns its disposer, Stop / unload /
reload / shutdown remain force-destroy boundaries, and the reason is
re-evaluated at the instance's next lifecycle point, not continuously. The
untyped shape "hide the DOM node, keep the handlers" remains the leak this
invariant forbids; late async continuations check a destroyed flag before
touching state or re-arming loops. A module widget's disposer is the ordered
dispose with acknowledgement (the sequence and WIDGET_DISPOSE_ACK_TIMEOUT_MS:
ARCHITECTURE §3 "Skills and Widgets") — that bounded wait is not the forbidden
shape, because its handlers live only until the settle promise the page tracks
per card key resolves; the masonry's applyMasonry returns an idempotent
disposer for its observers and pending frame.
Enforcement (honest disclosure): the deterministic leak test runs in the
release-tier ui_browser lane, not at commit tier; commit-tier coverage is
the advisory Repo Commit Checklist item 24. The class is closed
deterministically for the instrumented surfaces and advisorily for future
ones.
Invariant: Embedded surfaces declare geometry and refresh semantics
Every owner-visible embedded or framed surface has an explicit host-owned geometry/overflow contract, a paired disposer for every long-lived resource, declared refresh/stream/error semantics, and a named real-consumer visual verification path; intentional omissions record why they are safe to defer.
For Widgets, framed height values are bounded and module auto-height is
host-controlled. Below its finite ceiling, applying a reported block size must
not change the child's inline-size basis; the host owns vertical scrollbar
mode without disabling the orthogonal horizontal overflow capability; and
content measurement includes the measured document's bottom padding and
border. Feedback-sensitive verification is event-driven on the relevant
engine: it proves temporal convergence to a quiet fixed point with a real
consumer or production-derived fixture that crosses the known wrapping
threshold, rather than comparing two snapshots.
A module widget's own faults are declared error semantics, not silence (the
ouro-widget-error channel: ARCHITECTURE §3 "Skills and Widgets"). There is
no server-side widget fault ledger, so those faults are visible only while the
Widgets page is open — safe to defer because the browser is the verification
path for a widget in the first place. Module source loading and declarative
requests have a bounded host timeout; declarative job widgets keep their
job_id and bounded retry/timeout behavior visible in the refresh contract,
where missing or malformed job status is an immediate protocol error while an
unknown non-empty in-progress label remains a bounded pending state for
producer compatibility.
Enforcement: Repo Commit Checklist item 24 points lifecycle changes here
instead of re-deriving a second domain-specific rule; the widget
geometry/refresh contracts are pinned in tests/test_widgets_ui_static.py and
tests/test_extension_surfaces.py.