needs: also_moved_by - a need carried by more than one group's desk
Test / test (push) Successful in 27s
Test / test (push) Successful in 27s
Roles overlap on one aggregate: a project manager prices a change, a site manager completes it. A need may list the other groups whose desks hold some of its moves; their buttons count toward reaching `done_when` and toward the strand check exactly as `handled_by`'s do. `handled_by` must still hold a desk of its own, so that someone is answerable for the record. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4jrCgLiKKHAEFuZUJjckH
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
6efd7a0c6b
commit
d7f60eaa31
+34
-2
@@ -86,6 +86,13 @@ pub struct Need {
|
||||
/// becoming open -> quoted) fails instead of passing.
|
||||
#[serde(default)]
|
||||
pub stages: Vec<String>,
|
||||
/// Other groups whose desks carry some of this need's moves: a
|
||||
/// site manager closing what a project manager opened. Their
|
||||
/// buttons count toward reaching `done_when` exactly as
|
||||
/// `handled_by`'s do. `handled_by` must still hold a desk of its
|
||||
/// own - someone has to be answerable for the record.
|
||||
#[serde(default)]
|
||||
pub also_moved_by: Vec<String>,
|
||||
}
|
||||
|
||||
fn root() -> String {
|
||||
@@ -392,9 +399,11 @@ pub fn check(
|
||||
let desks = desks_over(questions, &need.lands_in);
|
||||
let own: Vec<&Desk> = desks
|
||||
.iter()
|
||||
.filter(|d| d.group.as_deref() == Some(need.handled_by.as_str()))
|
||||
.filter(|d| {
|
||||
d.group.as_deref().is_some_and(|g| g == need.handled_by || need.also_moved_by.iter().any(|m| m == g))
|
||||
})
|
||||
.collect();
|
||||
if own.is_empty() {
|
||||
if !desks.iter().any(|d| d.group.as_deref() == Some(need.handled_by.as_str())) {
|
||||
let others: Vec<String> = desks
|
||||
.iter()
|
||||
.map(|d| format!("{} ({})", d.page, d.group.as_deref().unwrap_or("ungated")))
|
||||
@@ -883,6 +892,29 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_need_can_be_carried_by_more_than_one_groups_desk() {
|
||||
// owners may only quote or lose; closing a quoted job is the
|
||||
// site managers' button, on a desk of their own.
|
||||
let (mut questions, aggregates) = site(" - { to: quoted, label: Quote }\n - { to: lost, label: Lose }\n");
|
||||
let more = build_questions(&[(
|
||||
"site/index.yaml".to_string(),
|
||||
"name: Site desk\nqualifies: site_managers\nalternatives:\n - name: Projects\n features:\n - name: \"\"\n resource:\n source: { kind: kv, bucket: projects }\n requires_group: site_managers\n transitions:\n - { from: quoted, to: won, label: Win }\n - { from: quoted, to: lost, label: Lose }\n".to_string(),
|
||||
)])
|
||||
.unwrap();
|
||||
questions.extend(more);
|
||||
|
||||
let mut file = needs("");
|
||||
// Alone, owners cannot finish it...
|
||||
assert_eq!(fails(&check(&file, &questions, &aggregates)).len(), 2);
|
||||
// ...with the site managers' buttons counted, they can.
|
||||
file.needs[0].also_moved_by = vec!["site_managers".to_string()];
|
||||
assert!(check(&file, &questions, &aggregates).is_empty());
|
||||
// But someone has to be answerable: handled_by needs its own desk.
|
||||
file.needs[0].handled_by = "nobody".to_string();
|
||||
assert!(fails(&check(&file, &questions, &aggregates))[0].starts_with("no desk gated on \"nobody\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_wrong_group_holding_the_desk_is_a_failure() {
|
||||
let (questions, aggregates) = site(FULL_DESK);
|
||||
|
||||
Reference in New Issue
Block a user