✅ Cover projected content's lifetime with real modifiers - #326
Merged
Conversation
The invocation boundary owns everything projected content creates, and three of #203's coverage points had no test. `persist eval` and `daemon` were exercised only in a component's *own* body — L3 and Q5 — and the ordering claims rode entirely on the stub `watch` modifier in Tier O. The spec's §6.11 promise that a daemon started inside `<TempDir>` stops before the directory is removed was documented, and unproven. L8 puts a real `persist` block on both sides of the boundary: the caller projects one, and the component retains its own *after* projecting, which is the order a single scope would release in reverse. It asserts `start:projected, start:own, stop:projected, stop:own`. Anchoring projected content on the invocation's eval scope instead of the content scope flips the last two — the test was written against that mutation. Q14 is Q5 with the daemon written at the call site and the component doing nothing but `<Content />`: a projected process still stops with the invocation that hosted it, not with the document. TempDir.test.md tells the same story in Markdown with no JavaScript. The daemon traps SIGTERM and records whether its directory still exists at the moment it is signalled, into a second `<TempDir />` that outlives the first. `ALIVE` is the ordering; `REMOVED` is the cleanup. It fails on the same mutation. No behavior changes. Two conformance rows accompany the new tests.
PR #326: ✅ Cover projected content's lifetime with real modifiers4 files, +135 / -0 Scope✅ PR scope looks good. Structural✅ No structural bloat detected. Slop✅ Slop indicators look low. Static Analysis✅ Oxlint found no issues. CorrectnessNo extraneous code patterns detected. |
The in-component block printed `ready` unconditionally, and the probe after it chose `STOPPED` whenever `kill -0` failed — which is also what a missing pid file produces. Q14 therefore passed when the projected daemon never started at all: the exact premise it needs in order to prove that the daemon later stops with the invocation. Both markers now fail closed. `RUNNING` is printed only after the pid file is non-empty *and* `kill -0` succeeds, still inside `<Holder>`, so the process is known to have been alive. The probe distinguishes a missing pid file as `NOPID` rather than reading it as a stopped process. The assertion requires `RUNNING` and `STOPPED` and excludes `LEAKED`; the positive marker is load-bearing because a printed root error does not stop the run. Removing the daemon block makes it report `NOPID` and fail on `RUNNING`.
taras
added a commit
that referenced
this pull request
Aug 4, 2026
Rebased onto 511776e. #326 closed #203 by proving the two projection lifetimes with real modifiers — L8 for a projected `persist` resource and Q14 for a projected daemon — which were the acceptance criteria this target had the weakest evidence for. Projected content keeping its caller's bindings while its live effects belong to the component invocation is now stated as shipped, along with why it matters: a process a stage starts stops before the workspace it ran in is removed, which is the ordering `<Worktree>` (#293) is built on. `architecture.md` is authoritative on the error model, so the `output` row reads "fails the run; `<PrintErrors>` can print instead" as the registry states it. The engine does not do that yet — an outer boundary ends the run instead, whether the failure arose in the region itself or in content projected into it — so the discrepancy is classified as missing and filed as #327 rather than allowed to redefine the settled contract. No stage writes `<PrintErrors>`, so nothing here depends on it. The distinction the target does keep is the one the registry makes: a `throw` decision is the mode a boundary cannot replace. Every stage's parsing sits in documentation, so the final `<Parse>` is a gate for that reason and not because printing is unavailable.
This was referenced Aug 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
#203 asks that content projected into a component belong to that component's
resource scope, and lists what has to be covered. The engine already does it —
withInvocation(§4.4), the content scope, the three-stage teardown — and thespecification and website already describe it. Three of the issue's coverage
points had no test:
persist evalresource stopping with the invocation,JavaScript.
persistanddaemonwere exercised only in a component's own body (L3, Q5),and the ordering claims rode entirely on the stub
watchmodifier in Tier O.Spec §6.11 promises that a daemon started inside
<TempDir>stops before thedirectory is removed; nothing checked it.
What changes
No behavior changes. Three tests and their two conformance rows.
Before: the caller's side of the boundary was covered only through the Tier O
stub. A regression that anchored projected
persist/daemonresources on theinvocation's eval scope instead of the content scope passed every suite.
After: it fails L8 and the
TempDirdocument.How it works
L8 (
packages/core/tests/eval-persist.test.ts) puts a realpersist evalblock on both sides of the boundary. The caller projects one; the component
retains its own after
<Content />, which is the order a single scope wouldrelease in reverse. A later sibling reads the timeline:
Q14 (
packages/core/tests/daemon-integration.test.ts) is Q5 with the daemonwritten at the call site and the component body being nothing but
<Content />.It fails closed at both ends:
RUNNINGis printed only once the process hasbeen seen alive from inside the component, and the probe after the component
chooses
STOPPEDonly against a pid file that exists — a missing one reportsNOPID. A daemon that never started therefore cannot satisfy the premise.TempDir.test.mdtells the same story in Markdown with no JavaScript. Thedaemon traps
SIGTERMand records whether its working directory still exists atthe moment it is signalled, writing into a second
<TempDir />that outlives thefirst:
Review guide
Start with:
packages/core/tests/eval-persist.test.ts— L8Then review:
packages/core/src/components/TempDir.test.md— the reader-facing form ofthe same contract
packages/core/tests/daemon-integration.test.ts— Q14specs/executable-mdx-spec.md— the L8 and Q14 conformance rowsLook carefully at: the daemon in
TempDir.test.mdarms itsTERMtrapbefore writing the marker the following block waits on, so the wait cannot
succeed against an unarmed trap.
What must stay true
withInvocation's teardown and by the content scope's parentage, checked byL8 and by
ALIVEinTempDir.test.md.How to verify it
Each test was written against a mutation of
Invocation.useContentScope()that returns the invocation's eval scope insteadof creating the content scope — the pre-#203 anchoring:
component's scope. Under the mutation it produces
start:projected, start:own, stop:own, stop:projected— the exact inversion.TempDir.test.mdproves the same thing without JavaScript and fails underthe same mutation (
REMOVEDis absent).if projected processes anchor at the document scope. Removing its daemon block
makes it report
NOPIDand fail onRUNNING, so it cannot pass for a daemonthat never started. It does not discriminate the content-scope mutation,
and no test of its shape can: under that mutation the daemon anchors on the
invocation's eval scope, which is still torn down at stage 3 of the same
invocation, before the probe runs. Only the relative order changes, and a
lone daemon has nothing to be ordered against. L8 and
TempDir.test.mdcarrythat direction.
Scope
Included
Intentionally unchanged
withInvocation,projection.tsandexpand.tsare untouched.model; §6.11 already states the
<TempDir>claim the Markdown test now proves.was in Q7.
Generated or mechanical changes
None.
Scope confirmation