Summary
On an iOS simulator build without keychain entitlements, ciris_keyring falls back from Secure Enclave to software on every open, and its tier-mismatch rule then supersedes the node key that provision_node_identity minted and verified ~120 ms earlier in the same boot. Edge's use_node_identity open follows and fails with Key not found: ed25519.seed. The engine reports it as a "[NODE-KEY] identity name mismatch", which sends the reader after the wrong cause.
Observed on ciris-server 0.5.204 (ciris_server._native, iOS simulator arm64 slice from ciris-server-v0.5.204-ios.tar.gz), CIRISAgent engine d5bcbdf, macos-15 runner, iOS 26.2 simulator. CIRISClient five-platform gate run 35372833410.
The sequence, from Documents/ciris/logs/ciris-server.log.2026-09-18
17:49:00.076 WARN ciris_keyring::storage: Secure Enclave storage initialization failed, falling back to software alias=ciris-agent-bootstrap error=Key generation failed: SE wrapper key generation failed: (OSStatus error -34018 - failed to add key to keychain ...)
17:49:00.139 WARN ciris_keyring::storage: Secure Enclave storage initialization failed, falling back to software alias=ciris-agent-bootstrap ...
17:49:00.250 WARN ciris_keyring::storage: Secure Enclave storage initialization failed, falling back to software alias=ciris-agent-bootstrap-node ...
17:49:00.337 INFO ciris_server::node_key: node key registered (identity_type=node) node_key_id=ciris-node-bootstrap-cbm6itzsb7
17:49:00.351 INFO ciris_server::node_key: node identity provisioned and VERIFIED (CC 3.4.7.3) — minted, registered and read back ahead of edge init ... node_key_id=ciris-node-bootstrap-cbm6itzsb7 alias=ciris-node-bootstrap
17:49:00.470 WARN ciris_keyring::storage: KEY SUPERSEDED — a higher-tier backend minted a FRESH key for this alias; a prior-install key was found and ARCHIVED ... alias=ciris-node-bootstrap
17:49:00.471 WARN ciris_keyring::storage: KEY SUPERSEDED — ... alias=ciris-node-bootstrap
Then, from the engine (ciris_agent_*.log):
[NODE-KEY] identity name mismatch: we provisioned 'ciris-node-bootstrap-tksnnmy4nf' under alias 'ciris-agent-bootstrap' ... and edge asked for a different name
[FAIL] Initialize Edge Runtime failed: ... use_node_identity=True: could not open the sealed Ed25519 node identity "ciris-node-bootstrap" under <container>/Documents/ciris/identity: Key not found: ed25519.seed. Edge OPENS this identity and never mints it
And the identity directory after the failure:
ciris-agent-bootstrap.ed25519.seed.blob 60 B
ciris-agent-bootstrap.master.key 32 B
ciris-node-bootstrap.ed25519.seed.blob.superseded-pre-v8 60 B ← the key edge needed
ciris-node-bootstrap.master.key.superseded-pre-v8 32 B
ml_dsa_65.seed / node_ml_dsa_65.seed
Same substrate, signed build: works
With Xcode's default ad-hoc simulator signing (entitlements applied — no CODE_SIGNING_ALLOWED=NO), no -34018, no software fallback, no supersede; the identity dir carries ciris-node-bootstrap.ed25519.seed.blob at 113 B (SE-wrapped) and edge opens it. CIRISAgent's own gate builds this way, which is why its nightly never meets this. Full boot to a serving node in 10.1 s (CIRISClient run 35383728911).
What seems wrong
- A key minted, registered and read back 120 ms earlier is not a "prior-install key." The supersede rule fires because the second open of the same alias in the same process took a different tier path (software, after the SE attempt failed) and treated the freshly minted software key as lower-tier leftovers. Within one boot — or more precisely, for a key
provision_node_identity just verified — supersede should not be reachable.
- The fallback is per-open, not per-process. The SE attempt fails identically three times in 200 ms; the keyring tries SE again each time and re-decides the tier. A process-wide "SE unavailable, software it is" decision would also make the supersede question moot.
- The engine's diagnostic points at name derivation ("these are derived independently and there is no parameter to reconcile them"); the names agreed here. That message is in CIRISAgent, but the fact it needs — "the key existed and was superseded by this process" — is only in the Rust log, which Python logging never sees. If
KEY SUPERSEDED is ever a legitimate outcome, surfacing it through the PyO3 boundary (a warning the engine can log, or a distinct error from use_node_identity open) would make it diagnosable from the engine side.
Footgun scope: any unentitled or dev build — xcodebuild ... CODE_SIGNING_ALLOWED=NO, or a host without keychain access — hits this deterministically, and the failure reads as an engine name-derivation bug.
Repro
CIRISClient .github/workflows/five-platform-live-qa.yml at b59f572^ (the commit before the signing fix) — the macOS job's "iOS simulator" step; the captured ios-logs/ artifact from run 35372833410 has all of the above. Same step at 58c222c passes.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Xv1s8qrUoPoFX2MHZUPA15
Summary
On an iOS simulator build without keychain entitlements,
ciris_keyringfalls back from Secure Enclave to software on every open, and its tier-mismatch rule then supersedes the node key thatprovision_node_identityminted and verified ~120 ms earlier in the same boot. Edge'suse_node_identityopen follows and fails withKey not found: ed25519.seed. The engine reports it as a "[NODE-KEY] identity name mismatch", which sends the reader after the wrong cause.Observed on ciris-server 0.5.204 (
ciris_server._native, iOS simulator arm64 slice fromciris-server-v0.5.204-ios.tar.gz), CIRISAgent engined5bcbdf, macos-15 runner, iOS 26.2 simulator. CIRISClient five-platform gate run 35372833410.The sequence, from
Documents/ciris/logs/ciris-server.log.2026-09-18Then, from the engine (
ciris_agent_*.log):And the identity directory after the failure:
Same substrate, signed build: works
With Xcode's default ad-hoc simulator signing (entitlements applied — no
CODE_SIGNING_ALLOWED=NO), no-34018, no software fallback, no supersede; the identity dir carriesciris-node-bootstrap.ed25519.seed.blobat 113 B (SE-wrapped) and edge opens it. CIRISAgent's own gate builds this way, which is why its nightly never meets this. Full boot to a serving node in 10.1 s (CIRISClient run 35383728911).What seems wrong
provision_node_identityjust verified — supersede should not be reachable.KEY SUPERSEDEDis ever a legitimate outcome, surfacing it through the PyO3 boundary (a warning the engine can log, or a distinct error fromuse_node_identityopen) would make it diagnosable from the engine side.Footgun scope: any unentitled or dev build —
xcodebuild ... CODE_SIGNING_ALLOWED=NO, or a host without keychain access — hits this deterministically, and the failure reads as an engine name-derivation bug.Repro
CIRISClient
.github/workflows/five-platform-live-qa.ymlatb59f572^(the commit before the signing fix) — the macOS job's "iOS simulator" step; the capturedios-logs/artifact from run 35372833410 has all of the above. Same step at58c222cpasses.🤖 Generated with Claude Code
https://claude.ai/code/session_01Xv1s8qrUoPoFX2MHZUPA15