Skip to content

Only the installer's view writes /apps - #1203

Merged
Japabu merged 4 commits into
mainfrom
apps-read-only-but-the-installer
Nov 11, 2026
Merged

Japabu merged 4 commits into
mainfrom
apps-read-only-but-the-installer

Conversation

@Japabu

@Japabu Japabu commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Every row the image declares saw /apps read-write, so a shell and everything it spawned could rewrite an installed package. The owner ruled on 2026-11-05 that only the installer service writes /apps (stage 2 of issues/every-program-sees-only-the-files-it-was-given.md).

What changes

  • A declared row's view holds /apps read-only unless the row declares installs = true (toyos_manifest::Program::installs, beside the /log arm keeps_the_log selects). The supervisor is unchanged: it already mints a row's grants from Program::view, storage rows excepted (below), and the file server already refuses every changing request on a read-only grant.
  • images/system.toml gives installs to pkg's row and no other.
  • pkg refuses install and remove by name where its /apps capability is read-only: pkg: /apps is read-only to this process; run it with launch pkg``. pkg list still works anywhere.
  • The stale statements that /apps is writable to every row are gone from pkg's header, the manifest crate and system.toml.

Decision: pkg is the installer service, not its client

The brief left this open. pkg becomes the service because that shape adds no parts:

  • A launch already gives a row its own view, so launch pkg install <file> holds /apps read-write while the shell that typed it does not. update, swap, grants and disks already work this way.
  • Who may install is the starts lists naming pkg (the shell's and toybox's). Installed packages have no starts, so none can reach it. The whole authority is in images/system.toml.
  • The client-and-daemon shape would need a new port, a protocol to carry an archive the daemon cannot see in the client's view, a boot-started process and a second consent path. Nothing in the records asks for those yet.

Consequence for users: a bare pkg install x at the shell is a helper holding the shell's view and is now refused with the message above. launch pkg install x installs.

No build gate restricts installs to a row named pkg. A test image needs another row to be its installer (below), and the field is one reviewed line in the image's own manifest. The build's new test states which row writes /apps in every committed config instead.

The manual's files page now says "the installer's view" rather than "the installer service's view", matching "the updater's view" beside it: the holder is a launched program, not a daemon.

Tests

Host:

  • toyos_manifest::tests::only_the_installer_writes_apps: the row declaring installs holds /apps read-write; the shell that starts it, a service and a row merely named pkg hold it read-only; a package's own directory stays read-only.
  • build::tests::only_the_installer_s_view_writes_apps: over every committed config, the rows whose rendered view writes /apps are exactly pkg in images/system.toml and hostedclangcase.toml, test-runner in proctreecase.toml, and none elsewhere.
  • Negative controls, each a checked patch applied to the head, run and reverted:
    • the view arm ignoring installs (main's behaviour): both new tests and a_declared_row_sees_the_whole_tree_but_state_and_of_state_its_own are red;
    • the shell's row granted installs in images/system.toml: the manifest crate's tests stay green (they read no config) and the build test is red with ["pkg", "shell"] against ["pkg"].

Metal (app_view, on the T14, because it needs a real file server, supervisor and launcher; it has no QEMU registration):

  • proctreecase.toml makes test-runner that image's installer, because the job writes and rewrites its packages under /apps directly, which no installer does.
  • The job gains the exit's first arm: a shell launched in the login session redirects one line into /home, which must write, and into /apps/appview, which must fail with the file server's read-only reason and leave no file. /home and /apps are one volume and one server and differ only in the grant, so the grant is what refused. This stands in for the exit's control boot (the shell's row granted /apps read-write), by the orchestrator's decision, recorded in the issue.

hosted_clang_hello now launches pkg rather than spawning it, and its image gives test-runner starts = ["pkg"]. It is on demand, needs a product no CI job or the guest runner holds, and was not run.

Not established by this PR

  • An install by a launched pkg on a machine, and hosted_clang_hello as edited. Its judge is the owed metal row pkg_install_gbae (issues/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.md, stage D). Until it exists the claim rests on composition: pkg's host tests for the install itself, the view tests above for who holds /apps read-write, and the launcher and file server unchanged.
  • pkg's own refusal message as a helper has no test: it is one branch on Dir::writable(), which needs a file server.

Recorded, not fixed here

By inspection, diskserver and the file servers for memory and system start holding a duplicate of the supervisor's whole-tree namespace, /apps read-write included. They are the signed image's storage stack and open no path, so the shell-to-package boundary does not depend on it. It is unverified on a machine and filed as issues/storage-rows-start-holding-the-supervisors-whole-tree.md, with its exit tied to stage 2.

A caller that can launch pkg answers its question itself (--yes, or the stdin it supplies), so it can still replace a package whole. The boundary this PR adds is between launcher holders and everything else.

Checks and evidence

Evidence: https://github.com/ToyOSOrg/evidence/tree/eff6d9e767dc90c4472908660613872f68f76dbb (INDEX.md lists each file). Host is macOS arm64.

Check Revision Exit
cargo run -- --ci host 65f2ec1 1: 1 of 110 steps red
control A (view ignores installs): manifest tests, build test 65f2ec1 + patch 101, 101
control B (shell granted installs): manifest tests, build test 65f2ec1 + patch 0, 101
T14 boot of proctreecase, run by the orchestrator e78085e boot verdict passed
the harness's judge over that readback, four rows e78085e 1: 3 passed, 1 failed
cargo test -p toyos-manifest head 0
cargo test --lib head 101: 384 passed, 1 failed

T14. app_view, launch_toctou and port_badge pass. The log holds both shell-arm lines:

  a shell's redirect into /home: written
  a shell's redirect into a package: refused (shell: /apps/appview/planted: the directory is read-only to this process)

process_tree fails its judge: "the loader refused the invalid executable 0 times, not once per B". The job itself ended exit=0. The judge counts a kernel line that no spawn has printed since the spawner began laying programs out itself (d40e9b3, on main): an image that is no ELF is refused in the caller before any syscall. This branch changes no kernel, spawn or process_tree code. Recorded as issues/process-trees-metal-judge-waits-for-a-kernel-line-no-spawn-prints.md; not fixed here.

Host. The one red in the full run and in cargo test --lib is llvm::tests::the_caller_s_environment_reaches_neither_the_build_nor_the_key, which fails on every macOS host and is recorded in issues/the-llvm-environment-test-breaks-git-on-macos.md. Since the full run the branch changed one manifest test, comments, the app_view guest job, records and one manual sentence; the full run and clippy were not repeated. Since the T14 boot it changed records and one test comment only.

No guest row was dispatched. No QEMU-registered row writes /apps or runs pkg except the on-demand hosted_clang_hello.

🤖 Generated with Claude Code

Japabu and others added 2 commits November 11, 2026 01:21
Every declared row saw /apps read-write, so a shell and everything it
spawned could rewrite an installed package. A row's view now holds /apps
read-only unless the row declares `installs = true`, which in the image is
pkg's alone (owner ruling, 2026-11-05: only the installer service writes
/apps).

pkg is that service, not a client of one. A launch already gives a row its
own view, and who may launch pkg is the `starts` that list it, so the whole
authority is in images/system.toml and nothing new runs: no port, protocol
or daemon. `launch pkg install <file>` installs; a pkg spawned as a helper
holds its parent's view, lists, and refuses install and remove by name.

app_view's job writes its packages under /apps itself, so test-runner's row
is that image's installer, and the job gains the exit's first arm: a
shell's redirect into /tmp writes and into a package is refused.
hosted_clang_hello launches pkg.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@Japabu

Japabu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Review r1 of #1203 at 83f1abb

Read: both commits, the full diff against origin/main (merge base d49f483; it merges cleanly onto 9349705), each changed file whole, the supervisor's minting and launch paths, the file server's dispatch and resolver, pkg's planner, the manual's files, overview, services, sessions, commands and installation pages, and the evidence at edf121fc. Nothing was built or run. The brief was not available to me and the PR has no discussion; "the brief left this open" is the PR body's word, consistent with the record as it stood before the branch.

Architecture

The change is the right size. One boolean on the row, one arm in Program::view, and the two mechanisms that already exist do the rest: the supervisor mints a row's grants from its view, and the file server refuses every changing request on a read-only grant before the volume sees it. Making pkg the row that holds /apps adds no port, protocol or process, and matches how update already holds the idle slot. About 20 lines of production code, 80 of tests, the rest records and comments. No ABI, dependency, workflow or kernel change.

Authority: who can still write /apps

Through everything this branch touches, only a row declaring installs and the supervisor.

  • Declared rows. Program::view sets write on /apps only from installs; the supervisor's mint turns that into the grant's access; the file server's accept reads writes from the badge; rights::changes is closed by default.
  • Launches. Minted from the target row's view. Extras are connectors the caller already held.
  • Direct children. A duplicate of the parent's namespace, so never wider.
  • Exported connections. They inherit the exporter's writes (system/fileserver/src/main.rs:1345), and a description opened to write exists only on a connection that could write.
  • Installed packages. Own directory read-only. A granted folder is canonical, inside the home and outside Apps (grants::folder).
  • Handles held earlier. Grants are minted per start; nothing survives one.
  • Path aliases. This is where /apps differs from /log: it shares DATA with /home, /config and /state. Every DATA grant's root is apps, config, home, state/<row>, apps/<package> read-only, home/toy/Apps/<package> or a granted home/toy/.... None but the installer's is at or above apps with write. The resolver refuses a start or a .. outside the root, a relative link is expanded inside it, an absolute one goes back to the client's own table, DATA's lookup does not follow a link in the grant's root itself, RENAME resolves both ends on one connection and LINK is unsupported. No path under /home reaches apps.
  • pkg as a deputy. It now holds more than its caller, so its input checks carry weight they did not before. They hold: one top-level name, no manifest.toml, no link leaving the package, nothing beneath a link or a file, no path twice, no existing directory replaced, and remove takes one validated name. All host-tested already.

One exception, not this branch's and not blocking it: see finding 4.

Who may launch pkg

Bounded by starts, as claimed. The caller is the badge the supervisor minted on its launcher, may_start checks that row's list, and the launcher is never in the namespace a helper inherits. images/system.toml lists pkg for shell and toybox only; a package's row has no starts. Transitively it is whoever can start a shell: the terminal, sshserver after authentication, and the compositor through the terminal.

A launched pkg given an attacker's archive opens nothing that main did not. Compared with main:

  • Gone: any write under /apps by a helper (an unpackaged binary or a package's code run from the shell included), by every other declared row, and through a file the picker hands over; and every in-place edit of a package.
  • Remaining: a process holding a launcher that lists pkg can still replace a package whole, with nobody asked: launch pkg remove <name>, then launch pkg install <archive> --yes with its own SHA256SUMS beside it. pkg's question is answered by its caller, by --yes or by the stdin the caller supplies.

So the boundary this adds is between launcher holders and everything else, not between the person and the shell. That is what the ruling asked for; the comments that say a shell "cannot rewrite an installed package" are true of its own writes only (suggestion 3).

No name gate on installs

Acceptable. Every config is committed and ALL_CONFIGS is held to a walk of the tree, so only_the_installer_s_view_writes_apps covers what a render-time gate would, and also reds a shipped image with no installer. A gate in the build would need an exception for a test image in production code. Writing /apps is also a smaller authority than the HELD_ALONE class: a package's folder grant is keyed on its binary's digest, so rewriting a package does not inherit its grant. proctreecase naming test-runner is the cheapest way to keep a job that writes and rewrites packages directly.

The manual

Not contradicted: the services page counts the overview's components as services, each "an ordinary process holding only what its row grants", and the record before this branch already named pkg becoming that service as one of the two outcomes. But the same sentence in docs/src/architecture/files.md:24-26 says "the updater's view" and "the login authority's view" for holders of the same shape, so "installer service" suggests a daemon that does not exist. It should read:

/apps is writable only in the installer's view, and every other program sees it read-only.

That is the owner's sentence, so it goes to him through the orchestrator; if he agrees it belongs in this PR. Nothing else in the manual needs to change.

Findings

  1. BLOCKER — issues/every-program-sees-only-the-files-it-was-given.md:77-83 — the record misstates its control and drops the exit's.

    • It says both host tests are "red with the shell's row granted installs". The PR's own evidence says otherwise: under that patch toyos_manifest's tests exit 0, only_the_installer_writes_apps included (controls/control-b-shell-installs-manifest.log; INDEX.md: "they read no config"). Only the build's test is red there. The manifest test is red under the other control, the view ignoring installs.
    • The exit read "granting the shell's row /apps read-write reds the first", the first being the shell's write refused by the file server. That clause is gone, while the check it controls is still owed on a machine. The PR body calls the system.toml patch "the exit's own control", but it reds a different check.
    • Fix: say which control reds which test, and keep the exit's clause until it is met. It is met by one proctreecase boot with the shell's row given installs, expecting the shell arm red. If the orchestrator would rather not spend that boot, the replacement is his to state in the record, not a silent deletion; suggestion 1 is the cheap half of one.
  2. SUGGESTION — tests/guest/rust-tests/src/bin/app_view.rs:304-310 — the refusal arm asserts no file and a non-zero status, not why. A shell that failed for another reason passes it. Assert that the shell's stderr names the read-only refusal, or make the positive arm a redirect into /home, which is the same volume and server as /apps and differs only in the grant.

  3. SUGGESTION — lib/manifest/src/lib.rs:1141-1143 — "whatever the image's rows say" is not tested: app_row never sets installs, and the whole-view test above already pins the package's directory. Set installs: true on the synthesized row before asserting, or delete the three lines.

    • Same file, Program::view's doc ("a shell and what it spawns cannot rewrite an installed package"), and images/system.toml:57 ("changes no package"): true of direct writes; the shell's row lists pkg. "A shell's own writes and its helpers' are refused" says it without overclaiming.
    • build/build.rs:3348 — "a row a shell spawns": a shell spawns helpers, not rows.
  4. For the orchestrator to record, not this branch's — by inspection, three storage processes are started holding the supervisor's whole-tree namespace, /apps and /log read-write among it. Grants::view returns nothing for a storage row, build_namespace then returns None when the row has no other name (system/supervisor/src/main.rs:3406), and Helper::spawn endows a duplicate of the spawner's own namespace whenever none was named (sdk/toyos/src/spawn.rs:225). That is diskserver and the file servers for memory and system. All three are the signed image's storage stack and open no path, so the shell-to-package boundary is unaffected, but "a storage row sees none" is not what happens, and Memory overwrite on every boot; the black box goes, and a clean stop proves an image #1196's review said no row gets it. Unverified on a machine; one names.open("fs:/apps") from such a process would settle it. It belongs in a defect of its own, and stage 2's exit ("nothing still sees the global tree") depends on it.

  5. SUGGESTION, records — issues/a-package-is-a-directory-under-apps-and-the-installer-is-a-program.md could say in one clause that a caller able to launch pkg answers its question itself. With /apps closed, that is now the whole of who decides an install.

    • issues/every-program-sees-only-the-files-it-was-given.md:66 still says "the rest read-write" ahead of two exceptions.

Evidence

  • Host: both new tests pass in the full run at 65f2ec1 (ci-host.log lines 75 and 3385). The one red step is llvm::tests::the_caller_s_environment_reaches_neither_the_build_nor_the_key, failing in the host's git on SDKROOT=/no/such.sdk, as its record describes; independent of this branch. The log's other FAILED lines are the CI's own controls.
  • 83f1abb changes one test comment, so the results carry.
  • Control A reds three tests as stated; control B reds the build's test with ["pkg", "shell"]. Both patches are in the evidence.
  • No guest row: confirmed that only app_view and hosted_clang_hello touch /apps or pkg, and every other row's grant uses the read-only path /log already exercises.
  • Gaps the PR states and I agree are gaps: pkg's refusal message is one untested branch; hosted_clang_hello was changed and not run; and no machine has seen a launched pkg install. After this lands the only install path is one no machine has exercised, until pkg_install_gbae exists.

For the PR description (orchestrator's): "the exit's own control" describes a different check, and "mints each row's grants from Program::view" excepts storage rows.

Before landing

  • Finding 1 fixed.
  • The staged proctreecase boot on the T14 green for all four jobs it carries (process_tree, launch_toctou, port_badge, app_view), with both shell-arm lines in the log and the refusal's printed reason the read-only one.
  • The exit's control boot, or the orchestrator's recorded replacement for it.
  • The PR's own host checks green; the llvm red is macOS-only.
  • The manual sentence settled with the owner, either way.

SEND BACK

… say which control reds what

app_view's positive arm redirects into /home, the same volume and server
as /apps, and its refusal must carry the read-only reason. The record
names which control reds which test and states the exit's check. The
manual says the installer's view. Storage rows starting with the
supervisor's namespace are recorded as their own defect.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@Japabu

Japabu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Review r2 of #1203 at e78085e

Scope: the one commit since r1 (83f1abb..e78085e), against r1's findings and the orchestrator's four decisions. Read: that diff whole, app_view.rs whole, the shell's redirect path, std's error mapping in sdk/std/sys/, toyos::files' router, the file server's dispatch and rights, the supervisor's start, namespace and storage paths, Helper::spawn, both records, the PR body and the evidence at f2cd0c43. Nothing was built or run. What r1 settled is not reopened.

Architecture

Unchanged from r1. This round adds no production code: one doc line in Program::view, comments in build/build.rs and images/system.toml, one manual sentence. Tests: app_view's shell arm reworked (+16/-12) and one line of the manifest test. Records: two amended, one new.

r1's findings

  1. Blocker, resolved. issues/every-program-sees-only-the-files-it-was-given.md:81-96.
    • The record now says only_the_installer_writes_apps is red with the view ignoring installs, and the build's test is red with that and with the shell's row granted installs. The evidence agrees: control A reds two manifest tests and the build's test over all 42 rows; control B leaves the manifest crate at 56 passed and reds the build's test with ["pkg", "shell"] against ["pkg"].
    • The exit's clause is back, and its check is stated as the orchestrator decided: app_view's shell arm in place of the control boot, attributed to the orchestrator and dated, with the reason (one volume, one server, only the grant differs). The row is listed as still owed on a machine, which is true.
  2. Taken, both halves. See the next section.
  3. Taken. The manifest test forces installs: true onto the synthesized package row before asserting. The three overclaiming comments now speak of the shell's own writes and its helpers'.
  4. Filed as issues/storage-rows-start-holding-the-supervisors-whole-tree.md. See below.
  5. Taken. The package record says who answers pkg's question; "the rest read-write" is reworded to match the two exceptions.

Does the shell arm fail for the wrong reason? (by reading)

Yes. The refusal arm passes only on a read-only grant.

  • The text is real and is what the shell prints. apply_redirect (system/shell/src/main.rs:920-962) opens with create, write and truncate, and on failure prints shell: {path}: {e} to stderr, sets status 1 and does not run the command; -c exits with that status.
  • std's open goes files::open -> files_error -> served_error, where Error::ReadOnly is the constant message the directory is read-only to this process (sdk/std/sys/pal/mod.rs:69). Display of a constant-message error prints the message alone (the pinned fork's library/core/src/io/error.rs:529). No other arm of served_error shares the text.
  • ReadOnly can reach the shell three ways:
    • the grant: serve_one refuses before the volume is asked (system/fileserver/src/main.rs:833). This is the claim.
    • the volume or its device being read-only (system/fileserver/src/data.rs:138). The /home redirect, same volume and process, would then fail first.
    • the client's own answer for a synthetic ancestor directory (sdk/toyos/src/files/owner.rs:447). It applies only to a path that is itself such a directory, not to a file beneath a held /apps.
  • The client end does not short-circuit on writable, so the refusal is the server's, as the record says.
  • Wrong outcomes go red: a shell holding /apps read-write leaves a file; a shell holding no /apps is told this process holds no such directory; a shell that did not launch or a failed cat reds the /home arm.
  • The positive arm is sound. /apps and /home are both DATA's (toyos_manifest::ROLES), the shell's row in proctreecase.toml holds /home read-write, /home/toy exists by then, and the file is removed before and after.
  • stderr travels the same descriptor path as the stdout granted() already reads from a launched shell.
  • The metal judge needs no change: either arm failing panics the job, and job_passed reds it.

The constant is a copy of std's wording, so a reworded message reds this row on the bench. That is the right direction to fail and the comment says where the text comes from.

The new defect record

Accurate by inspection, and labelled as unverified.

  • Grants::view returns nothing for a storage row; start adds only the block or disk port, authorizations and netstack.
  • build_namespace returns None when all of that is empty (system/supervisor/src/main.rs:3406), and block and disk are filtered from receives.
  • Helper::spawn endows a duplicate of the spawner's namespace when the spawn names none (sdk/toyos/src/spawn.rs:225), and the supervisor's is whole_tree(), adopted before SYSTEM starts.
  • So: diskserver, MEMORY (no block grant) and SYSTEM (ROOT's image) inherit it. DATA, LOG and BOOT hold block; nvme and usbd hold disk. Three rows, as the record says.
  • No severity is right: the three open no path, so it is a missing layer, not a broken boundary by itself.
  • The exit is tied to stage 2 and the track cites the file.

The orchestrator's decisions

All four are carried.

  • No control boot; the record states the app_view check and why it suffices.
  • The storage finding is its own defect, by inspection, exit at stage 2.
  • docs/src/architecture/files.md:24-26 has the sentence as decided.
  • No name gate was added.

PR description

Corrected. It no longer calls the system.toml patch the exit's own control, and it excepts storage rows from "mints from Program::view". The table matches the evidence: the full host run and both controls at 65f2ec1, the manifest and library tests and the staging run at e78085e (r2/metal-readback.rev). The controls carry across the later edits: the only test change since is the last assertion of only_the_installer_writes_apps, after the lines control A reds.

Findings

No blocker.

  1. SUGGESTION — issues/storage-rows-start-holding-the-supervisors-whole-tree.md:11-12 — "read-write but /system, /apps and /log among them" reads at first as a list of three exceptions, the opposite of what is meant. "read-write but /system; /apps and /log are among them" fixes it.
    • Same file, line 27-28: if the claim is wrong the process holds no namespace at all, so there is no names to answer NotFound. "holds no namespace, or one without fs:/apps" is the other outcome.
  2. SUGGESTION — lib/manifest/src/lib.rs:1141 — "A package's row has no word for it" sits above a line that gives the row one. "Even declaring it, a package's own directory stays read-only" says what the assertion checks.

Neither needs another round.

Before landing (the orchestrator's)

  • The staged proctreecase boot on the T14 green for its four jobs, with a shell's redirect into /home: written and a shell's redirect into a package: refused (shell: /apps/appview/planted: the directory is read-only to this process) in the log. The record's "Owed on a machine: that row" is then updated.
  • The PR's host checks: every check on the draft currently reads skipping, so none has run on this head. The author's own runs are the macOS ones above, red only on the recorded llvm test.

LAND, on those two preconditions.

… stale judge is recorded

app_view's shell arm ran on the T14 at e78085e, so the isolation record
no longer owes it. The same boot's process_tree job passed and its judge
did not: it counts a kernel line no spawn prints since the spawner lays
programs out itself, which is main's and gets its own record. Two wording
suggestions taken.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@Japabu

Japabu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator verification at 3298f19, after review r2's LAND at e78085e.

  • e78085e47..3298f192d changes three issue records and one test comment in lib/manifest/src/lib.rs (r2's wording suggestion). No code.
  • The T14 proctreecase boot of e78085e ran; the harness judges 3 passed, 1 failed. app_view, launch_toctou and port_badge pass, and the log holds both shell-arm lines r2 required (/home: written; /apps/appview/planted: refused, "the directory is read-only to this process").
  • process_tree's job ended exit=0; its host-side judge is red because it counts a kernel line, spawn: /tmp/process_tree_invalid_elf: …, that nothing prints since The loader is the program's, and the kernel parses no ELF #1189 moved image layout to the spawner: the 64 zero bytes are refused in the caller before any syscall. I checked main: the kernel's remaining spawn: {path}: … refusals are for the entry, the address space, the ranges and the stack, none for an unparseable file. This branch touches no kernel, spawn or process_tree code, so the red is main's and independent; it is recorded in issues/process-trees-metal-judge-waits-for-a-kernel-line-no-spawn-prints.md and will be repaired with A child's arguments and environment are memory its parent lays out #1200, which changes that test.

Queueing.

@Japabu
Japabu marked this pull request as ready for review November 11, 2026 01:59
@Japabu
Japabu added this pull request to the merge queue Nov 11, 2026
Merged via the queue into main with commit 7b912fc Nov 11, 2026
10 checks passed
@Japabu
Japabu deleted the apps-read-only-but-the-installer branch November 11, 2026 02:16
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