Compare commits

..

13 Commits

Author SHA1 Message Date
lilleman c400ed7908 Make great documentation goal 1.2, and renumber goals 13 to 16 as 12 to 15 wherever they are cited
Tests / vet + fmt + tests (pull_request) Has been cancelled
Tests / Known vulnerabilities in this Go version and the required modules (pull_request) Has been cancelled
Tests / Release from CHANGELOG.md (pull_request) Has been cancelled
Tests / packages (pull_request) Has been cancelled
2026-10-14 22:55:20 +02:00
lilleman 79dafa38f0 Make goal 12 great documentation, state how the README meets it, and plan the README's reorganisation for 0.2.0
Tests / vet + fmt + tests (pull_request) Has been cancelled
Tests / Known vulnerabilities in this Go version and the required modules (pull_request) Has been cancelled
Tests / Release from CHANGELOG.md (pull_request) Has been cancelled
Tests / packages (pull_request) Has been cancelled
2026-10-14 22:50:30 +02:00
lilleman 5b1169f9a5 File nested-template gaps: a name bound through a name, a repeat range and a JSON-escape transform
Tests / vet + fmt + tests (pull_request) Has been cancelled
Tests / Known vulnerabilities in this Go version and the required modules (pull_request) Has been cancelled
Tests / Release from CHANGELOG.md (pull_request) Has been cancelled
Tests / packages (pull_request) Has been cancelled
2026-10-14 22:27:54 +02:00
lilleman 16b5e0767c File relative date bounds and exact-count choices as plan items
Tests / vet + fmt + tests (pull_request) Has been cancelled
Tests / Known vulnerabilities in this Go version and the required modules (pull_request) Has been cancelled
Tests / Release from CHANGELOG.md (pull_request) Has been cancelled
Tests / packages (pull_request) Has been cancelled
2026-10-14 22:05:26 +02:00
lilleman 7739c09163 Check the plan before filing into it, keep filed items in order, name the filer's tests in the gate, and record the merge over a worse ruling 2026-10-14 13:51:22 +02:00
lilleman 16afcff09c Test that the filer keeps its items in order and exits 2 on a plan it cannot read 2026-10-14 13:51:22 +02:00
lilleman 5aca8a1fe8 Record the comprehension check: paired ruling against f90b4d6 rules worse 2026-10-14 13:51:22 +02:00
lilleman 5a6928750c Give the vulnerability filer a folder of its own, and leave its item's score to rank_todo.py 2026-10-14 13:51:22 +02:00
lilleman 15965e7a77 State the filer's contract with the plan where it is read, name the step an outage fails, and mark an unfixed vulnerability by name 2026-10-14 13:51:22 +02:00
lilleman 6a7da08002 Answer architecture round 4: 0 🔴, 0 🟡; an entry with no summary files without a bare colon 2026-10-14 13:51:22 +02:00
lilleman 9049f90cc5 Answer architecture round 3: 0 🔴, 2 🟡; a plan-only change runs the test that pins the plan's shape, and a module record carries its own path 2026-10-14 13:51:22 +02:00
lilleman 2d86f4f0c0 Answer architecture round 2: 1 🔴, 3 🟡; read the report and edit the plan in two functions, propose only while no pull request is open, and test filing into the repository's own todo.md 2026-10-14 13:51:22 +02:00
lilleman 273dd45128 Answer architecture round 1: 0 🔴, 5 🟡; a job of its own that only releases wait for, filing each vulnerability atop the plan by a pull request, a recorded decision, one gate list, and Renovate on the go run tools 2026-10-14 13:51:22 +02:00
14 changed files with 608 additions and 221 deletions
+38 -9
View File
@@ -14,6 +14,7 @@ permissions:
jobs: jobs:
test: test:
name: vet + fmt + tests name: vet + fmt + tests
if: github.event_name != 'schedule'
runs-on: ubuntu-26.04 runs-on: ubuntu-26.04
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
@@ -22,7 +23,6 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Relevant changes - name: Relevant changes
id: changes id: changes
if: github.event_name != 'schedule'
run: | run: |
if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
git fetch --no-tags origin "$GITHUB_BASE_REF" git fetch --no-tags origin "$GITHUB_BASE_REF"
@@ -30,7 +30,7 @@ jobs:
else else
base='${{ github.event.before }}' base='${{ github.event.before }}'
fi fi
if git diff --quiet "$base"...HEAD -- '*.go' go.mod go.sum Dockerfile .dockerignore .github data release-tooling testdata README.md; then if git diff --quiet "$base"...HEAD -- '*.go' go.mod go.sum Dockerfile .dockerignore .github data release-tooling testdata vulnerability-filing README.md todo.md; then
echo "code=false" >> "$GITHUB_OUTPUT" echo "code=false" >> "$GITHUB_OUTPUT"
else else
echo "code=true" >> "$GITHUB_OUTPUT" echo "code=true" >> "$GITHUB_OUTPUT"
@@ -42,7 +42,6 @@ jobs:
fi fi
echo "base=$base" >> "$GITHUB_OUTPUT" echo "base=$base" >> "$GITHUB_OUTPUT"
- name: CHANGELOG entry for data changes - name: CHANGELOG entry for data changes
if: github.event_name != 'schedule'
run: | run: |
base='${{ steps.changes.outputs.base }}' base='${{ steps.changes.outputs.base }}'
if ! git diff --quiet "$base"...HEAD -- data testdata/shipped_shape.txt && git diff --quiet "$base"...HEAD -- CHANGELOG.md; then if ! git diff --quiet "$base"...HEAD -- data testdata/shipped_shape.txt && git diff --quiet "$base"...HEAD -- CHANGELOG.md; then
@@ -59,11 +58,6 @@ jobs:
- name: Lowest supported Go - name: Lowest supported Go
if: ${{ !cancelled() && steps.changes.outputs.code == 'true' }} if: ${{ !cancelled() && steps.changes.outputs.code == 'true' }}
run: docker build --build-arg GO_VERSION=1.22.12 --target portable . run: docker build --build-arg GO_VERSION=1.22.12 --target portable .
# Runs on every event, and daily, since a vulnerability is published without a change
# here; `--no-cache-filter` keeps a cached pass from hiding one.
- name: Known vulnerabilities in this Go release and the module
if: ${{ !cancelled() }}
run: docker build --no-cache-filter vulnerabilities --target vulnerabilities .
- name: Package repositories, signed with throwaway keys - name: Package repositories, signed with throwaway keys
if: ${{ !cancelled() && steps.changes.outputs.packaging == 'true' }} if: ${{ !cancelled() && steps.changes.outputs.packaging == 'true' }}
run: | run: |
@@ -72,9 +66,44 @@ jobs:
docker build -f release-tooling/package-repos.Dockerfile --target checks \ docker build -f release-tooling/package-repos.Dockerfile --target checks \
--secret id=gpg,env=PACKAGES_GPG_KEY --secret id=rsa,env=PACKAGES_RSA_KEY . --secret id=gpg,env=PACKAGES_GPG_KEY --secret id=rsa,env=PACKAGES_RSA_KEY .
vulnerabilities:
name: Known vulnerabilities in this Go version and the required modules
runs-on: ubuntu-26.04
timeout-minutes: 10
permissions:
actions: write
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Fetch govulncheck's report from vuln.go.dev
run: docker build --no-cache-filter vulnerabilities --target vulnerability-report --output "$RUNNER_TEMP/vulnerabilities" .
- name: File each one in todo.md
id: file
run: python3 vulnerability-filing/file_vulnerabilities.py "$RUNNER_TEMP/vulnerabilities/report.json" todo.md
- name: Propose the filed items
if: failure() && steps.file.outcome == 'failure' && github.ref == 'refs/heads/main'
env:
GH_TOKEN: ${{ github.token }}
run: |
if git diff --quiet todo.md || [ "$(gh pr list --head vulnerabilities --state open --json number --jq length)" != 0 ]; then
exit 0
fi
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git switch -C vulnerabilities
git commit -qam "File the known vulnerabilities govulncheck reports"
git push -qf "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" vulnerabilities
gh pr create --head vulnerabilities --title "File the known vulnerabilities govulncheck reports" \
--body "govulncheck reports known vulnerabilities in the Go version or a required module, and no release is cut until they are gone. This files them atop the next release in todo.md, scored 0.0 until rank_todo.py scores them; each item names the bump that fixes it."
# A pull request the workflow's token opens starts no workflow.
gh workflow run test.yml --ref vulnerabilities
release: release:
name: Release from CHANGELOG.md name: Release from CHANGELOG.md
needs: test needs: [test, vulnerabilities]
if: github.event_name == 'push' if: github.event_name == 'push'
runs-on: ubuntu-26.04 runs-on: ubuntu-26.04
permissions: permissions:
+2 -1
View File
@@ -1,6 +1,6 @@
# Rules # Rules
- Go runs only through `docker compose run --rm <test|vet|fmt|build|cyclo|generate>`; the merge gate is `docker build .`, CI's changelog check, its known-vulnerabilities check and, for a change the README's Development section lists, its package-repository checks. - Go runs only through `docker compose run --rm <test|vet|fmt|build|cyclo|generate|vulncheck>`; the merge gate is what the README's Development section lists.
- Tests first, in their own commit; the implementation follows in the next. A re-pin of seeded output or of `testdata/shipped_shape.txt` is its own commit. - Tests first, in their own commit; the implementation follows in the next. A re-pin of seeded output or of `testdata/shipped_shape.txt` is its own commit.
- A change under `data/` or to the shape pin adds its `CHANGELOG.md` entry under `Unreleased` in the same PR, and so does a change to a flag, an exit code, an exported name, a fence, a builtin or the lowest Go; what is major is the README's Versioning table. - A change under `data/` or to the shape pin adds its `CHANGELOG.md` entry under `Unreleased` in the same PR, and so does a change to a flag, an exit code, an exported name, a fence, a builtin or the lowest Go; what is major is the README's Versioning table.
- One-line commit messages: no ticket prefix, no repo name, no authorship trailers. - One-line commit messages: no ticket prefix, no repo name, no authorship trailers.
@@ -124,6 +124,7 @@ In [docs/decisions.md](docs/decisions.md):
- A country with no open address register covering it whole ships its places and postal codes without streets - A country with no open address register covering it whole ships its places and postal codes without streets
- The apt, dnf and apk repositories are static files on GitHub Pages, holding the latest release only - The apt, dnf and apk repositories are static files on GitHub Pages, holding the latest release only
- The image is pushed to GHCR and copied to Docker Hub, whose name the README shows - The image is pushed to GHCR and copied to Docker Hub, whose name the README shows
- A known vulnerability in the Go version or a required module blocks a release, even in code fejkdata never calls, and blocks no merge
# Scoring run # Scoring run
+1 -1
View File
@@ -14,7 +14,7 @@ Every shipped dataset, its source, its licence and the attribution it asks for.
| `sv_SE/first-name.tsv`, `last-name.tsv` | [SCB](https://www.scb.se/) names with at least two bearers, 31 December 2022 | CC0 1.0 | "Källa: SCB" | `data-import/names-se.py` | | `sv_SE/first-name.tsv`, `last-name.tsv` | [SCB](https://www.scb.se/) names with at least two bearers, 31 December 2022 | CC0 1.0 | "Källa: SCB" | `data-import/names-se.py` |
| `en_US/first-name.tsv` | [SSA](https://www.ssa.gov/oact/babynames/) baby names, births 1930 to 2020, through [hackerb9/ssa-baby-names](https://github.com/hackerb9/ssa-baby-names) | public domain | none required | `data-import/names-us.py` | | `en_US/first-name.tsv` | [SSA](https://www.ssa.gov/oact/babynames/) baby names, births 1930 to 2020, through [hackerb9/ssa-baby-names](https://github.com/hackerb9/ssa-baby-names) | public domain | none required | `data-import/names-us.py` |
| `en_US/last-name.tsv` | Census Bureau surnames occurring 100 or more times, 2010 | public domain | none required | `data-import/names-us.py` | | `en_US/last-name.tsv` | Census Bureau surnames occurring 100 or more times, 2010 | public domain | none required | `data-import/names-us.py` |
| `sv_SE/sex.tsv`, `sv_SE/birth-number.tsv`, `sv_SE/title.tsv`, `en_US/sex.tsv`, `en_US/title.tsv`, `misc/car.tsv` | curated; Skatteverket's test birth numbers are facts, and `car.tsv` waits for an international source (goal 14) | — | — | — | | `sv_SE/sex.tsv`, `sv_SE/birth-number.tsv`, `sv_SE/title.tsv`, `en_US/sex.tsv`, `en_US/title.tsv`, `misc/car.tsv` | curated; Skatteverket's test birth numbers are facts, and `car.tsv` waits for an international source (goal 13) | — | — | — |
| `misc/currency.tsv` | [datasets/currency-codes](https://github.com/datasets/currency-codes); symbols from [Unicode CLDR](https://github.com/unicode-org/cldr) `en.xml` and `root.xml` | PDDL 1.0; [Unicode License v3](https://www.unicode.org/license.txt) | CLDR: "Copyright © 1991-2025 Unicode, Inc. Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the United States and other countries." | `data-import/currency.py` | | `misc/currency.tsv` | [datasets/currency-codes](https://github.com/datasets/currency-codes); symbols from [Unicode CLDR](https://github.com/unicode-org/cldr) `en.xml` and `root.xml` | PDDL 1.0; [Unicode License v3](https://www.unicode.org/license.txt) | CLDR: "Copyright © 1991-2025 Unicode, Inc. Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the United States and other countries." | `data-import/currency.py` |
| `misc/httpmethod.tsv` | [IANA HTTP Method Registry](https://www.iana.org/assignments/http-methods/) | [public domain](https://www.iana.org/help/licensing-terms) | none required | `data-import/httpmethod.py` | | `misc/httpmethod.tsv` | [IANA HTTP Method Registry](https://www.iana.org/assignments/http-methods/) | [public domain](https://www.iana.org/help/licensing-terms) | none required | `data-import/httpmethod.py` |
| `misc/httpstatus.tsv` | [IANA HTTP Status Code Registry](https://www.iana.org/assignments/http-status-codes/) | [public domain](https://www.iana.org/help/licensing-terms) | none required | `data-import/httpstatus.py` | | `misc/httpstatus.tsv` | [IANA HTTP Status Code Registry](https://www.iana.org/assignments/http-status-codes/) | [public domain](https://www.iana.org/help/licensing-terms) | none required | `data-import/httpstatus.py` |
+11 -1
View File
@@ -14,12 +14,21 @@ RUN go vet ./... && \
FROM golang:${GO_VERSION} AS vulnerabilities FROM golang:${GO_VERSION} AS vulnerabilities
WORKDIR /app WORKDIR /app
COPY . . COPY . .
RUN go run golang.org/x/vuln/cmd/govulncheck@v1.8.0 -scan module RUN go run golang.org/x/vuln/cmd/govulncheck@v1.8.0 -scan module -format json > /report.json
FROM scratch AS vulnerability-report
COPY --from=vulnerabilities /report.json /
FROM python:3.14.8-slim AS release-tooling FROM python:3.14.8-slim AS release-tooling
COPY release-tooling/ /release-tooling/ COPY release-tooling/ /release-tooling/
RUN python3 -m unittest discover -s /release-tooling -p '*_test.py' && touch /tested RUN python3 -m unittest discover -s /release-tooling -p '*_test.py' && touch /tested
FROM python:3.14.8-slim AS vulnerability-filing
COPY vulnerability-filing/ /vulnerability-filing/
# The filer's test files an item into the plan itself.
COPY todo.md /todo.md
RUN python3 -m unittest discover -s /vulnerability-filing -p '*_test.py' && touch /tested
FROM goreleaser/goreleaser:v2.18.3 AS packaging FROM goreleaser/goreleaser:v2.18.3 AS packaging
WORKDIR /check WORKDIR /check
COPY release-tooling/goreleaser.yaml ./ COPY release-tooling/goreleaser.yaml ./
@@ -28,6 +37,7 @@ RUN git init -q && git remote add origin https://github.com/larvit/fejkdata.git
FROM portable FROM portable
# BuildKit skips a stage nothing copies from. # BuildKit skips a stage nothing copies from.
COPY --from=release-tooling /tested /tmp/release-tooling-tested COPY --from=release-tooling /tested /tmp/release-tooling-tested
COPY --from=vulnerability-filing /tested /tmp/vulnerability-filing-tested
COPY --from=packaging /tested /tmp/packaging-tested COPY --from=packaging /tested /tmp/packaging-tested
RUN unformatted="$(gofmt -l .)"; test -z "$unformatted" || \ RUN unformatted="$(gofmt -l .)"; test -z "$unformatted" || \
{ echo "unformatted files:"; echo "$unformatted"; exit 1; } { echo "unformatted files:"; echo "$unformatted"; exit 1; }
+44 -24
View File
@@ -417,9 +417,9 @@ carry sub-fields — `misc.currency.symbol`, `misc.territory.alpha2`,
Where a script under [`data-import/`](data-import) reads a source, the facts come from Where a script under [`data-import/`](data-import) reads a source, the facts come from
that source, and the script drops a row only by a rule it states; elsewhere a small hand-written that source, and the script drops a row only by a rule it states; elsewhere a small hand-written
set ships, and [`todo.md`](todo.md) carries the step that replaces it (goal 14). Copy set ships, and [`todo.md`](todo.md) carries the step that replaces it (goal 13). Copy
that states no fact, such as `word` and `sentence`, stays hand-written. Where a category that states no fact, such as `word` and `sentence`, stays hand-written. Where a category
has no path that draws only values reaching nothing real (goal 16), write a fixed value such has no path that draws only values reaching nothing real (goal 15), write a fixed value such
as `example.com` into the template; `todo.md` plans the path. as `example.com` into the template; `todo.md` plans the path.
`misc.httpmethod`, `misc.protocol` and `misc.port` are IANA's registries. `misc.httpmethod`, `misc.protocol` and `misc.port` are IANA's registries.
@@ -980,7 +980,7 @@ advertises no such path; read that level whole.
### Performance ### Performance
The target is about a microsecond per value (goal 13). The target is about a microsecond per value (goal 12).
A source with an [index](#data), as the shipped set has, loads nothing in `New`: a A source with an [index](#data), as the shipped set has, loads nothing in `New`: a
category loads on the first `Fake`, `FakeRecord`, `FakeStruct` or template that reads category loads on the first `Fake`, `FakeRecord`, `FakeStruct` or template that reads
@@ -1053,6 +1053,7 @@ Where the project is heading; the sections before Audience document what ships t
1. **The best fake-data tool there is, and the one developers reach for.** 1. **The best fake-data tool there is, and the one developers reach for.**
1. You can install it the way you install your other tools, and people hear about 1. You can install it the way you install your other tools, and people hear about
it where developers read. it where developers read.
2. **Great documentation.**
2. **Enough data ships built in to fill a real test database: the CLI carries all of it, a 2. **Enough data ships built in to fill a real test database: the CLI carries all of it, a
library imports what it needs, and nothing is fetched at run time.** library imports what it needs, and nothing is fetched at run time.**
1. Each locale covers the common categories: names, addresses, phone numbers, ids, 1. Each locale covers the common categories: names, addresses, phone numbers, ids,
@@ -1117,16 +1118,12 @@ Where the project is heading; the sections before Audience document what ships t
1. In the library, the caller supplies everything that can change the output: data, 1. In the library, the caller supplies everything that can change the output: data,
seed and date. seed and date.
11. **No unnecessary runtime dependencies** — use standard library by default. 11. **No unnecessary runtime dependencies** — use standard library by default.
12. **The docs are a map of the template language.** 12. **It is fast enough that you never notice it with the shipped data.**
1. Every feature has its own heading.
2. Every example runs as a test and shows its output.
3. Each rule is stated in one place.
13. **It is fast enough that you never notice it with the shipped data.**
1. A render's cost grows only with the data it reaches. 1. A render's cost grows only with the data it reaches.
14. **Strongly prioritize data sourced from good online sources** 13. **Strongly prioritize data sourced from good online sources**
15. **The most common data comes first,** such as names, addresses, phone numbers and 14. **The most common data comes first,** such as names, addresses, phone numbers and
dates. dates.
16. **Values look real by default. You can also ask for values that can never reach 15. **Values look real by default. You can also ask for values that can never reach
anything real,** such as a domain nobody can register. anything real,** such as a domain nobody can register.
## Layout ## Layout
@@ -1172,6 +1169,7 @@ data-import/ the scripts that rebuild each sourced table (see DATA-LICENSES.m
docs/ the decision log, the register research behind the shipped data, the survey of other libraries, panels' scores and rulings on how readable the code is, and what each chunk cost an agent to read docs/ the decision log, the register research behind the shipped data, the survey of other libraries, panels' scores and rulings on how readable the code is, and what each chunk cost an agent to read
release-tooling/ the release CI publishes from the changelog heading, and the binaries, packages, image and package repositories built for it release-tooling/ the release CI publishes from the changelog heading, and the binaries, packages, image and package repositories built for it
testdata/ the pinned shipped shape (see Versioning) testdata/ the pinned shipped shape (see Versioning)
vulnerability-filing/ the script CI runs to file each known vulnerability govulncheck reports as a `todo.md` item
``` ```
## Development ## Development
@@ -1207,26 +1205,33 @@ docker compose run --rm --user root --entrypoint chown test -R "$(id -u):$(id -g
``` ```
Every pull request runs `docker build .` against both the latest and the lowest Every pull request runs `docker build .` against both the latest and the lowest
supported Go, and must pass before it can be merged — unless it changes none of supported Go, and must pass before it can be merged — unless it changes none of the
the files the build and its tests read, nor the workflow itself, in which case files the build and its tests read, nor the workflow itself, in which case it's skipped.
it's skipped. That build runs vet, complexity, the format check, the Go tests and the That build runs vet, complexity, the format check, the Go tests, the release tooling's
release tooling's tests; the rest of the gate is the changelog check, which CI runs tests and the vulnerability filer's tests, which file an item into `todo.md` itself, so
against the PR base, and the package-repository checks below. Run `docker build .` a change to the plan's shape can fail it; the rest of the gate is the changelog check,
locally before pushing. The lowest Go builds the `portable` stage, which skips the format which CI runs against the PR base, and the package-repository checks below. Run `docker
check and the release tooling's tests: build .` locally before pushing. The lowest Go builds the `portable` stage, which skips
the format check and both Python test suites:
```sh ```sh
docker build . # latest docker build . # latest
docker build --build-arg GO_VERSION=1.22.12 --target portable . # lowest supported docker build --build-arg GO_VERSION=1.22.12 --target portable . # lowest supported
GO_VERSION=1.22.12 docker compose run --rm test # the same tests, without the image build GO_VERSION=1.22.12 docker compose run --rm test # the same tests, without the image build
docker build --no-cache-filter vulnerabilities --target vulnerabilities . # known vulnerabilities
``` ```
The last line runs `govulncheck` over the Go release the gate builds with and the module's A known vulnerability in the Go version the gate builds with, or in a module fejkdata
requirements, and fails on any known vulnerability, whether or not fejkdata calls it: the requires, holds the release and no merge. CI checks on every pull request, every push to
release binaries carry that Go release, and image scanners flag it. CI runs it on every pull `main`, and daily. On `main` and in the daily run, CI files each vulnerability as a
request and push, and daily, and a release waits for it. A failure is fixed by raising `security` item atop the next release in `todo.md`, through a pull request from the branch
`GO_VERSION`, which Renovate opens a pull request for. `vulnerabilities` unless one is open; each item names the version to raise to. When the
step "Fetch govulncheck's report from vuln.go.dev" fails, vuln.go.dev did not answer:
rerun it. When GitHub pauses the daily run after 60 days with no activity, re-enable it
under Actions. The same check:
```sh
docker compose run --rm vulncheck
```
A change to Go code, `release-tooling/`, the `Dockerfile`, `.dockerignore`, the licence A change to Go code, `release-tooling/`, the `Dockerfile`, `.dockerignore`, the licence
files or either workflow also runs the package-repository checks. They build the release files or either workflow also runs the package-repository checks. They build the release
@@ -1305,6 +1310,21 @@ replaces what it uploaded before. For a tag older than the latest release it lea
tap, the bucket, the image's `latest` tags and the repositories alone, since each holds one tap, the bucket, the image's `latest` tags and the repositories alone, since each holds one
version. version.
### Documentation
How the README serves goal 1.2:
- The top shows the CLI and the library side by side, each a short quickstart rendering
the same value.
- A table of the shipped data follows: every path, an example value, and what makes it
valid or where it comes from.
- Recipes show common tasks end to end.
- Every feature has its own heading, and the docs are a map of the template language.
- Every example runs as a test and shows its output.
- Each rule is stated in one place.
- Edge cases and a category's caveats sit in the reference sections, below what a first
reader needs.
## License ## License
MIT — see [LICENSE](LICENSE). Forked from [github.com/Timewave-AB/fakes](https://github.com/Timewave-AB/fakes). MIT — see [LICENSE](LICENSE). Forked from [github.com/Timewave-AB/fakes](https://github.com/Timewave-AB/fakes).
+4
View File
@@ -35,6 +35,10 @@ services:
<<: *go <<: *go
command: go run github.com/fzipp/gocyclo/cmd/gocyclo@v0.6.0 -over 14 -ignore _test . command: go run github.com/fzipp/gocyclo/cmd/gocyclo@v0.6.0 -over 14 -ignore _test .
vulncheck:
<<: *go
command: go run golang.org/x/vuln/cmd/govulncheck@v1.8.0 -scan module
build: build:
<<: *go <<: *go
command: go build ./... command: go build ./...
+38
View File
@@ -682,3 +682,41 @@ Ruling: same
|---|---|---|---|---|---| |---|---|---|---|---|---|
| Mid A | same | same | same | same | same | | Mid A | same | same | same | same | same |
| Inherited architect | same | same | same | same | same | | Inherited architect | same | same | same | same | same |
## 2026-10-14T11:34:34Z, PR #225 at f734c9e, against f90b4d6
Ruling: worse
| Seat | Navigation | Locality | Shape | Self-sufficiency | Overall |
|---|---|---|---|---|---|
| Mid A | same | worse | same | same | same |
| Inherited architect | same | worse | same | same | worse |
| Maintainability senior | same | worse | same | worse | worse |
| Junior A | same | worse | same | worse | worse |
Mid A, decided by:
- Locality: `Dockerfile:26` `vulnerability-filing`: the filer's test reads the real todo.md inside docker build ., so a plan edit can turn the Go gate red
- Locality: `vulnerability-filing/file_vulnerabilities.py:24` `ROW`: the filer splices todo.md as text, assuming its row shape, the Next ID marker and the Details heading
- Locality: `.github/workflows/test.yml:106` `release`: a release now also waits on vuln.go.dev's answer, visible only in a needs list
Inherited architect, decided by:
- Locality, Overall: `Dockerfile:26` `vulnerability-filing`: the plan is copied into the merge-gate build, so a planning edit that changes the table's shape fails docker build .
- Locality: `vulnerability-filing/file_vulnerabilities.py:101` `file_items`: edits todo.md as text; a missing Next ID crashes with exit 1, which reads like a vulnerability already planned
- Locality: `.github/workflows/test.yml:69` `vulnerabilities`: whether a release happens depends on vuln.go.dev that day, and every PR shows a red job while one is known
Maintainability senior, decided by:
- Locality: `vulnerability-filing/file_vulnerabilities.py:101` `file_items`: finds the release by an unnamed regex group, never checks the Next ID match, and splices Details by a replace that does nothing when the heading is missing
- Locality, Self-sufficiency: `.github/workflows/test.yml:83` `File each one in todo.md`: one exit code holds the release and triggers the proposal, which must tell filed, planned and crashed apart by a git diff
- Locality: `Dockerfile:26` `vulnerability-filing`: copies todo.md into the gate build, and the path filter lists todo.md
- Self-sufficiency: `README.md:1213` "Development": says the build runs the release tooling's tests and leaves out the filer's
- Self-sufficiency: `vulnerability-filing/file_vulnerabilities.py:2` module docstring: builds on rank_todo.py, which the repository does not hold
Junior A, decided by:
- Locality, Overall: `Dockerfile:26` `vulnerability-filing`: the Go merge gate copies todo.md and files an item into it, so editing the plan can turn the build red
- Locality: `.github/workflows/test.yml:106` `release`: whether a push to main releases also depends on vuln.go.dev's answer that day
- Self-sufficiency: `vulnerability-filing/file_vulnerabilities.py:24` `ROW`: the regex and the Details splice encode todo.md's layout, which the script never shows
- Self-sufficiency: `README.md:1213` "Development": the gate paragraph leaves out the filer's tests while AGENTS.md now defers to it
+29 -8
View File
@@ -171,7 +171,7 @@ so no path repeats it. Valid until a locale's lowered name clashes with another'
2026-10-03, a restructure the maintainer approved; widened to any source on 2026-10-09. 2026-10-03, a restructure the maintainer approved; widened to any source on 2026-10-09.
Goal 13: `New` pays nothing for a category a run never reaches. A manifest, Goal 12: `New` pays nothing for a category a run never reaches. A manifest,
`.fejkdata.json` at a source's root, may carry an index: each category's parent table and `.fejkdata.json` at a source's root, may carry an index: each category's parent table and
the paths `List` advertises below it. A category of an indexed source loads with every the paths `List` advertises below it. A category of an indexed source loads with every
category it references and its whole table family, and binds as a whole load does, so category it references and its whole table family, and binds as a whole load does, so
@@ -446,7 +446,7 @@ cost.
## The performance gate asserts allocations, not wall-clock time ## The performance gate asserts allocations, not wall-clock time
2026-09-03, Lilleman auf Larv. Goal 13. 2026-09-03, Lilleman auf Larv. Goal 12.
`AllocsPerRun` is deterministic across machines, so a 10% ceiling does not flake under `AllocsPerRun` is deterministic across machines, so a 10% ceiling does not flake under
CI load, while time varies with the machine and its neighbours. A rendering slowdown CI load, while time varies with the machine and its neighbours. A rendering slowdown
@@ -459,7 +459,7 @@ heap through `pickKey.under`, holds at its baseline with no margin: from a basel
## Rows live in a TSV, the shape in JSON ## Rows live in a TSV, the shape in JSON
2026-09-17, Lilleman auf Larv. Goals 8 and 13. 2026-09-17, Lilleman auf Larv. Goals 8 and 12.
`New` allocates once per node, so a register of thirty thousand rows written as JSON `New` allocates once per node, so a register of thirty thousand rows written as JSON
objects would cost it a second; a TSV is one allocation whose cells are substrings, and objects would cost it a second; a TSV is one allocation whose cells are substrings, and
@@ -579,7 +579,7 @@ generators of their own.
2026-09-17, Lilleman auf Larv. 2026-09-17, Lilleman auf Larv.
Goals 10 and 13: a path that fails moves no seeded stream, at the cost of one draw-free Goals 10 and 12: a path that fails moves no seeded stream, at the cost of one draw-free
walk per call, compiling its steps into a stack buffer of 16. Valid while that walk walk per call, compiling its steps into a stack buffer of 16. Valid while that walk
costs little against the draw it guards. costs little against the draw it guards.
@@ -607,7 +607,7 @@ them; a data package imports only the packages it reads.
## The default embed holds every Swedish postort the import can place, with all its streets, and, of the US places that hold most of a postal code's land, every state capital, every place of 100,000 or more and a population-weighted tenth, each with a quarter of its streets, at least ten ## The default embed holds every Swedish postort the import can place, with all its streets, and, of the US places that hold most of a postal code's land, every state capital, every place of 100,000 or more and a population-weighted tenth, each with a quarter of its streets, at least ten
2026-10-11, Lilleman auf Larv; revised the same day, after a tenth of the streets gave 2026-10-11, Lilleman auf Larv; revised the same day, after a tenth of the streets gave
Göteborg 163 and Stockholm 37. Goals 2 and 14: ten streets per place put 500 Stockholm Göteborg 163 and Stockholm 37. Goals 2 and 13: ten streets per place put 500 Stockholm
customers on ten streets, and US places of 25,000 or more left out every town and customers on ten streets, and US places of 25,000 or more left out every town and
village. `geo-us.py` draws with Efraimidis and Spirakis's weighted sampling. It draws village. `geo-us.py` draws with Efraimidis and Spirakis's weighted sampling. It draws
only among places holding most of some ZCTA's land, since no other place can carry a only among places holding most of some ZCTA's land, since no other place can carry a
@@ -620,7 +620,7 @@ else by a name no other place in its state carries. No census-designated place s
so Honolulu is the one capital missing. Every Swedish street is 174k rows; a quarter so Honolulu is the one capital missing. Every Swedish street is 174k rows; a quarter
of the US streets 475k. On 2026-10-11 the embed grew from 5.2 MB to 16 MB, and a CLI of the US streets 475k. On 2026-10-11 the embed grew from 5.2 MB to 16 MB, and a CLI
run printing the first `en_US.address` took 145 ms, `sv_SE.address` 95 ms and run printing the first `en_US.address` took 145 ms, `sv_SE.address` 95 ms and
`misc.territory` 3 ms, within goal 13. `--place-share`, `--street-share` and `misc.territory` 3 ms, within goal 12. `--place-share`, `--street-share` and
`--min-streets` on the import scripts build another set. Valid while the first reach of `--min-streets` on the import scripts build another set. Valid while the first reach of
a country stays unnoticed beside a test run. a country stays unnoticed beside a test run.
@@ -748,7 +748,7 @@ frequency it is read, as `misc.timezone` reads GeoNames populations and
2026-09-24, Lilleman auf Larv. 2026-09-24, Lilleman auf Larv.
The weight is the population GeoNames records in the zone's cities of 15,000 or more, The weight is the population GeoNames records in the zone's cities of 15,000 or more,
floored at 15,000, which goal 16 asks for: over 300 seeded draws of floored at 15,000, which goal 15 asks for: over 300 seeded draws of
`misc.territory[US].timezone`, the four zones most Americans live in took 278 where an `misc.territory[US].timezone`, the four zones most Americans live in took 278 where an
even weight gave them 44, and `America/Indiana/Petersburg`, a town of 2,400, fell from 17 even weight gave them 44, and `America/Indiana/Petersburg`, a town of 2,400, fell from 17
to 0. Valid while a draw weighted this way lands where people live. to 0. Valid while a draw weighted this way lands where people live.
@@ -855,7 +855,7 @@ the direct chain is the one a reader can predict from the tables' parents.
2026-09-30, larv-review on the comprehension round the maintainer approved on 2026-09-29; 2026-09-30, larv-review on the comprehension round the maintainer approved on 2026-09-29;
replaces "The path walks are separate loops". replaces "The path walks are separate loops".
Goals 3 and 13: `drawSteps` draws every path from its steps, compiled when templates resolve, by Goals 3 and 12: `drawSteps` draws every path from its steps, compiled when templates resolve, by
`compiledPath` or, for a caller's path, by `callerPathSteps` into a stack buffer. Both take each `compiledPath` or, for a caller's path, by `callerPathSteps` into a stack buffer. Both take each
step through `compileStep`, and differ only at a choice: `compiledPath` walks every variant, step through `compileStep`, and differ only at a choice: `compiledPath` walks every variant,
`callerPathSteps` the first. On 2026-09-29 one loop over all three walks, switching on a mode field, `callerPathSteps` the first. On 2026-09-29 one loop over all three walks, switching on a mode field,
@@ -989,3 +989,24 @@ Hub, so a Docker Hub failure holds up no other channel. GHCR also serves CI on s
runners, which Docker Hub's per-IP limit on anonymous pulls blocks. Leaving Docker Hub runners, which Docker Hub's per-IP limit on anonymous pulls blocks. Leaving Docker Hub
costs each user an edit of every `docker run` line and pinned script that names the bare costs each user an edit of every `docker run` line and pinned script that names the bare
`larvit/fejkdata`. `larvit/fejkdata`.
## A known vulnerability in the Go version or a required module blocks a release, even in code fejkdata never calls, and blocks no merge
2026-10-14, decided by the maintainer after Docker Hub flagged v0.1.0's image. Serves goal
1.1. `govulncheck -scan module` fails on any known vulnerability in the Go version the gate
builds with or in a module fejkdata requires, even one fejkdata never calls: the release
binaries carry that Go version, and image scanners flag it whatever the code reaches. It
runs on every pull request and push and daily, as a job the release job needs but branch
protection does not require. So a vulnerability published in Go, or an outage of
vuln.go.dev, holds the release and leaves unrelated pull requests mergeable. Each
vulnerability is filed as a `security` item atop the next release in `todo.md`, by a pull
request CI opens, so the plan names it before anyone reads a red run.
`vulnerability-filing/file_vulnerabilities.py` writes that item in `todo.md`'s shape with a
Score of 0.0, since CI cannot reach `rank_todo.py`, which scores it on the next commit to the
plan; a test files one into the repository's own `todo.md`. When the next release has no item left and waits to be cut,
`todo.md` names no version for it, so the item lands in the release after; the maintainer
moves it. Where no Go version fixes a vulnerability yet, govulncheck has no ignore list: the
maintainer drops the job from the release's `needs` until one does, and records why here.
The paired ruling against f90b4d6 found the project reads worse for it: the gate now reads
`todo.md`, and a release waits on vuln.go.dev. The maintainer merged it anyway, as the cost
of filing into the plan. Valid while the binaries are built from the gate's Go version.
+1 -1
View File
@@ -90,7 +90,7 @@ Recommendation: embed SCB 2022 xlsx-derived lists (top-N per sheet with counts)
## 3. Phone ## 3. Phone
- Source: PTS "The Swedish numbering plan for telephony according to ITU-T E.164", 2024-01-08 (verified, text extracted): https://pts.se/globalassets/globala-block/nummertillstand/the-swedish-numbering-plan-for-telephony-according-to-itu---2024-01-08.pdf. Swedish "nrplansammanstallning-2026-02-04.pdf" and https://nummer.pts.se/NbrPlanSearch are behind a Radware bot check (both curl and Playwright blocked). No licence stated by PTS anywhere found; pts.se says contact pts@pts.se for data. - Source: PTS "The Swedish numbering plan for telephony according to ITU-T E.164", 2024-01-08 (verified, text extracted): https://pts.se/globalassets/globala-block/nummertillstand/the-swedish-numbering-plan-for-telephony-according-to-itu---2024-01-08.pdf. Swedish "nrplansammanstallning-2026-02-04.pdf" and https://nummer.pts.se/NbrPlanSearch are behind a Radware bot check (both curl and Playwright blocked). No licence stated by PTS anywhere found; pts.se says contact pts@pts.se for data.
- Reserved for fiction, PTS "Telefonnummer till böcker och filmer" (verified 2026-09-21; curl and Playwright are Radware-blocked, a rendering fetcher gets through): https://www.pts.se/internet-och-telefoni/telefonnummer-och-adressering/telefonnummer-till-bocker-och-filmer/ — mobile 070-1740605–1740699, geographic 08-46500400–46500499 (Stockholm), 031-3900600–3900699 (Göteborg), 040-6280400–6280499 (Malmö), 0980-319200–319299 (Kiruna). 495 numbers PTS holds back so they reach nobody, which is goal 16's never-reaching path for `sv_SE.phone`. Same licence status as the numbering plan: none stated, facts only, cite PTS. The mobile block fits the shipped `{a} {b} {c}` grouping (`174 06 05`–`99`), as do 031 and 040; Stockholm's 8 subscriber digits and Kiruna's 6 need a format of their own, and 0980 is not among the 20 shipped area codes. - Reserved for fiction, PTS "Telefonnummer till böcker och filmer" (verified 2026-09-21; curl and Playwright are Radware-blocked, a rendering fetcher gets through): https://www.pts.se/internet-och-telefoni/telefonnummer-och-adressering/telefonnummer-till-bocker-och-filmer/ — mobile 070-1740605–1740699, geographic 08-46500400–46500499 (Stockholm), 031-3900600–3900699 (Göteborg), 040-6280400–6280499 (Malmö), 0980-319200–319299 (Kiruna). 495 numbers PTS holds back so they reach nobody, which is goal 15's never-reaching path for `sv_SE.phone`. Same licence status as the numbering plan: none stated, facts only, cite PTS. The mobile block fits the shipped `{a} {b} {c}` grouping (`174 06 05`–`99`), as do 031 and 040; Stockholm's 8 subscriber digits and Kiruna's 6 need a format of their own, and 0980 is not among the 20 shipped area codes.
- 264 geographic area codes (PDF text yields 263 "Area code for" rows; one likely split across a page break — flag). Area code = trunk "0" + NDC of 1–3 digits (08 Stockholm, 031 Göteborg, 040 Malmö, 0480 Kalmar). Geographic N(S)N (NDC + subscriber) max 9 digits, min 7 (2-digit NDC) or 8 (3-digit NDC); Stockholm min 7. Each row in the PDF gives NDC, max/min length, "Area code for <name>", so a `{ndc, name}` list can be built from it. - 264 geographic area codes (PDF text yields 263 "Area code for" rows; one likely split across a page break — flag). Area code = trunk "0" + NDC of 1–3 digits (08 Stockholm, 031 Göteborg, 040 Malmö, 0480 Kalmar). Geographic N(S)N (NDC + subscriber) max 9 digits, min 7 (2-digit NDC) or 8 (3-digit NDC); Stockholm min 7. Each row in the PDF gives NDC, max/min length, "Area code for <name>", so a `{ndc, name}` list can be built from it.
- Mobile: NDC 70, 72, 73, 76, 79 — N(S)N exactly 9 digits, i.e. 07X-XXX XX XX (10 digits with trunk 0). Others: 71 mobile broadband/M2M (13 digits), 74 paging, 75 personal numbering, 77 shared cost, 20 freephone, 10 location-independent (10 AXX XX XX, A=1–8), 378 M2M fixed (10 digits). - Mobile: NDC 70, 72, 73, 76, 79 — N(S)N exactly 9 digits, i.e. 07X-XXX XX XX (10 digits with trunk 0). Others: 71 mobile broadband/M2M (13 digits), 74 paging, 75 personal numbering, 77 shared cost, 20 freephone, 10 location-independent (10 AXX XX XX, A=1–8), 378 M2M fixed (10 digits).
- Formatting (Språkrådet/Isof frågelådan, via search excerpts): hyphen after area code, subscriber digits grouped 2–3 from the left: `08-668 01 50`, `031-123 45 67`, `0480-123 45`, `070-123 45 67` (also `0701-23 45 67` accepted). International: `+46 70 123 45 67` (drop trunk 0). - Formatting (Språkrådet/Isof frågelådan, via search excerpts): hyphen after area code, subscriber digits grouped 2–3 from the left: `08-668 01 50`, `031-123 45 67`, `0480-123 45`, `070-123 45 67` (also `0701-23 45 67` accepted). International: `+46 70 123 45 67` (drop trunk 0).
@@ -1,142 +0,0 @@
import io
import json
import os
import tempfile
import unittest
from contextlib import redirect_stdout
import file_vulnerabilities
TODO = """# todo
## Scoring
`Score = -R - S/4 + 2*A + 2*G*W`
`Bar = 9`
`Next ID = 7`
| Goal | W |
|---|---|
| 1 | 1.00 |
| 1.1 | 0.90 |
## Items
| ID | Release | Exempt | Item | R | S | C | A | G | Goals | Score |
|---|---|---|---|---|---|---|---|---|---|---|
| 5 | 0.2.0 | | **Add a thing.** | 2 | 2 | 2 | 7 | 8 | 1 | 26.5 |
## Details
### 5. Add a thing.
Why.
"""
def report(*findings, osvs=()):
messages = [{"config": {"go_version": "go1.27.1", "scan_level": "module"}}]
messages += [{"osv": osv} for osv in osvs]
messages += [
{"finding": {"osv": osv, "fixed_version": fixed, "trace": [{"module": module, "version": version}]}}
for osv, module, version, fixed in findings
]
return "\n".join(json.dumps(m, indent=2) for m in messages)
OSVS = (
{"id": "GO-2026-0002", "summary": "Denial of service in net/http", "aliases": ["CVE-2026-2"]},
{"id": "GO-2026-0001", "summary": "Cross-site scripting in html/template", "aliases": ["CVE-2026-1"]},
{"id": "GO-2026-0003", "summary": "Panic in x/text", "aliases": []},
)
STDLIB = (
("GO-2026-0002", "stdlib", "v1.27.1", "v1.27.2"),
("GO-2026-0001", "stdlib", "v1.27.1", "v1.27.10"),
("GO-2026-0001", "stdlib", "v1.27.1", "v1.27.10"),
)
class FileItems(unittest.TestCase):
def test_no_finding_files_nothing(self):
self.assertEqual(file_vulnerabilities.file_items(report(osvs=OSVS), TODO), (TODO, 0))
def test_files_one_security_item_per_module_at_the_top_of_the_next_release(self):
todo, found = file_vulnerabilities.file_items(report(*STDLIB, osvs=OSVS), TODO)
self.assertEqual(found, 2)
self.assertIn("`Next ID = 8`", todo)
rows = [line for line in todo.splitlines() if line.startswith("| ") and line[2].isdigit()]
self.assertEqual(
rows[0],
"| 7 | 0.2.0 | security | **Raise Go to 1.27.10, so the release binaries carry none of"
" its known vulnerabilities.** | 1 | 1 | 1 | 8 | 8 | 1.1 | 29.1 |",
)
self.assertTrue(rows[1].startswith("| 5 |"))
detail = todo.split("## Details\n\n", 1)[1]
self.assertTrue(
detail.startswith(
"### 7. Raise Go to 1.27.10, so the release binaries carry none of its known vulnerabilities.\n\n"
"govulncheck found these in Go 1.27.1:\n\n"
"- GO-2026-0001 (CVE-2026-1): Cross-site scripting in html/template\n"
"- GO-2026-0002 (CVE-2026-2): Denial of service in net/http\n\n"
),
detail,
)
self.assertIn("### 5. Add a thing.", detail)
def test_a_module_requirement_gets_its_own_item(self):
findings = STDLIB + (("GO-2026-0003", "golang.org/x/text", "v0.3.0", "v0.3.8"),)
todo, found = file_vulnerabilities.file_items(report(*findings, osvs=OSVS), TODO)
self.assertEqual(found, 3)
self.assertIn("| 8 | 0.2.0 | security | **Raise `golang.org/x/text` to v0.3.8, so the module requires none of its known vulnerabilities.** |", todo)
self.assertIn("- GO-2026-0003: Panic in x/text\n", todo)
self.assertIn("`Next ID = 9`", todo)
def test_a_vulnerability_already_planned_is_not_filed_again(self):
filed, _ = file_vulnerabilities.file_items(report(*STDLIB, osvs=OSVS), TODO)
self.assertEqual(file_vulnerabilities.file_items(report(*STDLIB, osvs=OSVS), filed), (filed, 2))
def test_a_plan_with_no_items_is_refused(self):
empty = TODO.split("| 5 |")[0].rstrip("\n") + "\n\n## Details\n"
with self.assertRaises(SystemExit):
file_vulnerabilities.file_items(report(*STDLIB, osvs=OSVS), empty)
class Main(unittest.TestCase):
def setUp(self):
cwd = os.getcwd()
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
os.chdir(tmp.name)
self.addCleanup(os.chdir, cwd)
with open("todo.md", "w", encoding="utf-8") as f:
f.write(TODO)
def run_main(self, text):
with open("report.json", "w", encoding="utf-8") as f:
f.write(text)
out = io.StringIO()
with redirect_stdout(out):
code = file_vulnerabilities.main(["report.json", "todo.md"])
with open("todo.md", encoding="utf-8") as f:
return code, out.getvalue(), f.read()
def test_a_clean_report_passes_and_leaves_the_plan(self):
self.assertEqual(self.run_main(report(osvs=OSVS)), (0, "No known vulnerabilities.\n", TODO))
def test_a_finding_fails_and_files_it(self):
code, out, todo = self.run_main(report(*STDLIB, osvs=OSVS))
self.assertEqual(code, 1)
self.assertEqual(out, "2 known vulnerabilities; todo.md item 7 plans the fix.\n")
self.assertIn("| 7 | 0.2.0 | security |", todo)
def test_a_finding_already_planned_still_fails(self):
self.run_main(report(*STDLIB, osvs=OSVS))
code, out, _ = self.run_main(report(*STDLIB, osvs=OSVS))
self.assertEqual(code, 1)
self.assertEqual(out, "2 known vulnerabilities, already planned in todo.md.\n")
if __name__ == "__main__":
unittest.main()
+12
View File
@@ -23,6 +23,18 @@
"matchStrings": [ "matchStrings": [
"goreleaser/goreleaser-action@\\S+\\s+with:\\s+version: (?<currentValue>\\S+)" "goreleaser/goreleaser-action@\\S+\\s+with:\\s+version: (?<currentValue>\\S+)"
] ]
},
{
"customType": "regex",
"datasourceTemplate": "go",
"description": "Go tools the Dockerfile and compose.yaml run with go run, by their module path",
"managerFilePatterns": [
"/^Dockerfile$/",
"/^compose\\.yaml$/"
],
"matchStrings": [
"go run (?<depName>[^@\\s]+?)(/cmd/[^@\\s]+)?@(?<currentValue>v\\S+)"
]
} }
], ],
"extends": [ "extends": [
+73 -33
View File
@@ -6,12 +6,13 @@
`Bar = 9` `Bar = 9`
`Next ID = 253` `Next ID = 262`
| Goal | W | | Goal | W |
|---|---| |---|---|
| 1 | 1.00 | | 1 | 1.00 |
| 1.1 | 1.00 | | 1.1 | 1.00 |
| 1.2 | 1.00 |
| 2 | 0.95 | | 2 | 0.95 |
| 2.1 | 0.95 | | 2.1 | 0.95 |
| 2.2 | 0.95 | | 2.2 | 0.95 |
@@ -53,23 +54,22 @@
| 11 | 0.50 | | 11 | 0.50 |
| 12 | 0.45 | | 12 | 0.45 |
| 12.1 | 0.45 | | 12.1 | 0.45 |
| 12.2 | 0.45 |
| 12.3 | 0.45 |
| 13 | 0.40 | | 13 | 0.40 |
| 13.1 | 0.40 |
| 14 | 0.35 | | 14 | 0.35 |
| 15 | 0.30 | | 15 | 0.30 |
| 16 | 0.25 |
## Items ## Items
| ID | Release | Exempt | Item | R | S | C | A | G | Goals | Score | | ID | Release | Exempt | Item | R | S | C | A | G | Goals | Score |
|---|---|---|---|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|---|---|---|---|
| 259 | 0.2.0 | | **Follow the quickstarts with a table of every shipped path, its example value and what makes it valid or where it comes from, generated from the data and checked by a test.** | 2 | 4 | 4 | 8 | 8 | 1.2, 14 | 29.0 |
| 258 | 0.2.0 | | **Open the README with a CLI quickstart and a Go quickstart side by side, rendering the same value.** | 1 | 1 | 2 | 7 | 7 | 1.2 | 26.8 |
| 243 | 0.2.0 | | **Add `en_US.ein`, drawing only the prefixes the IRS assigns.** | 2 | 2 | 2 | 7 | 8 | 2.1, 4 | 26.7 | | 243 | 0.2.0 | | **Add `en_US.ein`, drawing only the prefixes the IRS assigns.** | 2 | 2 | 2 | 7 | 8 | 2.1, 4 | 26.7 |
| 230 | 0.2.0 | | **Add `sv_SE.organisationsnummer`, whose first digit is its legal form's group digit and whose last is a Luhn check digit, and `sv_SE.vat`, `SE` + the number + `01`.** | 2 | 3 | 3 | 7 | 8 | 2.1, 4 | 26.4 | | 230 | 0.2.0 | | **Add `sv_SE.organisationsnummer`, whose first digit is its legal form's group digit and whose last is a Luhn check digit, and `sv_SE.vat`, `SE` + the number + `01`.** | 2 | 3 | 3 | 7 | 8 | 2.1, 4 | 26.4 |
| 260 | 0.2.0 | | **Add a Recipes section: seed a PostgreSQL table, fill a struct in a Go test, make a column unique, and build parent and child records in Go.** | 1 | 3 | 3 | 7 | 7 | 1.2 | 26.2 |
| 210 | 0.2.0 | | **Add `fr_FR` with its `geo/FR` tree.** | 3 | 8 | 4 | 8 | 8 | 2 | 26.2 | | 210 | 0.2.0 | | **Add `fr_FR` with its `geo/FR` tree.** | 3 | 8 | 4 | 8 | 8 | 2 | 26.2 |
| 15 | 0.2.0 | | **Add `misc` tables and templates, one row of its detail's table per chunk.** | 3 | 9 | 3 | 8 | 8 | 2, 15, 14 | 25.9 | | 15 | 0.2.0 | | **Add `misc` tables and templates, one row of its detail's table per chunk.** | 3 | 9 | 3 | 8 | 8 | 2, 14, 13 | 25.9 |
| 201 | 0.2.0 | | **Place each Swedish street in the postort whose area holds it, so a big postort such as Stockholm carries its own streets.** | 4 | 6 | 6 | 8 | 8 | 2, 16 | 25.7 | | 201 | 0.2.0 | | **Place each Swedish street in the postort whose area holds it, so a big postort such as Stockholm carries its own streets.** | 4 | 6 | 6 | 8 | 8 | 2, 15 | 25.7 |
| 206 | 0.2.0 | | **Add `zh_CN` with its `geo/CN` tree.** | 4 | 8 | 4 | 8 | 8 | 2 | 25.2 | | 206 | 0.2.0 | | **Add `zh_CN` with its `geo/CN` tree.** | 4 | 8 | 4 | 8 | 8 | 2 | 25.2 |
| 208 | 0.2.0 | | **Add `es_MX` with its `geo/MX` tree.** | 4 | 8 | 5 | 8 | 8 | 2 | 25.2 | | 208 | 0.2.0 | | **Add `es_MX` with its `geo/MX` tree.** | 4 | 8 | 5 | 8 | 8 | 2 | 25.2 |
| 212 | 0.2.0 | | **Add `pt_BR` with its `geo/BR` tree.** | 4 | 8 | 4 | 8 | 8 | 2 | 25.2 | | 212 | 0.2.0 | | **Add `pt_BR` with its `geo/BR` tree.** | 4 | 8 | 4 | 8 | 8 | 2 | 25.2 |
@@ -78,6 +78,7 @@
| 235 | 0.2.0 | | **Add `sv_SE.bankgiro` and `sv_SE.plusgiro` with their Luhn check digit, and `sv_SE.bankaccount` with a clearing number and the check digit its bank's account type demands.** | 3 | 5 | 5 | 7 | 8 | 4, 2 | 24.9 | | 235 | 0.2.0 | | **Add `sv_SE.bankgiro` and `sv_SE.plusgiro` with their Luhn check digit, and `sv_SE.bankaccount` with a clearing number and the check digit its bank's account type demands.** | 3 | 5 | 5 | 7 | 8 | 4, 2 | 24.9 |
| 163 | 0.2.0 | | **Lift the CLI's `--repeat` cap, since the CLI streams each render, and give the builtins' count and decimal caps a technical reason in the README.** | 2 | 2 | 2 | 7 | 7 | 2, 5.7 | 24.8 | | 163 | 0.2.0 | | **Lift the CLI's `--repeat` cap, since the CLI streams each render, and give the builtins' count and decimal caps a technical reason in the README.** | 2 | 2 | 2 | 7 | 7 | 2, 5.7 | 24.8 |
| 234 | 0.2.0 | | **Draw `sv_SE.phone` from every area code in PTS's numbering plan, grouped as Språkrådet writes it, and add its international form, `+46 70 123 45 67`.** | 2 | 3 | 3 | 7 | 7 | 2.1, 4 | 24.5 | | 234 | 0.2.0 | | **Draw `sv_SE.phone` from every area code in PTS's numbering plan, grouped as Språkrådet writes it, and add its international form, `+46 70 123 45 67`.** | 2 | 3 | 3 | 7 | 7 | 2.1, 4 | 24.5 |
| 55 | 0.2.0 | | **Document `ErrNoColumns` and `MaxRepeat` in the README's Library section.** | 1 | 2 | 1 | 6 | 7 | 1.2 | 24.5 |
| 227 | 0.2.0 | | **Add a README quick start per task, for the bulk fixture author and the Go test author, each rendering one person whose name, sex, id, email and address agree.** | 1 | 2 | 2 | 7 | 6 | 1, 7 | 24.5 | | 227 | 0.2.0 | | **Add a README quick start per task, for the bulk fixture author and the Go test author, each rendering one person whose name, sex, id, email and address agree.** | 1 | 2 | 2 | 7 | 6 | 1, 7 | 24.5 |
| 202 | 0.2.0 | | **Add `nb_NO` with its `geo/NO` tree.** | 3 | 7 | 3 | 7 | 8 | 2 | 24.4 | | 202 | 0.2.0 | | **Add `nb_NO` with its `geo/NO` tree.** | 3 | 7 | 3 | 7 | 8 | 2 | 24.4 |
| 203 | 0.2.0 | | **Add `da_DK` with its `geo/DK` tree.** | 3 | 7 | 3 | 7 | 8 | 2 | 24.4 | | 203 | 0.2.0 | | **Add `da_DK` with its `geo/DK` tree.** | 3 | 7 | 3 | 7 | 8 | 2 | 24.4 |
@@ -87,12 +88,13 @@
| 211 | 0.2.0 | | **Add `bn_BD` with its `geo/BD` tree.** | 4 | 8 | 4 | 7 | 8 | 2 | 23.2 | | 211 | 0.2.0 | | **Add `bn_BD` with its `geo/BD` tree.** | 4 | 8 | 4 | 7 | 8 | 2 | 23.2 |
| 213 | 0.2.0 | | **Add `id_ID` with its `geo/ID` tree.** | 4 | 8 | 4 | 7 | 8 | 2 | 23.2 | | 213 | 0.2.0 | | **Add `id_ID` with its `geo/ID` tree.** | 4 | 8 | 4 | 7 | 8 | 2 | 23.2 |
| 214 | 0.2.0 | decision | **Add `ur_PK` with its `geo/PK` tree.** | 4 | 8 | 4 | 7 | 8 | 2 | 23.2 | | 214 | 0.2.0 | decision | **Add `ur_PK` with its `geo/PK` tree.** | 4 | 8 | 4 | 7 | 8 | 2 | 23.2 |
| 154 | 0.2.0 | | **Add `en_US.routing`, an ABA routing number over the Fed's prefix ranges with its check digit drawn by `{aba()}`.** | 2 | 2 | 2 | 6 | 7 | 2.1, 15 | 22.8 | | 154 | 0.2.0 | | **Add `en_US.routing`, an ABA routing number over the Fed's prefix ranges with its check digit drawn by `{aba()}`.** | 2 | 2 | 2 | 6 | 7 | 2.1, 14 | 22.8 |
| 155 | 0.2.0 | | **Add `misc.isin` and `misc.cusip`, built by their structure rules with their check digits drawn by `{isin()}` and `{cusip()}`.** | 2 | 2 | 2 | 6 | 7 | 2, 15 | 22.8 | | 155 | 0.2.0 | | **Add `misc.isin` and `misc.cusip`, built by their structure rules with their check digits drawn by `{isin()}` and `{cusip()}`.** | 2 | 2 | 2 | 6 | 7 | 2, 14 | 22.8 |
| 236 | 0.2.0 | | **Add `sv_SE.licenseplate`, `ABC 123` and `ABC 12A`, never drawing a combination Transportstyrelsen blocks.** | 2 | 2 | 4 | 6 | 7 | 2, 4 | 22.8 | | 236 | 0.2.0 | | **Add `sv_SE.licenseplate`, `ABC 123` and `ABC 12A`, never drawing a combination Transportstyrelsen blocks.** | 2 | 2 | 4 | 6 | 7 | 2, 4 | 22.8 |
| 245 | 0.2.0 | | **Draw `en_US.phone` from every area code NANPA has in service.** | 2 | 2 | 2 | 6 | 7 | 2.1, 4 | 22.8 | | 245 | 0.2.0 | | **Draw `en_US.phone` from every area code NANPA has in service.** | 2 | 2 | 2 | 6 | 7 | 2.1, 4 | 22.8 |
| 205 | 0.2.0 | | **Add `is_IS` with its `geo/IS` tree.** | 3 | 6 | 3 | 6 | 8 | 2 | 22.7 | | 205 | 0.2.0 | | **Add `is_IS` with its `geo/IS` tree.** | 3 | 6 | 3 | 6 | 8 | 2 | 22.7 |
| 26 | 0.2.0 | defect | **Spell `sv_SE.personnummer` with the `+` Skatteverket uses from the year the holder turns 100, judged by a date the caller gives.** | 4 | 6 | 8 | 7 | 8 | 4 | 22.1 | | 26 | 0.2.0 | defect | **Spell `sv_SE.personnummer` with the `+` Skatteverket uses from the year the holder turns 100, judged by a date the caller gives.** | 4 | 6 | 8 | 7 | 8 | 4 | 22.1 |
| 261 | 0.2.0 | | **Move the README's edge cases and each category's caveats into the reference sections, below what a first reader needs.** | 2 | 4 | 4 | 5 | 7 | 1.2 | 21.0 |
| 34 | 0.2.0 | | **Add `{isin()}`, `{cusip()}`, `{aba()}` and `{vin()}`.** | 2 | 4 | 4 | 6 | 7 | 4 | 20.9 | | 34 | 0.2.0 | | **Add `{isin()}`, `{cusip()}`, `{aba()}` and `{vin()}`.** | 2 | 4 | 4 | 6 | 7 | 4 | 20.9 |
| 241 | 0.2.0 | | **Give `sv_SE.person` a `birthdate`.** | 2 | 2 | 3 | 6 | 6 | 2.1 | 20.9 | | 241 | 0.2.0 | | **Give `sv_SE.person` a `birthdate`.** | 2 | 2 | 3 | 6 | 6 | 2.1 | 20.9 |
| 218 | 0.2.0 | decision | **Give every country shipped without streets its streets, once the maintainer accepts ODbL data or the country opens a register.** | 3 | 6 | 4 | 6 | 7 | 2.1 | 20.8 | | 218 | 0.2.0 | decision | **Give every country shipped without streets its streets, once the maintainer accepts ODbL data or the country opens a register.** | 3 | 6 | 4 | 6 | 7 | 2.1 | 20.8 |
@@ -129,7 +131,6 @@
| 157 | 0.2.0 | | **Show in the README's CLI section how PowerShell and cmd.exe pipe a template, one line per shell family.** | 1 | 1 | 1 | 5 | 6 | 7.2 | 17.1 | | 157 | 0.2.0 | | **Show in the README's CLI section how PowerShell and cmd.exe pipe a template, one line per shell family.** | 1 | 1 | 1 | 5 | 6 | 7.2 | 17.1 |
| 229 | 0.2.0 | | **Name the line and column where JSON parsing stopped, when a template that opens like a JSON object then fails as a format string.** | 2 | 2 | 2 | 5 | 6 | 6, 5.1 | 17.1 | | 229 | 0.2.0 | | **Name the line and column where JSON parsing stopped, when a template that opens like a JSON object then fails as a format string.** | 2 | 2 | 2 | 5 | 6 | 6, 5.1 | 17.1 |
| 53 | 0.2.0 | | **Let a table column carry a `datatype`, so `--format json` writes `"safe": true` and `--format sql` a boolean.** | 4 | 5 | 5 | 6 | 6 | 4 | 16.9 | | 53 | 0.2.0 | | **Let a table column carry a `datatype`, so `--format json` writes `"safe": true` and `--format sql` a boolean.** | 4 | 5 | 5 | 6 | 6 | 4 | 16.9 |
| 55 | 0.2.0 | | **Document `ErrNoColumns` and `MaxRepeat` in the README's Library section.** | 1 | 2 | 1 | 6 | 7 | 12 | 16.8 |
| 181 | 0.2.0 | decision | **Fail a load where two sources define one category, unless the library's option or the CLI's flag says the later replaces the earlier, and then make every read reach the replacement.** | 5 | 4 | 6 | 6 | 9 | 9.3 | 16.8 | | 181 | 0.2.0 | decision | **Fail a load where two sources define one category, unless the library's option or the CLI's flag says the later replaces the earlier, and then make every read reach the replacement.** | 5 | 4 | 6 | 6 | 9 | 9.3 | 16.8 |
| 232 | 0.2.0 | | **Add `sv_SE.sni`, a table of SCB's SNI 2025 industry codes with their Swedish labels.** | 2 | 3 | 2 | 5 | 5 | 2 | 16.8 | | 232 | 0.2.0 | | **Add `sv_SE.sni`, a table of SCB's SNI 2025 industry codes with their Swedish labels.** | 2 | 3 | 2 | 5 | 5 | 2 | 16.8 |
| 233 | 0.2.0 | | **Add `sv_SE.ssyk`, a table of SCB's SSYK 2012 occupation codes with their Swedish labels.** | 2 | 3 | 2 | 5 | 5 | 2 | 16.8 | | 233 | 0.2.0 | | **Add `sv_SE.ssyk`, a table of SCB's SSYK 2012 occupation codes with their Swedish labels.** | 2 | 3 | 2 | 5 | 5 | 2 | 16.8 |
@@ -142,11 +143,12 @@
| 20 | 0.2.0 | defect | **Weight `sv_SE.sex` and `en_US.sex` by bearers, as the README's Data section says they are.** | 2 | 2 | 2 | 4 | 6 | 4 | 15.7 | | 20 | 0.2.0 | defect | **Weight `sv_SE.sex` and `en_US.sex` by bearers, as the README's Data section says they are.** | 2 | 2 | 2 | 4 | 6 | 4 | 15.7 |
| 50 | 0.2.0 | | **Reword `prove`'s refusal of a typed column reading a row, `{/region}`.** | 1 | 2 | 2 | 4 | 6 | 6 | 15.5 | | 50 | 0.2.0 | | **Reword `prove`'s refusal of a typed column reading a row, `{/region}`.** | 1 | 2 | 2 | 4 | 6 | 6 | 15.5 |
| 224 | 0.2.0 | | **Name the module in every error about its manifest or its files: today an `fs.FS` module's errors name only the file inside it.** | 1 | 2 | 3 | 4 | 6 | 6 | 15.5 | | 224 | 0.2.0 | | **Name the module in every error about its manifest or its files: today an `fs.FS` module's errors name only the file inside it.** | 1 | 2 | 3 | 4 | 6 | 6 | 15.5 |
| 238 | 0.2.0 | | **Draw `sv_SE.word` and `sv_SE.sentence` from SALDO's nouns, verbs, adjectives and adverbs, each part of speech a path of its own.** | 3 | 5 | 4 | 5 | 5 | 2, 14 | 15.2 | | 238 | 0.2.0 | | **Draw `sv_SE.word` and `sv_SE.sentence` from SALDO's nouns, verbs, adjectives and adverbs, each part of speech a path of its own.** | 3 | 5 | 4 | 5 | 5 | 2, 13 | 15.2 |
| 249 | 0.2.0 | | **Draw `en_US.word` and `en_US.sentence` from Moby or WordNet, each part of speech a path of its own, and add `en_US.paragraph`.** | 3 | 5 | 4 | 5 | 5 | 2, 14 | 15.2 | | 249 | 0.2.0 | | **Draw `en_US.word` and `en_US.sentence` from Moby or WordNet, each part of speech a path of its own, and add `en_US.paragraph`.** | 3 | 5 | 4 | 5 | 5 | 2, 13 | 15.2 |
| 17 | 0.2.0 | | **Give every shipped category a column for each part its format composes, and one record shape across the national ids; re-pin the shape.** | 5 | 6 | 5 | 6 | 6 | 5 | 15.1 | | 17 | 0.2.0 | | **Give every shipped category a column for each part its format composes, and one record shape across the national ids; re-pin the shape.** | 5 | 6 | 5 | 6 | 6 | 5 | 15.1 |
| 165 | 0.2.0 | | **Let a table carry `name` with neither `key` nor `parent`, and list the rows a name matches by their line where it matches several.** | 2 | 2 | 3 | 4 | 6 | 5.7 | 15.1 | | 165 | 0.2.0 | | **Let a table carry `name` with neither `key` nor `parent`, and list the rows a name matches by their line where it matches several.** | 2 | 2 | 3 | 4 | 6 | 5.7 | 15.1 |
| 240 | 0.2.0 | | **Add `sv_SE.territory`, the Swedish name of each `misc.territory` row, keyed by its alpha-2 code.** | 2 | 2 | 2 | 4 | 5 | 2 | 15.0 | | 240 | 0.2.0 | | **Add `sv_SE.territory`, the Swedish name of each `misc.territory` row, keyed by its alpha-2 code.** | 2 | 2 | 2 | 4 | 5 | 2 | 15.0 |
| 22 | 0.2.0 | | **Give `url` and `email` a path that draws only domains nobody can register, keeping the wide set as the default.** | 3 | 4 | 3 | 7 | 8 | 15 | 14.8 |
| 170 | 0.2.0 | | **Decide in one function whether a read lands in a name's pick, which `refuseTwiceDrawnIn` calls at load and `keptInPick` at render.** | 3 | 3 | 8 | 2 | 8 | 3, 3.2 | 14.7 | | 170 | 0.2.0 | | **Decide in one function whether a read lands in a name's pick, which `refuseTwiceDrawnIn` calls at load and `keptInPick` at render.** | 3 | 3 | 8 | 2 | 8 | 3, 3.2 | 14.7 |
| 197 | 0.2.0 | decision | **Replace the namespace mount with one the loader states in one place, and give `datafiles.Source.at` one job.** | 3 | 3 | 7 | 2 | 8 | 3.2, 3 | 14.7 | | 197 | 0.2.0 | decision | **Replace the namespace mount with one the loader states in one place, and give `datafiles.Source.at` one job.** | 3 | 3 | 7 | 2 | 8 | 3.2, 3 | 14.7 |
| 29 | 0.2.0 | | **Let a path go from a selected row into the template beside its family: `geo.US.locality[1714000].address`.** | 4 | 4 | 7 | 5 | 6 | 5 | 14.6 | | 29 | 0.2.0 | | **Let a path go from a selected row into the template beside its family: `geo.US.locality[1714000].address`.** | 4 | 4 | 7 | 5 | 6 | 5 | 14.6 |
@@ -155,11 +157,9 @@
| 127 | 0.2.0 | | **Give a Swedish street only postal codes whose delivery digit means street or rural delivery, dropping codes whose delivery digit is 9, or record why they stay.** | 3 | 3 | 2 | 4 | 6 | 4 | 14.4 | | 127 | 0.2.0 | | **Give a Swedish street only postal codes whose delivery digit means street or rural delivery, dropping codes whose delivery digit is 9, or record why they stay.** | 3 | 3 | 2 | 4 | 6 | 4 | 14.4 |
| 251 | 0.2.0 | | **Export from the library whether a reference body is a lone path, so the CLI's `isPath` stops copying the path grammar.** | 2 | 2 | 3 | 3 | 6 | 3.2 | 14.3 | | 251 | 0.2.0 | | **Export from the library whether a reference body is a lone path, so the CLI's `isPath` stops copying the path grammar.** | 2 | 2 | 3 | 3 | 6 | 3.2 | 14.3 |
| 242 | 0.2.0 | | **Add `sv_SE.bloodtype`, weighted by Sweden's distribution.** | 1 | 2 | 1 | 4 | 4 | 2 | 14.1 | | 242 | 0.2.0 | | **Add `sv_SE.bloodtype`, weighted by Sweden's distribution.** | 1 | 2 | 1 | 4 | 4 | 2 | 14.1 |
| 22 | 0.2.0 | | **Give `url` and `email` a path that draws only domains nobody can register, keeping the wide set as the default.** | 3 | 4 | 3 | 7 | 8 | 16 | 14.0 |
| 46 | 0.2.0 | | **Report the same error every load for a table with two bad options.** | 2 | 2 | 2 | 3 | 7 | 6, 10 | 14.0 | | 46 | 0.2.0 | | **Report the same error every load for a table with two bad options.** | 2 | 2 | 2 | 3 | 7 | 6, 10 | 14.0 |
| 110 | 0.2.0 | | **Refuse an empty segment in a path `Fake` and `FakeRecord` take, as a reference does: `sv_SE.person.` reports `path has an empty segment`.** | 1 | 1 | 2 | 3 | 6 | 6 | 13.8 | | 110 | 0.2.0 | | **Refuse an empty segment in a path `Fake` and `FakeRecord` take, as a reference does: `sv_SE.person.` reports `path has an empty segment`.** | 1 | 1 | 2 | 3 | 6 | 6 | 13.8 |
| 158 | 0.2.0 | | **Drop a UTF-8 byte order mark that starts stdin, and say in the README how to stop PowerShell 5.1 sending `Göteborg` as `G?teborg`.** | 2 | 2 | 2 | 4 | 5 | 7.2, 5.1 | 13.5 | | 158 | 0.2.0 | | **Drop a UTF-8 byte order mark that starts stdin, and say in the README how to stop PowerShell 5.1 sending `Göteborg` as `G?teborg`.** | 2 | 2 | 2 | 4 | 5 | 7.2, 5.1 | 13.5 |
| 226 | 0.2.0 | | **Show in the README how to make a column unique, such as an email's local part carrying `{seq()}`.** | 1 | 2 | 1 | 4 | 4 | 4 | 13.3 |
| 166 | 0.2.0 | | **Let a key or name cell hold a bracket, a brace, a double quote or a pipe, so the table loads, its row draws at random, and only a selector cannot reach it.** | 2 | 2 | 4 | 3 | 6 | 5.7 | 13.1 | | 166 | 0.2.0 | | **Let a key or name cell hold a bracket, a brace, a double quote or a pipe, so the table loads, its row draws at random, and only a selector cannot reach it.** | 2 | 2 | 4 | 3 | 6 | 5.7 | 13.1 |
| 171 | 0.2.0 | | **Handle a template that only reads one record column in one place, so `checkColumns`, `itemDatatype`, `proveColumnItem` and `renderLeaf` no longer each special-case `readsColumn`.** | 3 | 3 | 7 | 2 | 7 | 3, 3.2 | 12.8 | | 171 | 0.2.0 | | **Handle a template that only reads one record column in one place, so `checkColumns`, `itemDatatype`, `proveColumnItem` and `renderLeaf` no longer each special-case `readsColumn`.** | 3 | 3 | 7 | 2 | 7 | 3, 3.2 | 12.8 |
| 252 | 0.2.0 | | **Publish `fejkdata-bin` to the AUR from the Packages workflow, so Arch installs with `yay -S fejkdata-bin`.** | 1 | 1 | 1 | 3 | 4 | 1.1 | 12.8 | | 252 | 0.2.0 | | **Publish `fejkdata-bin` to the AUR from the Packages workflow, so Arch installs with `yay -S fejkdata-bin`.** | 1 | 1 | 1 | 3 | 4 | 1.1 | 12.8 |
@@ -178,6 +178,12 @@
| 174 | 0.2.0 | | **Spell once the fact that a walk's next step leaves a selected row, which `pathPos.fromRow` and `tableRoute.nextLinked` each spell today.** | 1 | 1 | 3 | 1 | 5 | 3.2 | 9.8 | | 174 | 0.2.0 | | **Spell once the fact that a walk's next step leaves a selected row, which `pathPos.fromRow` and `tableRoute.nextLinked` each spell today.** | 1 | 1 | 3 | 1 | 5 | 3.2 | 9.8 |
| 108 | 0.2.0 | defect | **Name item 26 in the decision "No builtin reads the clock, so a date is bounded by days, never by an age", as the item that ends its conflict with goal 4.** | 1 | 1 | 1 | 1 | 4 | 3.4 | 8.0 | | 108 | 0.2.0 | defect | **Name item 26 in the decision "No builtin reads the clock, so a date is bounded by days, never by an age", as the item that ends its conflict with goal 4.** | 1 | 1 | 1 | 1 | 4 | 3.4 | 8.0 |
| 169 | 0.2.0 | question | **Reword the decision "A parent row with no child row is a load error" to say what each geo import does with such a row: drop it, or exit where `geo-se.py` would lose a kommun or a län.** | 1 | 1 | 2 | 1 | 4 | 3 | 8.0 | | 169 | 0.2.0 | question | **Reword the decision "A parent row with no child row is a load error" to say what each geo import does with such a row: drop it, or exit where `geo-se.py` would lose a kommun or a län.** | 1 | 1 | 2 | 1 | 4 | 3 | 8.0 |
| 255 | 0.3.0 | | **Let a repeat or a choice item bind a name to a path through an enclosing name, `{a.object as o}`, so each iteration draws a fresh row inside the enclosing pick.** | 4 | 4 | 7 | 6 | 7 | 4.1, 5.3 | 18.9 |
| 256 | 0.3.0 | | **Let `repeat` take a range, such as 5 to 15, drawing the count on each render.** | 2 | 3 | 3 | 6 | 6 | 5 | 18.9 |
| 75 | 0.3.0 | | **Run every README `sh`, `go` and `text` example as a test, and compare each "Renders" line to a seeded render.** | 2 | 6 | 5 | 4 | 7 | 1.2 | 18.5 |
| 257 | 0.3.0 | | **Add a transform that writes a value escaped for a JSON string, so a text template spelling JSON stays valid whatever a drawn value holds.** | 2 | 2 | 3 | 5 | 6 | 4, 5 | 17.7 |
| 253 | 0.3.0 | | **Let `date()` take bounds relative to the date the caller gives, such as 30 days before it to 14 days after it.** | 2 | 3 | 4 | 6 | 6 | 7.3, 10.1 | 17.6 |
| 254 | 0.3.0 | | **Let a choice deal its items in exact counts across a run's renders, so 200 issues hold exactly 4 in progress, where weights only approach it.** | 3 | 4 | 6 | 6 | 6 | 5, 10 | 17.6 |
| 38 | 0.3.0 | defect | **Compare `calcParser.binary`'s operator as a rune.** | 1 | 1 | 2 | 3 | 6 | 6 | 13.8 | | 38 | 0.3.0 | defect | **Compare `calcParser.binary`'s operator as a rune.** | 1 | 1 | 2 | 3 | 6 | 6 | 13.8 |
| 45 | 0.3.0 | | **Name the node a selector follows in `childNamed`'s refusal.** | 1 | 1 | 2 | 3 | 6 | 6 | 13.8 | | 45 | 0.3.0 | | **Name the node a selector follows in `childNamed`'s refusal.** | 1 | 1 | 2 | 3 | 6 | 6 | 13.8 |
| 49 | 0.3.0 | | **Report a CRLF rows file holding only its header as having no rows.** | 1 | 1 | 2 | 3 | 6 | 6 | 13.8 | | 49 | 0.3.0 | | **Report a CRLF rows file holding only its header as having no rows.** | 1 | 1 | 2 | 3 | 6 | 6 | 13.8 |
@@ -189,21 +195,19 @@
| 11 | 0.3.0 | | **Test that every `geo` tree holds the five table names and the `address` columns that port across countries.** | 1 | 2 | 2 | 3 | 5 | 4 | 13.0 | | 11 | 0.3.0 | | **Test that every `geo` tree holds the five table names and the `address` columns that port across countries.** | 1 | 2 | 2 | 3 | 5 | 4 | 13.0 |
| 35 | 0.3.0 | | **Add `{base64url(n)}` for JWT shapes.** | 1 | 2 | 3 | 4 | 5 | 8 | 13.0 | | 35 | 0.3.0 | | **Add `{base64url(n)}` for JWT shapes.** | 1 | 2 | 3 | 4 | 5 | 8 | 13.0 |
| 40 | 0.3.0 | | **Parse `defaultTable`'s path with the library's grammar.** | 2 | 2 | 3 | 3 | 5 | 3.2 | 12.5 | | 40 | 0.3.0 | | **Parse `defaultTable`'s path with the library's grammar.** | 2 | 2 | 3 | 3 | 5 | 3.2 | 12.5 |
| 21 | 0.3.0 | | **List `title` in both locales, and `sv_SE`'s `birth-number`, in the README's Data list of what each locale carries.** | 1 | 1 | 1 | 4 | 6 | 12 | 12.2 | | 23 | 0.3.0 | | **Give every other shipped category that could reach something real a path that never does.** | 3 | 6 | 4 | 6 | 7 | 15 | 11.7 |
| 86 | 0.3.0 | | **Read both `en_US.phone` formats' area codes from one category, as their exchange reads `en_US.phone-exchange`.** | 1 | 2 | 2 | 2 | 5 | 3.2 | 11.5 | | 86 | 0.3.0 | | **Read both `en_US.phone` formats' area codes from one category, as their exchange reads `en_US.phone-exchange`.** | 1 | 2 | 2 | 2 | 5 | 3.2 | 11.5 |
| 23 | 0.3.0 | | **Give every other shipped category that could reach something real a path that never does.** | 3 | 6 | 4 | 6 | 7 | 16 | 11.0 |
| 75 | 0.3.0 | | **Run every README `sh`, `go` and `text` example as a test, and compare each "Renders" line to a seeded render.** | 2 | 6 | 5 | 4 | 7 | 12.2 | 10.8 |
| 33 | 0.3.0 | | **Group `data/misc` into folders where a group name makes a path easier to guess.** | 4 | 5 | 4 | 4 | 5 | 5 | 10.8 | | 33 | 0.3.0 | | **Group `data/misc` into folders where a group name makes a path easier to guess.** | 4 | 5 | 4 | 4 | 5 | 5 | 10.8 |
| 72 | 0.3.0 | principle | **Fail the merge gate when test coverage falls below the last recorded figure.** | 2 | 3 | 3 | 2 | 5 | 3 | 10.2 | | 72 | 0.3.0 | principle | **Fail the merge gate when test coverage falls below the last recorded figure.** | 2 | 3 | 3 | 2 | 5 | 3 | 10.2 |
| 30 | 0.3.0 | | **Draw `email.local` from `username`, so the two share one handle list.** | 2 | 2 | 2 | 3 | 5 | 8 | 10.0 | | 30 | 0.3.0 | | **Draw `email.local` from `username`, so the two share one handle list.** | 2 | 2 | 2 | 3 | 5 | 8 | 10.0 |
| 19 | 0.3.0 | | **Fill the 398 Swedish localities weighted 200 from SCB småorter.** | 2 | 4 | 4 | 4 | 6 | 13 | 9.8 |
| 95 | 0.3.0 | principle | **Hold a struct's columns and the field each fills in one slice.** | 1 | 2 | 3 | 1 | 5 | 3.2 | 9.5 | | 95 | 0.3.0 | principle | **Hold a struct's columns and the field each fills in one slice.** | 1 | 2 | 3 | 1 | 5 | 3.2 | 9.5 |
| 116 | 0.3.0 | | **Turn a row index into its rows file's line in one function.** | 1 | 2 | 2 | 1 | 5 | 3.2 | 9.5 | | 116 | 0.3.0 | | **Turn a row index into its rows file's line in one function.** | 1 | 2 | 2 | 1 | 5 | 3.2 | 9.5 |
| 19 | 0.3.0 | | **Fill the 398 Swedish localities weighted 200 from SCB småorter.** | 2 | 4 | 4 | 4 | 6 | 14 | 9.2 |
| 56 | 0.3.0 | defect | **Build on a manual run of `test.yml`.** | 1 | 1 | 2 | 1 | 4 | 1 | 8.8 | | 56 | 0.3.0 | defect | **Build on a manual run of `test.yml`.** | 1 | 1 | 2 | 1 | 4 | 1 | 8.8 |
| 109 | 0.3.0 | principle | **Indent every script under `data-import/` with hard tabs, as `AGENTS.md` requires and `release-tooling/` does.** | 2 | 4 | 1 | 1 | 4 | 3 | 6.2 | | 109 | 0.3.0 | principle | **Indent every script under `data-import/` with hard tabs, as `AGENTS.md` requires and `release-tooling/` does.** | 2 | 4 | 1 | 1 | 4 | 3 | 6.2 |
| 63 | 0.4.0 | | **Pair a street with its exact postnummer.** | 3 | 6 | 5 | 5 | 8 | 4.1 | 19.1 | | 63 | 0.4.0 | | **Pair a street with its exact postnummer.** | 3 | 6 | 5 | 5 | 8 | 4.1 | 19.1 |
| 59 | 0.4.0 | | **Promise in `Generator`'s godoc that its renders run one at a time.** | 1 | 1 | 1 | 5 | 5 | 1.2 | 18.8 |
| 62 | 0.4.0 | | **Add `{btc()}` and `{eth()}`.** | 2 | 4 | 5 | 4 | 6 | 4 | 15.2 | | 62 | 0.4.0 | | **Add `{btc()}` and `{eth()}`.** | 2 | 4 | 5 | 4 | 6 | 4 | 15.2 |
| 59 | 0.4.0 | | **Promise in `Generator`'s godoc that its renders run one at a time.** | 1 | 1 | 1 | 5 | 5 | 12 | 13.2 |
| 65 | 0.4.0 | | **Hold every change `AGENTS.md` says owes a `CHANGELOG.md` entry to one in CI.** | 2 | 3 | 5 | 4 | 5 | 5.8 | 13.2 | | 65 | 0.4.0 | | **Hold every change `AGENTS.md` says owes a `CHANGELOG.md` entry to one in CI.** | 2 | 3 | 5 | 4 | 5 | 5.8 | 13.2 |
| 66 | 0.4.0 | | **Cut the README's Layout block to the lines that say what a file name cannot.** | 1 | 1 | 1 | 2 | 5 | 3.4 | 11.8 | | 66 | 0.4.0 | | **Cut the README's Layout block to the lines that say what a file name cannot.** | 1 | 1 | 1 | 2 | 5 | 3.4 | 11.8 |
| 67 | 0.4.0 | | **Cut `AGENTS.md` to the rules only it states, and explain every term its comprehension rule uses.** | 1 | 2 | 2 | 1 | 6 | 3.4 | 11.3 | | 67 | 0.4.0 | | **Cut `AGENTS.md` to the rules only it states, and explain every term its comprehension rule uses.** | 1 | 2 | 2 | 1 | 6 | 3.4 | 11.3 |
@@ -215,6 +219,14 @@
## Details ## Details
### 259. Follow the quickstarts with a table of every shipped path, its example value and what makes it valid or where it comes from, generated from the data and checked by a test.
"What can I get?" is a reader's first question, and today the answer is one sentence in the Data section, about 440 lines down. Group the rows by `sv_SE`, `en_US`, `misc` and `geo`. Generate them from `List` and one seeded render per path, and have `readme_test.go` fail when the table drifts. The Data section keeps the sources and the linked-table detail. Replaces item 21, so `title` and `birth-number` are listed with the rest.
### 258. Open the README with a CLI quickstart and a Go quickstart side by side, rendering the same value.
The opening line says "a Go library and a CLI", but its example is CLI-only, and the Library section starts after about 150 lines of CLI detail. A Go reader learns late that the library composes with code: a loop per parent, a random count, a shuffle for exact counts. README → Development → Documentation states the layout.
### 243. Add `en_US.ein`, drawing only the prefixes the IRS assigns. ### 243. Add `en_US.ein`, drawing only the prefixes the IRS assigns.
The IRS's prefix list is facts. [Source research](docs/research/research-sources-world.md). The IRS's prefix list is facts. [Source research](docs/research/research-sources-world.md).
@@ -223,6 +235,10 @@ The IRS's prefix list is facts. [Source research](docs/research/research-sources
Bolagsverket's group digits and the Luhn rule are facts. [Source research](docs/research/research-sources-se.md). Bolagsverket's group digits and the Luhn rule are facts. [Source research](docs/research/research-sources-se.md).
### 260. Add a Recipes section: seed a PostgreSQL table, fill a struct in a Go test, make a column unique, and build parent and child records in Go.
Each recipe runs as a test. The unique column carries `{seq()}`, as in an email's local part (it replaces item 226). The parent and child recipe compiles one template per parent with `NewTemplate`, draws the child count in Go, and marshals with `encoding/json`, as the 2026-10-14 Jira seeding experiment needed.
### 210. Add `fr_FR` with its `geo/FR` tree. ### 210. Add `fr_FR` with its `geo/FR` tree.
France holds more French speakers than DR Congo: 64M to 49M on Wikipedia's "Geographical distribution of French speakers", which credits a 2022 OIF report but flags its table as not taken from that report. The Base Adresse Nationale (Licence Ouverte 2.0) holds every street with its code postal. France holds more French speakers than DR Congo: 64M to 49M on Wikipedia's "Geographical distribution of French speakers", which credits a 2022 OIF report but flags its table as not taken from that report. The Base Adresse Nationale (Licence Ouverte 2.0) holds every street with its code postal.
@@ -367,6 +383,10 @@ The format hard-codes `-`, and its earliest birthdate, 1930-01-01, makes the old
- Rewrite the README's `--seed` and `WithSeed` rows to goal 10's wording, and name the new required option in the changelog: `New` breaks again here, the first time after 0.1.0, as the maintainer accepted. - Rewrite the README's `--seed` and `WithSeed` rows to goal 10's wording, and name the new required option in the changelog: `New` breaks again here, the first time after 0.1.0, as the maintainer accepted.
- Research: `sv_SE.samordningsnummer` also hard-codes `-` and draws from 1930. Check whether Lag (2022:1697) om samordningsnummer gives it the same `+`; folkbokföringslagen 18 § states the rule for personnummer only. - Research: `sv_SE.samordningsnummer` also hard-codes `-` and draws from 1930. Check whether Lag (2022:1697) om samordningsnummer gives it the same `+`; folkbokföringslagen 18 § states the rule for personnummer only.
### 261. Move the README's edge cases and each category's caveats into the reference sections, below what a first reader needs.
Examples: the bytes stdin renders, MySQL's `ANSI_QUOTES`, CSV rows spanning lines, `misc.timezone`'s standard offset against DST, and a Swedish street that may sit under a neighbouring postort. A category's caveat may instead be a note in item 259's table. Each rule still sits in one place.
### 34. Add `{isin()}`, `{cusip()}`, `{aba()}` and `{vin()}`. ### 34. Add `{isin()}`, `{cusip()}`, `{aba()}` and `{vin()}`.
`isin`: Luhn over letters expanded to digits. `aba`: 3-7-1 weights. `vin`: position 9 over the whole; a sample taking the WMI, since the check sits mid-string. `isin`: Luhn over letters expanded to digits. `aba`: 3-7-1 weights. `vin`: position 9 over the whole; a sample taking the WMI, since the check sits mid-string.
@@ -563,6 +583,10 @@ The maintainer ruled so on 2026-10-09. Today `bindOptions` in `internal/rows` re
From CLDR's `sv` territory names, under the Unicode licence. [Source research](docs/research/research-sources-se.md). From CLDR's `sv` territory names, under the Unicode licence. [Source research](docs/research/research-sources-se.md).
### 22. Give `url` and `email` a path that draws only domains nobody can register, keeping the wide set as the default.
17 of the 40 distinct ones shipped today sit on `.se`, `.nu`, `.io` and `.co`, which anyone may register, and only RFC 2606's `example.com`, `.net`, `.org`, `.test`, `.example`, `.invalid` and `.localhost` provably reach nothing.
### 170. Decide in one function whether a read lands in a name's pick, which `refuseTwiceDrawnIn` calls at load and `keptInPick` at render. ### 170. Decide in one function whether a read lands in a name's pick, which `refuseTwiceDrawnIn` calls at load and `keptInPick` at render.
All 13 seats of the 2026-10-09 panel on b9d1af6 named `keptInPick` and `readUnder` hardest, and 12 named the rule both apply as the code they would least want to change. A key `addressedKeys` builds at load must equal the one `pickKey.under` builds at render, and a mismatch splits a correlated draw without an error. `refuseTwiceDrawnIn` applies the same rule at load in code of its own. In the new function's comment, say why a fresh read's levels start at its head, `levelKeys(…, 1)` in `pathArm`, and a named read's at its target, `levelKeys(full, 0)` in `compileArm`, which three seats rebuilt by hand. All 13 seats of the 2026-10-09 panel on b9d1af6 named `keptInPick` and `readUnder` hardest, and 12 named the rule both apply as the code they would least want to change. A key `addressedKeys` builds at load must equal the one `pickKey.under` builds at render, and a mismatch splits a correlated draw without an error. `refuseTwiceDrawnIn` applies the same rule at load in code of its own. In the new function's comment, say why a fresh read's levels start at its head, `levelKeys(…, 1)` in `pathArm`, and a named read's at its target, `levelKeys(full, 0)` in `compileArm`, which three seats rebuilt by hand.
@@ -591,10 +615,6 @@ The inherited architect of the 0.1.0 scoring run on 52e0304 named `isPath` (`cli
geblod.nu's 2007 figures, via sv.wikipedia: A+ 37, O+ 32, B+ 10, A− 7, O− 6, AB+ 5, B− 2, AB− 1 (%). Item 15 plans a global `misc.bloodtype` from AABB. [Source research](docs/research/research-sources-se.md). geblod.nu's 2007 figures, via sv.wikipedia: A+ 37, O+ 32, B+ 10, A− 7, O− 6, AB+ 5, B− 2, AB− 1 (%). Item 15 plans a global `misc.bloodtype` from AABB. [Source research](docs/research/research-sources-se.md).
### 22. Give `url` and `email` a path that draws only domains nobody can register, keeping the wide set as the default.
17 of the 40 distinct ones shipped today sit on `.se`, `.nu`, `.io` and `.co`, which anyone may register, and only RFC 2606's `example.com`, `.net`, `.org`, `.test`, `.example`, `.invalid` and `.localhost` provably reach nothing.
### 46. Report the same error every load for a table with two bad options. ### 46. Report the same error every load for a table with two bad options.
`readTableOptions` in `table.go` returns on the first in Go's map order. `readTableOptions` in `table.go` returns on the first in Go's map order.
@@ -671,6 +691,30 @@ Item 26 revises the decision, but the decision does not point back to it.
Its entry says "The import script drops or fills such rows". There are two geo imports, and `geo-se.py` exits when `geo.fence` drops a kommun or a län, while `geo-us.py` drops counties and states. Rewording a decision is the maintainer's call. Its entry says "The import script drops or fills such rows". There are two geo imports, and `geo-se.py` exits when `geo.fence` drops a kommun or a län, while `geo-us.py` drops counties and states. Rewording a decision is the maintainer's call.
### 255. Let a repeat or a choice item bind a name to a path through an enclosing name, `{a.object as o}`, so each iteration draws a fresh row inside the enclosing pick.
A template spelling a parent with its children, an epic and its stories, binds the parent's area once, `{/jira.area as a}`, and wants each child in a `repeat` to name a different object of that area. Today `{a.object}` keeps one pick for every iteration, since a name keeps every level its reads pass, and `{a.object as o}` inside the repeat is refused: `no field "a"; … a binding names a field, or a reference`. The way round is one run per parent, selecting the area by key in a template the caller writes. Revise the decision "Every `{…}` draws afresh, and only a name keeps a pick", whose binding list leaves out a path through a name, and state how the new pick sits beside the enclosing name's memo (`drawMemo.stepDownPins` and "A pick keeps only the levels a read of the name addresses"). `readName` and `keptInPick` are places the comprehension seats found hard; make them easier to read where the change touches them.
### 256. Let `repeat` take a range, such as 5 to 15, drawing the count on each render.
An epic holds between 5 and 15 stories, an order between 1 and 8 lines. Today `repeat` is one integer, so a varied count takes a weighted choice of templates that differ only in their `repeat`. The repeat cap counts a range's upper bound. Bench the spelling with the bulk fixture author and the data author, such as `"repeat": [5, 15]` or `"repeat": "{int(5,15)}"`; the README's Repeat section and the decision "The repeat cap bounds renders, not bytes" name it.
### 75. Run every README `sh`, `go` and `text` example as a test, and compare each "Renders" line to a seeded render.
`readme_test.go` loads and renders each `json` block, but runs no `sh` example (CLI, Records, Table, Linked tables), no `go` example (Library) and no `text` block of error text, and compares no "Renders e.g." output, so goal 1.2 is not met.
### 257. Add a transform that writes a value escaped for a JSON string, so a text template spelling JSON stays valid whatever a drawn value holds.
A nested structure, a parent with an array of children, renders as one text template whose format spells JSON (`{{"summary":"{s}"}}`), since a record's columns are flat. A drawn value holding `"`, `\` or a newline breaks that JSON, as a company name or a sentence may. A transform such as `{json(s)}` writes the value with JSON's string escapes, beside `lowercase`, `uppercase` and `ascii`. Bench its name with the bulk fixture author.
### 253. Let `date()` take bounds relative to the date the caller gives, such as 30 days before it to 14 days after it.
Needs item 26, which makes the caller give the date and revises the decision "No builtin reads the clock, so a date is bounded by days, never by an age". Today a due date or a sprint window "around now" is two fixed days, re-pinned whenever the fixture should look current. A relative bound reads the caller's date, never the clock, so the same seed and date still give the same output (goal 10). Bench the spelling with the hand fixture author and the bulk fixture author, such as `date(-30d,+14d,'2006-01-02')`; the README's Functions row and the clock decision change with it.
### 254. Let a choice deal its items in exact counts across a run's renders, so 200 issues hold exactly 4 in progress, where weights only approach it.
A fixture often needs a fixed mix: of 200 issues, 4 in progress, 30 done and the rest to do. A weighted choice gives about 4, and the count shifts with the seed, so a test asserting on it, or a board built from it, needs a loader to fix the counts. A dealt choice is a deck, drawn without replacement until it runs out. Decide what a render past the last card does: refuse at load where `--repeat` is known to exceed the deck, start a fresh deck, or fall back to the weights. Decide too which renders share one deck: the run, as `{seq()}` shares one counter, or each `repeat`. Bench the spelling with the bulk fixture author. The deck is per-generator state beside `seq()`'s counters, so it must stay reproducible from one goroutine (goal 10). Where the change touches `render.go`'s `readField` or the pick state in `pick.go`, places the comprehension seats found hard, make it easier to read.
### 38. Compare `calcParser.binary`'s operator as a rune. ### 38. Compare `calcParser.binary`'s operator as a rune.
`byte(p.rs[p.pos])` reads U+012B `ī` as `+`, so `{calc(a ī b)}` compiles as `a + b`. `byte(p.rs[p.pos])` reads U+012B `ī` as `+`, so `{calc(a ī b)}` compiles as `a + b`.
@@ -711,17 +755,13 @@ Nothing checks it, and a new country breaks it silently.
The CLI counts brackets of its own, so a change to the selector grammar desyncs the default `--table` silently. The CLI counts brackets of its own, so a change to the selector grammar desyncs the default `--table` silently.
### 86. Read both `en_US.phone` formats' area codes from one category, as their exchange reads `en_US.phone-exchange`.
Both phone formats carry the same 21-code `area` list, so an edit to one copy can miss the other.
### 23. Give every other shipped category that could reach something real a path that never does. ### 23. Give every other shipped category that could reach something real a path that never does.
`phone` draws live PTS and NANP ranges — PTS's five fiction series are the Swedish inert set, sourced in [research-sources-se.md](docs/research/research-sources-se.md), and the NANP one still wants a source — and `bankgiro`, `plusgiro` and `routing` draw live prefixes. `phone` draws live PTS and NANP ranges — PTS's five fiction series are the Swedish inert set, sourced in [research-sources-se.md](docs/research/research-sources-se.md), and the NANP one still wants a source — and `bankgiro`, `plusgiro` and `routing` draw live prefixes.
### 75. Run every README `sh`, `go` and `text` example as a test, and compare each "Renders" line to a seeded render. ### 86. Read both `en_US.phone` formats' area codes from one category, as their exchange reads `en_US.phone-exchange`.
`readme_test.go` loads and renders each `json` block, but runs no `sh` example (CLI, Records, Table, Linked tables), no `go` example (Library) and no `text` block of error text, and compares no "Renders e.g." output, so goal 12.2 is not met. Both phone formats carry the same 21-code `area` list, so an edit to one copy can miss the other.
### 72. Fail the merge gate when test coverage falls below the last recorded figure. ### 72. Fail the merge gate when test coverage falls below the last recorded figure.
@@ -747,14 +787,14 @@ Technical principle: indent with hard tabs. All 19 scripts indent with four spac
Needs an application to Lantmäteriet; today a street goes to the nearest postal code centroid. It rewrites shipped rows, so it is a major once 1.0 is cut and a minor before that. Needs an application to Lantmäteriet; today a street goes to the nearest postal code centroid. It rewrites shipped rows, so it is a major once 1.0 is cut and a minor before that.
### 62. Add `{btc()}` and `{eth()}`.
They need sha256 and keccak for Base58Check and EIP-55.
### 59. Promise in `Generator`'s godoc that its renders run one at a time. ### 59. Promise in `Generator`'s godoc that its renders run one at a time.
A caller wanting parallel throughput then makes one generator per goroutine. A caller wanting parallel throughput then makes one generator per goroutine.
### 62. Add `{btc()}` and `{eth()}`.
They need sha256 and keccak for Base58Check and EIP-55.
### 65. Hold every change `AGENTS.md` says owes a `CHANGELOG.md` entry to one in CI. ### 65. Hold every change `AGENTS.md` says owes a `CHANGELOG.md` entry to one in CI.
The check covers `data` and `testdata/shipped_shape.txt` alone, where `AGENTS.md` adds a flag, an exit code, an exported name, a fence, a builtin and the lowest Go. The check covers `data` and `testdata/shipped_shape.txt` alone, where `AGENTS.md` adds a flag, an exit code, an exported name, a fence, a builtin and the lowest Go.
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
"""File each module's known vulnerabilities as one security item atop todo.md's next release.
Usage: file_vulnerabilities.py <govulncheck -format json report> <todo.md>
Exits 1 while any vulnerability is known, filed now or before, 0 on a clean report, and 2 on
a todo.md it cannot read.
todo.md's `Next ID = N` gives the item's ID, and the release of the plan's top row, the next
release, gives its release. Its Score is 0.0 until rank_todo.py, which CI cannot run, scores
it; a security item ranks first in its release whatever it scores. The test that files into
the repository's own todo.md fails the gate when the plan's shape drifts from what this
script reads.
Scores, as todo.md's table spells them: R (risk), S (size) and C (difficulty) are 1, since
the fix is a version bump; A (value to the audience) and G (value to the goal) are 8, the goal
being README goal 1.1, installing fejkdata the way one installs other tools, which a flagged
binary works against.
"""
import json
import re
import sys
from dataclasses import dataclass, field
ROW = re.compile(r"^\| (?P<id>\d+) \| (?P<release>[^|]+?) \| [^|]*\| \*\*", re.M)
NEXT_ID = re.compile(r"`Next ID = (\d+)`")
DETAILS = "## Details\n\n"
SCORES = {"R": 1, "S": 1, "C": 1, "A": 8, "G": 8}
GOAL = "1.1"
@dataclass
class Module:
path: str
found_in: str
fixed: list[str] = field(default_factory=list)
unfixed: bool = False
osvs: dict[str, dict] = field(default_factory=dict)
@property
def name(self) -> str:
return "Go" if self.path == "stdlib" else f"`{self.path}`"
def version(self, version: str) -> str:
return version.removeprefix("v") if self.path == "stdlib" else version
@property
def carried(self) -> str:
return f"{self.name} {self.version(self.found_in)}"
def messages(report: str):
decoder, at = json.JSONDecoder(), 0
while at < len(report):
if report[at].isspace():
at += 1
continue
message, at = decoder.raw_decode(report, at)
yield message
def vulnerable_modules(report: str) -> list[Module]:
"""Each module govulncheck found a vulnerability in, Go (stdlib) first, then by path."""
osvs, modules = {}, {}
for message in messages(report):
if "osv" in message:
osv = message["osv"]
osvs[osv["id"]] = {"id": osv["id"], "aliases": osv.get("aliases", []), "summary": osv.get("summary", "")}
elif "finding" in message:
finding = message["finding"]
frame = finding["trace"][0]
module = modules.setdefault(frame["module"], Module(frame["module"], frame["version"]))
module.osvs[finding["osv"]] = osvs[finding["osv"]]
if "fixed_version" in finding:
module.fixed.append(finding["fixed_version"])
else:
module.unfixed = True
return sorted(modules.values(), key=lambda m: (m.path != "stdlib", m.path))
def version_key(version: str):
return [int(part) for part in re.findall(r"\d+", version)]
def tagline(module: Module) -> str:
if module.unfixed:
return f"Ship no release while fejkdata carries {module.carried}, some of whose known vulnerabilities no version fixes yet."
fixed = module.version(max(module.fixed, key=version_key))
if module.path == "stdlib":
return f"Raise Go to {fixed}, so the release binaries carry none of {module.carried}'s known vulnerabilities."
return f"Raise {module.name} to {fixed}, so fejkdata requires no version of it with a known vulnerability."
def detail(module: Module) -> str:
lines = []
for osv in sorted(module.osvs.values(), key=lambda o: o["id"]):
aliases = f" ({', '.join(osv['aliases'])})" if osv["aliases"] else ""
summary = f": {osv['summary']}" if osv["summary"] else ""
lines.append(f"- {osv['id']}{aliases}{summary}")
return f"govulncheck found these in {module.carried}:\n\n" + "\n".join(lines) + "\n"
def file_items(modules: list[Module], todo: str) -> tuple[str, list[int]]:
"""Return todo.md with an item for each module whose vulnerabilities it does not yet name, and the items' IDs."""
first = ROW.search(todo)
if first is None:
raise ValueError("todo.md has no item to take the next release from")
if NEXT_ID.search(todo) is None:
raise ValueError("todo.md has no `Next ID = N` line to number the item by")
if DETAILS not in todo:
raise ValueError(f"todo.md has no {DETAILS.strip()!r} heading to put the item's detail under")
release, rows_at = first.group("release"), first.start()
details_at = todo.index(DETAILS) + len(DETAILS)
filed = []
for module in modules:
if all(osv in todo for osv in module.osvs):
continue
next_id = int(NEXT_ID.search(todo).group(1))
line = tagline(module)
scores = " | ".join(str(SCORES[k]) for k in "RSCAG")
row = f"| {next_id} | {release} | security | **{line}** | {scores} | {GOAL} | 0.0 |\n"
text = f"### {next_id}. {line}\n\n{detail(module)}\n"
todo = todo[:rows_at] + row + todo[rows_at:]
rows_at += len(row)
details_at += len(row)
todo = todo[:details_at] + text + todo[details_at:]
details_at += len(text)
todo = NEXT_ID.sub(f"`Next ID = {next_id + 1}`", todo, count=1)
filed.append(next_id)
return todo, filed
def main(argv: list[str]) -> int:
report_path, todo_path = argv
with open(report_path, encoding="utf-8") as f:
modules = vulnerable_modules(f.read())
if not modules:
print("No known vulnerabilities.")
return 0
count = sum(len(m.osvs) for m in modules)
known = f"{count} known {'vulnerability' if count == 1 else 'vulnerabilities'}"
with open(todo_path, encoding="utf-8") as f:
try:
todo, filed = file_items(modules, f.read())
except ValueError as err:
print(err, file=sys.stderr)
return 2
if not filed:
print(f"{known}, already planned in todo.md.")
return 1
with open(todo_path, "w", encoding="utf-8") as f:
f.write(todo)
items = ", ".join(map(str, filed))
print(f"{known}; todo.md {'item ' + items + ' plans' if len(filed) == 1 else 'items ' + items + ' plan'} the fix.")
return 1
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
@@ -0,0 +1,195 @@
import io
import json
import os
import tempfile
import unittest
from contextlib import redirect_stderr, redirect_stdout
import file_vulnerabilities
TODO = """# todo
## Scoring
`Score = -R - S/4 + 2*A + 2*G*W`
`Bar = 9`
`Next ID = 7`
| Goal | W |
|---|---|
| 1 | 1.00 |
| 1.1 | 0.90 |
## Items
| ID | Release | Exempt | Item | R | S | C | A | G | Goals | Score |
|---|---|---|---|---|---|---|---|---|---|---|
| 5 | 0.2.0 | | **Add a thing.** | 2 | 2 | 2 | 7 | 8 | 1 | 26.5 |
## Details
### 5. Add a thing.
Why.
"""
OSVS = (
{"id": "GO-2026-0002", "summary": "Denial of service in net/http", "aliases": ["GHSA-2222", "CVE-2026-2"]},
{"id": "GO-2026-0001", "summary": "Cross-site scripting in html/template", "aliases": ["CVE-2026-1"]},
{"id": "GO-2026-0003", "summary": "Panic in x/text"},
{"id": "GO-2026-0004"},
)
STDLIB = (
("GO-2026-0002", "stdlib", "v1.27.1", "v1.27.2"),
("GO-2026-0001", "stdlib", "v1.27.1", "v1.27.10"),
("GO-2026-0001", "stdlib", "v1.27.1", "v1.27.10"),
)
TEXT = (("GO-2026-0003", "golang.org/x/text", "v0.3.0", "v0.3.8"),)
GO_TAGLINE = "Raise Go to 1.27.10, so the release binaries carry none of Go 1.27.1's known vulnerabilities."
def report(*findings):
messages = [{"config": {"go_version": "go1.27.1", "scan_level": "module"}}]
messages += [{"osv": osv} for osv in OSVS]
for osv, module, version, fixed in findings:
finding = {"osv": osv, "trace": [{"module": module, "version": version}]}
if fixed:
finding["fixed_version"] = fixed
messages.append({"finding": finding})
return "\n".join(json.dumps(m, indent=2) for m in messages)
def file(todo, *findings):
return file_vulnerabilities.file_items(file_vulnerabilities.vulnerable_modules(report(*findings)), todo)
def rows(todo):
return [line for line in todo.splitlines() if "| **" in line]
class FileItems(unittest.TestCase):
def test_no_finding_files_nothing(self):
self.assertEqual(file(TODO), (TODO, []))
def test_files_one_security_item_per_module_atop_the_next_release(self):
todo, filed = file(TODO, *STDLIB)
self.assertEqual(filed, [7])
self.assertIn("`Next ID = 8`", todo)
self.assertEqual(rows(todo)[0], f"| 7 | 0.2.0 | security | **{GO_TAGLINE}** | 1 | 1 | 1 | 8 | 8 | 1.1 | 0.0 |")
self.assertTrue(rows(todo)[1].startswith("| 5 |"))
self.assertTrue(
todo.split("## Details\n\n", 1)[1].startswith(
f"### 7. {GO_TAGLINE}\n\n"
"govulncheck found these in Go 1.27.1:\n\n"
"- GO-2026-0001 (CVE-2026-1): Cross-site scripting in html/template\n"
"- GO-2026-0002 (GHSA-2222, CVE-2026-2): Denial of service in net/http\n\n"
"### 5. Add a thing."
)
)
def test_a_required_module_gets_its_own_item(self):
todo, filed = file(TODO, *STDLIB, *TEXT)
self.assertEqual(filed, [7, 8])
self.assertIn(
"| 8 | 0.2.0 | security | **Raise `golang.org/x/text` to v0.3.8, so fejkdata requires no version"
" of it with a known vulnerability.** |",
todo,
)
self.assertIn("govulncheck found these in `golang.org/x/text` v0.3.0:\n\n- GO-2026-0003: Panic in x/text\n", todo)
self.assertIn("`Next ID = 9`", todo)
self.assertEqual([row.split(" | ")[0] for row in rows(todo)], ["| 7", "| 8", "| 5"])
details = todo.split("## Details\n\n", 1)[1]
self.assertLess(details.index("### 7."), details.index("### 8."))
def test_a_vulnerability_already_planned_is_not_filed_again(self):
planned, _ = file(TODO, *STDLIB)
self.assertEqual(file(planned, *STDLIB), (planned, []))
def test_a_module_newly_vulnerable_is_filed_beside_one_already_planned(self):
planned, _ = file(TODO, *STDLIB)
todo, filed = file(planned, *STDLIB, *TEXT)
self.assertEqual(filed, [8])
self.assertIn("| 8 | 0.2.0 | security | **Raise `golang.org/x/text`", todo)
def test_a_vulnerability_no_version_fixes_names_no_target(self):
todo, _ = file(TODO, *STDLIB, ("GO-2026-0004", "stdlib", "v1.27.1", ""))
self.assertIn("- GO-2026-0004\n", todo)
self.assertIn(
"| 7 | 0.2.0 | security | **Ship no release while fejkdata carries Go 1.27.1, some of whose known"
" vulnerabilities no version fixes yet.** |",
todo,
)
def test_a_plan_with_no_next_id_is_refused(self):
with self.assertRaises(ValueError):
file(TODO.replace("`Next ID = 7`", ""), *STDLIB)
def test_a_plan_with_no_details_heading_is_refused(self):
with self.assertRaises(ValueError):
file(TODO.replace("## Details", "## Notes"), *STDLIB)
def test_a_plan_with_no_items_is_refused(self):
empty = TODO.split("| 5 |")[0].rstrip("\n") + "\n\n## Details\n"
with self.assertRaises(ValueError):
file(empty, *STDLIB)
def test_the_repositorys_plan_takes_an_item_in_its_own_shape(self):
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "todo.md")
with open(path, encoding="utf-8") as f:
plan = f.read()
todo, filed = file(plan, *STDLIB)
header = next(line for line in plan.splitlines() if line.startswith("| ID |"))
row = rows(todo)[0]
self.assertEqual(row.count("|"), header.count("|"))
self.assertEqual(row.split(" | ")[1], rows(plan)[0].split(" | ")[1])
self.assertRegex(row, rf"^\| {filed[0]} \| .* \| 0\.0 \|$")
self.assertIn(f"`Next ID = {filed[0] + 1}`", todo)
class Main(unittest.TestCase):
def setUp(self):
cwd = os.getcwd()
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
os.chdir(tmp.name)
self.addCleanup(os.chdir, cwd)
with open("todo.md", "w", encoding="utf-8") as f:
f.write(TODO)
def run_main(self, *findings):
with open("report.json", "w", encoding="utf-8") as f:
f.write(report(*findings))
out = io.StringIO()
with redirect_stdout(out):
code = file_vulnerabilities.main(["report.json", "todo.md"])
with open("todo.md", encoding="utf-8") as f:
return code, out.getvalue(), f.read()
def test_a_clean_report_passes_and_leaves_the_plan(self):
self.assertEqual(self.run_main(), (0, "No known vulnerabilities.\n", TODO))
def test_a_finding_fails_and_files_it(self):
code, out, todo = self.run_main(*STDLIB)
self.assertEqual((code, out), (1, "2 known vulnerabilities; todo.md item 7 plans the fix.\n"))
self.assertIn("| 7 | 0.2.0 | security |", todo)
def test_findings_in_two_modules_name_both_items(self):
_, out, _ = self.run_main(*STDLIB, *TEXT)
self.assertEqual(out, "3 known vulnerabilities; todo.md items 7, 8 plan the fix.\n")
def test_a_plan_it_cannot_read_exits_2(self):
with open("todo.md", "w", encoding="utf-8") as f:
f.write(TODO.replace("`Next ID = 7`", ""))
with redirect_stderr(io.StringIO()) as err:
self.assertEqual(self.run_main(*STDLIB)[:2], (2, ""))
self.assertEqual(err.getvalue(), "todo.md has no `Next ID = N` line to number the item by\n")
def test_one_finding_already_planned_still_fails(self):
self.run_main(*TEXT)
self.assertEqual(self.run_main(*TEXT)[:2], (1, "1 known vulnerability, already planned in todo.md.\n"))
if __name__ == "__main__":
unittest.main()