These fixtures assert LF diff bodies but wrote files through text mode, so on Windows they produced CRLF and relied on the runner Git system config (autocrlf=true) to normalize blobs. The isolated test environment scrubs that system config, exposing the dependency: the fixture owns its own EOL now.
Windows folds environment keys to upper case, so the regression token is spelled upper; the audit spawn allowlist admits the inherited OURO_PROC_CONTAINER_* keys and nothing else; the encoding regression covers PYTHONIOENCODING on the commit-gate env too.
Forcing PYTHONUTF8/PYTHONIOENCODING onto every pytest child and the hermetic commit gate would hide exactly the cp1252 bugs Windows CI exists to catch. Isolation is about roots, never the interpreter encoding; pin it with a regression.
Container membership is env-token based; the audit spawner built an allowlisted child env that dropped OURO_PROC_CONTAINER_*, so a forced server teardown could reap the container while the audit child kept writing the data root. Propagate the token and pin it with a regression.
Compare file modes between two path stats (Windows adds execute bits by filename to a path stat but not to fstat), derive the origin-proof sentinel mode from an observed sibling, keep child patches as bytes, pin LF fixtures and posix worktree paths, unlink a stampless orphan monetary lock after a proven reap, and trim chapters 01/06/14 under their budgets.
The Windows full-test leg of #1247 found three defects in the new lock-scope
tests and one pre-existing runtime wart the tests exposed.
Runtime: after `reset --hard` the snapshot copies the source's exact bytes
over the checkout. Under `core.autocrlf=true` (Git for Windows' default) the
checkout wrote CRLF and recorded that size in the index; the copy restored the
source's LF bytes, and git trusts a size mismatch as a modification without
re-hashing, so every such file (`.gitmodules` on the runner) read as modified
in the child's `git status` for the run's whole life. One `update-index -z
--stdin` over the copied paths re-hashes them through the same clean filters
the baseline used and re-records their stat; the blob is unchanged.
Tests: the lock holder reports its own pid (a venv `python.exe` on Windows is a
launcher whose child is the interpreter, so `Popen.pid` is not the pid in the
lock) and exits on stdin EOF so a killed launcher never leaves a child holding
the OS lock; the populate test runs a second time under an autocrlf=true
global config so the stat re-record is proven on every OS (it fails with the
re-record removed); the consumer scan compares POSIX path spellings.
Docs: the populate sentence names the stat re-record; chapter budget raised.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Second review wave, four accepted advisories. A helper's row may carry only
its parent, or only the subagent role: the note now says "its root is",
"its parent is" or "its root is unknown" and never goes silent on a helper.
The route receipt reads the landing project from the admission receipt like
the promote receipt does (today identical to the requested id, so the claim
in the door commit is literally true on both verbs). The tool text says "any
settled status" instead of enumerating three of the four settled statuses.
The child capability summary and the pointer-stamp comment describe the
lineage and the hint as they are now. Pinned on both verbs, on a nested
helper and on a role-only helper.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
The focused Fable delta review of cd04248b0 found the head red on one
pre-existing expectation and two guards that no test would miss:
- tests/test_configured_session_prestart.py pins the startup-refusal stash
exactly; it now expects the `detail` key a refused provision fills (empty
for a blocked route), the contract fix batch 2 introduced.
- `_deletable` is strict: the snapshot root itself (which holds every live
snapshot) is never a deletable path; `_remove_paths` and the payload stale
delete reuse the one guard; a registry row naming the root deletes nothing
(test).
- The busy-first-section guard is two-sided: the typed refusal must arrive
after the one lock wait, never after a second discard wait.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
The first review wave read the candidate and found four sentences that
described the door the way it was: the missing-selector error and the
pending-promote refusal told the model to name a finished root, the room
manifest's docstring said the predicate admits a settled root, and the
last-result lookup's docstring said a child is never the room's
continuation because the promote door refuses it. The handler continues any
settled result, a helper's included, and the hint offers roots only; the
sentences now say that. Wording only, no behaviour change.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Three delta reviews of e1727bc25 (Fable, grok-4.7, gpt-6-astra) agreed on the
remainder:
- The non-UTF-8 fixture name is created only off Windows: that platform decodes
names with surrogatepass and raised before any OSError, which the guard did
not catch; the fixture's DOS device names (`nul.*`) are renamed.
- The three lock-scope sentences and the PR text say what the code does now:
the acting self_worktree lane populates and deletes outside the lock (its
post-checkout hook no longer fires at provision), only the boot-time
prune_orphans sweep and the genesis init still work under it; the binary
verdict is one process, two when empty files need their attribute verdict,
with the per-file fallback named.
- A busy FIRST lock section is a plain refusal: nothing was registered, so
nothing is discarded (no second wait, no spurious warning); a failure after
the row still discards row, pin and admin dir, now pinned by injections at
update-ref and worktree add.
- The START_FAILED row carries the producer's detail beside the typed facts;
the refusal-fact keys have one owner (delegate_shared.REFUSAL_FACT_KEYS).
- One `_deletable` guard for every pre-lock checkout delete; candidate order
matches the per-file predicate (PEM head before the size cap).
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
The door still refused a delegated helper's result as a predecessor and
told the model to name the root: the accident the refusal guarded against,
a room's pointer moved onto a helper by a child finalization, is closed by
construction now that only a root stamps the pointer and the hint window
never offers a child. What remained was a refusal with no danger behind it,
and one real gap: a helper's own files live under the helper's id, so a
continuation of the root cannot read the design a helper wrote, while a
continuation of the helper can.
A named helper's result is continued like any settled result, and the
receipt says whose helper it was and where its root is, beside the landing
note; the receipt's two facts ride one render-only event key that is popped
before emission. The predicate is now what the tool descriptions say: a
settled result (completed, failed or cancelled), not a live root and not a
pending promote. A failed root is pinned as a settled predecessor, because
the coordinator's first refused predecessor had failed at its ceiling and
was still the work to continue. The routing docstrings that equated
addressability with the host list say what the list is, a hint.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
A continuation admitted with a forked execution drive could read its
predecessor's complete result, artifact path and hash included, and could
not open the artifact: the lineage read covered the actor's own, its
parent's and its root's task_drive/artifact_store, and a predecessor named
by the work order was not in that set, so the task asked the owner to upload
a file that already existed (issue #1232).
lineage_task_ids now also names the ONE predecessor the task's contract
carries (predecessor_authority.source.task_id, the host-minted pointer
written at startup binding), one hop only and read-only: the same lineage
seam PR-era T4=A opened for parent and root, over the canonical data root
and the task's own drives. A child that inherits the envelope through the
contract spread reads it too; a write into the predecessor's drive stays
refused, a stranger's drive stays refused, and a task without the envelope
keeps today's lineage. Eight two-sided tests pin it; six of them fail with
the new id removed.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
A promoted task with a workspace runs on a forked execution drive under
state/headless_tasks/<id>/data, and the fork never carries
state/projects.json. list_projects, route_to_project and the promote
receipt's project name read the registry through ctx.drive_root, so such a
task answered "No projects yet" on an install with dozens of projects and a
task-authored route_to_project refused every existing project as
target_not_found. Five of the six rooms on the live install have a working
folder, so a coordinator in any of them could not route at all; the promote
path was already right because the durable project lookup reads the
canonical root.
The three reads now go through canonical_data_root (the task's budget root,
then its own drive), the same resolver the lineage reads use. A context
with no canonical root keeps reading its own drive.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
A coordinator task in one project named the settled roots of five other
projects, each as the predecessor of work it sent INTO that project, and
was refused seven times: the door accepted a predecessor only when the host
had listed it this turn or when it belonged to the caller's own room, and a
pooled task carries no host manifest at all. The workaround was four bridge
tasks promoted into each room, which then continued the exact predecessor
from inside - the same successor, two hops and a re-stamped pointer later.
The room comparison protected nothing. The pointer the door withholds is
rebuilt from the task id alone; the successor inherits data (its ceiling,
origin and contract come from the caller and admission); a fresh root into
any project needs no room check; and a Main-lane turn already continued a
listed root of one project into another. The door is now the predicate on
the root the refusals were really made of: a ROOT (not a delegated child),
a readable result, not live (a live root is steer_task, and a pending
promote is not a result). Where the caller sits and where the work lands
are no longer inputs; a continuation landing outside its predecessor's
project is disclosed in the receipt like the second-project note, on both
verbs, from the project admission actually returned. The render-only fact
rides the event only until emission, so the supervisor sees nothing new.
The tool descriptions stop telling the model that a host-listed id is
required and name the refusals that remain. Invariant 27 and the routing
paragraph state the predicate; both chapters' byte budgets rise with the
reason recorded beside the number.
Tests: the two pins that encoded the room comparison and the wake pin are
inverted; the coordinator shape (a pooled task with only its client surface
and contract as metadata, continuing another room's root into that room on
both verbs), the disclosed landing note (fires away from home, silent at
home), a project-less Main root continued from a room, and a public
conversation continuing a settled root while its promote still lands
without a project are pinned two-sided. With the room comparison restored
seven of these tests fail.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Five reviews of 226285a1f (Fable triad, grok-4.7 triad and scope, gpt-6-astra
triad and scope; dispositions in the sprint ledger) converged on these fixes:
- untracked_binary_verdicts: a path the first diff omits is identical to the
staged empty blob, i.e. an empty file, which git still classifies by
attribute; those paths get a second batch staged as a one-byte blob, so an
empty `-diff` / `binary` / driver-binary file is binary exactly as the
per-file verdict said. Only the two staging blobs enter the target's object
database. Scratch-index allocation is inside the guarded lifecycle, so an
unavailable temp dir also falls back to the per-file verdict with a warning.
- binary_verdict_candidates: the dotenv policy, the name rules, the PEM head
(restricted modes) and the size cap decide BEFORE the batch, so a vetoed or
over-cap file is never handed to git and never reaches a clean filter or an
encoding conversion; both callers batch only candidates.
- provision_execution_snapshot: the provisional row carries no per-file maps;
the first lock section is inside the cleanup scope, so a failed update-ref or
worktree add after the row discards row, pin and admin dir; a discard after a
busy lock waits 5 s, not the full timeout.
- provision_worktree / remove_worktree follow the same split: admin dir and
branch under the lock, the checkout populated (`reset --hard --quiet
--no-recurse-submodules`) and deleted outside it.
- A refused snapshot provision keeps its facts on the configured-child path:
cause, holder, waited seconds and the producer's detail ride
`subagent_availability`, the $0 terminal text, the START_FAILED row and the
acceptance evidence.
- Tests: platform guard on the newline-named fixture entry; non-UTF-8 name
only where the filesystem accepts it; hook positive control; `_git_env`
spy; acting-lane split; empty-file classes; filter-tee guard; refusal facts
on the bootstrap path; the receipt's timing facts join the per-case identity
set of the directory-geometry payload comparison.
- Docs: the delegated-lane sentences name the acting checkout/delete, the
registry read-modify-write residual and the target object-database growth
(owner decision: disclose only); data-layout inventory regenerated.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
A mutating delegate_start provisioned the child's execution snapshot inside
subagent_worktrees' ops lock, and the untracked-file classification inside
it spawned one `git diff --no-index --numstat` per file. One project with
67,692 untracked files held the lock for 40 minutes; every other mutating
start on the machine timed out after 120 s (issue #1241).
subagent_worktrees: the ops lock now guards shared metadata only. Provisioning
takes it twice for milliseconds - registry row FIRST, then the baseline pin,
then `worktree add --no-checkout` - so a crash after the row is reclaimable
by the startup GC; the tree walk, hashing, populate (`reset --hard --quiet
--no-recurse-submodules`, what `worktree add` runs internally, minus the
target's post-checkout hook, which no longer executes project-authored code
at provision) and the raw-bytes copy run outside it. Removal deletes files
outside the lock and forgets admin dir, pin and row inside. Payload snapshots
copy and commit outside the lock. A malformed registry refuses before the
tree is hashed. The lock file names its holder (pid/task/op/since/target) so
the owner-aware stale check evicts a SIGKILLed holder at once, and a timeout
is a typed WorktreeOpsLockBusy.
workspace_patch_capture: untracked_binary_verdicts stages every regular file
as the empty blob into a scratch index and asks ONE index-versus-worktree
`git diff --numstat -z`, so the verdict stays git's own (attributes, diff
drivers, clean filters, working-tree encodings) with one process per
inventory; a failed batch falls back to the per-file verdict with a warning.
Both the snapshot and the finalization patch capture use it.
delegate: every pre-POST provisioning refusal is definitely_unrun (a
configured leaf ends at $0), carries the lock holder when the cause is a
busy lock, and settles its invocation with a durable START_FAILED row; the
started receipt discloses the snapshot's entries, untracked files,
file-input bytes and provisioning seconds.
Docs: ARCHITECTURE section 6 snapshot paragraph, section 1 module row,
DEVELOPMENT delegated-lane bullet; two chapter byte budgets raised for the
added rationale text.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>