From e1ca382ea575a927c12551fbd1c899a1d9d9d080 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Sun, 9 Aug 2026 14:34:59 +0200 Subject: [PATCH] docs: worker identity framing revision 2 --- drop owner, specify the name path Two review blockers, both correct, both about the same failure: a field or a claim that looks like attribution without being it. BLOCKER 1 --- owner is not honest before P3. Revision 1 proposed owner = package-or-builtin while populating it from static per-subsystem constants at each dispatcher. Those disagree. A generic dispatcher has no trustworthy knowledge of who invoked it, and pmacs.process.spawn is callable by any package, so a static "lsp" label is an origin or category and would confidently misattribute third-party work to a builtin at exactly the point section 9 wants attribution. owner is removed rather than renamed. origin or subsystem would be honest wording, but a second string field beside purpose, used to group the view, gets adopted as ownership by the next reader regardless of its name --- and it would squat on the slot P3 has to fill. Stage 2 needs a grouping key and should get a real one. No P3 alignment is claimed any more. BLOCKER 2 --- the handler name needs a mechanism, not a parameter. Revision 1 said the name was "in hand at the one place that throws it away". That was wrong about the call chain, and re-reading it is what showed why: dispatch(name) calls an arbitrary handler, which calls a Lua wrapper, which calls the Rust binding, and name is a parameter of none of them. Worse, async.lua:337-345 documents the wrapper layer as bypassable --- other runtime files are told to call their own raw _dispatch_* primitives --- so capturing in the wrappers would miss exactly the callers attribution exists for. Q#W-2 is rewritten as a contract: a dispatch-name stack owned by the async runtime and read at allocate, the same single funnel Q#W-1 collapses. Seven rules, including the two that decide whether it is better than nothing --- unwind-safe popping, because one erroring handler would otherwise poison every later dispatch with a stale name, and composition rather than replacement of a caller-supplied purpose, because replacing recreates blocker 1 in a new place. It also answers the objection it invites: why is this ambient allowed when Q#W-5 defers parent for needing one. Because they are different mechanisms --- this is a synchronous single-threaded extent with a deterministic pop, and parent needs a lifetime model spanning ticks and post-settlement callbacks. Verification takes the reviewer wording fix: presence is a type obligation now that purpose is non-optional in a private JobSpec, so the compiler proves it and the tests prove semantics at representative entry paths. The handler witness must be a registered handler calling a real dispatcher, not a synthetic funnel test. The title changed too: "who asked for it" overclaimed once owner left. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai --- docs/active-work.md | 22 ++- docs/worker-identity-framing.md | 322 +++++++++++++++++++++++--------- 2 files changed, 254 insertions(+), 90 deletions(-) diff --git a/docs/active-work.md b/docs/active-work.md index 958216e..03c3567 100644 --- a/docs/active-work.md +++ b/docs/active-work.md @@ -275,21 +275,33 @@ from #171 and #215. the authoritative tip** — the ref, not a SHA. Recover with `git fetch githubsucks && git checkout worker-identity-stage1`. -- **Framing `docs/worker-identity-framing.md`, revision 1**, in review. +- **Framing `docs/worker-identity-framing.md`, revision 2**, in review. Scope: `COHERENCE.md` §9's "mechanism without identity", and journey step 11 — the last of Priority 1's own work, sitting in another section's arc. +- **Revision 2 took two blockers.** `owner` is **removed entirely**: + populated from static per-subsystem constants it is an origin, not an + owner, and would misattribute third-party work at the exact point §9 + wants attribution. It is not retained under a safer name either — + `origin`/`subsystem` would be adopted as ownership by use and would + squat on the slot P3 must fill. And the handler-name recovery was + **respecified as a mechanism**: revision 1 claimed the name was "in + hand at the one place that throws it away", which was wrong about the + call chain (`dispatch` → arbitrary handler → Lua wrapper → Rust + binding, with the wrapper layer documented as bypassable). - **NO WIRE CHANGE**, which is what lets this run beside the two lanes already in flight. The statusline activity indicator is a **fourth** `pmacs.statusline.register` provider (terminal/syntax/lsp are the three existing adopters), evaluated per frame inside `paint_frame` (`src/editor.rs:4560`) and riding the existing `StatuslineSegments` vector. No variant, no bump. -- **Scope:** `owner`/`purpose` on `PendingJob` and `ProcessSpec` +- **Scope:** a **required** `purpose` on `PendingJob` and `ProcessSpec` through the single allocation funnel (`src/async_runtime.rs:746`, - which every dispatcher and `register_external` passes through), the - handler name that `pmacs.workers.dispatch` currently discards, the - `*workers*` rendering, and the indicator. + which every dispatcher and `register_external` passes through), a + runtime-owned dispatch-name ambient recovering the handler name that + `pmacs.workers.dispatch` currently discards, the `*workers*` + rendering, and the indicator. Non-optional so the **compiler**, not a + test, proves every caller supplied one. - **Two scouting findings that shaped the design**, both verified: `PendingJob` carries **eight** fields, not the audit's seven, and the eighth's doc comment **cites §9 by name** as the reason identity diff --git a/docs/worker-identity-framing.md b/docs/worker-identity-framing.md index 9f9c38b..1b5fdb4 100644 --- a/docs/worker-identity-framing.md +++ b/docs/worker-identity-framing.md @@ -1,8 +1,22 @@ -# Worker identity — Stage 1: what is running, and who asked for it +# Worker identity — Stage 1: what is running, and what it is doing -**Status: framing pass, revision 1. Pre-implementation. Awaiting +*(Revision 1 was subtitled "and who asked for it". With `owner` +removed that title overclaimed the lane: it answers **what**, and — +under `pmacs.workers.dispatch` — **under which registered handler**. +Neither is who owns it.)* + +**Status: framing pass, revision 2. Pre-implementation. Awaiting approval.** +**Revision 2 removes `owner` and respecifies the handler-name path, +after review found the first dishonest and the second unbuildable as +described.** `owner` populated from static per-subsystem constants is +an *origin*, not an owner, and would misattribute third-party work at +exactly the point §9 wants attribution. And "the name is in hand at the +one place that throws it away" was **wrong about the call chain** — it +is thrown away across three layers, one of which callers are documented +to bypass. Both re-scouted in the tree. + --- ## 1. Why this, and why now @@ -78,14 +92,36 @@ drifted in one place, recorded below. Parse, FsReadDir, FsStat, FsRename, FsChmod, FsRemove, McpRequest, LspRequest. Confirmed unchanged since the audit. -- **A third-party job's own name is retained nowhere.** - `pmacs.workers.dispatch(name, args, opts)` - (`builtin/runtime/async.lua:369`) looks `name` up in a `handlers` - table and calls it; the handler must itself call one of the builtin - dispatchers, so the job records that builtin's `JobKind` and **`name` - is discarded at the call**. The audit's "every third-party job renders - under a builtin's label" is exact, and the fix is cheap: the name is - in hand at the one place that throws it away. +- **A third-party job's own name is retained nowhere, and recovering it + is NOT cheap. Revision 1 said it was, and was wrong about the call + chain.** The full path, read rather than assumed: + + ``` + pmacs.workers.dispatch(name, args, opts) -- async.lua:369 + → handlers[name](args, opts) -- arbitrary Lua + → dispatch_grep(spec, opts) -- Lua wrapper, :312 + → async_mod._dispatch_grep(spec, supersede_key(opts), max_batch) + → the Rust binding → allocate() + ``` + + **`name` is not a parameter of any layer below the first.** The Rust + dispatchers accept job arguments, a supersede key and stream data — + nothing else. So revision 1's "change the allocation funnel and the + name is recovered" is false: changing `allocate` gives the name + nowhere to arrive *from*. + + **And the wrapper layer cannot be the capture point either.** + `async.lua:337-345` deliberately exposes `pmacs.workers._new_handle` / + `_new_stream` so that "other builtin runtime files (`pmacs.fs` in + M8.1, future siblings) can construct handles for ids dispatched + through **their own raw `_dispatch_*` primitives**". A handler that + goes straight to `async_mod._dispatch_*` bypasses `dispatch_grep` and + friends entirely — and those are precisely the callers doing + non-standard work, i.e. the ones attribution is for. + + The audit's "every third-party job renders under a builtin's label" + is exact. The mechanism that fixes it is Q#W-2, and it is a real + mechanism, not a parameter. - **`ProcessSpec` has one identity field and it is a convention** (`src/process.rs:193-235`): `label: String`, documented as @@ -138,17 +174,36 @@ all right now**: `Workspace` is §7, graded *missing*, and `Location` is `workspace: Option` would be adding a field typed on a thing that does not exist. -**Stage 1 (this lane): identity on the job and the process, and the -first indicator. NO WIRE CHANGE.** +**Stage 1 (this lane): a required `purpose` on the job and the process, +and the first indicator. NO WIRE CHANGE. NO `owner`.** -- `owner` and `purpose` on `PendingJob`, carried through the single - allocation funnel, and on `ProcessSpec` alongside the existing +- **`purpose`, non-optional**, on `PendingJob`, carried through the + single allocation funnel, and on `ProcessSpec` alongside the existing `label`. -- `pmacs.workers.dispatch` stops discarding the registered handler name. -- `*workers*` renders owner and purpose. +- **A dispatch-identity ambient** so `pmacs.workers.dispatch` stops + discarding the registered handler name (Q#W-2). +- `*workers*` renders `purpose`. - **A statusline activity indicator** — the fourth provider registration, and the part a user feels on day one. +**`owner` is deliberately absent, and revision 1 was wrong to include +it.** The proposal was `owner = "lsp"` populated from a static +per-subsystem constant at each dispatcher. But a generic dispatcher has +no trustworthy knowledge of who invoked it, and `pmacs.process.spawn` +is callable by any package — so a static subsystem label is an +**origin or category, not an owner**, and it would confidently +misattribute third-party work to a builtin at exactly the point §9 +wants attribution. A field that asserts a falsehood is worse than an +absent one: `*workers*` would *look* attributed while naming the wrong +party. + +**Nor is it retained under a safer name.** Calling it `origin` or +`subsystem` would be honest, but a second string field sitting beside +`purpose` and grouping the view would be *adopted* as ownership by the +next reader regardless of its name — and it would squat on the slot +P3's real package signal has to fill. Stage 2 needs a grouping key; it +should get a real one, not a placeholder promoted by use. + **Stage 2 (separate lane): join the planes.** One activity view over jobs, processes, LSP servers and terminals. This is what Stage 1's identity is *for* — the audit's own conclusion is that "the four views @@ -177,7 +232,11 @@ beside both. - **§9 worker ownership — the direct target**, and specifically the audit's named prerequisite: *"Owner/purpose/parent fields on the job and process specs are the prerequisite; the unified view and the - ownership tree fall out of them."* Stage 1 takes owner and purpose. + ownership tree fall out of them."* **Stage 1 takes ONE of the three + — `purpose`.** `owner` waits for P3 to supply a package signal worth + recording (§3); `parent` waits for Stage 3 (Q#W-5). Taking one of + three named prerequisites is a deviation from the audit, and it is + stated here rather than left to be noticed. - **Journey step 11 — the direct target.** §0 names background-work ownership as one of two remaining thin ends. This does not close the step (Stage 2's unified view is most of that) but it is the first @@ -195,8 +254,12 @@ beside both. - **The debt this repays is named and dated.** `git-integration-framing.md` Q#G-5 recorded a deliberate negative §9 impact. This lane does not fully discharge it — a labelled process is still not in `*workers*` - until Stage 2 — but it makes the label structured rather than - conventional, which is the prerequisite. + until Stage 2 — but it makes the process state *what it is doing* in + a required field rather than a caller-spelled convention. +- **No P3 alignment is claimed.** Revision 1 argued this lane aligned + with P3's ownership arc. With `owner` removed, it does not: P3 stays + entirely ahead of it, and this lane deliberately leaves that slot + empty rather than filling it with something P3 would have to displace. ## 5. Open questions @@ -208,11 +271,18 @@ more positional parameters gives a five-argument function and a six-argument variant, and the next lane adds a seventh. *My vote: **collapse the pair into one funnel taking a struct***, e.g. -`allocate(JobSpec { kind, supersede, stream, resource, identity })`, -with `JobSpec` carrying a `Default`-derived constructor so the ten -dispatchers read as named-field literals rather than positional soup. -Ten call sites plus `register_external` is a bounded, mechanical edit, -and it removes the `_with_resource` wart rather than adding beside it. +`allocate(JobSpec { kind, supersede, stream, resource, purpose })`, so +the ten dispatchers read as named-field literals rather than positional +soup. Ten call sites plus `register_external` is a bounded, mechanical +edit, and it removes the `_with_resource` wart rather than adding +beside it. + +**`JobSpec` is private, and `purpose` is non-optional.** Private +because the public dispatcher APIs should not grow a parameter every +time this arc adds a field; non-optional because that is what makes the +compiler, rather than a test, the thing that proves every caller +supplied one (§6). A `Default` impl would defeat exactly that, so +`purpose` is not defaulted even if other fields are. **The counter-argument, which is real:** this touches every dispatcher in a lane whose subject is identity, which is scope the reviewer did not @@ -220,42 +290,88 @@ ask for. **If review prefers the minimal edit**, the alternative is one more parameter on the existing pair, and the collapse becomes its own small lane. I would rather be told than assume. -### Q#W-2 — what IS an owner? **(the hard one)** +### Q#W-2 — the dispatch identity path **(rewritten in rev 2)** -This is the question that decides whether the field is useful or -decorative, and I do not think it should be answered by whatever is -convenient at the call site. +Revision 1 treated this as a parameter-passing detail. §2 shows it is +not: `name` dies at `pmacs.workers.dispatch` and nothing below it takes +a name, so the value must be carried *out of band* across an arbitrary +handler. -Candidates: the **package** that registered the code (P3's -`CurrentlyLoadingPackage` signal already exists and §20 P3 names -owner-carrying registrations as its work unit); the **command** that -the user invoked; or the **subsystem** (lsp, syntax, git, compile). +**The capture point is Rust, not Lua**, and the reason is the bypass in +§2. If the ambient lived in the Lua wrapper layer, a handler calling +`async_mod._dispatch_*` directly — the documented pattern for runtime +files with their own primitives — would produce an unattributed job, +and those are the callers attribution exists for. Putting it in the +runtime means it is read at `allocate`, **the same single funnel Q#W-1 +is already collapsing**. One mechanism, one site, no path around it. -*My vote: **`owner` is a package-or-builtin identity, `purpose` is the -human sentence.*** Concretely: `owner = "lsp"` / `purpose = "indexing -src/editor.rs"`. The reasons: +*My vote: **a dispatch-name stack owned by the async runtime***, with +`pmacs.workers.dispatch` bracketing its handler call through two +runtime-internal bindings (`_push_dispatch_name` / `_pop_dispatch_name`). -- It is the only one of the three that a **third party** can be - attributed by, which is the whole point of attribution — a user - wanting to know why their editor is busy is usually asking *whose - code* is doing it. -- It aligns this arc with P3 rather than duplicating it. §20 says P3's - ownership arc "unblocks ... package-scoped task cancellation in §9", - so the two are meant to share a notion of owner. +**The contract, in full:** -**Named risk, stated rather than hidden:** P3 has not been built, so -Stage 1 populates `owner` from a **static per-subsystem constant** at -each dispatcher, not from a live package signal. That is honest for -builtins and gives third-party Lua nothing better than today until P3 -lands. **If review thinks a field that third parties cannot populate is -premature, deferring `owner` and shipping only `purpose` is a coherent -smaller lane** — and it would still fix the indicator, which is the felt -part. +1. **Extent is the SYNCHRONOUS handler call, and nothing more.** Push + before, pop after. Every job reaching `allocate` during that window + carries the name. +2. **Work dispatched later is NOT covered, deliberately.** A job + dispatched from an `on_complete` callback or a resumed coroutine + runs ticks later, outside the extent, and carries only its own + `purpose`. Pretending otherwise would need the asynchronous + lifetime mechanism this lane defers (Q#W-5). +3. **Nesting is a stack; innermost wins.** Handler `a` calling + `pmacs.workers.dispatch("b", …)` gives jobs allocated inside `b` the + name `b`, and restores `a` on return. +4. **Fan-out shares the name.** A handler dispatching five jobs + produces five jobs named alike. They *were* all dispatched under it; + that is the fact being recorded, not a collision. +5. **Unwind-safe, and this is the one that makes a naive version worse + than none.** A handler that errors must still pop — otherwise one + failure poisons every subsequent dispatch in the session with a + stale name, and the feature silently starts lying. `pmacs.workers. + dispatch` runs the handler under `pcall`, pops, and rethrows. +6. **Precedence over a caller-supplied purpose: COMPOSE, do not + replace.** Where the dispatch site supplied its own purpose, the + recorded value is `": "`; where it did not, the + recorded value is `""`. Replacing would recreate blocker 1 in + a new place — `dispatch_grep` supplies `"grep: …"`, and letting that + win would lose the third party again, while letting the name win + would discard the only description of the actual work. Composition + is capped at the innermost name by rule 3, so no unbounded chain. +7. **Outside any extent, nothing changes.** A builtin invoked directly + records its own `purpose`. + +**A known and accepted property, stated rather than discovered later:** +the ambient captures *causal* extent, not *intent*. If a handler +synchronously triggers unrelated work — an edit that schedules a parse +— that job is inside the window and takes the name. Within a +synchronous extent I think that is the honest reading ("this ran +because that handler ran"), and it is the only definition enforceable +at a single funnel. **If review disagrees, the alternative is +capture-at-the-Lua-wrapper**, which is narrower and misses the raw +`_dispatch_*` callers — a trade of false positives for false negatives, +and I would rather over-attribute inside a synchronous call than +silently drop the third-party case. + +**Why this ambient is admissible while Q#W-5's is not.** They are not +the same mechanism. This one is a synchronous, single-threaded, bounded +dynamic extent with a deterministic pop — a `let` binding in disguise. +A `parent` ambient must span a job's *asynchronous* lifetime, across +ticks, through callbacks that run after the parent settled. The first +is a stack; the second is a lifetime model. ### Q#W-3 — what does the indicator actually show? -*My vote: **a count with the busiest purpose, and nothing when idle*** -— e.g. `⋯2 lsp: indexing`, absent entirely at zero. +*My vote: **a count plus the oldest in-flight job's `purpose`, and +nothing when idle*** — e.g. `⋯2 lsp: indexing`, absent entirely at +zero. With `owner` gone (§3) `purpose` is the only identity there is, +which is also why it is required rather than optional. + +**Oldest, not newest or "busiest".** Revision 1 said "busiest", which +is not a defined quantity — jobs carry no cost estimate. Oldest is +computable from `dispatched_at`, which `PendingJob` already has, and it +answers the question a user actually asks of a stuck editor: *what is +taking so long?* - **Absent at zero, not `0 jobs`.** A statusline segment that is always present costs width forever to say "nothing is happening". The @@ -285,21 +401,31 @@ under its existing callers. ### Q#W-5 — does `parent` belong in Stage 1? -*My vote: **no**, and this is where I would most expect to be -overruled.* The audit names owner/purpose/**parent** together as the -prerequisite, so leaving one out is a deviation I should justify. +*My vote: **no.*** The audit names owner/purpose/**parent** together as +the prerequisite, and after revision 2 this lane takes only `purpose` — +so both omissions need justifying, not just this one. `owner`'s is in +§3; `parent`'s is here. -The justification: owner and purpose are **values a dispatcher already -knows** at the call site. A parent is not — it is whatever job is -*currently running* when a child is dispatched, which means either an -ambient context (a mechanism, with re-entrancy and cleanup failure -modes) or threading a parameter through every intermediate layer. A -`parent` field that nothing populates is worse than no field: it renders -as `None` everywhere and reads as "this job has no parent" rather than -"this system does not track parents". +`purpose` is a **value the dispatcher already knows** at the call site. +A parent is not — it is whatever job is *currently running* when a +child is dispatched. A `parent` field that nothing populates is worse +than no field: it renders as `None` everywhere and reads as "this job +has no parent" rather than "this system does not track parents". -Stage 3 builds the ambient and the field together, where the field can -be tested by a populated case. +**And the objection revision 2 has to answer, since it now builds an +ambient of its own (Q#W-2):** why is one admissible and not the other? +Because they are not the same mechanism. Q#W-2's extent is +synchronous, single-threaded, and bounded by one function call, with a +deterministic pop on both the normal and the error path. A `parent` +ambient must identify the running job *across ticks* — a job dispatched +from an `on_complete` callback should name the job whose completion +fired it, and that callback runs after the parent settled, on the main +thread, outside any dispatch call. That is a lifetime model, not a +stack, and it is Stage 3's subject rather than a field this lane can +add cheaply. + +Stage 3 builds the lifetime model and the field together, where the +field can be tested by a populated case. ### Q#W-6 — is any of this configurable? @@ -312,22 +438,42 @@ on every frame, and "I do not want this in my modeline" is a preference someone will genuinely hold on day one rather than a hypothetical. `git.enabled` and `ui.line-wrap` are the precedent shape. -No setting for owner/purpose capture itself — that is substrate, not +No setting for `purpose` capture itself — that is substrate, not preference. ## 6. Verification -- **Every job carries an identity, asserted at the funnel, not per - dispatcher.** The point of a single allocation site is that one - assertion covers all ten dispatchers plus `register_external`; a test - that checks three dispatchers individually would pass while a - fourteenth added later carries nothing. -- **A `pmacs.workers.dispatch("name", ...)` job reports `"name"`**, not - the builtin `JobKind` label underneath it — the exact defect §9 names, - witnessed on a handler registered from Lua. -- **`register_external` jobs carry identity too** (MCP and LSP), since - they bypass the worker pool entirely and are the ones most likely to - be missed. +- **Presence is enforced by the COMPILER, not by a test.** `purpose` is + non-optional in `JobSpec`, so a dispatcher that supplies none does not + build. Revision 1 claimed a single funnel assertion proved "every job + carries an identity"; **it does not** — a funnel test proves the + funnel stores what it was handed, and says nothing about whether + fourteen callers handed it anything meaningful. Presence is a type + obligation; the tests below are for *semantics*. +- **Representative entry paths assert the semantics**, one per distinct + shape rather than one per dispatcher: a pool dispatcher, an + `register_external` job (MCP/LSP bypass the worker pool entirely and + are the likeliest to be missed), and a spawned process. +- **A `pmacs.workers.dispatch("name", …)` job reports `"name"`**, and + the witness is **a handler registered from Lua that calls a real + dispatcher** — not a synthetic funnel test. A test that pushes the + ambient by hand proves the stack works and leaves the actual defect + (`name` dying in an arbitrary handler) unwitnessed. +- **The ambient survives a failing handler** (Q#W-2 rule 5): a handler + that errors, then a subsequent unrelated dispatch, asserting the + second job does **not** carry the first's name. This is the + regression that would otherwise appear as intermittent + misattribution long after the lane lands. +- **Nesting and fan-out** (rules 3–4): a handler dispatching two jobs + gives both its name; a handler dispatching through another registered + handler gives the inner jobs the inner name and restores the outer. +- **Composition, not replacement** (rule 6): a handler calling a + dispatcher that supplies its own purpose yields `": "` + — asserted for both halves, since a test on the prefix alone passes + when the description is dropped. +- **Work dispatched from an `on_complete` callback carries no handler + name** (rule 2) — the boundary of the extent, asserted deliberately + so it reads as designed rather than broken. - **The statusline shows nothing at idle**, asserted as *absent segment*, not as empty string — a zero-width segment still consumes a separator. @@ -344,7 +490,7 @@ preference. `lean4_stage1_acceptance`) are the assertion, and they must pass untouched. **If any of them needs editing, the design is wrong**, and that is the signal to stop rather than to adjust a baseline. -- **A spawned process carries structured owner/purpose alongside its +- **A spawned process carries a required `purpose` alongside its existing `label`**, and **`label`'s current callers keep working unchanged** — `lsp:{name}` and terminal buffer names are live conventions with existing consumers. @@ -356,9 +502,11 @@ preference. **What this will NOT prove:** that background work is attributable from one place (that is Stage 2's unified view — this lane makes it *possible*, not *done*), that a terminal PTY is visible anywhere -(Q#W-4), that cancellation can range over an owner (Stage 3), or that a -third-party package's own identity flows through (Q#W-2 — blocked on -P3). +(Q#W-4), that cancellation can range over an owner (Stage 3), or **that +any job is attributed to the PACKAGE responsible for it** — `purpose` +records what work is being done and, under `pmacs.workers.dispatch`, +which registered handler it ran under. Neither is package ownership, +which waits for P3 (§3). Gates via `scripts/gate --acceptance `. **No `--protocol`**: this lane has no wire change, which is the property that @@ -366,15 +514,19 @@ lets it run beside the two lanes already in flight. ## 7. Not in scope +**`owner`, in any spelling** — including `origin` or `subsystem` (§3). +The slot stays empty until P3 can fill it with a package signal; +nothing in this lane may be promoted into it later by use. `Workspace` and `Location` fields (§7/§8 — the entities do not exist). `parent`/`children` and the ownership tree (Stage 3, Q#W-5). Scoped cancellation of any kind — cancel-all, by-kind, by-buffer, by-owner, by-subtree (Stage 3; there is nothing to range over until identity exists). The unified activity view joining the four planes (Stage 2). Making terminal PTYs visible (Stage 2, Q#W-4). Widening `JobKind` or -making it open — third-party jobs are attributed by `owner`/`purpose`, -which is the point, and reopening a closed wire-adjacent enum is a -separate decision. Latency classes and resource budgets (§9 names them; +making it open — third-party jobs are described by `purpose`, which is +the point, and reopening a closed wire-adjacent enum is a separate +decision. Latency classes and resource budgets (§9 names them; neither has a consumer yet). Supersession coverage — §9 notes parse jobs and MCP requests pass `None`, which is a real defect and a **different** -one. P3's package-ownership signal (Q#W-2 depends on it and says so). +one. P3's package-ownership signal — §3 defers `owner` to it and makes +no claim of alignment with it.