feat: integrate confidential ballot provider evidence

This commit is contained in:
2026-08-02 03:41:05 +02:00
parent 133e55987e
commit 11b946ea78
4 changed files with 36 additions and 1 deletions
+5
View File
@@ -73,6 +73,11 @@ Approval, and evidence.
A governed ballot is delegated to `voting.ballots` when Voting is installed;
Committee retains the meeting/agendum linkage and verified aggregate result.
For provider-backed Voting ballots, Committee also snapshots the assurance
profile, provider and ballot references, and sanitized provider evidence. A
reference `local_confidential` result remains explicitly uncertified; its
server-encrypted casts do not satisfy secret-ballot, anonymity,
coercion-resistance, or legal-certification requirements.
The following direct provider path remains a 0.1 compatibility contract only.
A provider-bound vote on that path is finalized through
`committee.ballot_adapter.<provider>`. The adapter receives tenant, vote,
+13
View File
@@ -275,6 +275,13 @@ class CommitteeBallotFinalizer:
expected_revision=voting_expected_revision,
idempotency_key=f"committee:{idempotency_key}",
)
assurance_profile = str(
ballot.get("assurance_profile") or "recorded"
).strip()
if assurance_profile != "recorded" and not result.evidence:
raise CommitteeWorkspaceError(
"Provider-backed Voting results require sanitized provider evidence."
)
choices = tuple(str(item) for item in current.attributes.get("choices", ()))
if set(result.counts) != set(choices):
raise CommitteeWorkspaceError(
@@ -301,6 +308,12 @@ class CommitteeBallotFinalizer:
"voting_ballot_id": voting_ballot_id,
"voting_ballot_revision": result.revision,
"voting_result_sha256": result.result_sha256,
"voting_assurance_profile": assurance_profile,
"voting_provider_id": ballot.get("provider_id"),
"voting_provider_ballot_ref": ballot.get("provider_ballot_ref"),
"voting_provider_evidence": [
dict(item) for item in result.evidence
],
"counts": {key: int(value) for key, value in result.counts.items()},
"weighted_counts": {
key: int(value) for key, value in result.weighted_counts.items()
+3 -1
View File
@@ -219,7 +219,9 @@ DOCUMENTATION = (
"effects. Committee does not own generic Mandate or Decision persistence; optional "
"providers resolve and record those objects when installed, while a protected local "
"projection preserves the bounded fallback. Provider-bound ballots are finalized "
"through adapter capabilities without retaining individual ballots."
"through adapter capabilities without retaining individual ballots. Voting-backed "
"results preserve sanitized provider assurance evidence and never promote the "
"uncertified local confidential reference provider to a certified profile."
),
layer="available",
documentation_types=("admin", "user"),
+15
View File
@@ -90,6 +90,9 @@ class VotingBallots:
"id": ballot_id,
"revision": 2,
"state": "open",
"assurance_profile": "confidential",
"provider_id": "local_confidential",
"provider_ballot_ref": "local-confidential:ballot-1",
"context": {"module": "committee", "resource_id": "vote-voting"},
}
@@ -117,6 +120,13 @@ class VotingBallots:
threshold_met=True,
winning_options=("yes",),
result_sha256="c" * 64,
evidence=(
{
"kind": "reference_provider_result",
"provider_id": "local_confidential",
"certified": False,
},
),
)
def certify_ballot(self, *args, **kwargs):
@@ -732,6 +742,11 @@ class CommitteeWorkspaceTests(unittest.TestCase):
self.assertEqual("closed", closed.state)
self.assertEqual({"yes": 2, "no": 1}, closed.attributes["counts"])
self.assertEqual("c" * 64, closed.attributes["voting_result_sha256"])
self.assertEqual(
"confidential",
closed.attributes["voting_assurance_profile"],
)
self.assertFalse(closed.attributes["voting_provider_evidence"][0]["certified"])
self.assertEqual("voting", closed.evidence[0].owner_module)
self.assertNotIn("selections", closed.attributes)