882 lines
44 KiB
Markdown
882 lines
44 KiB
Markdown
# Decisions
|
||
|
||
## GitHub is canonical, and the module path names it
|
||
|
||
2026-09-20, Lilleman auf Larv.
|
||
|
||
Goal 1 wants the usage the tool earns and goal 3 expects extenders who did not write it;
|
||
both need a stranger to file an issue and open a pull request. Gitea has no anonymous
|
||
issue, and no cross-host pull request at all, so a contributor would need an account and
|
||
a fork on a personal instance. Valid while the project wants contribution from outside:
|
||
the workflow decided nothing, being portable already — `github.api_url` and
|
||
`secrets.GITHUB_TOKEN` resolve on either host, so only the names moved.
|
||
|
||
## The Gitea copy stays, as a pull mirror of GitHub
|
||
|
||
2026-10-03, decided by the maintainer. Technical principle: one owner per value
|
||
(`~/.claude/principles/technical-principles.md`). GitHub owns the history, and
|
||
`gitea.larvit.se/larvit/fejkdata` stays as a read-only pull mirror of it, so it is never
|
||
a second owner. Until `todo.md` item 68 converts it, the Gitea copy goes stale. Valid
|
||
while the maintainer keeps a Gitea copy.
|
||
|
||
## The vocabulary sits below `doc.go`'s package clause, not in the package doc
|
||
|
||
2026-09-21, Lilleman auf Larv.
|
||
|
||
Goal 1 is what a developer choosing a library reads first, and the package doc is that
|
||
page: the glossary would fill two thirds of it with the render path's units, mostly
|
||
unexported, and Go doc comments have no code markup, so every name would render its
|
||
backticks literally. Below the clause it reaches goal 3's reader — who opens the file —
|
||
and nothing else. Valid while the glossary's subject is the render path; one written to
|
||
teach the exported API belongs above the clause.
|
||
|
||
## Options and fields share one namespace
|
||
|
||
2026-09-02, Lilleman auf Larv.
|
||
|
||
`format`, `weight`, `repeat`, `separator` and `datatype` are reserved; every other key
|
||
is a field. Nesting fields under a key, or prefixing options, would tax every template to
|
||
guard against a misspelt option.
|
||
|
||
## `{a|b}` stays beside nested choices
|
||
|
||
2026-09-02, Lilleman auf Larv.
|
||
|
||
`[[…], […]]` picks the same way, but its arms are anonymous; `{female|male}` keeps
|
||
`person.female` addressable.
|
||
|
||
## Flags follow getopt_long
|
||
|
||
2026-09-02, Lilleman auf Larv.
|
||
|
||
`--name value` and `--name=value` both work; a short flag's value attaches or follows
|
||
(`-s42`, `-s 42`) and short flags bundle (`-hn 3`), as every shell user expects. A
|
||
single-dash long flag is rejected naming the double-dash spelling, and `-s=42` is
|
||
rejected naming both short spellings: `=` belongs to the long form, and reading `=42` as
|
||
the value would make `-d=./x` a directory named `=./x`.
|
||
|
||
## A struct tag is a template by its shape
|
||
|
||
2026-09-03, Lilleman auf Larv.
|
||
|
||
A JSON object, array or string, or a string carrying a `{` token, is an inline template;
|
||
anything else is a path. A name may not contain a brace, a bracket or a quote, so a path
|
||
can never collide with any of those spellings, and the leading `[` or `"` is gated on
|
||
valid JSON so a stray copied bracket never swallows a tag — it names nothing, and says
|
||
so. Reserving the characters whole — though only a leading one
|
||
could collide — keeps one simple name rule instead of a leading-position special case.
|
||
A JSON string stays a template even where its bare text renders alike, `"x {/a}"` beside
|
||
`x {/a}`: its escapes put a tab or a newline on one `echo` line. A folder-relative `{.name}` or `{..name}` is refused naming
|
||
`{/name}`, since an inline template sits in no folder. `IsTemplate` exports the rule, so
|
||
struct tags and any other caller read one.
|
||
|
||
## The CLI renders exactly the template on its stdin, and adds nothing
|
||
|
||
2026-10-07, Lilleman auf Larv. Serves goals 7, 7.1 and 7.2.
|
||
|
||
The template comes from stdin, from `echo`, a file or a heredoc, so one command line gets
|
||
a value and a quoted heredoc needs no escape. A format string is stdin's every byte, and
|
||
fejkdata adds no newline of its own under `--format text`: `echo` ends each render with its newline,
|
||
`printf '%s'` sends none, and `-n` joins renders with `--separator`, empty by default. A
|
||
bench of the README's personas picked this on 2026-10-07 over dropping a newline or
|
||
adding one: a rule a user states in one sentence, at the cost of `printf` or a
|
||
`--separator` now and then.
|
||
|
||
A JSON template is its JSON, so the whitespace around it is no part of the template, per
|
||
the decision "Whitespace around a JSON template is dropped, in `NewTemplate` and on
|
||
stdin". Stdin holding only whitespace is misuse, since an unset variable sends just a
|
||
newline.
|
||
|
||
A lone reference, `{/users}` or `"{/users}"`, names the record `users` under `--format`,
|
||
one newline after it allowed, with `users` the default `--table`; one to nothing there
|
||
exits 1. `todo.md` item 129 moves that reading into the library; until then it works
|
||
against goal 3.2.
|
||
|
||
The CLI takes no argument but its flags: one is misuse naming the spelling that reads what
|
||
it likely means, `fejkdata < FILE` for a file's name, dropping it for `-`, and the template
|
||
piped in otherwise; so is a terminal with nothing piped in, which would otherwise wait for
|
||
typed input. `--list` prints paths, each read as `{/path}`. Valid while the CLI reads one
|
||
template per run.
|
||
|
||
## A template that does not compile is misuse (exit 2), including a reference that resolves to nothing, save a lone reference under the CLI's `--format` until item 129
|
||
|
||
2026-09-24, Lilleman auf Larv.
|
||
|
||
The whole template is the spelling under test, and `NewTemplate` compiles, links and
|
||
validates as one step. Under the CLI's `--format`, a lone reference to nothing exits 1
|
||
until item 129: it reads a path, and only the data is absent.
|
||
|
||
## Whitespace around a JSON template is dropped, in `NewTemplate` and on stdin
|
||
|
||
2026-10-07, Lilleman auf Larv. Applies KISS.
|
||
|
||
Padding is where the two readings disagree: a format string renders it, JSON drops it. A
|
||
template that parses as JSON is its JSON, so its padding is dropped, as `encoding/json`
|
||
drops it. Goal 5.7 refuses only a spelling that suggests a mistake, and padding around
|
||
JSON suggests none.
|
||
|
||
## `FakeTemplate` and `NewTemplate` both stay
|
||
|
||
2026-09-03, Lilleman auf Larv.
|
||
|
||
They reach the same value but not at the same cost: `NewTemplate` pays the compile and
|
||
validation once and renders many times, `FakeTemplate` is the one-shot call, and
|
||
`--repeat` is exactly the case that needs the first. The pair is `regexp.MustCompile`
|
||
and `regexp.Match`, not two spellings of one result.
|
||
|
||
## The shipped data is embedded, not discovered
|
||
|
||
2026-09-02, Lilleman auf Larv.
|
||
|
||
A directory a machine happens to have would make `--seed 42` machine-dependent. Data
|
||
still lives in `data/`; `--data-path` layers over it.
|
||
|
||
`todo.md` item 77 revises it: a library imports the shipped data as Go modules.
|
||
|
||
## With only the shipped set, a category loads on the first call reaching it; beside a `--data-path`, every category loads in `New`
|
||
|
||
2026-10-03, a restructure the maintainer approved.
|
||
|
||
Goal 13: `New` pays nothing for a category a run never reaches, which matters once the
|
||
shipped set grows to hundreds of MB. A category loads with every category it references
|
||
and its whole table family, and binds as a whole load does, so it renders the same as
|
||
after a whole load. `List` reads `shippedindex.go`, which `go generate` writes from
|
||
`data/`; `TestShippedIndexIsCurrent` fails while the index is stale. CI loads the whole
|
||
shipped set, and each shipped category alone into a fresh `New`, so none fails at first
|
||
reach.
|
||
|
||
Goal 6: beside a `--data-path`, `WithDataPath` or `WithDataFS`, every category loads in
|
||
`New`, so each mistake in the user's data is `New`'s error. That parses the whole shipped
|
||
set, against goal 13, until `todo.md` item 79 ends it.
|
||
|
||
Valid while CI proves the shipped set whole and each category alone.
|
||
|
||
Works against goal 2.3 until `todo.md` item 77 ends it.
|
||
|
||
## Every `{…}` draws afresh, and only a name keeps a pick
|
||
|
||
2026-10-05, larv-review on the change the maintainer planned on 2026-10-03.
|
||
Serves goals 5.2 and 4.1: each `{…}` makes a new pick, and values that belong together come
|
||
from the one pick a name keeps.
|
||
|
||
- `{p.a} {p.b}`, `{w} {uppercase(w)}`, `{/p.first} {/p.last}` and `{/p} {/p}` are two draws
|
||
each, and `{p as q}{q.a} {q.b}` is one. Nothing is kept per format or per render, so the
|
||
only load checks on draws are names, paths through names, cycles and the repeat cap.
|
||
- `{x as n}` binds a reference, or a field of the template binding it or a path into one,
|
||
`{place as p}`: an inline template, and a choice of rows inside one category, have no reference to
|
||
bind, and goal 4.1 holds there too.
|
||
|
||
Valid while goal 5.2 has each `{…}` make a new pick.
|
||
|
||
## Reference sigils follow the filesystem
|
||
|
||
2026-09-02, Lilleman auf Larv.
|
||
|
||
`/` is the root, `.` this file's folder, `..` the folder above — what those spellings
|
||
already mean to anyone who has typed a path. A locale's files reach each other without
|
||
naming the locale, so a folder renames and copies without editing its references.
|
||
|
||
## A change to what exists is a major; a minor only adds
|
||
|
||
2026-09-16, Lilleman auf Larv.
|
||
|
||
Data files, the CLI and the Go API are the public API, and a consumer must be able to
|
||
take a minor without an edit — so an added column is a major, since it changes the CSV
|
||
header and the `INSERT` column list, as is a removed value, which changes what a fixture
|
||
holds, and a new option, which reserves a field name. A new refusal tightens what
|
||
loads, so every new fence invalidates some file. Each such release names the rejected
|
||
spelling and its replacement in the changelog and in the load error, and that is the
|
||
whole migration: a fence rejects one spelling with one replacement, so the fix is local
|
||
to each site. A fence that would need a non-local rewrite ships a converter with its
|
||
release instead. Before `v1.0.0` a minor carries what a major would.
|
||
|
||
## An error is a contract by what it names, not its bytes
|
||
|
||
2026-09-16, Lilleman auf Larv.
|
||
|
||
Goal 6. A script branches on the exit code and reads the named path or spelling, so
|
||
those hold; wording improves in a minor.
|
||
|
||
## A load reports every mistake at once
|
||
|
||
2026-09-30, Lilleman auf Larv. Goal 6. An author learns everything wrong with the data
|
||
in one load, not one mistake per run: `New` and `NewTemplate` report every mistake in one
|
||
error, ordered by the path each names. A mistake following only from another, such as a
|
||
reference into a category that did not compile, is left out, so each one names something
|
||
to fix. Valid while a load checks the whole set before its first render.
|
||
|
||
## Formats are checked after all data is loaded, so their errors name the category
|
||
|
||
2026-09-28, larv-review; approved 2026-09-28 by lilleman. Goal 3. Loading reads every
|
||
file first, then connects references across them. A format holding a reference can only
|
||
be checked once it is connected, so every format is checked then, and all checks run at
|
||
one moment. An error from those checks names the category path you would type (`sub.x`),
|
||
or a table cell's category and line, not the file. Valid while checking a format needs
|
||
its references connected.
|
||
|
||
## Raising the lowest supported Go is a major
|
||
|
||
2026-09-16, Lilleman auf Larv.
|
||
|
||
A consumer building on it breaks, which is the one test every rule above applies; Go's
|
||
convention of a minor is not followed.
|
||
|
||
## The format check runs on the latest Go only
|
||
|
||
2026-09-21, Lilleman auf Larv.
|
||
|
||
`gofmt`'s output is the toolchain's, not the code's: 1.27 stopped padding a map
|
||
literal's values out to a lone long key, so no source satisfies both it and 1.22's. A
|
||
consumer on the lowest supported Go depends on the code compiling and its tests passing
|
||
there, which is the `portable` stage, and never runs `gofmt` over this source, so the
|
||
latest toolchain alone defines the one canonical form goal 3 asks a reader to meet.
|
||
Valid while the lowest supported Go is not the latest.
|
||
|
||
## The changelog heading is the one spelling of a release; CI cuts the tag
|
||
|
||
2026-09-16, Lilleman auf Larv.
|
||
|
||
A tag pushed by hand is served by `go get` at once, so a tag whose commit lacks its
|
||
heading is burnt, not fixed. The heading on a gate-passed `main` commit is the trigger
|
||
instead: the tag can land only there, and the GitHub release the same job publishes
|
||
keeps one text as its body and is where prebuilt binaries will attach.
|
||
|
||
## A `--data-path` override rebinds every reference to the category it replaces
|
||
|
||
2026-09-24, Lilleman auf Larv.
|
||
|
||
References bind against the merged tree, so once shipped data uses `{.person}`, a
|
||
consumer's `sv_SE/person.json` is what every shipped reference into `person` reads, and
|
||
`New` fails on shipped data the consumer never wrote when that file lacks a field those
|
||
references read. Accepted: overriding is the point of layering, the error names the
|
||
reference and the field, and the fix is the consumer's file carrying the fields the
|
||
shipped tree reads.
|
||
|
||
A `--data-path` category replaces a shipped one without saying so, against goal 9.3, until `todo.md` item 77 ends it.
|
||
|
||
## The repeat cap bounds renders, not bytes
|
||
|
||
2026-09-02, Lilleman auf Larv.
|
||
|
||
A repeat, alone or nested, may ask for at most 1 048 576 renders; how large each render
|
||
is stays what the data asked for, so `{hex(1048576)}` repeated to the cap is a terabyte,
|
||
loaded without complaint. A byte estimate would need every builtin to declare a width to
|
||
fence a shape no data comes near, and the harm lands on the author who wrote it.
|
||
|
||
## 64-bit targets only
|
||
|
||
2026-09-02, Lilleman auf Larv.
|
||
|
||
The gate builds amd64, and the buffer sizing a render pre-computes (renders × bytes)
|
||
assumes a 64-bit int; on a 32-bit target it could overflow and panic.
|
||
|
||
## A constant zero divisor is a load error; in a string column a divisor that is not constant prints `Inf`
|
||
|
||
2026-09-24, Lilleman auf Larv.
|
||
|
||
`1/0` and a fixed `"0"` field are decidable, so they join the never-numeric operand as a
|
||
load error; the fold stops where an operand varies, so `a/(b*c)` with `b` fixed at `0`
|
||
and `c` varying loads and prints `Inf` every draw — catching it needs zero-absorbing
|
||
algebra for a shape nobody writes. A [typed column](../README.md#datatype) bounds its operands
|
||
instead and refuses a divisor it cannot keep from zero. In a string column, an operand that
|
||
is sometimes not a number loads, and prints `NaN` on each draw where it is not one. `Inf`
|
||
and `NaN` in a string column work against goals 6.1 and 4 until `todo.md` item 81
|
||
refuses them there too.
|
||
|
||
## In data, a constant spelled as a sample is a load error
|
||
|
||
2026-09-24, Lilleman auf Larv; narrowed 2026-10-07. Serves goal 5.7.
|
||
|
||
`int(5,5)` and `float(1,1,2)` draw nothing, which suggests a range typed wrong, so each is
|
||
rejected naming the constant. A default written out, `weight: 1`, `repeat: 1`,
|
||
`separator: ""` or `datatype: "string"`, and a number spelled `+5` or `05`, load as the
|
||
shorter form, as the CLI's `--seed 007` and `--repeat +3` do.
|
||
|
||
## Samples say what they emit, transforms what they do
|
||
|
||
2026-09-02, Lilleman auf Larv.
|
||
|
||
`{upper(2)}` is two letters, `{uppercase(x)}` is `x` upper-cased; one name for both
|
||
would turn on whether the argument looks like a number.
|
||
|
||
## A record is a template seen as columns; a Go struct is the one second schema
|
||
|
||
2026-09-15, Lilleman auf Larv.
|
||
|
||
A template's `format` composes its fields into one string; `FakeRecord` and `--format`
|
||
project the same fields as columns. Two views of one dataset, so a record author writes
|
||
the same JSON they already know, and a column is the same field `Fake` renders by dotted
|
||
path. The `format` is inert to a record — a record-only template writes `"format": ""` —
|
||
but it is compiled and fenced, so a template that loads renders as whichever shape is
|
||
asked for. `FakeStruct` takes its columns from a struct instead, because a Go caller has
|
||
already written that schema: the fields name the columns and their types are the
|
||
datatypes, so a tag says only what to draw, and a `datatype` in it would be a second
|
||
spelling of the type. The Go type is a struct column's one datatype, so its items need
|
||
not agree on one among themselves; each must only hold that type.
|
||
|
||
## A struct's records follow Go's field access, and compile on first use
|
||
|
||
2026-09-15, Lilleman auf Larv.
|
||
|
||
The fields an embedded struct promotes are the struct's own — `e.First`, as
|
||
`encoding/json` and SQL mappers read them — so they are columns of its record and share
|
||
its names: one tag may bind `{/person as p}{p.first}` and another read `{p.sex}` (goal 4.1),
|
||
while a path tag draws afresh as `{/path}` does (goal 5.2).
|
||
|
||
A tag binding a name only an embedding type reads is refused on the embedded type alone, as
|
||
any name nothing reads is, so the embedder binds it in a tag of its own. A tagged field that
|
||
another field hides is refused, not dropped. A named
|
||
struct field is another entity and a record of its own. `fake:"-"` leaves any struct
|
||
field unfilled, so no name may be `-`; a pointer back to a struct
|
||
already being filled is left alone, since filling it would never end. `New` cannot see a
|
||
caller's types, so the first `FakeStruct` for a type compiles its tags and the answer,
|
||
error included, is kept per type: a test's first call is its load, and no `NewStruct`
|
||
handle is needed, as the cache already compiles once.
|
||
|
||
## A category never references itself, and a record's fences run at load
|
||
|
||
2026-09-16, Lilleman auf Larv.
|
||
|
||
A category is one unit: a reference back into it — `{/users.first}` inside `users` —
|
||
describes a draw other than the fields beside it, so `New` refuses it and the sibling
|
||
field stays the one spelling for a field of one's own. A value two fields share goes in its
|
||
own category, which one field binds to a name and both read (goal 4.1). That settled, a record's column fences
|
||
run at `New` too, so a category that loads renders as a value and as a record alike.
|
||
|
||
## A record's column set is fixed before the first draw
|
||
|
||
2026-09-04, Lilleman auf Larv.
|
||
|
||
Only a category-level template is a record: a path descending into a field, or naming a
|
||
folder or a choice, errors. A tail may pass through a choice whose variants carry
|
||
different fields, so the columns — and with them the CSV header written once ahead of
|
||
every row — would vary per draw. A fixed column set is what the CSV and `INSERT`
|
||
contracts rest on, so the restriction holds even where a particular choice would happen
|
||
to agree.
|
||
|
||
## Null is a `null` item, not a rate
|
||
|
||
2026-09-15, Lilleman auf Larv.
|
||
|
||
A null is one more outcome of a column's draw, so a choice's weights skew it like any
|
||
other; a null-rate option would be a second way to state odds.
|
||
|
||
## A typed column holds one value, not composed text
|
||
|
||
2026-09-15, Lilleman auf Larv.
|
||
|
||
Its bounds come from a literal or a call's arguments, so a load error names a real
|
||
value, a range check is one comparison, and `1{digits(2)}` is a second spelling of
|
||
`{int(100,199)}`.
|
||
|
||
## A column that only reads one reference or name is the column it reads
|
||
|
||
2026-09-15, Lilleman auf Larv.
|
||
|
||
`{/src.score}` renders exactly what `src.score` draws, and so do `{s.score}` where `s` is
|
||
bound to `/src` and `{p}` where `p` is bound to the column `place` of its own record, so it
|
||
takes that column's datatype and null rather than restating them, and a `datatype`
|
||
restating the one it takes is a second spelling. Any other `datatype` still types the values — the one way to
|
||
type a column someone else wrote.
|
||
|
||
## A typed column's calc is refused unless proven
|
||
|
||
2026-09-15, Lilleman auf Larv.
|
||
|
||
Operand bounds must keep each divisor from zero and the result finite; what they cannot
|
||
show is refused rather than trusted, since a bare `NaN` breaks the JSON and SQL it lands
|
||
in.
|
||
|
||
## `Column` carries text, not a Go value
|
||
|
||
2026-09-15, Lilleman auf Larv.
|
||
|
||
`Value` is the rendered string beside `DataType` and `Null`, which each serializer
|
||
writes as the load check proved it; a `Value any` would hand every caller a type switch.
|
||
|
||
## A unit takes the stem of what it is, and a file the stem of the units it holds
|
||
|
||
2026-09-29, Lilleman auf Larv.
|
||
|
||
Goal 3 wants a name to reach one unit: `drawMemo` is what a draw is kept in, `readValue` what
|
||
a read drew.
|
||
|
||
## One name, one meaning
|
||
|
||
Two declarations of any kind — function, method, field, type — share a name only where one
|
||
definition covers both: the same kind of value, counted or addressed the same way, so a
|
||
reader landing on the wrong one concludes nothing false, as `Fake` renders one value on
|
||
`Generator`, `Template` and `RecordTemplate`. `TestNoFunctionSpellsAMethod` keeps a bare function off a method's
|
||
name, since a search returns both and a call site shows no receiver; review judges the
|
||
rest. Serves goal 3: names that tell the truth, and reaching the unit behind a symptom
|
||
without asking a person. Decided 2026-09-25 by Lilleman; valid while goal 3 counts reading
|
||
cost.
|
||
|
||
## The performance gate asserts allocations, not wall-clock time
|
||
|
||
2026-09-03, Lilleman auf Larv. Goal 13.
|
||
|
||
`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
|
||
almost always costs an allocation too (a lost pre-size, a per-item map, an extra copy).
|
||
The benchmark suite (the README's Development) reports time for a human, not as a pass/fail gate.
|
||
|
||
A case guarding against one allocation per render, such as a render's scope reaching the
|
||
heap through `pickKey.under`, holds at its baseline with no margin: from a baseline of
|
||
10, a 10% margin lets that allocation through. Lilleman auf Larv decided it on 2026-10-06.
|
||
|
||
## Rows live in a TSV, the shape in JSON
|
||
|
||
2026-09-17, Lilleman auf Larv. Goals 8 and 13.
|
||
|
||
`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
|
||
the JSON says only how a row is composed. The TSV sits beside its category file, named
|
||
by `rows`, so a data directory stays a directory of categories, and one nothing names is
|
||
a load error.
|
||
|
||
## A selector is bracketed, and a dot inside it is literal
|
||
|
||
2026-09-17, Lilleman auf Larv.
|
||
|
||
`municipality[0180]` reads as selection to anyone who has indexed an array, and `[St.
|
||
Louis]` keeps a name whole where a colon or a dot-separated spelling could not; zsh
|
||
needs the brackets quoted, which the README's examples show.
|
||
|
||
## `parent` names the link column and the table alike
|
||
|
||
2026-09-17, Lilleman auf Larv.
|
||
|
||
One word says both, so a child table sits in its parent's folder and links on a column
|
||
of the parent's name; the geo plan wants exactly that, and a table needing another
|
||
folder or another column name would be asking for a second spelling.
|
||
|
||
## After a row, a path names a column or a linked table
|
||
|
||
2026-09-17, Lilleman auf Larv.
|
||
|
||
`locality[Lund].address`, a template beside the family under a selected row, is refused
|
||
today; admitting it later is additive, since a refused spelling gains a meaning and no
|
||
accepted one changes, so the door stays open for the address records the plan describes.
|
||
|
||
## A selected row is fixed, not drawn
|
||
|
||
Decided 2026-09-27 by the maintainer, for goals 4 and 5; valid while a selector names
|
||
exactly one row.
|
||
|
||
Each selector reads the row it names: `{/misc.territory[SE].capital} / {/misc.territory[FI].capital}`
|
||
renders `Stockholm / Helsinki`, and two items of one choice may select different rows.
|
||
|
||
## `..` steps up to the parent row, and a step down after it draws afresh, or once per name's pick
|
||
|
||
2026-10-05, larv-review in PR #158. Serves goal 5.4, which has `..` go up one level, and goal
|
||
5.2, which has each `{…}` make a new pick.
|
||
|
||
- `..` names the parent table next, `city..country`, as a folder path names the folder it
|
||
enters. `.country` after a row stays the link column's cell.
|
||
- A step down after a `..` draws afresh inside the row stepped up to (`drawSteps`). Under a
|
||
name, the paths stepping down from one `..` share that draw through the pick's memo
|
||
(`drawMemo.stepDownPins`), as every level a name's reads pass is shared, so two reads of
|
||
one step down describe one row.
|
||
- A route through another row is another path to the same data, not a second spelling:
|
||
`city[Oslo]..country.name` and `country[NO].name` both load.
|
||
|
||
## A table selects by one key column, a code, never a free-form name
|
||
|
||
Decided 2026-09-27 by the maintainer, for goal 5.5: a name may repeat across rows or change
|
||
with its register, while a code selects one row for good. Valid while
|
||
the key is the code a user writes, as a database keys a table. `country[NO]` loads, and
|
||
`country[Norway]` is refused naming `[NO]`, found by the row whose cell spells the
|
||
selector. The key is the friendliest code the register holds, `misc.territory[SE]` and
|
||
`misc.currency[EUR]`. There is no `name` option, and a table nothing selects into, a name
|
||
table, `street`, `car` or `useragent`, carries no key and is only drawn.
|
||
|
||
- `geo.US.locality` keys on the Census GEOID, `[1714000]` for Chicago.
|
||
- `geo.SE.locality` keys on an integer the import assigns, since a postort has no code of
|
||
its own. The first import numbers the postorter in name order; later imports keep each
|
||
id by the postort's name, unique across Sweden as the key it replaces, give a new
|
||
postort the next unused integer, and retire a removed or renamed one's id for good.
|
||
|
||
The code still takes a `name` option and selects by it; `todo.md` item 41 brings it to this decision.
|
||
|
||
## A parent row with no child row is a load error
|
||
|
||
2026-09-17, Lilleman auf Larv.
|
||
|
||
A descendant is drawn inside the nearest pinned ancestor, so every ancestor row must
|
||
lead to a row at every level below it, or a render could find nothing to draw. The
|
||
import script drops or fills such rows; the alternative, falling back to a free draw,
|
||
would break the consistency the link exists for without saying so.
|
||
|
||
## A table is a record of string columns
|
||
|
||
2026-09-17, Lilleman auf Larv.
|
||
|
||
Its columns are the CSV header and the `INSERT` column list, fixed by the TSV header, so
|
||
a table is a record by construction; every column is a string until a typed column
|
||
option earns its place.
|
||
|
||
## The key map is built at load, the rest on first draw
|
||
|
||
2026-09-28, Lilleman auf Larv.
|
||
|
||
A link is proved against the parent's keys and a key's uniqueness is a data mistake, so
|
||
both are load-time; the row lookup, by name and by parent, serves only a draw, so it waits
|
||
for the first one, keeping `New` linear in the bytes read. On 2026-10-05, building every
|
||
shipped lookup at load added 13 ms and 3.2 MiB to a 60 ms `New`. That cost grows with every
|
||
locale and with item 78, and most tables are never drawn by name or parent. The maintainer
|
||
kept building the lookup on first draw, once per table.
|
||
|
||
## Two categories may name one TSV
|
||
|
||
2026-09-17, Lilleman auf Larv.
|
||
|
||
Each is a view of the file with its own format and options, at the cost of holding the
|
||
rows twice, which is what a category over a register with two natural formats asks for;
|
||
a TSV nothing names stays a load error, since that one is a file forgotten rather than
|
||
shared.
|
||
|
||
## A cell may read a reference, and each row its own
|
||
|
||
2026-09-29, Lilleman auf Larv.
|
||
|
||
Goal 4: facts that belong together come from one draw, so a shop row's `phone` cell reads
|
||
`{/sv_SE.phone}` on a Swedish shop and `{/en_US.phone}` on a US one, and the table's
|
||
format, the same for every row, cannot say that. Valid while a table's rows need
|
||
generators of their own.
|
||
|
||
## A path is walked once without drawing before it is walked for real
|
||
|
||
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
|
||
walk per call, compiling its steps into a stack buffer of 16. Valid while that walk
|
||
costs little against the draw it guards.
|
||
|
||
## A country's postal codes and streets are siblings under its locality
|
||
|
||
2026-09-18, Lilleman auf Larv.
|
||
|
||
No open source pairs a Swedish street with its postnummer, and pairing the US through
|
||
its ZIPs would shape the two trees differently, so both draw inside the pinned locality
|
||
and an address agrees at that level. A street's own code is the exact pairing to add
|
||
when a source carries it.
|
||
|
||
## A locale's `address` reads its country's `geo` tree, so a locale folder is no data set on its own
|
||
|
||
2026-09-24, Lilleman auf Larv.
|
||
|
||
`data/sv_SE` alone no longer loads: a test loads `data` and prefixes the locale, and
|
||
`--no-shipped-data -d` takes the whole `data` folder or a set of one's own.
|
||
|
||
## The default embed holds every Swedish postort the import can place and give a street-delivery code and a street, and the US places of 25,000 or more
|
||
|
||
2026-09-24, Lilleman auf Larv.
|
||
|
||
Sweden fits whole in 700 KB; every US place of 10,000 would pass a megabyte and fetch
|
||
1,200 counties of TIGER files, so the threshold sits where the two countries match in
|
||
size, and `--min-population` and `--streets-per-locality` on the import scripts build a
|
||
fuller set. Loading the whole shipped set took about 45 ms, about 20 ms of it the two
|
||
trees.
|
||
|
||
Withdrawn by the maintainer on 2026-10-03; `todo.md` item 78 replaces it.
|
||
|
||
## A locale's `address` binds its country's record once and prints it whole
|
||
|
||
2026-09-18, Lilleman auf Larv; revised 2026-10-05. Goal 4.1.
|
||
|
||
A record cannot read another whole and keep its columns, so `sv_SE.address` names the
|
||
same four columns as `geo.SE.address`. Its format binds one name,
|
||
`{/geo.SE.address as a}`, and prints it whole as `{a}`. Each column reads one part of it,
|
||
`{a.street}`, so the country's format is written once and every column describes the
|
||
address printed. Valid while a record cannot read another whole and keep its columns.
|
||
|
||
## A postort's kommun comes from its name, its tätort or its codes, never from distance
|
||
|
||
2026-09-24, Lilleman auf Larv.
|
||
|
||
GeoNames leaves a fifth of Sweden's codes without a kommun and carries stale spellings;
|
||
the nearest code across a border named the wrong kommun half the time it was tried, so a
|
||
postort none of the three rules place is dropped, as is one not cased like a place name.
|
||
|
||
## A highway designation is not a street, and a US postal code belongs to the place holding most of its land inside places
|
||
|
||
2026-09-24, Lilleman auf Larv.
|
||
|
||
`I- 55 Bus` and `US Hwy 1` carry the most address ranges in many places and would head
|
||
every address, so the import drops names spelled as a route. A ZCTA goes to the place
|
||
its largest in-place part lies in, census-designated places left out since they never
|
||
ship, and ships only when that place does, so a few dozen places whose every code lies
|
||
mostly in a bigger neighbour ship no address; counting the land outside every place too
|
||
would drop a quarter of the places, whose codes straddle unincorporated land, for a
|
||
postal city the USPS mostly names the same way.
|
||
|
||
## `--list` stays a plain list of paths
|
||
|
||
2026-09-18, Lilleman auf Larv.
|
||
|
||
It is what a script reads, so every line has to be a path that `Fake` takes; a marker
|
||
for the tables a `[selector]` follows, or a legend above them, would make the output
|
||
something to parse before use. `--help` names the selector spelling instead, and the
|
||
Table section teaches it.
|
||
|
||
## A layout is always quoted
|
||
|
||
2026-09-18, Lilleman auf Larv.
|
||
|
||
A layout may carry the comma that separates arguments, `'January 2, 2006'`, and one
|
||
spelling for every layout beats a rule about which ones need the quotes, so the bare
|
||
spelling is refused naming the quoted one. The layout is Go's reference time because the
|
||
library renders with it and a Go caller already knows it; its names are English, and a
|
||
locale's own month and weekday names are data.
|
||
|
||
## A title is a table under `sex`
|
||
|
||
2026-09-18, Lilleman auf Larv.
|
||
|
||
A prefix drawn apart would put `Mr` on a record whose `sex` column says `female`, which
|
||
is the disagreement the record exists to prevent; the tables this set already has are
|
||
what a title needs, so `en_US.title` links to `sex` as `first-name` does. Swedish has no
|
||
everyday sexed honorific, so `sv_SE.title` is a table as well but carries no `parent`.
|
||
Its weight column is `share`, not the `count` a name table carries, because the values
|
||
are a curated proportion rather than bearers anyone counted.
|
||
|
||
## A table owns the spelling of a selector on it
|
||
|
||
2026-09-18, Lilleman auf Larv.
|
||
|
||
A reference reaches a table by a path that carries no selector — `sv_SE.address` reads
|
||
`geo.SE.locality` through its template — so the walk that reached it cannot say where
|
||
a reader would type a selector. The table's own location can, which is why it keeps its
|
||
path, and why an error names `geo.SE.municipality[1281]` rather than `municipality[1281]`.
|
||
|
||
## No builtin reads the clock, so a date is bounded by days, never by an age
|
||
|
||
2026-09-18, Lilleman auf Larv.
|
||
|
||
An `age(min,max)` would make a seeded fixture change with the day it runs on, which is
|
||
what a seed exists to prevent; a birthdate for someone 20 to 60 is
|
||
`date(1966-01-01,2006-12-31,…)`, re-pinned as any fixture is.
|
||
|
||
## `misc` is what every locale shares
|
||
|
||
2026-09-18, Lilleman auf Larv.
|
||
|
||
A category whose facts differ by country belongs in that country's locale, read from the
|
||
register that country's own records use; `misc` takes only sources that are
|
||
international. NHTSA vPIC and Mobility Sweden's registrations are national, so they are
|
||
for `en_US.car` and `sv_SE.car`, and `misc.car` waits for an international source.
|
||
|
||
## A register's canonical spelling loses to the one its domain writes
|
||
|
||
2026-09-18, Lilleman auf Larv.
|
||
|
||
Where a source offers several spellings of one fact, the shipped one is what records in
|
||
that domain carry. `misc.timezone` reads `zone.tab` and not the `zone1970.tab` that
|
||
supersedes it, because the latter keeps one zone per set of countries that have agreed
|
||
since 1970: it spells Sweden `Europe/Berlin`, and no Swedish system writes that. For the
|
||
same reason `misc.language` takes the ISO 639-2 register's first synonym over CLDR,
|
||
which says "Chinese, Mandarin" for `zh`.
|
||
|
||
## `misc.territory` is the spine, and a `misc` table naming a territory links to it
|
||
|
||
2026-09-24, Lilleman auf Larv.
|
||
|
||
Where every territory row has a child the column is a `parent`, and the import drops the
|
||
child rows whose territory the set does not ship — 17 of `misc.timezone`'s, Antarctica's
|
||
ten among them. Agreement across a record is worth more than the last rows of a table
|
||
(goal 4.1), as the maintainer confirmed on 2026-10-03.
|
||
Where no such link can hold the fact stays a column. Layer your own `misc.territory`
|
||
over the shipped one and you must layer `misc.timezone` too, or the link fails at load
|
||
naming the row.
|
||
|
||
## A table whose register publishes no frequency draws evenly
|
||
|
||
2026-09-20, Lilleman auf Larv.
|
||
|
||
`misc.httpmethod`, `misc.port`, `misc.httpstatus`, `misc.mimetype` and `misc.tld` weigh
|
||
every row alike, so GET is a ninth of the methods drawn. Where a register publishes a
|
||
frequency it is read, as `misc.timezone` reads GeoNames populations and
|
||
`sv_SE.first-name` SCB bearers.
|
||
|
||
## `misc.timezone` weighs a zone by the people living in it
|
||
|
||
2026-09-24, Lilleman auf Larv.
|
||
|
||
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
|
||
`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
|
||
to 0. Valid while a draw weighted this way lands where people live.
|
||
|
||
## `misc.car` is one flat table, not a make linked to its models
|
||
|
||
2026-09-18, Lilleman auf Larv.
|
||
|
||
A row is a make and a model drawn together, so no render pairs a Volvo with a RAV4. Two
|
||
linked tables would reach the same pairs and add a selector nothing asks for; a make
|
||
alone is `misc.car.make`.
|
||
|
||
## `misc.territory` names its sovereign in a column, and there is no `misc.country` table
|
||
|
||
2026-09-24, Lilleman auf Larv.
|
||
|
||
ISO 3166-1 codes territories, so `territory` is the honest name, and `is_independent` in
|
||
the register gives each one's state. A second table of the 195 sovereigns would hold a
|
||
`DK` row beside the territory `DK` row, both carrying Denmark's capital, currency, flag
|
||
and TLD — two owners for one fact, drifting at the next import. Splitting the columns to
|
||
avoid that is worse: put `capital` on the territory alone and a country row can no
|
||
longer name Copenhagen. The column cannot be a `parent`, since a table is never its own
|
||
parent; a test proves every value names a row instead. A territory the register
|
||
records no state for stands alone, which is `EH` alone, and naming one for it would be a
|
||
claim fejkdata has no business making.
|
||
|
||
## `misc.loglevel` is a table keyed by POSIX's keyword, carrying the code
|
||
|
||
2026-09-27, Lilleman auf Larv.
|
||
|
||
A flat list of names carries neither the code a PRI encodes nor a selector reaching it,
|
||
and goal 4 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.
|
||
|
||
The data still keys it on the code; `todo.md` item 41 brings it to this decision.
|
||
|
||
## `misc.tld` keys carry the leading dot, where other tables key on a bare code
|
||
|
||
2026-09-20, Lilleman auf Larv.
|
||
|
||
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` item 44 ends.
|
||
|
||
## `misc.tld` is a table of its own, and `misc.territory.tld` stays a column
|
||
|
||
2026-09-20, Lilleman auf Larv.
|
||
|
||
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 2 wants them shipped. 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`
|
||
|
||
2026-09-18, Lilleman auf Larv.
|
||
|
||
A `parent` demands a child for every parent row, and ISO 4217 registers codes no
|
||
country's row can name: the funds codes (Mvdol, WIR Euro, US Dollar (Next day)), and VED
|
||
beside VES, both Venezuela's, of which a country row names one. Linking would trade the
|
||
register for the link, and `misc.territory.currency` already pairs a country with its
|
||
currency in one draw.
|
||
|
||
## An extension may name two media types
|
||
|
||
2026-09-18, Lilleman auf Larv.
|
||
|
||
`.xml`, `.rtf`, `.sub`, `.mpp` and `.ac` each name two rows of `misc.mimetype`.
|
||
Separating them would mean dropping a registered type, or naming one by an extension
|
||
that is not its own — `.mpt` is Project's template, not its document. Selecting such a
|
||
name is an error listing both keys; select by type instead.
|
||
|
||
## The Swedish ids draw Skatteverket's test series
|
||
|
||
2026-09-18, Lilleman auf Larv.
|
||
|
||
A Luhn-valid personnummer over a random birth number may be a living person's; 238 and
|
||
239 after any date are blocked from assignment, so the shipped `personnummer` and
|
||
`samordningsnummer` use those. They sit in a `birth-number` table under `sex`, not in a
|
||
column of it, so a pick of a sex can read the number, and `sex` keeps one shape across
|
||
locales. `todo.md` item 28 has `personnummer` read it that way, so the number agrees
|
||
with the person's sex. A samordningsnummer's day, the birthday plus 60, is
|
||
drawn from 61 to 88, valid in every month, rather than computed from the date drawn.
|
||
|
||
## The US given names come from a mirror of the SSA file
|
||
|
||
2026-09-18, Lilleman auf Larv.
|
||
|
||
ssa.gov refuses a client outside the US, so `names-us.py` reads a GitHub copy that ends
|
||
at 2020, which a count over the births since 1930 barely feels; `--names` takes the
|
||
official zip. The SSA's placeholder rows are top-1000 entries that name nobody, so the
|
||
import drops them by name rather than by a rank a regeneration would move.
|
||
|
||
## `List` advertises direct descents only
|
||
|
||
2026-09-17, Lilleman auf Larv.
|
||
|
||
`region.municipality.locality` is listed, and `region.locality` resolves too but is not:
|
||
the set of every descent 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.
|
||
|
||
## A path draws through its compiled steps
|
||
|
||
2026-09-30, larv-review on the comprehension round the maintainer approved on 2026-09-29;
|
||
replaces "The path walks are separate loops".
|
||
Goals 3 and 13: `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
|
||
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,
|
||
leaked `compiledPath`'s leaves and errors with the draw's pins, arm and memo, since Go tracks a
|
||
struct's fields as one, and a repeat of a reference path rose from 66 to 106 allocations.
|
||
`drawSteps` and `compileStep` take their state as parameters and a step holds no node, so no
|
||
benchmark gained an allocation. Valid while Go's escape analysis tracks a struct's fields
|
||
as one.
|
||
|
||
## A name lives in the category binding it, or in the repeat binding it, and is drawn on its first read
|
||
|
||
2026-10-03, larv-review in PR #145; approved by Lilleman auf Larv. Goals 5.3 and 5.6.
|
||
`bindNames` scopes a name at compile, and a render keeps one `pickFrame` per scope. One
|
||
field binds a name where another reads it, `"first": "{/person as p}{p.first}"` beside
|
||
`"last": "{p.last}"`, so the scope cannot be the template holding the binding; the category
|
||
is the smallest unit holding every field and record column. A choice's item binds no name,
|
||
since a read outside the item would see a binding another item leaves out. A pick is drawn on
|
||
its first read, so a record's columns, rendered in name order, read one pick whichever binds
|
||
it.
|
||
|
||
The scope is lexical. A read entering a category, by a reference, through a name or from
|
||
`Fake`, sees no frame (one render of a scope's picks) opened above it, so a category renders
|
||
the same whoever references it. Such a read opens the frames around where it lands: a read through a
|
||
name from its pick's memo, so `{n}` and `{n.path}` read one pick of each name inside, and a
|
||
reference or `Fake` fresh ones.
|
||
|
||
A pick keeps only the levels a read of the name addresses, so a template draws the same way
|
||
under a name as anywhere else (goal 5.2). A read of a name bound to a field stays in the
|
||
category's frames, since the field sits inside it.
|
||
|
||
Valid while names are read only inside the category binding them.
|
||
|
||
## comprehension floor: every dimension and the overall at 7.0 or above
|
||
|
||
2026-10-04, Lilleman auf Larv. Serves goal 3 and applies KISS.
|
||
|
||
Shipped under the floor by the maintainer's decisions of 2026-10-07: the CLI reading its template from stdin, with the scoring run and its ratchet waived for that chunk; and narrowing goal 5.7 to the refusals that catch a mistake, dropping the rest.
|
||
|
||
Last scoring run, 2026-10-07, on commit ad59967: Navigation 7.00, Locality 5.88, Shape 6.50, Self-sufficiency 6.13, overall 6.38. The nine-seat panel at depth 1, run beside it: Navigation 7.17, Locality 6.06, Shape 6.67, Self-sufficiency 6.56, overall 6.56. Against the runs on ac6158e, the scoring run's overall rose from 6.25 and its Shape from 6.38, while its Self-sufficiency fell from 6.38; the panel's overall held, and its Locality and Self-sufficiency fell by 0.05 each. Locality is lowest again, as at every panel since 2026-09-22. All 13 seats named the named-pick read path hardest: `readField`, `readName`, `readUnder` and `keeps`, with the pick keys `levelKeys` and `addressedKeys` build at load; twelve kept `doc.go`'s vocabulary open beside it. What the two runs found is filed in `todo.md`, and `todo.md` item 153 runs both again. Valid while `AGENTS.md` gates goal 3 at 7.0.
|
||
|
||
## The template engine stays the root package until item 107; what reads no engine type, a table's rows included, sits in `internal/`
|
||
|
||
2026-10-05, Lilleman auf Larv. Serves goals 3.2 and 3.4. Two architect reviews found the
|
||
engine's types, `template`, `table`, `arm`, `op` and `nameBinding`, bound in one cycle
|
||
because several passes each write part of the same structs; the template language's
|
||
recursion reads few of those types. Moving the engine while several resolve passes still
|
||
wrote each value would have spread that knot across packages, behind forwarding calls from
|
||
every public method. The draw state, the grammar, `DataType`, what a proof knows of a
|
||
value, the builtins, the reading of data files and a table's rows read no engine type, so
|
||
each sits in a package under `internal/`, behind the import list `imports_test.go` checks.
|
||
A rows table holds the engine table owning it as a value of a type parameter, so
|
||
`internal/rows` imports no engine type. Valid until `todo.md` item 107 moves the engine.
|
||
|
||
## Goal 5.1's `{…}` covers `{{`, `}}` and a lone `}`, so the goal names no escape
|
||
|
||
2026-10-05, Lilleman auf Larv. Serves goal 5.1: everything outside `{…}` prints exactly as
|
||
written. Yet `{{` prints `{`, `}}` prints `}`, and a lone `}` is a load error naming `}}`, as
|
||
the README's Format string section states. The maintainer ruled the escape implicit in the
|
||
goal: doubled braces and a lone `}` belong to the `{…}` syntax, so the goal names no escape and
|
||
a lone `}` stays an error. Valid while a template escapes a brace by doubling it.
|
||
|
||
## Goal 7.2 holds while a first template can be written with no escape; a literal brace still takes `{{` or `}}`
|
||
|
||
2026-10-05, Lilleman auf Larv. Serves goal 7.2. Goals are aims: it is enough that a first
|
||
template can be written with no escape and no options, as `echo 'name: {/sv_SE.person.last}' | fejkdata`
|
||
is. A template that prints a literal brace writes `{{` or `}}`, and the goal allows that. Valid
|
||
while `{{` and `}}` are the template syntax's only escape.
|