Skip to content

Content journal and release hardening - #533

Merged
Skidamek merged 620 commits into
mainfrom
gen/release-hardening
Sep 16, 2026
Merged

Skidamek merged 620 commits into
mainfrom
gen/release-hardening

Conversation

@Skidamek

@Skidamek Skidamek commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Summary

The largest layer of the stack. It replaces the generation store with a content journal, hardens the finished workflow, dissolves loader-core, and supersedes parts of every layer below.

The content journal

The store built in #524..#531 persisted every generation several times over, as a record, a catalogue entry, a commit, a delta, and a checkpoint, and validated each artifact by re-deriving a canonical digest of its content. Every change to that canonicalization made previously written state unreadable. It changed twice in one week, and either change could silently leave hosting dead while clients were still told to sync.

Storage is now a content-addressed object store plus one append-only journal. An entry records the resulting content token, which hashes only what is served (path, sha1, size), the exact policy document bytes kept in the CAS, the patch notes, and the path-level change list. Policy churn therefore never mints a new content identity, and a publish without content changes appends nothing and leaves its patch notes pending for the next real one. Rollback appends a restore entry that hosts exactly the target generation's content and policy.

Everything else is a view rebuilt from the journal head when it mismatches: the served projection document carrying policy, ownership ledger, and head sequence, plus the hosting map. A future canonicalization change is absorbed by the rebuild instead of stranding state. This retires the record, checkpoint, commit, and catalogue machinery, the historical-catalogue wire endpoint, and #531's GenerationHistoryIndex.

Hardening

Shared CAS state publishes with create-only semantics and durable ownership receipts (SharedObjectOwnership), and objects are verified before cache insertion. Collection keeps objects that other game instances still reference, rejects changed hard-linked cache entries, handles read-only objects, and tells corrupt and missing apart.

The update transaction rechecks live files and requests a replan when they changed after planning. Deferred updates run through the detached recovery helper, which now also owns the Windows projection locks; stale settings are invalidated and reviewed consequences survive pending-update recovery. Platform detection yields nothing when it cannot detect anything, so an unknown system resolves platform-agnostic groups only instead of guessing Linux.

Connection lifetimes are bounded. Transfers close cleanly on mid-stream errors and abort on cancel, so a retry gets a fresh connection. Cancelled Modrinth and CurseForge lookups abort instead of returning zero hits, and undownloadable or unlisted hits are dropped.

Restructuring

loader/core is gone; its client engine and screen seam moved into core and the Minecraft mod. The screen layer is rebuilt around a typed ScreenManager/ScreenService seam, the group list lives on the shared panel, and dropdowns stop growing past the screen edge. Preload is replaced by ClientLaunch and BootRecovery, and an unreachable modpack server boots the installed pack. Windows natives gained a lock probe that names lock holders and a single native stat for reparse and identity questions. Config gains file groups (synced, excluded, edit-allowed) and admin-declared platforms beyond the auto-detected trio, with docs for both plus client storage maintenance.

Loaders and autotester

Loader integration tracks the consolidated mcholepunch artifacts, carries TLS records inside vanilla-shaped frames with encrypted headers (TlsRecordCamouflage), and requires stable MixinExtras hooks for the critical mixins. Locale catalogues are refreshed.

Autotester scenarios gain cache deduplication, a detached supervisor for cleanup, fail-fast interruption handling, retry handling, cold-cache and legacy-target cases, and controlled concurrency.

Known limits

The journal is the history and nothing compacts it. A changed file costs roughly two hundred bytes of line, the server parses the file once per boot, and a client re-fetches the whole journal whenever its mirror is stale. Client-side storage maintenance reclaims old content objects but keeps the mirror whole. If a long-lived server ever feels that, the fix is serving entries after the client's head sequence, since compaction would renumber rollback targets and every client mirror.

Tests

Core coverage for shared CAS publication, ownership receipts, object collection, and projection swaps. Windows native tests. Autotester coverage for cache deduplication, interruption, deferred updates, cold-cache, and legacy targets.

@Skidamek
Skidamek force-pushed the gen/release-hardening branch from 178452e to ad76958 Compare August 20, 2026 11:12

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 178452ed9c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread autotester/docker/client/run-headlessmc-client
Comment thread core/src/main/java/pl/skidam/automodpack_core/utils/cache/FileMetadataCache.java Outdated
@Skidamek
Skidamek force-pushed the gen/release-hardening branch from 6db55da to b29a50a Compare August 20, 2026 13:47
@Skidamek
Skidamek force-pushed the gen/release-hardening branch 3 times, most recently from e77f036 to 5ec1999 Compare August 20, 2026 14:10
@Skidamek
Skidamek force-pushed the gen/release-hardening branch from 5ec1999 to a42815b Compare August 20, 2026 14:14
@Skidamek
Skidamek force-pushed the gen/release-hardening branch from d662faf to ffaa099 Compare August 20, 2026 18:57
@Skidamek
Skidamek force-pushed the gen/release-hardening branch 2 times, most recently from 6c91fb3 to 3a62cf9 Compare August 21, 2026 10:49
@Skidamek
Skidamek force-pushed the gen/release-hardening branch 4 times, most recently from 1cc50f5 to e165873 Compare August 24, 2026 14:20
@Skidamek
Skidamek force-pushed the gen/release-hardening branch 9 times, most recently from 107e61b to 30929b1 Compare August 31, 2026 10:09
The storage screen already arms on the first click and only deletes on the
second. The fake bridge still compacted immediately, so the release-gate
flow timed out looking for Click again to clean.
The file now lives at automodpack/host-modpack/patch-notes.md, is created empty on generate, and is truncated in place after a successful publish instead of being deleted.
Preload and the preview used the same boolean, so a projection-only mod
change could say no restart and then prompt. RestartPolicy answers
REQUIRED, OFFERED, or NONE for both questions; preload never offers.
The preview line and the post-apply screen now tell the same story, and
in-game Back returns to the multiplayer hub.
Declining the offer already left the login; completing UPDATE_REQUIRED
made the server log a missing-pack failure. JOIN_CANCELLED names the
player's answer, logs that they cancelled during the offer, and
disconnects without the install kick.
Applied-but-unloaded content used to toast on every DisconnectedScreen.
The flag now clears when Join Game finishes, so an in-world kick stays
quiet. The autotester can see toast titles and treats a refused connect
as the pre-world disconnect that should show the nudge.
Server config declarations move under a 'modpack' section keyed by
category name -> group id -> declaration, and the shipped content json
mirrors that shape under 'categories'. The category is now where a group
lives, not a field on it: the client can no longer invent a bucket, the
category key doubles as the player-facing section label, and a typo
cannot point a group at a category that does not exist. Category name
guards (blank, padding, control characters, 64-char cap matching the
group ID cap, case-insensitive duplicates), empty categories and
duplicate group ids are loud validation errors. Group ids, requires and
breaksWith semantics are unchanged; the group id remains the slug used
for requires/breaksWith, saved selections and the host-modpack folder
name, while displayName stays the free-form player-facing label.

The manifest stays flat and its record shape is unchanged; declared
category and group order is now preserved end to end instead of being
force-sorted alphabetically.
The screen is now Group Selection, and Modpack details is Modpack
Settings; the multiplayer button that opens the installed-pack list
becomes Modpacks. Every group sits in a declared category, so the list
is always sectioned: headers render in declared order with their groups
beneath them, and the invented General bucket, its caption row and the
title-casing of category ids are gone - category names are display
strings and render verbatim.

Conflicts no longer ask: the resolver's deterministic replacement
applies immediately and a yellow receipt line names what was turned off
and which choice caused it; when even the replacement cannot resolve,
the failure line explains why and the selection stays unchanged. The
conflict modal and its strings are deleted. The selection summary line
gains the estimated download size, deduplicated by SHA-1 so shared
files count once. Pack-level change effects in the browser name the
pack instead of borrowing the old General label. Locales drop the
renamed and deleted keys and fall back to en_us for the rest.
The server config docs now describe the modpack section (category name,
then group id, then declaration), the category-name guards, and the
declared-order guarantee. An identities note spells out the rename cost
tiers: group ids anchor requires/breaksWith, saved selections and the
host-modpack folder name; category names anchor saved category choices;
displayName is the safe rename. Screen mentions follow the renames to
Group Selection and Modpacks.
Scenario server configs declare their groups under the modpack section
with real category names, the projection/policy parsers walk
categories, and the metadata assertion treats 'category' as the name of
the containing category. The fake bridge models the General header row
and writes nested active manifests. The first gate run failed here, not
in the product: servers silently ran on the default config because the
old flat 'groups' key was ignored, which is exactly the stale-fixture
shape this fixes.
…ction rows

The group selection header compared description line count against a
pixel budget, so its last wrapped line drew straight over the platform
summary; the description is now capped at the rows actually above it.
Below 26.1 the tooltip panel sprite was addressed by its texture path,
but blitSprite on 1.21.2+ resolves atlas sprite ids, so 1.21.x drew the
missing-texture checkerboard behind every tooltip; those targets now
use the sprite id while pre-1.21.2 keeps the path for its hand nine-
slice blit. The review screen's History and Customize actions share one
auxiliary row instead of two full-width ones.
@Skidamek
Skidamek force-pushed the gen/release-hardening branch from 9b8a6ee to b5e6268 Compare September 16, 2026 19:41
@Skidamek
Skidamek merged commit a98fa26 into main Sep 16, 2026
5 checks passed
@Skidamek
Skidamek deleted the gen/release-hardening branch September 16, 2026 19:42
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