Skip to content

Release/0.1.0 - #2

Merged
Splatcrafter merged 13 commits into
mainfrom
release/0.1.0
Sep 1, 2026
Merged

Splatcrafter merged 13 commits into
mainfrom
release/0.1.0

Conversation

@Splatcrafter

Copy link
Copy Markdown
Member

Release v0.1.0

java.lang.classfile writes a member class two ways. Where nothing
enclosing it is parameterized it is flat, Ljava/util/Map$Entry;, and
className() is the whole slash-separated name. Where something is, it is
a chain, Ljava/util/EnumMap<TK;TV;>.EntrySet;, and className() is the
simple name alone -- the rest of it lives in the outer signature, which
is the only place the enclosing type's arguments can be stated.

classDesc() answers with the whole name either way. mapSignature fed
that back into the chained form, so a rebuild emitted

    Ljava/util/EnumMap<TK;TV;>.java/util/EnumMap$EntrySet;

which nothing can parse. JDK 25 accepted it in silence: ClassTypeSig.of
did not validate, and neither the verifier nor
rebuildingPreservesMembersAcrossTheJdk looks at generic signatures. JDK
26 added validateIdentifier and the build stopped. The corruption is
present on every JDK the project has run on; only the newer one says so.

mapClassTypeSig now maps the outer type first and takes the mapped whole
name back apart against it to recover the simple name. Where a mapping
moves the member out of its enclosing type no simple name relates the
two, so the flat form is emitted instead -- that costs the enclosing
type's arguments, which a name that no longer nests has nowhere to put.

Three tests, each of which fails on JDK 25 without the fix:
chainedSignatureKeepsItsSimpleName covers identity over a chain, a
three-deep chain and a chain inside a type argument;
chainedSignatureFollowsARename covers a rename that carries the member
and one that detaches it; rebuiltSignaturesAcrossTheJdkStayParseable
re-parses every class, field, method and generic-local signature in a
seventh of java.base, and catches a class signature the JDK 26 run never
reached because it failed on a generic local first.

Signed-off-by: Erik Pförtner <splatcrafter@splatgames.de>
Both were committed as mode 100644, so a fresh checkout has no execute
bit on either and the shell refuses them before the script runs.

The IDE plugin job in build.yml calls ./gradlew by path twice. The first
call died with "Permission denied" and exit code 126, which is the shell
saying it would not run the file -- nothing in the wrapper, the JDK or
the Gradle setup was involved. verifyPlugin, the step after it, was
blocked by the same bit.

shoot.sh is not on the CI path but three documents tell an agent to run
it by path, unprefixed: the docsite skill and the implementer and
clarity-reviewer agents. As written those commands could not work.

Recorded with git update-index --chmod=+x so the bit is in the tree
rather than only in a working copy. The other scripts carrying a shebang
need no change: the .devcontainer ones are invoked as bash <path>, and
writerside.sh is chmod +x'd in the Dockerfile.

Signed-off-by: Erik Pförtner <splatcrafter@splatgames.de>
KEYS carries the public half of the key the release pipeline signs with:
Splatgames.de Software CI Release Signing <release@splatgames.de>, RSA
4096, created 2026-01-11, no expiry, fingerprint C6BE 25BF 2A46 39A6
7A49 1EBD 37B5 9B93 DC75 6EE8. No private material is in the file.

SECURITY.md said the key and its fingerprint were "published with the
first signed release", which the file makes untrue. Its Signing keys
section now identifies the key, gives the import-and-verify commands,
and keeps the two bullets about pipeline ownership and how the private
half reaches the build.

It also says that importing KEYS and verifying against it proves only
that the file and the signatures agree, since one push can replace both,
and points at keyserver.ubuntu.com as a source outside this repository.
That is the copy Maven Central validates against; the key is there and
the fingerprint matches. keys.openpgp.org does not have it, so the text
does not claim it does.

The README repeats the fingerprint and the two commands under Security,
because someone verifying a download reaches the README first.

RELEASING.md told the releaser to add the fingerprint to SECURITY.md
once the key existed. That is now done, so the instruction would have
asked for it twice; it records where the key lives instead, and that
rotating it means replacing all three in one commit.

Signed-off-by: Erik Pförtner <splatcrafter@splatgames.de>
Three things that were stopping the build or misdescribing the project.

ClassRemapper rebuilt a member of a parameterized outer type by putting
the whole slash-separated name where the simple name belongs, producing
a generic signature nothing can parse. JDK 25 accepted it in silence and
JDK 26 rejects it, which is why the pipeline broke on a JDK bump rather
than on a change to this code. Three tests cover it, each of which fails
on JDK 25 without the fix.

gradlew and shoot.sh were committed without an execute bit, so the IDE
plugin job died at exit 126 before the wrapper ran, and three documents
told an agent to run shoot.sh by a path that could not work.

KEYS is published with its fingerprint in SECURITY.md and the README,
which replaces the claim that the key would arrive with the first signed
release.

Signed-off-by: Erik Pförtner <splatcrafter@splatgames.de>
Code scanning needs GitHub Advanced Security on a private repository, so
the analyze step never got past its upload: CodeQL scanned 316 files,
wrote the SARIF, and then failed with "Advanced Security must be enabled
for this repository to use code scanning". It failed that way on every
push to main and develop, on every pull request, and weekly. Removing
the workflow removes the cost and a permanently red check; the file is
worth restoring if this repository ever goes public, where the same
scanning is free.

SECURITY.md claimed CodeQL under Automated scanning and the README
carried its badge. Both are gone rather than left describing something
the repository no longer runs. What is left there is what the build
itself enforces on every run.

The release workflow named four secrets that do not exist. The ones the
repository has are CENTRAL_USERNAME, CENTRAL_TOKEN, GPG_PRIVATE_KEY and
GPG_PASSPHRASE, and a secret cannot be renamed -- only deleted and
recreated -- so the workflow follows the names rather than the other way
round.

Only the secret references moved. The environment variables the publish
job sets from them are unchanged and are not free to change:
MAVEN_CENTRAL_USERNAME and MAVEN_CENTRAL_TOKEN are the placeholders
settings.xml resolves by name, and MAVEN_GPG_PASSPHRASE is the variable
maven-gpg-plugin reads by default, which pom.xml documents at the gpg
plugin. RELEASING.md now says which of the two a name belongs to.

Signed-off-by: Erik Pförtner <splatcrafter@splatgames.de>
Removes the CodeQL workflow, which could never finish on a private
repository: the analysis ran and the upload failed on the Advanced
Security gate, on every push to a long-lived branch, every pull request
and weekly. The README badge and the claim in SECURITY.md go with it,
rather than being left to describe a scanner the repository no longer
runs.

Also points the release workflow at the four secrets this repository
actually has -- CENTRAL_USERNAME, CENTRAL_TOKEN, GPG_PRIVATE_KEY,
GPG_PASSPHRASE. Only the secret names moved; the environment variables
the publish job sets from them are fixed by settings.xml and by
maven-gpg-plugin's default, and are unchanged.

Signed-off-by: Erik Pförtner <splatcrafter@splatgames.de>
The IntelliJ plugin job did not fail because the plugin is broken. The
verifier finished and said so:

    Finished 1 of 1 verifications (in 78.9 s): IU-262.10315.69 against
    de.splatgames.aether.weaver:0.1.0-SNAPSHOT: Compatible.

The job then died writing the report:

    Exception in thread "main" java.io.IOException: No space left on device
        at ...HtmlResultPrinter...PluginVerifierMain.main

The runner was out of disk. The download failures logged before that --
radler, org.jetbrains.android, PythonCore, DevKit -- were the same cause,
not a separate one. This job pulls a full IntelliJ IDEA Ultimate
distribution to compile against, another for every release
`recommended()` resolves, and then each bundled plugin the verifier walks
to; a ubuntu-latest image starts with about 14 GB free.

Four preinstalled toolchains are removed before anything downloads. The
Android SDK is roughly 9 GB on its own and nothing here compiles for
Android. Each is removed only if present, so an image that stops shipping
one of them does not fail the step, and the step prints df before and
after so the next person does not have to guess what it bought.

Nothing about the verification itself changed. `recommended()` and the
unset `untilBuild` are a deliberate pair -- the plugin claims
compatibility with every future IDE and this is what checks the claim --
and narrowing either to fit a full disk would have made the job pass by
testing less.

CodeQL comes back with the repository being public: code scanning is free
there, and the Advanced Security gate that made it fail on every run does
not apply. The workflow, the README badge and the SECURITY.md entry are
restored as they were. The Dependency Review bullet keeps the fuller
description it gained.

Signed-off-by: Erik Pförtner <splatcrafter@splatgames.de>
The IntelliJ plugin job was failing on a full runner, not on a broken
plugin: the verifier reported the plugin compatible and the job then died
writing its HTML report with "No space left on device". Four preinstalled
toolchains are removed before the IDE downloads start, the Android SDK
among them at roughly 9 GB. The verification itself is untouched --
recommended() and the unset untilBuild still check the compatibility the
plugin claims.

CodeQL returns now that the repository is public, where code scanning is
free and the Advanced Security gate no longer applies. Workflow, README
badge and SECURITY.md entry are back as they were.

Signed-off-by: Erik Pförtner <splatcrafter@splatgames.de>
Six places said it was not, and it ships to the JetBrains Marketplace
beside this release. A reader who believes any of them builds a Gradle
project to get something the IDE would have installed for them.

  README, IntelliJ section  "not published yet; build it from ..."
  README, modules table     aether-weaver-ide -> "Not published"
  README, project status    listed among the deliberate limitations
  CHANGELOG, tooling        "Built from source; not published ... yet"
  CHANGELOG, limitations    "The IntelliJ plugin is not published"
  intellij-plugin.topic     tldr: "Nothing publishes it, so you build it
                            yourself"

Building from source stays where it is useful and is still named; it is
no longer the only way in. The Marketplace is reached by name in the
plugin browser rather than by a numeric plugin URL, which is assigned on
first upload and would be a guess today; the CHANGELOG links the vendor
page, which exists.

The modules table's column asks what you depend on, and the plugin is
not a dependency at all -- "Not published" answered a question about
Maven Central that the row was not asking. It now says what a reader
does with that row: install it in the IDE.

python3 build-config/docsite/check-docs.py passes. The --build run fails
in this container with 36 builder/INT009 "Rendering of the diagram timed
out" across 35 topics, none of them this one and no other error kind, so
it is the diagram rendering here rather than the change. docs.yml builds
in the pinned image on any push under Writerside/ and is the real check.

Signed-off-by: Erik Pförtner <splatcrafter@splatgames.de>
Step 1 listed the four files the release workflow checks and mentioned
gradle.properties in passing, as "worth doing, though nothing checks it".
Two files decide what the plugin ships as, and both were at
0.1.0-SNAPSHOT while the release is being cut:

  build.gradle.kts   version = "0.1.0-SNAPSHOT"
  gradle.properties  aetherWeaverVersion=0.1.0-SNAPSHOT

The first names the archive. The second is worse: the API and the engine
are implementation dependencies and are bundled into the plugin, so the
plugin would ship the SNAPSHOT jars from whoever ran the build instead of
the published ones. Neither is checked by anything, which is exactly why
the runbook has to carry them, and the table now says what each one
breaks rather than only what to set.

The heading said four files and the section now covers six; it says six,
and the sentence under it keeps the distinction, because the workflow
still reads four and two later entries still count that way.

Step 6 is the plugin release itself, which the runbook did not describe
at all: wait for Central to actually serve the artefacts, since the
plugin resolves them as ordinary dependencies, then buildPlugin and
upload the archive to the vendor page. publishPlugin and signPlugin are
registered by the Gradle plugin but neither is configured here -- no
token, no certificate -- so the upload is a manual step, and the runbook
says so rather than leaving a task name that looks like it would work.

Signed-off-by: Erik Pförtner <splatcrafter@splatgames.de>
Six places in the README, the CHANGELOG and the IntelliJ topic said the
IDE plugin was not published. It ships to the JetBrains Marketplace
beside 0.1.0, so all six now say how to install it and keep building
from source as the alternative rather than the only way.

The runbook gains the two versions that decide what the plugin ships as
-- its own `version` and the `aetherWeaverVersion` it bundles the API and
engine from, both still at 0.1.0-SNAPSHOT and neither checked by
anything -- and a step for the plugin release, which it did not describe
at all.

Signed-off-by: Erik Pförtner <splatcrafter@splatgames.de>
The root is a `pom` artefact and goes to Central with the rest of the
release, where a coordinate cannot be taken back. It read
`de.splatgames.aether.weaver:aether-weaver-parent`; it reads
`de.splatgames.aether.weaver:aether-weaver`.

Thirteen places, because the artifactId is not only declared:

  pom.xml                    the declaration
  nine module poms           each names it as <parent>, and a module
                             whose parent no longer exists does not build
  artifacts.topic            "Every jar names aether-weaver-parent as its
                             own" is a published statement of fact
  S-install.md               four references in the dossier the reference
                             page was written from

The dossier is not checked by anything and is not published, but it is
what the next revision of that page is written against, so a stale fact
left in it is the drift the pipeline exists to prevent.

`<name>Aether Weaver (Parent)</name>` is unchanged. It still describes
what the pom is, and it is not a coordinate.

mvn -B -o clean verify passes over all ten modules, and
build-config/docsite/check-docs.py passes.

Signed-off-by: Erik Pförtner <splatcrafter@splatgames.de>
The reactor root ships to Maven Central as a pom artefact alongside the
jars, and a published coordinate cannot be withdrawn. It is
de.splatgames.aether.weaver:aether-weaver now, not -parent.

Thirteen places: the declaration, the nine modules that name it as their
parent, the reference page that states it as a fact, and the research
dossier that page was written from.

Signed-off-by: Erik Pförtner <splatcrafter@splatgames.de>
@Splatcrafter
Splatcrafter merged commit 0110d95 into main Sep 1, 2026
25 checks passed
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