16 KiB
Plan
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
templatestruct which pass fills which field, and thatcompileFormatruns a second time oncelinkTemplateRefsbinds the references: nine of its fields are written by four other files, and the order lives only inloadData's body. - State on
rowSetthat it simulates(*draws).pinandrowOf, 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
drawsand its methods in one file: it is declared inhold.go:262and all eight of its methods sit infamily.go, so a jump to the type lands where none of the behaviour is. - Split
hold.go,draw.goandfamily.goso each file holds one phase, the load-time fences apart from the render-time machinery, changing no logic. - Move
nodeScope,checkScopeandcheckRendersout ofgraph.goand refresh the README Layout lines forgraph.goanddata.go: the order every load fence runs in is the load path's spine, and no file name carries it. - Name the
drawstem apart: the freepinnedinhold.gois not(*draws).pinnedinfamily.go,compileFormatis a method on both*templateand*table, anddraws,drawSet,drawScope,drawWalk,drawAt,drawKey,drawVisitanddrawRouteno longer tell each other apart. - Spell one fresh draw set one way:
newDrawSetexists, andrender.goanddraw.gohand-rolldrawSet{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 12 asks.
- Add the remaining
misctables and templates, one row of the table below per chunk. - Read
misc.emojifrom the Unicodeemoji-test.txtregister, andmisc.carfrom an international make and model source; vPIC and Mobility Sweden are national, so they builden_US.carandsv_SE.car. - Add the remaining locale categories: company, phone, finance, vehicle, words.
- Give every shipped category building-block columns,
misc.uuid→variantamong them, and one record shape across the national ids; re-pin the shape. - Give the address records one column set across countries:
regionandmunicipalityas columns ongeo.SE.addresstoo. - Fill the 398 Swedish localities weighted 200 from SCB småorter.
- Give
urlandemaila path that draws only domains nobody can register, keeping the wide set as the default, per goal 14: 17 of the 40 distinct ones shipped today sit on.se,.nu,.ioand.co, which anyone may register, and only RFC 2606'sexample.com,.net,.org,.test,.example,.invalidand.localhostprovably reach nothing. - Audit the rest of the shipped set against goal 14 and give each a never-reaching
path where it lacks one:
phonedraws live PTS and NANP ranges,bankgiro,plusgiroandroutingdraw live prefixes. - Stop
misc.territory[EH].tldrendering.eh, the one shipped TLDmisc.tlddoes 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 3 promises otherwise. - Draw
en_US.phone'sexchas a NANP central office code:{int(100,999)}renders a leading 1 in about an eighth of draws, which libphonenumber rejects, andTestShippedUSPhoneproves the shape rather than the rule. Assert the rule with it. - Give a
sv_SE.personnummerover 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.ipandsv_SE.ip, today byte-identical: either onemisc.ipas the decision "miscis 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
personnummerinside 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 another category, and report a struct column's draw conflict with the path spelling a tag takes. - Let
geo.SE.locality[Lund].addressdescend from a selected row into the template 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 andusername's handles from the shipped name tables, which goal 12 asks for and the hand-written list they use today does not meet. Keep the handle shape:usernameshortens a surname toahlorsjo, and the US one draws unisex given names, neither of which falls out of the tables unaided. - Decide whether
email.localandusernameshare one list: a reference between shipped categories breaks a consumer once tagged, so it rides a 0.(x+1).0 after that.
misc (locale-neutral)
| Category | Shape | Source | Licence |
|---|---|---|---|
ip v4 documentation ranges, private, CIDR; ipv6 2001:db8::/32; mac locally administered |
t | RFC 5737, 1918, 9637 | facts |
creditcard per network: IIN, length, CVV, expiry |
T+t | network rules, {luhn()} |
facts |
bic, isin, cusip |
t | structure rules | facts |
ean, upc, gtin, isbn, issn, imei, asin, sku |
t | check-digit rules, GS1 demo prefix 952 | facts |
product name, adjective, material, department, garmentsize |
T/c | LLM-written lists | — |
airport IATA, ICAO, territory, municipality; airline; aircraft; flight, seat, pnr |
T+t | OurAirports, Wikidata | public domain, CC0 |
element, unit, planet |
T | PubChem, BIPM, NASA | public domain, CC BY 4.0 |
animal by class, plant |
T | Wikidata | CC0 |
bloodtype weighted (global) |
c | AABB | facts |
color named, hex, rgb, hsl, cmyk |
T+t | CSS Color 4, XKCD | W3C, CC0 |
programminglanguage, os, browser, database column/type/engine, fileext |
T/c | Linguist, curated | MIT |
commit message, branch, sha |
t | LLM-written, {hex(40)} |
— |
password, hash, jwt, slug, hostname, avatar, placeholderimage |
t | shapes | — |
book title, author; genre; instrument; dish, ingredient |
T | Gutenberg catalog, MusicBrainz, USDA | CC0 |
lorem, hacker, hipster, catchphrase, buzzword, quote |
T/c | lorem ipsum, LLM-written | — |
direction, continent, ulid |
c/t | — | — |
sv_SE (source research)
| Category | Shape | Source | Licence |
|---|---|---|---|
person title, birthdate, age, blood type weighted |
t | geblod.nu distribution | facts |
organisationsnummer by form, vat |
t | Bolagsverket group digits, Luhn, SE…01 |
facts |
company name patterns, legal form weighted |
t | Bolagsverket registrations 2025 | CC BY 2.5 SE |
sni industry codes |
T | SCB SNI 2025 | "Källa: SCB" |
ssyk occupations |
T | SCB SSYK 2012 | "Källa: SCB" |
phone mobile, fixed per area code, E.164 |
T+t | PTS numbering plan 2024 | facts |
bankgiro, plusgiro, bankaccount clearing + Luhn/mod-11 |
T+t | Bankinfrastruktur CSV, Bankgirot rules | facts |
licenseplate ABC 123, ABC 12A, blocked combinations |
t | Transportstyrelsen | facts |
car make, model weighted by registrations |
T | Mobility Sweden 2025 | cite |
word noun, verb, adjective, adverb; sentence |
T+t | SALDO | CC BY 4.0 |
month and weekday names, holiday |
t+T | CLDR sv, lag 1989:253 | Unicode |
territory localised names keyed by alpha2 |
T | CLDR sv territory names | Unicode |
en_US (source research, and part1–part4)
| Category | Shape | Source | Licence |
|---|---|---|---|
ein valid ranges |
t | IRS | facts |
company names, suffix, naics |
t+T | SEC tickers for patterns, NAICS 2022 | public domain |
phone NANP with valid NPA/NXX |
t | NANPA rules | facts |
routing ABA with check, bankaccount |
t | Fed prefix ranges | facts |
licenseplate per state |
T | hand-authored patterns | facts |
car make, model |
T | NHTSA vPIC | public domain |
word, sentence, paragraph |
T+t | Moby POS or WordNet | public domain / WordNet |
month and weekday names, holiday |
t+T | CLDR en | Unicode |
Builtins the data cannot express
- Add
{isin()}(Luhn over letters expanded to digits),{cusip()},{aba()}(3-7-1 weights) and{vin()}(position 9 over the whole; a sample taking the WMI, since the check sits mid-string). - Add
{base64url(n)}for JWT shapes. - Add a
unixlayout todate()once something needs it.
Library and CLI
- Name a spelling that works when a row selector misses:
misc.protocol[tcp],misc.httpmethod[get]andmisc.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 nonamecolumn could say it selects by key alone.misc.tld[se]misses on the leading dot its keys carry rather than on case, andmisc.loglevel[Error]misses although a column carriesError, so the near miss is worth naming whatever shape it takes. - Let a table column carry a
datatype, so--format jsonwrites"safe": trueand--format sqla boolean rather than the text'true'. Today only a JSON field takes one, somisc.httpmethod's booleans are typed in Go and text everywhere else. Typing a shipped column changes whatjsonandsqlwrite, so the capability is a minor and applying it tomisc.httpmethodis a major.
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, somisc/net/tldis a rename a consumer pays for, and goal 13 promises the directory keeps growing. - Decide whether
misc.browserbecomes a parent ofmisc.useragent, somisc.browser[Chrome].useragentresolves. Adding aparentafter v0.1.0 breaks a consumer, so it rides a 0.(x+1).0. - Decide whether
misc.timezonekeeps every zone tzdb names: one added recently,Europe/Kyivamong them, is rejected by a consumer resolving it against older tzdata, and the population weight makes that zone likelier, not rarer. Today the README names the action a reader takes instead; narrowing what ships is the alternative. - Decide whether
misc.territory.countryis renamedsovereign: it holds a code, andcountrycollides with themisc.countrypath it replaced. A rename after v0.1.0 breaks a consumer, so it rides a 0.(x+1).0. - Decide whether
parent: territorystays, given a--data-pathoverride ofmisc.territorynow failsNewunlessmisc.timezoneis overridden with it.
Release
- Move the README's Decisions section to
docs/decisions.md, leaving a one-line index of the titles inAGENTS.md, and drop theAGENTS.mdline pointing decisions at the README. Carry the timezone weight's premise in with it: weighting by GeoNames city population moved 300 seeded draws ofmisc.territory[US].timezonefrom 44 landing on the four zones most Americans live in to 278, and droppedAmerica/Indiana/Petersburg(pop. 2,400) from 17 draws to 0. - Rewrite
CHANGELOG.md's[Unreleased]as what v0.1.0 holds: nothing has shipped, so "no longer paths", "where it used to fail" and "where it used to be an empty string" describe versions no reader can have installed. - Ship prebuilt binaries so the CLI needs no Go: GoReleaser attaching them to the Gitea
release the tag workflow publishes, a container image, Homebrew and Scoop. A checkout
build prints
develfor--version; the binaries carry the stamped tag.
v0.2.0
- Ship the full registers as packs, each a Go module with its own
embed.FSand a zip for--data-path: every US place of 10,000 and more streets per locality, built by--min-populationand--streets-per-locality. The default embed stays under ~1 MB per country andNewunder ~50 ms. - Add the locales nb_NO, da_DK, fi_FI, de_DE, en_GB, nl_NL, fr_FR and es_ES, with
person, address, phone, national id, company and date names each, and the
geo/trees NO, DK, FI, NL, FR, AU, CA, ES, GB and DE theiraddressreads by reference; AU and CA get a tree with no locale of their own. - Add
{btc()}and{eth()}, which need sha256 and keccak for Base58Check and EIP-55. - Pair a street with its exact postnummer, which 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.
v0.3.0
Names no items yet, so planning it is a chunk of its own: it takes the additive work that lands after v0.2.0, and it has to add something, since a minor only adds. Its job is to be the release with no breaking change that v1.0.0 waits on — v0.2.0 cannot be, 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: 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
- Stop
/cachecollecting root-owned files, which today every command documented without--userleaves for the next--userone to trip over, and which the README answers with a chown a reader has to repeat. Put--useron all of them, or give thex-goanchor auser:— that one needs a mechanism, since Compose interpolates from the process environment and neitherUIDnorGIDis exported there, souser: "${UID}:${GID}"resolves to":". - Publish a homepage with an in-browser generator: the library compiled to WebAssembly, so visitors generate as much data as they like in their own browser.