Skip to content

Commit 38b4601

Browse files
authored
Merge pull request #336 from StrangeDaysTech:feat/baton-work-verb-framework-graduation
feat(framework): graduate work_verb/design_provenance to first-class fields (fw-4.31.0 / cli-3.30.0, #332)
2 parents 08337b8 + d7b6e59 commit 38b4601

31 files changed

Lines changed: 232 additions & 30 deletions

CHANGELOG.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,37 @@ and this project uses [independent versioning](README.md#versioning) for Framewo
77

88
---
99

10+
## Framework 4.31.0 / CLI 3.30.0 — 2026-06-28
11+
12+
Graduate the Baton experiment's declared work-classification fields to the
13+
framework (#332 step 2). After the schema was ratified
14+
(`experiment-baton/06-work-verb-schema-ratification.md`), `work_verb` and
15+
`design_provenance` become optional, additive, first-class Charter / follow-up
16+
fields — declarable at authoring (cost ≈ 0) and surfaced by the validator as an
17+
advisory nudge. No existing document changes shape; the 100%-undeclared legacy
18+
corpus stays quiet.
19+
20+
### Added (Framework)
21+
22+
- **`work_verb` and `design_provenance` Charter frontmatter fields** (#332).
23+
Added to `charter.schema.v0.json` as optional `string` properties (documented
24+
vocabulary, intentionally **not** an `enum` so an out-of-vocabulary value is an
25+
advisory warning, never a blocking schema error) and to the Charter template
26+
(EN/es/zh-CN) as commented guidance. `work_verb`: design | implement | audit |
27+
operate. `design_provenance`: new | upstream (only meaningful for implement —
28+
upstream degrades the routing tier to operator).
29+
- **Follow-up entry schema gains `Work verb` / `Design provenance` lines**
30+
(`FOLLOW-UPS-BACKLOG-PATTERN.md`, EN/es/zh-CN). Optional; documents the same
31+
controlled vocabulary at the follow-up grain.
32+
33+
### Added (CLI)
34+
35+
- **`straymark validate` advisory for declared classification** (#332). New
36+
warning-only rules `CHARTER-WORK-VERB` and `CHARTER-DESIGN-PROVENANCE` fire
37+
only when a Charter declares the field with a value outside the controlled
38+
vocabulary. An absent field emits nothing (anti-noise for legacy corpora); the
39+
check never affects the exit code.
40+
1041
## Framework 4.30.0 / CLI 3.29.0 / Core 0.9.0 — 2026-06-20
1142

1243
Adopter-feedback consolidation: four validator gaps from a 150-doc housekeeping

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,8 @@ StrayMark uses independent version tags for each component:
277277

278278
| Component | Tag prefix | Example | Includes |
279279
| --- | --- | --- | --- |
280-
| Framework | `fw-` | `fw-4.30.0` | Templates (12 types), governance, directives, Charter template + schema |
281-
| CLI | `cli-` | `cli-3.29.0` | The `straymark` binary |
280+
| Framework | `fw-` | `fw-4.31.0` | Templates (12 types), governance, directives, Charter template + schema |
281+
| CLI | `cli-` | `cli-3.30.0` | The `straymark` binary |
282282
| Loom (EXPERIMENTAL) | `loom-` | `loom-0.4.2` | The `straymark-loom` visualization server, downloaded on demand by `straymark loom serve` |
283283

284284
Check installed versions with `straymark status` or `straymark about`.

cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "straymark-cli"
3-
version = "3.29.0"
3+
version = "3.30.0"
44
edition = "2021"
55
description = "CLI for StrayMark — the cognitive discipline your AI-assisted projects need"
66
license = "MIT"

cli/src/validation.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

cli/tests/validate_test.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,3 +855,84 @@ fn test_charter_files_exist_detects_spanish_heading() {
855855
.stdout(predicate::str::contains("CHARTER-FILES-EXIST"))
856856
.stdout(predicate::str::contains("src/no-existe.rs"));
857857
}
858+
859+
// --- CHARTER-WORK-VERB / CHARTER-DESIGN-PROVENANCE (Baton #332 graduation) ---
860+
861+
/// Write a minimal Charter whose frontmatter carries the given extra YAML lines
862+
/// (e.g. `work_verb: implement`). Used to exercise the declared-classification
863+
/// advisory.
864+
fn create_charter_with_frontmatter(dir: &std::path::Path, extra_frontmatter: &str) {
865+
let charters = dir.join(".straymark").join("charters");
866+
std::fs::create_dir_all(&charters).unwrap();
867+
let content = format!(
868+
"---\ncharter_id: CHARTER-01\nstatus: declared\neffort_estimate: M\ntrigger: \"test trigger\"\n{}---\n\n# Charter: Work Verb Test\n\n## Tasks\n\n1. Run.\n",
869+
extra_frontmatter
870+
);
871+
std::fs::write(charters.join("01-work-verb.md"), content).unwrap();
872+
}
873+
874+
#[test]
875+
fn test_charter_work_verb_invalid_value_warns() {
876+
let dir = TempDir::new().unwrap();
877+
setup_straymark(dir.path());
878+
create_charter_with_frontmatter(dir.path(), "work_verb: refactor\n");
879+
880+
let mut cmd = cargo_bin_cmd!("straymark");
881+
cmd.arg("validate")
882+
.arg(dir.path().to_str().unwrap())
883+
.arg("--include-charters")
884+
.assert()
885+
.success() // advisory: must NOT fail the exit code
886+
.stdout(predicate::str::contains("CHARTER-WORK-VERB"));
887+
}
888+
889+
#[test]
890+
fn test_charter_work_verb_valid_value_is_quiet() {
891+
let dir = TempDir::new().unwrap();
892+
setup_straymark(dir.path());
893+
create_charter_with_frontmatter(dir.path(), "work_verb: implement\ndesign_provenance: upstream\n");
894+
895+
let mut cmd = cargo_bin_cmd!("straymark");
896+
cmd.arg("validate")
897+
.arg(dir.path().to_str().unwrap())
898+
.arg("--include-charters")
899+
.assert()
900+
.success()
901+
.stdout(predicate::str::contains("CHARTER-WORK-VERB").not())
902+
.stdout(predicate::str::contains("CHARTER-DESIGN-PROVENANCE").not());
903+
}
904+
905+
#[test]
906+
fn test_charter_work_verb_absent_is_quiet() {
907+
// Anti-noise: an undeclared field must emit nothing (the legacy corpus is
908+
// 100% undeclared and must stay quiet).
909+
let dir = TempDir::new().unwrap();
910+
setup_straymark(dir.path());
911+
create_charter_with_frontmatter(dir.path(), "");
912+
913+
let mut cmd = cargo_bin_cmd!("straymark");
914+
cmd.arg("validate")
915+
.arg(dir.path().to_str().unwrap())
916+
.arg("--include-charters")
917+
.assert()
918+
.success()
919+
.stdout(predicate::str::contains("CHARTER-WORK-VERB").not());
920+
}
921+
922+
#[test]
923+
fn test_charter_design_provenance_invalid_value_warns() {
924+
let dir = TempDir::new().unwrap();
925+
setup_straymark(dir.path());
926+
create_charter_with_frontmatter(
927+
dir.path(),
928+
"work_verb: implement\ndesign_provenance: inherited\n",
929+
);
930+
931+
let mut cmd = cargo_bin_cmd!("straymark");
932+
cmd.arg("validate")
933+
.arg(dir.path().to_str().unwrap())
934+
.arg("--include-charters")
935+
.assert()
936+
.success()
937+
.stdout(predicate::str::contains("CHARTER-DESIGN-PROVENANCE"));
938+
}

dist/.straymark/00-governance/AGENT-RULES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,4 +412,4 @@ When a project accumulates a high volume of AILOGs across multiple Charters and
412412
413413
---
414414
415-
*StrayMark fw-4.30.0 | [Strange Days Tech](https://strangedays.tech)*
415+
*StrayMark fw-4.31.0 | [Strange Days Tech](https://strangedays.tech)*

dist/.straymark/00-governance/C4-DIAGRAM-GUIDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,4 +234,4 @@ Use a Level 1 (Context) diagram to illustrate:
234234

235235
---
236236

237-
*StrayMark fw-4.30.0 | [Strange Days Tech](https://strangedays.tech)*
237+
*StrayMark fw-4.31.0 | [Strange Days Tech](https://strangedays.tech)*

dist/.straymark/00-governance/DOCUMENTATION-POLICY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,4 +319,4 @@ See also [ADR-2025-01-20-001] for architectural context.
319319
320320
---
321321

322-
*StrayMark fw-4.30.0 | [Strange Days Tech](https://strangedays.tech)*
322+
*StrayMark fw-4.31.0 | [Strange Days Tech](https://strangedays.tech)*

dist/.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ Each entry inside a bucket follows this shape (v1 fields marked; all of them opt
107107
- **Origin-class**: ex-ante-planning | testing | telemetry | staging | real-env-bug (v1, optional)
108108
- **Status**: open | in-progress | suspected-closed | closed | superseded | promoted
109109
- **Severity**: normal | blocking (v1, optional; default normal)
110+
- **Work verb**: design | implement | audit | operate (optional; declared work-classification, Baton #332)
111+
- **Design provenance**: new | upstream (optional; only for implement — upstream degrades to operator)
110112
- **Trigger**: ready | <calendar date> | when <X> | <other>
111113
- **Destination**: chore | mini-charter | charter-replanning | operations | <charter-id> | <TDE id>
112114
- **Cost**: <effort estimate>
@@ -314,4 +316,4 @@ Contributed via [issue #111](https://github.com/StrangeDaysTech/straymark/issues
314316

315317
---
316318

317-
*StrayMark fw-4.30.0 | [Strange Days Tech](https://strangedays.tech)*
319+
*StrayMark fw-4.31.0 | [Strange Days Tech](https://strangedays.tech)*

0 commit comments

Comments
 (0)