Skip to content

feat(workflow): decentrally-hosted external party#245

Open
schronck wants to merge 10 commits into
mainfrom
decentralice
Open

feat(workflow): decentrally-hosted external party#245
schronck wants to merge 10 commits into
mainfrom
decentralice

Conversation

@schronck

Copy link
Copy Markdown
Collaborator

Makes the external party genuinely decentrally-hosted: hosted across N participants at an M-of-N confirmation threshold, instead of the single-host v0.

Flow:

  • Coordinator generates the party's client-side Ed25519 key, builds the multi-host onboarding topology naming every host, signs the multi-hash once, and authorizes hosting on its own participant.
  • It fans the party-signed bundle out to each hosting peer over Noise (new InviteExternalParty + AllocateExternalParty message types); each peer authorizes hosting on its own participant.
  • Topology stays a proposal until the last host signs, so all invited hosts are required to complete.
  • The client key is sealed into the durable identity store so the party stays recoverable.

Validated on a 3-node localnet: onboards a real 2-of-3 party, all three hosts authorize, and it lists on every node.

Tests: unit (threshold bounds, key persistence, fingerprint derivation) plus a 2-of-3 e2e phase.

Not in scope: interactive submission (the party transacting) and /unhost.

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.

Pull request overview

Adds a new “ExternalParty” workflow so an external party can be onboarded as a multi-host, M-of-N-confirmation identity (hosted across multiple participants), plus end-to-end validation and UI surface to create/list these external parties.

Changes:

  • Introduces the ExternalParty workflow (coordinator + peer execution) with new Noise message types and workflow plumbing.
  • Adds API endpoints/types and frontend UI to start onboarding and list external parties.
  • Updates integration/e2e tooling and tests (new phase + optional single-test runner behavior).

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
integration-tests/run.sh Ensures script runs from repo root; adds selectable e2e test + interactive hold mode.
integration-tests/env.sh Hardens cleanup on macOS/bash3 set -u empty-array behavior.
crates/decman/tests/governance_workflows.rs Runs new external-party phase in the main e2e; adds standalone ignored test.
crates/decman/tests/common/phases/mod.rs Registers new external_party phase module.
crates/decman/tests/common/phases/external_party.rs New e2e phase driving /external-party and asserting results.
crates/decman/src/workflow/storage.rs Adds artifact/identity kinds for external-party onboarding/key persistence.
crates/decman/src/workflow/mod.rs Wires WorkflowType::ExternalParty coordinator + peer dispatch/step mapping.
crates/decman/src/workflow/external_party/config.rs Defines ExternalPartyConfig for workflow runs.
crates/decman/src/workflow/external_party/mod.rs Declares workflow step machine + command mapping.
crates/decman/src/workflow/external_party/keys.rs Implements client-side Ed25519 key generation, fingerprinting, signing.
crates/decman/src/workflow/external_party/steps.rs Wraps Canton Ledger-API calls to prepare topology and allocate external party.
crates/decman/src/workflow/external_party/coordinator.rs Coordinator driver: key mgmt, topology prep, allocate, fan-out, persistence.
crates/decman/src/workflow/external_party/peer.rs Peer handler to authorize hosting via AllocateExternalParty.
crates/decman/src/server/types.rs Exposes new API types for external parties.
crates/decman/src/server/mod.rs Adds invitation meta handling and registers external-party routes.
crates/decman/src/server/handlers/workflows.rs Implements /external-party start/status/cancel + /external-parties listing.
crates/decman/src/server/handlers/mod.rs Re-exports new workflow handlers.
crates/decman/src/server/handlers/invitations.rs Adds step totals for ExternalParty invitation cards.
crates/decman/src/noise/mod.rs Adds AllocateExternalParty + InviteExternalParty message types and tests.
crates/decman/src/noise/server.rs Registers ExternalParty as an active workflow variant and peer threshold behavior.
crates/decman/src/db/schema.rs Adds schema traits for querying runs by kind + persisting dec_party_id mid-run.
crates/decman/src/db/sqlite.rs Implements new DB read/write helpers (get_workflow_runs_by_kind, set_workflow_run_dec_party_id).
crates/decman/src/bin/gen_types.rs Includes new external-party types in type generation.
crates/decman/frontend/src/components/NotificationsView.tsx Avoids dead link for ExternalParty dec_party_id in workflow cards.
crates/decman/frontend/src/components/ExternalPartyList.tsx New table UI for listing external parties.
crates/decman/frontend/src/components/ExternalPartyDialog.tsx New dialog UI to start external-party onboarding with validation + mesh errors.
crates/decman/frontend/src/App.tsx Adds “External Parties” view/tab and create button behavior.
crates/decman-cli/src/api.rs Adds cancel path for ExternalParty.
crates/common/src/types.rs Adds ExternalParty to WorkflowKind/InvitationType.
crates/common/src/api.rs Adds request/response/invite payload types for external parties.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/common/src/api.rs Outdated
Comment thread crates/decman/src/workflow/external_party/mod.rs Outdated
Comment thread crates/decman/src/workflow/external_party/steps.rs
Comment thread crates/decman/src/server/handlers/workflows.rs Outdated
Comment thread crates/decman/src/workflow/storage.rs Outdated
Comment thread crates/decman/src/workflow/external_party/keys.rs
@schronck

Copy link
Copy Markdown
Collaborator Author

Copilot review: 6 findings, all addressed in ad7881a and threads resolved.

5 were stale comments, now fixed: dropped the single-node framing on ExternalPartyRequest, removed two dead links to an uncommitted doc, and corrected the /external-parties (reads dec_party_id, not artifacts) and EXTERNAL_PARTY_SEED (AES-GCM encrypted, not plaintext) comments.

Declined 1 (keys.rs fill_bytes): compiles as-is via the Rng trait in rand 0.10; the suggested RngCore import doesn't build. Verified with clippy --all-targets --all-features -D warnings.

@schronck
schronck requested a review from Copilot July 12, 2026 15:59
@schronck
schronck marked this pull request as ready for review July 12, 2026 15:59

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.

Pull request overview

Copilot reviewed 31 out of 32 changed files in this pull request and generated 3 comments.

Comment thread crates/decman/src/server/mod.rs Outdated
Comment thread integration-tests/run.sh
Comment thread crates/decman/frontend/src/components/ExternalPartyDialog.tsx Outdated
@schronck

Copy link
Copy Markdown
Collaborator Author

Copilot round 2 (on 6619c02): 3 findings, all fixed in c1ef838 and resolved.

  • server/mod.rs: auto-accept returned unconditionally, dropping the invite on insert/enqueue failure and hanging the coordinator; now falls through to a pending card for manual retry. (real fix)
  • run.sh: e2e now runs with --exact so DECPM_E2E_TEST matches exactly one test.
  • ExternalPartyDialog: corrected the threshold comment (server's unset default is all hosts).

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.

Pull request overview

Copilot reviewed 31 out of 32 changed files in this pull request and generated 4 comments.

.await
{
Ok(response) => interpret_invite_reply(peer_id, "external-party", &response)?,
Err(e) => tracing::error!("Failed to send invite to {peer_id}: {e}"),
observing_participant_uids: Vec::new(),
};

let mut client = utils::create_party_client(config, external_party_token()).await?;
identity_provider_id: String::new(),
};

let mut client = utils::create_party_client(config, external_party_token()).await?;
Comment on lines +100 to +101
let resp: Value = f.get_json(f.p1.http, "/external-parties").await.ok()?;
let parties = resp.get("parties").and_then(Value::as_array)?;
schronck added 7 commits July 13, 2026 14:41
Onboard a sovereign external party hosted across N participants at an M-of-N confirmation threshold. The coordinator generates the party's client-side Ed25519 key, builds the multi-host onboarding topology, authorizes hosting on its own participant, and fans the party-signed bundle out to each hosting peer over Noise to authorize on theirs. The client key is sealed into the durable identity store so the party stays recoverable.

Includes the Noise message types, invitation flow, peer-coordinated workflow, HTTP handler, frontend dialog, and the 2-of-3 e2e phase.
cargo-machete (#244) dropped rand from decman as unused; the external-party keys module now uses it. Also apply cargo fmt to the import blocks touched by this feature.
Drop the single-node framing and dead docs/ links, and fix the /external-parties and EXTERNAL_PARTY_SEED comments to match actual behavior (dec_party_id column; AES-GCM-encrypted artifacts).
…y FK

workflow_artifacts has a FK to workflow_runs, so the test must create the run row before writing artifacts (as the real flow does). Verified: full decman lib suite (283 tests) passes.
The phase posted /external-party but never accepted the invitations on P2/P3, so on non-interactive CI the peers never joined and the coordinator hung at WaitingForPeers until the 180s timeout. Mirror create_dec_party: probe + accept the ExternalParty invitations before waiting for completion.
Hosting an external party is a safe operation (the party's namespace key is client-held; a host only authorizes hosting on its own participant), so it shouldn't need a manual approval. A peer receiving an InviteExternalParty now spawns the peer run directly and records no pending card. Refactored insert_peer_run to take &SqlitePool so record_invitation can reuse it; the e2e phase no longer accepts manually.
Review round 2: (1) the auto-accept path returned unconditionally, dropping the invite when insert_peer_run or the job send failed and leaving the coordinator waiting for a host that never starts; now falls through to a pending card for manual retry. (2) run the e2e with --exact so DECPM_E2E_TEST resolves to exactly one test. (3) fix the ExternalPartyDialog threshold comment (server's unset default is all hosts, not majority).
schronck added 3 commits July 13, 2026 15:36
Adds /v0/tenant/* endpoints so a wallet (holding the party's Ed25519 key client-side) drives onboarding and transacting; DPM relays. Authenticated by a provider-issued API key (DECPM_TENANT_API_KEYS), separate from the Keycloak UI token (bypasses the JWT middleware; each handler enforces require_tenant_api_key; relaxed in insecure/test mode).

Onboarding: POST /v0/tenant/prepare (DPM builds the multi-host topology from the wallet's public key, returns the multi-hash to sign) -> POST /v0/tenant/onboard (wallet submits its signed bundle; DPM allocates + fans out via the existing multi-host workflow, skipping key-gen/sign) -> GET /v0/tenant/{party}/status. Transacting: POST /v0/tenant/{party}/prepare-submission + execute-submission (single-signer interactive submission) and GET /v0/tenant/{party}/acs.

Reshapes prepare_topology to take a public key; ExternalPartyConfig gains prepared_bundle; the coordinator skips key generation + identity persistence in the wallet flow. start_external_party's core is factored into spawn_external_party_onboarding, shared by both flows.
New phase: a client-generated key drives onboarding via /v0/tenant/prepare -> sign -> /v0/tenant/onboard (DPM never sees the private key), then asserts completion across P1+P2+P3 and that the party's ACS is readable via /v0/tenant/{party}/acs. Reuses ExternalKeyPair as the wallet. Full transacting (prepare/execute-submission of a real contract) is exercised on DevNet since it needs a concrete template.
The tenant ACS/prepare-submission calls use the participant's ledger user token, but that user has no rights on a freshly-hosted wallet party, so Canton returned 'no permission'. After a successful AllocateExternalParty, grant the ledger user CanReadAs the party on that host (read-only — never CanActAs, which would let a host impersonate the sovereign party). Best-effort + insecure/test-scoped, mirroring external_party_token; production external-party ledger auth remains the doc's open item.
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