partial flag is bornOne Safe-controlled cell fans out into an anchor + one cell per signer. ~11 min.
L19's very first aggregation step was "drop partial cells — multisig signer derivatives."
We took that on faith. This lesson is where those cells come from. The story is small and elegant: most DeFi admin power
doesn't sit behind one key, it sits behind a Gnosis Safe — a k-of-N multisig. The engine takes a cell pinned to
the Safe and fans it out to the individual signers, so you can see which human keys carry the risk — without
ever double-counting the dollars.
expandMultisigAnchors (at_risk_multisig.go) scans every existing cell. When a cell's
entity is a node of kind multisig, it becomes an anchor, and the engine spawns one
signer cell per Safe owner:
Each signer cell is a copy of the anchor with three changes:
frac := c.AtStakeUSD / float64(threshold) // k-of-N → divide by k (the THRESHOLD, not N) &Cell{ Entity: owner, // the signer key, not the Safe AtStakeUSD: frac, // fractional share Partial: true, // ← THE flag L19 drops AnchorOf: strPtr(best.anchorID), // back-pointer to the Safe ViaRole: strPtr("signer"), NSigners: intPtrFromInt(best.nOwners), // N (for context) // role/caps/severity/scope_market/oracle_ltv all inherited from the anchor }
at_stake / k — and exactly k of them recombine to the full anchor value (k × at_stake/k =
at_stake). Dividing by N would under-credit the keys: in a 3-of-5, any 3 keys suffice, so each is worth a
third, not a fifth. If safe_threshold is missing or nonsensical (≤ 0 or > N),
the code falls back to threshold = N. (readSafeThreshold / the frac calc in at_risk_multisig.go.)
partial: forensics without double-countingHere's the resolution of L19's loose end. Those five signer cells share the anchor's value_pool_id
(they're keyed identically to the anchor, just with the owner swapped in — see dedupCellKeyForSigner). So in
the rollup:
| If partials were summed | What actually happens (L19 drop-partial) | |
|---|---|---|
| cells in the Safe's pool | anchor $900M + 5 × $300M | only the anchor $900M survives |
| pool total (MAX-within) | $2.4B — inflated 2.67× | $900M — correct |
partial=true +
anchor_of + via_role="signer" are exactly the labels that let a reader tell a derived signer share
from a real direct authority. The flag isn't noise to discard — it's the provenance that makes the cell safe to
drop in one view and useful in another.
One real edge case. A Safe owner might also hold a direct admin cell on the same target (they're personally a key-holder and a Safe signer). The engine doesn't blindly add a second cell — the signer key already exists in the cell map. Instead:
if best.frac > existing.AtStakeUSD { // only if the signer share is LARGER existing.AtStakeUSD = best.frac existing.Partial = true // downgrade the direct cell to a derivative existing.AnchorOf = strPtr(best.anchorID) existing.ViaRole = strPtr("signer") overridden++ }
The direct authority stays visible, but gets correctly relabeled as a signer derivative when the Safe share dominates. Otherwise (direct authority is the bigger, more dominant signal) it's left untouched. And when a signer sits on multiple anchors for the same key, the code keeps the largest fractional share — the worst case, the same max-discipline as everywhere else.
Remember L20's per-(oracle, market) oracle cells with scope_market, oracle_ltv, and
oracle_other_borrowable_usd? If such a cell's entity is a Safe, its signer cells inherit all three, so
extractable recomputes against the same consuming market (min(at_stake × LTV, other_borrowable)) — just
with the signer's fractional at_stake. And because oracle cells use the 5-tuple key (L20), the signer key is built
the same way, preserving the value-defining-ness. The two subsystems compose cleanly:
// the Safe at 0x21f73D…73CA aggregates ~$4.5B at_stake across 7 RWAs; // post-#89, its 9 signers each get a partial-authority share — // today the full at_stake attributes to the Safe as one 'admin' entity.
at_stake = $700M. Each signer cell gets at_stake =partial = true. What happens to them in L19's aggregation?anchor_of and via_role = "signer" on a signer cell are there to…at_stake / threshold — divide by k (collusion count), not N, so k signers recombine to the anchor.partial cells share the anchor's value pool and are dropped to avoid double-counting, but persist as AT_RISK edges for per-key forensics.Grounded in: pkg/risk/at_risk_multisig.go (expandMultisigAnchors fan-out, frac = at_stake/threshold, partial/anchor_of/via_role/n_signers stamping, override-when-larger, largest-share-wins, oracle-anchor inheritance, readSafeThreshold fallback to N, dedupCellKeyForSigner 5-tuple-aware key). Verify against source — the code is the truth.