feat(tee-cli)!: assemble the founding validator set from the harvest - #91
Merged
Conversation
The founding reorder's assemble half: `manifest assemble` now derives the founding validator set from the harvested cohort instead of shipping the transitional empty set #87 left behind. With this the founding order is whole — init → up → harvest → assemble → configure — and `network_id` pins the founders' keys by construction. ## What assemble does now - Pairs each harvested box (`inputs/harvest/<node>.json`) with an authored withdrawal-credentials address and the box's current IP from `nodes/<node>.json`, and emits the completed `[[validators]]` through `summit genesis set-validators`. The empty-set prepend is gone. - Re-verifies every archived quote against the policy compiled from `inputs/measurements.json` before pinning anything. This is the second of the two gates: root_key admission gates privacy, assemble-time DCAP gates consensus membership — summit never talks to the custodian, so an unverified founding pubkey could otherwise vote from outside a TEE. - Refuses a harvest carrying a repeated node or consensus pubkey: summit keys validator accounts by node pubkey, so a duplicate silently collapses the set, and a shared consensus key is accidental-equivocation material. ## Withdrawal credentials are a list, not a node-name map `inputs/founder-withdrawal-credentials.json` is now a JSON array of addresses, one per founding node, paired to the boxes in node-name order at assemble time. Keying it by cohort node name forced the file to be authored after `up`, which is backwards — the founders' addresses are a fact about the founders, not about the infrastructure. `manifest init` scaffolds it as the fourth authored input, and `--founders N` fills in `0x00…0<i>` placeholders, which is all a throwaway founding needs. The count check that replaces the exact name match runs twice: at harvest before any quote is fetched, and again at assemble. The pairing is positional and nothing downstream can tell a swapped pair from an intended one, so assemble logs each pairing as it builds the set. ## up - `--count` is optional with `--network`: the authored credentials are the founding set, so they size the cohort. A contradicting `--count` is an error rather than a silent override. - The preflight image-pin check reads the authored `inputs/measurements.json` rather than the assembled manifest and policy. Provisioning precedes assembly now, so at `up` time the inputs are all a network directory holds. The success banner walks harvest → assemble → configure instead of pointing at the ceremony. ## Renames, docs, and the throwaway path - `measurement-policy.json` → `measurement-policy-bootstrap.json`: the file is only the *founding* allowlist — what `bootstrap_policy_hash` pins and registry genesis storage compiles from. The live policy is the registry contract's state, which the authority can mutate after genesis. - `manifest init --force` re-authors inputs for a re-found, matching the `assemble --force` that already existed. - `tee/docs/runbook-devnet.md` is the copy-pasteable throwaway recipe (init → provision → harvest → assemble → configure → smoke-test → down), and `networks/.gitignore` ignores `tmp-*/` wholesale so a scratch founding never dirties git. - `example-devnet` is documented as a schema example, not a runnable founding: no artifact set can be assembled without a live harvest. - Every path the CLIs print is absolute, so it resolves from any cwd. The seismic_node Pulumi program path and its default stack config move to `tee/cli/common/repo.py`, letting `manifest init` point the founder at the stack config to review (vhd_blob_url, operator_ip_cidr) without common importing the network CLI. - The dev stack's `vhd_blob_url` moves to a 2026-08-07 image, built after the summit-key-holder unit landed (seismic-images #48). Breaking for any existing network directory: the policy filename and the credentials file shape both changed, and `up --network` now expects `inputs/`. Nothing is founded on the old layout. Tests stay offline throughout (verifier subprocess and summit emitter mocked). Next: configure's per-boot IP splice, the launch-time pubkey and block-0 assertions, and deleting the genesis ceremony.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The founding reorder's assemble half:
manifest assemblenow derives the founding validator set from the harvested cohort instead of shipping the transitional empty set #87 left behind. With this the founding order is whole — init → up → harvest → assemble → configure — andnetwork_idpins the founders' keys by construction.What assemble does now
inputs/harvest/<node>.json) with an authored withdrawal-credentials address and the box's current IP fromnodes/<node>.json, and emits the completed[[validators]]throughsummit genesis set-validators. The empty-set prepend is gone.inputs/measurements.jsonbefore pinning anything. This is the second of the two gates: root_key admission gates privacy, assemble-time DCAP gates consensus membership — summit never talks to the custodian, so an unverified founding pubkey could otherwise vote from outside a TEE.Withdrawal credentials are a list, not a node-name map
inputs/founder-withdrawal-credentials.jsonis now a JSON array of addresses, one per founding node, paired to the boxes in node-name order at assemble time. Keying it by cohort node name forced the file to be authored afterup, which is backwards — the founders' addresses are a fact about the founders, not about the infrastructure.manifest initscaffolds it as the fourth authored input, and--founders Nfills in0x00…0<i>placeholders, which is all a throwaway founding needs.The count check that replaces the exact name match runs twice: at harvest before any quote is fetched, and again at assemble. The pairing is positional and nothing downstream can tell a swapped pair from an intended one, so assemble logs each pairing as it builds the set.
up
--countis optional with--network: the authored credentials are the founding set, so they size the cohort. A contradicting--countis an error rather than a silent override.inputs/measurements.jsonrather than the assembled manifest and policy. Provisioning precedes assembly now, so atuptime the inputs are all a network directory holds. The success banner walks harvest → assemble → configure instead of pointing at the ceremony.Renames, docs, and the throwaway path
measurement-policy.json→measurement-policy-bootstrap.json: the file is only the founding allowlist — whatbootstrap_policy_hashpins and registry genesis storage compiles from. The live policy is the registry contract's state, which the authority can mutate after genesis.manifest init --forcere-authors inputs for a re-found, matching theassemble --forcethat already existed.tee/docs/runbook-devnet.mdis the copy-pasteable throwaway recipe (init → provision → harvest → assemble → configure → smoke-test → down), andnetworks/.gitignoreignorestmp-*/wholesale so a scratch founding never dirties git.example-devnetis documented as a schema example, not a runnable founding: no artifact set can be assembled without a live harvest.tee/cli/common/repo.py, lettingmanifest initpoint the founder at the stack config to review (vhd_blob_url, operator_ip_cidr) without common importing the network CLI.vhd_blob_urlmoves to a 2026-08-07 image, built after the summit-key-holder unit landed (seismic-images feat(deploy_tee): open summit consensus port 18551 in the node NSG #48).Breaking for any existing network directory: the policy filename and the credentials file shape both changed, and
up --networknow expectsinputs/. Nothing is founded on the old layout.Tests stay offline throughout (verifier subprocess and summit emitter mocked). Next: configure's per-boot IP splice, the launch-time pubkey and block-0 assertions, and deleting the genesis ceremony.