@junghanacs/entwurf 0.38.0 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/BASELINE.md +9 -4
  2. package/CHANGELOG.md +221 -0
  3. package/LICENSES/node-gyp.txt +24 -0
  4. package/README.md +14 -2
  5. package/VERIFY.md +14 -5
  6. package/docs/setup-clean-host.md +3 -1
  7. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +13 -4
  8. package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-socket-path.js +56 -1
  9. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +25 -0
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +14 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +14 -2
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-probe.js +21 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/win32-native.js +182 -0
  14. package/mcp/entwurf-bridge/src/index.ts +13 -4
  15. package/native/win32-x64/entwurf_win.node +0 -0
  16. package/native/win32-x64/manifest.json +45 -0
  17. package/package.json +4 -2
  18. package/pi/pi-durable/carrier/harness-setup.js +3 -3
  19. package/pi/pi-durable/overlay/upstream-pin.json +2 -2
  20. package/pi-extensions/entwurf-control.ts +6 -1
  21. package/pi-extensions/lib/control-socket-path.js +56 -1
  22. package/pi-extensions/lib/entwurf-control-rpc.ts +32 -0
  23. package/pi-extensions/lib/meta-session.ts +14 -0
  24. package/pi-extensions/lib/socket-discovery.ts +12 -1
  25. package/pi-extensions/lib/socket-probe.ts +20 -0
  26. package/pi-extensions/lib/win32-native.js +198 -0
  27. package/run.sh +105 -1
  28. package/scripts/check-entwurf-self-address.ts +15 -4
  29. package/scripts/check-gate-qualification.ts +2 -1
  30. package/scripts/mutants/pack-install.json +28 -2
  31. package/scripts/mutants/pi-durable.json +5 -5
  32. package/scripts/mutants/win32-preview.json +215 -0
package/BASELINE.md CHANGED
@@ -23,7 +23,8 @@ or **unverified**. Since 0.20.0 the table also carries two qualifiers:
23
23
  runner is not a physical host, so bare **certified** still means a physical-host
24
24
  doctor green; and **NOT CERTIFIED — pending physical host** marks a rail whose
25
25
  missing evidence only a physical host can supply. That is an evidence boundary,
26
- not **UNSUPPORTED** — the deliberate never, which today is native Windows only.
26
+ not **UNSUPPORTED** — the deliberate never, which today names the native-Windows surfaces
27
+ outside the experimental preview (rows below).
27
28
 
28
29
  | Surface | Verdict | Evidence |
29
30
  |---|---|---|
@@ -43,8 +44,11 @@ not **UNSUPPORTED** — the deliberate never, which today is native Windows only
43
44
  | Doctor fail-closed on unreadable environ | Linux measured; Darwin unmeasured | Copilot launch-flag and omp identity-carrier: missing `/proc/<pid>/environ` as predicate INPUT is `UNVERIFIABLE` (non-green), not a benign note. omp distinguishes `pgrep` exit 1 (absent) from exit 2 (enumeration failed); copilot distinguishes ENOENT/ESRCH (gone) from other errno (read denied). |
44
45
  | setup on uncertified platform with harness present | named non-green | All 13 harness units ask `harness_rail_certified_platform()`; uncertified → FAIL whose wording is not "install failed". Harness-absent Darwin setup (the CI cell) can still be computed green. |
45
46
  | macOS physical-host probe | shipped, unrun on a Mac | `scripts/raw-macos-measure/` — `/bin/sh`, cells M1–M9, 3.5 s; needs neither entwurf install nor login. Still unmeasured: Darwin foreign-process environ (`ps -Eww`) and whether macOS `python3` is a CommandLineTools stub. |
46
- | WSL2 | unverified | Counts as Linux evidence, never a Windows surface (AGENTS Hard Rule 17) |
47
- | native Windows | UNSUPPORTED | Locked pending GLG; a scope decision, not an evidence gap |
47
+ | WSL2 Entwurf-only install + zero-token Pi citizen (GitHub Actions `windows-2025` runner, not a physical machine) | shape-only | Experimental, non-voting CI (not read by the release oracle): 2026-10-18, `.github/workflows/wsl2-install-surface.yml` run 39098773010 @ `b03d646` ([#78 receipt](https://github.com/junghan0611/entwurf/issues/78#issuecomment-6245947301)). Guest kernel `6.18.40.1-microsoft-standard-WSL2`, host `wsl --list --verbose` VERSION 2, guest fstype quoted raw as `ext2/ext3`. The run's packed tgz digest is equal across producer, host and guest; guest Node 24.18.1; harness-absent `setup` green with protected auth/harness state unchanged. Pi 1.3.0 (sha512 pinned to the registry integrity) wired by `setup`: one V3 record whose filename is its gid, a gid-keyed control socket with a native connect, `peer-facts` alive; only `get_state` was sent and no `agent_start` was observed. Pi creating an empty `auth.json` is not a login. It certifies no harness rail. Linux-guest runtime evidence only: not native-Windows evidence, and not a substitute for the required Linux CI jobs. |
48
+ | WSL2 delivery, non-pi harnesses, vendor turn, physical user machine | unverified | No receipt. Authentication and model turns were deliberately excluded from the CI cells' scope (not a CI impossibility); non-pi harnesses, delivery, fresh/resume and physical machines are unmeasured. |
49
+ | native Windows experimental preview: Pi citizen + `entwurf_v2` delivery (GitHub Actions `windows-2025` x64 runner, Node 24.18.1, Pi 1.3.0; 0.39.0 candidate, not yet released; release and npm publication pending; the evidence below is the feature-branch CI artifact at `3b65bdc`) | shape-only | Experimental, non-voting CI (not read by the release oracle): 2026-10-18, `.github/workflows/windows-native-preview.yml` run 39123555360 @ `3b65bdc`, with the required `ci.yml` four jobs green at the same SHA (run 39123555224). Build: MSVC (VS 2026, toolset v145, SDK 10.0.26100.0), two builds byte-identical (`entwurf_win.node` sha256 `9837893521ec…`), imports advapi32 + kernel32 with `node.exe` the only delay-load; node-gyp 13.1.0 and the Node headers/`node.lib` pinned. The pack carries exactly the addon and its manifest; npm installed it into a sandboxed consumer prefix; Pi came from a tarball checked against its pinned SRI. Six addon cells PASS (final-component reparse and non-regular refusals on the read handle, no connect through a symlink to a pipe, one-deadline timeout with reclaim, pipe-name stability and loader refusals). B1: two `pi --entwurf-control` citizens, one V3 record each, record-derived pipes alive through the product probe, `peer-facts` alive, graceful exit with both addresses dead afterwards. D1: A's model called `entwurf_v2` toward B's garden id with a nonce; A's receipt `control-socket → sent`, B's model context and B's transcript `entwurf-message` entry from A carry it. The model is an offline pi-ai `fauxProvider`: no vendor turn and no comprehension claim. The transcript fact is the harness's assertion; the raw transcript is not archived. The run used the elevated Administrators owner branch, so user-SID-only privacy is not measured. Pi's `peerDependencies` warning was observed, not waived. Not evidence for any other harness on Windows. |
50
+ | native Windows: own pre-existing pipe-name birth refusal, record link/rename syscalls, descendant cleanup after a forced kill, cross-user, mixed elevation, remote callers, user-SID-only privacy, physical user machine | unverified | No receipt; the preview cells name these as unmeasured. |
51
+ | native Windows: other harnesses, `setup` and the `.sh` bins, fresh/resume, mux/Herdr/ACP, ARM64, records over 16 MiB, reparse-backed store folders | UNSUPPORTED | Outside the preview's scope by decision; the preview loader refuses an install without the Windows addon by name. |
48
52
 
49
53
  Notes the table cannot carry without becoming prose again:
50
54
 
@@ -65,7 +69,8 @@ Notes the table cannot carry without becoming prose again:
65
69
  - **macOS evidence is split, not a support claim.** The Entwurf-only install surface is
66
70
  CERTIFIED (CI) (row above); the harness rails are NOT CERTIFIED — pending
67
71
  physical host (rental remains unscheduled). The package-level `os` field stays
68
- unrestricted, and native Windows is UNSUPPORTED.
72
+ unrestricted, and native Windows is an experimental preview (0.39.0 candidate, not
73
+ yet released; release and npm publication pending) with its own named gaps (rows above), not a supported platform.
69
74
 
70
75
  **Operator acceptance rule:** on a claimed Claude host, reinstall from the released
71
76
  artifact, restart every old Claude process, open a new session, and run the doctor
package/CHANGELOG.md CHANGED
@@ -4,6 +4,227 @@ All notable changes to this project will be documented here. Format follows [Kee
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.39.0 - 2026-10-18
8
+
9
+ ### Native Windows (experimental preview, #78)
10
+
11
+ - **A native-Windows Pi citizen, shape-only.** On Windows x64 the package carries a small
12
+ N-API addon (`native/win32-x64/entwurf_win.node` plus its `manifest.json`), loaded by
13
+ `pi-extensions/lib/win32-native.js` only after the manifest's schema, platform, arch,
14
+ file, sha256 and `napiVersion` 8 agree with the bytes (`ENTWURF_WIN32_UNSUPPORTED` /
15
+ `ENTWURF_WIN32_NATIVE_DRIFT` otherwise). It supplies the two primitives POSIX gets from
16
+ the OS: the record read refuses a final-component reparse point (`ELOOP`) and a
17
+ non-regular file (`ENTWURF_ENOTREG`) on the read handle itself, and the control
18
+ address is a named pipe `\\.\pipe\entwurf-<sha256(root)[:16]>-<gid>` whose
19
+ probe and RPC check the server's owner SID and connect at identification level. The
20
+ POSIX `O_NOFOLLOW` and ancestor policies are unchanged, and non-Windows hosts take the
21
+ existing POSIX branches of the shared modules; each axis's own CI and LIVE gates are
22
+ that evidence, not a claim of zero impact.
23
+ - **Wiring.** `meta-session`, `control-socket-path`, `socket-probe`, `socket-discovery`,
24
+ `entwurf-control-rpc`, `entwurf-control` and the bridge take the win32 branch through
25
+ that loader; pipe names come from records and are never stat-ed; nothing unlinks or
26
+ GCs a pipe on win32.
27
+ - **Carrier and provenance.** The release carrier is produced in CI (MSVC, `/Brepro`, two builds
28
+ compared byte-for-byte, imports exactly advapi32 + kernel32 with `node.exe` the only
29
+ delay-load, node-gyp 13.1.0 and the Node headers/`node.lib` pinned) and ships with
30
+ node-gyp's MIT notice (`LICENSES/node-gyp.txt`). `check-pack` reports the carrier as
31
+ `absent`, `pair-broken`, `invalid` or `present <sha>` through the loader's own verifier.
32
+ A Linux checkout pack is addon-less, so this release's candidate is the preview
33
+ workflow's pack at the prepared SHA (release skill M3w), not a local `npm pack`.
34
+ - **Measured scope.** windows-native-preview run 39123555360 @ `3b65bdc` on a GitHub
35
+ Actions `windows-2025` x64 runner, Node 24.18.1, Pi 1.3.0: six addon cells, then two
36
+ `pi --entwurf-control` citizens (one V3 record each, pipes alive through the product
37
+ probe, `peer-facts`, graceful exit with both addresses dead) and one garden-id
38
+ `entwurf_v2` delivery between them (`control-socket → sent`, B's model context and
39
+ transcript entry from A), with an offline pi-ai `fauxProvider`: no vendor turn.
40
+ - **Limits.** Experimental and shape-only; not a supported platform. UNSUPPORTED: other
41
+ harnesses, `setup` and the `.sh` bins, fresh/resume, mux/Herdr/ACP, ARM64, records over
42
+ 16 MiB, reparse-backed store folders. Unmeasured: the own pre-existing pipe-name birth
43
+ refusal, record link/rename syscalls, descendant cleanup after a forced kill,
44
+ cross-user, mixed elevation and remote callers, user-SID-only privacy (the runs took
45
+ the elevated Administrators owner branch), worker/environment teardown with a pipe
46
+ call in flight, and any physical user machine. Rows in BASELINE.
47
+
48
+ ### Verification
49
+
50
+ - **Windows workflows (#78).** `windows-native-probe.yml`, `-e1a.yml`, `-e1b.yml` and
51
+ `-preview.yml` are experimental and non-voting; their push triggers name the feature
52
+ branch, so on main they run only by dispatch. The release oracle still reads only the
53
+ four required `ci.yml` jobs. New gates `check-win32-preview` and
54
+ `check-pack-win32-carrier` (in `check:contracts`); one new mutant lane, `win32-preview`
55
+ (19 claims), and the carrier's 2 claims appended to the existing `pack-install` lane.
56
+ The committed set is now 1000 mutants across 73 lanes.
57
+ - **Experimental WSL2 workflow (#78).** `.github/workflows/wsl2-install-surface.yml`
58
+ installs this run's packed tgz inside a real WSL2 guest on a GitHub-hosted Windows runner,
59
+ runs a harness-absent `setup`, then births a zero-token Pi citizen (record, socket,
60
+ `peer-facts`; `get_state` only). It is not a release axis: the release oracle reads only
61
+ `ci.yml`, whose four required jobs are unchanged. It runs when the workflow file changes
62
+ on main, or by manual dispatch. No WSL2 support, harness rail or physical-machine claim.
63
+
64
+ ### Release boundary and verification
65
+
66
+ - **Implementation commits.** WSL2 `afcda79`..`9b492a4` (landed `86f0eb5`); native
67
+ Windows `95933c7`..`5da8f12`, landed with the carrier supply contract `32fcc1c`
68
+ (exact push CI 39128022109, four jobs green). The runtime proof stays on `3b65bdc`'s
69
+ own SHA. Coordinator-routed independent reviews (primary and supplemental) closed
70
+ every shipped implementation leaf.
71
+ - Kill-power re-measured at this shipped SHA: none (all 1000 committed claims not
72
+ re-proved, #144).
73
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head 864e026877ca surface
74
+ 200536056020 -- `["bash","run.sh","check-win32-preview"]` 19/19 receipt b4de9e681dfb
75
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head 864e026877ca surface
76
+ 200536056020 -- `["bash","run.sh","check-pack-win32-carrier"]` 2/2 receipt
77
+ c51ef11b84a6
78
+ - Earlier-candidate PARTIAL (operator-attested, not this SHA): head 3e527cdbea81 surface
79
+ 8f5cc4c34473 -- `["bash","run.sh","check-node-floor-coherence"]` 2/2 receipt
80
+ 6c0ec40a291f
81
+ - **Static/LIVE split.** Local P4/M0 `check:full` is not run on this operator host (its
82
+ FULL includes Herdr checks prohibited here) and is not counted as passed: the whole
83
+ static floor is owed to this prepared SHA's exact push CI.
84
+ - **Cut blockers.** VERIFY §3.4 (strict `doctor-meta-bridge` on an installed 0.39.0
85
+ Claude host in a new post-install session) and §3.7 (three agy doctors and a same-gid
86
+ native-push reply in a new operator conversation) need the operator and are not met by
87
+ this record; no tag or GitHub release until they are or GLG decides otherwise. npm
88
+ publication is outside the current authorization and was not performed; it needs a
89
+ separate GLG GO.
90
+ - **Herdr follows core.** `plugins/herdr/runtime-lock.json` stays on the closed
91
+ `herdr-checkout` source (comment re-dated only); no plugin manifest version, registry
92
+ pin or snapshot changes in this release.
93
+
94
+ ## 0.38.1 - 2026-10-17
95
+
96
+ ### pi-durable
97
+
98
+ - **Background compaction returns to the SDK default (#146).** The owned Harness settings
99
+ hand the SDK Pi's compaction settings unchanged instead of forcing `backgroundTokens: 0`
100
+ (#143, 0.37.0-0.38.0), so the SDK's own default background policy applies again; no
101
+ Entwurf copy of its value exists. Past the background threshold a summary runs beside the
102
+ turn, which answers without waiting for it; threshold, overflow and manual compaction are
103
+ unchanged. Transport and the other Harness mappings are unchanged.
104
+ - **Limits.** A process started from 0.37.0-0.38.0 keeps the forced 0 it imported until the
105
+ operator restarts it. The beside proof is a faux scheduling proof on a loopback wire, not
106
+ live GPT latency, billing or overload evidence; thresholds for a given window are
107
+ predictions from the SDK's policy, not measurements. `backgroundTokens` has no operator
108
+ tuning entry, and Pi's per-model compaction overrides are not applied by the app.
109
+ - **Use native capabilities; refuse what a harness lacks.** `docs/durable-native-support.md`
110
+ now states the receive-side principle with the Pi / pi-durable contrast and maps which
111
+ layer decides each Harness setting.
112
+
113
+ ### Release boundary and verification
114
+
115
+ - **Implementation commits.** `58c4d0f` (principle doc) and `9229452` (#146 source, carrier,
116
+ pin, proof, mutant, docs). Coordinator independent review B0/D0 after one amendment;
117
+ additional independent review (ee20b5) B0/D0/O4.
118
+ - **Earlier-candidate PARTIAL (not this SHA):** `["bash","run.sh","check-pi-durable-carrier"]`
119
+ 30/30 killed, control ok, on the uncommitted amendment-1 surface (worktree diff
120
+ `fc146d97`, head beb5925), receipt seal `7906a9c0d88d`; the other 949 committed claims
121
+ not run there.
122
+ - **Kill-power at this SHA: not yet measured.** The final same-SHA manual `qualify=true`
123
+ whole BODY is required before the tag; its result is recorded in the #146 ledger and the
124
+ GitHub release, not in this frozen text.
125
+ - **Static/LIVE split.** Local P4/M0 `check:full` is not run on this operator host (its FULL
126
+ includes Herdr checks prohibited here) and is not counted as passed: the whole static
127
+ floor is owed to this prepared SHA's exact push CI.
128
+ - **Earlier LIVE r1 (history of the RED SHA c90b5bf, not this release's LIVE): MUST
129
+ (non-static 23) PASS=23 FAIL=0 SKIP=0 UNRUN=0; BEHAVIOR PASS=1 FAIL=0.** It ran once on
130
+ 2026-10-17 15:11:01-15:15:47 KST through the 0.38.0 stepwise driver. Its step lines are
131
+ unchanged, and `run.sh` is unchanged since d25b71e. Candidate: 9229452 plus `CHANGELOG.md`,
132
+ `NEXT.md` and `package.json`, which still carried the 0.38.0 npm runtime lock; the
133
+ fingerprint was equal at freeze, before and after. `smoke-codex-fresh-live` ran against an
134
+ owned app-server in its own session A, with `gpt-5.6-luna` / `openai-codex/gpt-5.6-luna`,
135
+ stopped by TERM afterwards. Receipts: `.tmp-verify/release-0.38.1/live-r1/` (`live.log`
136
+ 3ce295e1, `steps.SHA256SUMS` 5a18e7da). It was not a local `release-gate --cut` PASS.
137
+ - **Herdr follows core.** `plugins/herdr/runtime-lock.json` returns to the closed
138
+ `herdr-checkout` source for this unpublished candidate, as at the 0.37.1 and 0.38.0 cuts:
139
+ an npm lock must name the checkout's own version (`certifyLockCoherence`), and 0.38.1 has
140
+ no registry bytes yet. No plugin manifest version or registry pin changes; the npm pin
141
+ returns only in a separate Herdr snapshot after the operator publishes 0.38.1.
142
+ - **First prepared SHA was RED.** `c90b5bf` kept the 0.38.0 npm lock; its push run
143
+ 39009471179 failed `check` at `check-herdr-plugin-build` `[QK:HPB-DEFAULT-LOCK-READ]`
144
+ (`runtime-lock-incoherent`), with the other three jobs green. That gate is prohibited on
145
+ this operator host, so it first showed in CI. The RED run and that SHA's LIVE r1 stay as
146
+ history.
147
+ - **Repaired candidate LIVE r2 (RED, kept as history): MUST PASS=4 FAIL=1 UNRUN=19;
148
+ BEHAVIOR not reached.** It ran 15:31:58-15:37:27 KST on c90b5bf plus `CHANGELOG.md`,
149
+ `NEXT.md`, `plugins/herdr/README.md` and `plugins/herdr/runtime-lock.json`. Step
150
+ `smoke-mux-lifecycle-live` failed. In its pi-native arm, the sibling's first model turn
151
+ (`openai-codex/gpt-6.1-sol:low`) showed "Codex error: An error occurred while processing
152
+ your request", no nonce callback came, and the gate gave up after 300 s; the claude-code
153
+ arm passed. Whether that error is transient was not measured. Receipts:
154
+ `.tmp-verify/release-0.38.1/live-r2/` (`LIVE-R2-RED.md` 96070f72).
155
+ - **Local LIVE r3, this release's LIVE: MUST (non-static 23) PASS=23 FAIL=0 SKIP=0 UNRUN=0;
156
+ BEHAVIOR PASS=1 FAIL=0.** It ran once, 15:41:19-15:45:47 KST, on the same frozen repaired
157
+ candidate as r2 (fingerprint equal at freeze, before and after), with the same driver and
158
+ models. GLG approved this single re-measurement and allowed a repeat of that one vendor
159
+ failure to be recorded and continued past; it did not repeat, so the exception and its
160
+ continuation driver were not used. Each LIVE ran against a fresh owned app-server in its
161
+ own session A, stopped by TERM afterwards. Receipts: `.tmp-verify/release-0.38.1/live-r3/`
162
+ (`live.log` 929b7952, `steps.SHA256SUMS` 99f87db2). It was not a local
163
+ `release-gate --cut` PASS: the static step did not run locally.
164
+ - **Post-LIVE delta: evidence records only.** After r3, `CHANGELOG.md` and `NEXT.md` took
165
+ these records; `package.json`, the lockfile, `run.sh`, `plugins/herdr/` and all
166
+ source/carrier bytes equal the r3 candidate. The repaired prepared SHA's exact push CI is
167
+ owed.
168
+
169
+ ## v2026.10.16-herdr.0.38.0 — Herdr Entwurf plugin 0.38.0
170
+
171
+ A separate Herdr plugin snapshot, not another core release: core package 0.38.0 and its
172
+ `v0.38.0` tag remain unchanged. The operator has published the core to npm.
173
+
174
+ ### Herdr plugin
175
+
176
+ - **Re-pin the published core 0.38.0 and close the checkout window.**
177
+ `plugins/herdr/runtime-lock.json` returns from the `herdr-checkout` carrier that core 0.38.0
178
+ rode to production npm source `@junghanacs/entwurf@0.38.0` with the registry's measured
179
+ sha512 (`sha512-A7Pd4FOe…aqbA==`). The plugin manifest moves 0.37.0 → 0.38.0; admission
180
+ remains Herdr >= 0.9.0 and the one-build/one-pane/Linux shape is unchanged.
181
+ - **Pi 1.3 first, then the plugin.** The runtime this snapshot acquires carries core 0.38.0, whose
182
+ accepted Pi range is `>=1.3.0 <1.4` (#145). A host still on Pi 1.2 is refused by name
183
+ (`outside the supported range`) at plugin activation, the same check `entwurf setup` applies,
184
+ and no Pi settings are written. Upgrade Pi to 1.3, then install or reinstall the plugin. The
185
+ plugin README's Requirements say so.
186
+ - **Upgrade by acquisition source, not just version.** A settled npm-source 0.37.0 installation
187
+ can rebind under the plugin README's ownership conditions. A host that installed an
188
+ intervening `herdr-checkout` candidate must explicitly deactivate by its own absolute runtime
189
+ path before reinstalling the npm-source snapshot. No operator installation or active session
190
+ is rewired by this release work.
191
+
192
+ ### Source-specific verification
193
+
194
+ - Registry metadata reports version/latest 0.38.0, repair 0.12.8-repair.1, 599 files. The
195
+ registry's `dist.integrity` and `dist.shasum` equal the sha512 and sha1 independently
196
+ computed from the preserved accepted core candidate (SHA256
197
+ `6d21699b1a37a8441c9631bd9128f413aecd37146dfb091cc768eb7a69ea7438`, 11144202 bytes), so
198
+ core make's Docker consumer of those bytes (`node:24-bookworm`, repoDigest
199
+ `node@sha256:6dac556d…`) is reused here, not repeated. A sandboxed `pi install` of the
200
+ registry package resolved its bridge inside the sandbox, recorded the registry URL with that
201
+ integrity, and listed the curated Claude anchors with `--list-models entwurf`.
202
+ - **The npm runtime swap against the real registry**, by the registry-shipped helper (byte-equal
203
+ to this checkout's) in sandboxed user roots, 22/22 checks. The 0.37.0 runtime was seeded from
204
+ its own committed lock; the swap to 0.38.0 took one acquisition, the cached tarball's sha512
205
+ equals the lock and its sha256 equals both the ready journal's observed digest and the
206
+ accepted candidate. The `installing` journal carried the 0.37.0 identity as
207
+ `previousRuntime` and the `runtime-ready` journal carries `null`; when acquisition began the
208
+ 0.37.0 tree was still at the active address. A manually seeded torn swap (the active tree
209
+ moved to the backup address with a half candidate in staging; not a killed process) was
210
+ restored before acquisition and reported `recovered: torn-swap-with-candidate`. An exact
211
+ re-request acquired nothing and left journal and tree byte-identical, and the owned removal
212
+ reclaimed cache, runtime and journal. The helper is a leaf that probes no Pi, so these cells
213
+ certify neither a Pi upgrade nor Herdr's install/update relocation, harness wiring, the
214
+ installed CLI's deactivate path, a model turn, or recovery from a real kill.
215
+ - **Focused gates on the branch**, with HEAD, index and work surface unchanged across them:
216
+ `check-herdr-supply` (11 assertions), `check-herdr-plugin` (49),
217
+ `check-herdr-plugin-profile` (14), `check-herdr-runtime-bootstrap` (31),
218
+ `check-herdr-activation` (33), `check-herdr-placement` (25), `check-herdr-fresh-call` (45)
219
+ and `check-gate-manifests` (979 committed mutants across 72 lanes validated, none executed).
220
+ `check-herdr-plugin-build` and `check-herdr-sandbox` were not run locally because they run
221
+ the operator's own herdr; they ride the pushed SHA's FULL floor in CI.
222
+ - Pending, and not claimed here: the pushed SHA's four-job CI and the public-ref
223
+ install/use/deactivate journey on Herdr 0.9.5 with Pi 1.3.0. Earlier npm or checkout-carrier
224
+ receipts cannot certify these source-specific axes. This section is the prepared source
225
+ record; the actual post-push, public-ref and CI outcomes are recorded at the snapshot's
226
+ [acceptance ledger](https://github.com/junghan0611/entwurf/issues/145#issuecomment-6209375125).
227
+
7
228
  ## 0.38.0 - 2026-10-16
8
229
 
9
230
  ### Pi SDK 1.3.0
@@ -0,0 +1,24 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2012 Nathan Rajlich <nathan@tootallnate.net>
4
+
5
+ Permission is hereby granted, free of charge, to any person
6
+ obtaining a copy of this software and associated documentation
7
+ files (the "Software"), to deal in the Software without
8
+ restriction, including without limitation the rights to use,
9
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the
11
+ Software is furnished to do so, subject to the following
12
+ conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24
+ OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -78,8 +78,20 @@ Plain MCP registration alone does not make a garden citizen.
78
78
  installation is **CERTIFIED (CI)**, weaker than a physical-host doctor; its native
79
79
  harness rails, ACP and mux remain **NOT CERTIFIED — pending physical host**.
80
80
  Detected harness wiring on Darwin can therefore be written while setup stays
81
- non-green. Native Windows is **UNSUPPORTED**. The package has no npm `os`
82
- restriction; that is not certification of every platform's harness rails.
81
+ non-green. WSL2: a CI runner (experimental) measured the Entwurf-only install and a
82
+ zero-token Pi citizen; general, physical-machine or harness-parity WSL2 support is **not
83
+ declared**, and delivery, other harnesses and physical machines are unmeasured. Native
84
+ Windows has an **experimental first preview**, a CI-built x64 addon on `main` for the
85
+ 0.39.0 candidate (not yet released; release and npm publication pending); the registry's
86
+ 0.38.1 has no Windows addon. On a GitHub Actions `windows-2025` x64 runner with Node
87
+ 24.18.1 and Pi 1.3.0, a CI-built artifact was installed, wired with `pi install`, birthed
88
+ two Pi citizens and
89
+ delivered one garden-id `entwurf_v2` message between them, with an offline scripted model
90
+ and no vendor turn. Every other native-Windows surface (other harnesses, `setup` and the
91
+ `.sh` bins, fresh/resume, mux/Herdr/ACP, physical machines) is **not declared**; the
92
+ measured scope and its named gaps are in [BASELINE](./BASELINE.md#release-host-baseline).
93
+ The package has no npm `os` restriction; that is not certification of every platform's
94
+ harness rails.
83
95
 
84
96
  ### Native durable (pi-durable)
85
97
 
package/VERIFY.md CHANGED
@@ -233,8 +233,8 @@ are the visible-terminal and consumer subjects. The 1.2 bootstrap child also obs
233
233
  reopening the chosen session and cancelling to nothing (#143). Two more sandboxed children reach the
234
234
  reference app's settings and prompt mapping: one drives the real bootstrap against a loopback
235
235
  WebSocket Responses endpoint, whose received frames decide the forwarded transport and its
236
- continuation and the reference compaction policy (no background summary; blocking and overflow
237
- kept, #143); the other renders the carrier prompt's skills section with no model, against a fixture
236
+ continuation and the reference compaction policy (the SDK's background summary beside the turn, held on the
237
+ wire while the turn answers; blocking and overflow kept, #146); the other renders the carrier prompt's skills section with no model, against a fixture
238
238
  of both settings scopes, an absolute entry, a same-name pair and a wrong-base decoy.
239
239
  `check-pi-runtime-version` asks the
240
240
  same installed verifier (`check-pi-durable-beside`) whether this checkout resolves exactly the pinned SDK set (the native set; the PATH
@@ -276,14 +276,16 @@ caller/target/cross-harness LIVE are owed by `smoke-pi-durable-fresh-live`, a re
276
276
  | Linux artifact consumer | required `check-install-container` CI job against one preserved candidate | Fixtures prove package/oracle shape, not a real Claude lifecycle. |
277
277
  | Exact release commit | the exact SHA's push run: all four required CI jobs green (the qualification body is not read, #144) | A different green SHA is not transferable evidence. |
278
278
  | macOS Entwurf-only install | required `macos-install-surface` CI job at the exact release SHA | Shape-only, CERTIFIED (CI): certifies the Entwurf-only install path on macOS only — never Claude/Copilot/OMP/agy wiring, the pi rail, ACP or mux. Promoted from non-voting (`continue-on-error`) to required in 0.20.0 after its first CERTIFIED (CI) green on a GitHub Actions `macos-latest` runner (not a physical host): run 34303884286 @ `70eda03` (2026-09-09), 65s, all 10 steps success — the packed candidate installed clean, `entwurf --help` and `entwurf check-bridge` ran (the latter listing the seven `entwurf-bridge` MCP tools of that date; eight today), an all-harness-absent `entwurf setup` reached `result: green` across the five documented SKIP probes (`pi`/`claude`/`agy`/`copilot`/`omp`), and five checked paths (`.pi`, `settings.json`, `.gemini`, `.copilot`, `auth.json.bak`) plus a byte-identical `auth.json` proved zero harness/credential writes. A macOS runner failure now blocks every release cut, the same as any other required job. |
279
+ | Native-Windows carrier (carrier-bearing release, from 0.39.0) | one `windows-native-preview.yml` dispatch at the exact prepared SHA after its four `ci.yml` jobs are green: accepted build receipt (two byte-identical MSVC builds, exact import set), `pack` printing `present`, eight Windows addon/citizen cells on the installed tarball; that run's `win32-preview-tgz` is the release candidate, retrieved (never packed locally) with its `SHA256SUMS`, the Windows-printed install hash and the downloaded hash equal, then accepted by the Linux `check-install-container` unchanged (release skill M3w) | Experimental, shape-only, non-voting for `ci.yml`: no vendor turn, x64 runner only, named gaps in BASELINE. The four `ci.yml` jobs (incl. macOS) saw an addon-less pack, so the carrier-bearing tarball's macOS install is unmeasured. Artifacts expire after 14 days; the preserved copy is the durable one. |
279
280
  | LIVE runtime | `LIVE=1 ./run.sh release-gate <scratch> --cut` plus any shipped on-demand backend axis | `--cut` enforces `SKIP=0`; a red wired gate blocks the cut. |
280
281
  | Native Claude host | installed strict doctor against a new real session | Missing live join is `NOT CERTIFIED`, not a fixture PASS. |
281
282
  | Native agy host | three doctors plus conversation-id-gated native-push round trip | Aggregate release-gate does not own an agy conversation id. |
282
283
  | Native Codex host | caller-seat topology accepted 2026-09-16 (56 assertions, exit 0); caller-DIRECTORY axis accepted the same day (65 assertions, exit 0) | Operator-owned birth/MCP/statusline/terminal-title atoms and the on-demand four-coordinate chain are measured on Linux at Codex 0.153.4: the app-server alone in session A (`$30/@41/%45`, pid 737636), and initial Pi / omitted-placement fresh Codex / Codex-opened outbound Pi all in a different session S (`$2`, `@46`/`@47`/`@48`; `@58`/`@59`/`@61` in the lane C run); exact callbacks and addressed delivery both ways. A ≠ S is the load-bearing half — the app-server's inherited `TMUX` names A, so the outbound Pi landing in S can only have come from the caller's own pane title, and its receipt records `seat-source=codex-title-anchor`. **#95 lane C added the second axis to the same card: WHERE the sibling starts.** Hop 1 joins four authorities that cannot borrow from each other — the pane's `#{pane_current_path}`, the vendor rollout's `session_meta.cwd`, the Entwurf record and the requested scratch — and hop 1's leg requests a cwd while hop 2 requests NONE, so hop 2's directory can only come from the Codex caller's own record; both are asserted different from the app-server's live `/proc/<pid>/cwd`. 65 and 56 are different contracts, not a regression. The 2026-09-12 fixed-home chain is PAST CONTRACT (#95 D1); `DELIVERY.md` owns every receipt and digest. Placement beside a TUI whose thread nobody named is unsupported and unclaimed. |
283
284
 
284
285
  The repo-local `entwurf-release` skill owns the `land → prepare → make → publish`
285
- state machine. Each mode is a separate GLG authorization. Preserve one candidate,
286
- accept and publish those exact bytes without repacking, and record its digest and
286
+ state machine. Each mode is a separate GLG authorization. Preserve one candidate
287
+ (for a carrier-bearing release, the one retrieved from the preview run, never a local
288
+ pack), accept and publish those exact bytes without repacking, and record its digest and
287
289
  consumer image outside this standing protocol.
288
290
 
289
291
  ### Verifying the two capabilities a gate cannot fully judge
@@ -385,6 +387,13 @@ not certified, a detected harness is named non-green (FAIL) whose wording is
385
387
  not an install failure: the wiring WAS written, but the rail is NOT CERTIFIED —
386
388
  pending physical host. That FAIL is the honest setup verdict, not a skip.
387
389
 
390
+ WSL2: a separate experimental workflow (`.github/workflows/wsl2-install-surface.yml`)
391
+ measures the Entwurf-only install and a zero-token Pi citizen inside a real WSL2 guest on
392
+ a GitHub-hosted Windows runner. The release oracle does not read it
393
+ (`verify-exact-ci.sh` selects only `ci.yml` runs), so it is not a release axis and a red
394
+ there blocks no cut. It runs when that workflow file changes on main, or by manual
395
+ dispatch. Recorded in [BASELINE.md](./BASELINE.md).
396
+
388
397
  ### 1.1 Variables (optional)
389
398
 
390
399
  ```bash
@@ -518,7 +527,7 @@ Pass: user/assistant turns accumulate normally; the transcript is not broken/emp
518
527
  The minimum passing bar:
519
528
 
520
529
  1. **Deterministic floor green:** `pnpm run check:full` passes (the core tier plus the hermetic-integration and package/install tiers, incl. `check-pack`), in the exact-SHA CI push run of the same prepared candidate (criterion 2's run). The qualification body is not a criterion (#144).
521
- 2. **All four required CI jobs green on the exact release commit:** `check`, `install-surface`, the required Linux `artifact-consumer`, and `macos-install-surface` — promoted from non-voting to required in 0.20.0 (see the release acceptance axes table above for what its green run does and does not certify); preserve the Linux artifact-consumer's tarball digest and image identity. A macOS runner failure now blocks the cut like any other required job.
530
+ 2. **All four required CI jobs green on the exact release commit:** `check`, `install-surface`, the required Linux `artifact-consumer`, and `macos-install-surface` — promoted from non-voting to required in 0.20.0 (see the release acceptance axes table above for what its green run does and does not certify); preserve the Linux artifact-consumer's tarball digest and image identity. For a carrier-bearing release those CI tarballs are addon-less and are NOT the release candidate: the exact release bytes are the M3w-retrieved preview tarball, accepted separately by the eight Windows cells and the Linux container (axes row "Native-Windows carrier"). The four-job floor, macOS included, is unchanged. A macOS runner failure now blocks the cut like any other required job.
522
531
  3. **Live floor MUST green:** `LIVE=1 ./run.sh release-gate <dir> --cut` exits 0 reporting `MUST PASS=N FAIL=0 SKIP=0`; with `--cut` a single SKIP is red, so the exit code itself now carries this condition. A BEHAVIOR FAIL is advisory, not blocking.
523
532
  4. **Native-host doctor green where the Claude meta-bridge is claimed:** a new post-install Claude session exists, live evidence is present, and the installed `doctor-meta-bridge` exits 0. `NOT CERTIFIED` is a release failure for that host, not a skip.
524
533
  5. **Honest self-recognition:** the bridged model identifies its actual harness/backend, lists `entwurf-bridge` as the single MCP server with its eight current tools, and presents a backend-native (not normalized) tool surface.
@@ -18,7 +18,9 @@ evidence split, and its three states must not collapse into "macOS is supported"
18
18
  ACP turn, mux. Wiring may be written; that is not a rail receipt. The remaining
19
19
  unmeasured Darwin doctor reason is per-process environment DISCOVERY
20
20
  (start-key is portable).
21
- - **UNSUPPORTED** — native Windows only. macOS is never this.
21
+ - **UNSUPPORTED** — native Windows `setup` and its harness rails. macOS is never this.
22
+ An experimental native-Windows Pi preview (0.39.0 candidate, not yet released; release
23
+ and npm publication pending) exists outside `setup`; see [BASELINE](../BASELINE.md#release-host-baseline).
22
24
 
23
25
  ## Requirements
24
26
 
@@ -84,6 +84,7 @@ import { probeNativeSenderAlive, reconcileSenderIdentityClaims, resolveCodexRequ
84
84
  import { applyOmpBridgeChildRootPolicy, defaultMetaMailboxDir, defaultMetaSessionsDir, makeStoreRecordReader, readActiveStoreEntries, readMetaIdentityByGardenId, readMetaInbox, readMetaReceiverMarker, readMetaSenderMarker, requireBackend, } from "../../../pi-extensions/lib/meta-session.js";
85
85
  import { RESUME_CALL_REJECT_HINT, resumeCall } from "../../../pi-extensions/lib/mux-resume-call.js";
86
86
  import { registerNativeConversation } from "../../../pi-extensions/lib/native-push/register.js";
87
+ import { probeSocketLiveness } from "../../../pi-extensions/lib/socket-probe.js";
87
88
  const HOME = os.homedir();
88
89
  // ============================================================================
89
90
  // OMP root policy — FIRST, before any lazy default-root consumer (#87 B1).
@@ -110,6 +111,14 @@ catch (err) {
110
111
  // Directory SOURCE is this adapter's own policy — the bridge honours an explicit
111
112
  // ENTWURF_DIR override the pi side does not. The path GRAMMAR is the shared leaf.
112
113
  const ENTWURF_DIR = process.env.ENTWURF_DIR ?? defaultControlSocketDir(HOME);
114
+ /** Is this session's own control address up? A socket file's presence on Linux/macOS. On
115
+ * Windows (#78) the address is a named pipe, which has no file to look at and must not be
116
+ * opened by a stat (that connects): the owner-validated probe answers instead. */
117
+ async function controlAddressPresent(socketPath) {
118
+ if (process.platform === "win32")
119
+ return (await probeSocketLiveness(socketPath)) === "alive";
120
+ return existsSync(socketPath);
121
+ }
113
122
  // ============================================================================
114
123
  // Live control-socket discovery for entwurf_peers lives in the TS fact-provider
115
124
  // (pi-extensions/lib/entwurf-fact-provider.ts → listEntwurfFacts), which the
@@ -171,7 +180,7 @@ class EntwurfSenderIdentityError extends Error {
171
180
  "the send is then marked external/non-replyable).");
172
181
  }
173
182
  }
174
- function buildStrictPiSenderEnvelope() {
183
+ async function buildStrictPiSenderEnvelope() {
175
184
  const sessionId = process.env.PI_SESSION_ID?.trim();
176
185
  const agentId = process.env.PI_AGENT_ID?.trim();
177
186
  const cwd = process.cwd();
@@ -195,7 +204,7 @@ function buildStrictPiSenderEnvelope() {
195
204
  const socketPath = controlSocketPathIn(ENTWURF_DIR, sessionId);
196
205
  const self = computeSelfAddressability({
197
206
  origin: "pi-session",
198
- socketAlive: existsSync(socketPath),
207
+ socketAlive: await controlAddressPresent(socketPath),
199
208
  socketPathComputable: true,
200
209
  });
201
210
  return {
@@ -258,7 +267,7 @@ async function buildMetaSenderEnvelope(identity, cwd, trustedMarker) {
258
267
  async function resolveAuthoritativeSender(context, cwd = process.cwd()) {
259
268
  const sessionId = process.env.PI_SESSION_ID?.trim();
260
269
  const agentId = process.env.PI_AGENT_ID?.trim();
261
- const pi = sessionId && agentId && cwd ? buildStrictPiSenderEnvelope() : null;
270
+ const pi = sessionId && agentId && cwd ? await buildStrictPiSenderEnvelope() : null;
262
271
  const marker = resolveTrustedMetaSenderIdentity({
263
272
  markerPath: process.env.ENTWURF_META_SENDER_MARKER?.trim() || undefined,
264
273
  });
@@ -463,7 +472,7 @@ server.tool("entwurf_self", "Return this caller's authoritative identity envelop
463
472
  // live socket). The old code synthesized the path and printed it as if
464
473
  // it existed — a lie when the session has no --entwurf-control (SE-1).
465
474
  const socketPath = controlSocketPathIn(ENTWURF_DIR, sender.sessionId);
466
- const socketState = existsSync(socketPath) ? "alive" : "expected";
475
+ const socketState = (await controlAddressPresent(socketPath)) ? "alive" : "expected";
467
476
  extra.socketPath = socketPath;
468
477
  extra.socketState = socketState;
469
478
  lines.push(socketState === "alive"
@@ -24,8 +24,17 @@
24
24
  * adapter keeps its own root policy: the pi side derives from HOME, the MCP
25
25
  * bridge honours `ENTWURF_DIR`. What is unified is the grammar, not the policy.
26
26
  *
27
- * Keep dependency-free except `node:path`.
27
+ * Windows (#78) has no socket files: the address is a named pipe,
28
+ * `\\.\pipe\entwurf-<sha256(canonRoot(dir))[:16]>-<gardenId>`. The digest keeps two
29
+ * store roots on one host apart without putting a path in the pipe namespace; it is not
30
+ * a new address axis (still the garden id plus the store root). The inverse grammar has
31
+ * no Windows half because nothing scans the pipe namespace: candidates come from records.
32
+ *
33
+ * Keep dependency-free except node builtins (`node:path`, and `node:crypto`/`node:fs`
34
+ * for the Windows root canonicalisation only).
28
35
  */
36
+ import { createHash } from "node:crypto";
37
+ import * as fs from "node:fs";
29
38
  import * as path from "node:path";
30
39
  /** Control-socket filename suffix. The stem is a bare garden id. */
31
40
  export const CONTROL_SOCKET_SUFFIX = ".sock";
@@ -47,8 +56,54 @@ export function defaultControlSocketDir(home) {
47
56
  * @returns {string}
48
57
  */
49
58
  export function controlSocketPathIn(dir, gardenId) {
59
+ if (process.platform === "win32")
60
+ return win32PipeNameFor(dir, gardenId);
50
61
  return path.join(dir, `${gardenId}${CONTROL_SOCKET_SUFFIX}`);
51
62
  }
63
+ /** Every Windows control pipe this package names starts here. */
64
+ export const WIN32_PIPE_PREFIX = "\\\\.\\pipe\\entwurf-";
65
+ /**
66
+ * The one spelling of a Windows store root, so Pi A, Pi B, the bridge and peer-facts
67
+ * derive the same pipe name from differently written paths. The nearest EXISTING ancestor
68
+ * of `path.win32.resolve(dir)` goes through `realpath` (8.3 names expanded, case from the
69
+ * filesystem); the components below it are appended as written. Separators become `\`,
70
+ * there is no trailing separator, and the whole string is lower-cased. Nothing is created
71
+ * or touched, so the control dir need not exist yet. A failure other than ENOENT throws.
72
+ *
73
+ * @param {string} dir
74
+ * @param {(p: string) => string} [realpath]
75
+ * @returns {string}
76
+ */
77
+ export function canonRootWin32(dir, realpath = fs.realpathSync.native) {
78
+ let head = path.win32.resolve(dir);
79
+ /** @type {string[]} */
80
+ const tail = [];
81
+ for (;;) {
82
+ try {
83
+ const joined = path.win32.join(realpath(head), ...tail);
84
+ return joined.replace(/[\\/]+$/, "").toLowerCase();
85
+ }
86
+ catch (err) {
87
+ const parent = path.win32.dirname(head);
88
+ if ( /** @type {{ code?: unknown }} */(err).code !== "ENOENT" || parent === head)
89
+ throw err;
90
+ tail.unshift(path.win32.basename(head));
91
+ head = parent;
92
+ }
93
+ }
94
+ }
95
+ /**
96
+ * Forward grammar on Windows: `\\.\pipe\entwurf-<16 hex>-<gardenId>`.
97
+ *
98
+ * @param {string} dir
99
+ * @param {string} gardenId
100
+ * @param {(p: string) => string} [realpath]
101
+ * @returns {string}
102
+ */
103
+ export function win32PipeNameFor(dir, gardenId, realpath) {
104
+ const digest = createHash("sha256").update(canonRootWin32(dir, realpath), "utf8").digest("hex").slice(0, 16);
105
+ return `${WIN32_PIPE_PREFIX}${digest}-${gardenId}`;
106
+ }
52
107
  /**
53
108
  * Inverse grammar: a socket filename back to its garden id, or `null` when the
54
109
  * name does not carry the suffix.
@@ -14,6 +14,7 @@
14
14
  * asked to open, and touches nothing pi-specific — it is a pure transport over a path.
15
15
  */
16
16
  import * as net from "node:net";
17
+ import { win32Native } from "./win32-native.js";
17
18
  /**
18
19
  * THE `<sender_info>` synthesis — the one place the sender envelope becomes
19
20
  * message text. ONE consumer since the visible-first cut (#50 C3):
@@ -101,6 +102,8 @@ export async function fetchControlSocketRuntimeInfo(socketPath, options = {}) {
101
102
  }
102
103
  export async function sendRpcCommand(socketPath, command, options = {}) {
103
104
  const { timeout = 5000 } = options;
105
+ if (process.platform === "win32")
106
+ return await sendPipeRpcCommand(socketPath, command, timeout);
104
107
  return new Promise((resolve, reject) => {
105
108
  const socket = net.createConnection(socketPath);
106
109
  socket.setEncoding("utf8");
@@ -173,3 +176,25 @@ export async function sendRpcCommand(socketPath, command, options = {}) {
173
176
  });
174
177
  });
175
178
  }
179
+ /**
180
+ * The Windows client (#78). The addon makes one owner-validated connect, writes the request
181
+ * line and returns the first response line, all inside one deadline; a timeout is
182
+ * `ETIMEDOUT`, a pipe owned by anyone but this token's expected owner is `EPERM` before any
183
+ * byte. The protocol is request-bound on its own connection, so that first line IS the
184
+ * answer: anything other than this command's response is refused, never waited past.
185
+ */
186
+ async function sendPipeRpcCommand(pipeName, command, timeout) {
187
+ const { responseLine } = await win32Native().pipeRpc(pipeName, JSON.stringify(command), Math.max(0, Math.floor(timeout)));
188
+ let msg;
189
+ try {
190
+ msg = JSON.parse(responseLine.trim());
191
+ }
192
+ catch {
193
+ throw new Error(`control pipe answered ${command.type} with a line that is not JSON`);
194
+ }
195
+ const response = msg;
196
+ if (response?.type !== "response" || response.command !== command.type) {
197
+ throw new Error(`control pipe answered something other than the ${command.type} response`);
198
+ }
199
+ return { response };
200
+ }
@@ -52,6 +52,7 @@
52
52
  import { execFileSync } from "node:child_process";
53
53
  import * as crypto from "node:crypto";
54
54
  import * as fs from "node:fs";
55
+ import { createRequire } from "node:module";
55
56
  import * as os from "node:os";
56
57
  import * as path from "node:path";
57
58
  import { generateSessionId, SESSION_ID_RE } from "./session-id.js";
@@ -845,6 +846,8 @@ const O_NONBLOCK_FLAG = typeof fs.constants.O_NONBLOCK === "number" ? fs.constan
845
846
  * Shaping a message is the job of the caller that knows what the answer is FOR.
846
847
  */
847
848
  export function readStoreRecordFile(file) {
849
+ if (process.platform === "win32")
850
+ return win32RecordReader()(file).toString("utf8");
848
851
  if (O_NOFOLLOW_FLAG === 0) {
849
852
  throw new MetaRecordError(`cannot read meta-record ${path.basename(file)} safely: this platform exposes no O_NOFOLLOW, so a record ` +
850
853
  "read cannot refuse a symlink swapped in at the final component. entwurf certifies Linux only.");
@@ -863,6 +866,17 @@ export function readStoreRecordFile(file) {
863
866
  fs.closeSync(fd);
864
867
  }
865
868
  }
869
+ /**
870
+ * Windows has no `O_NOFOLLOW` (libuv defines it as 0), so the same three decisions — a
871
+ * final-component reparse point refused as `ELOOP`, a non-regular entry as
872
+ * `ENTWURF_ENOTREG`, both on the handle the read uses — are made by the preview addon
873
+ * (#78). It is reached only here and only on win32, through `createRequire`, so the
874
+ * installers that copy this file without its siblings never load it on Linux or macOS.
875
+ */
876
+ function win32RecordReader() {
877
+ const native = createRequire(import.meta.url)("./win32-native.js");
878
+ return native.win32ReadNoFollow;
879
+ }
866
880
  /** The reader every store-WIDE scan pairs with {@link readActiveStoreEntries}. The
867
881
  * caller's entry kind decides the POLICY — whether a non-regular entry is a defect
868
882
  * (certification), a diagnostic (listing) or a non-candidate (the rival scan) — because