fix(studio): acquireSubstrate's pre-flight guards answer from inside the try - #517
Merged
KnockOutEZ merged 2 commits intoAug 28, 2026
Merged
Conversation
…type contracts The injected-source seam never passes `readSubstrateManifest`, so the type established there does not hold for a manifest handed in through `deps.source` — the seam a published channel re-enters with a manifest of its own. The pre-flight guards answer from outside the try, and `staysInsideItsDirectory` calls `isAbsolute()`, which throws `ERR_INVALID_ARG_TYPE` on a non-string straight past a documented NEVER THROWS into `warmup`'s unguarded await. The discriminator is settle-vs-reject rather than an outcome string, because that is the property the contract is about: an assertion on `outcome` alone cannot tell a refusal apart from a crash. `readSubstrateRecord` checks truthiness where `readSubstrateManifest` establishes `typeof === 'string'`, so a numeric `version` reads back PRESENT and flows into the detail text, the logs and every doctor row downstream. Red against unfixed source: three executable-shape arms reject, and the numeric record version reads back present.
…the try `8f465f67` moved the join inside the try so NEVER THROWS would be true of the function's shape rather than of its guards, and then left the guards themselves outside — where `staysInsideItsDirectory` calls `isAbsolute()`, which throws `ERR_INVALID_ARG_TYPE` on a non-string. The class reopened one line higher, on the one seam a manifest can still arrive mistyped through: `deps.source`, which never passes `readSubstrateManifest` and which is where a published channel plugs in. `warmup` awaits this call unguarded on the strength of the contract, so the throw took the browser, model and search phases with it. Both guards now sit inside the try, and the type is established at the seam the way `readSubstrateManifest` establishes it for a manifest on disk — so the refusal names its own reason instead of surfacing as a generic install failure. What the guards refuse is unchanged; only where they answer from. Same file, the sibling asymmetry: `readSubstrateRecord` checked truthiness where `readSubstrateManifest` establishes `typeof === 'string'`. The other two fields threw their way to null through its own catch, so the shape that got through was a numeric `version`, reading back as PRESENT and flowing into the detail text, the log line and every doctor row downstream as a number.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Closes
KnockOutEZ/wigolo-studio-run#188(PX0 exit-6).CI deferred — quota.
What was wrong
8f465f67(#513) movedjoin(root, version)inside the try precisely so "NEVER THROWS is true of the function's shape rather than of its guards". It left the guards themselves outside — andstaysInsideItsDirectory(:136) callsisAbsolute(), which throwsERR_INVALID_ARG_TYPEon a non-string. The exact class #513 closed forversionreopened one line higher forexecutable, on the one seam a manifest can still arrive mistyped through:deps.source, which never passesreadSubstrateManifestand which is where S16-alpha's published channel plugs in.warmupawaits this call with no try/catch on the strength of the documented contract, so the throw takes the browser, model and search phases with it.Sibling asymmetry in the same file (security review LOW-2):
readSubstrateRecord(:173) checked truthiness wherereadSubstrateManifest(:294) establishestypeof === 'string'. The other two fields throw their way tonullthrough the function's own catch, so the shape that actually got through was a numericversion— read back as PRESENT, then flowing intoalready_present's detail text, the acquisition log and every doctor row downstream as a number.What changed
readSubstrateManifestdoes for a manifest on disk — so the refusal names its own reason instead of surfacing as the catch's generic install failure. What the guards refuse is unchanged; only where they answer from.readSubstrateRecordestablishestypeof === 'string'onversion,executableandpath.Demo
Repro against the built
dist/, injected source + a hand-editedrecord.json.Before (tip
89f423b1):After:
The
well-formedlines are the anti-vacuity arms: they are unchanged across the fix, so the new refusals cannot be satisfied by a guard that refuses everything.Red-then-green
7b0b94cd(test) is the direct parent ofd920d200(fix). Against unfixed source it was red on exactly the two defects and nothing else:The discriminator in the acquire arms is settle-vs-reject, not an outcome string — that is the property the never-throws contract is about, and an assertion on
outcomealone cannot tell a refusal apart from a crash.Suites (local — CI deferred, quota)
Non-goals honoured
No concurrent-acquire interlock, no TOCTOU work between record validation and spawn, no change to what the guards refuse.