diff --git a/polkadot/node/core/approval-voting/src/approval_checking.rs b/polkadot/node/core/approval-voting/src/approval_checking.rs
index d8384f88d4c9598c528ee097ec148d887e26d29e..4bdf3ffb6e1731a810f16a36f69d8a5c03480365 100644
--- a/polkadot/node/core/approval-voting/src/approval_checking.rs
+++ b/polkadot/node/core/approval-voting/src/approval_checking.rs
@@ -66,6 +66,8 @@ pub enum Check {
 	Unapproved,
 	/// The candidate is approved, with the given amount of no-shows.
 	Approved(usize),
+	/// The candidate is approved by one third of all validators.
+	ApprovedOneThird,
 }
 
 impl Check {
@@ -74,6 +76,15 @@ impl Check {
 		match *self {
 			Check::Unapproved => false,
 			Check::Approved(_) => true,
+			Check::ApprovedOneThird => true,
+		}
+	}
+
+	/// The number of known no-shows in this computation.
+	pub fn known_no_shows(&self) -> usize {
+		match *self {
+			Check::Approved(n) => n,
+			_ => 0,
 		}
 	}
 }
@@ -84,16 +95,17 @@ pub fn check_approval(
 	approval: &ApprovalEntry,
 	required: RequiredTranches,
 ) -> Check {
+
+	// any set of size f+1 contains at least one honest node. If at least one
+	// honest node approves, the candidate should be approved.
+	let approvals = candidate.approvals();
+	if 3 * approvals.count_ones() > approvals.len() {
+		return Check::ApprovedOneThird;
+	}
+
 	match required {
 		RequiredTranches::Pending { .. } => Check::Unapproved,
-		RequiredTranches::All => {
-			let approvals = candidate.approvals();
-			if 3 * approvals.count_ones() > 2 * approvals.len() {
-				Check::Approved(0)
-			} else {
-				Check::Unapproved
-			}
-		}
+		RequiredTranches::All => Check::Unapproved,
 		RequiredTranches::Exact { needed, tolerated_missing, .. } => {
 			// whether all assigned validators up to `needed` less no_shows have approved.
 			// e.g. if we had 5 tranches and 1 no-show, we would accept all validators in
@@ -411,7 +423,7 @@ mod tests {
 	}
 
 	#[test]
-	fn all_requires_supermajority() {
+	fn exact_takes_only_assignments_up_to() {
 		let mut candidate: CandidateEntry = approval_db::v1::CandidateEntry {
 			candidate: Default::default(),
 			session: 0,
@@ -419,26 +431,62 @@ mod tests {
 			approvals: bitvec![BitOrderLsb0, u8; 0; 10],
 		}.into();
 
-		for i in 0..6 {
+		for i in 0..3 {
 			candidate.mark_approval(ValidatorIndex(i));
 		}
 
 		let approval_entry = approval_db::v1::ApprovalEntry {
-			tranches: Vec::new(),
+			tranches: vec![
+				approval_db::v1::TrancheEntry {
+					tranche: 0,
+					assignments: (0..2).map(|i| (ValidatorIndex(i), 0.into())).collect(),
+				},
+				approval_db::v1::TrancheEntry {
+					tranche: 1,
+					assignments: (2..5).map(|i| (ValidatorIndex(i), 1.into())).collect(),
+				},
+				approval_db::v1::TrancheEntry {
+					tranche: 2,
+					assignments: (5..10).map(|i| (ValidatorIndex(i), 0.into())).collect(),
+				},
+			],
 			assignments: bitvec![BitOrderLsb0, u8; 1; 10],
 			our_assignment: None,
 			backing_group: GroupIndex(0),
 			approved: false,
 		}.into();
 
-		assert!(!check_approval(&candidate, &approval_entry, RequiredTranches::All).is_approved());
-
-		candidate.mark_approval(ValidatorIndex(6));
-		assert!(check_approval(&candidate, &approval_entry, RequiredTranches::All).is_approved());
+		assert!(check_approval(
+			&candidate,
+			&approval_entry,
+			RequiredTranches::Exact {
+				needed: 0,
+				tolerated_missing: 0,
+				next_no_show: None,
+			},
+		).is_approved());
+		assert!(!check_approval(
+			&candidate,
+			&approval_entry,
+			RequiredTranches::Exact {
+				needed: 1,
+				tolerated_missing: 0,
+				next_no_show: None,
+			},
+		).is_approved());
+		assert!(check_approval(
+			&candidate,
+			&approval_entry,
+			RequiredTranches::Exact {
+				needed: 1,
+				tolerated_missing: 2,
+				next_no_show: None,
+			},
+		).is_approved());
 	}
 
 	#[test]
-	fn exact_takes_only_assignments_up_to() {
+	fn one_honest_node_always_approves() {
 		let mut candidate: CandidateEntry = approval_db::v1::CandidateEntry {
 			candidate: Default::default(),
 			session: 0,
@@ -446,7 +494,7 @@ mod tests {
 			approvals: bitvec![BitOrderLsb0, u8; 0; 10],
 		}.into();
 
-		for i in 0..6 {
+		for i in 0..3 {
 			candidate.mark_approval(ValidatorIndex(i));
 		}
 
@@ -471,32 +519,56 @@ mod tests {
 			approved: false,
 		}.into();
 
-		assert!(check_approval(
+		let exact_all = RequiredTranches::Exact {
+			needed: 10,
+			tolerated_missing: 0,
+			next_no_show: None,
+		};
+
+		let pending_all = RequiredTranches::Pending {
+			considered: 5,
+			next_no_show: None,
+			maximum_broadcast: 8,
+			clock_drift: 12,
+		};
+
+		assert!(!check_approval(
 			&candidate,
 			&approval_entry,
-			RequiredTranches::Exact {
-				needed: 1,
-				tolerated_missing: 0,
-				next_no_show: None,
-			},
+			RequiredTranches::All,
 		).is_approved());
+
 		assert!(!check_approval(
 			&candidate,
 			&approval_entry,
-			RequiredTranches::Exact {
-				needed: 2,
-				tolerated_missing: 0,
-				next_no_show: None,
-			},
+			exact_all.clone(),
+		).is_approved());
+
+		assert!(!check_approval(
+			&candidate,
+			&approval_entry,
+			pending_all.clone(),
 		).is_approved());
+
+		// This creates a set of 4/10 approvals, which is always an approval.
+		candidate.mark_approval(ValidatorIndex(3));
+
 		assert!(check_approval(
 			&candidate,
 			&approval_entry,
-			RequiredTranches::Exact {
-				needed: 2,
-				tolerated_missing: 4,
-				next_no_show: None,
-			},
+			RequiredTranches::All,
+		).is_approved());
+
+		assert!(check_approval(
+			&candidate,
+			&approval_entry,
+			exact_all,
+		).is_approved());
+
+		assert!(check_approval(
+			&candidate,
+			&approval_entry,
+			pending_all,
 		).is_approved());
 	}
 
diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs
index 87eda0ef972e6fe2ce417ef4a096b1fe87e07c5e..4ee997d77d8ec8aa5cb5985181d858df7b65f5e4 100644
--- a/polkadot/node/core/approval-voting/src/lib.rs
+++ b/polkadot/node/core/approval-voting/src/lib.rs
@@ -1316,7 +1316,7 @@ fn check_and_apply_full_approval(
 			required_tranches.clone(),
 		);
 
-		if let approval_checking::Check::Approved(no_shows) = check {
+		if check.is_approved() {
 			tracing::trace!(
 				target: LOG_TARGET,
 				?candidate_hash,
@@ -1324,6 +1324,8 @@ fn check_and_apply_full_approval(
 				"Candidate approved under block.",
 			);
 
+			let no_shows = check.known_no_shows();
+
 			let was_approved = block_entry.is_fully_approved();
 
 			newly_approved.push(*block_hash);
diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs
index 40d84d698a1805bef2598fbe7cded786e15fb758..9b9c153919fbc2ec1441a662762d9db3fd142dcf 100644
--- a/polkadot/node/core/approval-voting/src/tests.rs
+++ b/polkadot/node/core/approval-voting/src/tests.rs
@@ -869,7 +869,7 @@ fn check_and_apply_full_approval_does_not_load_cached_block_from_db() {
 }
 
 #[test]
-fn assignment_triggered_by_all_with_less_than_supermajority() {
+fn assignment_triggered_by_all_with_less_than_threshold() {
 	let block_hash = Hash::repeat_byte(0x01);
 
 	let mut candidate_entry: CandidateEntry = {
@@ -896,19 +896,13 @@ fn assignment_triggered_by_all_with_less_than_supermajority() {
 		}.into()
 	};
 
-	// 2-of-4
+	// 1-of-4
 	candidate_entry
 		.approval_entry_mut(&block_hash)
 		.unwrap()
 		.import_assignment(0, ValidatorIndex(0), 0);
 
-	candidate_entry
-		.approval_entry_mut(&block_hash)
-		.unwrap()
-		.import_assignment(0, ValidatorIndex(1), 0);
-
 	candidate_entry.mark_approval(ValidatorIndex(0));
-	candidate_entry.mark_approval(ValidatorIndex(1));
 
 	let tranche_now = 1;
 	assert!(should_trigger_assignment(
@@ -920,7 +914,7 @@ fn assignment_triggered_by_all_with_less_than_supermajority() {
 }
 
 #[test]
-fn assignment_not_triggered_by_all_with_supermajority() {
+fn assignment_not_triggered_by_all_with_threshold() {
 	let block_hash = Hash::repeat_byte(0x01);
 
 	let mut candidate_entry: CandidateEntry = {
@@ -947,7 +941,7 @@ fn assignment_not_triggered_by_all_with_supermajority() {
 		}.into()
 	};
 
-	// 3-of-4
+	// 2-of-4
 	candidate_entry
 		.approval_entry_mut(&block_hash)
 		.unwrap()
@@ -958,14 +952,8 @@ fn assignment_not_triggered_by_all_with_supermajority() {
 		.unwrap()
 		.import_assignment(0, ValidatorIndex(1), 0);
 
-	candidate_entry
-		.approval_entry_mut(&block_hash)
-		.unwrap()
-		.import_assignment(0, ValidatorIndex(2), 0);
-
 	candidate_entry.mark_approval(ValidatorIndex(0));
 	candidate_entry.mark_approval(ValidatorIndex(1));
-	candidate_entry.mark_approval(ValidatorIndex(2));
 
 	let tranche_now = 1;
 	assert!(!should_trigger_assignment(
diff --git a/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md b/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md
index b2b9a243b2a716dcdedf926c29aa721f1541b318..dd6994caea790db61170eee14a71bebb2b22abd5 100644
--- a/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md
+++ b/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md
@@ -360,8 +360,9 @@ Likewise, when considering how many tranches to take, the no-show depth should b
 #### Check Approval
   * Check whether a candidate is approved under a particular block.
   * Requires `(block_entry, candidate_entry, approval_entry, n_tranches)`
+  * If we have `3 * n_approvals > n_validators`, return true. This is because any set with f+1 validators must have at least one honest validator, who has approved the candidate.
   * If `n_tranches` is `RequiredTranches::Pending`, return false
-  * If `n_tranches` is `RequiredTranches::All`,  then we return `3 * n_approvals > 2 * n_validators`.
+  * If `n_tranches` is `RequiredTranches::All`, return false.
   * If `n_tranches` is `RequiredTranches::Exact { tranche, tolerated_missing, .. }`, then we return whether all assigned validators up to `tranche` less `tolerated_missing` have approved. e.g. if we had 5 tranches and 1 tolerated missing, we would accept only if all but 1 of assigned validators in tranches 0..=5 have approved. In that example, we also accept all validators in tranches 0..=5 having approved, but that would indicate that the `RequiredTranches` value was incorrectly constructed, so it is not realistic. `tolerated_missing` actually represents covered no-shows. If there are more missing approvals than there are tolerated missing, that indicates that there are some assignments which are not yet no-shows, but may become no-shows, and we should wait for the validators to either approve or become no-shows.
 
 ### Time