Repository navigation
Releases: OnlyWorlds/python-sdk
Releases · OnlyWorlds/python-sdk
Release list
onlyworlds 0.45.2
- The doctor's flag 4 counts a dependency as a link on a spine world. A spine draws its edges with
x_spine_depends_on, so on a world that carries the profile (anyx_spine_key, flag 13's test) a non-empty list is a link out from its element and a link in to each id it names. Before, every element of the OnlyWorlds Spine read as having no links in or out (348 of 348 on its first run through a crew seat); now 102 of 378 do, each with neither a link field nor a dependency either way. An empty list is no dependency. On any other world nothing changes. A test with a mutant watched failing.
onlyworlds 0.45.1
- The
.owworldreader's NFC is Unicode 15.1's own, built from the set's files. OW-18 folds every entry name with NFC, then simple case folding, and two names that fold to one refuse the file. The fold was already a table from the vendoredCaseFolding-15.1.0.txt; the NFC before it was the interpreter'sunicodedata.normalize, which is Python's Unicode (15.0 on 3.12, 16.0 on 3.14). Since schema-dist serial 57 the spec (§4.7) rules that the NFC is built fromUnicodeData-15.1.0.txtandCompositionExclusions-15.1.0.txt, vendored besideCaseFolding, never a runtime's. On 3.14 the old reader joined 20 code points that 15.1 leaves unassigned (Todhri, Tulu-Tigalari, Gurung Khema, Kirat Rai): U+11383 against U+11382 U+113C9 was refused as one name, and is two at 15.1. The same fold matches a private producer'sRECORDS.jsonentry to its directory (OW-8), so that follows too. onlyworlds.worldfile.nfc, UAX #15 from the two files: full canonical decomposition (Hangul by arithmetic), canonical ordering, canonical composition skipping the listed exclusions, singletons and non-starter decompositions. Its tables are_nfc.py, generated bycodegen/generate_nfc.pyas_casefold.pyis bygenerate_casefold.py, so the wheel needs no file fromcodegen/.worldfile.pyno longer importsunicodedata.- Tests: every line of Unicode's
NormalizationTest-15.1.0.txt(19,074 lines, vendored undertests/fixtures/, 2.6 MB, its sha256 checked) holds both NFC invariants, and every code point Part 1 does not list is its own NFC; the 20 code points stay apart in a world file (and are checked joined underunicodedata16.0 or later, so the test is live); the generated module is current; the.owworldset holds at 93 of 93. Mutants, each restored: composing an exclusion, skipping canonical ordering, ordering by code point, dropping Hangul composition, removing blocking, and one-level decomposition each fail the NormalizationTest test; skipping ordering and ordering by code point also fail the set (fail-OW-18-nfc-reorder,pass-OW-18-nfc-same-class); the oldunicodedatacall fails the 20-code-point test.
onlyworlds 0.45.0
- The doctor's flag 13, the spine (OW Spine #614's last half: "the doctor lints the Spine world against it"). On a world where some element carries an
x_spine_key (any other world skips the flag and says so), every element is read byread_spine_element. A listed type whose supertype is off the spine's list or empty is a finding; so is each value the reader drops (its key and value) and a probe that is not in the probe form. Then the audit's definite parts: each row (an Object, Construct, Location or Law of a listed kind, the world's Charter included, since the page exempts none) must be kept by exactly one Title with supertypeSeat, through the Seat's field for that type; no seat, or two, is a finding. Everyx_spine_depends_onid must be in the world (not checked through a guest key: an id a guest cannot see is not a missing one). Never checked, and named so in the report: alegacyorprovenancerow's Retirement Event (the page names it by the row's text, which no structure attributes), whether a probe passes, andx_spine_probed. Known-of stubs are not read. Run on the landing's 360 rows as a folder, it names the 21 probes written as prose; every row reads "no seat keeps it" until the seats' Titles land. - Schema repinned to schema-dist
v0.33.0-dist.57(from dist.56; same canonical 00.33.00, no schema file moved). The.owworldset grows to 93: eight OW-18 cases on NFC, with Unicode 15.1.0'sUnicodeDataandCompositionExclusionsbesideCaseFolding.read_world_fileagrees with all 93. Its NFC is the interpreter'sunicodedata(Python 3.12 ships Unicode 15.0, 3.14 ships 16.0), not a normaliser built from the set's files; the eight cases use characters every one of those versions treats alike.
onlyworlds 0.44.1
- Schema repinned to schema-dist
v0.33.0-dist.56(from dist.55; same canonical 00.33.00, no schema file moved). The one serial settles five readings ofprofiles/spine.mdthat the first reader found ambiguous, and the fixture grows to 40 cases. SpineRead.droppedentries are{"key", "value"}dicts (were"<key>: <JSON>"strings), the value exactly as the element held it, a copy. The page now comparesdroppedas JSON values, never as text, because Python and JavaScript write JSON differently. A caller that read the strings changes; aSpineReadthat dropped something is no longer hashable.- A non-string
x_spine_truthorx_spine_probeis named indropped(it was left out silently). The order is the page's: address, truth, access, status, layer, audience, depends_on, probe, verified. - Already so in 0.44.0, and now on the page: probe verbs are syntax (case-sensitive, single spaces),
verified.atmust be a real calendar day, and an emptydepends_onis stated. - Tests: the fixture is compared as canonical JSON, because Python's
==holdsTrue == 1; a dropped boolean stays a boolean; the page's key order; the probe form's syntax. Two mutants watched failing (a boolean written as1, a non-string probe not named).
onlyworlds 0.44.0
onlyworlds.spine, the spine profile's first reader (schema-distprofiles/spine.md, serial 55; OW Spine #614): a world that maps a software ecosystem, in the 22 types and nothing more.read_spine_element(type, element)reads one element (a body or aFolderElement) and returns aSpineRead: its kind (the closed list's spelling,off-listfor a listed type whose supertype is off its list or empty and for every type the list does not name,freefor a Character, an Institution or a Collective), eachx_spine_*key as read orNonewhen not stated (address, truth, access, status, layer, audience and its primary, depends_on, the probe's verb orunsupported, the verified date), and every value it left out, named indropped;as_dict()gives the fixture's shape. Supertypes and machine values are compared trimmed (Unicode White_Space, exactly) and by Unicode simple case folding, the room profile's definition andis_room_element's helpers, neverstr.strip()orstr.casefold().SPINE_KINDS(exported, asROOM_PAIRSis) is generated from the page's table into_schema.pyand checked against the fixture's cases at generation; the key value lists (SPINE_ACCESS,SPINE_STATUSES,SPINE_LAYERS,SPINE_AUDIENCE,SPINE_PROBE_VERBS,SPINE_FREE_TYPES) are typed in the module and tested against the page's rows. All 34 fixture cases read as theirreadblock. Not done here, as the page says: the world's audit (one owner per row, a depends_on id resolving, a Retirement Event), running a probe, and the calendar: the fixture's six calendar steps are the npm SDK's kernel's (4.70.0) to check, and this package has none. One choice past the reference reader: a verifiedatmust be a real day (2026-02-30is not stated, and dropped).- Schema repinned to schema-dist
v0.33.0-dist.55(from dist.54; same canonical 00.33.00, no schema file moved). The one serial addsprofiles/spine.mdandprofiles/spine.fixture.json(34 cases, six calendar steps) and the README's profiles row;_schema.pygainsSPINE_KINDSand moves its provenance lines (SCHEMA_DIST_TAG,SCHEMA_DIST_SERIAL55,SCHEMA_MANIFEST_SHA256). Tree fromgit archiveof the tag (commit 2937031); MANIFEST sha256 7b4db33d... matched against the raw file GitHub serves at the tag.FORMAT_VERSIONstays0.3.18. - The PyPI page links the docs, the source, the issues and this changelog (
[project.urls]; until now it linked nothing, so a person with a bug had no door from it).
onlyworlds 0.43.1
- Schema repinned to schema-dist
v0.33.0-dist.54(from dist.49; same canonical 00.33.00, no schema file moved, so_schema.pymoves its provenance lines only:SCHEMA_DIST_TAG,SCHEMA_DIST_SERIAL54,SCHEMA_MANIFEST_SHA256). Five serials in the vendored tree: 50 and 51,example/world-export.json's world block is the World whole, withoriginandx_names(keel #516, #571); 52,profiles/review.md's reader rule (a reader that hands a world on leaves proposed elements out unless asked); 53, the folder lint (walk/lint_folder.py) takeshiddenas a folder key, true or false (folder spec §4.8), and review.md's rule 4 on a malformed mark; 54,profiles/checks.md'swhere.typeas a list and reader rules 6 to 8 (a known-of stub judged by no rule, a pattern outside the shared subset not checked by name, the size cap no step limit), andchecks.fixture.json's rule 6 with four cases and three not-checked rows. The doctor already reads all of it: the fixture replays clean (7 rules, 17 cases, 39 expectations, 11 not-checked rows). Tree fromgit archiveof the tag (commit 6153745); MANIFEST sha256 82ba25c1... matched against the raw file GitHub serves at the tag.FORMAT_VERSIONstays0.3.18: the dist carries no folder-format stamp, and the package's moved by hand in 0.42.0. No API change. apply_checksjudges no known-of stub itself (the checks profile's rule 6, dist.54): a stub is neither scoped nor short. Until now only the doctor left stubs out before calling it, so a direct caller passing a guest's stubs got them reported short on fields that are unknown, not empty.- Tests: the door tests' lint checks (LegendKeeper's API and file roads, Kanka, World Anvil) tolerated one "
hiddenis not a field of" line per hidden element while the lint did not know the mark; now that it does, any lint line fails them (test_folder_lint_passes_with_the_hidden_mark, renamed from..._but_for_the_hidden_mark, in three files;lint_okandhidden_lint_ok), each watched failing against dist.49's lint. The doctor's checks tests hold dist.54's counts as their floor and require the fixture's null-reason control row, so the not-checked comparison runs both ways.
onlyworlds 0.43.0
draught(..., compact=)(keel #585, PR 198, live 2026-10-23 13:30):compact=Trueleaves out every section that is an empty list or object, andcutwhen it is empty; the frame (world,calendar,as_of,now,mode,policy) and every null stay, since a null is an answer. Sent astrueorfalse;None(the default) sends nothing. Anything but a bool is aTypeErrorbefore anything is sent, as the npm SDK's 4.69.0. The live wire test now callsdraughtwith all eight policy keys, so each is checked against the document's parameters.- A create sends the hidden mark (keel #587, PR 199, live 2026-10-23 13:30; folder spec §4.8).
create,upsertandbulknow sendhiddenas the element has it (CREATE_FIELDS;sanitize_payload(..., create=True)), so an element hidden in a folder is created hidden from its first moment, never visible for a moment. Until now the three stripped it, so such an element was created visible: a behaviour change. Only the owner's keys and a co-builder's may sendtrue; any other key gets a 403hidden_not_allowed(newApiError.is_hidden_not_allowed; a bulk slot's 403) and nothing is created; a server with the hidden mark off answers 422,paramhidden. An update never sends it:patch,proposeandpush's patches strip it as before (keel drops it from an update, and a PUT that replaces ignores it). A create retried after a lost answer reads the element back and takes it as its own only if the mark matches.pushcreates nothing, so its withholding stands: an element hidden in the folder and not in the baseline is in the baseline, so on the server and visible, and keel moves an existing element's mark only by its own route (PushResult.hidden, unchanged; the docstring now says why).stops_runis unchanged: push never sends the mark, so it never meets the 403. - A reader that hands a world on leaves hidden elements out (Skeld's ruling, OW Council h58 #11461, #11462; folder spec §4.8): an element carrying
hidden: trueis hidden from whoever the world is handed to, never from its holder.folder.is_hidden(exported) is the one test: any value of the mark but absent, null andfalse, so a doubt leaves the element out. The Markdown pack leaves one out unlesshidden=True(--hidden) onbuild_pack,pack_world,pack_folderandpack_api, counts it in00-world.md's opening lines (*1 hidden element is left out of this pack.*),PackReport.hidden_left_outand the command's line; kept, it is labelledhiddenunder its id line (pack.HIDDEN_LINE), on its index line and on every link to it (Cy (character, hidden)),PackReport.hidden_kept. Unlike a proposed element, one left out is never named: a link to it is left out and an in-prose reference keeps only its own label (PackReport.hidden_links_left_out), as keel leaves a hidden element out of every answer to a reader who may not see it. A hidden element counts as hidden, never as proposed or a room element. The OKF bundle the same: left out unlesshidden=True(build_bundle,export_okf,--hidden), no file, no index line, links to it left out (never aurn:uuid:), an in-prose reference its label, counted inOkfReport.hidden_left_outand world.md'sonlyworlds_export.hidden_left_out; kept, its frontmatter carrieshidden: trueand every link and index line to it says(hidden). A world with no hidden element packs and exports byte for byte as before.export_worldandwrite_foldercopy a world and keep the mark; the doctor shows a world to its holder and drops nothing. - The
.owworldwriter keeps hidden elements unless the file is a share (Skeld's ruling, OW Atlas h20 #11469; folder spec §4.8).write_world_fileandworld_file_bytestakeinclude_hidden=True: a world file is a copy, and a hidden element's file travels byte for byte.include_hidden=Falseleaves every element file inelements/orspatial/whose JSON object carries the mark out whole, nothing else rewritten (a link to it keeps the id, which dangles, as ruled). The count: private records have no report to follow (the writers return a path or bytes), so a list passed ashidden_left_out=receives the left-out files' paths, sorted; its length is the count. The two defaults differ on purpose: private records out unless asked, hidden kept unless the write is a share. - Tests: compact sent as true and false, absent sends nothing, a refusal sends nothing;
hiddenon create, upsert and bulk, none on a visible create, never on patch or propose, the 403 and the 422 raised once and not resent, a bulk slot's 403, the read-back with the mark; a push never sends the mark (the folder unhid it), and creates nothing for a hidden element new to the server; the pack and OKF each with a hidden element left out, counted and never named, kept and labelled, links to it either way, the command lines; the world file's copy byte-identical, the share's files and count, determinism either way; a staging case (an owner's hidden create unseen by a contributor, a contributor's refused; skipped without the staging keys, not run). 30 mutants watched failing, one per rule.
onlyworlds 0.42.0
- No tool name in a supertype (the schema owner's lane rule, Skeld, OW Market h40 #11385, #11386: "a converter never writes its own name into supertype or subtype; where a thing came from is the producer line and its
x_<tool>_keys, what a thing is goes in the type words"). Behaviour change in three doors: an untyped element came in as a Narrative with supertypekanka(a Kanka module with no OnlyWorlds type),notion(a row of an untyped Notion database) orlegendkeeper(a LegendKeeper page whose tags name no type, or two); each now comes in with supertypeArticle, as the World Anvil and Campfire doors already write. Nothing else moves: the subtype (Kanka's module folder, Notion's database title), the tags, thex_<tool>_keys and every id stay as they were, and a test pins each door's ids to those 0.41.0 gave (Kanka's and LegendKeeper's real exports, Notion's built fixture, LegendKeeper's recorded API answers). No other place in the package writes a door's name into a supertype or subtype. - LegendKeeper maps (
onlyworlds.legendkeeper; Skeld's ruling, OW Market h40 #11385; Boss's three cases, #11387; Seethe's measured placement, #11426, #11429). From the export file, a map document becomes a Map, named<resource>: <document>as a hidden page's Narrative is:widthmax_x - min_xandheightmax_y - min_yfrom the document'smapbounds, the bounds kept asx_legendkeeper_boundsandmax_zoomasx_legendkeeper_max_zoom;image_urlthe map'smapIdonly when it is an http(s) address, as given and never copied (the image lives on LegendKeeper's host and loads only as long as that account's asset does, which the report says); a bare asset id (the tutorial map's) kept asx_legendkeeper_map_id, never written as a URL;locationwhen the map's resource became a Location, elsex_legendkeeper_resourceits element id. A map document with nomapobject is a Map with no width, height or image, named so in the report, never zeros. Each pin becomes a Pin on its Map: its name, or, for a pin with none, the name of the element it places (what LegendKeeper shows), each named in the report;element_typeandelement_idthe element itslk://resources/<id>uri names; a pin with no uri, or one naming no resource of the export, a Pin with no element, named, never a dangling id. Its place: the bounds are image pixels, but LegendKeeper'sposis[y, x]in zoom-0 units, an image pixel divided by2^max_zoom, y counting down from 0 at the map's top tomin_y; OnlyWorlds counts image pixels from the bottom left, soxisx * 2^max_zoom - min_xandyy * 2^max_zoom - min_y, a half rounded up, the exact pair kept asx_legendkeeper_pos, and the report names each map's scale ("positions scaled by 2^2 = 4"). Measured on two pins of Seethe's quay map, an 800 by 600 image withmax_zoom2 (OW Market h40 #11426, #11429; Skeld #11428): the first, read at image pixel (127, 110) from the top left, is stored[-27.5, 32], which times 4 is (128, 110): x 128, y 490; the second, read at about (796, 524), is stored[-131.118, 199.314]: x 797, y 76. Both fit the scaled reading and neither the unscaled one. A pin on a map with nomax_zoomgets no x, y (the scale is not known, never guessed), keeps itspos, and the report names the map; its icon and label (iconGlyph,iconColor,iconShape,labelSize,labelVisibility,isSynced, any other) together asx_legendkeeper_pin. A hidden map document, or one on a hidden resource, is a hidden Map whose Pins are hidden; a pin whoseisHiddenis true a hidden Pin (no real file has a hidden pin or map yet: the tests plant them). Ids UUIDv5 oflegendkeeper:<project>:map:<resource>:<document>and...:pin:<resource>:<document>:<pin>. The API road keeps 0.41.0's behaviour: the API's map shape (bounds, pins) is not known, never read on the wire, so a map read through the API stays not imported, and the report now says why. - The third real export (Seethe's, a few hours after the first, the JSON type, her quay map with two pins: "The quay" on her page, and one near the map's right edge with no name and no uri, a Pin with no element named
Untitled pin (uhljd0gj)in the report): banked astests/fixtures/legendkeeper/the-quay-world-3.json, Boss's scrubbed copy (OW Market h40 #11434): the account'screatedByid and her map image's asset id each a same-length placeholder, LegendKeeper's tutorial asset ids and its authors' ids as exported. A test checks every uuid left on LegendKeeper's asset hosts is also in the first export (LegendKeeper's own tutorial content) or the placeholder. It becomes 113 elements: 2 Characters, a Creature, 29 Events, a Location, 5 Maps, 54 Narratives, 18 Pins, 3 Relations. The first export's folder is byte for byte 0.41.0's but for the supertype, its maps (4 Maps, 16 Pins) added. - Fixtures: no account identifier (Skeld's doctrine, OW Market h40 #11415; Boss's scrubs, #11420, #11430, #11434). The Kanka fixture is Boss's scrubbed copy, renamed
tests/fixtures/kanka/quay-export-world_20261023_074706.zip(the old name carried the username): the username, user id and campaign id each a same-length placeholder. The Kanka fixture's ids moved with the scrubbed campaign id (the door keys them on it); a real user's ids are unchanged: 0.41.0 run on the scrubbed file gives the ids the test now pins. The LegendKeeper.lkfixture is Boss's scrubbed copy too (the account'screatedByid a placeholder), and the door's folder from it is the unscrubbed file's, byte for byte. A new test (tests/test_fixture_identifiers.py) holds the Kanka username, the LegendKeeper account id and its map image's asset id by sha256 only, and fails if any word-start window of any fixture (zip members and gzip read through) or test file hashes to one; run on the unscrubbed exports it finds each. - Folder format v0.3.18: a stamp move, the spec's one change being the
campfire-exportword (Skeld, OW Market h40 #11413).FORMAT_VERSIONis0.3.18. - Tests: on the third export the quay map and its two pins at their measured places, the scale the report names per map, the bare-id map and its 16 unnamed pins (Wiki City x 3553, y 5445 at 2^6), the three map-less documents, counts, the lint, determinism; on planted copies bounds that do not start at 0 and half bounds, a map with no
max_zoom, a hidden pin, a hidden map, a pin naming no resource, a visible pin on a hidden page; the API road's map named; Kanka's untyped module on a planted copy; the ids of every door; no account identifier in any fixture or test file, by hash. 27 mutants watched failing, one per rule (the unscaled position, a guessed scale with nomax_zoom, and a blind hash check among them).
onlyworlds 0.41.0
- The Campfire door (
onlyworlds.campfire):python -m onlyworlds.campfire --file EXPORT.html OUT-DIR [--project NAME] [--plan]andimport_campfire(source, out_dir, plan_only=False, project=None)read Campfire's export (File, Export Elements, the html format) into a world folder written throughwrite_folder. No AI, no account, no new dependency (the Kanka door's HTML to Markdown). A documents door, said in its docstring (the schema owner's rules, Skeld, OW Market h40 #11368): Campfire's web app has no data export, only four documents of the same text (docx, pdf, html, rtf), and the door reads the html. Proven on one real export (Seethe's project on a free account, 2026-10-23; its bytes banked astests/fixtures/campfire/campfire-export.html): the document's title, then onesection.export-itemper element, its name, its description, its pages of panels (custom panels of Label / value pairs, text panels), an em dash for an empty value. What becomes what: each element heading is an element, its description block thedescription; each filled panel's Label / value pairs are kept asx_campfire_<panel>_<label>in lower snake case (a text panel asx_campfire_<panel>, a number as a number), an em dash or an empty paragraph read as empty and not written; no panel fills an OnlyWorlds field. The type: the file names no module (no Characters or Locations heading, in the html or the docx), so the module is read from each template's own panel words (TEMPLATE_MODULES: Basic Information / Place of Residence and its neighbours for Characters, Location Details for Locations, each read on the real file) and the type from the module by one table,MODULE_TYPES, mapped against the definitions; the module from the panels and the table are as the schema owner ruled (Skeld, OW Market h40 #11396): Characters Character and Locations Location, seen; Species and Cultures Species, Items Object, Languages Language, Religions, Philosophies, Systems and Magic Construct ("a magic system is how the world works, not a thing anyone does"), Maps Map, believed; Timelines, Arcs, Manuscript, Encyclopedia and Research stay unknown, and Relationships too until a real file shows one with two ends (then Relation). A module not in the table is a Narrative with supertypeArticleand the module's name as subtype; an element whose panels match no known module, or two, a Narrative with supertypeArticle. The report names, per element, the panel words its module was read from (which panel and label matched which module), or that none matched (ImportReport.evidence,ModuleEvidence). Ids are UUIDv5, since the file has none, and the file holds no links: the report says both on every run (a second export gives the same ids only while the names hold; a renamed element comes in new).--project NAME(project=; Skeld, h40 #11396): the file names no project, so a project name given keys every id (campfire:<project>:<type>:<name>, the worldcampfire:<project>:world) and names the world. Without it each element is keyedcampfire:<type>:<name>and the worldcampfire:world:<title>, named for the document's title, and the report says plainly that two projects exported under Campfire's default title "Campfire Export" would share ids, and that--project NAMEkeys them apart; a blank name is refused. One resolution: a Character's Place of Residence that equals a Location's name in the same export becomes itslocationlink, else stays thex_campfire_field; a name inside prose stays prose. No hidden rule: nothing in the file marks an element hidden, and the door says so. A second element of one type and name is keyed:2, an exact copy folds, text outside the parts read is kept asx_campfire_unread; all named.producer.inputiscampfire-export, the word on the folder spec's §4.2 list since v0.3.18 (Skeld, h40 #11396); the folder'sformat_versionstaysFORMAT_VERSION(0.3.17) until the next schema-dist serial moves it. - The real file becomes 2 elements: Ada Okafor a Character (
x_campfire_basic_information_full_name,_place_of_residence"The quay",x_campfire_bionaming Mara Voss, kept as prose) and The Ferry Quay a Location (four numbers Campfire printed as 0, kept as 0 and named: the file cannot tell a 0 from a number never set). "The quay" is not "The Ferry Quay", so no link: the negative is the real file, the link a planted copy. The dist's folder lint passes both. Tests: the fixture's hash and size, and that it carries no script, no form and no mail address (each pattern watched catching a sample); counts, types and every field; the em dash; the link and its two negatives (no such Location, not exactly equal); prose stays prose; the report's lines on ids, links, hidden, modules and the default title; each element's module evidence; the report naming everything not placed; the table pinned whole; without--projecttoday's ids (pinned as literals), with it every id keyed and stable across runs, two names two sets of ids, the world named, a second element of one name, a blank name refused, the flag on the command line and--planwith it writing nothing; the lint; two runs byte-identical; the round trip;--planwriting nothing; and on planted copies an element of no known module, a Magic module a Construct, a module not in the table, two modules, a missing panel, a second element of one name, an exact copy, unread text, a panel kind not yet seen, refusals and the command line. 26 mutants watched failing, one per rule.
onlyworlds 0.40.0
- The World Anvil door takes the schema owner's three rulings (Skeld, OW Market h40 #11360). The template table (Skeld's ruling): every row of 0.39.0 holds, and four are added on his reading of the definitions page: Ethnicity to Species, Myth / Legend to Narrative, Vehicle to Object, Profession to Title (believed: none yet seen in a real export). Anything else stays unknown. The unknown template (Skeld's ruling): still a Narrative, but with supertype
Article, what the thing is rather than where it came from (the producer line says that); the subtype is the template's name as World Anvil spells it, the template word still inx_worldanvil_template, and the report names each unknown template and its count. Behaviour change: an article whose template 0.39.0 left unknown came in with supertypeworldanvil; it now comes in with supertypeArticle, and a Vehicle, Ethnicity, Myth / Legend or Profession article now comes in typed. The extra fields and the dates (Skeld's ruling): a template field with no OnlyWorlds name staysx_worldanvil_<name>, as before; the article's three dates, which 0.39.0 left out, are now kept on the element under the export's own words,x_worldanvil_created,x_worldanvil_updatedandx_worldanvil_published(DATE_KEYS), as ISO 8601 parsed from what the page prints (23 Oct, 2026is2026-10-23); a date not parsed exactly (another shape, or no such day) is kept as printed and named in the report, never guessed. The author stays out of the elements and is named in the report only (one platform username for the whole export). The real files: the three articles of each now carry their three dates, 2026-10-23, and the report names the author once. - The account's handle is out of the package (Boss's find, OW Market h40 #11375): 0.39.0's
tests/test_worldanvil.pyspelled the export account's handle as a literal in its forbidden list, so the name shipped with the tests. The test now checks it by hash: no word-ish token of a fixture, lowercased, may hash to the handle's sha256, and only the digest is in the file; the check is proven on a planted stand-in name, in either case. The handle appears nowhere in the tree. - Tests: the table as ruled; the four added templates each typed; an unknown template an Article Narrative; the dates on the real files; a date not parsed exactly, kept and named; the parse exact (no 31 February); the author on no element. 9 mutants watched failing, one per rule.