Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70c5718505 | |||
| 46bd07c792 | |||
| b6d514ee21 | |||
| 7bd8e47241 | |||
| 016039d1a3 | |||
| fc2b020423 |
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"phase": 2,
|
||||
"phase": 3,
|
||||
"stage": "verify",
|
||||
"milestone": "v0.8",
|
||||
"milestone_slug": "coverage-trust-hardening",
|
||||
"phase_role": "execution",
|
||||
"attempts": 0,
|
||||
"updated_at": "2026-08-04T01:10:00Z",
|
||||
"updated_at": "2026-08-04T01:20:00Z",
|
||||
"milestone_complete": false,
|
||||
"next_milestone": null
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
# Phase 3 Verification — v0.8 Coverage & Trust Hardening
|
||||
|
||||
**Phase**: P03 — Requirements-hygiene gate
|
||||
**Milestone**: v0.8
|
||||
**REQ**: REQ-060
|
||||
**Date**: 2026-08-04
|
||||
**Result**: ✅ PASS (all 4 layers)
|
||||
|
||||
## Layer 1 — Structural ✅
|
||||
|
||||
- `go build ./...` PASS
|
||||
- `go vet ./...` PASS
|
||||
- `cmd/verify-reqs/main.go` (~180 LOC, stdlib only) compiles + links
|
||||
- All exports resolve: `verify(roadmapPath, reqsPath) (diff []string, count int, err error)`
|
||||
- No new dependencies
|
||||
|
||||
## Layer 2 — Behavioral ✅
|
||||
|
||||
- `go test ./cmd/verify-reqs/...` PASS (7 golden-file tests: clean, multi-drift, default-args, malformed, missing-file, v0.2-substring-tolerant, real-repo regression)
|
||||
- `make verify-reqs` → exit 0 on the current repo (`✓ 60 requirements consistent with roadmap`)
|
||||
- T03.5 synthetic drift verification: scratch flip of REQ-053 → `make verify-reqs` exit 1 + `REQ-053: status=Pending, expected=Complete (direction=forward)`; revert → exit 0
|
||||
- `go test ./...` PASS (all 16 packages)
|
||||
- Forward + reverse assertions both exercised (golden test `TestVerify_drift` asserts `direction=reverse` for REQ-003)
|
||||
|
||||
## Layer 3 — Security ✅
|
||||
|
||||
- verify-reqs is a static doc-consistency checker — no network, no secrets, no input injection (markdown is parsed with `regexp` over local files only)
|
||||
- `.coreci.yml` step runs in the existing `golang:1.25` container (no new image, no new permissions)
|
||||
- No STRIDE surface added
|
||||
|
||||
## Layer 4 — Quality ✅
|
||||
|
||||
- Testable core (`verify()` function) + thin `main()` — follows the `cmd/orca/main.go` → `run()` pattern from T01.11
|
||||
- Golden-file test fixtures cover the substring-tolerant regex regression (v0.2 header variant)
|
||||
- GRILL condition #4 satisfied: substring-tolerant regex + reverse-direction assertion + scope note (doc-vs-doc only)
|
||||
- P0 issues: none. P1+ issues: none flagged.
|
||||
|
||||
## Requirement Coverage
|
||||
|
||||
| REQ | Status | Evidence |
|
||||
|-----|--------|----------|
|
||||
| REQ-060 | ✅ Complete | `cmd/verify-reqs` (T03.1) + golden tests (T03.2) + `make verify-reqs` (T03.3) + `.coreci.yml` validate hook (T03.4) + synthetic drift verification (T03.5) |
|
||||
|
||||
## GRILL Conditions Check
|
||||
|
||||
- **#4 (verify-reqs regex + reverse direction)**: ✅ substring-tolerant regex matches v0.2's `**COMPLETE (merged to main via v0.3)**` header (golden test `TestVerify_v0_2_substring_tolerant`); reverse-direction assertion implemented + tested; scope note documented in the commit + the verification report.
|
||||
|
||||
## Lessons
|
||||
|
||||
- The two-regex parser (one for REQ rows, one for milestone-complete headers) with substring tolerance is the right shape — a single strict regex would have silently exempted v0.2 (the exact drift the GRILL flagged).
|
||||
- Refactoring `main()` into a testable `verify()` function made golden-file testing trivial (no subprocess orchestration). This mirrors the T01.11 `main()→run()` pattern and should be the house style for all `cmd/` programs.
|
||||
@@ -13,6 +13,8 @@ description: Orca — offline/CLI-first orchestration engine. Full release flow
|
||||
# - gosec (REQ-014, REQ-040) Static analysis for Go security smells
|
||||
# - govulncheck (REQ-014, REQ-027) Offline vuln scan of dependencies
|
||||
# - gitleaks (REQ-039) Pre-commit-style secret scan
|
||||
# v0.8 P03 added a requirements-hygiene stage:
|
||||
# - verify-reqs (REQ-060) ROADMAP COMPLETE ↔ REQUIREMENTS Complete
|
||||
# The `test` pipeline runs with -race (REQ-031).
|
||||
# See docs/security-scanning.md for operator-facing details.
|
||||
|
||||
@@ -27,6 +29,11 @@ pipelines:
|
||||
- gofmt -l .
|
||||
- go vet ./...
|
||||
|
||||
- name: verify-reqs
|
||||
image: golang:1.25
|
||||
commands:
|
||||
- make verify-reqs
|
||||
|
||||
- name: gosec
|
||||
image: golang:1.25
|
||||
commands:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.PHONY: build test test-race lint fmt clean run release version changelog help security-scan
|
||||
.PHONY: build test test-race lint fmt clean run release version changelog help security-scan verify-reqs
|
||||
|
||||
BINARY := bin/orca
|
||||
GOFLAGS := -trimpath
|
||||
@@ -30,6 +30,7 @@ help:
|
||||
@echo " changelog Generate CHANGELOG.md from ---ci--- commit blocks"
|
||||
@echo " release Run scripts/release.sh [VERSION] — build, tar, publish"
|
||||
@echo " security-scan Run gosec+govulncheck+gitleaks (P03, REQ-014/027/039)"
|
||||
@echo " verify-reqs Assert ROADMAP COMPLETE ↔ REQUIREMENTS Complete (REQ-060)"
|
||||
|
||||
build:
|
||||
@mkdir -p bin
|
||||
@@ -98,3 +99,9 @@ release:
|
||||
# in a developer's local environment; CI requires all three).
|
||||
security-scan:
|
||||
./scripts/security_scan.sh
|
||||
|
||||
# verify-reqs asserts ROADMAP milestone COMPLETE ↔ REQUIREMENTS row Complete
|
||||
# consistency (REQ-060). Catches doc-vs-doc drift; code-vs-doc drift is out
|
||||
# of scope (P04 audit). Exits 0 on consistency, 1 with a diff on drift.
|
||||
verify-reqs:
|
||||
go run ./cmd/verify-reqs .ciagent/ROADMAP.md .ciagent/REQUIREMENTS.md
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// reqRowRe captures a REQUIREMENTS.md table row's REQ-ID, Phase cell, and
|
||||
// status in one pass. The leading .* is greedy so it consumes the
|
||||
// Requirement and Priority cells (which may contain markdown-escaped pipes
|
||||
// like `localhost\|linux\|proxmox` — see REQ-049) and backtracks to anchor
|
||||
// the Phase + Status match at the END of the line, where those two columns
|
||||
// always live. The status token is optionally wrapped in markdown bold
|
||||
// (real rows use `**Complete**`; synthetic/future rows may use bare
|
||||
// `Pending`), and may carry trailing notes (e.g. "**Complete** (P01
|
||||
// shipped v0.2.1)") matched by [^|]* before the closing pipe.
|
||||
var reqRowRe = regexp.MustCompile(`^\|\s*(REQ-\d+)\s*\|.*\|\s*([^|]*?)\s*\|\s*\*{0,2}(Complete|Pending)\*{0,2}[^|]*\|\s*$`)
|
||||
|
||||
// milestoneCompleteRe matches a ROADMAP.md milestone header that is marked
|
||||
// COMPLETE. The bold span is substring-tolerant (GRILL #4): it matches
|
||||
// `**COMPLETE**`, `**COMPLETE (merged to main via v0.3)**`, and any future
|
||||
// variant where the word COMPLETE appears inside the bold span, possibly
|
||||
// preceded or followed by non-asterisk text. The milestone version (v0.X)
|
||||
// is captured.
|
||||
var milestoneCompleteRe = regexp.MustCompile(`^##\s*Milestone\s+(v0\.\d+):.*—\s*\*\*[^*]*\bCOMPLETE\b[^*]*\*\*`)
|
||||
|
||||
// phaseRe extracts the milestone version from a REQUIREMENTS Phase cell such
|
||||
// as `v0.7 P1`, `**v0.2 P01**`, `v0.2 P01–P04`, or bare `v0.7`. The cell may
|
||||
// contain multiple milestone refs separated by `/` or `–`; each is extracted.
|
||||
var phaseTokenRe = regexp.MustCompile(`v0\.\d+`)
|
||||
|
||||
// reqRow holds a parsed REQUIREMENTS.md row.
|
||||
type reqRow struct {
|
||||
id string
|
||||
phase string // raw Phase cell (e.g. "v0.7 P1", "**v0.2 P01 / v0.3 P02**")
|
||||
status string // "Complete" or "Pending"
|
||||
}
|
||||
|
||||
// milestoneVersions returns the distinct v0.X milestones referenced in the
|
||||
// phase cell (e.g. "v0.7 P1" → ["v0.7"]; "v0.2 P01 / v0.3 P02" →
|
||||
// ["v0.2","v0.3"]).
|
||||
func (r reqRow) milestoneVersions() []string {
|
||||
matches := phaseTokenRe.FindAllString(r.phase, -1)
|
||||
seen := map[string]bool{}
|
||||
var out []string
|
||||
for _, m := range matches {
|
||||
if !seen[m] {
|
||||
seen[m] = true
|
||||
out = append(out, m)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func main() {
|
||||
roadmapPath := ".ciagent/ROADMAP.md"
|
||||
reqsPath := ".ciagent/REQUIREMENTS.md"
|
||||
if len(os.Args) > 1 {
|
||||
roadmapPath = os.Args[1]
|
||||
}
|
||||
if len(os.Args) > 2 {
|
||||
reqsPath = os.Args[2]
|
||||
}
|
||||
diff, count, err := verify(roadmapPath, reqsPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "verify-reqs: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
if len(diff) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "requirements drift detected (%d):\n", len(diff))
|
||||
for _, line := range diff {
|
||||
fmt.Fprintln(os.Stderr, line)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("✓ %d requirements consistent with roadmap\n", count)
|
||||
}
|
||||
|
||||
// verify parses the ROADMAP and REQUIREMENTS markdown and returns a diff
|
||||
// listing of any drift. On success diff is nil and count is the number of
|
||||
// consistent REQ rows. A non-nil error signals a parse/read failure (not
|
||||
// drift); drift is reported via the diff slice.
|
||||
func verify(roadmapPath, reqsPath string) (diff []string, count int, err error) {
|
||||
completeMilestones, err := parseRoadmap(roadmapPath)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("parse roadmap %q: %w", roadmapPath, err)
|
||||
}
|
||||
rows, err := parseRequirements(reqsPath)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("parse requirements %q: %w", reqsPath, err)
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return nil, 0, fmt.Errorf("no REQ rows found in %s", reqsPath)
|
||||
}
|
||||
|
||||
type driftEntry struct {
|
||||
id string
|
||||
current string
|
||||
want string
|
||||
dir string // "forward" or "reverse"
|
||||
}
|
||||
var drifts []driftEntry
|
||||
|
||||
for _, r := range rows {
|
||||
milestones := r.milestoneVersions()
|
||||
anyComplete := false
|
||||
for _, m := range milestones {
|
||||
if completeMilestones[m] {
|
||||
anyComplete = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// Forward assertion: a REQ whose milestone is COMPLETE in ROADMAP
|
||||
// must be marked Complete in REQUIREMENTS.
|
||||
if anyComplete && r.status != "Complete" {
|
||||
drifts = append(drifts, driftEntry{r.id, r.status, "Complete", "forward"})
|
||||
}
|
||||
// Reverse assertion (GRILL #4): a REQ marked Complete in
|
||||
// REQUIREMENTS must reference at least one milestone ROADMAP marks
|
||||
// COMPLETE. If all referenced milestones are NOT complete (or no
|
||||
// milestone is referenced), that is premature-Complete drift.
|
||||
if r.status == "Complete" && !anyComplete {
|
||||
drifts = append(drifts, driftEntry{r.id, r.status, "Pending (milestone not COMPLETE in ROADMAP)", "reverse"})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(drifts, func(i, j int) bool { return drifts[i].id < drifts[j].id })
|
||||
for _, d := range drifts {
|
||||
diff = append(diff, fmt.Sprintf(" %s: status=%s, expected=%s (direction=%s)", d.id, d.current, d.want, d.dir))
|
||||
}
|
||||
|
||||
consistent := len(rows) - len(drifts)
|
||||
return diff, consistent, nil
|
||||
}
|
||||
|
||||
func parseRoadmap(path string) (map[string]bool, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
complete := map[string]bool{}
|
||||
sc := bufio.NewScanner(f)
|
||||
sc.Buffer(make([]byte, 1024*1024), 1024*1024)
|
||||
for sc.Scan() {
|
||||
line := sc.Text()
|
||||
m := milestoneCompleteRe.FindStringSubmatch(line)
|
||||
if m != nil {
|
||||
complete[m[1]] = true
|
||||
}
|
||||
}
|
||||
if err := sc.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return complete, nil
|
||||
}
|
||||
|
||||
func parseRequirements(path string) ([]reqRow, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
var rows []reqRow
|
||||
sc := bufio.NewScanner(f)
|
||||
sc.Buffer(make([]byte, 1024*1024), 1024*1024)
|
||||
for sc.Scan() {
|
||||
line := sc.Text()
|
||||
m := reqRowRe.FindStringSubmatch(line)
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, reqRow{id: m[1], phase: strings.TrimSpace(m[2]), status: m[3]})
|
||||
}
|
||||
if err := sc.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Tests run with cwd = package dir (cmd/verify-reqs), so `testdata/...`
|
||||
// paths resolve relative to the package. The real-repo tests use a
|
||||
// repoRoot helper to locate `.ciagent/...`.
|
||||
|
||||
func repoRoot(t *testing.T) string {
|
||||
t.Helper()
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("getwd: %v", err)
|
||||
}
|
||||
return filepath.Join(wd, "..", "..")
|
||||
}
|
||||
|
||||
// case (1): clean pair → no drift.
|
||||
func TestVerify_clean(t *testing.T) {
|
||||
diff, count, err := verify(
|
||||
filepath.Join("testdata", "roadmap_clean.md"),
|
||||
filepath.Join("testdata", "requirements_clean.md"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(diff) != 0 {
|
||||
t.Fatalf("expected no drift, got:\n%s", strings.Join(diff, "\n"))
|
||||
}
|
||||
if count != 5 {
|
||||
t.Fatalf("expected 5 consistent rows, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
// case (2)+(3): drift pair → all drifts reported, both directions.
|
||||
func TestVerify_drift(t *testing.T) {
|
||||
diff, _, err := verify(
|
||||
filepath.Join("testdata", "roadmap_drift.md"),
|
||||
filepath.Join("testdata", "requirements_drift.md"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(diff) != 4 {
|
||||
t.Fatalf("expected 4 drifts (REQ-002 forward, REQ-003 reverse, REQ-004 forward, REQ-005 forward), got %d:\n%s",
|
||||
len(diff), strings.Join(diff, "\n"))
|
||||
}
|
||||
joined := strings.Join(diff, "\n")
|
||||
for _, want := range []string{"REQ-002", "REQ-003", "REQ-004", "REQ-005"} {
|
||||
if !strings.Contains(joined, want) {
|
||||
t.Errorf("diff missing %s:\n%s", want, joined)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(joined, "direction=reverse") {
|
||||
t.Errorf("expected a reverse-direction drift, got:\n%s", joined)
|
||||
}
|
||||
if !strings.Contains(joined, "direction=forward") {
|
||||
t.Errorf("expected a forward-direction drift, got:\n%s", joined)
|
||||
}
|
||||
}
|
||||
|
||||
// case (4): missing args → defaults resolve to the repo's .ciagent/ files.
|
||||
// Runs the program as a subprocess from the repo root.
|
||||
func TestVerify_defaultArgs(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("subprocess test skipped in -short mode")
|
||||
}
|
||||
root := repoRoot(t)
|
||||
cmd := exec.Command("go", "run", "./cmd/verify-reqs")
|
||||
cmd.Dir = root
|
||||
out := &strings.Builder{}
|
||||
cmd.Stdout = out
|
||||
cmd.Stderr = out
|
||||
if err := cmd.Run(); err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
t.Fatalf("verify-reqs on real repo exited %d (should be 0): %s",
|
||||
exitErr.ExitCode(), out.String())
|
||||
}
|
||||
t.Fatalf("go run failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// case (5): malformed markdown (no REQ rows) → clear error, not silent pass.
|
||||
func TestVerify_malformed(t *testing.T) {
|
||||
_, _, err := verify(
|
||||
filepath.Join("testdata", "roadmap_clean.md"),
|
||||
filepath.Join("testdata", "requirements_malformed.md"),
|
||||
)
|
||||
if err == nil {
|
||||
t.Fatal("expected error on malformed (no REQ rows) requirements, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "no REQ rows") {
|
||||
t.Errorf("expected 'no REQ rows' error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// case (6): missing file → clear error, not silent pass.
|
||||
func TestVerify_missingFile(t *testing.T) {
|
||||
_, _, err := verify(
|
||||
filepath.Join("testdata", "roadmap_clean.md"),
|
||||
filepath.Join("testdata", "does_not_exist.md"),
|
||||
)
|
||||
if err == nil {
|
||||
t.Fatal("expected error on missing file, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "does_not_exist.md") {
|
||||
t.Errorf("expected error to mention the missing file, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// GRILL #4 golden test: the v0.2-style
|
||||
// `**COMPLETE (merged to main via v0.3)**` header MUST be recognized as a
|
||||
// complete milestone by the substring-tolerant regex. The drift fixture's
|
||||
// roadmap carries exactly this header on its v0.2 line, and the drift
|
||||
// fixture's REQ-002 (v0.2 P1, Pending) would be silently skipped if the
|
||||
// regex regressed to the exact `\*\*COMPLETE\*\*` form. TestVerify_drift
|
||||
// already asserts REQ-002 is flagged forward-drift; this test makes the
|
||||
// intent explicit and guards against a regex regression.
|
||||
func TestVerify_v02SubstringTolerantHeader(t *testing.T) {
|
||||
diff, _, err := verify(
|
||||
filepath.Join("testdata", "roadmap_drift.md"),
|
||||
filepath.Join("testdata", "requirements_drift.md"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
// REQ-002 references milestone v0.2, whose header uses the
|
||||
// `**COMPLETE (merged to main via v0.3)**` variant. If the regex
|
||||
// regressed, v0.2 would not be marked complete and REQ-002 (Pending)
|
||||
// would NOT be flagged as forward drift.
|
||||
found := false
|
||||
for _, d := range diff {
|
||||
if strings.Contains(d, "REQ-002") && strings.Contains(d, "direction=forward") {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("REQ-002 forward drift not reported — substring-tolerant regex may have regressed; diff:\n%s",
|
||||
strings.Join(diff, "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the actual repo passes (regression guard for the real docs).
|
||||
func TestVerify_realRepo(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("real-repo test skipped in -short mode")
|
||||
}
|
||||
root := repoRoot(t)
|
||||
diff, count, err := verify(
|
||||
filepath.Join(root, ".ciagent", "ROADMAP.md"),
|
||||
filepath.Join(root, ".ciagent", "REQUIREMENTS.md"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("verify on real repo errored: %v", err)
|
||||
}
|
||||
if len(diff) != 0 {
|
||||
t.Fatalf("real repo has requirements drift (should be clean after SPECIFY):\n%s",
|
||||
strings.Join(diff, "\n"))
|
||||
}
|
||||
if count <= 0 {
|
||||
t.Fatalf("real repo reported %d consistent rows (expected > 0)", count)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Requirements: Clean Fixture
|
||||
|
||||
| ID | Requirement | Priority | Phase | Status |
|
||||
|----|-------------|----------|-------|--------|
|
||||
| REQ-001 | Foundation req | High | v0.1 P1 | **Complete** |
|
||||
| REQ-002 | Multi-node with escaped pipes (kind\|os) | Medium | **v0.2 P1** | **Complete** (shipped v0.2.1) |
|
||||
| REQ-003 | Scheduling req | Low | v0.3 P1 | **Complete** |
|
||||
| REQ-004 | Future req spanning phases | Low | v0.2 P1 / v0.3 P2 | **Complete** (multi-phase) |
|
||||
| REQ-005 | Pending future req | Low | v0.9 P1 | Pending |
|
||||
@@ -0,0 +1,9 @@
|
||||
# Requirements: Drift Fixture
|
||||
|
||||
| ID | Requirement | Priority | Phase | Status |
|
||||
|----|-------------|----------|-------|--------|
|
||||
| REQ-001 | Foundation req (clean) | High | v0.1 P1 | **Complete** |
|
||||
| REQ-002 | Multi-node req (forward drift: milestone COMPLETE but row Pending) | Medium | **v0.2 P1** | Pending |
|
||||
| REQ-003 | Scheduling req (reverse drift: row Complete but milestone NOT complete) | Low | v0.9 P1 | **Complete** |
|
||||
| REQ-004 | Multi-phase with range (forward drift: spans COMPLETE v0.2 + non-COMPLETE v0.9) | Low | v0.2 P1–P2 | Pending |
|
||||
| REQ-005 | Second forward drift (v0.3 COMPLETE, row Pending) | Low | v0.3 P1 | Pending |
|
||||
@@ -0,0 +1,8 @@
|
||||
# Requirements: Malformed Fixture
|
||||
|
||||
This file has no valid REQ rows, just prose and a broken table.
|
||||
|
||||
| ID | Requirement | Priority | Phase | Status |
|
||||
|----|-------------|----------|-------|--------|
|
||||
| NOT-A-REQ | broken row | High | v0.1 | **Complete** |
|
||||
| REQ-999 | missing status cell | High | v0.1 |
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
# Roadmap: Clean Fixture
|
||||
|
||||
## Milestone v0.1: Foundation — **COMPLETE**
|
||||
|
||||
- [x] Phase 1
|
||||
|
||||
## Milestone v0.2: Networking — **COMPLETE (merged to main via v0.3)**
|
||||
|
||||
- [x] Phase 8
|
||||
- [x] Phase 9
|
||||
|
||||
## Milestone v0.3: Scheduling — **COMPLETE**
|
||||
|
||||
- [x] Phase 1
|
||||
|
||||
## Milestone v0.9: Future Work
|
||||
|
||||
Not yet shipped.
|
||||
|
||||
- [ ] Phase 1
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
# Roadmap: Drift Fixture
|
||||
|
||||
## Milestone v0.1: Foundation — **COMPLETE**
|
||||
|
||||
- [x] Phase 1
|
||||
|
||||
## Milestone v0.2: Networking — **COMPLETE (merged to main via v0.3)**
|
||||
|
||||
- [x] Phase 8
|
||||
- [x] Phase 9
|
||||
|
||||
## Milestone v0.3: Scheduling — **COMPLETE**
|
||||
|
||||
- [x] Phase 1
|
||||
|
||||
## Milestone v0.9: Future Work
|
||||
|
||||
Not yet shipped.
|
||||
|
||||
- [ ] Phase 1
|
||||
Reference in New Issue
Block a user