@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.
- package/BASELINE.md +9 -4
- package/CHANGELOG.md +221 -0
- package/LICENSES/node-gyp.txt +24 -0
- package/README.md +14 -2
- package/VERIFY.md +14 -5
- package/docs/setup-clean-host.md +3 -1
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +13 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-socket-path.js +56 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +25 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +14 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +14 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-probe.js +21 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/win32-native.js +182 -0
- package/mcp/entwurf-bridge/src/index.ts +13 -4
- package/native/win32-x64/entwurf_win.node +0 -0
- package/native/win32-x64/manifest.json +45 -0
- package/package.json +4 -2
- package/pi/pi-durable/carrier/harness-setup.js +3 -3
- package/pi/pi-durable/overlay/upstream-pin.json +2 -2
- package/pi-extensions/entwurf-control.ts +6 -1
- package/pi-extensions/lib/control-socket-path.js +56 -1
- package/pi-extensions/lib/entwurf-control-rpc.ts +32 -0
- package/pi-extensions/lib/meta-session.ts +14 -0
- package/pi-extensions/lib/socket-discovery.ts +12 -1
- package/pi-extensions/lib/socket-probe.ts +20 -0
- package/pi-extensions/lib/win32-native.js +198 -0
- package/run.sh +105 -1
- package/scripts/check-entwurf-self-address.ts +15 -4
- package/scripts/check-gate-qualification.ts +2 -1
- package/scripts/mutants/pack-install.json +28 -2
- package/scripts/mutants/pi-durable.json +5 -5
- 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
|
|
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 |
|
|
47
|
-
|
|
|
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
|
|
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.
|
|
82
|
-
|
|
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 (
|
|
237
|
-
kept, #
|
|
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
|
-
|
|
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.
|
package/docs/setup-clean-host.md
CHANGED
|
@@ -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
|
|
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:
|
|
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 =
|
|
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
|
-
*
|
|
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
|