markharness

0015: Introducing a step concept for Behavior (a phased approach)

Status

Superseded by 0016 (2026-08-31). Phase 1 (introducing inline Behavior.steps) was implemented and once satisfied the “Conditions for changing to Accepted” below, but real-world use exposed that the underlying premise — sharing Behavior.steps as the common granularity across every Condition — did not hold, so 0016 supersedes this ADR.

Context

The TestCase.steps produced by generate.rs::generate_testcases is array-typed, but in practice it is always a single-element array, [behavior.description] (testcase-generation-design.md §3.3; the section heading now reflects the model introduced by ADR 0016, but it still documents the plain-transcription approach — title = condition.description / steps = behavior.steps — that was in effect at the time this ADR was written). behavior.yml (behavior.schema.json) itself has only one free-text description field, so Test Designers are forced to cram what should be an ordered sequence of operations into a single string. testcase-generation-design.md §7 explicitly called out “more advanced grouping via the Behavior layer and multi-tier axis management” as future work, but did not mention splitting steps into multiple elements.

At the same time, a future reuse need may arise where multiple Behaviors repeat the same procedure (e.g., a login sequence) verbatim. If description is copy-pasted each time, drift becomes undetectable — if one Behavior’s copy is updated and another’s is not, nothing surfaces the discrepancy.

The initial draft of this ADR considered introducing multi-element steps, a shared Step registry, UIDs, hash-based integrity checking, and an accept command all at once. A review concluded that this all-at-once approach was excessive. The sharing need has not yet been confirmed against real data (as of this ADR, no behavior.yml instance data exists anywhere in the repository), and neither the blast radius of a shared-Step change (whether to bulk-update every referencing Behavior or approve them individually, atomicity, recovery on failure) nor the necessity of integrating Step into the identity lifecycle infrastructure as a sixth EntityKind has been validated. Locking in fail-closed hash-mismatch behavior before those are settled carries significant risk.

This ADR therefore decides only on the multi-element split itself (Phase 1); the shared registry and everything downstream of it (Phases 2–4) is left as future direction, to be designed once real data confirms the need.

Premises

Decision

Phase 1 (decided by this ADR): introduce inline Behavior.steps

  1. Add steps: Vec<String> (required, ordered array) to behavior.yml. Each element is a plain inline string; there is no reference to a shared registry.
  2. Step granularity is fixed at “one steps array element equals one operation.” Bundling multiple operations into a single element is not allowed.
  3. description remains a one-sentence, human-facing summary but is no longer consumed by test-case generation at all.
  4. Replace generate.rs::generate_testcases’s steps = [behavior.description] with steps = behavior.steps. behavior.description is entirely excluded from generation logic and retains only its role as human-facing documentation within knowledge/.
  5. Knowledge validation must reject an empty steps array, and must reject any element that is an empty string.
  6. A shared Step registry, UIDs, hash-based integrity checking, and a steps accept-style recovery command are not introduced in this Phase.
  7. knowledge add --edit (KnowledgeDraft / BehaviorDraft, src/knowledge_draft.rs) — the only supported path for creating a Behavior — must be updated so it can accept and validate the newly required steps. Concretely: add a steps field to BehaviorDraft; include a steps: entry in the blank draft template that knowledge add --edit opens and in the non-interactive template output (the markharness knowledge add --edit --print-template-equivalent in cli.rs); and, mirroring push_missing_description, reject an empty or all-blank steps on the draft side too. Without this update, the only creation path could not satisfy the new required field, and Behaviors could not be created at all.
# behavior.yml
id: todo-add-task
feature: todo
label: Add Task
axis: [ui]
description: "User adds a task."
steps:
  - "Click the title field"
  - "Leave it empty"
  - "Press submit"

Implementation notes (not decided by this ADR)

Phase 2 (future direction, undecided): confirm sharing demand against real data

After Phase 1 ships and real data has accumulated for a while, check:

No quantitative threshold is set; the criterion is qualitative — move to the next Phase once duplication or update drift is actually observed. If it is not observed, inline Behavior.steps (Phase 1) alone is considered complete.

Phase 3 (future direction, undecided): design a shared Step registry as a separate ADR

Only if Phase 2 confirms sharing demand, design the following as a separate ADR (or a revision of this one). None of this is decided by this ADR.

Phase 4 (future direction, undecided): add hash-based integrity checking and accept operations

Only if Phase 3 introduces a shared registry, design the following. None of this is decided by this ADR.

Conditions for moving to Accepted

Out of scope