Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
id: AILOG-2026-07-26-001
title: Follow-ups mutation + ex-ante creation verbs (CHARTER-01, #355 + #360)
status: accepted
created: 2026-07-26
agent: claude-opus-5-1m
confidence: high
review_required: false
risk_level: low
eu_ai_act_risk: not_applicable
nist_genai_risks: []
iso_42001_clause: []
lines_changed: 0
files_modified: []
observability_scope: none
tags: [follow-ups, cli, governance, adopter-feedback, self-adoption, charter-01]
related: [AIDEC-2026-07-18-001]
---

# AILOG: Follow-ups mutation + ex-ante creation verbs (CHARTER-01)

## Summary

Executed [CHARTER-01](../../charters/01-follow-ups-registry-mutation-and-ex-ante-creation.md): three new verbs — `followups note`, `followups set-status`, `followups new` — closing the last flows that required hand-editing the CLI-parsed registry (Weft field reports #355, #360). Ships as **fw-4.37.0 / cli-3.39.0**. First *executed* Charter of the self-adopted repo.

## Context

Every pre-existing `followups` verb either read the registry or wrote it as a side effect of extraction. Mutating an entry meant hand-editing markdown the CLI parses; creating one at Charter-declaration time was impossible (both population paths assume an ex-post origin). The #360 hazard was live in the adopter's tree: `CHARTER-06` cited `FU-011` three times as a forward reference with nothing reserving it, and ids are minted `max(existing) + 1` at extraction, so the next unrelated `drift --apply` would have silently repointed those citations.

## Actions Performed

1. **Shared write path** — `followups::write_recounted` / `recounted_frontmatter`: recompute the CLI-owned counters *from the body about to be written* and persist in one call. `recount` and `verify` were refactored onto it, so there is one implementation of the arithmetic rather than two that could disagree.
2. **`note`** — appends to `Notes` via the pure `append_note` composer: `[date]` or `[date · source]` stamp, composed onto any existing value (the field is single-line by parser contract, so annotations compose instead of stacking).
3. **`set-status`** — validates against the `FuStatus` vocabulary and writes status + counters together. Two refusals rather than silent damage: an out-of-vocabulary value (the lenient parser would degrade it to `unknown`, dropping the entry from every counter without failing) and `promoted` (redirected to `followups promote`, which writes the TDE the status points at).
4. **`new`** — mints an ex-ante entry through `render_declared_entry` (a deliberate sibling of `render_new_entry`, without its AILOG-derived `Source-hash`), inserts it into the requested bucket, recounts, and prints the assigned id.
5. **Guard for all three** — `guard_parse_warnings` aborts before any write when the registry has parse warnings.
6. **Framework** — pattern doc (EN + es + zh-CN) documents the verbs and the ex-ante origin; the registry template states the write path and that entries are never hand-edited; the `/straymark-followups` skill uses `set-status` + `note` for triage and gains a Charter-declaration step.
7. **Docs + release** — CLI-REFERENCE ×3 locales, `CLAUDE.md` command table, version tables, `CHANGELOG.md`, `dist-manifest.yml` → 4.37.0, `cli/Cargo.toml` → 3.39.0, governance footers.

## Decisions Made

- **The schema needed no change.** The Charter listed "confirm the ex-ante entry shape validates without `Source-hash`; adjust `$defs.entry` if not". It validates as-is: `origin` is required (so `--origin` is required, not optional), `origin_class` already carries **`ex-ante-planning`** in its enum, and `source_hash` is not a schema property at all. **The schema had already named the ex-ante case; only the creation path was missing.** That reframes #360 from "add a field" to "the vocabulary was there, the verb was not".
- **`--origin` is required** rather than defaulted. Schema v1 requires it, and an entry whose origin is guessed is the kind of half-truth the registry exists to prevent.
- **Two fixes to shared helpers, not to my new code** (see `## Risk` R6): the spacing defects belonged to `set_entry_field` / `insert_into_bucket` and were degrading the file on every `drift`/`promote`/`verify` write. Fixing them at the root rather than working around them in `note` is why the diff touches those two functions.
- **Deferred options stayed deferred.** No id-reservation primitive and no Charter-scanning in `drift`, exactly as declared. Nothing in execution argued for either.

## Verification

- Full workspace `cargo test`: **38 groups green**. 10 new integration tests (one per verb path: happy path, composition, malformed-registry abort with byte-identical file, unknown/`promoted` status refusal, no-op same-status, id assignment, bucket routing + `TBD` defaults, required-origin/bucket validation, and the R4 `new`-then-`drift` no-duplicate check) plus 5 new unit tests (`append_note` composition incl. whitespace-only inputs, `render_declared_entry` shape + parser round-trip, premise omission, and the two spacing regressions).
- `cargo clippy -p straymark-cli`: no warnings in `note.rs` / `set_status.rs` / `new.rs`.
- `gen_codex_skills --check`: in sync (15 skills).
- **End-to-end with the built binary** against a scratch registry, running the exact flow the Charter declared: `new` (assigned FU-003, `Origin-class: ex-ante-planning`, no hash) → `note` on an unrelated entry (status unchanged, prior note preserved) → `set-status … closed` (counters moved in the same step) → **`recount` reports "already in sync"** (the desync #355 describes cannot occur) → `list` renders all three → `drift` does not re-extract. Then the guard: appending a malformed `### FU-` heading made `note` exit 1 with nothing written.

## Risk

- **R1 (registry corruption) — mitigated and tested.** The parse-warning guard aborts before any write; the integration test asserts the file is byte-identical after a refused write.
- **R2 (counter desync moves into the verb) — mitigated.** One shared recompute path; the `set-status` test asserts a following `recount` finds nothing to do.
- **R3 (concurrent id minting)** — unchanged and still not claimed: atomicity is scoped to "one command, not two steps", which is the actual #360 hazard.
- **R4 (ex-ante entry confuses `drift` dedup) — verified benign.** `new`-then-`drift --scan-all` produces no duplicate; a duplicate arising from a *later* AILOG mention remains accepted behavior, resolved with `set-status … superseded`.
- **R5 (scope creep into the deferred options)** — did not occur.
- **R6 (new, not in Charter) — two pre-existing markdown-shape defects in shared write helpers.** `set_entry_field` collapsed an entry's trailing `"\n\n"` to `"\n"` (gluing the edited entry to the next `## Bucket:` heading) and `insert_into_bucket` appended entries with no blank line before the following section. Both predate this Charter and were silently degrading the registry's shape on every `drift --apply` / `promote` / `verify` write; making field writes routine via `note` is what exposed them. Fixed at the root with two regression unit tests. Severity low (cosmetic on a human-read artifact, no parse impact), but left unfixed it would have made every new `note` call visibly reshape the file.

## Modified Files

| File | Change |
|------|--------|
| `cli/src/followups.rs` | `write_recounted` / `recounted_frontmatter`, `append_note`, `render_declared_entry`, `CANONICAL_BUCKETS`, two shared-helper spacing fixes, 5 unit tests |
| `cli/src/commands/followups/note.rs` | New — `note` verb + the shared parse-warning guard |
| `cli/src/commands/followups/set_status.rs` | New — `set-status` verb |
| `cli/src/commands/followups/new.rs` | New — `new` verb (`NewArgs`) |
| `cli/src/commands/followups/mod.rs` | Wire the three modules |
| `cli/src/commands/followups/recount.rs` | Use the shared recompute path |
| `cli/src/commands/followups/verify.rs` | Use the shared write path (removes a duplicated recount block) |
| `cli/src/main.rs` | Three subcommands + flags + routing |
| `cli/tests/followups_test.rs` | 10 integration tests |
| `cli/Cargo.toml` | Version 3.39.0 |
| `dist/.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md` (+ i18n es, zh-CN) | Verbs + ex-ante origin section |
| `dist/.straymark/templates/follow-ups-backlog.md` | Write path + never-hand-edit note |
| `dist/.claude/skills/straymark-followups/SKILL.md` | Triage via `set-status`/`note` + Charter-declaration step |
| `dist/.codex/skills/straymark-followups/SKILL.md` | Regenerated (`gen_codex_skills`) |
| `dist/.gemini/skills/straymark-followups/SKILL.md`, `dist/.agent/workflows/straymark-followups.md` | Hand-synced (see Closing notes — the generator covers only `.codex`) |
| `dist/dist-manifest.yml` | Version 4.37.0 |
| `dist/.straymark/00-governance/{QUICK-REFERENCE,AGENT-RULES,DOCUMENTATION-POLICY,C4-DIAGRAM-GUIDE,FOLLOW-UPS-BACKLOG-PATTERN}.md` (+ i18n ×2) | fw version footers (see Closing notes) |
| `docs/adopters/CLI-REFERENCE.md` (+ i18n es, zh-CN) | Three verbs documented |
| `README.md` (+ i18n es, zh-CN) | Version tables |
| `CLAUDE.md` | Command table rows |
| `CHANGELOG.md` | fw-4.37.0 / cli-3.39.0 entry |
| `.straymark/charters/01-follow-ups-registry-mutation-and-ex-ante-creation.md` | Status → closed, closing notes |
| `.straymark/charters/README.md` | Row moved to Closed |

## Follow-ups

- None. The Charter's declared scope shipped in full; both deliberately-deferred options (#360 Options B and C) remain deferred with the reasoning recorded in the Charter, and nothing in execution argued for revisiting them.
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
---
charter_id: CHARTER-01-follow-ups-registry-mutation-and-ex-ante-creation
status: declared
status: closed
closed_at: 2026-07-26
effort_estimate: M
execution_ailogs: [AILOG-2026-07-26-001]
trigger: "Two adopter field reports from Weft (#355, #360) filed against the same seam: every path that mutates or creates a registry entry currently goes through hand-editing a CLI-parsed file. #360 documents a live id-collision hazard in the adopter's tree — CHARTER-06 cites FU-011 three times as a forward reference with nothing reserving it."
work_verb: implement
design_provenance: new
---

# Charter: Follow-ups registry: mutation and ex-ante creation verbs

> **Status (mirrored from frontmatter — source of truth is above):** declared. Effort: M (1–3 days).
> **Status (mirrored from frontmatter — source of truth is above):** closed 2026-07-26. Effort: M (declared 1–3 days; executed in one session). Execution: `AILOG-2026-07-26-001`. Released as fw-4.37.0 / cli-3.39.0.
>
> **Origin:** adopter field reports [#355](https://github.com/StrangeDaysTech/straymark/issues/355) and [#360](https://github.com/StrangeDaysTech/straymark/issues/360), both from the Weft adopter (registered in #357). No originating AILOG — this Charter precedes execution.

Expand Down Expand Up @@ -167,3 +169,15 @@ When closing this Charter:
4. **Status frontmatter** moves from `in-progress` to `closed` (+ `closed_at:`).
5. **Do not delete** this file.
6. Close #355 and #360 with a comment stating what shipped and what was deliberately not built (the two deferred options above), so the adopter sees the reasoning rather than silence.

## Closing notes

`straymark charter drift CHARTER-01` reports **1 omission and 14 scope expansions** against `## Files to modify` (29 declared, 43 modified). All four causes, remediated atomically in this same PR:

- **`dist/.gemini/skills/…` and `dist/.agent/workflows/…` were declared as "Regenerated (same generator)" — they are not.** `gen_codex_skills` emits only the `.codex` surface; the Gemini and `.agent` mirrors have been hand-maintained since fw-4.22.0 (#221). Hand-synced with the same content edits instead. The declaration was authored from the CI check's name (`gen_codex_skills --check`) rather than from what the generator actually writes — the reconnaissance discipline (#210) was applied to the *files* but not to the *mechanism* that maintains them.
- **The governance version footers were not declared.** A framework bump requires updating the `fw-X.Y.Z` footer in `QUICK-REFERENCE`, `AGENT-RULES`, `DOCUMENTATION-POLICY`, `C4-DIAGRAM-GUIDE` and `FOLLOW-UPS-BACKLOG-PATTERN` (×3 locales each) per the release protocol in `CLAUDE.md`; `## Files to modify` listed only `dist-manifest.yml`. Applied.
- **`dist/.straymark/schemas/follow-ups-backlog.schema.v1.json` was declared and NOT modified** — correctly. The Charter declared it conditionally ("confirm the ex-ante shape validates; adjust `$defs.entry` if it does not"), and it validates unchanged: `origin_class` already contains `ex-ante-planning`, and `source_hash` is not a schema property at all. Kept in the declaration rather than deleted: the check was real work, and its outcome sharpened what #360 was — the vocabulary for the ex-ante case already existed, only the creation path was missing.

- **`cli/src/commands/followups/verify.rs` and `Cargo.lock` were not declared.** `verify.rs` was refactored onto the shared `write_recounted` path — the same consolidation the Charter declared for `recount.rs`, but only that one file was named; the declaration under-counted its own de-duplication. `Cargo.lock` moves with any version bump and is mechanical.

One new risk emerged, documented in the AILOG as `R6 (new, not in Charter)`: two pre-existing markdown-shape defects in the shared write helpers (`set_entry_field` collapsing an entry's trailing blank line; `insert_into_bucket` omitting the one before the next section), which every `drift`/`promote`/`verify` write had been degrading the registry with. Fixed at the root with regression tests, since `note` would otherwise have made the reshaping visible on every call.
6 changes: 4 additions & 2 deletions .straymark/charters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ Rows move from `## Active` to `## Closed` as part of the closing PR, not afterwa

| NN | Charter | Status | Effort | Origin |
|---|---|---|---|---|
| 01 | [Follow-ups registry: mutation and ex-ante creation verbs](01-follow-ups-registry-mutation-and-ex-ante-creation.md) | declared | M | Adopter feedback [#355](https://github.com/StrangeDaysTech/straymark/issues/355), [#360](https://github.com/StrangeDaysTech/straymark/issues/360) |
| | _None._ | | | |

## Closed

_None yet._
| NN | Charter | Closed | Effort | Shipped as | PR |
|---|---|---|---|---|---|
| 01 | [Follow-ups registry: mutation and ex-ante creation verbs](01-follow-ups-registry-mutation-and-ex-ante-creation.md) | 2026-07-26 | M | fw-4.37.0 / cli-3.39.0 | #376 |
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@ and this project uses [independent versioning](README.md#versioning) for Framewo

---

## Framework 4.37.0 / CLI 3.39.0 — 2026-07-26

Closes the last flows that required hand-editing the CLI-parsed follow-ups registry (CHARTER-01; Weft field reports #355 and #360).

### Added (CLI)

- **`straymark followups note FU-NNN "<text>" [--source ID]`** *(new)* — append a dated annotation to an entry's `Notes` in one validated edit. `Notes` is a single-line field by parser contract, so annotations **compose** onto the existing value rather than stacking. `--source CHARTER-NN|AILOG-…` records what motivated the note, so it stays attributable instead of becoming undated prose. Covers #355's case: recording a *partial* mitigation without changing status.
- **`straymark followups set-status FU-NNN <status>`** *(new)* — change the status **and** recompute the CLI-owned counters in the same step, closing the desync window `recount` exists to clean up after (#355): the two-step it replaces desyncs the moment you forget the second half, leaving the counters quietly lying about the backlog. A status outside the vocabulary is **refused, not written** — the parser is lenient, so a typo would not fail: it would silently drop the entry from every counter. `promoted` redirects to `followups promote`, which also writes the TDE that gives the status something to point at.
- **`straymark followups new --title … --origin …`** *(new)* — create an entry whose origin is a **Charter declaration** (ex-ante), before any AILOG exists (#360). Written with `Origin-class: ex-ante-planning` and **no `Source-hash`** (there is no AILOG to hash; inventing one would make a later `drift --apply` believe it had extracted something it never saw). The id is assigned atomically and printed, so a Charter cites an entry that **exists** instead of a reserved guess: ids are minted `max(existing) + 1` at extraction time, so the reporting adopter's forward-referenced `FU-011` would have been handed to an unrelated entry by the next `drift --apply`.
- All three **refuse to write a registry with parse warnings** — a surgical edit against a structure the parser mis-read can corrupt neighbouring entries.

### Fixed (CLI)

- **Field writes no longer nudge the registry's markdown shape.** `set_entry_field` collapsed an entry's trailing `"\n\n"` to `"\n"`, gluing the edited entry to the following `## Bucket:` heading; `insert_into_bucket` appended entries with no blank line before the next section. Both are shared helpers, so `drift --apply`, `promote` and `verify` were reshaping the file a little on every write. Surfaced by making field writes routine.

### Changed (Framework)

- [`FOLLOW-UPS-BACKLOG-PATTERN.md`](dist/.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md) (EN + es + zh-CN) documents the three verbs and the ex-ante origin (`Origin: CHARTER-NN §Scope`) as a first-class case alongside the ex-post one. The registry template documents the write path and states that entries are never hand-edited.
- The `/straymark-followups` skill (all four agent surfaces) uses `set-status` + `note` for post-Charter-close triage instead of hand-flips-then-`recount`, and gains a **Charter-declaration** step for registering an ex-ante deferral.

### Changed (CLI)

- **Internal:** one shared recompute-and-write path (`followups::write_recounted`) for every mutating verb, so "the entry edit and the counter update are one step" holds by construction. `recount` uses the same function, keeping it the idempotent check on the verbs' arithmetic rather than a second implementation that could disagree.

## CLI 3.38.0 — 2026-07-25

Auditor isolation is enforced where the prompt is built, not where the report is read (#372, Sentinel field report).
Expand Down
Loading