rmoriedata 0.4.3
-
hostload()returns a table as its file holds it. It read the file withread.csv()’s defaults, so codes lost their leading zeros (046became the number 46), column names were rewritten (Offence DescriptionbecameOffence.Description) and the textNAbecame a missing value. Now the names are kept as written, only an empty field is missing, and a column becomes logical, integer or double only when every value in it is written the way R writes such a value; any other column stays text. Text is marked UTF-8 in every locale (it was unmarked in a non-UTF-8 locale), and bytes that are not UTF-8 are marked"bytes". The help page says exactly which values count as numbers. - A table already cached and verified loads offline once the cached manifest is more than a day old: when the manifest cannot be fetched again, the cached copy is used with a message, and the table is still checked against the digest it lists.
hostload()failed with a network error. -
hostload()checks a cached table under the same lock as a download, so it can no longer delete a copy that another R session has just refreshed. A session waiting for another session’s download stops with an error once that download has not grown foroptions(rmoriedata.lockwait = )seconds (600 by default); it waited for ever behind a stalled session. - A failed download from data.rmorie.com names the function called (
hostcat()orhostload()), the table and the address, without the dependency’s own function name in front (bldl:). - A privacy budget copied within one R session (written with
saveRDS()and read back, passed throughserialize(), or rebuilt from its fields) is refused like a budget from another session: the copy and the original could each spend the whole budget.dpbudget()’s help says what is and is not prevented. - The Parquet reader refuses a string that is not valid UTF-8, or that holds a NUL byte, with a
parquet:error naming the column: it marked such bytes as UTF-8, andnchar()ortoupper()failed later.columns = character(0)gives the file’s rows with no columns (it failed with “footer says 3 rows, decoded 0”), and a damaged footer length is reported as damage, not as a file over the row cap. - The native Parquet reader is about 90 times faster and the writer about 8 times (on a 400,000-row table of 13 columns, 3,500 and 4,400 rows a second became 326,000 and 35,000): string values are read and written in compiled code, and the other per-value loops work on whole vectors. The bytes written and the values read are unchanged.
-
chiload(full = TRUE)reads text as decimal numbers only, so"0x1A"is no longer 26 and"Inf"or"1e400"no longer an infinite coordinate. A value in Socrata’s export that does not fit its column becomesNAwith one warning per column, instead of refusing the whole download; a mirror’s or the cache’s file in the sample’s columns must still fit exactly, and the error names that file (it blamed Socrata’s export). -
mdask()no longer probes or asks an Ollama server the user did not choose: with the automatic route, a local server atlocalhost:11434is left out unlessroute = "ollama", anOLLAMA_*variable or a saved Ollama setting names it. -
cihiget()checkstimeoutunder its own name and takes 1 to 86400 seconds (0.5 was refused as “tmoutmust be a positive number”), and a failed download namescihiget(), not the dependency’sblfetch(); strings marked"bytes"passed tokanonvrf(),ldivvrf(),mdload(),mdpath(),mddict(),cihiget(),hostload()andmdask()are refused in words, where base R stopped with “translating strings with”bytes” encoding is not allowed”. -
mdload()’s help says how the Parquet and CSV copies differ: in five tables one or two doubles of the Parquet copy are the next double to the CSV’s value. -
kanonvrf()andldivvrf()compare every numeric column exactly, whatever its class:difftime,I(),Date,POSIXct,POSIXltand complex columns were compared to 15 significant digits, so two distinct values (such as two times a microsecond apart) formed one class and a violated k could be reported as satisfied. A fractionalDateis now compared by its stored value, not its printed day. Columns that cannot be compared exactly (bit64::integer64, list and matrix columns) are refused with an error naming them. -
kanonvrf()andldivvrf()build classes column by column, so strings holding control characters can no longer join two different rows into one class, and a factor level that isNAis missing while the string"NA"is a value (they were one class). As a sensitive value, anNAfactor level is withheld and no longer counts towards diversity. - A privacy budget of
delta = 0refuses every release withdelta > 0: a release atdelta = 5e-13was accepted. The check of the totals allows only floating-point rounding, relative to the budget, and never a release more than twice what remains: a budget ofepsilon = 1e-13paid for a release at1e-12. - A privacy budget saved and read back in another R session is refused even when that session has the same process ID, as every
Rscriptstarted as a container’s entrypoint does: each budget carries a random token drawn from the operating system when the package loads. Budgets saved by earlier versions are refused too.dpbudget()’s help says that a release interrupted after it is charged stays charged. -
chiload(full = TRUE)reads a text time with an offset orZ(from a mirror) as that instant:2024-06-01T10:00:00Zwas read as 10:00 in Chicago, five hours off. A value that does not fit its column’s type is refused with an error naming the column, from a mirror and from Socrata’s export alike: text"1.5"became the integer 1,2becameTRUE, and junk in Socrata’s numeric columns becameNA. - The Zstandard decoder refuses a block whose sequence count of 0 is followed by more bytes (libzstd does too), and reports one to three stray bytes after the last frame as trailing data rather than as a truncated frame. The zstd, gzip and Snappy decoders refuse, before allocating, a size that this build of R cannot hold.
- A hosted table downloaded in parts checks that every part is there and readable before it joins any: a missing or unreadable part stopped with a base-R warning after the earlier parts had been deleted; now all parts are kept and the next run downloads only what is missing. Two sessions joining the same table at once each write a file of their own instead of sharing one.
- R sessions downloading the same hosted table at once take turns on an operating-system lock that the system releases when its holder exits or is killed, so it is never left stale: one session downloads, checks the table against the manifest’s SHA-256 and only then moves it into place in one step, and the others wait and use it. The sessions shared one
.partsfolder and could delete or overwrite each other’s parts, and an unchecked table sat under its final name until its checksum was read. A download that is cut short or killed still resumes from the parts it finished. The manifest is written under a name of its own and renamed, so sessions fetching it at once no longer write into one file. - The Parquet writer refuses matrix, data frame and
integer64columns, naming them: a matrix column was written with more values than rows, which other readers cut short. Dates and times are written down to the whole day and millisecond as R prints them, so aDateof -0.5 (printed 1969-12-31) no longer reads back as 1970-01-01, and dates or times outside what Parquet holds, or infinite, are refused. - The Parquet reader reads ZSTD-compressed pages, such as those of files written with
compression = "zstd"by nanoparquet, arrow or DuckDB, with no other package: the Zstandard decoder is in the package, written from RFC 8878 (no dictionaries), and checks every page against its stated size, and against the frame’s checksum when it has one. Such a file stopped with “compression codec 6 not implemented”. The writer still writes GZIP, Snappy or no compression. - A hosted table downloaded in parts no longer comes out damaged when the disk fills up while the parts are joined: the failed write is an error that names the folder and
options(rmoriedata.cache_dir = ), where it was a short file that then failed its checksum. Each part is deleted once it is joined, so the download needs the table’s size plus one part on disk, not twice the table’s size. - The title, description and citation put the package’s disclosure-control tools first (differential privacy, k-anonymity and l-diversity checks, cell suppression); the datasets follow. Nothing in the functions changes.
-
chiload(full = TRUE)returns the bundled sample’s columns, in its order and with its types (16 columns for complaints and 8 for arrests, withdate_isoand aPOSIXctdate), derived from Socrata’s export the way the samples were: it returned Socrata’s raw 22 or 24 columns. Socrata’s export is read as text first, so an IUCR code keeps its leading zero. -
chiload(mirror = )with a URL downloads<type>_full.parquetfrom the mirror over the package’s checked https transport, and checks it against<type>_full.parquet.sha256when the mirror has one; a mirror that fails or does not match falls back to Socrata. A URL was taken for a missing local file, so the mirror was never used. -
chiload()checks its arguments in words:limitmust be a whole number from 1 to 2147483647 (Infand1e10failed with a coercion error),fractiona number above 0 and at most 1, andfullandrefreshTRUEorFALSE(full = "yes"orNAsilently returned the sample). -
cellsupp()checks which hidden cells the totals determine with one factorisation for all of them, not one per cell: a 50x50 table took 152 seconds and a 60x60 table did not finish in 17 minutes; both now take well under a second, with the same cells suppressed. Negative counts are refused, and the help says that zero cells are published as they are. -
mdload(slug, refresh = TRUE)checks the file against the signed manifest again: a bundled file changed after its first load in a session was still returned. - A cached
hosted/manifest.jsonthat does not parse is fetched again, once, with a message; it blockedhostcat(),hostload()andhostload(refresh = TRUE)until it was a day old. -
hostload()takes a large table whole when the server answers a byte-range request with the whole file, instead of failing on every run, and a failed run leaves no empty.partsdirectory. - A cache directory that cannot be written (read-only, or a file in its place) is an error naming that directory; it was reported as a failure of data.rmorie.com.
-
dplaphist()keeps the names and dimensions of its counts, so atable()’s labels stay on the release. - The Parquet reader reports any damaged file with a
parquet:message: about 4% of corrupt files stopped with a base-R message such as “subscript out of bounds”, and a footer of 5000 nested lists overflowed the stack. Its help, andchiload()’s, say that aPOSIXctcolumn reads back in UTC (the same instant): Parquet keeps no time zone. - Extreme arguments are refused in words:
korlofInfor above the integer range inkanonvrf()andldivvrf(), anepsilonabove 2^20 in the counting mechanisms (both stopped with “missing value where TRUE/FALSE needed”), aslugthat is not one name inmddict().mdsha()takes one string (it hashed only the first of several) and hashes it as UTF-8 whatever encoding it is marked with. -
dplapcnt(),dplaphist()anddpgmean()draw fresh random bytes in a forked worker (parallel::mclapply()): the workers inherited the parent’s buffer of random bytes and released identical noise, so the difference of two releases was the exact true difference. -
chiclr()deletes only whatchiload()wrote (the<type>_full.parquetfiles, their checksum files and unfinished writes): it removed everything in the cache directory, subfolders and the hosted-table cache included. -
cellsupp()refuses a table of three or more dimensions (it was flattened to one column and suppressed against the wrong margins).ldivvrf()counts a class’s values in one pass: 200,000 classes took minutes. - A refused download reads the status the server answered: a table whose name holds 401 or 403 no longer turns a 404 into “need no sign-in”, and a 410 names ICPSR only for an ICPSR table.
- Needs rmoriebricklayer 1.1.1 or later.
-
hostlogin()is gone, and no key is read or sent: every table at data.rmorie.com is open, so the sign-in asked for a name, a reason and an email or GitHub account for nothing.hostcat()andhostload()work as before. A key that an earlier version stored stays in its file, unread. The manifest and services fields are read by exact name. -
hostload()checks every table against the manifest’s SHA-256 again, whatever the field is called (sha256_gz,sha256orsha, as rmoriebricklayer reads it): 0.4.2 readsha256only, so 631 tables listed withsha256_gzwere used unchecked and a modified cached copy loaded. A digest field that is not a SHA-256 is an error, and a table listed without any digest loads with a warning, once per session. - Tables whose source terms do not allow redistribution (the ICPSR studies, the NCVS 38604 extract among them, and any table the manifest marks restricted) are left out of
hostcat(), with a message counting them, andhostload()refuses them before any request. The A2AJ case law is an ordinary open table. - The messages for a refused download no longer suggest signing in: the tables need no key.
-
dplapcnt(),dplaphist(),dpgmean()anddpbudget()refuse an infiniteepsilonin words;dplapcnt(5, Inf)stopped with “missing value where TRUE/FALSE needed”. - The signing test writes its XMSS used-index record to a temporary file, not to
~/.config/R/rmoriebricklayer. - The vignette’s reference to the integrity checks is no longer empty.
- A privacy budget from
dpbudget()cannot be spent from a forked worker or another R process:dplapcnt(),dplaphist()anddpgmean()refuse it there with an error. Four workers underparallel::mclapply()each spent 0.9 from one budget of 1 and the parent recorded nothing; give each worker its own budget and add up the epsilons. -
kanonvrf()andldivvrf()compare numeric quasi-identifiers exactly: values that agree to 15 significant digits, such as0.1 + 0.2and0.3, were one class, so a class of one could be reported as a class of five.-0and0are one class, andNaNis missing withNA. -
cellsupp()treats anNAcell as unpublished in its audit, as its help says, and gives it complements like any hidden cell: it was treated as known, so a hidden cell recoverable through it was reported as protected. Infinite and fractional counts are refused, and a function or an environment gets the package’s “must be a numeric matrix or table” error. -
chiclr()deletes onlyarrests_full.parquet,complaints_full.parquet, unfinishedwrite-*.parquetfiles and their.sha256files: it deleted any file ending in_full.parquetand left the.sha256of an unfinished write. -
chiload(full = TRUE, mirror = )gives a mirror’s table the bundled sample’s types when its columns have the sample’s names (adatestored as text came back as text), and refuses a column that cannot be read as the sample’s type. -
hostload()andmdload()checkrefreshin words (NA,"yes",NULLandc(TRUE, FALSE)gave base-R errors or acted asTRUE), and a table size in the hosted manifest that is not a whole number from 1 to 2^40 bytes is refused in words: an absurd size stopped inseq(). -
mdverify()andmdsums()list hidden files in the store too, and the help ofmdverify()says what it covers: every file of the data store, not the package code or the lazy-loadedcomplaint_sampleandarrest_sample.mdsums(),siuload()andcihitbls()read the store named byoptions(rmoriedata.store = ), the one they verify; they used to read the installed copy. -
dplapcnt()anddplaphist()refuse a count of 2^53 itself: 2^53 + 1 rounds back to 2^53, so the release could equal the truth. - The Parquet writer keeps
NaN(it read back asNA), refuses a data frame with no columns, which its reader could not open, and refuses list, complex, raw anddifftimecolumns in words, naming the column: they were written as text. Every error of the reader starts withparquet:, and a damaged file no longer lets a base-R warning such as “NAs introduced by coercion to integer range” escape before the error. - The help pages, README, vignette and website describe the package by its own purpose, disclosure control first and the datasets second, and no longer as a companion of another package; the README’s function list and the reference index follow that order. The README names the real source of each table’s attribution, the right path for reading the sample with
pyreadrand the righthostcat()columns, and drops an out-of-date note on package size.
rmoriedata 0.4.2
CRAN release: 2026-11-12
- Works with rmoriebricklayer 1.1.0 and 1.1.1 (where
yoy()is renamedyoychg()): rmoriedata no longer refers toyoy(). -
hostload()downloads a table larger than twice the part size (optionrmoriedata.dpart, 90 MB by default) in byte-range parts, each kept once whole, so a broken connection costs at most one part and running it again resumes; the joined file is checked against the manifest SHA-256. -
hostcat()andhostload()need no sign-in; a key stored earlier is still sent. ICPSR studies are no longer served by data.rmorie.com (ICPSR’s terms do not allow redistribution); asking for one says where to get it. -
hostlogin()’s help names the argumentsrmoriebricklayer::dlogin()takes (affil,purp,restr; it listedaffiliation,purpose,restricted). A table refused with 403 is reported as a key asked for or refused: no table sits behind accepted terms any more.
rmoriedata 0.4.1
CRAN release: 2026-10-23
- The Snappy and gzip page decoders check the size a file claims before allocating it. A damaged or hostile header asking for more than the compressed bytes can hold (a Snappy stream expands at most 64 bytes per 3, deflate about 1032 to 1, and both sizes are 32-bit) is now an R error up front. Before, R was asked for the full amount first: an ordinary build then failed with an error, but the address-sanitizer build CRAN runs (gcc-ASAN) aborted the test run.
- A function missing from the installed rmoriebricklayer is now named in the message that asks for an update, and the test of the old-name fallback no longer depends on which rmoriebricklayer is installed (a development build between the 1.0.0 rename and the return of the old names failed it).
-
R CMD checkon R before 4.6 no longer notes rmoriebricklayer as an unused import.
rmoriedata 0.4.0
CRAN release: 2026-10-21
- Short names, without underscores, for every exported function and argument, matching rmoriebricklayer 1.0.0:
morie_data_load()ismdload(),morie_dp_laplace_count()isdplapcnt(),load_chicago_data()ischiload(),quasi_identifiersisqids, and so on. Each help page gives the full form of its short name. The old names are not kept. - Works with rmoriebricklayer 0.5.1 or later and calls its 1.0.0 names when they are there. Signing in (
hostlogin()),mdask()and the downloads that carry the data key need rmoriebricklayer 1.0.0; with an older one they say so, and attaching rmoriedata prints a one-line note on how to update. - The citation gives the CRAN DOI and the r-universe page, and the author’s given names in full (“Ruhela VS”).
rmoriedata 0.3.7
- Needs rmoriebricklayer 0.5.14: a new data key from
morie_data_hosted_login()needs the data access terms accepted first, then your name, organisation and why you need the data (and, for the restricted tables, a supervisor, department chair, director or coordinator). Pass them as arguments (accept_terms = TRUE,name =,purpose =, …) or answer at the console.
rmoriedata 0.3.6
- Needs rmoriebricklayer 0.5.13, whose download bar now redraws in the RStudio, Positron and R.app consoles and stays a plain line in knitr and Jupyter.
- Needs rmoriebricklayer 0.5.12, whose language-model layer
ask()is built on (0.5.9 was accepted, so a build against an older one ranask()on the retired hosted tier). -
ask()uses a language model you bring (a provider’s key, Ollama, or your own server) through rmoriebricklayer, and the model is given rmoriedata’s and rmoriebricklayer’s help with tools to read it. Itsbackendargument is nowroute("auto","own","ollama"or a provider id such as"anthropic"); the hosted MORIE tier and thermoriecommand-line fallback are gone. -
morie_data_hosted_load()uses a downloaded table only when it matches the SHA-256 the manifest lists (fetching the manifest again once before calling a file bad); a mismatch deletes the copy and says so. A restricted table (the ICPSR studies, NCVS 38604, the a2aj case law) refused with 403 says to accept the terms at https://data.rmorie.com/terms. -
morie_data_llm_config()shows and saves the language-model settings (route, and the address, key and model of each route), shared with rmoriebricklayer, rmorie and morie.
rmoriedata 0.3.5
A hex logo (
man/figures/logo.png, sourcedata-raw/hex_logo.svg), on the README and the package website, with its favicons; the website indexesmorie_dp_budget()andmorie_dp_spent().A garbled download is refused, not padded. The full Chicago export and the tables from data.rmorie.com are read with
fill = FALSE: a row with a field missing or one too many was padded into the result (and, for Chicago, written to the cross-session cache); it is now an error, and a damaged hosted table’s local copy is removed so the next call fetches it again. The Chicago tests also pin the 1% row-count tolerance at its edge (495 of 500 accepted, 494 refused) and the request size (the service’s count, at least 5,000,000 rows).Privacy verifiers (review of 0.3.5).
morie_k_anonymity_verify()treats a missing quasi-identifier as a level of its own:stats::aggregate()had dropped every row with anNAkey, so a class of one person could passk = 3; class sizes now always sum tonrow(data).morie_l_diversity_verify()counts only known sensitive values ("HIV", NA, NAis diversity 1, not 2).morie_cell_suppress()adds complementary suppressions to a fixed point and then audits the result exactly: a hidden cell the row and column totals determine (its indicator in the row space of the margin constraints) gets another complement, and the result carriesrecoverable_mask,n_recoverableand aprotectedverdict to test before publishing. The 0.3.5 passes left a lone hidden cell in a row or column, from which the primary value followed by two subtractions; zero cells are no longer used as complements. Checked against brute-force enumeration of every margin-consistent fill on 464 random tables: no disagreement.Differential privacy. Noise comes from the operating system’s CSPRNG (
rmoriebricklayer::random_bytes()), not R’s seeded generator, soset.seed()no longer reproduces a release. Counts and histograms use the exact discrete Laplace sampler of Canonne, Kamath and Steinke (2020) in integer arithmetic (whole-number releases; no floating-point artefact of the kind Mironov (2012) showed in the textbook Laplace sampler). The mean uses the analytic Gaussian mechanism of Balle and Wang (2018), valid for every epsilon; the classical bound, valid only for epsilon <= 1, is gone. All three mechanisms use one neighbouring relation (add or remove one record): the mean is a noised sum over a noised count. Newmorie_dp_budget()/morie_dp_spent()charge releases against a budget (basic composition) and refuse one that would exceed it. Examples no longer callset.seed().Parquet reader. Every byte access is bounds-checked (an out-of-range raw index had silently read zeros); varints, lists, maps and nesting are capped by the bytes that remain, so the 21-byte file that looped forever is refused at once; the decoded row count must equal the row group’s and the footer’s exactly (no padding with
NA, no truncation); a column chunk’s type must match the schema’s; PLAIN values must fill their page exactly; dictionary indices are range-checked; page sizes and footer row counts are capped (options(rmoriedata.parquet_max_page_bytes, rmoriedata.parquet_max_rows));PageHeader.crcis verified when present and the writer now writes it.tests/testthat/test-parquet-hostile.Rfuzzes all three codecs.Downloads.
.rmd_dl(), which carries the MORIE key as a bearer header, goes throughrmoriebricklayer::bricklayer_download()(https to public hosts only, nofile://, size cap) instead of baseurl(); requires rmoriebricklayer 0.5.9.load_cihi_data_tables()checks the catalogue against the signed manifest before reading it.morie_data_verify()reports files added to the store (listed = FALSE), andmorie_data_checksums()compares each digest with the manifest (in_manifest).Workflows. The SIU manifest refresh runs only on demand and uploads an artifact instead of committing: the manifest is covered by the XMSS-signed
_checksums.csv, whose key never leaves the maintainer’s machine, so it is re-signed locally withdata-raw/sign_store.R. A new ASAN/UBSAN job builds the C and runs the suite, includingtest-codec-hostile.R.Packaging.
utilsis declared inImports; the staleRoxygenNoteis dropped (Config/roxygen2/versionis the roxygen 8 field);NOTICEmoves toinst/NOTICEso it installs, and its data attribution now names the publishers instead of per-file headers that did not exist. The licence text itself stays out of the tarball, as CRAN asks for a standard licence. DESCRIPTION describes the disclosure-control helpers by what they do.The curated-data service address comes from the signed services document at rmorie.com (through
rmoriebricklayer::bricklayer_services()when the installed bricklayer has it; an older bricklayer keeps the default address), so the endpoint can move or be paused without a release. Keys are personal and issued on request atwww.rmorie.com/access/; every hint says so, andmorie_data_hosted_login(token = )stores one (the GitHub and emailed-code sign-ins keep working).ask()followsrmoriebricklayer::bricklayer_llm_ask()’s route order: an endpoint of your own, a local Ollama server, then the hosted MORIE tier as a last resort;backend = "hosted"insists on the hosted tier. The setup hint when nothing answers names all three.
rmoriedata 0.3.4
The Parquet store is compressed: the writer’s Snappy stream was literal-only (valid, but it compressed nothing), so every table’s Parquet copy was as large as its raw pages. Pages are now written with GZIP (zlib, level 9) by default, and Snappy, still available, has a real match-finder; both codecs and the reader are C (
src/gzip.c,src/snappy.c). The Parquet copies shrink from 16.1 MB to 3.3 MB (the SIU corpus from 10.0 MB to 2.1 MB) and the installed package from about 27 MB to 14.5 MB; pandas/pyarrow read them as before.SIU corpus:
narrative_summaryholds each report’s own account of the incident on 4,605 of 4,613 reports (it was empty but for three values that described the SIU’s investigation). The text is the opening of the report’s “Incident Narrative” / “Description de l’incident” section, or of the “Notification of the SIU” / “Avis à l’UES” section in reports that have no narrative section (2005-2016 and re-opened files), about 900 characters cut at a sentence end. It was read from the SIU’s pages, re-crawled on 2026-10-05 at one request every 3 seconds;data-raw/add_siu_narratives.Rrebuilds it.-
SIU corpus, round 8 (4,613 rows; every change is a row of
data-raw/siu_round8_review.csv, with the reason read from the page, or a rule ofdata-raw/siu_round8_rules.R):- six French reports of 2005-2011 filed under /en/ are labelled French and carry the service their page names; three of them held “Guelph Police Service”, which the pages never name (05-TCD-004 is Toronto, 10-PFD-078 the OPP, 10-OOD-009 Ottawa);
- a corrigendum is applied to the report it corrects, in a new column
corrigenda, not counted as a report (drids 385, 620/621 and 5065 correct 328, 521/522 and 4561); six second copies of one report (identical page text) are removed; - case numbers the page body gives: drids 925/926 are 19-TCI-073a, and drid 2209 is the French report of 22-PCI-191 (the OPP in Quinte West), not of 21-OCI-191 as the SIU page’s header says;
- a French report carries its English report’s reviewed case facts (dates, team, counts, the affected person’s age and sex, the charges, the director’s view): they were empty on about 2,240 of 2,304 French rows; the French text fields stay French;
-
charges_recommendedis"FALSE","TRUE"or empty (it had 39 spellings), and logical in the typed table (morie_data_load("siu_directors_reports"), CSV and Parquet), aspanel_reviewedis;sex_gender_affectedismaleorfemale; one name for Kawartha Lakes and for Cornwall (the Cornwall Community Police Service is now the Cornwall Police Service); -
narrative_summaryheld the SIU’s mandate paragraph or the page title, not a narrative, and is empty except on the three rows that held report text;supplemental_materialslists the legislation and case law a report cites (it held the page’s share and privacy links).
ask()sends the bundled catalogue (slug, kind, rows) with the question, so the model names tables (it was told only to “prefer the bundled catalog”, which it could not see). Through the rmorie binary it callsrmorie agent [--model NAME] QUESTION, the agent’s own options: it passed--backend, which the agent refuses, and-mfor the model;backendis"auto","hosted"or"cli". The examples that need a stored key or the binary are\dontrun; the one that runs switches the hosted tier off (MORIE_HOSTED_BASE_URL = "off"), so it reaches no network whatever key the machine holds.morie_dp_laplace_count()andmorie_dp_laplace_histogram()refuseInfin words (they warned about integer coercion, then failed in R’s internals) and refuse counts above 2^53: a double that large has no room for Laplace noise of scale 1/epsilon, somorie_dp_laplace_count(1e300, 1)returned exactly 1e300, a “private” release equal to the true count. A whole count beyond R’s integer range but below 2^53 is still a count.morie_core_mean()refuses text instead of averagingNA.Help:
?rmoriedatadescribes the package’s functions (it said there were none);?load_siu_reportslost “% of them” to an Rd escape; “more than 160” databases at data.rmorie.com; a garbled sentence in?load_chicago_data; the README’s Chicago example is bounded (limit = 200).The SIU director’s-report corpus:
police_serviceis the service of the subject officials on every row (it was the notifying force on many French rows, and on 24 English rows a custody, requesting or neighbouring service – each corrected after reading the report); one spelling per service; 59 English reports published since the last build, reviewed by the same model panel, and their 60 French reports added; 54 French reports filed under /en/ relabelled; 29 rows that held no director’s report removed.ask()answers through the hosted MORIE tier with the keymorie_data_hosted_login()stores (ollama.com cloud models and additional AI models such askimi-k2.6:cforgpt-oss-120b:cf), and only falls back to the optionalrmoriecommand-line agent when no key is stored.Hosted tables download with a live progress bar (percent, size, rate) in an interactive session;
options(morie.quiet = TRUE)silences it.Requires rmoriebricklayer 0.5.2 or newer (the login flow).
morie_data_hosted_login()signs in to the hosted MORIE tier (GitHub device flow, email code, or a key you hold) and stores the key in the credentials file every MORIE package reads, so rmoriedata alone is enough to use data.rmorie.com.morie_data_hosted_catalog()andmorie_data_hosted_load("db/table"): the 160 databases the MORIE project materialises from BigQuery public datasets, served from the edge at data.rmorie.com and opened by the MORIE key that rmorie, morie or rmoriebricklayer store. Cached under the package cache directory (tempdir()unlessoptions(rmoriedata.cache_dir = )).
rmoriedata 0.3.3
CRAN release: 2026-09-24
Nothing is written outside
tempdir()unless the user asks. The Chicago full-dataset cache lives undertempdir()by default and moves to a persistent directory only throughoptions(rmoriedata.cache_dir = );clear_chicago_cache()empties it.load_chicago_data(as = "parquet_path")also writes undertempdir(): it wrote the bundled sample into the user’sR_user_dir()cache, and a boundedfull = TRUEfetch could land on the full-dataset cache path. The schema names the language column ofsiu_directors_reportsandsiu_drid_manifest_language, as the files do; it carried theX_languagethatcheck.nameshad made of it, so the typed store andload_siu_reports()disagreed on the name.load_siu_reports()documents that it returns the corpus as text (""for empty cells) whilemorie_data_load("siu_directors_reports")returns the typed frame.Every bundled table ships as Parquet again, next to its CSV (
inst/extdata/parquet/<slug>.parquet, plus_catalog.parquetandsiu_directors_reports_corpus.parquet).morie_data_load(format = "parquet")andload_siu_reports(format = "parquet")read those copies; the newmorie_data_path(slug, format)returns the verified path of either copy, which is the bridge topandas.read_parquet(). The catalog gains aparquet_pathcolumn and the signed manifest covers the new files.The Parquet writer no longer mangles non-ASCII text in a C locale (
enc2utf8()on an unmarked string turned each byte into its<c3><a9>display form, andload_chicago_data(full = TRUE)wrote that into the cross-session cache).load_siu_reports()reads its CSV as UTF-8 like the store does, so both copies agree in every locale; a data page v2 file is reported as such before any decompression; and CI runs the check in a C locale. Every CSV reader in the package now declares UTF-8, so a live Chicago fetch and its cached copy areidentical()in any locale. A string that is not valid UTF-8 and carries no encoding mark is refused by column name (a Parquet string is UTF-8; escaping lost the data, passing the bytes through made a file no other reader opens), and duplicate column names are refused on write and kept apart on read. The cross-session Chicago cache carries a SHA-256 sidecar checked on every read.load_cihi_data_tables()rejects anarchived_onlythat is not TRUE or FALSE instead of ignoring it.morie_data_checksums()gains apathcolumn relative to the extdata root; the barefilebasenames it returned were not unique and 20 of them did not resolve from the root (morie_data_verify()already returnedpath).ask()quotes every argument it hands to thermorieCLI. The preamble contains a parenthesis, so with the CLI installed every call had died in the shell before the binary ran; a;in the question would have run as a command.modelandbackendare validated, and a blank orNAquestion is rejected like""andNULL. A stub binary onPATHnow exercises the branch the tests never reached.morie_data_load()on a dictionary slug says to usemorie_data_dictionary()instead of pointing back at the catalog.load_chicago_data(full = TRUE)no longer caches a response that is not the dataset. A 200 carrying an outage page, a header-only body or an export shorter than the row count the service reports is refused with the reason; before, an outage was written to the cross-session cache and every later session read the empty frame back. The cache is written to a temporary file and renamed, so a concurrent reader never sees a half-written file; a cache that cannot be read is discarded and refetched; and a newrefresh = TRUEargument refetches on demand. When the service’s count endpoint is down the data is returned but not cached across sessions.fetch_cihi_table()checks the downloaded file’s size and signature against the catalogue’s declared format and validateswhich(a whole number within the catalogue, or a non-empty title substring) andtimeoutbefore any request. A live URL that answers 200 with an outage page now falls through to the Wayback copy.The verifier pins the signing key’s XMSS root and public seed in package code. A store re-signed with another key, even with a consistent manifest and shipped public key, no longer verifies; the signature now means more than a checksum against local tampering.
CSV tables are read with
encoding = "UTF-8"instead offileEncoding = "UTF-8-BOM": in a C locale the latter truncated 22 of the 99 tables (5 to zero rows); the bytes are now left alone and marked as UTF-8 in every locale.The data store is signed.
_checksums.csvlists every shipped file with its SHA-256, and the manifest’s own SHA-256 carries an XMSS (RFC 8391, SHA-256) signature whose public key ships as_signing_key.json. The signature is checked once per session and each file when first read, so a modified or corrupted install errors instead of returning altered data;morie_data_verify()checks the whole store at once. Needs rmoriebricklayer 0.4.0 or newer for the verification.load_siu_reports(format = "parquet")pointed at the parquet copy this release removed; the option now errors with a note that the gzip CSV is the same corpus. rmorie reads the corpus through the default"csv".morie_dp_gaussian_mean()now rejects infinitelowerorupperwith the error its message already promised, instead of returningNaNwith a warning fromrnorm().The data store ships every table once.
morie_data_load()reads the table’s CSV (the file rmorie reads by name) and applies a bundled column schema, so each table comes back with exactly the names and classes the retired parquet copies had (verified identical for all 99 tables), and keeps it in a session cache: repeated loads are instant. The parquet directory, a duplicatesamples/directory and a second copy of the SIU reports are gone, which takes the source tarball from 6.8 MB to under the 5 MB CRAN cap. The ten Victoria tables that existed only as parquet now ship as CSV undervic/.morie_data_catalog()hasn_rowsandn_colsfor every table;morie_data_dictionary()returns the shipped JSON.Documentation is generated with markdown roxygen; backticks in the help pages are now
\code{}(they rendered as stray opening quotes in the PDF manual).
rmoriedata 0.3.2 - 2026-09-08
CRAN release: 2026-09-17
describe_corpus.Rds ships here again
The narrative corpus behind morie_describe() was excluded from the 0.3.1 tarball to get this package under 5 MB, which left rmorie carrying the only shipped copy. rmorie has no room for it either, so the corpus is back where it belongs, and both packages sit in the 5-10 MB range CRAN accepts with a written justification.
rmoriedata 0.3.1 - 2026-09-08
Source tarball down to 4.77 MB, under CRAN’s 5 MB guideline
The package shipped 9.2 MB, and most of the excess was duplication:
-
rmoriedata.sqlite(15 MB uncompressed) was retired by the SQLite-to-Parquet migration and no shipped code reads it. Kept in the repository, excluded from the tarball. - The SIU director’s-report corpus was bundled three times – as a top-level Parquet file, again inside the Parquet store, and as a
.csv.gz.load_siu_reports(format = "parquet")now reads the store copy thatmorie_data_load()already serves, so the top-level duplicate is gone. The CSV remains: it is the default format and a genuinely different encoding, not a third copy of the same one. -
describe_corpus.Rds(1.6 MB, and it does not compress) is referenced by no R code, Rd page or test. Excluded.
Verified after the change: the Parquet path, the CSV path and the siu_directors_reports store slug all return the same 5,157 x 65 corpus.
rmoriedata 0.3.0 - 2026-09-08
Native Parquet codec: one fewer hard dependency
-
nanoparquetis gone from Imports. The bundled store is now read and written by this package’s own codec (R/aaa_parquet.R), so a plain install no longer pulls a compiled Parquet dependency, andload_siu_reports(format = "parquet")works on a machine that never had one – previously it turned a missing optional package into a hard stop, despite the corpus being bundled in exactly that format.
Victorian crime data
- Ten Victorian (Australia) crime tables added to the bundled Parquet store, from the Crime Statistics Agency’s “Latest Victorian crime data” release (year ending March 2026, CC BY 4.0): criminal incidents, recorded offences, victim reports, alleged offender incidents, family incidents, the LGA cuts of each, and the two Indigenous-status breakdowns. Reach them as
morie_data_load("vic_<key>"); they appear inmorie_data_catalog()like any other slug. - The workbooks are .xlsx and were read with rmorie’s native reader, so the bundled data comes through the same code path a user hits – no readxl/openxlsx dependency and no second parser to disagree with the first. Rebuild with
data-raw/build_vic_tables.R. - This closes a real gap rather than adding a convenience: rmorie’s
morie_datasets_vic_table()defaults tooffline = TRUE, and with nothing bundled it returned a 0-row frame on any machine that had not already downloaded the workbook.
rmoriedata 0.2.5
load_chicago_data(full = TRUE)gainslimit(exact row cap) andfraction(share of the live dataset, total looked up first); bounded fetches never touch the full-dataset cache. Network examples are now\donttest{}withtry()so they degrade gracefully offline.SIU corpus also bundled as Parquet;
load_siu_reports(format = "parquet")reads it via nanoparquet (columnar / SQL-friendly). CSV remains the default (no extra dependency).
rmoriedata 0.2.4
- SIU corpus: bundle the multi-agent panel-REVIEWED director’s reports (subject-official count now 100% for all 2182 English reports; witness-officer-only cases resolved to 0). Adds a
panel_reviewedflag column (65 cols).
rmoriedata 0.2.3
- CRAN size compliance: removed the legacy
inst/extdata/rmoriedata.sqlite(retired by the sqlite-to-parquet migration; no shipped code read it — the loader API is Parquet-only viananoparquet, and the file remains backed up outside the package). Tarball drops from 5.5 MB to 4.25 MB. - License field is now plain
AGPL (>= 3)(the LICENSE file was a verbatim copy of the standard license, which CRAN flags). -
CITATION.cff,NOTICE, andLICENSEare excluded from the built package (.Rbuildignore), clearing the top-level-files NOTE.
rmoriedata 0.2.0
Connected to the shared rmorie C core
- rmoriedata now declares
LinkingTo: rmoriebricklayer (>= 0.2.0)and links the ecosystem’s shared compiled core instead of duplicating any C code. - New exports
morie_core_sha256()andmorie_core_mean()call the shared kernels directly (fast data-integrity hashing + summaries for the integrated fixtures, with no dependency on rmorie). Tests assert they are byte-identical tormoriebricklayer’s owncore_sha256()/core_mean().
rmoriedata 0.1.1
New exported helpers — differential privacy + re-identification risk
Six small, base-R-only helpers for analysts releasing aggregate statistics from the integrated fixtures (or any other dataset) without re-identification risk:
-
morie_dp_laplace_count()— (epsilon, 0)-DP count via the Laplace mechanism (sensitivity 1). -
morie_dp_gaussian_mean()— approximate (epsilon, delta)-DP mean of a bounded numeric vector via the analytic Gaussian mechanism. -
morie_dp_laplace_histogram()— per-bin Laplace noise on a histogram. -
morie_k_anonymity_verify()— checks k-anonymity over a set of quasi-identifiers and returns the offending equivalence classes. -
morie_l_diversity_verify()— per-class distinct-count check on a sensitive attribute. -
morie_cell_suppress()— small-cell suppression with optional complementary suppression so primary-suppressed cells can’t be reconstructed from row/column marginals (StatCan-style).
All helpers use only base R + the existing stats import; no new runtime dependencies. Inputs are validated and edge cases (empty vectors, NAs, out-of-bounds inputs, invalid privacy parameters) throw informative errors.
