@@ -170,6 +170,13 @@ pub fn validate_charters(project_root: &Path, straymark_dir: &Path) -> (Validati
170170 }
171171 }
172172
173+ // Step 2b: work_verb / design_provenance advisory (Baton #332 graduation).
174+ // Declared classification fields. ADVISORY ONLY: a *present* value outside
175+ // the controlled vocabulary is a warning (never an error / exit-1, per the
176+ // schema-ratification posture §5); an *absent* field emits nothing — the
177+ // 100%-undeclared legacy corpus must stay quiet.
178+ check_charter_work_verb ( & raw_yaml, path, & mut result) ;
179+
173180 // Step 3: typed parse for referential-integrity checks. If schema
174181 // validation already caught problems, the typed parse may also fail —
175182 // in that case we skip ref checks (cannot trust the structure) but
@@ -274,6 +281,54 @@ pub fn validate_charters(project_root: &Path, straymark_dir: &Path) -> (Validati
274281 ( result, charter_count)
275282}
276283
284+ /// Advisory check for the declared classification fields `work_verb` /
285+ /// `design_provenance` (Baton #332, schema ratification `06-work-verb-schema-
286+ /// ratification.md`). Reads the raw frontmatter (the fields are intentionally
287+ /// absent from the typed `CharterFrontmatter` — they are optional and additive).
288+ ///
289+ /// Anti-noise by design: a value *present but outside* the controlled vocabulary
290+ /// is a Warning; an *absent* field emits nothing. The vocabulary is enforced here
291+ /// (CLI semantics) rather than via a schema `enum` (which would make it a blocking
292+ /// Error), keeping the posture advisory per the ratification §5.
293+ fn check_charter_work_verb ( raw_yaml : & serde_yaml:: Value , path : & Path , result : & mut ValidationResult ) {
294+ const WORK_VERBS : & [ & str ] = & [ "design" , "implement" , "audit" , "operate" ] ;
295+ const PROVENANCES : & [ & str ] = & [ "new" , "upstream" ] ;
296+
297+ let Some ( map) = raw_yaml. as_mapping ( ) else {
298+ return ;
299+ } ;
300+
301+ for ( key, valid, rule) in [
302+ ( "work_verb" , WORK_VERBS , "CHARTER-WORK-VERB" ) ,
303+ ( "design_provenance" , PROVENANCES , "CHARTER-DESIGN-PROVENANCE" ) ,
304+ ] {
305+ // Absent field → nothing (anti-noise). Only a *present* value is checked.
306+ let Some ( value) = map. get ( serde_yaml:: Value :: String ( key. to_string ( ) ) ) else {
307+ continue ;
308+ } ;
309+ let Some ( declared) = value. as_str ( ) else {
310+ continue ;
311+ } ;
312+ if valid. contains ( & declared. trim ( ) ) {
313+ continue ;
314+ }
315+ result. add ( ValidationIssue {
316+ file : path. to_path_buf ( ) ,
317+ rule : rule. to_string ( ) ,
318+ message : format ! (
319+ "`{key}: {declared}` is outside the controlled vocabulary (expected one of: {})." ,
320+ valid. join( ", " )
321+ ) ,
322+ severity : Severity :: Warning ,
323+ fix_hint : Some ( format ! (
324+ "Declare `{key}` as one of: {}. Leave it unset if undeclared — \
325+ the field is optional and only a wrong value is flagged.",
326+ valid. join( ", " )
327+ ) ) ,
328+ } ) ;
329+ }
330+ }
331+
277332/// True if an AILOG file matching the given ID exists under
278333/// `.straymark/07-ai-audit/agent-logs/`. The match is by filename prefix:
279334/// `AILOG-2026-04-28-021` matches `AILOG-2026-04-28-021-anything.md` but not
0 commit comments