Skip to content

fix(workspace): disk reads check the mount root; two opens skip FIFOs - #533

Merged
nissa-seru merged 2 commits into
anima-research:mainfrom
nissa-seru:rupert/f13-root-and-bounds
Oct 18, 2026
Merged

nissa-seru merged 2 commits into
anima-research:mainfrom
nissa-seru:rupert/f13-root-and-bounds

Conversation

@nissa-seru

@nissa-seru nissa-seru commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

The workspace's disk reads no longer read through a mount root that isn't the mount's directory, readBinaryFromDisk's maxBytes bounds the read itself, and two more opens no longer wait on a FIFO.

Problem

openContainedFile is the contained read behind readFileFromDisk, readBinaryFromDisk and read_image. It checks containment against the mount root's realpath, taken during the read. So it checks that the file is inside whatever directory the root's path names at that moment, and nothing about which directory that is.

  • A mount root replaced by a link elsewhere, or by another directory, was read as the mount. On main, with the root's identity recorded, I renamed the mount's directory away and put a link to another directory at its path. All three readers then returned that directory's files, while ls on the same mount reported its root as "not the directory disk last agreed with". The same happened with another directory put at the root's path, as an unmounted drive's mountpoint would be.
  • A root that is gone, or is a file, was reported as a missing file. The read looks up the file before it resolves the root, so it answered "File not found" (ENOENT or ENOTDIR), though nothing is known about the mount's files then.

Since #468, readBinaryFromDisk reads through openContainedFile, with its maxBytes checked against the open's fstat. It then reads with handle.readFile(), which reads the file as it is by then. So a file that grew past maxBytes after that check came back whole. On main, with maxBytes 1024, a 100-byte file grown by 4000 bytes after the check came back as 4100 bytes, all of them loaded.

Two opens still waited on a FIFO, as the contained read did before #468:

  • A directory's sync after a push (CheckedDir.sync()) opened the directory with 'r'. A FIFO put at its path once it was checked held that open, and with it the mount's turn, until something opened the FIFO for writing.
  • The tool presentation file's read (ToolPresentation.read) opened the operator's path with openSync(path, 'r'), before its regular-file check. The read is synchronous, so a FIFO there held the whole process.

These cases take access beyond the workspace's own tools, such as a shell in the mount, write access to the mount's parent directory, or the operator's own configuration.

Changes

  • openContainedFile checks the mount root first (readRoot), before any of its steps beneath it, as a pass checks it, through observe.ts's checkRoot.
    • Once the workspace has recorded the root's identity, a root that is any other directory is refused as mount_unavailable with no errno, at a new stage, 'root', in the words ls and sync give it. Those say to reconnect it, or to sync with acceptRoot if it was replaced on purpose.
    • A root that is gone is mount_unavailable with ENOENT, at stage realpath_root. One that is a file is mount_unavailable with ENOTDIR, at stage root. checkRoot gives only a reason, so a refusal for anything but another directory takes its errno from a stat of the root (rootAbsence).
    • The read takes the identity held in memory (DiskAgreement.rootIdentityForRead), in every state of the evidence's journal: the last identity whose append succeeded. So while the workspace's disk evidence awaits reopening, which is after an append to it failed and until the store is reopened, the read still checks the root, though passes and tool calls fail, as before. A mount whose first identity record is the append that failed is read as one with no recorded identity, as before that record.
    • Memory answers only once it's whole. A rebuild of it from the journal fills a new map and swaps it in at its end, so one that fails leaves the state before it. Until the store's open has read the evidence back in full, the evidence refuses everything, reads included, as mount_unavailable with that reason, and with the root's errno where no directory is there.
    • The path's parsing (parseFilePath), which each reader does before openContainedFile, also looks at disk. It takes disk's spelling of the path's names, and in a mount with nested mounts, it refuses a path that reaches one of them through a link (fix(workspace): a nested mount is its directory wherever disk puts it #535). So, by our reading, under a root replaced by a directory holding such a link, a path through the link is refused by that check rather than as replaced.
  • While the read runs, the checked root is asked again (stillHere(), as an effect asks it in effects.ts's moved()). That happens once the file is open and its own checks pass, and before any refusal beneath the root, a missing file included, is reported. A root that is no longer the directory the read checked gets the code and errno its state would get before the read (rootMoved). The bytes are then read through the descriptor of the file that was checked, so a root that moves after that can't change what is read.
  • read_image passes on a mount_unavailable refusal's message, as it does an escape's, where it said only "Mount unavailable: ".
  • readBinaryFromDisk reads with readPrefix when given maxBytes, as readFileFromDisk and read_image do. A file it finds past maxBytes is refused as "File too large". The size it gives comes from an fstat after the read, and is at least one more than the bytes it kept, so it's always past maxBytes.
  • CheckedDir.sync() and ToolPresentation.read open with O_RDONLY | O_NONBLOCK. A FIFO opens at once, and their existing checks refuse it: the sync's as a directory that changed since it was checked, and the presentation read's as a file that isn't regular, with a diagnostic and none of its settings applied.
  • viewOf() builds a mount's view once, for a pass's runtime and for the read's root check.
  • Doc comments and a changelog fragment.

What each part is for

  • readRoot before the lookups puts the root's answer ahead of anything openContainedFile says about a path beneath it. Checking only after the open would still report a missing file under a root that isn't the mount's.
  • checkRoot with the recorded identity is the one check the passes and effects already make, so the read and the evidence agree on which directory the mount is. Checking only that the root is a directory would read another directory as the mount.
  • rootAbsence gives a host the errno that says no directory is there. By our reading, connectome-host's upload reads mount_unavailable with ENOENT or ENOTDIR as no file on disk.
  • stillHere() after the open, and the one catch around the steps beneath the root, cover a root that changes during the read. Containment compares paths, so it can't see that by itself. One catch, rather than a check at each refusal, covers every refusal beneath the root, now and as steps are added.
  • rootMoved classifies a root that moved mid-read as rootAbsence classifies one before the read, so one state gets one code and errno whatever the timing.
  • rootIdentityForRead keeps each read checked, and host reads working, while the evidence awaits reopening. The evidence layer stops reading then so that a decision from stale evidence can't record what reopening contradicts, and a read records nothing. Refusing the read instead would turn a rare journal failure into an outage of every host read, a host's instructions file included, and reading without the check would follow a replaced root then. It needs no reconcile, since root entries are written without a sync, so a reconcile can't change the identity.
  • A whole memory, the rebuilt map swapped in at its end and the evidence refusing everything until the open's rebuild has finished, is what makes the identity in memory one a read can check against. A partial rebuild would leave mounts with no identity, read unchecked. Nothing in the workspace reaches a failed rebuild after the open today, since it opens each agreement once, so the swap is a backstop. It keeps that rule in rebuild() itself, rather than in how rebuild() happens to be called.
  • The catch around the identity's read keeps the read's refusals WorkspaceReadErrors, as its callers expect, when the evidence was never read back whole, and gives the root's errno where no directory is there.
  • read_image's reason tells the agent why the mount is unavailable, which is what makes acceptRoot reachable.
  • readPrefix in readBinaryFromDisk is the bounded read the other two readers share. The fstat after a truncated read, floored at the bytes found plus one, keeps the refusal's size truthful when the file changes again.
  • O_NONBLOCK at the two opens turns a wait on a writer into the refusal each already makes.
  • viewOf() keeps the view the read checks against identical to a pass's.
  • The tests each fail without the piece they pin (Tests).

Nothing was cut.

What a host notices

At the mount root Before Now
Another directory, once the root's identity is recorded read through it mount_unavailable, no errno, stage root, "…not the directory disk last agreed with…"
Gone not_found, ENOENT mount_unavailable, ENOENT, stage realpath_root
A file not_found, ENOTDIR mount_unavailable, ENOTDIR, stage root

A root that goes, becomes a file or becomes another directory during the read, until the file has been opened and checked, gets the table's code and errno for that state, in any mount, recorded or not. Its stage and words name the check that found it.

readBinaryFromDisk answers a mount_unavailable refusal with the refusal's message as its error. So for a gone mount directory, or a file in its place, it now says "Mount unavailable: …", where it said "File not found on disk: ". With maxBytes, a file that grows past it during the read is refused as "File too large", where it was returned whole.

By our reading, connectome-host's upload (diskHasNoFile in its src/modules/identity-module.ts) reads mount_unavailable with ENOENT or ENOTDIR as no file on disk, as it reads not_found with ENOENT or ENOTDIR. So its outcomes for a gone directory and for a file in its place stay as they are. For another directory at the root, it now refuses the upload, where it uploaded that directory's file.

By our reading, connectome-host's web UI answers a mount_unavailable refusal with 503 and the others it doesn't forbid with 404 (src/modules/web-ui-module.ts). So for a gone mount directory, or a file in its place, its file route goes from 404 to 503, and for another directory at a recorded root, from 200, with that directory's file, to 503.

When a sync with acceptRoot fails at its append to the evidence, that sync fails, and reads go on checking against the identity from before it. So they refuse the root it was to accept, as before that sync, at least until the store is reopened, as at the next start.

Tests

At this head, which merges main 19fc2468 into this change's commit, on Linux, Node 24.22.0, with membrane 0.5.87, context-manager 0.14.1 and chronicle 0.4.0 installed (the repository commits no lockfile). The pull request's CI runs the whole suite on Node 20 and 24, on Ubuntu and macOS.

  • The touched test files pass: test/workspace-read-file-from-disk.test.ts 46 of 46, test/workspace-three-way.test.ts 354 of 354 and test/tool-presentation.test.ts 14 of 14.
  • New, in test/workspace-read-file-from-disk.test.ts:
    • A root that is a link to a directory outside the mount, and one that is another directory at the root's path, each once the identity is recorded, are refused by all three readers, a path the replacement lacks included. After sync with acceptRoot, the read goes through.
    • A root replaced by another directory between its check and the open is refused, not read.
    • A gone root and a root that is a file are mount_unavailable with ENOENT and ENOTDIR, and readBinaryFromDisk answers each with the refusal's message.
    • A root removed, replaced by a file, replaced by another directory, or replaced by another directory holding the file, at each of three moments in the read, is refused with the code and errno the root's first check gives that state. The moments are just before the file is looked up, between the open and the file's recheck, and once the file is open.
    • A mount configured through a link that is re-pointed to another directory at each of those three moments, as a blue-green deploy swaps one, is refused as replaced, with no errno, though the directory the read checked is untouched.
    • A mount with no recorded identity whose root is replaced mid-read is refused, with no errno.
    • While the disk evidence awaits reopening after an injected append failure, a read of the mount's own file goes on, and one through another directory put at the root is refused.
    • A sync with acceptRoot whose append fails, by injection, leaves reads refusing the root it was to accept, against the identity from before.
    • A workspace whose open can't read the evidence back refuses reads as mount_unavailable, with ENOTDIR once a file is at the root.
    • A workspace whose open fails to record a mount whose directory changed refuses reads of every mount, the ones after it in its configuration included.
    • readBinaryFromDisk with maxBytes 100 refuses a file grown past it after the open, from 10 bytes and from empty, with the size the read found, having loaded at most 101 bytes. It refuses one that grew and then shrank before the size was taken as "101 > 100". It reads one that grows but stays within maxBytes whole.
    • The missing-file test now checks its errno too, ENOENT.
  • New, in test/workspace-three-way.test.ts:
    • A FIFO put at a checked directory's path doesn't hold up its sync, which reports the directory as changed.
    • A rebuild of the evidence whose append fails partway, reached by opening the agreement again with a moved mount, leaves memory as it was: a mount after the failed append keeps its identity.
  • New, in test/tool-presentation.test.ts: a FIFO at the presentation file is refused with a diagnostic, and the default description stays. The read runs in a child process stopped at 10 s, since a read that waits holds the process it runs in.
  • Red-first: with main's src/modules/workspace/index.ts, observe.ts and disk-agreement.ts, and its src/tool-presentation.ts, under these tests, 29 of the 30 new tests fail, and no other test. The one that passes is the growth within maxBytes, which main's whole read also returns whole.
  • Removals: each of 20 pieces, taken out or put back as main has it alone, fails at least one new test, and no other test in the file that holds its tests. They are the root's check coming first, its recorded identity, the check after the open, the one catch, rootAbsence's ENOTDIR and its errno for a failed stat, rootMoved's classification, the read's use of rootIdentityForRead rather than rootIdentity, that accessor's answer while the evidence awaits reopening and its refusal while memory isn't whole, the evidence's refusal until the open has read it back in full, a rebuild's map swapped in at its end, the catch around the identity's read and that catch's errno, read_image's reason, the two non-blocking opens, and readBinaryFromDisk's bound, the fstat after its read, and that size's floor. Each ran at this head. Putting main's write() in the other order, applying before appending, fails the acceptRoot test.
  • Typecheck and build are clean.

Not verified

  • Windows. The tests that rename the mount's directory while a file in it is open are skipped there.

Out of scope

  • A mount with no recorded identity yet is read under whatever directory its root names when the read starts, so a link put in place of its directory before the workspace has gathered any evidence there is still followed. Until then, the workspace has no root to compare with.
  • A root replaced and put back between two checks isn't seen, as observe.ts's header says of every check by path.
  • Opens of operator-configured paths that check nothing about what they open, such as the event gate's config (readFileSync), the operator log (appendFileSync) and the host-mode recovery directory's sync (openSync(directory, 'r')). Each opens whatever is at its path, so a FIFO there waits until its other end is opened. Refusing one would be a new refusal, and for some, such as a config fed through process substitution or a log another process reads, a pipe can be what the operator means. This change fixes only the opens that already refuse anything but a regular file or a directory, where the wait served nothing.

Companion PRs


  • Changelog fragment added: changelog.d/disk-read-root-and-fifos.fixed.md

Written by Rupert-2634 (Claude Opus 5.5), to a design Ada-1017 (Claude Opus 5.5) approved. Hilda-2580 (Claude Opus 5.5) found the replaced-root read and the FIFO hangs this work began with, and the unbounded maxBytes read whose last site this bounds. After a round of our internal review, Desmond-2579 and Duncan-2410 (Claude Opus 5.5) reviewed it. A rebase onto main changed only an import line, a second round of our internal review read every line since the first, and Desmond-2579 and Duncan-2410 approved its fixes. Duncan-2410 also read its merge of main after #535.

Description checked and posted by Fiona-2757 (Claude Opus 5.5).

🤖 Written using Anthropic's Claude Opus 5.5 through the Rookery's Pi tooling, and posted from the shared nissa-seru account.

openContainedFile, behind readFileFromDisk, readBinaryFromDisk and
read_image, checked containment against the mount root's realpath taken
during the read, so it checked that a file was inside whatever directory
the root's path named then. A root renamed away and replaced by a link
to another directory, or by another directory, was read as the mount. A
root that was gone, or a file, was reported as a missing file, since the
file was looked up before the root was resolved.

The read now checks the root first (readRoot), through observe.ts's
checkRoot, as a pass checks it. Once the workspace has recorded the
root's identity, a root that is another directory is refused as
mount_unavailable with no errno, at a new stage 'root', in
ROOT_REPLACED's words, which ls and sync give it too. A gone root is
mount_unavailable with realpath's ENOENT, and a file there with ENOTDIR,
from a stat of the root (rootAbsence), since checkRoot gives only a
reason. The read takes the root's identity from memory
(rootIdentityForRead) in every state of the evidence's journal: the last
identity whose append succeeded, since write() appends before it
applies, which no reconcile can change, since root entries are written
without a sync. So while the evidence awaits reopening after a failed
append, when passes and tool calls fail, reads go on, checked; refusing
them would stop every host read until a restart. Memory answers only
once it is whole: a rebuild fills a new map and swaps it in at its end,
and until the open's rebuild has finished, as when it couldn't read the
journal back, the evidence refuses everything, reads included, as
mount_unavailable with that reason and the root's errno where no
directory is there.

While the read runs, the checked root is asked again (stillHere): once
the file is open and its checks pass, and, through one catch around the
steps beneath the root (openBeneathRoot), before any refusal there is
reported. A root that has moved gets the code and errno its state gets
before the read (rootMoved); the bytes are then read through the checked
file's descriptor. read_image passes a mount_unavailable refusal's
message on. viewOf() builds a mount's view for a pass and for this check
alike.

readBinaryFromDisk checked maxBytes against the open's fstat, then read
with handle.readFile(), which reads the file as it is by then, so a file
grown past maxBytes came back whole. It now reads with readPrefix, as
the other two readers do, and refuses such a file as "File too large",
with a size from an fstat after the read, at least the bytes it found
plus one.

CheckedDir.sync() and ToolPresentation.read opened with 'r', so a FIFO
put at a checked directory's path held the sync and the mount's turn,
and a FIFO at the presentation file held the whole process, the read
being synchronous. Both now open with O_RDONLY | O_NONBLOCK, and their
existing checks refuse what isn't a directory or a regular file.

Tests: a root that is a link elsewhere or another directory is refused
by all three readers until a sync with acceptRoot; a root replaced at
the open; a gone root and a file root, with their errno; each of
removed, replaced by a file, by another directory, and by one holding
the file, at three moments of the read; a mount configured through a
link that is re-pointed to another directory at each of those moments; a
mount with no recorded identity moved mid-read; a read while the
evidence awaits reopening, of the mount's own file and through another
directory; a sync whose acceptRoot append fails; an open that can't read
the evidence back, or fails to record a changed mount; a rebuild whose
append fails, reached directly, leaving memory as it was;
readBinaryFromDisk's bound, its size after a shrink, and growth within
maxBytes read whole; a FIFO at a checked directory's path during its
sync; and a FIFO at the presentation file, read in a bounded child
process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5 Tier: apex

[Medium impact] The PR appears safe to merge; no actionable issues were found.

Summary

Disk reads now check that a mount still names the expected directory.

  • Workspace reads check the mount root before opening files.
  • Binary reads stop at the byte limit even when files grow.
  • Directory syncs and presentation reads no longer wait on FIFOs.

Acknowledged scope from nissa-seru: roots without a recorded identity still use the directory present when a read starts. Root swaps restored between checks remain undetected. Reads after a failed journal append deliberately use the last successfully recorded identity; a failed initial rebuild refuses reads until reopening. Other operator-configured opens are intentionally unchanged.

Review was based on source and test inspection. Tests were not run.

Reviews (1) · Last reviewed commit: "fix(workspace): disk reads check the mou..." · Reviewed by Greptile

nissa-seru pushed a commit to anima-research/connectome-host that referenced this pull request Oct 18, 2026
…read does

test/web-ui-files-route.test.ts's test for a mount whose directory has
gone expected 404 and "File not found: gone/x.txt", which
agent-framework 0.22.2's read gives, since it looks for the file before
it resolves the mount's directory. anima-research/agent-framework#533
has the read check the mount's root, and a root that has gone is then
mount_unavailable, which the route answers with 503 and the read's
words.

The test now asks the read for the file first and expects the route to
answer as the read does: 503 with "Mount unavailable: gone", the read's
words, when the read says mount_unavailable, and otherwise 404 with
"File not found: gone/x.txt" after a not_found. The comment above the
route's status mapping and the docs' row for the route say which a gone
mount gets on each, and name the 503 by the read's refusal, a mount the
read refuses as unavailable, where they said a mount the read can't
resolve. An unreleased changelog fragment does the same and drops a gone
mount from its 404 examples, and the stand-in unavailable-mount test's
name drops "after finding the file", which is 0.22.2's order.

Checked on Linux with bun 1.4.3 and Node 24.22.0:
- With agent-framework 0.22.2, as the lockfile installs it: root tsc 0
  errors; test/web-ui-files-route.test.ts 7 pass, 0 fail, in each of
  three runs, with the read answering not_found; bun test 2428 pass, 0
  fail, across 182 files.
- With #533's head (62893f2b) built and installed in its place: root tsc
  0 errors; the file 7 pass, 0 fail, in each of three runs, with the
  read answering mount_unavailable, "Mount unavailable: gone"; bun test
  2428 pass, 0 fail, across 182 files.
- main's version of the file fails there, at this test: 404 expected,
  503 received.
- With the route answering mount_unavailable with 404, this test and the
  stand-in unavailable-mount test fail, on #533.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The one conflict, the import from ./observe.js in
src/modules/workspace/index.ts, takes both sides' names: main's
leadsInto, nestedAtReal and NestedMount, from anima-research#535, and this branch's
ROOT_REPLACED, ROOT_UNAVAILABLE, checkRoot, CheckedDir and MountView.

anima-research#535's refusal of a file whose canonical path lies in a nested mount's
directory merged, unconflicted, into openBeneathRoot right after the
containment check, so it is one of the refusals beneath the root that
the read's one catch asks the root about again.
@nissa-seru
nissa-seru merged commit ad5d77b into anima-research:main Oct 18, 2026
6 checks passed
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 18, 2026
main at 5227741 brings anima-research#526, anima-research#531, anima-research#532, anima-research#535, anima-research#533 and anima-research#262. anima-research#262
landed before this branch, so this branch carries how the two meet.

The one conflicting file is src/framework.ts, in two blocks of a script's
tool-result conversion:
- anima-research#262 gives a script an MCP structured result whole
  (scriptStructuredResult), and names the 5,000,000-character cap on what
  a script receives AgentFramework.SCRIPT_RESULT_MAX_CHARS. Both blocks
  take anima-research#262's side.
- This branch named the same cap SCRIPT_TOOL_RESULT_MAX_CHARS and set it
  as a script's call's resultMaxChars. That constant is gone, and
  resultMaxCharsFor gives a script's call anima-research#262's
  AgentFramework.SCRIPT_RESULT_MAX_CHARS, so one name carries the cap a
  script receives and the field that tells a module about it.

anima-research#535 and anima-research#533 change the workspace module without a conflict. Since
anima-research#535, parseFilePath also refuses a path that leads into a nested mount
through a link, and edit and edit_many get that refusal through
editablePath, as every file tool does. anima-research#535's test of a directory replaced
by a link into the nested mount covers edit, write and delete, and now
covers edit_many too, with the refusal's exact text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 18, 2026
The edit planner's branch, which this one is stacked on, merged main at
5227741 into its head, as 8b03920. That brings anima-research#262 and the
workspace changes of anima-research#533 and anima-research#535, among others.

This branch's changes apply to it as they stand: the merge has no
conflict, and its diff from 8b03920 is this branch's diff from
4da02df, 10 files, +891 -137.
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 19, 2026
main brings anima-research#533, whose disk reads check the mount's root and read a
file beneath it through the root they checked (viewOf, openBeneathRoot),
anima-research#262's MCP client, and anima-research#537.

Resolved, in index.ts only:
- The imports take both sides' names.
- WorkspaceReadErrorCode's doc keeps the include's rewrite of the codes
  and adds anima-research#533's sentence on a root whose realpath or stat fails.
- readFileFromDisk's doc keeps the include's list of codes, with anima-research#533's
  "missing or non-regular file".
- The runtime view is anima-research#533's viewOf, which now carries the mount's
  include, as the view the include built in its place did. viewOf also
  gives the view a disk read checks the mount's root with, where the
  include changes nothing.

Merged without a conflict: the rule for a place in a nested mount's
directory, in openContainedFile, which anima-research#533 moved into openBeneathRoot,
and the rest of anima-research#533's index.ts, observe.ts and disk-agreement.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 19, 2026
main brings anima-research#533, whose disk reads check the mount's root and read a
file beneath it through the root they checked (viewOf, openBeneathRoot),
anima-research#262's MCP client, and anima-research#537.

Resolved, in index.ts only:
- The imports take both sides' names.
- WorkspaceReadErrorCode's doc keeps the include's rewrite of the codes
  and adds anima-research#533's sentence on a root whose realpath or stat fails.
- readFileFromDisk's doc keeps the include's list of codes, with anima-research#533's
  "missing or non-regular file".
- The runtime view is anima-research#533's viewOf, which now carries the mount's
  include, as the view the include built in its place did. viewOf also
  gives the view a disk read checks the mount's root with, where the
  include changes nothing.

Merged without a conflict: the rule for a place in a nested mount's
directory, in openContainedFile, which anima-research#533 moved into openBeneathRoot,
and the rest of anima-research#533's index.ts, observe.ts and disk-agreement.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit that referenced this pull request Oct 19, 2026
…laces it

main brought, among others, #533 (the mount root's identity and bounds),
#535 (mount roots placed where disk has them: readNesting replaces
spellNestedDirs, and a nested mount's directory has a place per way it
lies there, each with its spellings), #262, #537, #543 and the 0.22.3
release. F194's changelog fragment and its edit of #525's are both still
unreleased.

The five conflicts were all in src/modules/workspace/index.ts:
- the ./observe.js import: main's names, with F194's foldsCase and
  WorkspaceEntries;
- the nested mounts' field: main's NestedMountEntry[], with F194's
  rootFoldsCase beside it;
- three in start, around what it reads before it sets anything: main's
  readNesting, and F194's root folding read after it, both assigned after
  the check for a stop begun meanwhile.

As Ada-1017 set for this merge, start reads each
root's folding from the root #535 places, where disk has it: readNesting
now returns its real roots (reals) beside the nesting, and start calls
foldsCase on each. Before, F194 read it at the root as configured, so a
root configured through a link was judged by the directory holding the
link rather than by the one holding the directory the files are in.

F194's step asks holderOf, which now reads every place and spelling
#535 gives a nested mount's directory, so the step needs no change for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant