Some checks failed
lakehouse/auditor 3 blocking issues: todo!() macro call in tests/real-world/scrum_master_pipeline.ts
Phase 43 PRD (docs/CONTROL_PLANE_PRD.md:161) was the one audit finding
truly unimplemented — no crate, no trait, no tests, no workspace entry.
Neither PHASES.md nor the source tree had any Phase 43 presence.
Genuine greenfield gap.
Lands the scaffold as a real crate, registered in workspace Cargo.toml:
crates/validator/
src/lib.rs — Validator trait, Artifact enum (5 variants:
FillProposal, EmailDraft, Playbook,
TerraformPlan, AnsiblePlaybook), Report,
Finding, Severity, ValidationError
src/staffing/mod.rs — staffing validators module root
src/staffing/fill.rs — FillValidator (schema-level: fills array
+ per-fill {candidate_id, name}). 4 tests.
Worker-existence + status + geo checks
are TODO v2 (need catalog query handle).
src/staffing/email.rs — EmailValidator (to/body schema + SMS ≤160
+ email subject ≤78). 4 tests. PII scan +
name-consistency TODO v2.
src/staffing/playbook.rs — PlaybookValidator (operation prefix,
endorsed_names non-empty + ≤ target×2,
fingerprint present per Phase 25). 5 tests.
src/devops.rs — TerraformValidator + AnsibleValidator
scaffolds. Return Unimplemented — keeps
dispatcher shape stable, surfaces a clear
"phase 43 not wired" signal instead of
silently passing or panicking.
Total: 15 tests, all green. Covers the happy paths, the common
failure modes (missing fields, overfull arrays, length violations),
and the dispatch-error path (wrong artifact type into wrong validator).
Still open from Phase 43 (v2 work, beyond scaffold):
- FillValidator catalog integration (worker-existence, status,
geo/role match) — needs catalog handle in constructor
- EmailValidator PII scan (shared::pii::strip_pii integration) +
name-consistency cross-check
- Execution loop wiring: generate → validate → observer correction
+ retry (bounded by max_iterations=3) — spans crates, follow-up
- Observer logging: validation results to data/_observer/ops.jsonl
and data/_kb/outcomes.jsonl
- Scenario fixture tests against tests/multi-agent/playbooks/*
Workspace warnings still at 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
135 lines
4.6 KiB
Rust
135 lines
4.6 KiB
Rust
//! Sealed playbook validator.
|
||
//!
|
||
//! PRD checks:
|
||
//! - Operation format (`fill: Role xN in City, ST`)
|
||
//! - endorsed_names non-empty, ≤ target_count × 2
|
||
//! - fingerprint populated (Phase 25 validity window requirement)
|
||
|
||
use crate::{Artifact, Report, Validator, ValidationError};
|
||
use std::time::Instant;
|
||
|
||
pub struct PlaybookValidator;
|
||
|
||
impl Validator for PlaybookValidator {
|
||
fn name(&self) -> &'static str { "staffing.playbook" }
|
||
|
||
fn validate(&self, artifact: &Artifact) -> Result<Report, ValidationError> {
|
||
let started = Instant::now();
|
||
let value = match artifact {
|
||
Artifact::Playbook(v) => v,
|
||
other => return Err(ValidationError::Schema {
|
||
field: "artifact".into(),
|
||
reason: format!("PlaybookValidator expects Playbook, got {other:?}"),
|
||
}),
|
||
};
|
||
|
||
// Operation format: "fill: Role xN in City, ST" — at minimum
|
||
// we check the string-shape. Fuller grammar parse lives in
|
||
// phase 25 code where operations are structured beyond strings.
|
||
let op = value.get("operation").and_then(|v| v.as_str()).ok_or(
|
||
ValidationError::Schema {
|
||
field: "operation".into(),
|
||
reason: "missing or not a string".into(),
|
||
},
|
||
)?;
|
||
if !op.starts_with("fill:") {
|
||
return Err(ValidationError::Schema {
|
||
field: "operation".into(),
|
||
reason: format!("expected `fill: ...` prefix, got {op:?}"),
|
||
});
|
||
}
|
||
|
||
let endorsed = value.get("endorsed_names").and_then(|v| v.as_array()).ok_or(
|
||
ValidationError::Schema {
|
||
field: "endorsed_names".into(),
|
||
reason: "missing or not an array".into(),
|
||
},
|
||
)?;
|
||
if endorsed.is_empty() {
|
||
return Err(ValidationError::Completeness {
|
||
reason: "endorsed_names must be non-empty".into(),
|
||
});
|
||
}
|
||
|
||
if let Some(target) = value.get("target_count").and_then(|v| v.as_u64()) {
|
||
let max = (target * 2) as usize;
|
||
if endorsed.len() > max {
|
||
return Err(ValidationError::Completeness {
|
||
reason: format!(
|
||
"endorsed_names ({}) exceeds target_count × 2 ({max})",
|
||
endorsed.len()
|
||
),
|
||
});
|
||
}
|
||
}
|
||
|
||
if value.get("fingerprint").and_then(|v| v.as_str()).map_or(true, |s| s.is_empty()) {
|
||
return Err(ValidationError::Schema {
|
||
field: "fingerprint".into(),
|
||
reason: "missing — required for Phase 25 validity window".into(),
|
||
});
|
||
}
|
||
|
||
Ok(Report {
|
||
findings: vec![],
|
||
elapsed_ms: started.elapsed().as_millis() as u64,
|
||
})
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn well_formed_playbook_passes() {
|
||
let r = PlaybookValidator.validate(&Artifact::Playbook(serde_json::json!({
|
||
"operation": "fill: Welder x2 in Toledo, OH",
|
||
"endorsed_names": ["W-123", "W-456"],
|
||
"target_count": 2,
|
||
"fingerprint": "abc123"
|
||
})));
|
||
assert!(r.is_ok(), "got {:?}", r);
|
||
}
|
||
|
||
#[test]
|
||
fn empty_endorsed_names_fails_completeness() {
|
||
let r = PlaybookValidator.validate(&Artifact::Playbook(serde_json::json!({
|
||
"operation": "fill: Welder x2 in Toledo, OH",
|
||
"endorsed_names": [],
|
||
"fingerprint": "abc"
|
||
})));
|
||
assert!(matches!(r, Err(ValidationError::Completeness { .. })));
|
||
}
|
||
|
||
#[test]
|
||
fn overfull_endorsed_names_fails_completeness() {
|
||
let r = PlaybookValidator.validate(&Artifact::Playbook(serde_json::json!({
|
||
"operation": "fill: Welder x1 in Toledo, OH",
|
||
"endorsed_names": ["a", "b", "c"],
|
||
"target_count": 1,
|
||
"fingerprint": "abc"
|
||
})));
|
||
assert!(matches!(r, Err(ValidationError::Completeness { .. })));
|
||
}
|
||
|
||
#[test]
|
||
fn missing_fingerprint_fails_schema() {
|
||
let r = PlaybookValidator.validate(&Artifact::Playbook(serde_json::json!({
|
||
"operation": "fill: X x1 in A, B",
|
||
"endorsed_names": ["a"]
|
||
})));
|
||
assert!(matches!(r, Err(ValidationError::Schema { field, .. }) if field == "fingerprint"));
|
||
}
|
||
|
||
#[test]
|
||
fn wrong_operation_prefix_fails_schema() {
|
||
let r = PlaybookValidator.validate(&Artifact::Playbook(serde_json::json!({
|
||
"operation": "sms_draft: hello",
|
||
"endorsed_names": ["a"],
|
||
"fingerprint": "x"
|
||
})));
|
||
assert!(matches!(r, Err(ValidationError::Schema { .. })));
|
||
}
|
||
}
|