/feature-harden "" [--unit ] [--lite]
Applies adversarial domain-lens analysis to work-plan contracts and writes defensive tests BEFORE implementation. Tests define behavioral requirements the spec didn't anticipate — the implementation must satisfy them.
This is NOT an audit. There is no implementation to analyze. Instead, it asks: "Given these contracts, what behaviors can go wrong that the spec doesn't address?" — through the same domain lenses the audit uses.
Runs between /feature-test and /feature-implement. Idempotent — if
hardening is already complete for the current cycle, reports and stops.
With --lite, reduces scope to 2 lenses and defers all ABSENT findings.
Idempotency pre-flight (ALWAYS FIRST)
Read .feature/<slug>/status.md.
Work unit resolution
If work_units: none in status.md: ignore --unit flag, proceed as normal.
If work units are defined:
- If
--unitflag provided: scope all steps to that unit only - If no
--unitflag: find the next unit in testing-complete state
Stage check
Find the Hardening row in the Stage Completion table.
If Hardening is complete:
- Say: "Hardening already complete for cycle . adversarial tests written."
- Display
Next: /feature-implement "<slug>"and stop.
If Hardening is in-progress:
- Say: "Hardening was in progress — resuming from substage ."
- Read
hardening-report.mdfor progress, jump to the appropriate step.
If Hardening is not-started:
- Set status.md: Hardening →
in-progress, substage →loading-context - Proceed to Step 0.
Display opening header:
───────────────────────────────────────────────
🛡 HARDENING · <slug> · Cycle <n>< · WU-<n>>
───────────────────────────────────────────────
Step 0 — Load context
Read:
.feature/project-config.md— language, conventions, test commands.feature/<slug>/work-plan.md— contract definitions, construct graph edges (depends_on, shares_state, produces/consumes). If work units defined: read only the active unit's section and Contract Definitions..feature/<slug>/test-plan.md— already-written tests (dedup boundary).feature/<slug>/domains.md— technical domains, KB entries, constraints- Resolved SPEC_BUNDLE (if
.spec/exists) — behavioral requirements
If .kb/CLAUDE.md exists, scan the Topic Map for categories relevant to
this feature's domain. Read any type: adversarial-finding entries in
matching categories — these are bug patterns from prior audits that the
domain lens analysis should check.
Update status.md substage → lens-selection.
Step 1 — Lens selection
Derive which domain lenses apply from the work-plan contracts. This is a mechanical check — read the contracts and match against the activation signals below. Do NOT read source code (there is none yet).
| Lens | Activates when ANY contract has... |
|---|---|
| resource_lifecycle | Implements Closeable/AutoCloseable, mentions close/release/cleanup/dispose, acquires resources (files, connections, locks, memory) |
| contract_boundaries | Depends on constructs in a different package/module, has external callee contracts, crosses trust boundaries |
| concurrency | Mentions thread safety, has shares_state edges in construct graph, spec requirements mention concurrent access, multiple constructs access same mutable state |
| data_transformation | Describes encode/decode, serialize/deserialize, compress/decompress, format conversion, has produces/consumes edges where data changes form |
| shared_state | Mutable state accessed by 2+ constructs (check shares_state edges), co-mutator relationships in construct graph |
| dispatch_routing | Routing/dispatch/switching on type/enum, fan-out to 3+ constructs, handler registration patterns |
Count qualifying signals per lens. A lens activates if at least one construct qualifies.
Lite mode (--lite): Activate only the 2 lenses with the most
qualifying constructs. If tied, prefer resource_lifecycle > concurrency >
contract_boundaries > data_transformation > shared_state > dispatch_routing.
Skip threshold: If zero lenses activate, skip hardening entirely:
- Update status.md: Hardening →
complete, substage →skipped-no-signals - Display: "No domain lens signals in contracts — skipping hardening."
- Chain to
/feature-implement.
Display lens selection:
── Lens selection ─────────────────────────────
Active lenses (from contract signals):
✓ resource_lifecycle — <n> constructs (close/cleanup contracts)
✓ concurrency — <n> constructs (shared state edges)
✗ contract_boundaries — no cross-module deps
✗ data_transformation — no encode/decode contracts
✗ shared_state — no co-mutator edges
✗ dispatch_routing — no dispatch patterns
Proceeding with <n> active lenses, <n> constructs in scope.
Update status.md substage → behavioral-attack.
Step 2 — Behavioral attack (per lens, per construct)
For each active lens, analyze every qualifying construct. The goal is to find behaviors the spec and existing tests DON'T cover — gaps where the implementation will make silent assumptions.
Read test-plan.md first. For each finding below, check if an existing test already covers the scenario. If it does, verdict is TEST-COVERED.
Per-construct protocol
For each construct under the current lens:
Read its contract definition from work-plan.md (signature, receives, returns, side effects, error conditions, shared state).
Apply the lens-specific behavioral attack questions below.
For each question, produce a verdict:
SPEC-COVERED— a spec requirement addresses this. Cite R_N.TEST-COVERED— test-plan.md has a test for this. Cite test name.HARDENING-TEST— the behavior is implied by the contract but no test exists. Write an adversarial test.CONTRACT-SILENT— the contract doesn't address this behavior. A design decision is needed before a test can be written.
Lens: resource_lifecycle
For each construct with lifecycle contracts:
- Double-close: What happens if close/release is called twice? Is it idempotent (no-op) or does it throw? The contract must specify this.
- Use-after-close: What happens if any operation is called after close? Must throw, must no-op, or undefined?
- Partial construction failure: If the constructor/builder acquires resource A then fails acquiring resource B, is A cleaned up?
- Error path cleanup: For operations that acquire temporary resources (locks, buffers, intermediate state), is cleanup guaranteed on exception?
- Close ordering: If this construct owns other closeable resources, does close() close them in safe order? Does it aggregate exceptions?
Lens: contract_boundaries
For each construct with cross-module dependencies:
- Input trust: Does the contract validate all inputs, or does it trust the caller? What happens if the caller passes null, empty, out-of-range?
- Error propagation: If a callee throws, does the caller's contract specify how that error surfaces? Are all callee error types handled?
- Assumption match: Do the caller's documented assumptions about return values match the callee's documented guarantees?
- Builder validation: If a Builder, does it validate all configuration at build() time? Can contradictory configurations pass validation?
Lens: concurrency
Spec contracts drive this lens. If the resolved spec bundle contains concurrency requirements (from Step 1e of spec authoring), use them to determine which constructs need concurrency attacks and what the expected behavior is. A spec that says "thread-safe" means concurrent tests must pass. A spec that says "not thread-safe" means concurrent tests should verify the documented limitation, not find bugs in unsupported usage.
For each construct with thread-sharing signals OR spec concurrency contracts:
- Concurrent access: What happens if this method is called from two threads simultaneously? Is the outcome defined by the spec contract? If the spec declares thread-safe, the outcome must be correct. If the spec declares not-thread-safe, concurrent access is undefined — skip this construct for concurrent-access attacks.
- Atomicity: Are multi-step operations (check-then-act, read-modify- write) specified as atomic? Can callers observe intermediate state? The spec's concurrency contract should declare which operations are atomic. If it doesn't, that's a hardening finding — propose the requirement.
- Close under contention: What happens if close() races with an active operation? Is the outcome specified?
- Ordering: If operations must happen in a specific order, is that order enforceable? What if events arrive out of order?
- Spec-code mismatch: If the spec declares thread-safe but the implementation has no synchronization, that is a critical finding — the contract is violated.
Lens: data_transformation
For each construct with encode/decode contracts:
- Round-trip fidelity: Does encode → decode produce the original input? What about edge values (empty, max-size, special characters)?
- Encoding limits: What happens at type limits? Float overflow, integer overflow, precision loss, max array/string length?
- Format agreement: Do producer and consumer agree on encoding? What if the format version changes?
- Invalid encoded data: What happens if decode receives malformed input? Truncated, corrupted, wrong version?
Lens: shared_state
For each construct pair that shares mutable state:
- Write ordering: If two constructs both write the same state, is the ordering defined? Can a write from A be lost by a concurrent write from B?
- Partial visibility: Can a reader see partially-updated state from a writer? Is there an atomicity boundary?
- Stale reads: Can a read return data that was valid when read but invalidated by a concurrent operation?
Lens: dispatch_routing
For each construct with dispatch/routing patterns:
- Case coverage: Are all variants/types handled? What happens with an unknown or future variant?
- Handler consistency: Do all dispatch targets have compatible contracts with the dispatcher?
- Default behavior: Is there an explicit default/fallback? Is "do nothing" intentional or a bug?
Recording findings
Write findings to .feature/<slug>/hardening-report.md incrementally
(per lens section). This ensures progress survives a crash.
# Hardening Report — <slug>
## Lens: resource_lifecycle
| # | Construct | Question | Verdict | Detail |
|---|-----------|----------|---------|--------|
| H-RL-1 | HandleTracker | double-close | CONTRACT-SILENT | contract says "releases resources" but not what happens on 2nd call |
| H-RL-2 | HandleTracker | use-after-close | HARDENING-TEST | contract implies IllegalStateException — writing test |
| H-RL-3 | LocalTable | partial construction | TEST-COVERED | test_builder_cleanup_on_failure |
Update status.md substage → absent-resolution after all lenses complete.
Step 3 — Gap resolution (CONTRACT-SILENT findings)
Collect all CONTRACT-SILENT findings from the hardening report. These are behaviors the contract doesn't address but the domain lens analysis shows are necessary design decisions.
If there are zero CONTRACT-SILENT findings, skip to Step 4.
Format each finding as an ABSENT proposal
### ABSENT: <one-line description>
- **Lens:** <domain lens>
- **Construct:** <construct name>
- **Gap:** <what the contract doesn't specify>
- **Why it matters:** <concrete failure scenario>
- **Proposed requirement:** <behavioral requirement text>
Present to user
Group ABSENT proposals by lens. Display:
── Contract gaps found ────────────────────────
<n> behaviors your contracts don't specify.
These need design decisions before tests can be written.
resource_lifecycle (<n>):
1. HandleTracker.close() — idempotency unspecified
Proposed: "close() is idempotent — second call is a no-op"
2. LocalEngine constructor — partial failure cleanup unspecified
Proposed: "if HandleTracker build fails, close already-opened TableCatalog"
concurrency (<n>):
3. HandleTracker.register() — concurrent access behavior unspecified
Proposed: "register() is thread-safe — concurrent calls must not lose handles"
Lite mode: Display the findings but defer all by default:
- "Lite mode — gaps logged in hardening-report.md, deferred for later resolution."
- Skip to Step 4.
Full mode: Use AskUserQuestion with options:
- "Promote all" — add all proposed requirements, write tests for all
- "Review" — show full details, decide per-finding
- "Defer all" — log all, write no tests
If "Review": present each finding individually with options:
- "Promote" — add requirement to spec (or contract in work-plan.md), write test
- "Preserve as negative" — document as intentional non-behavior, write negative test
- "Defer" — log, no test
Apply promotions
For each promoted finding:
If
.spec/exists: Add the proposed requirement to the appropriate spec file using the [ABSENT] → promote flow. Runspec-validate.shto verify. Runspec-resolve.shto check for conflicts with existing requirements. If conflict found: present conflict and ask user to resolve before continuing.If no spec system: Append the requirement to the construct's contract definition in work-plan.md with a
<!-- Hardening: <date> -->annotation.
Record resolutions in hardening-report.md:
## ABSENT Resolutions
| # | Finding | Decision | Rationale |
|---|---------|----------|-----------|
| 1 | H-RL-1: close() idempotency | promoted | double-close from error recovery is common |
| 2 | H-CC-3: concurrent register() | promoted | spec says "thread-safe" but test was missing |
| 3 | H-DT-2: decode malformed input | deferred | low priority, encode is internal-only |
Update status.md substage → writing-tests.
Step 4 — Write adversarial tests
Write tests for all HARDENING-TEST findings and promoted ABSENT findings. These tests:
- Compile against existing stubs (no implementation yet)
- Fail with the expected behavior (TDD — implementation will make them pass)
- Are tagged with the hardening finding ID for traceability
Test organization
Append adversarial test methods to existing test files from the test plan.
Do NOT create separate *_adversarial test files — the implementation
agent needs to see all tests for a construct in one place.
Each adversarial test method gets an intent comment block (same format used by audit prove-fix, enabling the coverage check gate on re-audits):
// Finding: H-RL-2
// Bug: operations succeed after close() — no closed flag check
// Correct behavior: register() after close() must throw IllegalStateException
// Fix location: HandleTracker.register()
// Regression watch: register() before close() must still work
@Test
void test_HandleTracker_register_afterClose_throwsIllegalState() {
// ... test body
}
Test writing principles
- Behavioral, not implementation-prescriptive. Test what must be true,
not how it should be implemented.
assertThrows(IllegalStateException)is behavioral.assertTrue(field instanceof AtomicBoolean)is not. - Concrete attack scenarios. Each test has a specific input/sequence that triggers the behavior. "Call close(), then call register()" — not "verify thread safety."
- Concurrency tests use controlled scheduling. Use barriers, latches,
or blocking stubs to widen race windows. Don't rely on sleep() or
hope for interleaving. Add
@Timeout(10)to prevent hangs. - One concern per test. Don't combine double-close + use-after-close in one test method. Separate assertions make failures diagnostic.
Verify tests fail
Run the test suite after writing. All new adversarial tests should fail (stubs aren't implemented yet). If any adversarial test passes against stubs, it's testing something that's already true — either the stub is more complete than expected, or the test doesn't actually exercise the adversarial scenario. Review and fix.
Update status.md substage → verifying-failures.
Step 5 — Update artifacts and hand off
Update .feature/<slug>/test-plan.md — append a Hardening section:
## Hardening (adversarial, Cycle <n>)
### Lenses activated: <list>
| Test name | File | Construct | Lens | Finding |
|-----------|------|-----------|------|---------|
| test_close_idempotent | <path> | HandleTracker | resource_lifecycle | H-RL-1 |
| test_register_afterClose | <path> | HandleTracker | resource_lifecycle | H-RL-2 |
### Coverage delta
- Tests before hardening: <N>
- Tests after hardening: <N + M>
- New adversarial tests: <M>
- ABSENT promoted: <P>
- ABSENT preserved: <Q>
- ABSENT deferred: <D>
Update status.md:
- Hardening →
complete - substage →
all-tests-verified-failing - Stage Completion table: Hardening row → Est. Tokens
~<N>K
Append hardening-complete entry to cycle-log.md.
Hand off
Read automation_mode from status.md.
If automation_mode: autonomous:
───────────────────────────────────────────────
🛡 HARDENING complete · <slug> · Cycle <n>< · WU-<n>>
Tokens : <TOKEN_USAGE>
Lenses : <active lenses>
Tests : <M> adversarial tests written
Gaps : <P> promoted, <Q> preserved, <D> deferred
───────────────────────────────────────────────
Starting implementation · type stop to pause
───────────────────────────────────────────────
Then invoke /feature-implement "<slug>"< --unit WU-<n>> as a sub-agent
immediately.
If automation_mode: manual (or not set):
───────────────────────────────────────────────
🛡 HARDENING complete · <slug> · Cycle <n>< · WU-<n>>
Tokens : <TOKEN_USAGE>
Lenses : <active lenses>
Tests : <M> adversarial tests written
Gaps : <P> promoted, <Q> preserved, <D> deferred
───────────────────────────────────────────────
Use AskUserQuestion with two options:
- "Proceed to implement"
- "Stop"
If "Proceed": invoke /feature-implement "<slug>"< --unit WU-<n>>.
If "Stop": display Next: /feature-implement "<slug>" and stop.
Hardening must NOT
- Read source code (there is no implementation yet)
- Modify existing tests from feature-test (only append new ones)
- Make implementation decisions (tests define requirements, not solutions)
- Write tests that prescribe implementation details (no reflection, no checking field types, no asserting specific synchronization primitives)
- Skip findings without a verdict (every question gets SPEC-COVERED, TEST-COVERED, HARDENING-TEST, or CONTRACT-SILENT)
- Write tests that can only pass with a specific implementation strategy (tests must be satisfiable by any correct implementation)