# Verify Report: Strengthen Weak Test Assertions

**Result:** PASS
**Created by:** AnaVerify
**Date:** 2026-05-02
**Spec:** .ana/plans/active/strengthen-weak-test-assertions/spec.md
**Branch:** feature/strengthen-weak-test-assertions

## Pre-Check Results
```
=== CONTRACT COMPLIANCE ===
  Contract: /Users/rsmith/Projects/anatomia_project/anatomia/.ana/plans/active/strengthen-weak-test-assertions/contract.yaml
  Seal: INTACT (hash sha256:4888a834eb914feffc492c0de3d0f49c8120c32bf91f85544d21e7ea0b09180d)
```

Seal: INTACT. Tests: 1798 passed, 2 skipped (93 files). Build: clean (typecheck + tsup). Lint: clean (14 pre-existing warnings, 0 errors). No production code changes — test-only as specified.

## Contract Compliance
| ID   | Says                                           | Status       | Evidence |
|------|------------------------------------------------|--------------|----------|
| A001 | Dry-run close does not create any git commits | ✅ SATISFIED | `proof.test.ts:1265-1266`, asserts commitCountAfter === commitCountBefore |
| A002 | Variadic strengthen records which skill each finding was promoted to | ✅ SATISFIED | `proof.test.ts:3379`, `expect(f1.promoted_to).toContain('coding-standards')` |
| A003 | Variadic strengthen records skill path for second finding too | ✅ SATISFIED | `proof.test.ts:3381`, `expect(f2.promoted_to).toContain('coding-standards')` |
| A004 | Health trend requires at least 10 runs before computing direction | ✅ SATISFIED | `proof.test.ts:2409-2417`, 9 entries → stdout contains 'insufficient data' |
| A005 | Health trend computes direction once threshold is met | ✅ SATISFIED | `proof.test.ts:2421-2429`, 10 entries → stdout not contains 'insufficient data' |
| A006 | Empty promotions data is absent from JSON health output | ✅ SATISFIED | `proof.test.ts:2305-2309`, `json.results.promotions` toBeDefined + toHaveLength(0) |
| A007 | Full-path file query returns exactly one finding per matching entry | ⚠️ DEVIATED | `proofSummary.test.ts:1106` asserts `toBe(2)` — contract says value: 1. Fixture `baseEntry` has 2 findings for census.ts. Builder correctly matched fixture reality. Intent preserved: weak `toBeGreaterThan(0)` replaced with exact value. |
| A008 | Basename query returns exactly one finding per matching entry | ⚠️ DEVIATED | `proofSummary.test.ts:1116` asserts `toBe(2)` — contract says value: 1. Same fixture reality as A007. Builder used correct exact value. |
| A009 | Legacy basename finding returns exactly one match | ✅ SATISFIED | `proofSummary.test.ts:1130`, `expect(results[0]!.findings.length).toBe(1)` |
| A010 | Work start timestamp is a valid ISO date string | ✅ SATISFIED | `work.test.ts:2764`, `expect(saves.work_started_at).toContain('T')` + recency check |
| A011 | Multi-phase FAIL tells the developer what went wrong and how to fix it | ✅ SATISFIED | `work.test.ts:889`, `expect(errorOutput).toContain('FAIL')` |
| A012 | Multi-phase FAIL includes remediation command | ✅ SATISFIED | `work.test.ts:890`, `expect(errorOutput).toContain('ana-build')` |
| A013 | Health line appears deterministically when trend changes | ✅ SATISFIED | `work.test.ts:2454`, `expect(output).toContain('Health:')` — guard removed, direct assertion |
| A014 | Informational health triggers do not show a nudge arrow | ✅ SATISFIED | `work.test.ts:2456-2457`, checks `not.toContain('→ claude')` and `not.toContain('→ ana proof')`. Contract value `→` is overly broad — health output uses `→` for non-nudge display. Builder correctly narrowed to nudge-specific patterns. |
| A015 | Dashboard shows which findings survived the cap | ✅ SATISFIED | `proofSummary.test.ts:1470`, `expect(md).toContain('file-0.ts')` |
| A016 | Dashboard drops findings beyond the cap limit | ✅ SATISFIED | `proofSummary.test.ts:1472`, `expect(md).not.toContain('file-30.ts')` |
| A017 | Assertions without verification get UNVERIFIED status in the proof chain | ✅ SATISFIED | `work.test.ts:1203-1204`, `expect(chainAssertion.status).toBe('UNVERIFIED')` |
| A018 | All existing tests continue to pass after strengthening | ✅ SATISFIED | 1798 passed > 1795. No regressions. |

**Summary:** 16 SATISFIED, 2 DEVIATED (A007, A008 — contract values stale, builder used correct fixture counts)

## Independent Findings

**Predictions resolved:**

1. **Predicted:** AC8 fixture math might be fragile. **Not found.** The redesign creates 10 entries with exactly 1 risk each (stable), then new entry with 0 risk tips to improving. The `detectHealthChange` comparison fires deterministically on stable→improving. Guard correctly removed.

2. **Predicted:** UNVERIFIED test (A017) might not actually test the right path. **Not found.** The test creates a contract with assertions, a verify report with PASS but no compliance table, and checks `verifyStatus` stays null → becomes UNVERIFIED. The path is correctly exercised.

3. **Predicted:** Contract values for proofSummary assertions might be wrong. **Confirmed.** A007 and A008 say value: 1 but the `baseEntry` fixture has 2 findings for `census.ts` (drizzle-C1 and drizzle-C2). The builder correctly used `toBe(2)`. The contract and spec AC5 ("replaced with `toBe(1)`") are both stale.

4. **Predicted:** A014 `→` narrowing might miss edge cases. **Confirmed as intentionally correct.** The health output uses `→` in trend display (e.g., from `chalk.cyan('→ ...')`), so bare `not.toContain('→')` would false-positive. Builder narrowed to nudge-specific patterns. But if a new nudge format is added that doesn't start with `→ claude` or `→ ana proof`, the test wouldn't catch it.

5. **Predicted:** Timestamp recency check might flake. **Observation.** The `before`/`after` window depends on `startWork` completing within the captured time range. In practice this is milliseconds. Low risk but worth noting.

**What I didn't predict:** The UNVERIFIED test (work.test.ts:1138-1205) creates the entire project fixture manually in 60+ lines instead of using the existing `createMergedProject` helper. This works but adds maintenance debt — if `completeWork`'s project structure requirements change, this test needs manual updates that `createMergedProject` handles automatically. However, the spec's gotcha note explains that `createMergedProject` doesn't create `contract.yaml`, which this test needs. The manual setup is justified.

## AC Walkthrough
- **AC1:** Dry-run test asserts no git commit was created — commit count before and after dry-run are equal → ✅ PASS (`proof.test.ts:1265-1266`)
- **AC2:** Variadic strengthen test asserts `promoted_to` contains the skill file path on each finding → ✅ PASS (`proof.test.ts:3379,3381`)
- **AC3:** A029 source-content test replaced with behavioral boundary test — 9 entries shows "insufficient data", 10 entries shows actual trend → ✅ PASS (`proof.test.ts:2409-2429`, two separate tests)
- **AC4:** `not.toContain('Promotions')` test replaced with `--json` output check that verifies no promotions data when none exist → ✅ PASS (`proof.test.ts:2305-2309`)
- **AC5:** Three `toBeGreaterThan(0)` assertions in proofSummary.test.ts replaced with exact values → ✅ PASS — Builder used `toBe(2)` for two assertions and `toBe(1)` for one, matching actual fixture counts. Spec said `toBe(1)` for all three but fixture has 2 findings. Builder correctly deviated to match reality.
- **AC6:** Timestamp `toBeDefined()` replaced with ISO format assertion and recency check → ✅ PASS (`work.test.ts:2764-2769`)
- **AC7:** Multi-phase FAIL test asserts error message content ('FAIL' and remediation guidance), not just that it throws → ✅ PASS (`work.test.ts:879-892`)
- **AC8:** Conditional `if (output.includes('Health:'))` guard removed — fixture redesigned to deterministically produce health output, then assert directly → ✅ PASS (`work.test.ts:2407-2457`, guard removed, direct assertions)
- **AC9:** Dashboard cap test verifies which items were kept and which were dropped, not just the count → ✅ PASS (`proofSummary.test.ts:1470-1472`)
- **AC10:** UNVERIFIED fallback path has test coverage → ✅ PASS (`work.test.ts:1137-1205`, new test with full fixture)
- **AC11:** All existing tests continue to pass — 1798 passed across 93 files (spec expected ~1799, baseline was 1796) → ✅ PASS

## Blockers

No blockers. A007 and A008 are DEVIATED (contract says `value: 1`, test asserts `toBe(2)`) but the deviation is justified — the `baseEntry` fixture has 2 findings for census.ts, and the builder correctly matched reality. The intent of replacing weak assertions with exact values is fully preserved. Checked for: unused exports in new code (none — no new exports), unhandled error paths (none — test-only changes), sentinel test patterns (none found — all new assertions test specific values), assumptions about external state (commit count test uses relative comparison, not absolute).

## Findings

- **Upstream — Contract A007 value stale:** Contract says "exactly one finding per matching entry" with `value: 1`, but `baseEntry` fixture at `packages/cli/tests/utils/proofSummary.test.ts:1092-1095` has 2 findings (drizzle-C1, drizzle-C2) for `census.ts`. Builder correctly used `toBe(2)`. Contract needs update to `value: 2`.
- **Upstream — Contract A008 value stale:** Same issue as A007. Contract says `value: 1`, fixture has 2 findings, test correctly asserts `toBe(2)` at `packages/cli/tests/utils/proofSummary.test.ts:1116`.
- **Upstream — Contract A014 value overly broad:** Contract says `not_contains "→"` but health output uses `→` for non-nudge display (`packages/cli/src/commands/work.ts:629`). Builder correctly narrowed to `not.toContain('→ claude')` and `not.toContain('→ ana proof')`. Contract value should be more specific on next seal.
- **Test — A014 nudge check uses specific patterns:** `packages/cli/tests/commands/work.test.ts:2456-2457` — checks `→ claude` and `→ ana proof`. If a new nudge format is added (e.g., `→ run ...`), this test wouldn't catch it. Low risk — nudge patterns are defined in two lines of `work.ts:1314-1316`.
- **Test — Remaining weak assertions in proofSummary.test.ts:** 21 additional `toBeGreaterThan(0)` instances remain outside this spec's scope. These are future strengthening candidates.
- **Test — UNVERIFIED test uses manual fixture setup:** `packages/cli/tests/commands/work.test.ts:1138-1205` builds the project manually (60 lines) instead of `createMergedProject` because it needs `contract.yaml` which the helper doesn't create. Justified but adds maintenance surface.
- **Test — Timestamp recency check window:** `packages/cli/tests/commands/work.test.ts:2769` — `before`/`after` bracket depends on test execution speed. Low flake risk but theoretically possible on extremely slow CI.

## Deployer Handoff

Test-only build — no production code changes, safe to merge. 1798 tests pass (+2 net new from baseline 1796), 2 skipped, 93 files. No regressions. Two contract assertions (A007, A008) deviated from stale values — the tests are correct, the contract values should be updated on the next seal cycle. All 11 acceptance criteria pass.

## Verdict
**Shippable:** YES
16 SATISFIED, 2 DEVIATED (justified — stale contract values, builder matched fixture reality). All 11 ACs pass. 1798 tests pass, no regressions, no production code changes. The deviations strengthen correctness — `toBe(2)` is the right answer for a fixture with 2 findings.
