eoc: allocate a facility among tenants, then bind the published band to it #63
Loading…
Reference in a new issue
No description provided.
Delete branch "eoc/multi-tenant-allocation"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
eoc-carbonaccounted one request bottom-up —it_joules × PUE × intensity— with no notion of a facility, a tenant, a share, or a residual.That shape is right for a single tenant and wrong for a facility, because nothing forces the tenants' figures to add up to what the building actually drew. Both failure modes are silent and opposite:
An allocation here is anchored top-down on a measured total, and the arithmetic is required to close.
Integer microjoules, on purpose
CarbonAccountisf64throughout — fine for a reported figure, unusable for an invariant. A conservation law that holds to within floating-point error is not a conservation law. Allocation usesu64µJ, matchingeoc_core::JouleCost, soΣ shares + residual == totalholds exactly. Sums are taken inu128so an overflowing share set is reported as over-allocation rather than wrapping into a total that appears to conserve.The residual is required, not optional
An allocation assigning 100% of a facility to its tenants asserts that every joule the building drew was attributable to a paying workload — essentially never true. Making the remainder mandatory forces an operator to state it.
proportional()sends its integer-division loss there too, rather than spreading it by a rounding rule: the residual is the honest home for energy no tenant demonstrably caused. And it rejects rather than clamps — an over-allocation means the model is wrong, and trimming it silently would hide the only signal that says so.The basis is a disclosed choice
The same facility split by measured energy, compute-seconds, or reserved capacity yields three different numbers for one tenant — all defensible, none comparable. An allocation that doesn't say which was used cannot be checked against another.
A tenant checks its own figure without seeing its co-tenants
Shares are committed to a Merkle sum tree: every node carries a hash and its subtree total. A tenant gets its own leaf plus the sibling
(hash, sum)pairs along its path, recomputes the root, and confirms the root's sum plus the residual equals the signed facility total.Both halves are checked together on purpose — a share can be genuinely included in a tree that does not add up, so inclusion alone proves nothing.
What it leaks is stated rather than glossed: the tenant learns the aggregate of each sibling subtree on its path (about
log2(n)partial sums), and no individual co-tenant's share or identity.aggregates_revealed()reports the count so both sides know. Hiding the aggregates as well needs a zero-knowledge range proof, which this module does not attempt and does not claim.Scoped as multi-tenant plumbing — nothing here signs anything or speaks to carbon.
Verification
30
eoc-carbontests pass (9 new): exact conservation, the overflow path, rounding-to-residual, odd leaf counts 1..9, tampered shares, misstated totals, restated residuals. No clippy warnings in the new file.Second commit: bind a published band to the allocation it came from
A band published on its own is unfalsifiable — a letter, a facility name, and nothing a reader can check.
FacilityRatingcarries the commitment to the share set the rating aggregates, so a tenant'sShareProofverifies against the same root the public rating cites.That join is the point: a tenant confirms its own consumption sits inside the very set that produced the published label, without seeing any co-tenant.
admits()checks the proof against the receipt's root and that the totals agree, so a proof that is internally valid against a different period is rejected rather than quietly accepted.Assurance is derived, not stored
The obvious design is a
verified: boolwritten into the artifact — which would repeat exactly the failure the band itself had: a claim about evidence, asserted by the party under audit.So there is no assurance field.
assurance()takes aSignatureStatusthe caller must supply:SelfDeclaredNotCheckedAttestedUnverified— neverVerifiedValidVerifiedInvalidAttestationInvalid— worse than noneAn unchecked signature is a string; it becomes evidence when someone checks it, and the type makes skipping that step impossible to do silently.
This crate performs no signature verification, and says so. There is no signing dependency in this workspace, and manufacturing a
Verifiedstate it cannot substantiate is precisely what the rest of the module refuses to do.The rating is data, not a dependency
RatingRecordrecords what was rated, not how — scale id, metric, unit, measured value — so a reader re-derives the letter against the cited scale instead of trusting it, and the receipt takes no dependency on whichever crate produced the band.The signed preimage covers every field that makes the rating a claim (band, value, scale, basis, residual, commitment), length-prefixed so shifting a character across a boundary cannot produce the same bytes. Attaching an attestation does not change what it signs.
Verification
36
eoc-carbontests pass (7 new), no clippy warnings in either new file.One test assertion was corrected rather than the code: with an odd leaf count a promoted node has no sibling at that level, so the revealed-aggregate count varies between tenants — the guarantee is the bound, not a fixed number.
Third commit: unblock the workspace build — two dependency faults, neither new
cargo check --workspacedid not pass onmain. Both causes are dependency declarations rather than code, and both were verified against the pristine tree before being touched.1.
eoc-tools— reqwest'squeryis a feature now. Four search adapters call.query(..), which in reqwest 0.13 sits behind#[cfg(feature = "query")]; it was unconditional in 0.12. Withdefault-features = falsethe method is simply not compiled, so the crate has not built since the 0.13 bump. Adding"query"is the whole fix.2.
rand 0.10.2does not compile. Its own source contains:An upstream defect in a published version, not a configuration problem. It reaches this workspace through
eoc-route-learned,eoc-spec-decodeandrand_distr. Pinned to 0.10.1, which builds.The lock is pinned rather than the manifests: the constraint is "not this exact release", not a new minimum, and a manifest pin would outlive the broken version and quietly hold the workspace back once upstream fixes it.
With both fixed the entire
eoc-rsworkspace checks clean, which is what makes the allocation and rating work in the preceding commits verifiable in place rather than only crate-locally.eoc: divide a measured facility total among tenants without losing any of itto eoc: allocate a facility among tenants, then bind the published band to it65972aeb94toad49173ff8