Skip to content

config:* rows pre-written by the node key before first boot are not seen by config_resolution — the resolved snapshot keeps the baked defaults, so net.listen_addr cannot move a first boot off :4242 #547

Description

@emooreatx

Surfaced by CIRISConformance while building the CC 5.3.4 node fixture (the /v1/accord-holders gate beside CIRISServer#248) on ciris-server 0.5.198 / persist v41.2.0.

What I tried

Server 0.5's config lives in CEG: config:* knobs are scores rows on the config:v1 dimension at cohort_scope: self, self-emitted by the node key (graph_config.rs), and compose resolves them at the config_resolution phase before the edge is built, so net.listen_addr can move the node off 0.0.0.0:4242. A conformance harness cannot use POST /v1/config for that (it needs the node already up, on the port the harness is trying to move it off), so it pre-writes the rows into the node's own database before the first boot:

home = tempfile.mkdtemp(); os.makedirs(f"{home}/identity"); os.makedirs(f"{home}/data")
open(f"{home}/identity/ed25519.seed", "wb").write(secrets.token_bytes(32))
open(f"{home}/identity/ml_dsa_65.seed", "wb").write(secrets.token_bytes(32))
e = ciris_persist.Engine(f"sqlite:///{home}/data/ciris_engine.db", "probe-node",
        local_key_id="probe-node", local_key_path=f"{home}/identity/ed25519.seed",
        local_pqc_key_id="probe-node-pqc", local_pqc_key_path=f"{home}/identity/ml_dsa_65.seed")
kid = e.register_self_federation_key("node", "probe", None, None, None)   # probe-node-<b32>
for key, value in (("net.listen_addr", "127.0.0.1:44412"), ("transport.node", False)):
    env = {"attesting_key_id": kid, "attested_key_id": kid, "dimension": "config:v1", "score": 1.0,
           "asserted_at": "2026-05-28T14:00:00.000Z", "witness_relation": "self",
           "cohort_scope": "self", "key": key, "value": value}
    e.emit_attestation_self(json.dumps({"attestation_type": "scores", "attested_key_id": kid,
                                        "cohort_scope": "self", "attestation_envelope": env}))   # ADMITTED
ciris_persist.reset_engine()
ciris_server.serve_with_python_adapter(Adapter(), home=home, key_id="probe-node")

The rows admit and read back exactly as graph_config::set_config would have written them (attestation_type=scores, cohort_scope=self, envelope.dimension=config:v1, key, value); list_attestations_by(kid) returns them; node_identity() after boot reports the same key_id the pre-write registered (local_derived_key_id on both sides — graph_config::self_key_id is that call).

What happens

INFO ciris_server::compose: resolved initial config:* snapshot (Server 0.5 — knobs now live in CEG, not env)
     initial_config=ResolvedConfig { transport_node: true, store_and_forward: true, … listen_addr: "0.0.0.0:4242" … }
ERROR build reticulum transport: … reticulum node start: I/O error: Address already in use (os error 98)

The snapshot is the baked defaults; the pre-written rows are not seen, so the edge binds 0.0.0.0:4242 (held by another node on this host) and boot fails. No warning names the rows as rejected or filtered.

The question

Is a first boot meant to be able to read config:* rows written by the node key before compose ever ran? If yes, this is a defect somewhere between live_config_rows (self_key_id + AttestationFilter{attesting_key_id, scores, dimension_prefixes ["config:"]} + build_caller_admission + scope-gated list_attestations) and the store — the by-attester listing sees the rows, the scoped one apparently does not. If no — if the only supported path is POST /v1/config on a running node — then a node can never be moved off :4242 on a host where that port is taken, which is every developer box running a second node, and a harness on such a host cannot stand a node up at all. Either answer is fine; I need to know which, because the CC 5.3.4 gate (the /v1/accord-holders shape, with the signature half strict-xfail on #248) is written and waiting on a bootable node.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions