@@ -131,3 +131,71 @@ follow ordinary installation of the new release, in the same plugin PR.
131131Public records contain synthetic assertions and aggregate evidence. Raw
132132provider streams, user logs, usernames and private paths remain outside the
133133repository and pull-request description.
134+
135+ ## Correction, 2026-11-06: ownership across cancellation and generations
136+
137+ The shared-cap change exposed an existing lifetime gap. A real Ninja control
138+ cancelled the top-level build after BMI publication. Its detached compiler
139+ remained alive 200 milliseconds later. Deleting the previous directory tokens
140+ at the next build could therefore admit new compilers over the requested cap.
141+ The correction below supersedes directory-token ownership in the earlier
142+ shared-permit description.
143+
144+ Each detached-schedule backend drive owns a runtime generation. Normal graph
145+ execution and cached replay, including quiet replay, open the same scope. Its
146+ owner has both the operating system's process identity with birth time and an
147+ exclusive kernel lock. A PID alone, a surviving record, or an absent birth time
148+ cannot establish ownership. Closing the lock revokes the drive even while its
149+ process is alive. The generation is passed only in the child environment after
150+ cache decoding; it never enters an action, build fingerprint or saved graph.
151+
152+ Every phase-one helper, supervisor, join helper and ordinary compiler executor
153+ registers a kernel-locked actor before modifying build state and rechecks the
154+ owner after registration. The actor covers status publication, BMI equivalence
155+ settlement and backup restoration, as well as compiler execution. Interrupt
156+ callbacks retain that actor until their final write. A delayed helper from a
157+ closed generation returns cancellation before changing any build state.
158+
159+ A stable gate serializes generations for one canonical output directory. The
160+ next generation waits for all previous actor locks, including actors that have
161+ already released a compiler permit. It retains busy state and refuses admission
162+ if the actors do not drain within the cleanup bound. Runtime locks reside under
163+ the work directory's ` .mcpp/compiler-runs ` , outside the target output tree and
164+ independently of ` MCPP_HOME ` . Cleaning output files cannot unlink a live permit.
165+
166+ Actual compiler permits are stable kernel-locked files. A supervisor acquires
167+ its permit itself and holds it until the child has stopped; phase one does not
168+ transfer a lock or consume a second permit. Ordinary compilation uses the same
169+ slots. Files left after process death are reused by locking, never reclaimed by
170+ unlinking. A typed busy result is retried; permission and other failures stop
171+ the invocation. This also replaces the directory-creation contention path that
172+ reported access denied in the Windows-host GCC cross-build.
173+
174+ Child cancellation uses the existing launcher stop predicate. A further causal
175+ control found that inherited or discarded streams skipped that predicate when
176+ no absolute deadline was set. The launcher now polls child completion in that
177+ case, retaining its argv, streams, exit status and existing deadline. No timeout
178+ is increased. Failed or cancelled compiler invocations withdraw partial outputs;
179+ subsequent ordinary builds recover missing objects and BMIs.
180+
181+ The permanent real-Ninja control checks:
182+
183+ - Module and ordinary compilation under top-level SIGINT and SIGKILL, followed
184+ immediately by another generation; the old compiler has stopped before the
185+ new drive is admitted.
186+ - No-op builds across generations, retaining compiler count and object mtime;
187+ normal and quiet cached replay retain their graph, objects and BMIs.
188+ - Changed homes and a symlinked target directory retain the work-directory
189+ runtime barrier, independently of the disposable output location.
190+ - Owner-lock closure while the owner process remains alive.
191+ - Missing and incorrect process birth identities, rejected before state writes.
192+ - Delayed phase-one BMI settlement and backup restoration after compiler exit;
193+ both still block the next generation until their final state write.
194+ - Unregistered old phase-one and supervisor processes delayed until after a
195+ successful new generation; neither can alter its BMI, object or status.
196+
197+ The controls execute the actual helper and launcher implementations with a
198+ small synthetic compiler. The separate GCC mixed workload retains the real
199+ compiler cap, late failure, recovery, argv and no-op assertions. Selected-object
200+ controls establish causality; final qualification remains complete current-source
201+ CI and independent ecosystem review.
0 commit comments