Skip to content

test(app-lock): cover node classification and the kind → mode default - #500

Merged
pawellisowski merged 1 commit into
mainfrom
routine/test-hygiene-2026-09-08
Sep 8, 2026
Merged

test(app-lock): cover node classification and the kind → mode default#500
pawellisowski merged 1 commit into
mainfrom
routine/test-hygiene-2026-09-08

Conversation

@pawellisowski

Copy link
Copy Markdown
Contributor

Area picked: cli/src/app_lock.rs — what the lockfile tells an approver

kind and mode are the two CompiledNode fields a human actually reads.
render_glass_box_html (cli/src/commands/app.rs:1786) colours a card red and
stamps safety MISSING on mode: write, grey and silent on mode: read, and
labels an agent-less node by its kind. That renderer has nine tests — all against
hand-built CompiledNode fixtures. Nothing tested that compile puts the right two
strings into the lock in the first place.

cargo llvm-cov --all-targets on main put the whole of classify_node's tail at
zero (assert, compare, approve, snapshot, model-lock, unknown), together
with compile_node's entire agent-not-installed branch, its declared-write arm for a
missing command, collect_refs' sequence arm, and derive_connections' assert/sweep
scanners. So a primitive that drives a live-model write could have compiled to
mode: read and shown up grey, with no missing-safety badge, on the one surface a
run is approved from — and every test in the repo would still have been green.

Eight tests added, all in the existing app_lock::tests module. No tests deleted
see the last section.

Mutation evidence

Every test below was proven red by breaking the code it covers and green again on
restore. Each mutation killed exactly its target test and no other new test, so
none of the eight is riding on another's assertions.

# Mutation applied to src/app_lock.rs Test that went red
M1 classify_node: the sweep arm answers "for-each" every_declared_primitive_is_named_by_its_own_kind_in_the_lock
M2 classify_node: the fallback answers "inline" instead of "unknown" …named_by_its_own_kind… and a_node_that_declares_no_primitive_is_unknown_and_falls_to_write
M3 compile_node: read-mode default widened with || kind == "for-each" only_the_observing_primitives_default_to_read_mode
M4 compile_node: kind == "snapshot" dropped from the read-mode default only_the_observing_primitives_default_to_read_mode
M5 compile_node: agent branch returns a hardcoded "write" instead of resolved.mode only_the_observing_primitives_default_to_read_mode
M6 compile: body nodes are stamped with their top-level ancestor's kind a_do_body_node_is_classified_by_its_own_declaration_not_its_parents
M7 not-installed + mode: read note demoted warninfo an_uninstalled_agent_is_warned_about_whatever_mode_the_author_declared
M8 not-installed + mode: write resolves to "read" an_uninstalled_agent_is_warned_about_whatever_mode_the_author_declared
M9 missing-command + mode: write note promoted infowarn a_command_missing_from_an_installed_manifest_informs_when_told_and_warns_when_guessing
M10a derive_connections: the assert.expr scan deleted a_reference_from_an_assert_or_a_sweep_still_orders_the_node_after_its_source
M10b derive_connections: the sweep.values scan deleted a_reference_from_an_assert_or_a_sweep_still_orders_the_node_after_its_source
M11 collect_refs: the Value::Sequence arm removed a_reference_buried_in_a_list_value_is_ref_checked_like_any_other

Notes on the two mutations that are worth reading twice:

  • M5 is what makes only_the_observing_primitives_default_to_read_mode more than a
    kind-table restatement. agent sits on the write side of that table, yet a
    read-mode command must still compile to read — the test asserts both an agent node
    with a mode: read command and one with a mode: write command, so the two rules
    stay distinguishable on nodes where they would otherwise agree.
  • M9 is the discriminating one for …informs_when_told_and_warns_when_guessing:
    both branches compile to mode: write, so severity is the only thing that separates
    "the author already made this call" (info) from "the compiler had to guess" (warn),
    per Compile notes are untyped strings — add a severity/kind so consumers can render info vs warning without parsing prose #170. The test asserts the two kinds and then asserts they differ.

Vacuity check on the new tests

Against the three failure modes called out for Rust:

  • Nothing asserts on a value it built two lines earlier. Every assertion reads a
    field off a LockFile returned by compile, or an edge list returned by
    derive_connections. The fixtures are .flo source text and agent manifests; the
    assertions are on compiler output.
  • No serde round-trips. The one place YAML is deserialised is the fixture setup.
  • No unwrap() standing in for an assertion. The compiled() helper panics with
    the missing node's id when a node is absent from the lock — that is a real failure
    (M6 reaches it), not an accidental one, and it is never the only check in a test.

No deletions this run

I looked for tests in this area that assert nothing meaningful and did not find any.
The candidates I checked and why each survives:

  • The nine glass_box_tests in cli/src/commands/app.rs build CompiledNode fixtures
    by hand, which reads like the "asserts on its own fixture" pattern — but they are
    testing a renderer, and the fixture is its input. Breaking the escaper or the
    mode→class mapping fails them. They earn their place; this PR supplies the upstream
    half they were missing.
  • The 26 pre-existing app_lock::tests all drive compile / derive_connections and
    assert on output. None is vacuous.

One observation for the maintainer, deliberately not acted on here: the four
extract_group_* tests in cli/src/commands/tree.rs:186-217 are byte-identical to the
four in cli/src/commands/mod.rs:59-90 and, via use super::*, exercise the same
crate::commands::extract_group. They are pure duplicates. I left them alone for two
reasons — breaking extract_group kills both copies, so the redundancy cannot be
demonstrated by the mutation discipline this routine deletes tests under, and #499
(routine/abstractions-2026-09-07) is open over collapsing re-typed abstractions,
which is where that duplication properly belongs.

Gates

Prepared as CI does: clang libsecret-1-dev libdbus-1-dev pkg-config via apt, toolchain
pinned from cli/rust-toolchain.toml (1.95.0). Run from cli/:

  • cargo fmt --all -- --check — clean
  • cargo clippy --all-targets -- -D warnings — clean
  • cargo test1620 passed, 0 failed

🤖 Generated with Claude Code

https://claude.ai/code/session_016AhfthzLr7vdb8iuJtRdsL


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

@codex review


Generated by Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-08T03:10:21.075837Z 2d8933a Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

`kind` and `mode` are the two lockfile fields an approver reads: the Glass
Box colours a card red and demands a `safety:` block on `mode: write`, grey
and silent on `mode: read`. The renderer was tested against hand-built
`CompiledNode`s, but nothing tested that `compile` puts the right two
strings into the lock in the first place — `classify_node` had no test at
all, and coverage put six of its ten arms, plus the whole
agent-not-installed branch of `compile_node`, at zero.

Eight tests, each proven red under a mutation of the code it covers:

- every primitive is named by its own kind in the lock (all ten arms)
- only the observing primitives default to read mode; an agent node still
  takes its mode from the manifest
- a node declaring no primitive is `unknown` and falls to write
- a `do:` body node is classified by its own declaration, not its parent's
- an uninstalled agent warns whatever mode the author declared, and
  carries no output schema
- a command missing from an installed manifest informs when told and warns
  when guessing — the branches are indistinguishable by mode alone
- a reference from an assert expression or a sweep value still orders the
  node after its source
- a reference buried in a list value is ref-checked like any other
@pawellisowski
pawellisowski force-pushed the routine/test-hygiene-2026-09-08 branch from fe3f800 to 2d8933a Compare September 8, 2026 03:07

Copy link
Copy Markdown
Contributor Author

Force-pushed to re-author the single commit under Pawel <pawellisowski@o2.pl>: the no Claude co-author trailers check was red because the commit was authored by Claude <noreply@anthropic.com>, which is the third source scripts/no-claude-coauthor-trailers.py guards against (GitHub synthesises a Co-authored-by: trailer per non-merging commit author on squash — the #408 shape). Tree unchanged; the diff is byte-identical.

New head is 2d8933a301faed2bdc0797619bc872c01ede4947.

@codex review


Generated by Claude Code

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 2d8933a301

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@pawellisowski
pawellisowski merged commit 9265ebb into main Sep 8, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant