Skip to content

fix: the panel's silent 125, its unscrubbed record strings, and what it cannot see (#337) - #366

Merged
edgehero merged 5 commits into
mainfrom
fix/panel-sandbox-honesty
Sep 21, 2026
Merged

edgehero merged 5 commits into
mainfrom
fix/panel-sandbox-honesty

Conversation

@edgehero

@edgehero edgehero commented Sep 21, 2026 •

Copy link
Copy Markdown
Owner

Issue #337, items 2, 3 and 4. Item 1 landed in #361. This is the last of the four, so the issue closes with it.

Item 4: a sandbox that never opened redrew over itself

openSandboxSession paused on a refusal, on a detach and on a spawn error, and not on a non-zero exit. The panel has suspended pi's TUI to hand over the terminal, so docker's own line is painted over by tui.start() before anyone reads it. The operator presses b at a run that never opens and no screen ever says why.

It now names the code and waits. Worded around the CODE rather than around docker's message, because Podman uses the same 125/126/127 convention with different text, and the message is already on the operator's screen because stdio was inherited. 125 carries the hint its own code earns: the name may be taken by a container still around, or the image is missing under --pull=never.

Item 3: every record string RUN_DETAIL prints is scrubbed

The failedReason belt covered one field in the deps layer. This pane renders reason, host, backend, target, flow and other runs' job ids straight through.

The scrub lives inside show(), not around the lines it builds. styler.link(styler.fg("accent", show(r.target)), url) puts the value inside two layers of escapes the styler owns, so scrubbing the composed string would destroy the OSC-8 hyperlink and the pane's width math with it, and a test that asserted "no ESC in the output" would be both wrong and green. The class is unchanged: C0 plus DEL, C1 left alone, the same one triggers.mjs's validator uses, now shared rather than copied.

This is belt-and-braces and the spec says so, because DES-ADMIN-VIA-PI-EXTENSION argues that the injection boundary holds by PLACEMENT rather than by filtering, and a filter that reads as the boundary invites the placement rule to be relaxed later. The record is PII-free and worker-written, so this is not a trust judgement about the data: it is a property of the terminal. A byte that moves the cursor, clears the screen or opens a hyperlink must not reach it from a stored field, whoever wrote that field.

Item 2: the panel says what it can and cannot see

RUN_DETAIL's sandbox block gains two lines:

sandbox      retained · 19h left
             egress on via pi-dispatch-egress-proxy
             read from this shell, not the deployment

The second is what the issue actually asks for. The panel resolves PI_EGRESS from its own process because that is what openSandbox uses when b is pressed, and it has no way to compare that against the deployment's: nothing here loads a .env, the panel may have been started anywhere, and the deployment pointer carries paths and never capability grants. So it states the posture and its provenance rather than claiming a mismatch it cannot detect.

A malformed PI_EGRESS is its own state. egressArmed throws on a value it cannot parse and openSandbox refuses rather than opening a shell on the open network, so rendering that as "off" would be the one reading that is wrong in the dangerous direction.

Two lines and not one, and the budget is why. kv pays a 12-column label plus a space out of an inner of 66 at the pane's DRILL_WIDTH, leaving 53 columns for a value that fitLine clips rather than wraps. The one-line form, retained · 19h left · egress on (this shell, not the deployment), is 64 characters: it would have lost exactly the half that matters, silently. Measured against the real renderer, not estimated.

New OQ-038, because one fixed instance is not the whole blindness

The panel also resolves, from its own environment: the retention window it reports, the idle timeout the shell gets, which directory of retained runs it can see at all, and DOCKER_HOST.

That last one is the sharpest and deliberately did not get a line. A Podman deployment points DOCKER_HOST at Podman's socket, so a panel reading it from its own process opens the sandbox on a different daemon than the worker's jobs ran on, and that fails as a missing image rather than visibly. The egress posture at least fails loudly, because a missing proxy refuses.

Widening the deployment pointer is explicitly not the answer, and the entry says why, citing OQ-025: that allowlist is paths and URLs and never capability grants, and a pointer that could set PI_EGRESS=0 or redirect DOCKER_HOST would be the second unreviewed door it exists to forbid. The resolution named is a deployment-owned answer the panel can read without a grant. The launcher being hard-wired to the docker CLI belongs to #354 and is named as out of scope rather than omitted.

Review rounds

Three rounds, three reviewers each. The defect count is the headline: one defect in the original code and twenty-two in my own repairs, including two fixes I added and then withdrew when a reviewer measured them.

What the rounds found, in order of what it would have cost an operator:

  • The clipboard. y hands a job id to OSC-52. The payload cannot break the sequence, but the terminal decodes it into the operator's system clipboard, and a carriage return inside a job id submits a line when it is pasted at a shell prompt. The one route here that was never about the terminal.
  • "Every record string" was false of eight of them, including outcome, which the header is built from and which exit-code.mjs genuinely writes as unknown-exit-<n>; both timestamps through fmtStamp's raw fallback; and five numeric-by-contract fields guarded for presence and never for type.
  • The OSC-8 URL, on the exact call the design argument cites. The scrub lives inside show() because the target is wrapped as styler.link(styler.fg("accent", show(r.target)), url) — and the second argument was built from the raw field. Fixed once, then found again one class narrower.
  • The wrong control-byte class. The project has two: triggers.mjs's validator (C0 + DEL) decides whether a file is acceptable; panel.mjs's CONTROL_CHARS (C0 + DEL + C1) is the defensive strip for untrusted text heading to a terminal, and already backed the plain and ASCII paths for these same rows. The renderer was the outlier. That also answers what Sandbox loose ends: networks nothing sweeps, the panel's view of the egress setting, unscrubbed RUN_DETAIL strings, a silent 125 #337 asked about C1 with code rather than with an argument about terminals — which mattered, because the terminal argument does not hold for xterm.
  • A regression from a "better" fix. Moving the title scrub into clipPlain covered every frame title and lost the unframed degrade, which never calls it. A title has two consumers.
  • The exit message twice. First it claimed the sandbox "never opened" for any non-zero code, false for exit 1 since the entrypoint is bash. Narrowing to 125 alone made a genuine exec bash failed (127) silent. It now states the code and offers the runtime reading conditionally, with a cause that fits each code.
  • Duplication this PR re-introduced while quoting the docblock that warns about it. readSandboxInfo hand-copied resolveSandbox's refusals; there is now one exported predicate and two callers.

Two things I added were withdrawn on measurement: a proxy-name cap that was byte-identical to fitLine's own clip across 568 renders and hardcoded a glyph ASCII mode swaps, and a detached guard that openSandbox makes unreachable by construction.

Six residuals are filed in #367 rather than chased into a fourth round.

Verification

  • Full suite in the CI posture: 3982 tests, 0 fail, 1 skipped. dated-fixture-check, test-count-check and temp-dir-check green. admin/dist/ rebuilt and not committed.
  • Mutations confirmed red: drop the non-zero pause; render a malformed PI_EGRESS as off; remove the scrub from show(); narrow the scrub from the shared C0-plus-DEL class down to ESC alone; drop the caveat line.
  • One guard is recorded as belt rather than claimed as load-bearing: a detached session exits 0, so the !result.detached condition changes no test today. It is there so a runtime that someday detaches non-zero does not make the operator read two messages for one outcome, and the comment says exactly that.

Specs

AMENDED: DES-ADMIN-VIA-PI-EXTENSION (twice: RUN_DETAIL now carries two lines that are not record fields, and the render-time scrub is argued as braces rather than a boundary), OQ-035 (its belt is no longer failedReason-only, and the reason it gave for leaving the record's own fields alone is still why this is braces), INT-SANDBOX-CONTRACT (one clause). NEW: OQ-038.

UNCHANGED, checked: DES-SANDBOX-IS-A-FRESH-CONTAINER, INT-RUN-HISTORY-FILE-CONTRACT (no record field added, renamed or re-typed), OQ-016, OQ-025 (cited).

Docs: docs/sandbox.md and README.md.

Refs #337

…it cannot see (#337)

Items 2, 3 and 4, which are all the panel's.

A SANDBOX THAT NEVER OPENED REDREW OVER ITSELF. `openSandboxSession` paused on a
refusal, on a detach and on a spawn error, and not on a non-zero exit. The panel
has suspended pi's TUI to hand over the terminal, so docker's own line is
painted over by `tui.start()` before anyone reads it: the operator presses `b`
at a run that never opens and no screen says why. It now names the code and
waits. Worded around the CODE rather than docker's text, because Podman uses the
same 125/126/127 convention with different wording, and 125 carries the hint its
own code earns: the name may be taken, or the image is missing under
`--pull=never`.

EVERY RECORD STRING RUN_DETAIL PRINTS IS SCRUBBED. The `failedReason` belt
covered one field in the deps layer; this pane renders `reason`, `host`,
`backend`, `target`, `flow` and other runs' job ids straight through. The scrub
lives INSIDE `show()`, not around the lines it builds, because
`styler.link(styler.fg("accent", show(r.target)), url)` puts the value inside
two layers of escapes the styler owns and scrubbing the composed string would
take the hyperlink and the pane's width math with it. The class is unchanged,
C0 plus DEL, shared rather than copied.

This is belt-and-braces and the spec now says so rather than leaving it to be
read as a boundary. `DES-ADMIN-VIA-PI-EXTENSION` argues that the injection
boundary holds by PLACEMENT and not by filtering, and a filter that reads as the
boundary invites the placement rule to be relaxed later. The record is PII-free
and worker-written, so this is not a trust judgement about the data: it is a
property of the terminal. A byte that moves the cursor, clears the screen or
opens a hyperlink must not reach it from a stored field, whoever wrote it.

THE PANEL SAYS WHAT IT CAN AND CANNOT SEE. RUN_DETAIL's sandbox block gains the
egress posture a sandbox opened from here would get, and the sentence that it
was read from this shell rather than from the deployment. That second line is
the one the issue asks for: the panel resolves `PI_EGRESS` from its own process
because that is what `openSandbox` uses when `b` is pressed, and it has no way
to compare that against the deployment's, so it states the provenance instead of
claiming a mismatch it cannot detect. A malformed `PI_EGRESS` is its own state:
`egressArmed` throws and `openSandbox` refuses rather than opening a shell on
the open network, so rendering that as "off" would be the one reading that is
wrong in the dangerous direction.

Two lines and not one, and the budget is why. `kv` pays a 12-column label out of
an `inner` of 66 at the pane's DRILL_WIDTH, leaving 53 for a value that
`fitLine` CLIPS rather than wraps, and the one-line form is 64 characters: it
would have lost exactly the half that matters, silently.

NEW `OQ-038` records the rest of the blindness, because fixing one instance
should not imply the whole thing is answered. The panel also resolves the
retention window it reports, the idle timeout the shell gets, which directory of
retained runs it can see at all, and `DOCKER_HOST`. That last one is the
sharpest and deliberately did not get a line: a Podman deployment points it at
Podman's socket, so a panel reading it from its own process opens the sandbox on
a DIFFERENT DAEMON than the worker's jobs ran on, and that surfaces as a missing
image rather than visibly. Widening the deployment pointer is not the answer and
the entry says why, citing `OQ-025`: that allowlist is paths and URLs and never
capability grants, and a pointer that could set `PI_EGRESS=0` or redirect
`DOCKER_HOST` is the second unreviewed door it exists to forbid.

Specs: `DES-ADMIN-VIA-PI-EXTENSION` AMENDED twice, `OQ-035` AMENDED (its belt is
no longer `failedReason`-only, and the reason it gave for leaving the record's
fields alone is still why the wider scrub is braces rather than a boundary),
`INT-SANDBOX-CONTRACT` AMENDED (one clause), NEW `OQ-038`. UNCHANGED, checked:
`DES-SANDBOX-IS-A-FRESH-CONTAINER`, `INT-RUN-HISTORY-FILE-CONTRACT`, `OQ-016`,
`OQ-025` (cited as the reason the panel cannot simply be told).

Refs #337

Signed-off-by: Rob Boerman <robboerman@live.nl>
)

Gate round one, and the headline finding is that the previous commit's central
claim did not hold inside its own scope.

EIGHT FIELDS BYPASSED THE SCRUB, and one of them is the first thing the pane
prints. `outcome` built the header through `String(r.outcome)`; `fmtStamp`
returned a raw string for a timestamp that does not parse; and the four
numeric-by-contract fields (`budgetReserved`, `attempt`, `chainDepth`,
`chainRefused`, plus `replicas`) are guarded for PRESENCE and never for type,
so a corrupt record puts an arbitrary string on the terminal. `outcome` is not
hypothetical: `exit-code.mjs` writes `unknown-exit-<n>` into exactly that field.
All of them go through `show` now, and the enum comparisons read the raw value,
so a scrubbed display cannot change which glyph or colour a run gets.

THE PANE'S OWN TITLE WAS UNSCRUBBED. `frame()` gets `run <jobId>` and its
`clipPlain` clips without stripping, so the frame around the scrubbed lines
carried what they no longer did.

AND THE OSC-8 URL, ON THE EXACT CALL THE RATIONALE CITES. The previous commit
argued the scrub must live inside `show()` because the target is wrapped as
`styler.link(styler.fg("accent", show(r.target)), url)` -- and left the SECOND
argument of that call built from the raw field. `targetUrl`'s character class
excluded whitespace, and JS's `\s` does not include ESC, BEL or NUL, so a github
target carried them into an OSC-8 payload that a BEL terminates early. That
class now excludes control bytes.

That test was hard to write honestly and took three attempts, which is worth
recording. Parsing the OSC-8 payloads is not enough, because a BEL inside one
ends the sequence and a parser captures a clean prefix. `stripAnsi` is not
enough either, because it swallows everything between an OSC opener and its
BEL, which is exactly where a clear-screen hides. And the whole thing is vacuous
under the default test theme, whose `link` is a byte-identical passthrough that
emits no OSC-8 at all. The test now runs under a real theme, strips only the
styler's own SGR, and requires that no escape and no bell survive anywhere.

THE 125 MESSAGE CLAIMED WHAT NO EXIT CODE CAN CARRY. The sandbox runs
`--entrypoint bash -i`, so `docker run` returns the interactive shell's status.
An operator whose last command failed, or who typed `exit 1`, was told the
sandbox never opened and made to read a pause for it; 126 and 127 are bash's own
"not executable" and "not found", so a hint about the IMAGE fired on a healthy
session. Measured against real docker. It is now 125 alone, which is the code a
runtime reserves for its own pre-start refusal and the one the issue named. The
hint also gains the cause an earlier draft omitted and a review measured: an
unreachable DOCKER_HOST exits 125 too, and sending an operator to inspect
container names when they are talking to the wrong daemon is the expensive kind
of wrong hint.

THE `detached` GUARD IS GONE RATHER THAN EXPLAINED. It was kept as insurance
against a runtime that someday detaches with a non-zero code. That insurance
cannot pay out: `openSandbox` sets `detached` only inside
`if (network && !error && code === 0)`, so such a session arrives as
`detached: false` and gets the message anyway. A guard that cannot do the job it
is kept for is worse than none, because it reads as a handled case.

TWO SMALLER ONES. A whitespace-only `PI_EGRESS_PROXY` rendered `egress on via`
and then stopped, because `egressProxyName` falls back with `||` and whitespace
is truthy; it now says the name is blank, which is what `b` will look for. And
an operator-set proxy name longer than the budget was clipped silently by
`fitLine`, which is the failure the two-line split exists to avoid, so it is
shortened with a mark instead.

THE BELT NOW COVERS THE LIST TOO. The issue named RUN_DETAIL, but a row that
prints the same stored field raw into the same terminal is a belt the next
reader will assume covers both.

PROSE. `OQ-038` cited `OQ-025` for the pointer's policy, which `OQ-025` does not
state: that doctrine is `INT-DEPLOYMENT-POINTER-CONTRACT`'s and
`deployment-pointer.mjs`'s. Corrected in the entry, the row and two code
comments. The entry also used the capability argument to close off
`PI_SANDBOX_DIR`, which is an ordinary absolute path the allowlist could carry
today, so the bullet now splits by key and says which half is a cheap allowlist
review rather than a new interface. `OQ-035` answers the question #337 actually
asked about the C1 range rather than restating the convention: on a UTF-8
terminal a JavaScript C1 code point is emitted as two bytes and is not read as
CSI, so widening the class would change no terminal's behaviour and would mangle
legitimate text. And the design entry's sandbox block is three lines that are not
record fields, not two: the retention verdict already was not one.

Refs #337

Signed-off-by: Rob Boerman <robboerman@live.nl>
…lipboard (#337)

Gate round two. Three passes, and between them they found that the round-one
repair was still narrower than its own sentence in four places, that one thing I
added was a measured no-op, and that the narrowing I made to the exit message
traded a false positive for a false negative.

THE CLIPBOARD IS THE SHARP ONE, because it is the only route where this is not
about a terminal at all. `y` in RUN_DETAIL hands a job id to `copyText`, which
base64s it into OSC-52; the payload cannot break the sequence, but the terminal
decodes it into the operator's SYSTEM CLIPBOARD, and a carriage return inside a
job id then SUBMITS a line when it is pasted at a shell prompt. The bytes leave
the panel there, so that is the last place that can decline to hand them over.

THREE MORE ROUTES, all outside `renderRunDetail` and so outside where the first
repair looked. The armed cancel question prints a job id straight into the LIST
footer. The LIST row's replica badge printed `replica` and `replicas` raw, which
is the same field pair the first repair fixed one pane down. And the frame TITLE
goes through `clipPlain`, which clips without stripping.

The title fix is in `clipPlain` itself rather than at the call site, and that is
the point of it: two of the five frame titles are built from a record's job id,
the first repair scrubbed RUN_DETAIL's and left LIVE_TAIL's, and a per-call-site
rule is one a call site will miss. `panel.mjs`'s own `box` already titles through
a stripping clip, so the two frame builders now agree rather than differing by
which file a pane happens to use.

THE PROJECT HAS TWO CONTROL-BYTE CLASSES AND I PICKED THE WRONG ONE. The narrow
one is `triggers.mjs`'s VALIDATOR, C0 + DEL, which decides whether an
operator-authored file is acceptable. The wider one is `panel.mjs`'s
`CONTROL_CHARS`, C0 + DEL + C1, whose own comment calls it a defensive strip of
untrusted input and which already backs `clip` -- so the plain and ascii render
paths have been stripping C1 out of these same rows all along, while the themed
path did not. The renderer was the outlier, not the convention. It is the wider
class now.

That also answers what issue #337 actually asked about C1, and answers it with
code rather than with an argument about terminals. The argument was available
and is not what this rests on: a JavaScript C1 code point leaves Node as two
UTF-8 bytes and most terminals do not read that back as CSI, but "most" is doing
real work there, and matching a class this project already shipped is better
than a claim nobody here has measured.

THE PROXY CAP IS GONE, AND ITS TEST WITH IT. I added it last round on the
reasoning that an operator-set name is unbounded and `fitLine` truncates
silently. A pass measured it across 568 renders: byte-identical to `fitLine`'s
own clip at every width the pane can take, because the budget works out to
exactly the column `styler.cell` was already cutting at. Worse, the hand-cap
hardcoded an ellipsis while `fitLine` swaps that glyph under
`PI_DISPATCH_ASCII`, so its one observable effect was pushing a non-ASCII
character into the mode that exists to avoid them. The assertion that claimed to
tell "shortened by us" from "clipped by the frame" could not, because they are
the same bytes; it now pins the property that matters, which is that the caveat
keeps its own budget whatever the name.

THE EXIT MESSAGE NOW SAYS SOMETHING THE CODE CAN CARRY. Round one claimed the
sandbox "never opened" for any non-zero code, which is false for an operator who
typed `exit 1`, since the sandbox's entrypoint IS bash. Narrowing to 125 alone
fixed that and introduced the opposite: a genuine `exec bash failed: No such
file or directory` exits 127 and went silent again, which is the symptom item 4
exists to remove. No exit code separates the two, because docker reuses 125, 126
and 127 for both its own refusal and bash's last command. So the message states
the code and offers the runtime reading CONDITIONALLY, which is true either way,
and the redraw is stopped for all three. Telling them apart properly needs a
docker read after the exit, which belongs with OQ-038's other
unanswerable-without-docker question.

THE PANEL NO LONGER DESCRIBES A SESSION IT CANNOT OPEN. `readSandboxInfo`
stopped at the venue refusal, so a run whose manifest names no image, or whose
local folder moved, was offered `b`, given two lines of egress detail about the
session it would get, and refused the moment the key was pressed. Nobody reads
two lines of detail about a door they are also being told is shut. It now asks
the other two SYNCHRONOUS refusals `resolveSandbox` makes. The third, a proxy
that is not running, needs docker and stays OQ-038's.

Two wordings. A whitespace-only `PI_EGRESS_PROXY` is named rather than rendered
as an empty tail, and shown untrimmed, because the sentence beneath only means
something if the name shown is the name `b` will look for. And `egress off` now
says `(docker's default bridge)`, because alone it reads as "no network at all"
and it is the opposite.

OQ-038 gains the live consequence that is its own strongest argument:
`PI_LOGS_DIR` is on the pointer allowlist and `PI_SANDBOX_DIR` is not, while the
panel resolves both, so a wizard-pointed panel already reads the deployment's
run history beside its own sandbox directory and every retained run reports
itself swept.

Refs #337

Signed-off-by: Rob Boerman <robboerman@live.nl>
)

Gate round three. The sharpest finding is that this PR re-introduced, one layer
up, the exact duplication that `openSandbox`'s own docblock warns about, in the
commit that quotes it.

TWO CALLERS ASSEMBLING THE SAME ANSWER FROM PARTS. `readSandboxInfo` checked the
venue refusal and then, last round, gained hand-copies of the other two
`resolveSandbox` makes. That is the shape the docblock names: "how one of them
drops a part", which is precisely how the panel came to offer `b` for a run with
no image in the first place. There is now one exported `sandboxSyncRefusal` --
every refusal decidable from the manifest alone, in the order an operator should
read them -- and both callers use it. The panel keeps its own WORDING, because
`resolveSandbox` names the workspace path and this pane's rule is a retention
state and never a path.

A CAUSE THAT CANNOT PRODUCE THE CODE. Widening the exit message to 126 and 127
kept 125's three causes for all three: a name clash cannot produce a 127, and
the cause the widening was FOR -- `exec bash failed: No such file or directory`
-- was not in the list at all. Each code now carries what that code means, under
the same conditional frame, so the sentence stays true whichever side produced
it. Worth naming plainly: the previous round measured the 126/127 false positive
and rejected it, and this round re-accepts it deliberately. What changed is that
the wording no longer asserts which reading holds, so a healthy `exit 126` reads
"if no shell opened, the image's entrypoint is not executable" rather than a
claim about an image that is fine.

A SPEC POINTER THAT DID NOT RESOLVE, for the second time in this branch. The
code cited `OQ-038` for a residual `OQ-038` did not contain. Round one did the
same with `OQ-025`. The fix this time is the other direction: the residual
genuinely belongs in that row, so it is written there -- the panel cannot tell a
runtime refusal from a shell exit without a docker read, which is the same shape
as everything else that row records.

THE BELT'S CLASS MOVED WITHOUT A ROW. `scrubReason` shares `scrubControl`, so
last round's widening to C0 + DEL + C1 widened the `failedReason` belt too, and
`OQ-035` recorded only that the cap was unchanged. Nothing observable moved -- a
worker throw's message decoded as UTF-8 has no C1 code point -- but a contract
that moves without a row is the gap this project's rule exists to close. Written
down, and the fixture now carries a C1 byte so the class is pinned rather than
implied.

AND THE TWO FRAME BUILDERS AGREE ON THE CLASS, NOT THE BEHAVIOUR. `clip` deletes
a match; `clipPlain` substitutes a space. That difference is deliberate --
`frame` computes its top rule from the title's length at the call site, so a
deleting strip would silently change that arithmetic -- and the sentence claiming
they simply agree is corrected rather than the code.

`docs/sandbox.md` gains the `(docker's default bridge)` wording, which the last
commit introduced and did not document.

Refs #337

Signed-off-by: Rob Boerman <robboerman@live.nl>
…e panel did not write (#337)

Gate round three, part two. The sharpest item is a regression this branch
introduced one commit ago.

MOVING THE TITLE SCRUB INTO `clipPlain` LOST THE PATH THAT NEVER CALLS IT. Last
commit put the strip there so every frame title would inherit it, which is right
for the framed path. But a title has TWO consumers: below `MIN_WIDTH`, or with a
width that is not a finite number, the pane returns it as a bare array element
and never goes near `frame()`. That degrade is a supported mode this suite
already renders at width 4 and width 0. The earlier call-site scrub had covered
it, and the "better" fix did not. Both consumers hold the property now, and
`clipPlain` keeps its strip as the belt for any future caller. LIVE_TAIL turned
out to carry the id a third time, in a plain header beside its own title.

`targetUrl` WAS THE LAST NARROW CLASS IN `admin/src`. Everything else moved to
C0 + DEL + C1 last commit; its character class stayed C0 + DEL, and `\s` does not
cover C1 either, so a C1 byte in a github target still reached the terminal
inside an OSC-8 URL, in both panes. That is the round-one hole reopened one class
narrower, in the commit whose message said the drift was eliminated.

TWO MORE VALUES THE PANEL DID NOT WRITE. `cancelNote` interpolates the cancel
ack, which is a string ANOTHER PROCESS put in redis, and the queue-side job id,
into the same footer and the same render call as the armed question scrubbed two
lines above it. All three of its id-carrying branches are driven now, rather than
the one that happened to be first.

THE GATE'S CLAIM WAS WIDER THAN THE GATE. It said "every refusal decidable from
the manifest alone", and there is one more: `decideSandboxJobUser` can refuse a
linux run from `manifest.jobUser`. Folding that into the shared predicate would
change what the CLI refuses and when, which is a separate decision, so the claim
is narrowed to the three it asks and the exception is named where a reader will
meet it.

TWO TEST COMMENTS CLAIMED MORE THAN THEIR ASSERTIONS. The outcome test said its
shape was "the only one that can tell the two readings apart" -- it cannot, and
the source comment beside the code already said so. And an assertion guarded the
absence of two earlier drafts' wording, which is history rather than behaviour;
it now pins the conditional form positively.

Filed rather than fixed, and the PR body says so: the HELD and FAILED panes
render their own ids raw by the same mechanism (outside what this issue named,
same file), two genuine pre-start refusals exit 1 and stay silent (`-t` with no
TTY, a `DOCKER_CONTEXT` that does not resolve), a healthy shell whose last
command was a typo buys one 2.5 s pause, and `readSandboxInfo` can never set
`running` so the pane's "running" branch is unreachable.

Refs #337

Signed-off-by: Rob Boerman <robboerman@live.nl>
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