9 Commits

Author SHA1 Message Date
lilleman 976d2e5ac1 Rank understanding the code second, and say why it is not an afterthought
Tests / vet + fmt + tests (pull_request) Successful in 1m25s
Tests / Gitea release from CHANGELOG.md (pull_request) Has been skipped
Tests / vet + fmt + tests (push) Successful in 7s
Tests / Gitea release from CHANGELOG.md (push) Successful in 4s
2026-09-20 23:19:53 +02:00
lilleman 343c9fa98a Make being the best fake-data tool, and known for it, the goal the rest serve
Tests / vet + fmt + tests (pull_request) Successful in 1m23s
Tests / Gitea release from CHANGELOG.md (pull_request) Has been skipped
2026-09-20 23:01:30 +02:00
lilleman f206a18027 Gate everything on the comprehension score, and replay the round until it passes
Tests / vet + fmt + tests (pull_request) Successful in 1m24s
Tests / Gitea release from CHANGELOG.md (pull_request) Has been skipped
2026-09-20 22:53:14 +02:00
lilleman 3dc1cf82f1 Make the cost of reading the code a goal, and answer to a reader panel each PR
Tests / vet + fmt + tests (pull_request) Successful in 1m26s
Tests / Gitea release from CHANGELOG.md (pull_request) Has been skipped
2026-09-20 22:45:22 +02:00
lilleman 0d627d2b11 Plan the fence layer's readability first, as eight chunks in order
Tests / vet + fmt + tests (pull_request) Successful in 5s
Tests / Gitea release from CHANGELOG.md (pull_request) Has been skipped
Tests / vet + fmt + tests (push) Successful in 5s
Tests / Gitea release from CHANGELOG.md (push) Successful in 5s
2026-09-20 22:11:27 +02:00
lilleman ce8ea550a3 Correct what the readers proved false, and lift the file map out of last place
Tests / vet + fmt + tests (pull_request) Successful in 1m26s
Tests / Gitea release from CHANGELOG.md (pull_request) Has been skipped
Tests / vet + fmt + tests (push) Successful in 7s
Tests / Gitea release from CHANGELOG.md (push) Successful in 5s
2026-09-20 21:47:17 +02:00
lilleman 1035f8bb2a Name the register read before claiming none publishes a spelling
Tests / vet + fmt + tests (pull_request) Successful in 6s
Tests / Gitea release from CHANGELOG.md (pull_request) Has been skipped
Tests / vet + fmt + tests (push) Successful in 5s
Tests / Gitea release from CHANGELOG.md (push) Successful in 4s
2026-09-20 21:41:00 +02:00
lilleman ce59653ccd Say what selects, name the decision the keyword instances, and cite POSIX's macros
Tests / vet + fmt + tests (pull_request) Successful in 1m24s
Tests / Gitea release from CHANGELOG.md (pull_request) Has been skipped
Tests / vet + fmt + tests (push) Successful in 7s
Tests / Gitea release from CHANGELOG.md (push) Successful in 4s
2026-09-20 21:29:55 +02:00
lilleman ea9c1ad436 Name only the checker the keyword answers to, and file the Error near miss
Tests / vet + fmt + tests (pull_request) Successful in 1m26s
Tests / Gitea release from CHANGELOG.md (pull_request) Has been skipped
2026-09-20 21:26:54 +02:00
9 changed files with 167 additions and 86 deletions
+8 -1
View File
@@ -11,10 +11,17 @@
- Gitea queues Actions runs that `/repos/{owner}/{repo}/actions/tasks` does not
list, so an empty task list says nothing about whether CI ran; read
`/repos/{owner}/{repo}/commits/<sha>/status`.
- README goal 2 is gated at 7.0 on the `comprehension-panel` skill, and below it
nothing else merges — no feature, no category, no data — bar a security fix and a
dependency bump. `todo.md` carries the round: the nine-seat findings run at depth 1,
a PR per item it names, then the run again, until the score passes. At or above 7.0
every pull request scores with the four-seat run and answers it in one run. No merge
lowers the last score.
- Hard tabs. No comment by default; delete a restatement, a rationale, history, or a file preamble.
- One spelling per result: reject the other at `New`, and let the error name the spelling to use.
- A standing choice a reader would relitigate goes under Decisions in the README, not in a comment.
- Prose naming a source states what that source states: read the register's own field
before paraphrasing it.
before paraphrasing it, and name the register you read before claiming none
publishes a spelling.
- A README example is a `json` block that loads and renders as a category; `readme_test.go` runs every one.
- Cyclomatic complexity is gated at 14: the table-shaped dispatches (`linkParent`, `compileTemplate`, `renderEdges`) sit at it and stay whole; a function that would pass it is decomposed.
+2 -2
View File
@@ -14,12 +14,12 @@ 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` |
| `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` |
| `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 10) | — | — | — |
| `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 12) | — | — | — |
| `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/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/language.tsv` | [datasets/language-codes](https://github.com/datasets/language-codes), the [Library of Congress](https://www.loc.gov/standards/iso639-2/) ISO 639-2 register | [PDDL 1.0](https://opendatacommons.org/licenses/pddl/1-0/) | none required | `data-import/language.py` |
| `misc/loglevel.tsv` | [RFC 5424](https://www.rfc-editor.org/rfc/rfc5424.txt) Table 2, the syslog message severities; keywords from [POSIX](https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/syslog.h.html) `<syslog.h>` | facts | none required | `data-import/loglevel.py` |
| `misc/loglevel.tsv` | [RFC 5424](https://www.rfc-editor.org/rfc/rfc5424.txt) Table 2, the syslog message severities; keywords from the [POSIX](https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/syslog.h.html) `<syslog.h>` severity macros | facts | none required | `data-import/loglevel.py` |
| `misc/mimetype.tsv` | [mime-db](https://github.com/jshttp/mime-db), the IANA media type registry with filename extensions | [MIT](https://github.com/jshttp/mime-db/blob/master/LICENSE) | "Copyright (c) 2014 Jonathan Ong, Copyright (c) 2015-2022 Douglas Christopher Wilson" | `data-import/mimetype.py` |
| `misc/port.tsv` | [IANA Service Name and Transport Protocol Port Number Registry](https://www.iana.org/assignments/service-names-port-numbers/) | [public domain](https://www.iana.org/help/licensing-terms) | none required | `data-import/port.py` |
| `misc/protocol.tsv` | [IANA Protocol Numbers](https://www.iana.org/assignments/protocol-numbers/) | [public domain](https://www.iana.org/help/licensing-terms) | none required | `data-import/protocol.py` |
+69 -56
View File
@@ -194,7 +194,8 @@ a fixture needs one a reader recognises.
`misc.loglevel` is the eight syslog severities, rendering the keyword a configuration
writes, `err` and `info`, keyed by the numerical code a PRI encodes and selectable by
either, so `misc.loglevel[3]` and `misc.loglevel[err]` are one row.
`misc.loglevel.severity` is the name RFC 5424 spells, `Error` beside `err`.
`misc.loglevel.severity` is the name RFC 5424 spells, `Error` beside `err`; it
selects nothing, the code and the keyword do.
[`DATA-LICENSES.md`](DATA-LICENSES.md) names each table's source and licence.
`misc.timezone` is every zone tzdb gives a shipped territory, from one apiece for most
@@ -269,7 +270,7 @@ f, err := fejkdata.New(fejkdata.WithSeed(42))
if err != nil {
log.Fatal(err)
}
v, err := f.Fake("sv_SE.address") // "Kungsvägen 68\n379 17 Stockholm"
v, err := f.Fake("sv_SE.address") // "Järvedsvägen 43\n891 77 Järved"
paths := f.List() // every path Fake accepts, sorted
v, err = f.FakeTemplate("name: {/sv_SE.person.last}") // compile + render in one call
t, err := f.NewTemplate(`{"format":"name: {x}","x":["bosse","lina"]}`) // compile once
@@ -761,7 +762,7 @@ a minor only adds, and a major is the only release that changes what exists.
| Surface | Major | Minor |
|---------|-------|-------|
| Shipped data | remove or rename a path; change a category's format; remove a value, or change a weight or a repeat; add a reference from one shipped category into another; change a table's key, name, weight or parent column, or remove a row | a path outside a record's columns, a locale, a value in a list, a row |
| Records | remove, rename, retype or add a column; let a column be null | a record, as a new category |
| Records (a table is one) | remove, rename, retype or add a column; let a column be null | a record, as a new category |
| Data format | a fence: a spelling `New` rejects that it accepted; a template option, since it reserves a field name | a builtin |
| CLI | remove or rename a flag, or change its default; change what an exit code means; change the framing a `--format` writes (header, quoting, statement shape), the `--list` layout, or what an error names | a flag, a format |
| Library | change or remove an exported name; raise the lowest supported Go | an exported name, a `With…` option |
@@ -800,45 +801,57 @@ the Development section below, and who ships a register the four above then draw
## Goals
1. **Valid by construction** — every value passes the check its real consumer
1. **The best fake-data tool available, and used because it is** — a developer
choosing a seed-data library or CLI, in any language, should find this the one
worth switching to, and every goal below it is what that costs. Merit is only
half the goal: once the grammar, the data and the CLI are complete and stable,
it ships where those developers install from and is announced where they read.
2. **Understanding the code is part of the product** — it is a compiler with static
fences behind a small API, it will grow, and the people who extend it will not be
the people who wrote it. So reading cost is judged alongside behaviour: reaching
the unit behind a symptom without asking a person, changing one piece without
holding the rest, names that tell the truth, and a file that stands with no second
document open beside it. A simulated reader panel scores those four, the score is a
ratchet no release lowers, and [`AGENTS.md`](AGENTS.md) says what each pull request
owes it.
3. **Valid by construction** — every value passes the check its real consumer
applies; facts that belong together come from one draw, within a value and
across categories.
2. **Text means what it says** — a format renders as written; only `{…}` varies,
4. **Text means what it says** — a format renders as written; only `{…}` varies,
random characters included (`{digits(3)}`). One spelling per result; the wrong
one is a load error naming the right one.
3. **Every mistake is a load error** — `New` rejects the data and `NewTemplate`
5. **Every mistake is a load error** — `New` rejects the data and `NewTemplate`
the inline template; on a loaded generator `Fake` fails only for an unknown
path, `FakeStruct` only for a non-struct argument or a type its tags do not
describe, with the same error every call, and `Template.Fake` cannot fail at all.
4. **Zero to a value in one command** — `go install`, then `fejkdata sv_SE.person`:
6. **Zero to a value in one command** — `go install`, then `fejkdata sv_SE.person`:
no checkout, no flag. Flags are GNU-form (`--seed 42`, `-n 3`) in any position;
the first custom template needs no escape and no option.
5. **Data lives in JSON** — a builtin only for what data can't express.
6. **Reproducible** — seed in, same stream out; no builtin reads a clock.
7. **Zero dependencies** — standard library only.
8. **Docs index the grammar** — every syntax feature is a heading; every example
runs under test and shows its output; a rule is stated once.
9. **Fast enough to be free** — a value renders in about a microsecond and `New`
parses and validates the whole set once upfront, so generating fixtures stays
noise against a test's own runtime.
10. **Data is sourced, or on its way there** — a shipped fact, a name, place,
7. **Data lives in JSON** — a builtin only for what data can't express.
8. **Reproducible** — seed in, same stream out; no builtin reads a clock.
9. **Zero dependencies** — standard library only.
10. **Docs index the grammar** — every syntax feature is a heading; every example
runs under test and shows its output; a rule is stated once.
11. **Fast enough to be free** — a value renders in about a microsecond and `New`
parses and validates the whole set once upfront, so generating fixtures stays
noise against a test's own runtime.
12. **Data is sourced, or on its way there** — a shipped fact, a name, place,
code, id or classification, is read from a register or open dataset by a
[`data-import/`](data-import) script wherever one exists to read; where none
does yet a small hand-written set ships and [`todo.md`](todo.md) carries the
step that replaces it. Only non-factual copy stays authored. A sourced table
holds the rows its source holds: none is added by hand, and one is dropped
only by a rule the script states.
11. **Breadth follows what most systems store** — a category is added in proportion
13. **Breadth follows what most systems store** — a category is added in proportion
to how many real schemas hold it: names, addresses, phones, ids, money and
timestamps before anything domain-specific, and a catalogue serving one niche
waits behind everything serving many.
12. **Realism is the default, inertness is selectable** — where a value could reach
14. **Realism is the default, inertness is selectable** — where a value could reach
something real, a domain anyone may register or an account a bank could issue,
the realistic breadth ships *and* so does the subset that provably reaches
nothing, each on its own path. A fixture that looks nothing like production
tests nothing; the caller who needs a value that can touch nothing asks for it
by name.
## Decisions
- **Options and fields share one namespace.** `format`, `weight`, `repeat`,
@@ -1236,17 +1249,17 @@ the Development section below, and who ships a register the four above then draw
fejkdata has no business making.
- **`misc.loglevel` is a table keyed by the code, rendering POSIX's keyword.** A flat
list of names carries neither the code a PRI encodes nor a selector reaching it, and
goal 1 draws the two as one fact. RFC 5424 spells the severity `Informational`,
which no syslog, PSR-3 or `slog` checker accepts, so the shipped spelling is the one
a configuration writes, `info`, and the RFC's name stays the `severity` column. The
code and the keyword take the two selector slots, so `misc.loglevel[Error]` misses.
goal 3 draws the two as one fact. The canonical spelling losing to the one its domain
writes, above, settles the rest: a configuration writes `info`, so RFC 5424's
`Informational` stays the `severity` column, and the code and the keyword hold the
two selector slots.
- **`misc.tld` keys carry the leading dot, where other tables key on a bare code.**
The register spells a TLD `.se` and `misc.territory.tld` already ships it so, which a
bare key would make two spellings of one fact; `{/misc.tld}` also composes onto a
host with no separator. `misc.tld[se]` misses for it, which `todo.md` carries.
- **`misc.tld` is a table of its own, and `misc.territory.tld` stays a column.** A
`parent` demands a child for every parent row, so linking them would drop every root
zone row naming no territory, which is most of them, and goal 10 holds a sourced
zone row naming no territory, which is most of them, and goal 12 holds a sourced
table whole. The loader refuses the link outright anyway: `tld` is a column of
`misc.territory`, and a table may not be named like a column of its ancestor.
- **`misc.territory` carries a currency code, it does not link to `misc.currency`.**
@@ -1279,6 +1292,38 @@ the Development section below, and who ships a register the four above then draw
through a chain of five tables is every subsequence of it, and the direct chain is
the one a reader can predict from the tables' parents.
## Layout
```
fejkdata.go Generator, New, options, the embedded data set, List
node.go the node model and JSON -> node compilation
table.go tables: the rows TSV, its options and links, row selection and draws
path.go the dotted-path walk with its selectors, and proving a path resolves
render.go Fake and the recursive renderer (choices, format strings, expansions)
record.go records: Record, the JSON/CSV/SQL serializers, and their entry points
struct.go structs: FakeStruct, fake tags, and a field's Go type as its column's datatype
inline.go inline templates: Template, NewTemplate, FakeTemplate, IsTemplate, and their compile and link
template.go the {token} grammar: scanning, tokens, operands, validation, compiling a format
hold.go the hold: one draw per expansion for paths and operands, and its fences
draw.go one reference draw per render and group: draw sets, the group option, and its fence
family.go a family of linked tables: the rows a render pins, and the fence over paths into one family
reference.go reference sigils, and binding references across the tree
graph.go the render graph: edges, cycles, the repeat bound, tree walks
builtins.go the {name()} function registry and its implementations
layout.go date and time layouts: the instants one is proved against, and the two samples
checksum.go the check characters a derivation appends, and the IBAN they sit inside
transform.go the builtins that rewrite an operand's value, and the ASCII folding
calc.go the {calc()} arithmetic evaluator: parser, eval, validation
datatype.go column datatypes: DataType, where datatype and null may sit, a column's datatype
value.go the value proof: what a typed column or calc operand holds, checked at load
data.go data loading: fs.FS folders/files -> namespace tree, multi-source merge
cmd/fejkdata/ the fejkdata CLI
data/ shipped data (JSON, and a TSV per table), embedded at build: locale folders, geo, misc
data-import/ the scripts that rebuild each sourced table (see DATA-LICENSES.md)
release-tooling/ the release CI publishes from the changelog heading
testdata/ the pinned shipped shape (see Versioning)
```
## Development
Everything runs in Docker — **no local tooling beyond Docker is needed**.
@@ -1366,38 +1411,6 @@ and merge: once `main` passes the gate, CI tags that commit `vX.Y.Z` and publish
the Gitea release with the section as its body. A top heading of `[Unreleased]`
publishes nothing.
## Layout
```
fejkdata.go Generator, New, options, the embedded data set, List
node.go the node model and JSON -> node compilation
table.go tables: the rows TSV, its options and links, row selection and draws
path.go the dotted-path walk with its selectors, and proving a path resolves
render.go Fake and the recursive renderer (choices, format strings, expansions)
record.go records: Record, the JSON/CSV/SQL serializers, and their entry points
struct.go structs: FakeStruct, fake tags, and a field's Go type as its column's datatype
inline.go inline templates: Template, NewTemplate, FakeTemplate, IsTemplate, and their compile and link
template.go the {token} grammar: scanning, tokens, operands, validation, compiling a format
hold.go the hold: one draw per expansion for paths and operands, and its fences
draw.go one reference draw per render and group: draw sets, the group option, and its fence
family.go a family of linked tables: the rows a render pins, and the fence over paths into one family
reference.go reference sigils, and binding references across the tree
graph.go the render graph: edges, cycles, the repeat bound, tree walks
builtins.go the {name()} function registry and its implementations
layout.go date and time layouts: the instants one is proved against, and the two samples
checksum.go the check characters a derivation appends, and the IBAN they sit inside
transform.go the builtins that rewrite an operand's value, and the ASCII folding
calc.go the {calc()} arithmetic evaluator: parser, eval, validation
datatype.go column datatypes: DataType, where datatype and null may sit, a column's datatype
value.go the value proof: what a typed column or calc operand holds, checked at load
data.go data loading: fs.FS folders/files -> namespace tree, multi-source merge
cmd/fejkdata/ the fejkdata CLI
data/ shipped data (JSON, and a TSV per table), embedded at build: locale folders, geo, misc
data-import/ the scripts that rebuild each sourced table (see DATA-LICENSES.md)
release-tooling/ the release CI publishes from the changelog heading
testdata/ the pinned shipped shape (see Versioning)
```
## License
MIT — see [LICENSE](LICENSE). Forked from [github.com/Timewave-AB/fakes](https://github.com/Timewave-AB/fakes).
+5 -6
View File
@@ -3,10 +3,9 @@
data-import/loglevel.py [--source URL_OR_FILE] [--posix URL_OR_FILE] [--cache DIR] [--out FILE]
Table 2 is the whole set, read between the facility table's caption and its own. The
keyword is POSIX's severity macro less its `LOG_` prefix, lowercased, taken in the
order POSIX lists them, which `LOG_UPTO` states is the severity order; each must be a
prefix of the severity RFC 5424 numbers alike, so the two sources pin one pairing.
Every severity in Table 2 ships, paired to POSIX's keywords by the order each source
lists them, which holds only because a keyword prefixes the severity RFC 5424 numbers
alike.
"""
import argparse
import html
@@ -24,7 +23,7 @@ CACHE = Path(__file__).resolve().parent / "cache"
COLUMNS = ["code", "keyword", "severity"]
TABLE = re.compile(r"Table 1\.\s+Syslog Message Facilities(.*?)Table 2\.\s+Syslog Message Severities", re.S)
ROW = re.compile(r"^ +(\d) +([A-Z][a-z]+): ", re.M)
SEVERITIES = re.compile(r"severity level portion of the.*?macros:(.*?)The following shall be declared as functions", re.S)
SECTION = re.compile(r"severity level portion of the.*?macros:(.*?)The following shall be declared as functions", re.S)
MACRO = re.compile(r"\bLOG_([A-Z]+)\b")
EXPECTED = 8
@@ -37,7 +36,7 @@ def severities(text):
def keywords(page):
section = SEVERITIES.search(html.unescape(re.sub(r"<[^>]+>", "", page)))
section = SECTION.search(html.unescape(re.sub(r"<[^>]+>", "", page)))
if not section:
sys.exit("POSIX spells no severity list between its own caption and the function declarations; the page has moved")
return [m.lower() for m in MACRO.findall(section.group(1))]
+6 -6
View File
@@ -5,8 +5,8 @@
// then generate values by path:
//
// f, _ := fejkdata.New(fejkdata.WithSeed(42))
// f.Fake("sv_SE.address") // "Storgatan 12\n234 56 Göteborg"
// f.Fake("sv_SE.address.locality") // "Göteborg"
// f.Fake("sv_SE.address") // "Järvedsvägen 43\n891 77 Järved"
// f.Fake("sv_SE.address.locality") // "Sundbyberg": a second call draws afresh
//
// Several sources merge in order, the last winning a name clash, so custom data
// layers over the built-ins. The JSON template format is documented in the README.
@@ -29,8 +29,8 @@ import (
//go:embed data
var shippedFS embed.FS
// MaxRepeat caps a repeat, and the renders nested repeats multiply to along any
// path; the CLI's --repeat shares it.
// MaxRepeat caps a repeat, and caps the renders nested repeats multiply to along
// any one path; the CLI's --repeat shares it.
const MaxRepeat = 1 << 20
var _ [^uint(0)>>63 - 1]struct{} // 64-bit only, per the README's Decisions
@@ -40,8 +40,8 @@ var ErrNoData = errors.New("no data: WithoutShippedData needs at least one WithD
// Generator generates fake data from a loaded namespace tree. Create one with [New].
// It is safe for concurrent use; a seeded sequence is reproducible only when drawn
// from one goroutine. The compiled tree is immutable after [New], and Fake,
// NewTemplate and List read it concurrently without a lock.
// from one goroutine. The compiled tree is immutable after [New], so List reads it
// without a lock; mu guards the rest, which every other entry point takes.
type Generator struct {
mu sync.Mutex
rand *session
+3 -3
View File
@@ -9,9 +9,9 @@ import (
"strings"
)
// node is a compiled template element: a choice or a template. Compiling JSON
// into these once (see compile) means rendering never re-inspects the raw JSON or
// re-sums weights.
// node is a compiled element of the namespace tree: a folder, choice, null,
// template, table, column or row. Compiling JSON into these once (see compile)
// means rendering never re-inspects the raw JSON or re-sums weights.
type node interface{ isNode() }
// folder is a namespace of named children, built from a directory of JSON files
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"strings"
)
// rng is the randomness the renderer draws from. Passing it in keeps the render
// functions a pure core over an explicit effect; *rand.Rand satisfies it.
// rng is the randomness a builtin sample draws from; *rand.Rand satisfies it. The
// render path takes the concrete *session instead, for the reason below.
type rng interface {
IntN(n int) int
Float64() float64
+1 -1
View File
@@ -23,7 +23,7 @@ type table struct {
whole *row // the pinned row rendered by the format
cells []string // rows × columns, flat
tokens map[int]*template
key int // column index, or -1
key int // key, name, weight and parent are column indexes, or -1
name int
weight int
parent int
+71 -9
View File
@@ -4,10 +4,53 @@ Ordered as the releases that ship it.
## v0.1.0
### Comprehension, until the panel scores 7.0
Nothing else ships while the score is under 7.0 — no feature, no category, no data —
bar a security fix and a dependency bump, per README goal 2. This section is one
round, replayed: the `comprehension-panel` skill's nine-seat findings run at depth 1,
a PR per item it names, then the run again, until the score passes. The run is the
nine-seat one: the round is meant to buy architectural change, and the four-seat
scoring run names too little to steer one. A fresh run replaces the items below.
Scores, newest last: 5.9 on 2026-09-20 (Navigation 7.0, Locality 5.2, Shape 5.9,
Self-sufficiency 5.7), eight of nine seats capped by Locality or Self-sufficiency, all
nine naming `hold.go`, `draw.go` and `family.go` first for reading cost: 1,138 lines in
which each file carries both a load-time fence and render-time machinery, so no file
name says whether a unit runs at `New` or at `Fake`.
This round, in order:
- Define the fence vocabulary where a reader of the code meets it: fence, hold, pin,
draw, draw group, expansion against render, family, whole. It exists only across five
README sections today, and every reader paid for it before the three files read at all.
- Say on the `template` struct which pass fills which field, and that `compileFormat`
runs a second time once `linkTemplateRefs` binds the references: nine of its fields
are written by four other files, and the order lives only in `loadData`'s body.
- State on `rowSet` that it simulates `(*draws).pin` and `rowOf`, and prove the two
agree with a test: the load-time walk reimplements the render-time pinning, nothing
catches them drifting, and the failure is data that loads and then renders a family
that disagrees.
- Put `draws` and its methods in one file: it is declared in `hold.go:262` and all
eight of its methods sit in `family.go`, so a jump to the type lands where none of
the behaviour is.
- Split `hold.go`, `draw.go` and `family.go` so each file holds one phase, the
load-time fences apart from the render-time machinery, changing no logic.
- Move `nodeScope`, `checkScope` and `checkRenders` out of `graph.go` and refresh the
README Layout lines for `graph.go` and `data.go`: the order every load fence runs in
is the load path's spine, and no file name carries it.
- Name the `draw` stem apart: the free `pinned` in `hold.go` is not `(*draws).pinned`
in `family.go`, `compileFormat` is a method on both `*template` and `*table`, and
`draws`, `drawSet`, `drawScope`, `drawWalk`, `drawAt`, `drawKey`, `drawVisit` and
`drawRoute` no longer tell each other apart.
- Spell one fresh draw set one way: `newDrawSet` exists, and `render.go` and `draw.go`
hand-roll `drawSet{unnamed: draws{s: s}}` at three sites; name the lazy-map variant
if the difference is deliberate.
### Data
Shape: T = table, t = template, c = choice. Read every factual list from a register
by a `data-import/` script, as README goal 10 asks.
by a `data-import/` script, as README goal 12 asks.
- Add the remaining `misc` tables and templates, one row of the table below per chunk.
- Read `misc.emoji` from the Unicode `emoji-test.txt` register, and `misc.car` from an
@@ -20,16 +63,25 @@ by a `data-import/` script, as README goal 10 asks.
`municipality` as columns on `geo.SE.address` too.
- Fill the 398 Swedish localities weighted 200 from SCB småorter.
- Give `url` and `email` a path that draws only domains nobody can register, keeping
the wide set as the default, per goal 12: 17 of the 40 distinct ones shipped today
the wide set as the default, per goal 14: 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.
- Audit the rest of the shipped set against goal 12 and give each a never-reaching
- Audit the rest of the shipped set against goal 14 and give each a never-reaching
path where it lacks one: `phone` draws live PTS and NANP ranges, `bankgiro`,
`plusgiro` and `routing` draw live prefixes.
- Stop `misc.territory[EH].tld` rendering `.eh`, the one shipped TLD `misc.tld` does
not hold: ISO 3166 reserves it for Western Sahara and the root zone has never been
delegated it, so no resolver answers for it and goal 1 promises otherwise.
delegated it, so no resolver answers for it and goal 3 promises otherwise.
- Draw `en_US.phone`'s `exch` as a NANP central office code: `{int(100,999)}` renders
a leading 1 in about an eighth of draws, which libphonenumber rejects, and
`TestShippedUSPhone` proves the shape rather than the rule. Assert the rule with it.
- Give a `sv_SE.personnummer` over 100 the `+` separator Skatteverket spells, or stop
drawing birthdates that reach 100: the format hard-codes `-`, and the 1930 floor
makes the oldest draws invalid from 2031.
- Settle `en_US.ip` and `sv_SE.ip`, today byte-identical: either one `misc.ip` as the
decision "`misc` is what every locale shares" asks, or a decision saying why an
address that carries no locale stays per-locale.
- Accept a middle name, and draw a shipped `personnummer` inside a *selected* sex;
both want a draw group sharing its family's pins. Stop the conflict error naming a
rewrite that returns a different value where the read it conflicts with sits inside
@@ -39,7 +91,7 @@ by a `data-import/` script, as README goal 10 asks.
beside the family: require the path step to reach a sibling category, and seed every
draw group of the render from the outer selector's pins.
- Read `email.local`'s and `username`'s handles from the shipped name tables, which
goal 10 asks for and the hand-written list they use today does not meet. Keep the
goal 12 asks for and the hand-written list they use today does not meet. Keep the
handle shape: `username` shortens a surname to `ahl` or `sjo`, and the US one draws
unisex given names, neither of which falls out of the tables unaided.
- Decide whether `email.local` and `username` share one list: a reference between
@@ -110,8 +162,9 @@ by a `data-import/` script, as README goal 10 asks.
`misc.httpmethod[get]` and `misc.territory[se]` all answer "no row … has key or name"
and stop there, where a case-insensitive match could name the row that exists, and a
table with no `name` column could say it selects by key alone. `misc.tld[se]` misses
on the leading dot its keys carry rather than on case, so the near miss is worth
naming whatever shape it takes.
on the leading dot its keys carry rather than on case, and `misc.loglevel[Error]`
misses although a column carries `Error`, so the near miss is worth naming whatever
shape it takes.
- Let a table column carry a `datatype`, so `--format json` writes
`"safe": true` and `--format sql` a boolean rather than the text `'true'`. Today only
a JSON field takes one, so `misc.httpmethod`'s booleans are typed in Go and text
@@ -120,6 +173,12 @@ by a `data-import/` script, as README goal 10 asks.
### Open questions to settle
- Decide where the module is found from, before goal 1's announcement: the import
path is `gitea.larvit.se/larvit/fejkdata`, a developer looking for a fake-data
library searches GitHub and pkg.go.dev, and a module path is a major to change.
- Decide whether `data/misc`'s 33 flat files gain a level before v1.0.0: a folder is a
path segment, so `misc/net/tld` is a rename a consumer pays for, and goal 13 promises
the directory keeps growing.
- Decide whether `misc.browser` becomes a parent of `misc.useragent`, so
`misc.browser[Chrome].useragent` resolves. Adding a `parent` after v0.1.0 breaks a
consumer, so it rides a 0.(x+1).0.
@@ -174,8 +233,11 @@ because pairing a street with its exact postnummer rewrites shipped rows.
## v1.0.0
- Cut it once the shipped data is in its record shape and one full minor has shipped
with no breaking change, per the README's Versioning table. It plans no work of its
own: v0.1.0 settles the record shape and v0.3.0 is the quiet minor.
with no breaking change, per the README's Versioning table: v0.1.0 settles the
record shape and v0.3.0 is the quiet minor.
- Announce it where a developer choosing a fake-data tool already reads, which is
goal 1's second half and waits for the tag: complete, stable, installable without
Go. Write the README's first screen for someone deciding in a minute.
## Not release-bound