Four gates that were documented but unenforced, plus the flaky test that made a full-suite run untrustworthy. Rust lint/format: CLAUDE.md has required `cargo fmt` and `cargo clippy` before every commit for as long as the rule existed, yet neither ran anywhere in CI — the requirement rested on memory alone. Both now run in build-and-test.yml and build-release.yml. rustfmt and clippy are already baked into the builder image, so nothing is installed at job time. `cargo fmt --all -- --check` is strict immediately (the tree is clean). Clippy is advisory for now: ~51 pre-existing warnings mean `-D warnings` would fail on unrelated work, so the step carries a TODO to flip the flag once the backlog clears. A compile error still fails it, so it is not a no-op. Traceability threshold: MIN_THRESHOLD sat at 50 while real coverage was 86%, so nearly half the matrix could rot before the gate objected. Ratcheted to 82 with the policy written down — it only ever goes up, and is never lowered to make a red build pass. The same figure lives in MIN_COVERAGE_PERCENT so `traces:coverage` gates locally on the same bar, and a test fails if the two drift. Dangling IDs: a TRACES comment could name any well-formed ID and the extractor accepted it silently, so typos and renames that missed a call site passed unnoticed. `bun run traces:validate` cross-checks every traced ID against the table rows in requirements.md and fails with the referencing files listed. It spans UT/IT as well, which the coverage orphan list ignores by design. This currently reports DR-189 and UT-188, which are being defined separately. Flaky offlineCatalog test: the first dynamic import of the service paid ~1s to transform its dependency graph, charged to a test body against vitest's 5s default. Alone it passed; under suite-wide contention it timed out. The import is now warmed at collection time, so no test is timing the compiler — the timeout is deliberately unchanged. The store shim also drops subscribers from module instances discarded by resetModules, which previously leaked across tests.
335 lines
10 KiB
Markdown
335 lines
10 KiB
Markdown
# Requirement Traceability CI/CD Pipeline
|
|
|
|
This document explains the automated requirement traceability validation system for JellyTau.
|
|
|
|
## Overview
|
|
|
|
The CI/CD pipeline automatically validates that code changes are properly traced to requirements. This ensures:
|
|
- ✅ Requirements are implemented with clear traceability
|
|
- ✅ No requirement coverage regressions
|
|
- ✅ Code changes are linked to specific requirements
|
|
- ✅ Quality metrics are tracked over time
|
|
|
|
## Gitea Actions Workflows
|
|
|
|
Traceability validation lives in `.gitea/workflows/traceability-check.yml`:
|
|
|
|
- ✅ Automatic trace extraction
|
|
- ✅ Coverage validation against minimum threshold (82%, ratcheted)
|
|
- ✅ Modified file checking
|
|
- ✅ Artifact preservation
|
|
- ✅ Summary reports
|
|
|
|
**Runs on:** Every push and pull request to `master`/`main`/`develop`
|
|
|
|
A second workflow, `traceability.yml`, previously duplicated this one as a
|
|
"GitHub-compatible alternative". It was removed: CI here is Gitea Actions, and
|
|
its only unique step (PR comments via `actions/github-script`) depended on the
|
|
GitHub REST client, which Gitea does not provide. To add PR comments, post to
|
|
Gitea's `/api/v1/repos/{owner}/{repo}/issues/{index}/comments` from
|
|
`traceability-check.yml` rather than reviving the old file.
|
|
|
|
## What Gets Validated
|
|
|
|
### 1. Trace Extraction
|
|
```bash
|
|
bun run traces:json > traces-report.json
|
|
```
|
|
Extracts all TRACES comments from:
|
|
- TypeScript files (`src/**/*.ts`)
|
|
- Svelte components (`src/**/*.svelte`)
|
|
- Rust code (`src-tauri/src/**/*.rs`)
|
|
- Test files
|
|
|
|
### 2. Coverage Thresholds
|
|
The workflow checks:
|
|
- **Minimum overall coverage:** 82% (`MIN_THRESHOLD`)
|
|
|
|
Denominators are **derived from `docs/requirements.md` at run time** — they are
|
|
never hardcoded here or in the workflow. Run `bun run traces:coverage` for the
|
|
current per-type breakdown; any number written into this document is a snapshot
|
|
that will drift.
|
|
|
|
> **Why this matters.** The workflow used to divide by frozen literals
|
|
> (UR/39, IR/24, DR/48, JA/3, total 114) while `requirements.md` had grown past
|
|
> 200. It reported **158%** coverage, so the 50% threshold was unreachable and
|
|
> the job could not fail regardless of how far coverage dropped. See
|
|
> [specs/traceability-gate-repair.md](specs/traceability-gate-repair.md).
|
|
|
|
Coverage is the *intersection* of traced and defined IDs: an ID that appears in
|
|
a `TRACES:` comment but is not defined in `requirements.md` is reported as
|
|
**orphaned** and does not count toward coverage. UT/IT test identifiers are a
|
|
separate taxonomy and are excluded entirely.
|
|
|
|
The workflow **fails** and blocks merge if coverage drops below the threshold —
|
|
or if it computes above 100%, which can only mean the gate is miscounting.
|
|
|
|
#### Ratchet policy
|
|
|
|
`MIN_THRESHOLD` **only ever goes up.** It is deliberately set a few points below
|
|
the coverage actually achieved (82 against a real 86%), so a genuine regression
|
|
trips it. It previously sat at 50 while true coverage was 86%: nearly half the
|
|
matrix could have rotted before CI objected.
|
|
|
|
When coverage rises durably, raise the threshold to just under the new figure.
|
|
**Never lower it to make a red build pass** — add the missing TRACES comments
|
|
instead. The same number lives in `MIN_COVERAGE_PERCENT` in
|
|
`scripts/extract-traces.ts` (so `bun run traces:coverage` gates locally on the
|
|
same bar); `scripts/extract-traces.test.ts` fails if the two drift apart.
|
|
|
|
### 2b. Dangling requirement IDs
|
|
|
|
```bash
|
|
bun run traces:validate
|
|
```
|
|
|
|
Every ID named by a `TRACES:` comment must be defined as a table row in
|
|
`docs/requirements.md`. The extractor used to accept any well-formed ID
|
|
silently, so a typo or a rename that missed a call site passed unnoticed —
|
|
`DR-189` and `UT-188` were referenced from three source files, defined nowhere,
|
|
for months.
|
|
|
|
This check spans **all six** ID types (UR/IR/DR/JA/UT/IT), unlike the coverage
|
|
`orphaned` list above, which considers only the four requirement types so that
|
|
UT/IT noise cannot bury a real typo in the ratio's reporting. The workflow step
|
|
**fails the build** on any dangling ID and prints each offender with the files
|
|
that reference it.
|
|
|
|
### 3. Modified File Checking
|
|
On pull requests, the workflow:
|
|
1. Detects all changed TypeScript/Svelte/Rust files
|
|
2. Warns if new/modified files lack TRACES comments
|
|
3. Suggests the TRACES format for missing comments
|
|
|
|
## How to Add Traces to New Code
|
|
|
|
When you add new code or modify existing code, include TRACES comments:
|
|
|
|
### TypeScript/Svelte Example
|
|
```typescript
|
|
// TRACES: UR-005, UR-026 | DR-029
|
|
export function handlePlayback() {
|
|
// Implementation...
|
|
}
|
|
```
|
|
|
|
### Rust Example
|
|
```rust
|
|
/// TRACES: UR-005 | DR-001
|
|
pub fn player_state_changed(state: PlayerState) {
|
|
// Implementation...
|
|
}
|
|
```
|
|
|
|
### Test Example
|
|
```rust
|
|
// TRACES: UR-005 | DR-001 | UT-026, UT-027
|
|
#[cfg(test)]
|
|
mod tests {
|
|
// Tests...
|
|
}
|
|
```
|
|
|
|
## TRACES Format
|
|
|
|
```
|
|
TRACES: [UR-###, ...] | [IR-###, ...] | [DR-###, ...] | [JA-###, ...]
|
|
```
|
|
|
|
- `UR-###` - User Requirements (features users see)
|
|
- `IR-###` - Integration Requirements (API/platform integration)
|
|
- `DR-###` - Development Requirements (internal architecture)
|
|
- `JA-###` - Jellyfin API Requirements (Jellyfin API usage)
|
|
|
|
**Examples:**
|
|
- `// TRACES: UR-005` - Single requirement
|
|
- `// TRACES: UR-005, UR-026` - Multiple of same type
|
|
- `// TRACES: UR-005 | DR-029` - Multiple types
|
|
- `// TRACES: UR-005, UR-026 | DR-001, DR-029 | UT-001` - Complex
|
|
|
|
## Workflow Behavior
|
|
|
|
### On Push to Main Branch
|
|
1. ✅ Extracts all traces from code
|
|
2. ✅ Validates coverage is >= 82%
|
|
3. ✅ Generates full traceability report
|
|
4. ✅ Saves report as artifact
|
|
|
|
### On Pull Request
|
|
1. ✅ Extracts all traces
|
|
2. ✅ Validates coverage >= 82%
|
|
3. ✅ Checks modified files for TRACES
|
|
4. ✅ Warns if new code lacks TRACES
|
|
5. ✅ Suggests proper format
|
|
6. ✅ Generates report artifact
|
|
|
|
### Failure Scenarios
|
|
The workflow **fails** (blocks merge) if:
|
|
- Coverage drops below 82%
|
|
- A `TRACES:` comment names an ID `docs/requirements.md` does not define
|
|
- JSON extraction fails
|
|
- Invalid trace format
|
|
|
|
The workflow **warns** (but doesn't block) if:
|
|
- New files lack TRACES comments
|
|
- Coverage drops (but still above threshold)
|
|
|
|
## Viewing Reports
|
|
|
|
### In Gitea Actions UI
|
|
1. Go to **Actions** tab
|
|
2. Click the **Traceability Validation** workflow run
|
|
3. Download **traceability-reports** artifact
|
|
4. View:
|
|
- `traces-report.json` - Raw trace data
|
|
- `docs/traceability.md` - Formatted report
|
|
|
|
### Locally
|
|
```bash
|
|
# Extract current traces
|
|
bun run traces:json | jq '.byType'
|
|
|
|
# Generate full report
|
|
bun run traces:markdown
|
|
cat docs/traceability.md
|
|
```
|
|
|
|
## Coverage Goals
|
|
|
|
### Current Status
|
|
|
|
Run `bun run traces:coverage` — it prints the live figure and exits non-zero
|
|
below threshold. Numbers are deliberately not pinned here; the previous snapshot
|
|
in this section (51%, 56/114) was stale by roughly 100 requirements and was what
|
|
made the broken CI arithmetic look plausible for so long.
|
|
|
|
As of July 2026 overall coverage is ~86% (182/212).
|
|
|
|
### Targets
|
|
- **Short term** (Sprint): Maintain ≥82% overall (the current ratchet)
|
|
- **Medium term** (Month): Reach 70% overall coverage
|
|
- **Long term** (Release): Reach 90% coverage with focus on:
|
|
- IR requirements (API clients)
|
|
- JA requirements (Jellyfin API endpoints)
|
|
- Remaining UR/DR requirements
|
|
|
|
## Improving Coverage
|
|
|
|
### For Missing User Requirements (UR)
|
|
1. Review [README.md](../README.md) for unimplemented features
|
|
2. Add TRACES to code that implements them
|
|
3. Focus on high-priority features (High/Medium priority)
|
|
|
|
### For Missing Integration Requirements (IR)
|
|
1. Add TRACES to Jellyfin API client methods
|
|
2. Add TRACES to platform-specific backends (Android/Linux)
|
|
3. Link to corresponding Jellyfin API endpoints
|
|
|
|
### For Missing Development Requirements (DR)
|
|
1. Add TRACES to UI components in `src/lib/components/`
|
|
2. Add TRACES to composables in `src/lib/composables/`
|
|
3. Add TRACES to player backend in `src-tauri/src/player/`
|
|
|
|
### For Jellyfin API Requirements (JA)
|
|
1. Add TRACES to Jellyfin API wrapper methods
|
|
2. Document which endpoints map to which requirements
|
|
3. Link to Jellyfin API documentation
|
|
|
|
## Example PR Checklist
|
|
|
|
When submitting a pull request:
|
|
|
|
- [ ] All new code has TRACES comments linking to requirements
|
|
- [ ] TRACES format is correct: `// TRACES: UR-001 | DR-002`
|
|
- [ ] Workflow passes (coverage ≥ 82%)
|
|
- [ ] No coverage regressions
|
|
- [ ] Artifact traceability report was generated
|
|
|
|
## Troubleshooting
|
|
|
|
### "Coverage below minimum threshold"
|
|
**Problem:** Workflow fails with coverage < 82%
|
|
|
|
**Solution:**
|
|
1. Run `bun run traces:json` locally
|
|
2. Check which requirements are traced
|
|
3. Add TRACES to untraced code sections
|
|
4. Re-run extraction to verify
|
|
|
|
### "New files without TRACES"
|
|
**Problem:** Workflow warns about new files lacking TRACES
|
|
|
|
**Solution:**
|
|
1. Add TRACES comments to all new code
|
|
2. Format: `// TRACES: UR-001 | DR-002`
|
|
3. Map code to specific requirements from README.md
|
|
4. Re-push
|
|
|
|
### "Invalid JSON format"
|
|
**Problem:** Trace extraction produces invalid JSON
|
|
|
|
**Solution:**
|
|
1. Check for malformed TRACES comments
|
|
2. Run locally: `bun run traces:json`
|
|
3. Look for parsing errors
|
|
4. Fix and retry
|
|
|
|
## Integration with Development
|
|
|
|
### Before Committing
|
|
```bash
|
|
# Check your traces
|
|
bun run traces:json | jq '.byType'
|
|
|
|
# Regenerate report
|
|
bun run traces:markdown
|
|
|
|
# Verify traces syntax
|
|
grep "TRACES:" src/**/*.ts src/**/*.rs
|
|
```
|
|
|
|
### In Your IDE
|
|
Add a file watcher to regenerate traces on save:
|
|
```json
|
|
{
|
|
"fileWatcher.watchPatterns": [
|
|
"src/**/*.ts",
|
|
"src/**/*.svelte",
|
|
"src-tauri/src/**/*.rs"
|
|
],
|
|
"fileWatcher.command": "bun run traces:markdown"
|
|
}
|
|
```
|
|
|
|
### Git Hooks
|
|
Add a pre-push hook to validate traces:
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# .git/hooks/pre-push
|
|
bun run traces:json > /dev/null
|
|
if [ $? -ne 0 ]; then
|
|
echo "❌ Invalid TRACES format"
|
|
exit 1
|
|
fi
|
|
```
|
|
|
|
## References
|
|
|
|
- [Extract Traces Script](../scripts/README.md#extract-tracests)
|
|
- [Requirements Specification](../README.md#requirements-specification)
|
|
- [Traceability Matrix](./traceability.md)
|
|
- [Gitea Actions Documentation](https://docs.gitea.io/en-us/actions/)
|
|
|
|
## Support
|
|
|
|
For issues or questions:
|
|
1. Check this document
|
|
2. Review example traces in `src/lib/stores/`
|
|
3. Check existing TRACES comments for format
|
|
4. Review workflow logs in Gitea Actions
|
|
|
|
---
|
|
|
|
**Last Updated:** 2026-02-13
|