Skip to content

Native Termux remote bus: scoped UI grants and credential carry - #3861

Merged
trissim merged 6 commits into
mainfrom
plan/native-termux-remote-bus
Nov 2, 2026
Merged

trissim merged 6 commits into
mainfrom
plan/native-termux-remote-bus

Conversation

@trissim

@trissim trissim commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Current result

6 commits, 16 changed files, +1030/-94 against main, including the plan. Published head: a6eb688. The authorization/carry implementation and caller-owned history selection are pushed; the actual remote Toad attachment is the next implementation step.

Primary target: normal Toad input/rendering/scrolling on Termux, only remote Core bus data over the network. SSH is interim; mosh is parked. The maintained Android fork is separately merged and hosted-qualified. Actual phone migration and native remote-bus acceptance remain open.

Authorization: own UI caller, not a proxy agent

Before:

ConnectorCaller(ThreadCaller)
  ForkingCaller
  DirectorCaller

After:

ConnectorCaller(ToolCaller)
  ThreadConnectorCaller + ThreadCaller
    ForkingCaller
    DirectorCaller + OperatorOutcomes
  ReadOnlyConnectorCaller + NoThreadCaller
    UiReadCaller(namespace: RootId)
  • Read permission derives from the existing ReadTool capability, never an allowlist or descriptive MCP hint.
  • The original native namespace is checked at authentication, before argument-context reads on the opened call, and before direct read execution.
  • Existing thread/fork joining and Director directive behavior remain at their owners. The global claude-ai prerequisite is removed; a data reader needs no fabricated thread.
  • ReadAuthority.history_viewer owns the thread-reader default; NoThreadCaller decodes the existing anonymous/named viewer family. CommsHistoryTool delegates to that owner, so a scoped UI caller can read a channel while DM history still requires a participant.
  • Accepted requests retain the existing disconnect/shutdown behavior. Expiry/revocation rejects subsequent requests; this does not claim retrospective cancellation of an accepted call.

One bearer format, explicit carry

Before: separate access/thread dictionaries and a resolver choosing authority by dictionary location.

After: one ConnectorAccess family with distinct OAuth, local service, thread, UI, absent and retained-unattributed Director members. The retained case preserves its recorded label and prior authority; it does not fabricate which old producer issued it.

The explicit installer carry preserves clients, pending approvals and codes, refresh entries, browser trust, bearer digests, expiry and thread authority. Colliding old access/thread digests refuse without rewriting input, because the old precedence depended on expiry. Runtime decoding accepts only the new format; no compatibility alias, fallback decoder or on-load converter remains beside it.

Private profile publication

GrantUiVerb and RevokeUiVerb declare grant-ui and revoke-ui; installed command success is not yet proved.

The existing document transaction publishes a new private HTTPS profile before committing the bearer. A failed profile publication commits no grant. Existing files and leaf symlinks are not overwritten; the profile uses the checked-directory atomic publisher and owner-only file/directory roles. Console output contains public grant ID, root namespace, expiry and profile path, never the secret. The server's stored grant remains the authority, not profile metadata.

External credentials and shared lock behavior

  • The credential location is declared once by ConnectorCredentialsFile; ConnectorStore.owned() derives it.
  • The installed external-store entry point makes the existing inventory/backup/rehearsal/restore path see that resource.
  • Retaining moved unchanged from SQLite-only storage to StoreGuard; writer drains are one shared capability. SQLite's transaction-specific drain is unchanged.
  • GuardedExternalStore owns the one selected-resource borrowing loop inherited by SQLite metadata and JSON credentials. The installer's ExitStack-through-rollback lifetime is unchanged; capture drains remain separately released.

The replaced resolver and copied guard/borrowing implementations are deleted rather than retained as a second path. Published per-file additions/deletions are visible in the diff; growth is primarily new authorization, carry and controls, not a claim of measured speedup.

Evidence and actual gaps

Authored controls cover:

  • private/exclusive profiles, failure without grant activation, symlink/permission refusal;
  • expiry, independent revocation, wrong-root and direct mutation refusal;
  • real MCP channel reads without an agent, write refusal and revocation;
  • selected retained-file carry, exact original authority/data, dry run, idempotence;
  • ambiguous digest/unknown-current-case refusal, confined paths and real kernel-lock lifetime.

The hosted source/tools failures at7fd exposed two nonexistent FieldCodec.dumps calls, an untyped old issuer label and manual endpoint validation. Head8633 uses the actual encode/compact API (also in the authored carry controls), nominal AccessLabel preserves the former label, and yarl's origin/normalization operations own endpoint validation. The changed code also replaces jargon and removes an unused import.

git diff --check passed and all 11 edited Python files parsed. Affected control execution, OAuth/thread/service regressions and the real selected-file carry/rollback remain open. At8633 the tools gate passed and the source gate's sole remaining blocker was one word in a comment; a6eb corrects that word after rebasing current main. Exact-head source judgment is recorded through Core, including the per-file growth explanation. Current-head hosted conclusions are read from that head's own jobs.

Next implementation is the actual remote channel/page attachment at Toad's existing selection/read boundary, using the scoped MCP caller and existing conversation presentation.

Queue interactions later reuse the existing unified native-DM/channel/inbox queue owner (Tristan56446/56449); this work must not add a fourth queue.

Assumed / still to establish

  • The owner selects the real HTTPS endpoint; TLS credentials never enter its URL.
  • Namespace authority comes from the original private registry guard, not a path, label or SQLite identity.
  • The existing transaction and filesystem rollback guarantees apply; repeated unrecoverable I/O failures are not claimed impossible.
  • The carry covers the declared former split format. Unrecognized current records and ambiguous authority refuse.
  • Actual Play Termux signer/version/architecture/plugins and a verified external private HOME+PREFIX backup are required before any approved migration. No uninstall, data loss, forced downgrade or app-ID shortcut is authorized.

Plan: plans/NATIVE-TERMUX-REMOTE-BUS-PLAN.md.

Copilot AI balanced review requested due to automatic review settings November 2, 2026 10:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@trissim

trissim commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

Core source note for slice 1 (read at58daba13e, not a phone result): the history tool also needs an explicit viewer contract, not just an approved OAuth client.

  • CommsHistoryTool.apply, src/agent_comms/tools.py:1107–1115, builds NamedViewer(caller.subject(self.participant)).
  • ReadAuthority.subject/own_thread, read_basis.py:151–158, defaults an omitted participant to the caller's thread; a non-thread caller refuses that default.
  • IssuedAccess.caller, connector_store.py:106–112, currently returns CALLER, and connector_caller.py:79 binds that to DirectorCaller("claude-ai"). ThreadGrant.caller instead creates a ForkingCaller for its recorded thread. Neither is automatically a native human-display principal.

So the first-page implementation must carry the approved native client's actual read/display scope through the existing caller/read families. Decide and validate its viewer at that owner; do not inherit the Claude director identity or disguise the phone as an agent merely to satisfy the history default. Existing UiRead/display-cut capabilities are the related local behavior to preserve, not a reason to scatter a participant string through widgets.

For owner-specific reads, keep OwnerScopedRead.answer/unavailable_answer (bus_read.py:44–94) server-side. It captures the registered thread incarnation, fences root file identity and the original registry cut, and distinguishes unavailable owners from failures of a live owner. Core#3822's installed unavailable-owner MCP path has been exercised; that is useful existing server behavior, not remote/native/phone qualification.

This is context for your plan, not another implementation or transport proposal.

@trissim
trissim force-pushed the plan/native-termux-remote-bus branch 2 times, most recently from eaf59b3 to 13dc440 Compare November 2, 2026 14:12
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

debt-tools at a6eb688 passes.

Warn-only, never blocks (4)
tools/cutover/carry_connector_access.py: LintFindings +4
tools/cutover/carry_connector_access.py: Unformatted +1
found LintFindings:tools/cutover/carry_connector_access.py at tools/cutover/carry_connector_access.py:7 (I001 [*] Import block is un-sorted or un-formatted) tools/cutover/carry_connector_access.py:14 (E501 Line too long (101 > 100)) tools/cutover/carry_connector_access.py:61 (E501 Line too long (101 > 100)) tools/cutover/carry_connector_access.py:69 (E501 Line too long (106 > 100))
found Unformatted:tools/cutover/carry_connector_access.py at tools/cutover/carry_connector_access.py:1 (Black would reformat this file)

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

debt-src at a6eb688 passes.

Warn-only, never blocks (10)
src/agent_comms/connector.py: LintFindings +1
src/agent_comms/connector_profile.py: LintFindings +1
src/agent_comms/connector_store.py: LintFindings +1
src/agent_comms/connector_caller.py: Unformatted +1
src/agent_comms/connector_profile.py: Unformatted +1
found LintFindings:src/agent_comms/connector.py at src/agent_comms/connector.py:959 (E501 Line too long (104 > 100))
found LintFindings:src/agent_comms/connector_profile.py at src/agent_comms/connector_profile.py:3 (I001 [*] Import block is un-sorted or un-formatted)
found LintFindings:src/agent_comms/connector_store.py at src/agent_comms/connector_store.py:15 (E501 Line too long (101 > 100))
found Unformatted:src/agent_comms/connector_caller.py at src/agent_comms/connector_caller.py:1 (Black would reformat this file)
found Unformatted:src/agent_comms/connector_profile.py at src/agent_comms/connector_profile.py:1 (Black would reformat this file)

@trissim
trissim force-pushed the plan/native-termux-remote-bus branch from 13dc440 to 7fd4da9 Compare November 2, 2026 14:20
@trissim trissim changed the title Plan: native Termux Toad against the remote Core bus Native Termux remote bus: scoped UI grants and credential carry Nov 2, 2026
@trissim
trissim force-pushed the plan/native-termux-remote-bus branch from 7fd4da9 to 8633f7d Compare November 2, 2026 15:02
@trissim
trissim force-pushed the plan/native-termux-remote-bus branch from 8633f7d to a6eb688 Compare November 2, 2026 15:21
@trissim
trissim added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit d3bb454 Nov 2, 2026
9 of 10 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.

2 participants