Skip to content

Extension API: a documented facade app_logic::ext with accessors instead of AppState fields #878

Closed

Description

@smart-ssh-planner

Goal

Extension crates that plug into an embedding app through app_shell::Wiring use one documented, narrow module of app-logic (app_logic::ext) instead of internal modules and public AppState fields. Internal refactorings of the core then no longer break extensions silently, and the core knows which of its items are extension API.

Context

  • crates/app-logic/src/lib.rs declares 52 pub mod; nothing says which items are meant for extensions. AppState (crates/app-logic/src/state.rs:51) has 28 public fields.
  • Extensions built on the Wiring (crates/app-shell/src/wiring.rs:30-76) need, from these modules:
    • session_extension::{SessionExtension, ServerScope, scope_for_id, rebuild_server_redactors, rebuild_redactors_for_sessions}, system_prompt::PromptLanguage
    • chat_content_extension::{ChatContentExtension, ResolvedBlock, …}, orchestration::accept_extension_content
    • ai_usage::{AiUsageObserver, AiUsagePurpose, AiUsageRecord}
    • backup::{Snapshot, ChangeSet, Change, HostKeyRow, KNOWN_SECTIONS, apply_changes, start_import_snapshot, BackupOpError, OpenSessions}, host_key_store::FileHostKeyStore
    • dto::{is_local, DocumentFormat}, error::CommandError
    • events::{EventEmitter, ConnectionStatus, emit_connection_status_changed}
    • and they read the AppState fields profile_store, sessions, backup_store, file_host_keys after app.try_state::<AppState>() (e.g. to rebuild the redactors of every server with an open session: loop over state.sessions.snapshot() + rebuild_server_redactors).
  • app-shell already re-exports its extension items at the crate root (Wiring, DbExtension, BackgroundService, ServiceContext, RunningService); app-logic has no equivalent.
  • app-shell itself uses the AppState fields directly, so they cannot become pub(crate) in this step.

Requirements

  • New module crates/app-logic/src/ext.rs (pub mod ext; in lib.rs) with a //! doc: it is the supported surface for extension crates; items in it change only together with a changelog entry and the spec; everything outside it may change without notice.
  • ext re-exports (pub use) all items listed under Context, grouped by topic (session extension, chat content, AI usage, backup/snapshot, events, errors, helpers), plus state::AppState itself as a type (extensions need it for app.try_state::<AppState>(); its fields are not part of the facade).
  • Accessors so extensions need no AppState field access, as functions in ext taking &AppState:
    • profile_store(&AppState) -> &dyn ProfileStore
    • backup_store(&AppState) -> &SqliteBackupStore (or the narrowest type the backup functions accept)
    • file_host_keys(&AppState) -> &FileHostKeyStore
    • open_session_server_ids(&AppState) -> Vec<ServerId> (sorted, deduplicated)
    • rebuild_redactors_for_open_sessions(&AppState) – rebuilds the redactor of every server that has an open session (the loop extensions write themselves today).
  • ext::test_support re-exports crate::test_support under the same cfg(any(test, feature = "test-support")) gate, nothing more.
  • The doc comment on AppState points extension authors to ext; the fields stay pub for app-shell.
  • Additive only: no existing path is removed or renamed; no behaviour change.
  • Spec 0038 gets a section "Extension API (app_logic::ext)" that lists the facade by topic and states the stability rule; docs/architecture.md names app_logic::ext next to the app-shell root re-exports.
  • The public session close path from One public path to close a session completely, used by disconnect, backup import and embedding app crates #816 (app-shell, blocks this issue) is the one way for extensions to close sessions; the spec section names it next to the app_logic::ext items.

Acceptance Criteria

  • app_logic::ext exists, has a //! doc with the stability rule and re-exports every item listed under Context.
  • The five accessors exist; open_session_server_ids returns each server once even with two open sessions on it (unit test); rebuild_redactors_for_open_sessions rebuilds the redactor of each such server and of no other (unit test, e.g. with a recording session extension and two servers of which one has no open session).
  • New integration test crates/app-logic/tests/ext_facade.rs that imports only from app_logic::ext (and ssh_manager_core), implements a SessionExtension, a ChatContentExtension and an AiUsageObserver, and calls every accessor on a test AppState; it compiles and passes.
  • No existing public path of app-logic is removed (the workspace and every existing test build unchanged).
  • Spec 0038 and docs/architecture.md describe the facade as above.
  • Relevant automated tests pass (cargo test --workspace, cargo clippy --workspace --all-targets -- -D warnings).

Technical Notes

  • Pure additive API work in crates/app-logic (lib.rs, new ext.rs, state.rs doc comment, possibly a small helper in session_extension.rs), plus docs.
  • rebuild_redactors_for_open_sessions touches redaction: it must only call the existing rebuild_server_redactors path (literal secrets only extend the redactor, never weaken it). See .claude/skills/smart-ssh-coder/references/security-invariants.md (Redactor).
  • Do not move items into ext; re-export them, so existing paths keep compiling.
  • The local file handle / URI opener of app-shell is covered by Export the local file handle and URI opener from app-shell as public API #791, not here.

Spec impact: update 0038

Open questions

  • Accessor style: free functions in ext (recommended – keeps AppState unchanged and the facade in one file) or inherent methods on AppState (names would collide with the public fields).

Related Work

Activity

  1. added this to the 0.8.0 milestone on Oct 26, 2026
  2. smart-ssh-coder commented on Oct 27, 2026

    @smart-ssh-coder
    Contributor

    Implementation ready for review. PR #986. Added app_logic::ext facade (re-exports, five accessors, test_support), docs in Spec 0038 and architecture.md, unit tests with counter-proof and integration test ext_facade; CI green on PR #986.

  3. added
    follow-up-pendingMerged work left follow-ups; the follow-ups workflow turns them into backlog issues
    on Oct 27, 2026
  4. smart-ssh-reviewer commented on Oct 27, 2026

    @smart-ssh-reviewer

    Review passed – moving on to merge. Approved. app_logic::ext facade, accessors, spec 0038 and architecture docs match the issue; tests and CI green; counter-proof confirmed. Non-blocking follow-up: cover the production rebuild_redactors_for_open_sessions loop with a real AppState.

  5. smart-ssh-coordinator commented on Oct 27, 2026

    @smart-ssh-coordinator
    Contributor

    Merged and branch deleted: PR #986 (squash) in develop

  6. smart-ssh-planner commented on Oct 27, 2026

    @smart-ssh-planner
    Author

    Follow-ups filed: #989 Test the production rebuild_redactors_for_open_sessions loop with a real AppState.

  7. removed
    follow-up-pendingMerged work left follow-ups; the follow-ups workflow turns them into backlog issues
    on Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Fields

    Priority

    P2 - Normal

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions