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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
parent
dda90a2c37
commit
e1ca382ea5
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<WorkspaceId>` 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 `"<name>: <purpose>"`; where it did not, the
|
||||
recorded value is `"<name>"`. 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 `"<name>: <purpose>"`
|
||||
— 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 <the new suite>`. **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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue