Skip to content

Share one AtCommandExecutorContext per connection - #429

Open
akafredperry wants to merge 1 commit into
trunkfrom
fix/shared-executor-context
Open

Share one AtCommandExecutorContext per connection#429
akafredperry wants to merge 1 commit into
trunkfrom
fix/shared-executor-context

Conversation

@akafredperry

@akafredperry akafredperry commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

- What I did
This is groundwork for porting the cryptographic agility changes.

Standardize the command layer on (AtCommandExecutor, AtCommandExecutorContext) as its leading arguments, and create exactly one context per connection in AtClients.createAtClient, passing it to both the executor builder and AtClientImpl.

The context cannot be created by AtClientImpl, which receives an already-built executor, and AtCommandExecutors needs it to wire onReady - so AtClients creates it and shares it. AtCommandExecutors.resolveContext uses one when supplied, ANON when there is no atSign, and otherwise builds one - which keeps standalone builder callers working and stops a second clientId being minted.

This also fixes a lost-metadata defect: every client built with withMonitoring(true) sent a from: carrying no clientId and no client version, because AtClients passed the raw config to Notifications.monitor while the context held the enriched one, and an explicit onReady replaced the context-based sequence. AtClientsTest pins this - confirmed to fail with the previous wiring restored.

- How I did it
AtCommandExecutorContext gains a two-arg constructor, and its config is now never null and never modifiable - it is copied on construction, so a later change to the caller's map cannot alter what the connection sends. atSign and keys stay nullable because both states are load-bearing: a connection to the atDirectory / root server has no atSign, and one that only issues from: (or authenticates with CRAM before any keys exist) has no keys. A context with no atSign holds no challenge at all, so the shared ANON instance cannot leak one connection's state into another; setChallenge and consumeChallenge throw instead.

Commands that need no client identity are unchanged - KeyCommands, PublicKeyCommands.getSharedByMe/ByOther, and SharedKeyCommands.getEncryptKey, whose atSign is the other party's - since an unread parameter would be worse than uniformity. AtClientImpl now holds the context instead of its own atSign/keys/config copies.

No asserted command string changes; test edits are call sites only.

Also fixes javadoc left mis-wrapped by the 100-column comment formatter, and two incorrect doc comments: SharedKeyCommands.put said the value would be "decrypted", and EnrollCommands.unrevoke was documented as "revoke".

BREAKING CHANGE: the command signatures taking loose atSign / keys / config are removed rather than deprecated. SelfKeyCommands.get/put, SharedKeyCommands.get/put, PublicKeyCommands.get/put, Notifications.monitor, AuthenticationCommands.pkamAuthenticator and AuthenticationCommands.authenticateWithPkam now take an AtCommandExecutorContext; EnrollCommands.enroll, complete and approve likewise; and AtClientImpl.builder() takes context(...) in place of atSign(...), keys(...) and config(...). Callers using AtClients.builder() are unaffected.

closes #428

- How to verify it
Run the unit tests and integration tests, review the new tests

- Description for the changelog

standardize the command layer on (AtCommandExecutor, AtCommandExecutorContext)

Standardize the command layer on (AtCommandExecutor, AtCommandExecutorContext) as its
leading arguments, and create exactly one context per connection in
AtClients.createAtClient, passing it to both the executor builder and AtClientImpl.

The context cannot be created by AtClientImpl, which receives an already-built executor,
and AtCommandExecutors needs it to wire onReady - so AtClients creates it and shares it.
AtCommandExecutors.resolveContext uses one when supplied, ANON when there is no atSign,
and otherwise builds one - which keeps standalone builder callers working and stops a
second clientId being minted.

This also fixes a lost-metadata defect: every client built with withMonitoring(true) sent
a from: carrying no clientId and no client version, because AtClients passed the raw
config to Notifications.monitor while the context held the enriched one, and an explicit
onReady replaced the context-based sequence. AtClientsTest pins this - confirmed to fail
with the previous wiring restored.

AtCommandExecutorContext gains a two-arg constructor, and its config is now never null
and never modifiable - it is copied on construction, so a later change to the caller's
map cannot alter what the connection sends. atSign and keys stay nullable because both
states are load-bearing: a connection to the atDirectory / root server has no atSign, and
one that only issues from: (or authenticates with CRAM before any keys exist) has no
keys. A context with no atSign holds no challenge at all, so the shared ANON instance
cannot leak one connection's state into another; setChallenge and consumeChallenge throw
instead.

Commands that need no client identity are unchanged - KeyCommands,
PublicKeyCommands.getSharedByMe/ByOther, and SharedKeyCommands.getEncryptKey, whose
atSign is the other party's - since an unread parameter would be worse than uniformity.
AtClientImpl now holds the context instead of its own atSign/keys/config copies.

No asserted command string changes; test edits are call sites only.

Also fixes javadoc left mis-wrapped by the 100-column comment formatter, and two
incorrect doc comments: SharedKeyCommands.put said the value would be "decrypted", and
EnrollCommands.unrevoke was documented as "revoke".

BREAKING CHANGE: the command signatures taking loose atSign / keys / config are removed
rather than deprecated. SelfKeyCommands.get/put, SharedKeyCommands.get/put,
PublicKeyCommands.get/put, Notifications.monitor, AuthenticationCommands.pkamAuthenticator
and AuthenticationCommands.authenticateWithPkam now take an AtCommandExecutorContext;
EnrollCommands.enroll, complete and approve likewise; and AtClientImpl.builder() takes
context(...) in place of atSign(...), keys(...) and config(...). Callers using
AtClients.builder() are unaffected.

Closes #428

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jn2rsqSTPCBos8K9Xp2aAf
@akafredperry
akafredperry requested a review from gkc August 12, 2026 16:53
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.

Standardize command signatures on (AtCommandExecutor, AtCommandExecutorContext) and share one context per connection

1 participant