Skip to main content

AC-ID (Acceptance Criteria Identifier)

AC-ID is a unique identifier for acceptance criteria that enables complete traceability from specifications through implementation to tests. The format AC-US{story}-{number} creates an unbroken chain of accountability ensuring every requirement is implemented and tested.


Format

Structure: AC-US{story}-{number}

Components:

  • AC = Acceptance Criteria prefix
  • US = User Story
  • {story} = User story number (1, 2, 3, ..., no zero-padding for brevity)
  • - = Separator
  • {number} = Sequential AC number within story (01, 02, 03, ..., zero-padded for sorting)

Examples:

  • AC-US1-01 (First AC for User Story 1)
  • AC-US1-02 (Second AC for User Story 1)
  • AC-US12-05 (Fifth AC for User Story 12)
  • AC-US105-10 (Tenth AC for User Story 105)
  • AC-001 (missing user story link)
  • AC-US-01-01 (extra hyphen)
  • AC-US1-1 (AC number not zero-padded)
  • ACUS101 (missing hyphens)

Why AC-IDs Matter

The Problem Without AC-IDs

Traditional development without traceability:

Problems:

  • ❌ Can't trace requirement → code → test
  • ❌ Requirements get lost or forgotten
  • ❌ Tests don't validate actual requirements
  • ❌ No way to measure coverage
  • ❌ Audits fail (no proof of implementation)

The Solution With AC-IDs

SpecWeave with complete traceability:

Benefits:

  • ✅ Complete traceability (spec → task → test)
  • ✅ Every requirement tracked
  • ✅ Tests validate actual requirements
  • ✅ Automated coverage measurement
  • ✅ Audit-ready (proof of implementation)

Complete Traceability Flow

Phase 1: Specification (spec.md)

Define requirements with AC-IDs:

### US-001: Basic Login Flow

**As a** user
**I want** to log in with my email and password
**So that** I can access my personalized dashboard

**Acceptance Criteria**:
- [ ] **AC-US1-01**: User can log in with valid credentials (P1, testable)
- [ ] **AC-US1-02**: Invalid credentials show error message (P1, testable)
- [ ] **AC-US1-03**: 5 failed attempts lock account for 15 minutes (P2, testable)

Phase 2: Planning (tasks.md)

Link tasks to AC-IDs:

## T-001: Implement Authentication Service

**AC**: AC-US1-01, AC-US1-02, AC-US1-03

**Test Plan** (BDD format):
- **Given** user with valid credentials → **When** login → **Then** receive JWT token (AC-US1-01)
- **Given** invalid credentials → **When** login → **Then** error message (AC-US1-02)
- **Given** 5 failed attempts → **When** 6th attempt → **Then** account locked (AC-US1-03)

**Test Cases**:
- Unit (`auth.test.ts`):
- validLogin → Validates AC-US1-01
- invalidPassword → Validates AC-US1-02
- rateLimiting → Validates AC-US1-03
- **Overall: 90% coverage**

**Implementation**:
1. Create AuthService.ts class
2. Implement login method (AC-US1-01)
3. Add validation (AC-US1-02)
4. Add rate limiting (AC-US1-03)
5. Write unit tests for all ACs

Phase 3: Implementation (auth.test.ts)

Write tests referencing AC-IDs:

describe('AuthService', () => {
describe('AC-US1-01: Valid login', () => {
test('should authenticate user with valid credentials', async () => {
const result = await authService.login('user@example.com', 'password123');

expect(result.token).toBeDefined();
expect(result.user.email).toBe('user@example.com');
});

test('should return user data on successful login', async () => {
const result = await authService.login('user@example.com', 'password123');

expect(result.user).toMatchObject({
id: expect.any(String),
email: 'user@example.com',
name: 'Test User'
});
});
});

describe('AC-US1-02: Invalid credentials', () => {
test('should reject login with wrong password', async () => {
await expect(
authService.login('user@example.com', 'wrongpassword')
).rejects.toThrow('Invalid email or password');
});

test('should reject login with non-existent email', async () => {
await expect(
authService.login('nonexistent@example.com', 'password123')
).rejects.toThrow('Invalid email or password');
});
});

describe('AC-US1-03: Account lockout', () => {
test('should lock account after 5 failed attempts', async () => {
// Simulate 5 failed attempts
for (let i = 0; i < 5; i++) {
await expect(
authService.login('user@example.com', 'wrongpassword')
).rejects.toThrow();
}

// 6th attempt should show "Account locked" error
await expect(
authService.login('user@example.com', 'correctpassword')
).rejects.toThrow('Account locked for 15 minutes');
});

test('should unlock account after 15 minutes', async () => {
// Lock account
for (let i = 0; i < 5; i++) {
await authService.login('user@example.com', 'wrongpassword').catch(() => {});
}

// Fast-forward time 15 minutes
jest.advanceTimersByTime(15 * 60 * 1000);

// Should be able to login again
const result = await authService.login('user@example.com', 'password123');
expect(result.token).toBeDefined();
});
});
});

Phase 4: Validation (Coverage Report)

Automated coverage report links AC-IDs to tests:

/specweave:check-tests 0008

# Output:
## AC Coverage Report

### US-001: Basic Login Flow

- ✅ **AC-US1-01**: User can log in with valid credentials (P1)
- Covered by 2 tests:
- `auth.test.ts::should authenticate user with valid credentials`
- `auth.test.ts::should return user data on successful login`
- Coverage: 95% (19/20 lines)

- ✅ **AC-US1-02**: Invalid credentials show error message (P1)
- Covered by 2 tests:
- `auth.test.ts::should reject login with wrong password`
- `auth.test.ts::should reject login with non-existent email`
- Coverage: 90% (18/20 lines)

- ✅ **AC-US1-03**: 5 failed attempts lock account for 15 minutes (P2)
- Covered by 2 tests:
- `auth.test.ts::should lock account after 5 failed attempts`
- `auth.test.ts::should unlock account after 15 minutes`
- Coverage: 88% (22/25 lines)

**Overall Coverage**: 3/3 ACs covered (100%)
**Code Coverage**: 90% (59/65 lines)
**Status**: PASS ✅

Traceability Diagram


AC-ID Numbering Rules

Within User Story

ACs are numbered sequentially starting from 01:

### US-001: Basic Login Flow
- AC-US1-01 # First AC
- AC-US1-02 # Second AC
- AC-US1-03 # Third AC

### US-002: Session Management
- AC-US2-01 # Restarts at 01 for new user story
- AC-US2-02
- AC-US2-03
- AC-US2-04
- AC-US2-05

User Story Numbering

User story number is NOT zero-padded (for brevity):

# ✅ Correct (story number not padded, AC number padded)
AC-US1-01
AC-US2-01
AC-US12-01
AC-US105-01

# ❌ Wrong (story number padded)
AC-US001-01
AC-US002-01
AC-US012-01
AC-US105-01

Rationale: User stories rarely exceed 3 digits (US-105), so padding is unnecessary. AC numbers are padded for sorting (AC-US1-01, AC-US1-02, ..., AC-US1-10).

Uniqueness

AC-IDs are unique within a spec through the US prefix:

# SPEC-001: Authentication
US-001: AC-US1-01, AC-US1-02 # Unique within SPEC-001
US-002: AC-US2-01, AC-US2-02 # Unique within SPEC-001

# SPEC-002: User Profile
US-001: AC-US1-01, AC-US1-02 # Same AC-US1-01, but different spec (context makes it unique)

Global Uniqueness: When referencing across specs, include spec ID:

  • SPEC-001::AC-US1-01 (Authentication spec, US-001, AC-01)
  • SPEC-002::AC-US1-01 (User Profile spec, US-001, AC-01)

Practical Examples

Example 1: Simple Feature

Spec:

### US-005: Upload Profile Avatar

**Acceptance Criteria**:
- [ ] **AC-US5-01**: User can select image file (JPG, PNG) (P1, testable)
- [ ] **AC-US5-02**: Image is resized to 200x200px (P1, testable)
- [ ] **AC-US5-03**: Image is uploaded to CDN (P1, testable)
- [ ] **AC-US5-04**: Profile shows new avatar immediately (P2, testable)

Task:

## T-008: Implement Avatar Upload

**AC**: AC-US5-01, AC-US5-02, AC-US5-03, AC-US5-04

**Test Cases**:
- Unit (`avatar.test.ts`): imageResize (AC-US5-02), cdnUpload (AC-US5-03) → 90%
- E2E (`avatar-upload.spec.ts`): completeUploadFlow (all ACs) → 100%

Test:

test('should resize image to 200x200px (AC-US5-02)', async () => {
const result = await avatarService.uploadAvatar(largeImage);
expect(result.dimensions).toEqual({ width: 200, height: 200 });
});

Example 2: Complex Feature with Many ACs

Spec:

### US-012: Advanced Search

**Acceptance Criteria**:
- [ ] **AC-US12-01**: Search by keyword in title (P1, testable)
- [ ] **AC-US12-02**: Search by keyword in content (P1, testable)
- [ ] **AC-US12-03**: Filter by date range (P2, testable)
- [ ] **AC-US12-04**: Filter by category (P2, testable)
- [ ] **AC-US12-05**: Filter by author (P2, testable)
- [ ] **AC-US12-06**: Sort by relevance (default) (P1, testable)
- [ ] **AC-US12-07**: Sort by date (newest first) (P2, testable)
- [ ] **AC-US12-08**: Pagination (20 results per page) (P1, testable)
- [ ] **AC-US12-09**: Search completes in &lt;300ms (p95) (P1, testable)
- [ ] **AC-US12-10**: No results shows helpful message (P2, testable)

Tasks (Split into Multiple):

## T-015: Implement Core Search (AC-US12-01, AC-US12-02, AC-US12-06)
## T-016: Add Search Filters (AC-US12-03, AC-US12-04, AC-US12-05)
## T-017: Add Sorting and Pagination (AC-US12-07, AC-US12-08)
## T-018: Optimize Search Performance (AC-US12-09)
## T-019: Add Empty State (AC-US12-10)

Benefits of AC-IDs

1. Complete Traceability

Requirement (AC-US1-01) → Task (T-001) → Test (auth.test.ts) → Report (✅)

Result: Every requirement has a clear path from spec to validation.

2. Automated Coverage Measurement

/specweave:check-tests 0008

# Automatically reports:
# - Which ACs are tested (✅)
# - Which ACs are missing tests (❌)
# - Which tests cover which ACs
# - Overall coverage percentage

3. Audit Trail

## Audit Report: Feature Implementation

### SPEC-001: Authentication

**US-001: Basic Login Flow**
- AC-US1-01: ✅ Implemented in T-001, tested in auth.test.ts (95% coverage)
- AC-US1-02: ✅ Implemented in T-001, tested in auth.test.ts (90% coverage)
- AC-US1-03: ✅ Implemented in T-001, tested in auth.test.ts (88% coverage)

**Overall**: 3/3 ACs implemented and tested (100%) ✅

**Compliance**: Meets SOC 2 requirements for traceability ✅

4. Gap Detection

# Missing AC detection:
❌ AC-US3-05: Reset link can only be used once
- NOT FOUND in tasks.md
- NOT FOUND in test suite
- **Action**: Implement and test this requirement

❌ AC-US3-06: Rate limiting prevents abuse
- FOUND in tasks.md (T-004)
- NOT FOUND in test suite
- **Action**: Add test for rate limiting

5. Change Impact Analysis

# When requirement changes:
AC-US1-03: Changed from "3 failed attempts" to "5 failed attempts"

# Impact analysis:
- spec.md: Update AC-US1-03 description ✅
- tasks.md: Update T-001 test plan ✅
- auth.test.ts: Update test expectations ✅
- rate-limit.test.ts: Update rate limit config ✅

# All references found and updated ✅

AC-ID Best Practices

1. Reference AC-IDs in Commits

git commit -m "feat: implement login validation (AC-US1-02)

- Add email/password validation
- Return 401 for invalid credentials
- Add unit tests for AC-US1-02

Validates: AC-US1-02"

2. Reference AC-IDs in PR Descriptions

## PR #123: Implement Authentication Service

### Acceptance Criteria Covered

-**AC-US1-01**: Valid login returns JWT token
-**AC-US1-02**: Invalid credentials show error
-**AC-US1-03**: Rate limiting after 5 failed attempts

### Test Coverage

- Unit tests: 90% (27/30 lines)
- Integration tests: 85% (17/20 lines)
- Overall: 88% (44/50 lines)

### Validation

All ACs covered by automated tests. See test report in CI.

3. Reference AC-IDs in Test Names

// ✅ Good: Test name references AC-ID
test('should authenticate valid credentials (AC-US1-01)', async () => {
// ...
});

// ❌ Bad: No AC-ID reference
test('should authenticate user', async () => {
// ...
});

AC-ID Anti-Patterns

1. No AC-IDs

# ❌ Bad: No traceability
- User can log in with valid credentials
- Invalid credentials show error

# ✅ Good: With AC-IDs
- [ ] **AC-US1-01**: User can log in with valid credentials (P1, testable)
- [ ] **AC-US1-02**: Invalid credentials show error message (P1, testable)

2. Inconsistent Format

# ❌ Bad: Inconsistent formats
- AC-001: Login works
- ACUS1-02: Logout works
- AC_US1_03: Reset works

# ✅ Good: Consistent format
- AC-US1-01: Login works
- AC-US1-02: Logout works
- AC-US1-03: Reset works

3. No AC-IDs in Tests

// ❌ Bad: Can't trace test back to requirement
test('should authenticate user', async () => {
// Which AC does this validate?
});

// ✅ Good: Clear traceability
test('should authenticate user with valid credentials (AC-US1-01)', async () => {
// Validates AC-US1-01
});


Summary

AC-ID enables complete traceability in SpecWeave:

  • Format: AC-US{story}-{number} (e.g., AC-US1-01, AC-US12-05)
  • Traceability: spec.md → tasks.md → tests → coverage report
  • Validation: Automated AC coverage checking (/specweave:check-tests)
  • Audit: Clear proof of requirement → implementation → validation
  • Benefits: Complete traceability, automated coverage, audit trail, gap detection

Key Insight: AC-IDs create an unbroken chain of accountability from requirements to tests, ensuring nothing falls through the cracks.