Skip to content

Promote develop to main (2026-10-12) - #44

Merged
david-Darkcollective merged 31 commits into
mainfrom
promote/2026-10-12
Oct 12, 2026
Merged

david-Darkcollective merged 31 commits into
mainfrom
promote/2026-10-12

Conversation

@david-Darkcollective

Copy link
Copy Markdown
Member

The batch for 2026-10-12: thirty-one commits from develop, tidied from thirty-five. It is the first promotion since the command skeleton, and it carries the first release's contents: the engine pinned at 1.0.0-rc9 and the release workflow.

  • 6156197 Bump org.assertj:assertj-core from 3.27.4 to 3.27.7
  • 4d2497e Bump gradle/actions from 4 to 6
  • 1a154e7 Bump actions/github-script from 7 to 9
  • 5fb209f Bump org.junit:junit-bom from 5.13.4 to 6.1.3
  • ef4790c Bump actions/checkout from 4 to 7
  • cc83658 Bump actions/upload-artifact from 4 to 6
  • 9bcc77a Bump actions/setup-java from 4 to 6
  • c4af165 Bump gradle-wrapper from 8.14.4 to 9.8.0
  • 99fbbed Bump org.gradle.toolchains.foojay-resolver-convention
  • 885a877 Store gradlew.bat with LF line endings, as .gitattributes asks
  • 0a2e5a2 Say that building against a local engine needs a relix-core on Gradle 9
  • 1965668 Add the TSV and NDJSON renderers
  • 8ad6817 Choose the output format, and end a run cleanly at a closed pipe or an interrupt
  • 07435ac Bind data inputs with -i NAME=[FORMAT:]LOCATION, standard input included
  • 88a5f75 Load the catalog from the .relix/ directories, nearest declaration winning
  • abc065c Load a project's .relix/ only once the user has trusted it
  • 805f7c2 Inspect the catalog: relix catalog files, ls, schema and where
  • 889005f Add the report commands: check, explain, optimize, trace, bundle, ir, provenance
  • 663abbc Add relix fmt: print scripts canonically, keeping their comments
  • 52c9114 Add the management commands: drivers, connectors, doc, help, version, completion
  • c892456 Adopt the relix-core#102 engine changes and drop their workarounds (Adopt the relix-core#102 engine changes and drop their workarounds #37)
  • b7d33f3 Align a table row that ends in a comment, and the rows after it
  • 5814bdf Add -a/--arg NAME=VALUE: bind query parameters outside the script text
  • 49ef122 Re-enable fmt's test that a blank line after a comment is kept
  • 7d02521 Make zsh's completion script loadable from $fpath as _relix
  • fdb7829 Package the command: jlink image, AppCDS, man pages, completions, release
  • 619e33f Publish the command-line guide: a page per command, every example run
  • f93237c Name the guide's command-line chapters so they read among the guide's own
  • 2141f69 Run the guide's examples in a UTF-8 locale, and keep its pages' LF endings
  • 4721e94 Write the command pages with LF endings on Windows too
  • b542ce1 Pin the engine at 1.0.0-rc9

The four fixup! commits are squashed into the commits they fix (the report commands, the management commands, catalog inspection, and trusted .relix/ loading), and git diff origin/develop promote/2026-10-12 prints nothing.

Merge with Rebase and merge, then reset develop to the new main. That puts release.yml on the default branch, so its dry run can be dispatched before v0.1.0-rc1 is tagged.

dependabot Bot and others added 30 commits October 7, 2026 17:35
Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.4 to 3.27.7.
- [Release notes](https://github.com/assertj/assertj/releases)
- [Commits](assertj/assertj@assertj-build-3.27.4...assertj-build-3.27.7)

---
updated-dependencies:
- dependency-name: org.assertj:assertj-core
  dependency-version: 3.27.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [gradle/actions](https://github.com/gradle/actions) from 4 to 6.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](gradle/actions@v4...v6)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 9.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](actions/github-script@v7...v9)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 5.13.4 to 6.1.3.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](junit-team/junit-framework@r5.13.4...r6.1.3)

---
updated-dependencies:
- dependency-name: org.junit:junit-bom
  dependency-version: 6.1.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v4...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v4...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 6.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](actions/setup-java@v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 8.14.4 to 9.8.0.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](gradle/gradle@v8.14.4...v9.8.0)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.8.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps org.gradle.toolchains.foojay-resolver-convention from 0.9.0 to 1.0.0.

---
updated-dependencies:
- dependency-name: org.gradle.toolchains.foojay-resolver-convention
  dependency-version: 1.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
The Gradle 9.8.0 wrapper update committed it with CRLF in the repository,
so every checkout showed it as modified. The working copy keeps CRLF;
only what the repository stores changes.
A composite build runs the included build under this repository's Gradle,
9.8.0 since the wrapper update, and relix-core is still on 8.14.4, where its
pitest plugin fails on Gradle 9.
TSV is IANA TSV with a header row; a tab, line feed, carriage return or
backslash in a value is escaped, so a row is always one line for cut, awk
and sort. NDJSON is one object per line, in the json format's encoding.

Each format now hands back an encoder that turns a result set into text a
row at a time, so the command can write, flush and check each row itself.
The delimited formats take a header option and a NULL spelling, which the
table and Markdown formats honour too.

Fixes #5
…n interrupt

-o/--output, or RELIX_OUTPUT, chooses the format; with neither, a terminal
gets a table and a pipe gets tsv. --no-header, --null, --color (honouring
NO_COLOR, and colouring only a table) and --line-buffered modify it.

A table or Markdown prints every query, labelled. A machine format prints
one result set: the script's last query, or the one --query=NAME names; a
query not printed is not run. --all with ndjson prints every query, each row
tagged "_query": NAME, and is a usage error with any other machine format.
--fail-empty and --fail-rows exit 1 on what the printed queries returned.

Rows stream through a buffered sink over the raw standard output, flushed at
the end of each result set or per row with --line-buffered. A failed write
closes the row stream, which the engine takes as cancellation, and the run
exits 141 silently, so relix -e Naturals | head ends when head does. A
shutdown hook cancels the running query and closes the stream and the
session on SIGINT; the run exits 130.

Fixes #6
-i binds a relation name to a file, an http(s) URL (with --remote) or
standard input, in csv, tsv, json or ndjson. Without a format the extension
decides; standard input must name one. The engine's own readers parse it
(Relix.input), so a file or URL is reopened for each read and standard input
is read once.

The heading is inferred from the first --infer-rows records, or declared
with --schema NAME='{ ... }', read by the engine's parser as a source's
schema block. --unbounded=NAME marks an input that never ends, so that
tail -f ... | relix -i log=ndjson:- streams.

Only one input may be standard input, and then the script must come from
-e or one file. A malformed binding, a format that cannot be told, a file
that is not there, a URL without --remote, a name bound twice, and a
--schema or --unbounded for a name no -i binds are usage errors; text that
cannot be read as its format fails the script with exit 4.

Shadowing a catalog relation waits for the catalog (#8). The test that a
blocking operator over an unbounded input is refused is disabled: the
engine's plan-time check does not yet see an unbounded Relix.input.

Part of #7
…nning

A run now starts with the declarations of every catalog/*.relix file in the
.relix/ directories from ~/.relix down to the working directory (stopping
after $HOME or at a root marker), then the $RELIX_CATALOG_PATH and --catalog
files. Each file is parsed under its absolute path, so a relative source path
reads from beside the file. The nearest declaration of a name replaces the
farther ones, and the survivors are defined together before the -i inputs and
the script, each of which can replace a catalog name in turn.

relixrc sets default output and profile; options and environment variables
beat it. -N turns discovery off.

Part of #8.
A project catalog can declare a connection or HTTP source that sends a
${SECRET} to a host of its author's choosing, so, as direnv does, a project
.relix/ is now loaded only after `relix catalog trust [DIR]` has recorded a
hash of its files in ~/.relix/trusted. Changing, adding or removing any file
there, profiles.json and relixrc included, means trusting it again.
`--list` shows each trusted directory and whether it has changed;
`--revoke DIR` removes one.

An untrusted level is parsed but never defined, and is skipped with one
stderr line naming the command that trusts it. A script that then needs a
name only that level declares exits 5. ~/.relix, --catalog files and the
script are trusted without asking, and RELIX_TRUST_ALL=1 trusts everything.

The format options move into a mixin of their own, so that the catalog's
listings print through the same renderers as a run.

Part of #9.
`files` lists every catalog file in load order, with the names each declares
and whether it is trusted. `ls` lists the relations with their kind, arity
and the file whose declaration won; `schema NAME` lists a relation's columns
and types. Both are queries over the engine's relix.relations and
relix.columns, in a session holding only the catalog. `where NAME` lists every
declaration of a name, nearest first, as wins, shadowed or untrusted.

Each prints rows through the run's renderers, so a table on a terminal and
tsv or ndjson in a pipe. `schema` and `where` exit 3 for a name the catalog
does not declare.

Part of #10.
… provenance

Each view of a script is now a command that writes one thing to stdout. They
read scripts as run does, with the catalog and -i inputs, through one loop
(ScriptRunner) that takes each script through its session, its inputs and
analysis and hands it to the command's view; run's rows are one such view.

- check: nothing on stdout, diagnostics on stderr, exit 0 or 3, so
  `find … | xargs relix check` is a lint pass. --json writes every script's
  diagnostics as one document instead.
- explain: each query's physical plan; --json writes one object per query
  per line.
- optimize: the script with each query statement replaced by its rewritten
  query, every other statement and comment kept as written, so it works as a
  filter. A rewrite the engine cannot render as Relix that parses back is
  left as written, with a warning. --report prints the rules and the trees.
- trace: the event feed of each query, rows discarded, each query's feed
  ending in a ROWS event.
- bundle: the playground's JSON bundle, still produced for a script that
  does not analyse.
- ir: the IR report of the whole script.
- provenance: annotated rows, --semiring and --weight; a table on a
  terminal and JSON in a pipe.

`run --trace[=FILE]` writes the event feed to stderr, or FILE, while the rows
go to stdout. A file is named only with `=`, so `relix --trace q.relix`
traces q.relix rather than writing over it.

The example scripts the tests run every command over are in
src/test/resources/examples.

Part of #11.
`relix fmt [SCRIPT...]` prints each script with the engine's statement
printer, the one behind Relix.definitions(), and standard input when no file
is named. `-w` rewrites the files that change in place, and `--check` changes
nothing, names each file that would change, and exits 1 if any would.
`--glyphs` (the default) and `--keywords` choose the operator spelling. The
keyword spelling is read from the reference's spellings page.

The printer drops comments, and DarkCollective/relix-core#99 asks for that
and the spelling choice in the engine. Until then fmt keeps them itself:

- a comment between statements stays where it was, on its own line or after
  the statement it followed;
- a statement with a comment inside it is left exactly as written;
- blank lines between things collapse to one.

The comments are found by ScriptText's scan, which follows the language's
two lexers: `--` and `/* */` outside strings, and only at the start of a line
inside an inline table, whose rule and cells may hold `--`. Inline tables'
columns are aligned.

A script that does not parse is reported as FILE:LINE:COL, left alone, and
the run exits 3. Over every example script, fmt is idempotent, parses back
to the same statements in either spelling, and keeps every comment in order.

Part of #12.
… completion

- `drivers ls` lists the JDBC drivers relix can install, with each one's
  scheme and whether it is installed. `drivers install NAME` downloads and
  installs one. Together they replace the run-time --download-drivers.
- `connectors ls` lists the connectors installed, and `--available` adds the
  ones the hosted catalog offers. `connectors install TYPE` installs one.
- `doc [TOPIC]` shows a reference page, found by glyph, keyword or name,
  rendered for a terminal and paged through $PAGER on one. `--markdown`
  prints the page as it is. With no topic it lists the pages as rows.
  A language keyword keeps a name it shares with a function (`doc fix` is
  the operator), and `--function` finds the function.
- `help [COMMAND]` is picocli's, and `version` prints the front-end and
  engine versions.
- `completion bash|zsh|fish|powershell` prints a completion script built
  from the command model. bash's is picocli's, which runs under zsh too.
  fish and PowerShell get theirs here.

The listings print through the run's renderers. The index doc looks pages up
in is built here, over relix-docs' language pages and the installed
functions' pages, until DarkCollective/relix-core#95 puts it in relix-docs.
It holds every key of relix-console's index except the REPL's page about
itself.

Each completion script is syntax-checked by its shell when that shell is
installed. CI installs zsh and fish on Linux for this; the runners already
have bash and PowerShell.

Part of #13.
* Re-enable the test that a blocking operator over an unbounded input is refused

DarkCollective/relix-core#98 made stream() refuse τ and γ over an unbounded
Relix.input with UnboundedRelationException, so the test #34 disabled now
runs, for τ and for γ, and checks the error names the input as unbounded.
δ is not among them: it streams by design.

* Print relix fmt's output with the engine's script printer

DarkCollective/relix-core#99 made ScriptPrinter keep a script's comments and
print the ASCII spelling, so fmt no longer scans for comments itself or
respells the glyphs token by token. It prints
ScriptPrinter.print(Relix.parse(text, source), spelling) and aligns the
inline tables' columns, as before.

A statement with a comment inside it is now formatted too, with the comment
kept. The engine's printer drops a blank line between a comment and the
statement or namespace after it; a disabled test records that until the
engine keeps it.

* Print every relix optimize rewrite, now that each one parses

DarkCollective/relix-core#100 made render() of an optimized relation always
parse back, a seeded closure included, so optimize no longer parses each
rewrite, leaves a query it cannot write as written, or warns about it. The
introspection example's closure query is now rewritten, and the test checks
the output with relix check; the examples' optimize runs write nothing to
stderr.

* Look relix doc's pages up in relix-docs' ReferenceLookup

DarkCollective/relix-core#95 put the reference index in relix-docs:
ReferenceLookup indexes the language pages and the installed functions'
pages, and a language keyword keeps a name it shares with a function. That
is what DocIndex built here, so DocIndex and its test go and doc uses
ReferenceLookup. relix-cli has no pages of its own, so it passes none.

The listing gives the functions grouped by category, as ReferenceLookup
offers them, rather than in the order the libraries list them.
The engine's printer now writes a comment after a Markdown row back on that
row (relix-core fix/table-row-comments). fmt's column alignment took a row
to end in |, so a commented row ended the run being aligned and every row
after it was left as the printer wrote it. A row's cells are now aligned
without its comment, which follows them after two spaces.
A value given with -a is bound to $NAME through the engine's
Relix.Builder.parameters, never pasted into the script, so a value holding
a quote cannot change the query. The value is text, which the engine reads
as the type the parameter was analysed to (relix-core#101): -a id=42 binds
a number when $id is compared with a NUMBER, and text that does not read
as that type is an analysis error.

An unbound parameter is an analysis error (exit 3) that names it. The
engine's message names its Java remedy, so EngineMessages puts it in the
command's terms: "give it a value with -a NAME=VALUE". fmt, bundle and
optimize print $NAME, never the value.

Part of #14.
DarkCollective/relix-core#107 made the engine's printer keep a blank line
between a comment and the statement or namespace after it, so
FmtTest.Layout.blankAfterComment runs again: "-- header", a blank line,
"namespace demo;", then a comment set apart from its query the same way,
format to themselves.
`relix completion zsh` printed bash's script, which zsh runs through
bashcompinit when it is sourced. Homebrew installs zsh completions as a
file on $fpath instead, which compinit binds by its first line and runs
as a function on the first Tab. The script now starts with
`#compdef relix`, and ends by completing the word it was called for when
it runs inside a completion; sourced, it does what it did before.

Part of #15.
…ease

./gradlew releaseArchive builds this platform's archive: the jlink image,
with relix-solver-ojalgo and relix-docs, picocli and the H2 driver
force-merged; man/man1, one page per command from picocli's
ManPageGenerator through Asciidoctor's manpage backend; and completions/
for bash, zsh, fish and PowerShell. H2 is now a runtime dependency, the
one driver the image bundles.

Each stage of a pipeline starts a JVM, so the image carries an AppCDS
archive (lib/relix.jsa), trained at build time on a run that joins and
groups a bound CSV file, over the JDK archive jlink generates. The
launcher maps it and sends the JVM's warnings to standard error rather
than into the rows.

The generated launchers are patched: the sh one resolves a relative
symbolic link, as Homebrew installs it, and the Windows one no longer
runs from its own bin directory, which took relative paths and .relix/
discovery away from the user's.

./gradlew packageTest unpacks the archive with the system's tar and runs
its launcher: a script on stdin over a catalog in a temporary tree (a CSV
source and an H2 connection, trusted first), tsv out; OPTIMIZE and
relix doc, for the providers jlink would drop unnoticed; -Xshare:on with
the command's classes loaded from the archive, and the same flag refusing
a missing one; and man over every page. CI runs it on each platform.

The Release workflow builds and tests the five platform archives. On a
tag it attaches them and checksums.txt to the GitHub Release and pushes
the formula and the manifest, written whole from packaging/, to the tap
and the bucket; run by hand it is a dry run, which checks their
checksums against the archives and publishes nothing.

Part of #15.
docs/guide/command-line is the command-line section of the Relix
programming guide, laid out as the engine's manuals are: a README.md
index of chapters, and index.json as the machine-readable index.

Hand-written pages cover the command and where scripts come from, input
binding (-i, formats, standard input, headings, unbounded inputs, -a),
output formats, the catalog (discovery, the overlay, trust, relixrc),
secrets and profiles, and pipelines with jq, awk, find and xargs.

The command reference, commands/*.md, is generated by CommandPages from
the picocli model --help and the man pages come from, followed by each
command's hand-written examples in docs/command-examples. ./gradlew
commandPages rewrites them; GuideStructureTest fails while they are
stale, while the index misses a page, or while a link to a page or a
heading, here or in the engine's manuals, is broken.

GuideExamplesTest runs every shell block over a copy of the fixture under
src/test/resources/docs-fixture, the ~/shop the pages describe, and
compares standard output and the exit status. A plain relix command runs
in the test's JVM; anything else runs through bash, with a relix shim
that treats a stage's streams as a terminal when they are the example's
own, as a reader's would be. The guard was probed by mutation: a changed
row, a changed or unshown exit status, a dropped output block, a broken
link or heading, an unlisted page, a stale command page, an unrun bash
block, a change to the table renderer and a change to the harness itself
each fail it.

./gradlew docsArchive builds relix-<version>-docs.zip, which the release
workflow attaches to each GitHub Release for the site to render.

Fixes #16
… own

The site renders these chapters after the engine's programming guide's,
so each says it is about the command line.
…dings

On Linux the JVM decodes its arguments by the locale, so under LC_ALL=C
`relix doc σ` looked up '??'. A reader's terminal is UTF-8.

On Windows, text=auto checked the generated command pages out with CRLF
endings, which then differed from the generator's output; they keep LF,
as the .relix files the tests compare do.
picocli wraps the usage synopsis with the platform's line separator, so on
Windows the generated pages held CRLF and never matched the checked-in
ones.
rc9 is released and carries everything the command uses, so develop
builds against it rather than the engine's snapshot, and the snapshot
repository drops out of the build. The pin's comment and CONTRIBUTING
now say so: develop pins a released engine while one has what the
command needs, and returns to 1.0.0-SNAPSHOT only while it needs engine
work that is not yet released.
@david-Darkcollective
david-Darkcollective merged commit 659533f into main Oct 12, 2026
3 checks passed
@david-Darkcollective
david-Darkcollective deleted the promote/2026-10-12 branch October 12, 2026 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant