Skip to content

Commit 6e5a843

Browse files
committed
feat: align product config vocabulary (actsAs, label, holiday sets)
Aligns configuration vocabulary that differed between products; the product is pre-1.0. Every renamed key has a `config.removed-key` entry naming its replacement, a CFG-NAME-5 row, a `BREAKING:` line in the product CHANGELOG, a note in `release/notes/config-conventions/`, and updated examples, fixtures, docs and regenerated schemas. No migration code. BREAKING: - Casework, Documents and Messaging access profiles: `role` is renamed `actsAs`; values unchanged. The schema definitions `CaseworkRole` and `AccessRole` become `CaseworkActsAs` and `ActsAs`, and the Casework diagnostic `casework.access-profile.missing-role` becomes `casework.access-profile.missing-acts-as`. BReg profiles and the data sense of `role` (event records, Evidence subjects) are unchanged. - Coordinator: `title` is renamed `label` on a project, function, operation and step; the explain member `presentation.title` becomes `presentation.label`; the duplicate-title diagnostics become `...duplicate-function-label`, `...duplicate-operation-label` and `...duplicate-step-label`. - Casework holiday-set file: `holidaySet` is renamed `id`, and an optional `label` is allowed. A calendar's `holidaySet` member, which names the set it uses, is unchanged. - Scheduling: a holiday set's `because` is renamed `label`, and `label` is now optional (the id names a set that writes none), as in the Casework holiday-set file. The schema definition `HolidaySetPolicy` becomes `HolidaySet`. `because` stays on every other Scheduling item. - `caseworkctl dev` report: a client names `actsAs` where it named `role`. - Upgrade: this release does not read state stored by 0.45.0 or earlier. Start Casework and Scheduling on a new database; finish Coordinator runs before upgrading. A stored document that holds a removed member is refused with a message that says it was stored by an earlier release and names the member. Additive: - Coordinator event triggers accept an optional `label` and `description`, stored under `presentation.eventTriggers.<id>`, reported by `check --explain`, and outside the semantic revision (a wording edit keeps `workflowRevision`). Closes #2549. - Casework, Documents, Messaging and Evidence access profiles accept an optional `label` and `description` (BReg profiles already have `display`). They are accepted for authoring and tooling. Casework keeps them in the review policy description, which any caller who may read that description, requesters included, can see, and they do not change a policy's behavioral binding. Evidence drops them at compile; Documents and Messaging only parse them, and none serve them until a view exists. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
1 parent b6da20c commit 6e5a843

144 files changed

Lines changed: 1910 additions & 804 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎crates/registry-adapter-job/tests/support/casework_native_service.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,10 @@ impl Casework {
172172
"kind": "CaseworkProject",
173173
"project": {"id": "adapter-ingestion-review", "version": "1"},
174174
"accessProfiles": {
175-
"staff": {"principalClaim": "sub", "requiredScopes": ["casework:staff"], "role": "staff"},
176-
"supervisor": {"principalClaim": "sub", "requiredScopes": ["casework:supervisor"], "role": "supervisor"},
177-
"administrator": {"principalClaim": "sub", "requiredScopes": ["casework:admin"], "role": "administrator"},
178-
"coordinator-producer": {"principalClaim": "sub", "requiredScopes": ["casework:review"], "role": "requester"}
175+
"staff": {"principalClaim": "sub", "requiredScopes": ["casework:staff"], "actsAs":"staff"},
176+
"supervisor": {"principalClaim": "sub", "requiredScopes": ["casework:supervisor"], "actsAs":"supervisor"},
177+
"administrator": {"principalClaim": "sub", "requiredScopes": ["casework:admin"], "actsAs":"administrator"},
178+
"coordinator-producer": {"principalClaim": "sub", "requiredScopes": ["casework:review"], "actsAs":"requester"}
179179
},
180180
"queues": {"applications": {"label": "Household ingestion reviews"}},
181181
"teams": {"reviewers": {"label": "Household reviewers", "serves": ["applications"], "memberProfiles": ["staff"], "supervisorProfiles": ["supervisor"]}},

‎crates/registry-casework-client/tests/http_boundary.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3549,7 +3549,7 @@ fn fixture_closure(policy: ReviewPolicy) -> registry_casework_client::ReviewPoli
35493549
.remove("id");
35503550
let project: registry_casework_core::CaseworkProject = serde_json::from_value(json!({
35513551
"apiVersion":"id.registrystack.org/formats/casework/project/v1alpha1", "kind":"CaseworkProject", "project":{"id":"client-fixture","version":"1"},
3552-
"accessProfiles":{"supervisor":{"role":"supervisor","principalClaim":"sub","requiredScopes":["casework:supervisor"]},"administrator":{"role":"administrator","principalClaim":"sub","requiredScopes":["casework:admin"]},"staff":{"role":"staff","principalClaim":"sub","requiredScopes":["casework:staff"]},"requester":{"role":"requester","principalClaim":"sub","requiredScopes":["casework:request"]}},
3552+
"accessProfiles":{"supervisor":{"actsAs":"supervisor","principalClaim":"sub","requiredScopes":["casework:supervisor"]},"administrator":{"actsAs":"administrator","principalClaim":"sub","requiredScopes":["casework:admin"]},"staff":{"actsAs":"staff","principalClaim":"sub","requiredScopes":["casework:staff"]},"requester":{"actsAs":"requester","principalClaim":"sub","requiredScopes":["casework:request"]}},
35533553
"queues":queues, "reviewPolicies":{policy.id.clone(): policy_body}, "contextProviders":providers,
35543554
"trustedProducers":{"requester":{"profile":"requester","issuer":"https://issuer.example","subject":"producer","sourceNamespaces":["registry"],"reviewPolicies":[policy.id],"retention":{"receiptRetentionDays":30}}}
35553555
})).expect("fixture project");

‎crates/registry-casework-core/src/config.rs‎

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
1212
use thiserror::Error;
1313

1414
use crate::finding::{ConfigFinding, Findings, IDENTIFIER_ACTION, IDENTIFIER_MESSAGE};
15-
use crate::CaseworkRole;
15+
use crate::CaseworkActsAs;
1616
use crate::ReviewPolicy;
1717
use crate::MAXIMUM_RECEIPT_RETENTION_DAYS;
1818
use crate::{clock_policy_findings, routing_policy_findings, CalendarPolicy, ClockPolicy};
@@ -316,7 +316,11 @@ pub const CASEWORK_PROJECT_FORMAT: FormatSpec<'static> = FormatSpec {
316316
},
317317
RemovedKey {
318318
pointer: "/accessProfiles/*/id",
319-
replacement: "Write accessProfiles as a mapping from each id to the rest of its entry, accessProfiles: {PROFILE_ID: {role: ...}}, with no id member. See row F4 of the format table in the Casework CHANGELOG.",
319+
replacement: "Write accessProfiles as a mapping from each id to the rest of its entry, accessProfiles: {PROFILE_ID: {actsAs: ...}}, with no id member. See row F4 of the format table in the Casework CHANGELOG.",
320+
},
321+
RemovedKey {
322+
pointer: "/accessProfiles/*/role",
323+
replacement: "Write the same value as actsAs.",
320324
},
321325
RemovedKey {
322326
pointer: "/queues/*/id",
@@ -1033,19 +1037,23 @@ impl CaseworkProject {
10331037
);
10341038
}
10351039
for (name, ids, role) in [
1036-
("memberProfiles", &team.member_profiles, CaseworkRole::Staff),
1040+
(
1041+
"memberProfiles",
1042+
&team.member_profiles,
1043+
CaseworkActsAs::Staff,
1044+
),
10371045
(
10381046
"supervisorProfiles",
10391047
&team.supervisor_profiles,
1040-
CaseworkRole::Supervisor,
1048+
CaseworkActsAs::Supervisor,
10411049
),
10421050
] {
10431051
if ids.is_empty()
10441052
|| ids.iter().any(|id| {
10451053
!self
10461054
.access_profiles
10471055
.iter()
1048-
.any(|profile| profile.id == *id && profile.role == role)
1056+
.any(|profile| profile.id == *id && profile.acts_as == role)
10491057
})
10501058
{
10511059
findings.push(
@@ -1172,20 +1180,20 @@ impl CaseworkProject {
11721180
}
11731181
}
11741182
for (role, name) in [
1175-
(CaseworkRole::Staff, "staff"),
1176-
(CaseworkRole::Supervisor, "supervisor"),
1177-
(CaseworkRole::Administrator, "administrator"),
1183+
(CaseworkActsAs::Staff, "staff"),
1184+
(CaseworkActsAs::Supervisor, "supervisor"),
1185+
(CaseworkActsAs::Administrator, "administrator"),
11781186
] {
11791187
if !self
11801188
.access_profiles
11811189
.iter()
1182-
.any(|profile| profile.role == role)
1190+
.any(|profile| profile.acts_as == role)
11831191
{
11841192
findings.push(
1185-
"casework.access-profile.missing-role",
1193+
"casework.access-profile.missing-acts-as",
11861194
"/accessProfiles",
1187-
format!("no access profile has role {name}"),
1188-
format!("Declare an access profile with role: {name}."),
1195+
format!("no access profile acts as {name}"),
1196+
format!("Declare an access profile with actsAs: {name}."),
11891197
);
11901198
}
11911199
}
@@ -1251,14 +1259,17 @@ impl CaseworkProject {
12511259
.iter()
12521260
.find(|profile| profile.id == *profile_id)
12531261
.is_none_or(|profile| {
1254-
!matches!(profile.role, CaseworkRole::Staff | CaseworkRole::Supervisor)
1262+
!matches!(
1263+
profile.acts_as,
1264+
CaseworkActsAs::Staff | CaseworkActsAs::Supervisor
1265+
)
12551266
})
12561267
{
12571268
findings.push(
12581269
"casework.review-policy.ineligible-deciding-profile",
12591270
format!("{stage_at}/decidingProfiles/{profile_index}"),
12601271
"no staff or supervisor access profile has this id",
1261-
"Name an access profile with role staff or supervisor.",
1272+
"Name an access profile with actsAs staff or supervisor.",
12621273
);
12631274
}
12641275
}
@@ -1324,18 +1335,18 @@ impl CaseworkProject {
13241335
.access_profiles
13251336
.iter()
13261337
.find(|profile| profile.id == producer.profile)
1327-
.is_none_or(|profile| profile.role != CaseworkRole::Requester)
1338+
.is_none_or(|profile| profile.acts_as != CaseworkActsAs::Requester)
13281339
{
13291340
findings.push(
13301341
"casework.review-producer.not-a-requester-profile",
13311342
format!("{at}/profile"),
13321343
"no requester access profile has this id",
1333-
"Name an access profile with role requester.",
1344+
"Name an access profile with actsAs requester.",
13341345
);
13351346
}
13361347
if let Some(initiator_profile) = &producer.initiator_profile {
13371348
let requester_profile = self.access_profiles.iter().any(|profile| {
1338-
profile.id == *initiator_profile && profile.role == CaseworkRole::Requester
1349+
profile.id == *initiator_profile && profile.acts_as == CaseworkActsAs::Requester
13391350
});
13401351
let producer_profile = self
13411352
.trusted_producers
@@ -1773,24 +1784,24 @@ fn project_integrity_diagnostics(decoded: &mut Decoded<CaseworkProject>) -> Vec<
17731784
/// lower role. A profile that requires no scope is reported as such and not
17741785
/// here.
17751786
fn profiles_without_a_separate_scope(profiles: &[AccessProfile]) -> Vec<usize> {
1776-
fn role_rank(role: CaseworkRole) -> u8 {
1787+
fn role_rank(role: CaseworkActsAs) -> u8 {
17771788
match role {
1778-
CaseworkRole::Requester => 0,
1779-
CaseworkRole::Staff => 1,
1780-
CaseworkRole::Supervisor => 2,
1781-
CaseworkRole::Administrator => 3,
1789+
CaseworkActsAs::Requester => 0,
1790+
CaseworkActsAs::Staff => 1,
1791+
CaseworkActsAs::Supervisor => 2,
1792+
CaseworkActsAs::Administrator => 3,
17821793
}
17831794
}
17841795
profiles
17851796
.iter()
17861797
.enumerate()
17871798
.filter(|(target_index, target)| {
1788-
let target_rank = role_rank(target.role);
1799+
let target_rank = role_rank(target.acts_as);
17891800
let other_same_or_lower_scopes: BTreeSet<&String> = profiles
17901801
.iter()
17911802
.enumerate()
17921803
.filter(|(index, profile)| {
1793-
index != target_index && role_rank(profile.role) <= target_rank
1804+
index != target_index && role_rank(profile.acts_as) <= target_rank
17941805
})
17951806
.flat_map(|(_, profile)| profile.required_scopes.iter())
17961807
.collect();
@@ -1811,14 +1822,36 @@ pub struct AccessProfile {
18111822
#[serde(deserialize_with = "crate::typed::local_id")]
18121823
#[cfg_attr(feature = "schema", schemars(with = "registry_platform_yaml::LocalId"))]
18131824
pub id: String,
1825+
/// A name for the profile, accepted for authoring and tooling; never matched against a caller. Part of the review policy description, which any caller who may read that description can see.
1826+
#[serde(
1827+
default,
1828+
deserialize_with = "registry_platform_yaml::optional_display_text",
1829+
skip_serializing_if = "Option::is_none"
1830+
)]
1831+
#[cfg_attr(
1832+
feature = "schema",
1833+
schemars(with = "Option<registry_platform_yaml::DisplayText>")
1834+
)]
1835+
pub label: Option<String>,
1836+
/// What the profile is for, accepted for authoring and tooling; never matched against a caller. Part of the review policy description, which any caller who may read that description can see.
1837+
#[serde(
1838+
default,
1839+
deserialize_with = "registry_platform_yaml::optional_display_text",
1840+
skip_serializing_if = "Option::is_none"
1841+
)]
1842+
#[cfg_attr(
1843+
feature = "schema",
1844+
schemars(with = "Option<registry_platform_yaml::DisplayText>")
1845+
)]
1846+
pub description: Option<String>,
18141847
pub principal_claim: String,
18151848
#[serde(deserialize_with = "crate::typed::unique_list")]
18161849
#[cfg_attr(
18171850
feature = "schema",
18181851
schemars(with = "registry_platform_yaml::UniqueList<String>", length(min = 1))
18191852
)]
18201853
pub required_scopes: Vec<String>,
1821-
pub role: CaseworkRole,
1854+
pub acts_as: CaseworkActsAs,
18221855
}
18231856

18241857
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]

‎crates/registry-casework-core/src/fixture.rs‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::HolidaySetDocument;
33
use chrono::{DateTime, NaiveDate, Utc};
44
use registry_platform_yaml::{
55
ApiVersion, BoundedU64, Decoded, EnvelopeRule, Expect, ForeignValue, FormatSpec, Invalid,
6-
LocalId, Reader, Report, RetiredApiVersion, UniqueList,
6+
LocalId, Reader, RemovedKey, Report, RetiredApiVersion, UniqueList,
77
};
88
use serde::{Deserialize, Deserializer};
99
use std::fmt;
@@ -19,7 +19,10 @@ pub const CASEWORK_HOLIDAY_SET_FORMAT: FormatSpec<'static> = FormatSpec {
1919
api_versions: &[ApiVersion::current(CASEWORK_HOLIDAY_SET_API_VERSION)],
2020
retired_api_versions: &[],
2121
},
22-
removed_keys: &[],
22+
removed_keys: &[RemovedKey {
23+
pointer: "/holidaySet",
24+
replacement: "Write the set's identifier as id.",
25+
}],
2326
};
2427
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
2528
pub struct Timestamp(DateTime<Utc>);
@@ -91,8 +94,18 @@ pub struct CaseworkHolidaySet {
9194
pub api_version: String,
9295
pub kind: String,
9396
/// The holiday set a calendar names in `holidaySet`.
94-
pub holiday_set: LocalId,
97+
pub id: LocalId,
9598
pub revision: BoundedU64<1, MAXIMUM_HOLIDAY_SET_REVISION>,
99+
/// An optional human label for the set.
100+
#[serde(
101+
default,
102+
deserialize_with = "registry_platform_yaml::optional_display_text"
103+
)]
104+
#[cfg_attr(
105+
feature = "schema",
106+
schemars(with = "Option<registry_platform_yaml::DisplayText>")
107+
)]
108+
pub label: Option<String>,
96109
/// The dates that are not working days, at most 3660.
97110
#[cfg_attr(feature = "schema", schemars(length(max = 3660)))]
98111
pub dates: UniqueList<CalendarDate>,
@@ -128,7 +141,7 @@ impl CaseworkHolidaySet {
128141
/// The revision as the runtime's holiday-set document.
129142
pub fn to_document(&self) -> HolidaySetDocument {
130143
HolidaySetDocument {
131-
holiday_set: self.holiday_set.to_string(),
144+
holiday_set: self.id.to_string(),
132145
revision: self.revision.get(),
133146
dates: self.dates.iter().map(ToString::to_string).collect(),
134147
}

‎crates/registry-casework-core/src/model.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ mod directory_member_tests {
7373
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
7474
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
7575
#[serde(rename_all = "kebab-case")]
76-
pub enum CaseworkRole {
76+
pub enum CaseworkActsAs {
7777
Staff,
7878
Supervisor,
7979
Administrator,
@@ -86,7 +86,7 @@ pub enum CaseworkRole {
8686
pub struct ActorContext {
8787
pub principal: IssuerPrincipal,
8888
pub profile_id: String,
89-
pub role: CaseworkRole,
89+
pub role: CaseworkActsAs,
9090
}
9191

9292
/// A bearer credential may exist only for the duration of one request.

‎crates/registry-casework-core/src/review.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,10 @@ impl ReviewPolicy {
774774
}
775775
}
776776
}
777+
for profile in &mut dependencies.access_profiles {
778+
profile.label = None;
779+
profile.description = None;
780+
}
777781
if let Some(provider) = &mut dependencies.context_provider {
778782
provider.description = None;
779783
}

‎crates/registry-casework-core/src/task_grant.rs‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Governed task delegation policy and immutable source-bound authorization.
22
use crate::finding::{ConfigFinding, Findings};
3-
use crate::{CaseworkProject, CaseworkRole, IssuerPrincipal};
3+
use crate::{CaseworkActsAs, CaseworkProject, IssuerPrincipal};
44
use serde::{Deserialize, Deserializer, Serialize};
55
use serde_json::Value;
66
use std::{collections::BTreeMap, fmt};
@@ -476,7 +476,10 @@ impl TaskTemplate {
476476
for (index, id) in self.eligible_profiles.iter().enumerate() {
477477
let eligible = project.access_profiles.iter().any(|profile| {
478478
profile.id == *id
479-
&& matches!(profile.role, CaseworkRole::Staff | CaseworkRole::Supervisor)
479+
&& matches!(
480+
profile.acts_as,
481+
CaseworkActsAs::Staff | CaseworkActsAs::Supervisor
482+
)
480483
});
481484
if !eligible {
482485
findings.push(

‎crates/registry-casework-core/tests/format_alignment.rs‎

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ fn two_of_each(order: [usize; 2]) -> String {
169169
};
170170
format!(
171171
"apiVersion: {CASEWORK_API_VERSION}\nkind: {CASEWORK_KIND}\nproject: {{id: review-project, version: \"1\"}}\n\
172-
accessProfiles:\n reviewer: {{role: staff, principalClaim: principal, requiredScopes: [\"casework:review\"]}}\n supervisor: {{role: supervisor, principalClaim: principal, requiredScopes: [\"casework:supervise\"]}}\n administrator: {{role: administrator, principalClaim: principal, requiredScopes: [\"casework:admin\"]}}\n producer: {{role: requester, principalClaim: principal, requiredScopes: [\"casework:request\"]}}\n\
172+
accessProfiles:\n reviewer: {{actsAs: staff, principalClaim: principal, requiredScopes: [\"casework:review\"]}}\n supervisor: {{actsAs: supervisor, principalClaim: principal, requiredScopes: [\"casework:supervise\"]}}\n administrator: {{actsAs: administrator, principalClaim: principal, requiredScopes: [\"casework:admin\"]}}\n producer: {{actsAs: requester, principalClaim: principal, requiredScopes: [\"casework:request\"]}}\n\
173173
queues: {{reviews: {{label: Reviews}}}}\n\
174174
teams: {{review-team: {{label: Review team, serves: [reviews], memberProfiles: [reviewer], supervisorProfiles: [supervisor]}}}}\n\
175175
contextProviders:\n{}\
@@ -270,6 +270,73 @@ fn an_elapsed_budget_names_exactly_one_unit() {
270270
}
271271
}
272272

273+
#[test]
274+
fn an_access_profile_role_is_refused_and_names_acts_as() {
275+
let diagnostics =
276+
read(&replaced("actsAs: staff", "role: staff")).expect_err("the old spelling is refused");
277+
assert!(
278+
diagnostics
279+
.iter()
280+
.any(|diagnostic| diagnostic.code == "config.removed-key"
281+
&& diagnostic.path == "/accessProfiles/staff/role"
282+
&& diagnostic.suggested_action.contains("actsAs")),
283+
"no removed-key diagnostic names actsAs: {diagnostics:#?}"
284+
);
285+
}
286+
287+
#[test]
288+
fn a_holiday_set_file_reads_id_and_refuses_holiday_set() {
289+
let file = |first: &str| {
290+
format!(
291+
"apiVersion: {CASEWORK_HOLIDAY_SET_API_VERSION}\nkind: {CASEWORK_HOLIDAY_SET_KIND}\n{first}: office-holidays\nrevision: 7\nlabel: Office holidays\ndates: [2026-09-07]\n"
292+
)
293+
};
294+
let read = |text: String| CaseworkHolidaySet::read("holiday-set.yaml", text.as_bytes());
295+
let decoded = read(file("id")).expect("id reads").value;
296+
assert_eq!(decoded.id.to_string(), "office-holidays");
297+
assert_eq!(decoded.label.as_deref(), Some("Office holidays"));
298+
let diagnostics = read(file("holidaySet")).expect_err("holidaySet is refused");
299+
assert!(
300+
diagnostics
301+
.diagnostics()
302+
.iter()
303+
.any(|d| d.code == "config.removed-key"
304+
&& d.path == "/holidaySet"
305+
&& d.suggested_action.contains("id")),
306+
"{diagnostics:#?}"
307+
);
308+
}
309+
310+
#[test]
311+
fn an_access_profile_reads_acts_as() {
312+
let project = read(EXAMPLE).expect("the example reads");
313+
assert!(project
314+
.access_profiles
315+
.iter()
316+
.any(|profile| profile.acts_as == CaseworkActsAs::Staff));
317+
}
318+
319+
#[test]
320+
fn an_access_profile_reads_a_label_and_a_description() {
321+
let project = read(&replaced(
322+
"requiredScopes: [casework:staff], actsAs: staff}",
323+
"requiredScopes: [casework:staff], actsAs: staff, label: Reviewer, description: Decides review tasks}",
324+
))
325+
.expect("the labelled profile reads");
326+
let profile = project
327+
.access_profiles
328+
.iter()
329+
.find(|profile| profile.acts_as == CaseworkActsAs::Staff)
330+
.expect("the staff profile");
331+
assert_eq!(profile.label.as_deref(), Some("Reviewer"));
332+
assert_eq!(profile.description.as_deref(), Some("Decides review tasks"));
333+
let unlabelled = read(EXAMPLE).expect("the example reads");
334+
assert!(unlabelled
335+
.access_profiles
336+
.iter()
337+
.all(|profile| profile.label.is_none() && profile.description.is_none()));
338+
}
339+
273340
#[test]
274341
fn every_old_spelling_is_refused_with_its_replacement_and_table_row() {
275342
assert_refused(

0 commit comments

Comments
 (0)