Skip to content

405 map commands phase 22 implement integrationhub map command dispatcher#423

Open
owleyeview wants to merge 10 commits intomainfrom
405-map-commands----phase-22-implement-integrationhub-map-command-dispatcher
Open

405 map commands phase 22 implement integrationhub map command dispatcher#423
owleyeview wants to merge 10 commits intomainfrom
405-map-commands----phase-22-implement-integrationhub-map-command-dispatcher

Conversation

@owleyeview
Copy link
Collaborator

@owleyeview owleyeview commented Mar 17, 2026

Implement Runtime command dispatcher with centralized lifecycle enforcement

Closes #405

Summary

  • Implement CommandDescriptor with MutationClassification to declare lifecycle requirements (requires_open_tx, requires_commit_guard, mutation classification) per command variant
  • Centralize all lifecycle enforcement in Runtime::dispatch_inner(): open-transaction validation, commit guard acquisition, and host mutation entry checks — all descriptor-driven
  • Fill in all scope-specific dispatcher stubs: 17 transaction actions, 9 readable holon actions, 6 writable holon actions (Query remains explicitly NotImplemented)
  • Simplify MapResult/MapResultWire variants; add Value(BaseValue) and rename for clarity
  • Add RequestOptions (gesture_id, gesture_label, snapshot_after) and GestureId to MapIpcRequest
  • Change RequestId to wrap MapInteger instead of u64
  • Add context: Arc<TransactionContext> to HolonCommand so holon writes go through mutation entry checks

Deferred to follow-up issue (blocked on #412)

  • RuntimeSession stores ClientSession
  • snapshot_after policy executed by Runtime::dispatch_inner() for mutating commands
  • TransactionAction::Undo and Redo variants implemented and wired

@owleyeview owleyeview linked an issue Mar 17, 2026 that may be closed by this pull request
16 tasks
Copy link
Owner

@evomimic evomimic left a comment

Choose a reason for hiding this comment

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

Findings

1. [P1] Finalized transactions are never removed from RuntimeSession

File: [host/crates/map_commands/src/dispatch/transaction_dispatch.rs]

Commit and LoadHolons both finalize the transaction context, but this dispatcher no longer calls RuntimeSession::remove_transaction(). That leaves committed transactions in active_transactions indefinitely, which both leaks the session map over time and changes post-commit behavior from “invalid/removed tx” to “still bound but closed” on later requests. The Phase 2.2 spec explicitly calls out session cleanup when transactions are finalized, and the helper is still present in .../dispatch/runtime_session.rs so this looks like an accidental regression from the previous implementation.

However, this issue brought to light a bigger issue that I would like to resolve in this PR (although it does constitute a scope expansion). See additional comment for details.

2. [P2] Manual open-transaction precheck masks richer lifecycle errors

File: host/crates/map_commands/src/dispatch/runtime.rs

The new requires_open_tx branch returns TransactionNotOpen for every non-open state before the shared transaction policy gates run. That flattens committed transactions into a generic error even though the core lifecycle matrix distinguishes TransactionAlreadyCommitted from TransactionNotOpen in .../core_shared_objects/transactions/transaction_context.rs. Because this check runs before ensure_host_mutation_entry_allowed() / begin_host_commit_ingress_guard(), mutating commands on a committed transaction now lose the more specific, deterministic error the rest of the stack uses.

Verification

I confirmed happ and host both build clean. All sweetests pass. npm start yields expected runtime behavior.

@evomimic
Copy link
Owner

evomimic commented Mar 20, 2026

PR Note: Transaction Commit Semantics, Lifecycle Ownership, and Deferred Cleanup

As noted in the review findings, this PR brought to light a few nuances that we should clean up while we are establishing a strong foundation to move forward from.

Summary

This PR updates transaction commit behavior to support multiple concurrent transactions and preserve short-term post-commit reference resolution.

The key changes are:

  • A shift from commit-as-teardown to commit-as-lifecycle-transition
  • Explicit separation between transaction-local behavior and system-level lifecycle management
  • Clarification that Commit remains a transaction-scoped command, but is orchestrated by RuntimeSession

Changes Introduced

1. Commit no longer clears holon pools

On successful commit, the following are no longer cleared:

  • Nursery
  • TransientHolonManager pools

Rationale

Immediate teardown invalidates:

  • Staged references
  • Transient references

With concurrent transactions, this is no longer acceptable. Retaining these pools allows outstanding references to remain resolvable briefly after commit.


2. Commit is orchestrated by RuntimeSession (not TransactionContext alone)

Commit remains defined as:

pub enum TransactionAction {
    Commit,
    ...
}

However, introduce a commit_transaction method on RuntimeSession that delegates to TransactionContext for the actual commit mechanics, but if commit completes, then move transaction from active_transactions to archived_transasctions.
However, its execution is no longer purely local to TransactionContext.

Execution flow

  1. Runtime dispatches TransactionAction::Commit
  2. RuntimeSession resolves the active transaction
  3. TransactionContext::commit() performs commit mechanics
  4. RuntimeSession performs lifecycle transition (see below)

Rationale

Commit has system-level effects, including:

  • Removal from active transaction set
  • Insertion into archive
  • Changes to transaction lookup semantics

Therefore:

Commit cannot be treated as a purely transaction-local operation.

At the same time:

Commit is still conceptually an operation on a transaction, not on the space itself.


3. Commit remains a Transaction-scoped command (intentional decision)

We explicitly retain Commit as a TransactionAction, rather than moving it to SpaceCommand.

Rationale

  • Commit operates on a specific transaction (not the space itself)
  • TransactionCommand already carries a bound TransactionContextHandle
  • Moving Commit to space scope would:
    • Reintroduce tx_id-based lookup above binding
    • Weaken type safety
    • Break symmetry with other transaction-targeted operations

Design Principle

Scope reflects what the command targets, not who orchestrates its side effects.

So:

  • Target → Transaction → TransactionAction::Commit
  • Orchestration → RuntimeSession

4. TransactionContext is moved to an archive on commit

On successful commit:

  • The TransactionContext is removed from the active transaction set
  • It is added to a committed/archive collection

Rationale

This ensures:

  • Committed transactions are no longer considered active
  • Mutation is no longer permitted
  • Context remains available for short-term reference resolution

5. Cleanup is deferred (out of scope)

This PR does not implement resource reclamation.

Future work will include:

  • Archive pruning (TTL, LRU, or size-bound policies)
  • Reclamation of Nursery and TransientHolonManager resources
  • Eventual invalidation of stale references

Updated Transaction Lifecycle Model

Transactions now follow a three-stage lifecycle:

  1. Open

    • Mutable
    • Present in active transaction set
  2. Committed

    • Immutable
    • Removed from active set
    • Retained in archive for short-term resolution
  3. Purged (future)

    • Resources reclaimed
    • References may no longer resolve

Implementation Notes

  • RuntimeSession owns:

    • Active transaction registry
    • Archive transaction registry
    • Lifecycle transitions
  • TransactionContext owns:

    • Commit mechanics
    • Internal state transitions
  • Dispatch behavior:

Runtime
  → dispatch_transaction
    → RuntimeSession.commit_transaction(context)
      → TransactionContext.commit()
      → move context to archive
  • Mutation paths must only operate on active transactions

  • Reference-layer resolution may still succeed against archived transactions


Design Intent

This change establishes:

  • Clear separation between transaction-local behavior and system-level orchestration
  • Preservation of type-safe, context-bound transaction commands
  • Compatibility with concurrent transaction execution
  • A foundation for deferred, policy-driven cleanup

It ensures that:

A transaction does not manage its own lifecycle within the system.

Lifecycle is owned by RuntimeSession, while behavior remains within TransactionContext.

@owleyeview
Copy link
Collaborator Author

requested changes have been implemented:

  • RuntimeSession now owns commit orchestration via commit_transaction(), which delegates to TransactionContext::commit() for mechanics, then moves the context from active_transactions to archived_transactions
  • Commit remains a TransactionAction (not moved to SpaceCommand)

Note on clear_stage: I intentionally kept the clear_stage call in TransactionContext::commit(). Current sweettests rely on fixture tokens remaining usable across commits — e.g., stage_new_version_fixture.rs:36 commits and then reuses book_staged_token in later steps. clear_stage only clears the nursery's staged holons, not the transient pool (which can contain response holons). Let me know if you think the test should be changed instead.

On separating map_commands into map_runtime + map_commands: I looked into this and runtime and dispatch actually depend on each other — dispatch needs the runtime session for orchestration, and runtime delegates to the dispatchers. The only thing that could be cleanly factored out is the pure command vocabulary (domain types, wire types, and descriptors), which has no runtime dependencies. Would you like me to extract those into a separate map_command crate and rename whats left (still including dispatch) map_runtime?

@owleyeview owleyeview requested a review from evomimic March 22, 2026 03:00
@evomimic
Copy link
Owner

The change so that RuntimeSession now owns commit orchestration via commit_transaction() looks good and the move of transaction context from active_transactions to archived_transactions on commit also looks fine.

Note on clear_stage

I'm confused by your note on clear_stage. The request was to change commit so that it no longer removed EITHER staged or transient holon pools. In other words, post-commit BOTH the Nursery and TransientHolonManager would retain fully populated HolonPools. In the latest code, the Nursery's HolonPool is still being cleared and your comment doesn't say why. Your point about fixture tokens being used across commits argues for NOT clearing the transient holon pool on commit but seems unrelated to the question of retaining the Nursery's HolonPool.

Note on separating map_commands into map_runtime + map_commands

Thanks for the analysis. I agree the situation is a bit more complicated than I initially thought. The main thing I'm trying to ensure is that we not have wire types creep into command signatures.

See follow-on comment for a more complete proposal.

@evomimic
Copy link
Owner

Follow-on proposal: restructure map_commands dependencies to keep wire types out of command execution

The main architectural constraint I want to preserve is this:

wire / transport types should not creep into MAP command execution signatures

To get there, I think the cleaner split is not “Runtime vs Dispatcher” first, but rather:

  1. command contract / vocabulary
  2. wire transport layer
  3. runtime-backed execution layer

That also seems like a good opportunity to tighten the terminology. If the scope-specific units depend on RuntimeSession and other runtime-owned services, they are not really “dispatchers” in the thin architectural sense. They are closer to runtime command handlers. Since we do not yet have a broad stable API surface, this seems like the right time to make that terminology correction.

Proposed crate split

1. map_commands_contract

Owns the command vocabulary only:

  • command enums
  • result enums
  • descriptors
  • mutation classification
  • other contract-level types used by execution

This crate should have no dependency on wire/IPC types and no dependency on runtime/session code.

Suggested contents:

  • current domain/*
  • renamed to reflect “contract” rather than “domain” if we want the boundary to be explicit

2. map_commands_wire

Owns transport concerns only:

  • MapIpcRequest / MapIpcResponse
  • RequestId, RequestOptions, etc.
  • *Wire command/result types
  • serde / IPC envelope concerns
  • mapping between wire types and contract types

This crate depends on map_commands_contract, but not on runtime.

3. map_commands_runtime

Owns execution and orchestration:

  • Runtime
  • RuntimeSession
  • lifecycle / descriptor enforcement
  • command routing
  • scope-specific command handlers
  • access to runtime-owned services

This crate depends on map_commands_contract, but not on map_commands_wire.

Proposed dependency direction

map_commands_contract
        ↑
        │
map_commands_wire     map_commands_runtime
        ↑              ↑
        └──────┬───────┘
               │
          conductora

In other words:

  • contract knows nothing about wire or runtime
  • wire knows contract
  • runtime knows contract
  • conductora composes wire + runtime at the Tauri boundary

Proposed terminology changes

I’d recommend updating the naming so it matches actual responsibilities.

Runtime layer

Keep:

  • Runtime
  • RuntimeSession

Rename:

  • map_commands::dispatch -> map_commands_runtime / runtime

Scope-specific execution units

Rename:

  • space_dispatch.rs -> space_handler.rs
  • transaction_dispatch.rs -> transaction_handler.rs
  • holon_dispatch.rs -> holon_handler.rs

Rename functions:

  • dispatch_space -> handle_space
  • dispatch_transaction -> handle_transaction
  • dispatch_holon -> handle_holon

Reason:
these units are not just routing; they execute runtime-bound command behavior.

Runtime methods

Suggested renames:

  • Runtime::dispatch(...) -> Runtime::handle_ipc(...) or Runtime::execute_request(...)
  • dispatch_inner(...) -> execute_bound_command(...)
  • dispatch_command(...) -> route_command(...)

That gives a cleaner layered story:

  • ingress receives a request
  • runtime handles IPC
  • runtime binds + enforces policy
  • runtime routes to handlers
  • handlers execute command behavior

Why I think this is the right split

This preserves the architectural boundary I care about without forcing an artificial separation between runtime and runtime-bound handlers.

It also aligns better with the deployment diagram:

  • Runtime remains the owner of session state, policy enforcement, and shared services
  • handler modules remain part of the runtime-backed execution layer
  • transport/wire concerns live outside that layer instead of leaking inward

Transitional option

If we want to avoid a large one-shot rename, we could temporarily keep map_commands as a facade crate that re-exports:

  • map_commands_contract
  • map_commands_wire
  • map_commands_runtime

That would let us migrate internal structure first and simplify call sites incrementally.

Bottom line

I’m not attached to the exact crate names, but I do think we should aim for these two outcomes:

  1. runtime-backed command execution should not depend on wire types
  2. the current “dispatchers” should be renamed to “handlers” (or similar), because that is a better description of what they actually are

If that general direction makes sense, I’m happy to help refine the exact shape further.

- Remove clear_stage() from TransactionContext::commit(); nursery pools are no longer cleared on commit
- Split CommandDescriptor::read_only() into transaction_read_only() (requires open tx) and holon_read_only() (allows committed tx) so all TransactionActions reject committed transactions while HolonAction reads remain accessible
- Return TransactionAlreadyCommitted (not TransactionNotOpen) when commands target a committed transaction LookupFacade methods with assert_allowed for defense in depth                       - Update RuntimeSession::get_transaction to resolve from both active and archived pools
- Rename dispatch files/functions to handler terminology per architect review (dispatch_inner → execute_bound_command, dispatch_command → route_command, dispatch → handle_ipc)
…port types are kept out of execution signatures
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.

MAP Commands -- Phase 2.2 — Implement IntegrationHub MAP Command dispatcher

2 participants