fix(prepare): name the local plugin that lacks provenance metadata - #59
Merged
Merged
Conversation
-prepareManaged looked for <dll>.xml / <name>.xml next to a local plugin and passed the path to RefuseLink, which reads file attributes. For a plugin without metadata that raised an unhandled FileNotFoundException instead of the InvalidDataException that names the plugin. SE1-0043
Name where the metadata was expected (both accepted file names) when it is missing, and the file that was read when it is not a GitHubPlugin. SE1-0043
viktor-ferenczi
added a commit
to CometWorks/quasar
that referenced
this pull request
Sep 21, 2026
Fixes for the review of #137 at `3c07ea8`. One commit per finding; the local ticket number (SE1-00xx) is in each commit body. Multi-Host behaviour is out of scope. ## Fixed **Guided setup could never produce a working cluster** - Executor tokens are named by Host ID; the `host-` prefix made every poll fail with `executor_host_mismatch`. A roster already written with the prefix is rewritten in place (SE1-0016). - The cluster spec emits administrators as SteamID64 numbers and rejects `MaxPlayers < 2` before setup binds; the Gateway aborted at startup on strings (SE1-0017). - Local plugins without provenance metadata (UI-plugin companion DLLs) are left out of the cluster profile and named, instead of crashing `-prepareManaged` (SE1-0043, Quasar side; Magnetar side is CometWorks/magnetar#59). - The failed-setup error is shown once (SE1-0044). **Quasar.Host stays up and recovers** - Survives Gateway HTTP timeouts, per-cluster reconcile faults and a faulted Quasar tunnel (SE1-0022). - A reused PID is no longer a permanent unmanaged conflict: launch records carry boot ID + start ticks, final-state records are not inspected (SE1-0021). - A spawn cancelled by the lease budget no longer leaves a permanent `Launching` record; a started process whose identity cannot be committed is stopped and recorded as Failed (SE1-0023). - Invalid attachment / Gateway spec files are ignored; an unrecoverable activation or restore pauses only its cluster instead of exit code 2 on every start (SE1-0037). - Failed Gateway respawns back off 5 s … 5 min (SE1-0031, partial). **Quasar cluster management cannot be wedged by one bad file or one busy cluster** - An unreadable operation record is quarantined as `.corrupt` and logged; the store recovers after a transient write failure; atomic writes are fsynced; store failures answer with the JSON envelope (SE1-0020). - Local operations are closed on every failure path and failed as `interrupted_by_restart` at startup, so they no longer block cluster deletion; unhandled `InvalidOperationException` on cluster routes is a JSON 409 (SE1-0026). - The reconciler, pending-operation and update loops skip a cluster whose lifecycle gate is busy (SE1-0027). - Unacknowledged Gateway requests expire after 10 minutes and can be withdrawn with `DELETE …/operations/{operationId}` (SE1-0035). - A torn `cluster-credentials.json` or `host.json` no longer aborts startup or breaks tunnel authentication for other Hosts (SE1-0037). - Stuck update, partial (SE1-0019): failed lifecycle shutdown is retried under a new key, Stopping/Starting record `LastError` when overdue, and `POST …/update/abandon` plus an **Abandon update** button unlock the lifecycle tools (refused while Hosts are half-activated). - Scheduled cluster backups, partial (SE1-0034): failures are logged and retried, one cluster or one corrupt `backup.json` no longer breaks the rest, and the panel says they need a stopped cluster. **Smaller** - `server.json` `goalState` is numeric again so a downgrade still sees standalone servers; the cluster API keeps names (SE1-0036). - `QUASAR_HOST_BINARY` lets Host enrollment work from a development build; documented (SE1-0041). - Predefined DS worlds are listed once when a Steam and a managed DS install both exist (SE1-0042). - Removed committed `.playwright-mcp/` artifacts (SE1-0038). **Found by the live re-test** - Guided setup did not treat `InvalidDataException` as a failure: the status stayed on its last phase and the exception ended the operator's Blazor circuit (SE1-0045). - The web worker ignored SIGTERM while a Host was connected: the Host tunnel WebSockets held Kestrel's graceful shutdown until the 30 minute `ShutdownTimeout`. They now end on `ApplicationStopping` (SE1-0039). **Found by running a managed cluster with a headless client** - Guided setup sent Magnetar's game version as `binaryVersion`; the Registry compares the node's `Sandbox.Game.dll` assembly version, so every node was rejected and the cluster never left Bootstrapping (SE1-0049). - A managed Gateway is Steam-only. `QUASAR_CLUSTER_TEST_DIRECT_LISTEN` / `QUASAR_CLUSTER_TEST_DISABLE_STEAM` (test only, private addresses only) add the Direct Transport frontend and drop Steam in the generated specification; needs CometWorks/cluster#18 (SE1-0048). - The Steam frontend needs Valve's `steamclient.so` under the Host account and nothing provided it: guided setup and conversion now send the copy from Quasar's managed SteamCMD to the Gateway Host, which stages it with a checksum and an atomic rename (SE1-0048). **Testing without GitHub releases** (SE1-0046) - `QUASAR_CLUSTER_ARCHIVE_URL` / `Quasar:ManagedRuntime:ClusterArchiveUrl` points a test instance at a locally built `ClusterForLinux-<version>.tar.gz` (`file://` or HTTP(S)) with its `SHA256SUMS` next to it. Verification is unchanged, the GitHub token is never sent there, and such a package is accepted only while the override is set. - `QUASAR_MAGNETAR_ARCHIVE_URL` also accepts `file://`; a rebuilt local archive is installed again. Both are documented in `Docs/BuildingAndDevelopment.md`. ## Testing - `dotnet build Quasar.sln -c Release`: 0 errors, no new warnings in changed files. - `Quasar.Tests`: 411 passed (385 before; 26 new tests, one per fix where practical). - `Quasar.Host --self-test`: ok, extended for token names and roster migration, process identity, invalid state files, respawn backoff. - Live on the review install (web worker on `127.0.0.1:18080`, Host `local1` started by hand): - managed-credentials route writes token name `local1`; - zero-length file in `Operations/Clusters` → `/api/ready` 200, file renamed `.corrupt`, path logged; - Host attached to a listener that never answers survives repeated 15 s timeouts; - `Gone` launch record whose PID is held by an unrelated process → `recovery-readiness` 200 (was 409), same for a `Running` record from another boot; - `/world-templates` shows one row per predefined world. - Re-test after cluster v1.1.3 was released: guided setup ran to "Ready to start" on the review install. `admission.json` has numeric administrators, `tokens.json` names the Host ID, a failed setup shows one alert, a Gateway that exits at start is respawned with growing delays. - End to end with a headless client (locally built cluster releases from CometWorks/cluster#18, fed through `QUASAR_CLUSTER_ARCHIVE_URL=file://…`, Direct Transport only, no Steam on the Host account): the managed cluster reached Serving with two regular nodes and the World Authority, the Host's executor heartbeat was accepted, a headless Pulsar client from the cluster bench connected and spawned a living character (Gateway session `Ready`), server-measured movement was 23.6 m walking and 28.6 m / 42.4 m jetpack flight (up and strafe end at the ceiling and walls of the corridor the character spawns in), a rejoin re-possessed the same character, and goal Off shut the serving cluster down cleanly in 8 s. The `steamclient.so` Host route was exercised directly; a cluster serving through Steam was not started, and the Abandon update button was not seen in the browser. ## Deliberately left for later - SE1-0018: resolved for now by cluster v1.1.3; the exact-bytes PluginSdk pin will block setup again at the next Magnetar release. - SE1-0019 remainder: timeouts that act, rollback, abandon during a half-applied activation, CLI command. - SE1-0024 clean-Down proof cleared by activation: needs a "never started since activation" proof in the writer-safety checks; analysed in the ticket, not attempted. - SE1-0031 remainder: verified-bundle hash cache, narrower Host execution gate. SE1-0034 remainder: scheduled backups for a 24/7 cluster. - SE1-0026 remainder: long operations still run on the HTTP request token. SE1-0035 remainder: no UI button for cancel. - SE1-0040 install-root Host/Port ignored: not reproducible at this head; no change. - New, open: goal Off does not stop the Host respawning a Gateway that never started; a fix is proposed in the ticket but contradicts an existing safety test, so it is not in this PR (SE1-0051). - New, open: guided setup accepts a world without a static grid and the converter then fails after the cluster ID is bound (SE1-0047). - Not started: SE1-0025, SE1-0028, SE1-0029, SE1-0030, SE1-0032 (identity half done with SE1-0021), SE1-0033.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
-prepareManagedlooked for<dll>.xml/<name>.xmlnext to aLocalPluginand passed the path straight toRefuseLink, which callsFile.GetAttributes. For a local plugin without metadata this raised an unhandledFileNotFoundException(reported from Quasar guided cluster setup with a UI-plugin companion DLL inLocal/).The path is now checked first and the existing
InvalidDataException("Local plugin needs GitHubPlugin provenance metadata: <id>")is thrown. The message names the plugin ID and both metadata paths that were tried (<name>.xmland<name>.dll.xml). The sibling error for a metadata file that exists but is not aGitHubPluginnow names that file too.Testing:
dotnet build Legacy/Legacy.csproj -c Releasesucceeds; no unit test coversManagedPreparationyet, so this is compile-checked only.Quasar side (leaves such plugins out of the cluster profile before preparation) is CometWorks/quasar#163. Local ticket: SE1-0043.