Skip to content

refactor(runtime): isolate monitor and lifecycle callbacks - #350

Merged
alexei-led merged 2 commits into
mainfrom
refactor/monitor-lifecycle-boundaries
Oct 25, 2026
Merged

alexei-led merged 2 commits into
mainfrom
refactor/monitor-lifecycle-boundaries

Conversation

@alexei-led

@alexei-led alexei-led commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

Changes

  • Keep EventStreamMonitor neutral: require an awaited death callback instead of importing Telegram routing or private polling code.
  • Bootstrap binds the public handlers.polling.window_death.handle_window_death entrypoint. This 21-line Telegram adapter uses the existing session_query.iter_bound_topics snapshot and the unchanged death policy. It does not add direct singleton access.
  • Snapshotting fixes a reproduced RuntimeError: dictionary changed size during iteration: deleting a topic unbinds recipients during await.
  • Supply a live shutdown-reason getter from the process runner through application state. The bot no longer imports main._shutdown_signal; PR341's restart callback stays intact.
  • Add five Archfit fail rules and a nested-import audit with planted violations, included in make arch-guard. Regroup routing tests under polling and signal diagnostics under runtime; align architecture docs.

The destructive topic-policy handler is byte-for-byte unchanged. Exact-chat, pending-creation, authoritative-liveness, deduplication, cancellation, and stop-before-drain behavior remain covered. No broker, state framework, dependency, ownership/volatility changes, baseline reset, or blanket public declarations.

PR report feedback addressed

The first PR report flagged bootstrap's newly relocated private policy import. That was a real new runtime dependency, not dismissed as warning noise. The follow-up adds one supported adapter entrypoint, keeps private policy access inside its existing handler module group, and forbids bootstrap from importing the policy implementation. Only the small new adapter module is declared public; other polling internals stay private.

A layering test then rejected direct router access from the new handler. The adapter now uses the existing query seam; the audit and its allow-list were not weakened.

Verification

  • Python 3.14: 8,167 unit tests passed, 25 skipped.
  • 231 clean-interpreter checks passed, including transitive import purity for the neutral monitor and multiplexer contract.
  • Ruff format/lint, lazy-import audit, full source+test Pyright, deptry, wheel/sdist build, and relative-doc-link checks pass.
  • Independent review covered the complete initial patch and public-entrypoint follow-up. Its re-export-bypass finding is fixed with a package-prefix guard and a planted regression case.
  • No live backend/Telegram checks, session operations, deployment, release or tag changes. Diagrams are unchanged.

Mobile alignment

Reviewed the iOS and session-protocol plans against the implemented core. Shared session IDs, messages, requests, safe actions and replay remain transport-neutral. Telegram topic deletion and delivery receipts stay adapter-owned. The architecture doc records that current push subscriptions cover Telegram-bound targets while mobile-only sessions use core refresh; it does not claim headless startup or APNs is implemented. A clean-interpreter check rejects transitive Telegram/handler imports from the monitor.

Local Archfit 3.2 evidence

Used the checksum-verified published v3.2.0 binary, not the host's v3.0.1.

Code-only comparison under the original identical config/model/classification and v7 rubric: 98 → 95 advisory groups, three resolved, none added:

  • 00bacbce5c97967814736b85441f659a: event monitor → private Telegram death handler.
  • ebbc8fbd9c3c4185cc045c5fc9bffe15: event monitor → Telegram client.
  • 2c5200c672430d0cd1f2c2885f303c6d: bot → private main shutdown state.

With the new guards and narrow public-entrypoint declaration: eleven required import rules pass, zero blockers, zero unmapped first-party edges, still 95 advisories. Config lint passes. Public-surface/model changes are reported separately from code-only improvement; ownership, volatility/cascade, labels files, baseline, and waivers are unchanged.

archfit check previously exited 2, not a clean architecture verdict. Advisories and evidence gaps remain.

Findings retained deliberately

  • Bootstrap's old private reset access is inside reset_for_testing, not a runtime death-policy import.
  • Monitor/owned-reader receipt coordination and the neutral status cache stay cohesive.
  • The new adapter→session-query dependency uses an existing snapshot contract, not private state. The functional classification does not establish duplicated business rules.
  • Existing resume-wrapper debt belongs to PR340, not this change.
  • Two static-cycle findings remain. Optional cycle conformance is unmeasured; runtime/deploy topology, drift reference, and unrated-edge gaps are not called passed.

Comparison limits

On the untouched released base, local --base comparison already labelled three existing provider groups introduced, despite head finding IDs and the entire seam ledger matching merged-main CI. Do not mistake those zero-diff entries for code regressions. The later query-seam dependency is a real new dependency within an existing aggregated finding and is assessed as a supported read contract, not lumped into that noise.

The first PR report had one introduced private-bootstrap group and seven resolved IDs, including four new-rule violations of the base tree. That is a different comparison from the unchanged-model code-only counts above. Final-head verification is complete; details below.

Final-head Archfit review

Verified head 09398a9fdc36354afb3d050f2c03e61d90e44d34 in CI run 40190314059. Config 29f0b18a…, model b8bc8b9e…, classification and v7 rubric match the final local report. The final CI finding IDs and entire seam ledger match local analysis. CI and Greptile pass; archfit/state is neutral/advisory.

  • Eleven fail-gated import rules are evaluated and pass. The twelfth rule is warn-only no-import-cycles, whose conformance remains unmeasured. Zero blockers, zero unmapped first-party edges, empty agent tasks. This is not a clean architecture verdict: 95 advisories remain.
  • The earlier introduced private bootstrap policy import is gone. Its grouped intrusive finding now points only to the pre-existing test-reset site at bootstrap.py:523.
  • Final comparison has one introduced aggregated advisory, 08f2b089fa9734a8db6152f3c84ad5ba (handlers → session_state). The new member is window_death.py:7 → session_query.iter_bound_topics: the already-supported read/snapshot contract required by the existing handler layering audit. It returns copied value tuples, not mutable stores or duplicated death rules. Retained deliberately rather than adding another interface or calling the entire legacy group harmless. This is distinct from the three proven zero-diff provider-origin anomalies.
  • Seven resolved IDs include the three repaired BC groups and four gate violations under the newly enforced policy. They are not seven independent code defects.
  • The only new public declaration is the 21-line death adapter module. Ownership, volatility/cascade, label files, baselines and waivers remain unchanged. Partial/unmeasured dimensions and nineteen unrated edges remain explicit.

No merge, deployment, live bridge update, release or tag change.

Keep neutral event observation separate from Telegram routing and process-owned shutdown state. Snapshot death recipients before awaited topic cleanup can unbind them. Preserve destructive policy and shutdown ordering, with behavior and import guards plus aligned architecture docs.
@greptile-apps

greptile-apps Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no new actionable issues were found.

Summary

This PR separates event observation from Telegram cleanup and gives the bot a live shutdown-reason callback.

  • Window-death events reach topic cleanup through a Telegram adapter.
  • Shutdown messages use a reason supplied by the process runner.
  • Architecture checks guard the runtime import boundaries.

No new actionable issues were found in the changes since the previous review.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    Bootstrap["bootstrap.start_event_stream"] --> Monitor["EventStreamMonitor"]
    Monitor -->|"await on_window_died"| Adapter["handle_window_death"]
    Adapter -->|"snapshot recipients"| Query["iter_bound_topics"]
    Adapter --> Policy["_handle_dead_window_notification"]
    Runner["main.run_bot"] -->|"get_shutdown_reason"| State["application.bot_data"]
    State --> Bot["bot shutdown notification"]
Loading

Reviews (2) · Last reviewed commit: "refactor(polling): expose the window-dea..." · Reviewed by Greptile

@archfit-review

archfit-review Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Archfit: Architecture needs attention · advisory · no baseline

Next: Advisories never block. Review the listed dimensions and diagnostics on the archfit/state check.

Details in Archfit

95 advisories · never block
Dependency This PR Where Strength Suggested move
api → session_protocol existing src/ccgram/api/server.py:18, src/ccgram/api/server.py:19, src/ccgram/api/server.py:20 and 3 more functional Move the functionality: lower the distance, so the coupled code lives in one module
app_bootstrap → api existing src/ccgram/bootstrap.py:425 functional Move the functionality: lower the distance, so the coupled code lives in one module
app_bootstrap → domain_config existing src/ccgram/bootstrap.py:30, src/ccgram/bootstrap.py:67, src/ccgram/cli.py:306 and 15 more functional Move the functionality: lower the distance, so the coupled code lives in one module
app_bootstrap → handlers existing src/ccgram/bootstrap.py:29, src/ccgram/bootstrap.py:32, src/ccgram/bootstrap.py:33 and 12 more intrusive Introduce a contract: lower the integration strength, so the target can change without breaking this module
app_bootstrap → handlers existing src/ccgram/bootstrap.py:523 intrusive Introduce a contract: lower the integration strength, so the target can change without breaking this module
app_bootstrap → hooks existing src/ccgram/hook.py:29, src/ccgram/hook.py:33, src/ccgram/hook.py:1155 and 2 more functional Move the functionality: lower the distance, so the coupled code lives in one module
app_bootstrap → multiplexer existing src/ccgram/bootstrap.py:553, src/ccgram/hook.py:37 functional Introduce a contract: lower the integration strength, so the target can change without breaking this module
app_bootstrap → multiplexer_backends existing src/ccgram/hook.py:35, src/ccgram/hook.py:36, src/ccgram/main.py:227 functional Move the functionality: lower the distance, so the coupled code lives in one module
app_bootstrap → providers existing src/ccgram/bootstrap.py:52, src/ccgram/doctor_cmd.py:25, src/ccgram/doctor_cmd.py:182 and 1 more functional Move the functionality: lower the distance, so the coupled code lives in one module
app_bootstrap → session_protocol existing src/ccgram/bootstrap.py:401, src/ccgram/cli.py:303, src/ccgram/cli.py:357 and 2 more functional Move the functionality: lower the distance, so the coupled code lives in one module
app_bootstrap → session_state existing src/ccgram/bootstrap.py:53, src/ccgram/bootstrap.py:56, src/ccgram/bootstrap.py:65 and 3 more intrusive Introduce a contract: lower the integration strength, so the target can change without breaking this module
app_bootstrap → session_state existing src/ccgram/bootstrap.py:49, src/ccgram/bootstrap.py:404, src/ccgram/bootstrap.py:528 intrusive Introduce a contract: lower the integration strength, so the target can change without breaking this module
app_bootstrap → telegram_adapter existing src/ccgram/doctor_cmd.py:26, src/ccgram/main.py:54, src/ccgram/main.py:318 functional Move the functionality: lower the distance, so the coupled code lives in one module
app_bootstrap → terminal_adapter existing src/ccgram/bootstrap.py:55, src/ccgram/bootstrap.py:358 functional Move the functionality: lower the distance, so the coupled code lives in one module
app_bootstrap → transcript existing src/ccgram/bootstrap.py:66 functional Move the functionality: lower the distance, so the coupled code lives in one module
app_bootstrap → window_state existing src/ccgram/bootstrap.py:534, src/ccgram/status_cmd.py:30 functional Move the functionality: lower the distance, so the coupled code lives in one module
domain_config → handlers existing src/ccgram/utils.py:418 functional Introduce a contract: lower the integration strength, so the target can change without breaking this module
domain_config → multiplexer_backends existing src/ccgram/config.py:274 functional Move the functionality: lower the distance, so the coupled code lives in one module
domain_config → providers existing src/ccgram/command_catalog.py:11 functional Move the functionality: lower the distance, so the coupled code lives in one module
domain_config → session_protocol existing src/ccgram/utils.py:32 functional Move the functionality: lower the distance, so the coupled code lives in one module
handlers → domain_config existing src/ccgram/handlers/agent_command.py:31, src/ccgram/handlers/callback_registry.py:21, src/ccgram/handlers/callback_tokens.py:19 and 72 more functional Move the functionality: lower the distance, so the coupled code lives in one module
handlers → llm existing src/ccgram/handlers/hook_events.py:178, src/ccgram/handlers/shell/shell_capture.py:517, src/ccgram/handlers/shell/shell_commands.py:34 and 1 more functional Move the functionality: lower the distance, so the coupled code lives in one module
handlers → miniapp existing src/ccgram/handlers/status/status_bar_actions.py:32 functional Move the functionality: lower the distance, so the coupled code lives in one module
handlers → multiplexer existing src/ccgram/handlers/polling/window_tick/apply.py:30, src/ccgram/handlers/polling/window_tick/observe.py:21, src/ccgram/handlers/polling/window_tick/observe.py:24 functional Introduce a contract: lower the integration strength, so the target can change without breaking this module
handlers → providers existing src/ccgram/handlers/agent_command.py:215, src/ccgram/handlers/agent_command.py:281, src/ccgram/handlers/commands/forward.py:28 and 39 more intrusive Introduce a contract: lower the integration strength, so the target can change without breaking this module
handlers → providers existing src/ccgram/handlers/recovery/resume_command.py:42 intrusive Introduce a contract: lower the integration strength, so the target can change without breaking this module
handlers → session_protocol existing src/ccgram/handlers/commands/failure_probe.py:30, src/ccgram/handlers/interactive/interactive_callbacks.py:18, src/ccgram/handlers/recovery/recovery_banner.py:44 and 4 more functional Move the functionality: lower the distance, so the coupled code lives in one module
handlers → session_state added src/ccgram/handlers/agent_command.py:32, src/ccgram/handlers/agent_command.py:199, src/ccgram/handlers/agent_command.py:321 and 51 more functional Move the functionality: lower the distance, so the coupled code lives in one module
handlers → session_state_ports existing src/ccgram/handlers/hook_events.py:25, src/ccgram/handlers/status/status_bubble.py:22 functional Move the functionality: lower the distance, so the coupled code lives in one module
handlers → telegram_adapter existing src/ccgram/handlers/commands/menu_sync.py:23, src/ccgram/handlers/commands/panel.py:23, src/ccgram/handlers/dashboard_command.py:12 and 41 more functional Move the functionality: lower the distance, so the coupled code lives in one module
handlers → terminal_adapter existing src/ccgram/handlers/agent_command.py:35, src/ccgram/handlers/callback_helpers.py:10, src/ccgram/handlers/callback_registry.py:22 and 47 more functional Move the functionality: lower the distance, so the coupled code lives in one module
handlers → transcript existing src/ccgram/handlers/last_reply.py:51, src/ccgram/handlers/polling/polling_state.py:163, src/ccgram/handlers/polling/polling_state.py:202 and 1 more functional Move the functionality: lower the distance, so the coupled code lives in one module
handlers → tts existing src/ccgram/handlers/messaging_pipeline/message_queue.py:40 functional Move the functionality: lower the distance, so the coupled code lives in one module
handlers → whisper existing src/ccgram/handlers/voice/voice_handler.py:21, src/ccgram/handlers/voice/voice_handler.py:22 functional Move the functionality: lower the distance, so the coupled code lives in one module
handlers → window_state existing src/ccgram/handlers/commands/__init__.py:25, src/ccgram/handlers/commands/failure_probe.py:28, src/ccgram/handlers/commands/forward.py:31 and 41 more functional Move the functionality: lower the distance, so the coupled code lives in one module
handlers → window_state_ports existing src/ccgram/handlers/agent_command.py:36, src/ccgram/handlers/cleanup.py:25, src/ccgram/handlers/hook_events.py:24 and 19 more functional Move the functionality: lower the distance, so the coupled code lives in one module
hooks → providers existing src/ccgram/hooks/adapters.py:15 functional Move the functionality: lower the distance, so the coupled code lives in one module
llm → domain_config existing src/ccgram/llm/__init__.py:62 functional Move the functionality: lower the distance, so the coupled code lives in one module
llm → session_protocol existing src/ccgram/llm/summarizer.py:14 functional Move the functionality: lower the distance, so the coupled code lives in one module
miniapp → domain_config existing src/ccgram/miniapp/api/terminal.py:32 functional Move the functionality: lower the distance, so the coupled code lives in one module
miniapp → session_state existing src/ccgram/miniapp/api/transcript.py:53 functional Move the functionality: lower the distance, so the coupled code lives in one module
miniapp → window_state_ports existing src/ccgram/miniapp/api/terminal.py:98 functional Move the functionality: lower the distance, so the coupled code lives in one module
multiplexer → multiplexer_backends existing src/ccgram/multiplexer/__init__.py:23 functional Move the functionality: lower the distance, so the coupled code lives in one module
multiplexer → session_state existing src/ccgram/multiplexer/vim_state.py:20 functional Move the functionality: lower the distance, so the coupled code lives in one module
multiplexer → window_state existing src/ccgram/multiplexer/reconciliation.py:11, src/ccgram/multiplexer/window_liveness.py:14 functional Move the functionality: lower the distance, so the coupled code lives in one module
multiplexer_backends → domain_config existing src/ccgram/multiplexer/tmux.py:38 functional Move the functionality: lower the distance, so the coupled code lives in one module
multiplexer_backends → multiplexer existing src/ccgram/multiplexer/herdr.py:72 intrusive Introduce a contract: lower the integration strength, so the target can change without breaking this module
multiplexer_backends → multiplexer existing src/ccgram/multiplexer/tmux.py:51 intrusive Introduce a contract: lower the integration strength, so the target can change without breaking this module
multiplexer_backends → provider_detection existing src/ccgram/multiplexer/agterm.py:194, src/ccgram/multiplexer/agterm_panes.py:55 functional Move the functionality: lower the distance, so the coupled code lives in one module
multiplexer_backends → providers existing src/ccgram/multiplexer/agterm.py:504 functional Move the functionality: lower the distance, so the coupled code lives in one module

On every row: Imbalanced coupling · critical · across modules · high volatility.

  • and 45 more

Why: functional coupling across a module boundary to a volatile target: a change on one side forces a change on the other (Balanced Coupling: critical severity)

Why: intrusive (implementation-level) coupling across a module boundary to a volatile target: a change on one side forces a change on the other (Balanced Coupling: critical severity)

Evidence and inputs

Decision

  • Engine verdict needs attention: hard gates pass, 0 active blockers, every required rule evaluated
  • The gate is advisory for this repository: the engine's verdict does not block merging.
  • Attention only (architecture): dimensions not fully measured: intent, coupling, testability, operations, drift; active diagnostics: 95 · code attention_only

Dimensions

Dimension Status Measured Confidence Result
intent partial 11/12 ("declared rules evaluated") medium pass
structure measured 1279/1527 ("discovered dependencies resolved inside the declared module map") high warn
modularity measured 5/24 ("declared modules with a declared public surface") low pass
coupling partial 685/704 ("cross-boundary edges scored") medium warn
change_locality measured 24/24 ("declared modules touched in the scanned history window") high pass
complexity measured 24/24 ("declared modules with complete dependency-chain and degree values") high pass
testability partial 546/546 ("classified source files") medium pass
operations partial 0/24 ("declared modules with a corroborated deploy unit and qualifying owner") medium pass
drift unmeasured 0/0 unrated not applicable

Evidence gaps

  • Analyzers that did not run (absent or disabled): go/packages, dependency-cruiser, cargo, ast-grep, cargo-modules
  • Dimension intent, unknown fact: active rule conformance — one or more active rules lack the completed producer evidence their checks require: no-import-cycles
  • Dimension structure, unknown fact: external dependency structure — 248 dependencies leave the declared module map (248 on libraries, 0 to or from first-party code no module owns), so their direction and layer are outside this claim
  • Dimension coupling, unknown fact: coupling strength — integration strength is unknown on one or more cross-boundary candidates, so no rung is invented
  • Dimension coupling, unknown fact: coupling distance — architectural distance is unknown on one or more cross-boundary candidates, so no rung is invented
  • Dimension change_locality, unknown fact: essential vs accidental volatility — commit frequency corroborates a declared volatility; it cannot establish one
  • Dimension complexity, unknown fact: cognitive complexity — no cognitive-complexity analyzer is claimed; module-graph shape is the architecture-level measure
  • Dimension testability, unknown fact: supplied coverage units — coverage is disabled, so no supplied coverage units were observed
  • Dimension testability, unknown fact: coverage path resolution — coverage is disabled, so no supplied coverage paths were available to resolve
  • Dimension testability, unknown fact: coverage module attribution — coverage is disabled, so no supplied coverage was available to attribute to declared modules
  • Dimension testability, unknown fact: coverage freshness — coverage is disabled, so no supplied coverage freshness could be established
  • Dimension operations, unknown fact: corroborated deploy unit — one or more declared modules have no independently corroborating deploy manifest
  • Dimension operations, unknown fact: observed runtime topology — committed manifests corroborate declared deploy units; they do not observe what is actually running
  • Dimension operations, unknown fact: supply-chain inventory — SBOM and vulnerability facts are a separate report family and have no collector in v1
  • Dimension drift, unknown fact: admissible persisted reference — no comparable architecture-state reference is stored
  • Dimension drift, unknown fact: complete two-sided seam identity — two-sided seam identity cannot be compared without an admissible persisted reference
  • No baseline: every finding counts as new, including older debt. New does not mean added by this pull request.

Inputs and identity

  • Policy digest: 29f0b18ab48a2b0855a6585fd8435f4bdd074e6383ef5730b1028280a1930f25
  • Baseline digest: no baseline
  • Labels digest: no labels file
  • Measurement profile: version archfit.measurement.v2, settings hash 0bb81030aa53141846de3953d5df392c8ab250618e04f4388b06d4dc3016b0cb
  • Producer identity, claimed: engine v3.2.0, image sha256:5f2d4a5fff1ace10d7b08937429179631ae0c1d0e1f9e6f37c72b30954cf00db, linux/amd64
  • Protected path change: owner approval by alexei-led

Measurement is customer-attested: the producer is authenticated, the report content is not independently checked.

For coding agents

Agent tasks

[]

Address the PR Archfit private-bootstrap finding with one supported Telegram entrypoint and existing query snapshots. Guard direct and re-exported policy imports. Align mobile/core responsibilities and verify neutral imports without Telegram dependencies.
@alexei-led
alexei-led merged commit 6cfe0eb into main Oct 25, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant