fix(updater): accept only Monolith binary release assets - #104
fix(updater): accept only Monolith binary release assets#104kunkunGames wants to merge 1 commit into
Conversation
|
Merge-order note for the six open PRs from me (#104, #106, #107, #108, #112, #113) — posting once here and linking from the others. They conflict, but only in No source file conflicts in any pair. Suggested order (each is independently mergeable; this order just minimises rebases):
I will do the rebases — you should not have to resolve If you would rather not carry six separate changelog entries at all, I am equally happy to collapse the four |
|
Confirmed the bug and this is going into the next release, v0.21.4. Keeping it out of v0.21.3 was deliberate: it is the only change in that batch touching the auto-update path, and that path needs a full end-to-end Windows rehearsal before it ships. Bundling it with seven compile-and-ship fixes would have put pressure on that gate, which is exactly how v0.14.7 shipped an install path that could never succeed and went unnoticed for fifteen release cycles. Verified your diagnosis against master: the fallback at Your PR also prompted a change that matters more than the code fix. Two deviations when it lands, both worth flagging: I am not using an exact filename match. The tag and the asset name are coupled by convention only — the script builds the name from The description says the change also removes the I land contributor fixes as my own commits rather than merging the branch — shipped history stays single-author, and you will be credited in the v0.21.4 notes. Leaving this open until then. |
37a63cb to
c68c1c1
Compare
c68c1c1 to
57ad2d4
Compare
|
Both deviations you flagged are already in the branch — they landed in Prefix/suffix, not exact match — this is what you described, and nothing is coupled to the hand-typed tag: bool IsMonolithBinaryZipName(const FString& Name)
{
return Name.StartsWith(TEXT("Monolith-"), ESearchCase::IgnoreCase)
&& Name.EndsWith(TEXT(".zip"), ESearchCase::IgnoreCase);
}
bool MatchesEngineBinaryZipName(const FString& Name, const FString& EngineTag)
{
return IsMonolithBinaryZipName(Name)
&& Name.EndsWith(FString::Printf(TEXT("-%s.zip"), *EngineTag), ESearchCase::IgnoreCase);
}The if (!IsMonolithBinaryZipName(Name))
{
continue; // unrelated asset leaves the loop here
}
if (Name.Contains(TEXT("-UE5."), ESearchCase::IgnoreCase))
{
bPerEngineRelease = true; // only reachable for Monolith-*.zip
}So One ordering detail that looks like a bug on a quick read but is deliberate: The selector is a pure function over the release JSON with no HTTP or install side effects, so |
|
Status update: not in v0.22.0, and deliberately so. Still queued, and I want to be straight about why it keeps slipping rather than leaving you guessing. The change itself is good. I reviewed it properly this time — the selector is correct, it is well scoped, it has no regressions against master, and removing the Two things are holding it. It requires an end-to-end rehearsal, and that is a hard rule here. Any change to the auto-update path has to be driven through a real Install click — download, hash verification, staged swap — against a real release, including from an old client built from a prior tree. That rule exists because v0.14.7 shipped an install path that could never once succeed on Windows and it took fifteen release cycles to notice: And its urgency is genuinely low, which is the honest reason it gets cut when time runs short. The failure it targets — a client polling during the publish-then-upload window and installing a source zipball — is already closed server-side for every deployed client by publishing as a draft and flipping only after the assets are up and verified. That mitigation reaches clients no code change can, including all the ones already installed. Your PR protects clients installed at the version that ships it, against a window the draft flow has already shut. That makes it defence-in-depth against process error — someone publishing by hand, or a future maintainer dropping the gate — which is worth having, but not worth waiving a rehearsal for. So the rule I wrote down before this release started, and followed: budget the rehearsal and ship it; the moment that budget is at risk, cut this rather than shorten the gate. The budget went, so it was cut. Next release, with the rehearsal. Two things from your PR did land in v0.22.0 in the meantime: the release-body verification gate now catches pre-v2 SHA markers in any position rather than only at line start — it was line-anchored while the deployed parser it defends is unanchored, so an ordinary markdown bullet would have passed the gate and crashed old clients — and the offline-CLI build gate and freshness guard now actually run. |
Fixes tumourlove#83 and tumourlove#99 -- the same conceptual error twice, in two readers that answered confidently about the wrong thing. BOTH WERE REPRODUCED IN A LIVE EDITOR BEFORE ANY CODE WAS WRITTEN. That ordering was deliberate: earlier the same day three gaps (tumourlove#101, tumourlove#104, tumourlove#87) turned out to have real symptoms and WRONG STATED CAUSES, and two fixes had been funded on descriptions nobody had reproduced. tumourlove#83 -- get_module_script_inputs looped Cast<UNiagaraNodeInput> filtered to Usage == Parameter, which on a stock Epic module matches only the ParameterMap plumbing node. Epic exposes module inputs as ParameterMapGet Module.* PINS, not Input nodes. Measured before: GravityForce returned input_count 2 -- the InputMap and the Coordinate Space switch -- omitting Module.Gravity entirely, and carrying NO warnings key at all. After: Module.Gravity by name, InputMap gone, the switch keeping its full valid_options, count still 2. The NAMES were always the signal; the count never was. tumourlove#99 -- get_module_output_parameters was NOT A BUG, it was AN EMPTY BRANCH. The MapSet path was literally `if (MapOut) { }` plus three comments ending "But the formal output node approach above covers the standard case." It did not. Every call returned output_count 1, outputs [OutputMap] -- nothing, dressed as an answer. After: stock Collision returns 95 outputs, 50 addressable, OutputMap absent, split by kind, with Output.Module.X resolved to the caller-addressable Output.Collision.X. THE FIX ALREADY EXISTED ONE FUNCTION AWAY. Gap tumourlove#31-F fixed this exact bug in HandleGetDynamicInputInputs, and its own note points readers at get_module_script_inputs -- which still had it. A documented fix, never applied to the sibling. Both now share one traversal via a new MonolithNiagaraParameterNames.h holding the pure name rules, with HandleGetDynamicInputInputs and BuildStackWriterIndex refactored onto the same rules rather than a third variant. Unresolvable entries now WARN instead of vanishing. Verified on the output side: delete a placed module's script and the reader returns "CANNOT REPORT: ... outputs are UNKNOWN -- 'outputs' being empty does NOT mean the module writes nothing." The input-side unresolved path was code-reviewed but could not be constructed; it is NOT recorded as verified. Also removed both surviving statements of the refuted I-37 -- there were TWO sites, rename_script_parameter's schema description AND a runtime warning. That claim shipped inside the tool's own help text, so a caller reading the action rather than the docs still got the wrong rule. KEPT DELIBERATELY, flagged by the author as outside his brief and ruled on: the IsA-based detection makes BuildStackWriterIndex pick up UNiagaraNodeParameterMapFor writes it previously missed, affecting four actions. A writer index that misses writers is the same defect class being fixed here, so it stays. Measured afterwards: audit_stack_wiring, trace_parameter_binding, get_available_parameters and list_stack_writers all sane and mutually consistent -- BUT the new branch never fired, because no ParameterMapFor content exists in reach and add_graph_node cannot author one. Nothing broke; the widening's EFFECT is undetermined. Tests: 2 new, Monolith.Niagara.ParameterNames, and they were RUN -- 2/2 pass, alongside Monolith.RequiredAssetPath 7/7. They live in MonolithNiagara rather than MonolithCore because MonolithCore cannot link them, and they cover the NAME RULES ONLY, not the graph traversal that actually failed. The live validation is the real coverage here and the note says so. Logged, not fixed: Cast<UNiagaraNodeParameterMapGet/Set> does not compile under MONOLITH_RELEASE_BUILD=1 (private header, no export macro) and CollectStackParameterReaders already uses it unguarded -- a pre-existing latent break in a release path we do not build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
Make release selection fail closed: the updater now accepts only explicit Monolith binary ZIP assets and never falls back to GitHub-generated source archives.
Problem
The updater previously allowed a release source archive to become the installation URL when no suitable uploaded binary was found. Loose filename matching could also confuse neighboring engine versions such as
UE5.8andUE5.80, or let an unrelated plugin asset influence per-engine selection.That turns release metadata mistakes into unsafe installations instead of a clear "no compatible update" result.
Solution
Monolith-*.zipzipball_urlsource archivesVerification
MonolithCoreMonolith.Updater.ReleaseSelection: 7/7 tests passed, with zero test warnings, zero errors, and process exit code 0git diff --checkpassedDocs/testing/2026-07-26-updater-binary-release-selection.mdCompatibility and risk
Download, checksum, extraction, and self-replacement behavior are unchanged after a valid asset is selected. The intentional behavior change is that a malformed release now reports no compatible candidate instead of attempting to install a source archive or another binary.
This PR has no public API or configuration migration.
Visual evidence
Not applicable: this is updater selection and regression-test work with no visual or editor-facing UI change.