Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 32s
actor_state.erl fold_update routes patches through apply_patch/3
which special-cases two rotation patch entries per design §9.6:
{add_publicKey, KeyProplist}
Append to :public_keys; default :created to activity's
:published if unset.
{supersede, OldKeyId}
Mark the matching key with :superseded_at = activity's
:published. Existing :superseded_at preserved (idempotent);
unknown :id no-op.
Other patch entries still last-write-wins per key (Step 2b semantics
preserved; verified by actor_state_pure 19/19 unchanged).
New exports:
key_history/1 — full :public_keys list (preserves superseded)
active_keys_at/2 — subset active at time T (mirrors envelope's
is_active_at; envelope keeps that predicate
private, so a local copy lives here)
find_key_by_id/2 — lookup by :id in the history
Rotation-purpose schema gating per §9.6 (rotation must be signed
by a key with :rotate-key purpose) is deferred to Step 5 (peer-side
stage_signature will plumb purpose through the pipeline).
16/16 in next/tests/key_rotation.sh covering:
- rotation arithmetic (add_publicKey + supersede combined)
- new key :created = rotation activity's :published
- supersede marks :superseded_at correctly
- key_history preserves all keys (superseded included)
- active_keys_at semantics at T=pre / T=rotation / T=post
- live envelope:verify_signature/2 round-trips:
pre-rotation activity signed with K1 -> ok
post-rotation activity signed with K2 -> ok
post-rotation activity signed with K1 -> {error, no_active_key}
- non-rotation Update patches preserve key history
- add_publicKey alone (no supersede) keeps old key active
- supersede alone empties active set
- supersede with unknown id is a no-op
- second supersede on superseded key is idempotent
Conformance 761/761. 132/132 across 9 Step-3-adjacent suites
(key_rotation, actor_state_pure, actor_lifecycle, envelope_sig,
envelope_shape, envelope_canonical, nx_kernel_multi, bootstrap_start,
smoke_app_pure).
9.1 KiB
9.1 KiB