Example
tutorial-113-ai-assistant is a small library application in the
go_nago_ddd1 style. This page follows one requirement through it: lending a book.
1. The requirement
The functional requirement lives in the requirement tree, in the directory of its domain:
// requirements/fun/library/R-LIB-LEND.spec.go
var RLibLend = spec.Declare(spec.Requirement{
ID: "R-LIB-LEND",
Kind: spec.Functional,
Discipline: spec.Business,
Status: spec.Normative,
Title: "Exemplar ausleihen",
Text: "Ein freies Exemplar MUSS an eine namentlich genannte Person ausgeliehen werden können; ist keines frei, MUSS die Ausleihe abgelehnt werden.",
})2. The decision behind it
Lending records the borrower as a plain name, not as a user account. That is not obvious, so it is written down as a decision, together with what it costs:
// requirements/dec/R-DEC-BORROWER.spec.go
var RDecBorrower = spec.Declare(spec.Requirement{
ID: "R-DEC-BORROWER",
Kind: spec.Decision,
Discipline: spec.Business,
Status: spec.Normative,
Title: "Ausleiher werden als Name geführt, nicht als Benutzerkonto",
Text: "LentTo enthält Klartextnamen; es gibt keine Verknüpfung zur Benutzerverwaltung.",
Rationale: "Eine Bibliothek leiht auch an Menschen aus, die kein Konto in diesem System haben …",
Consequences: "Zwei Personen gleichen Namens sind nicht unterscheidbar, und es gibt keine automatische Erinnerung …",
})Three years later, somebody who wants to link borrowers to accounts reads first why it was not done, and what reversing the decision would change.
3. The code
The use case follows the architecture: its own file, a constructor, its own permission:
// app/library/uc_lend_book.go
type LendBook func(subject auth.Subject, req LendRequest) (LendResult, error)
func NewLendBook(repo BookRepository) LendBook {
return func(subject auth.Subject, req LendRequest) (LendResult, error) {
if err := subject.Audit(PermLendBook); err != nil {
return LendResult{}, err
}
// ... refuse if no copy is free, otherwise record the borrower
}
}4. The link
The annotation file next to it states the one fact speclink cannot infer, which requirements this use case was written for, plus a help text for users:
// app/library/uc_lend_book.annotation.go
var _ = spec.For[LendBook](
spec.Satisfies(fun.RLibLend, dec.RDecBorrower),
spec.Help("Gibt ein Exemplar an eine Person heraus. Ist keines frei, wird die Ausleihe abgelehnt — eine Vormerkung gibt es noch nicht."),
)That LendBook is a use case, that PermLendBook guards it and that BookRepository is a repository, speclink
finds out by itself.
5. What is planned, but not built
Reserving a lent-out book is known, but not built yet. The requirement exists with Status: spec.Planned and is
bound to nothing:
// requirements/fun/library/R-LIB-RESERVATION.spec.go
var RLibReservation = spec.Declare(spec.Requirement{
ID: "R-LIB-RESERVATION",
Kind: spec.Functional,
Status: spec.Planned,
Title: "Vormerkung auf ein ausgeliehenes Exemplar",
// ...
})speclink does not demand code for it. In the running application, the AI assistant can therefore answer “not implemented yet” instead of “does not exist”.
6. At run time
The requirement packages are imported by the annotation files and thus linked into the binary. With
cfgspeclink.Enable(cfg), Nago shows them in the admin center, and the assistant of tutorial-113 reads them
through tools. Ask it why borrowers are names and not accounts, and it answers with R-DEC-BORROWER and its
consequences, see Requirements.
What a checked project adds
The tutorial declares requirements and annotations, but the Nago repository does not run speclink verify on its
examples. Running it in the tutorial directory shows what is still missing:
cd example/cmd/tutorial-113-ai-assistant
speclink verify -profile go_nago_ddd1 ./...It ends with 24 findings:
SPEC-V5-020: a normative requirement names no source. Fixed by a source document inrequirements/_sources/andSourceson every normative requirement (SPEC-V5-027reports the missing directory itself).SPEC-V6-120: no test demonstrates the requirement. Fixed by tests which end withspec.Verified(t, …), handed tospeclink evidence.SPEC-V5-035:R-LIB-*lies infun/library/. Fixed by moving the files tofun/lib/or renaming the IDs toR-LIBRARY-*.SPEC-V6-021: the aggregateBookrests on no recorded decision about how it is stored. Fixed by writing a decision requirement which records the choice, e.g. a newR-DEC-STORAGE, and binding it withspec.For[Book](spec.Satisfies(dec.RDecStorage)).SPEC-V6-022: stored fields likeBook.Titletrace to no requirement. Fixed withspec.ForField[Book]("Title", spec.Satisfies(…))for each field.SPEC-V6-090:Bookis persisted, but its shape was never recorded. Fixed byspeclink freeze, or by marking itspec.Draft()while it is still in flux, see stored shapes.
The use cases themselves pass: each names a requirement, and each requirement is implemented. A real project also
keeps speclink.json with {"profile": "go_nago_ddd1"} and speclink.lock in version control.