Reconcile docs with reality; fix module graph, token compare, health #1

Open
kami wants to merge 216 commits from webui-and-audit-reconciliation into master
3 changed files with 165 additions and 1 deletions
Showing only changes of commit a5d361b59f - Show all commits
+29
View File
@@ -277,3 +277,32 @@ func assertBefore(t *testing.T, ctx, first, second string) {
t.Fatalf("%q must precede %q:\n%s", first, second, ctx)
}
}
// Ingested acceptance criteria must reach the rendered task in order, and an
// absent one must say so rather than be silently omitted.
func TestAcceptanceCriteriaRenderInOrder(t *testing.T) {
in := input()
in.Task.Acceptance = []string{"labelled score does not regress", "targeted cases improve"}
in.Intent.Task = in.Task
got, err := Build(in)
if err != nil {
t.Fatal(err)
}
first := strings.Index(got.Task, "- labelled score does not regress")
second := strings.Index(got.Task, "- targeted cases improve")
if first < 0 || second < 0 {
t.Fatalf("acceptance criteria missing from rendered task:\n%s", got.Task)
}
if first > second {
t.Fatal("acceptance criteria rendered out of order")
}
in.Task.Acceptance = nil
in.Intent.Task = in.Task
got, err = Build(in)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(got.Task, "## Acceptance\n\nNot stated.") {
t.Fatalf("absent acceptance did not render Not stated:\n%s", got.Task)
}
}
+57 -1
View File
@@ -13,6 +13,7 @@ import (
"io"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"sync"
@@ -297,6 +298,57 @@ func (g Gitea) client() *http.Client {
}
return http.DefaultClient
}
// acceptanceHeading opens the one recognized acceptance section. The
// convention is deliberately tiny: two spellings, markdown heading only. An
// ingestion that infers acceptance from arbitrary prose eventually invents
// requirements, and a fabricated acceptance criterion outranks every human
// decision below it in the authority order.
var acceptanceHeading = regexp.MustCompile(`(?i)^#{1,6}[ \t]*acceptance([ \t]+criteria)?[ \t]*:?[ \t]*$`)
// acceptanceItem matches a bullet or checklist item and captures its text.
var acceptanceItem = regexp.MustCompile(`^[ \t]*[-*+][ \t]+(?:\[[ xX]\][ \t]*)?(.*)$`)
var markdownHeading = regexp.MustCompile(`^#{1,6}[ \t]+`)
// splitAcceptance separates an issue body into the description and the
// acceptance criteria the issue stated for itself. Everything from the
// recognized heading to the next heading leaves the description, so a criterion
// is never also read as instruction prose. An empty result is not an ingestion
// failure: a task with no stated acceptance renders "Not stated." and the frame
// phase is where that gets resolved, through the decision-request path.
func splitAcceptance(body string) (string, []string) {
lines := strings.Split(body, "\n")
start := -1
for i, l := range lines {
if acceptanceHeading.MatchString(strings.TrimRight(l, " \t\r")) {
start = i
break
}
}
if start < 0 {
return body, nil
}
var acceptance []string
end := len(lines)
for i := start + 1; i < len(lines); i++ {
line := strings.TrimRight(lines[i], " \t\r")
if markdownHeading.MatchString(line) {
end = i
break
}
m := acceptanceItem.FindStringSubmatch(line)
if m == nil {
continue
}
if item := strings.TrimSpace(m[1]); item != "" {
acceptance = append(acceptance, item)
}
}
kept := append(append([]string{}, lines[:start]...), lines[end:]...)
return strings.Trim(strings.Join(kept, "\n"), "\n"), acceptance
}
func (g Gitea) event(issue giteaIssue, source, project string) domain.Event {
caps := []string{}
for _, l := range issue.Labels {
@@ -306,7 +358,11 @@ func (g Gitea) event(issue giteaIssue, source, project string) domain.Event {
// authority in every rendered context. Dropping it here made every
// Gitea-sourced task run on its title alone, with "Acceptance: Not stated."
// Found on the first burn-in task, 2026-08-26.
p := map[string]any{"source": source, "external_id": strconv.Itoa(issue.Number), "project": project, "title": issue.Title, "description": issue.Body, "capability": caps}
description, acceptance := splitAcceptance(issue.Body)
p := map[string]any{"source": source, "external_id": strconv.Itoa(issue.Number), "project": project, "title": issue.Title, "description": description, "capability": caps}
if len(acceptance) > 0 {
p["acceptance"] = acceptance
}
b, _ := json.Marshal(p)
return domain.Event{ID: domain.NewID(), TaskID: domain.NewID(), Type: "TaskCreated", Version: 1, Payload: b, Surface: string(authz.System)}
}
+79
View File
@@ -1,6 +1,7 @@
package provider
import (
"encoding/json"
"orchestra/internal/domain"
"strings"
"testing"
@@ -22,3 +23,81 @@ func TestJSONLRejectsMalformedLine(t *testing.T) {
t.Fatalf("n=%d err=%v", n, err)
}
}
// A Gitea issue states its own acceptance under one recognized heading. Before
// this, every Gitea-sourced task rendered "Acceptance: Not stated." no matter
// what the body said, because nothing on that path ever set the field.
func TestGiteaIssueAcceptanceSection(t *testing.T) {
body := "improve ambiguous attribution.\n\n## acceptance\n- labelled score does not regress\n- targeted cases improve\n"
e := Gitea{}.event(giteaIssue{Number: 7, Title: "fix attribution", Body: body}, "gitea", "p")
var p struct {
Description string `json:"description"`
Acceptance []string `json:"acceptance"`
}
if err := json.Unmarshal(e.Payload, &p); err != nil {
t.Fatal(err)
}
if p.Description != "improve ambiguous attribution." {
t.Fatalf("description=%q, want the prose without the acceptance section", p.Description)
}
want := []string{"labelled score does not regress", "targeted cases improve"}
if len(p.Acceptance) != len(want) {
t.Fatalf("acceptance=%q, want %q", p.Acceptance, want)
}
for i := range want {
if p.Acceptance[i] != want[i] {
t.Fatalf("acceptance[%d]=%q, want %q (order is preserved)", i, p.Acceptance[i], want[i])
}
}
}
// A body with no recognized section yields no acceptance, and that is correct
// rather than an ingestion failure: the task renders "Not stated." and the
// frame phase resolves it through the decision-request path. Inferring
// criteria from arbitrary prose would invent requirements that outrank every
// human decision beneath them.
func TestGiteaIssueWithoutAcceptanceSectionStaysEmpty(t *testing.T) {
body := "just do the thing.\n\n## notes\n- not an acceptance criterion\n"
e := Gitea{}.event(giteaIssue{Number: 8, Title: "t", Body: body}, "gitea", "p")
var p map[string]any
if err := json.Unmarshal(e.Payload, &p); err != nil {
t.Fatal(err)
}
if _, ok := p["acceptance"]; ok {
t.Fatalf("acceptance=%v, want none", p["acceptance"])
}
if p["description"] != body {
t.Fatalf("description=%q, want the body unchanged", p["description"])
}
}
func TestSplitAcceptanceConventions(t *testing.T) {
for _, tc := range []struct {
name string
body string
desc string
want []string
}{
{"checkboxes", "do it.\n\n## Acceptance\n- [ ] thing a works\n- [x] behaviour b unchanged\n", "do it.", []string{"thing a works", "behaviour b unchanged"}},
{"acceptance criteria spelling", "do it.\n\n### Acceptance Criteria\n* one\n", "do it.", []string{"one"}},
{"section ends at the next heading", "do it.\n\n## acceptance\n- one\n\n## notes\n- not acceptance\n", "do it.\n\n## notes\n- not acceptance", []string{"one"}},
{"empty items ignored", "do it.\n\n## acceptance\n-\n- [ ]\n- real\n", "do it.", []string{"real"}},
{"prose inside the section is not an item", "do it.\n\n## acceptance\nthese are the criteria:\n- one\n", "do it.", []string{"one"}},
{"heading only", "do it.\n\n## acceptance\n", "do it.", nil},
} {
t.Run(tc.name, func(t *testing.T) {
desc, got := splitAcceptance(tc.body)
if desc != tc.desc {
t.Fatalf("description=%q, want %q", desc, tc.desc)
}
if len(got) != len(tc.want) {
t.Fatalf("acceptance=%q, want %q", got, tc.want)
}
for i := range tc.want {
if got[i] != tc.want[i] {
t.Fatalf("acceptance[%d]=%q, want %q", i, got[i], tc.want[i])
}
}
})
}
}