@junghanacs/entwurf 0.38.1 → 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 +87 -0
- package/LICENSES/node-gyp.txt +24 -0
- package/README.md +14 -2
- package/VERIFY.md +12 -3
- 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-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/win32-preview.json +215 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* win32-native — the Windows preview's native surface (#78), and the only way to reach it.
|
|
3
|
+
*
|
|
4
|
+
* Node cannot open a file without following a final-component reparse point on Windows
|
|
5
|
+
* (libuv `UV_FS_O_NOFOLLOW` is 0), and it cannot check who owns a named pipe before
|
|
6
|
+
* talking to it. Those two capabilities come from one Entwurf-owned N-API addon,
|
|
7
|
+
* `native/win32-x64/entwurf_win.node`, built in CI from `native/win32/entwurf_win.c` and
|
|
8
|
+
* shipped beside `native/win32-x64/manifest.json`. This module verifies the manifest and
|
|
9
|
+
* the addon bytes BEFORE `process.dlopen`, and refuses by name otherwise:
|
|
10
|
+
*
|
|
11
|
+
* ENTWURF_WIN32_UNSUPPORTED not win32-x64, no addon in this install (an ordinary
|
|
12
|
+
* Linux pack), a Node without the N-API floor, or a
|
|
13
|
+
* verified addon this host cannot load
|
|
14
|
+
* ENTWURF_WIN32_NATIVE_DRIFT the manifest is malformed, or the addon bytes are not the
|
|
15
|
+
* bytes the manifest names
|
|
16
|
+
*
|
|
17
|
+
* There is no repair, no download and no fallback: a refusal is the whole answer.
|
|
18
|
+
*
|
|
19
|
+
* Authored as `.js` for the reason `control-socket-path.js` is: it is imported from the
|
|
20
|
+
* tsc-emit root program and from the strip-types lanes alike. `meta-session.ts` reaches it
|
|
21
|
+
* only through a win32-only `createRequire`, so the installers that copy `meta-session.ts`
|
|
22
|
+
* on their own are unchanged on Linux and macOS. Keep dependency-free except node builtins.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { createHash } from "node:crypto";
|
|
26
|
+
import * as fs from "node:fs";
|
|
27
|
+
import * as path from "node:path";
|
|
28
|
+
import { fileURLToPath } from "node:url";
|
|
29
|
+
|
|
30
|
+
export const WIN32_UNSUPPORTED_CODE = "ENTWURF_WIN32_UNSUPPORTED";
|
|
31
|
+
export const WIN32_DRIFT_CODE = "ENTWURF_WIN32_NATIVE_DRIFT";
|
|
32
|
+
|
|
33
|
+
const PACKAGE_NAME = "@junghanacs/entwurf";
|
|
34
|
+
const NATIVE_DIR = path.join("native", "win32-x64");
|
|
35
|
+
const ADDON_FILE = "entwurf_win.node";
|
|
36
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
37
|
+
// The addon is built against exactly this N-API version (native/win32/binding.gyp, build.mjs). A
|
|
38
|
+
// manifest that declares any other value describes some other build: drift, not a floor to honour.
|
|
39
|
+
const ADDON_NAPI_VERSION = 8;
|
|
40
|
+
// One addon object per process, even if two module instances of this file exist (a
|
|
41
|
+
// transpiling loader and Node's own loader can each evaluate it once).
|
|
42
|
+
const SLOT = Symbol.for("entwurf.win32-native.addon");
|
|
43
|
+
// DOS device names open a device, not a file, at any directory. The addon's GetFileType
|
|
44
|
+
// check refuses a non-disk handle anyway; this keeps the open from ever being attempted.
|
|
45
|
+
const DOS_DEVICE = /^(con|prn|aux|nul|com[0-9¹²³]|lpt[0-9¹²³]|conin\$|conout\$)$/i;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} code
|
|
49
|
+
* @param {string} message
|
|
50
|
+
* @param {unknown} [cause]
|
|
51
|
+
* @returns {Error & { code: string }}
|
|
52
|
+
*/
|
|
53
|
+
function refusal(code, message, cause) {
|
|
54
|
+
const err = /** @type {Error & { code: string }} */ (new Error(message, cause === undefined ? undefined : { cause }));
|
|
55
|
+
err.code = code;
|
|
56
|
+
return err;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The package root, found from where THIS module lives: the source depth
|
|
61
|
+
* (`<root>/pi-extensions/lib/`) first, then the compiled depth
|
|
62
|
+
* (`<root>/mcp/entwurf-bridge/dist/pi-extensions/lib/`). The root is the directory whose
|
|
63
|
+
* `package.json` names this package; `null` when neither depth does.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} [from]
|
|
66
|
+
* @returns {string | null}
|
|
67
|
+
*/
|
|
68
|
+
export function win32NativePackageRoot(from = path.dirname(fileURLToPath(import.meta.url))) {
|
|
69
|
+
for (const up of [
|
|
70
|
+
["..", ".."],
|
|
71
|
+
["..", "..", "..", "..", ".."],
|
|
72
|
+
]) {
|
|
73
|
+
const root = path.resolve(from, ...up);
|
|
74
|
+
let name;
|
|
75
|
+
try {
|
|
76
|
+
name = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"))?.name;
|
|
77
|
+
} catch {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (name === PACKAGE_NAME) return root;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Verify `<root>/native/win32-x64/` and return the addon path to load. Pure apart from
|
|
87
|
+
* reading the two files, so the refusals are provable on any host.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} root
|
|
90
|
+
* @param {{ napi?: string | undefined }} [versions]
|
|
91
|
+
* @returns {{ addonPath: string, sha256: string, napiVersion: number }}
|
|
92
|
+
*/
|
|
93
|
+
export function verifyWin32NativeAt(root, versions = process.versions) {
|
|
94
|
+
const dir = path.join(root, NATIVE_DIR);
|
|
95
|
+
const manifestPath = path.join(dir, "manifest.json");
|
|
96
|
+
let raw;
|
|
97
|
+
try {
|
|
98
|
+
raw = fs.readFileSync(manifestPath, "utf8");
|
|
99
|
+
} catch (err) {
|
|
100
|
+
if (/** @type {{ code?: unknown }} */ (err).code === "ENOENT") {
|
|
101
|
+
throw refusal(
|
|
102
|
+
WIN32_UNSUPPORTED_CODE,
|
|
103
|
+
`this entwurf install carries no Windows addon (${manifestPath} is absent): only the Windows preview ` +
|
|
104
|
+
"artifact runs on Windows; an ordinary pack does not.",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
throw refusal(WIN32_DRIFT_CODE, `cannot read ${manifestPath}`, err);
|
|
108
|
+
}
|
|
109
|
+
let manifest;
|
|
110
|
+
try {
|
|
111
|
+
manifest = JSON.parse(raw);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
throw refusal(WIN32_DRIFT_CODE, `${manifestPath} is not JSON`, err);
|
|
114
|
+
}
|
|
115
|
+
if (
|
|
116
|
+
manifest?.schema !== 1 ||
|
|
117
|
+
manifest.platform !== "win32" ||
|
|
118
|
+
manifest.arch !== "x64" ||
|
|
119
|
+
manifest.file !== ADDON_FILE ||
|
|
120
|
+
typeof manifest.sha256 !== "string" ||
|
|
121
|
+
!SHA256.test(manifest.sha256) ||
|
|
122
|
+
manifest.napiVersion !== ADDON_NAPI_VERSION
|
|
123
|
+
) {
|
|
124
|
+
throw refusal(WIN32_DRIFT_CODE, `${manifestPath} does not describe one win32-x64 ${ADDON_FILE}`);
|
|
125
|
+
}
|
|
126
|
+
const napi = Number(versions.napi);
|
|
127
|
+
if (!(napi >= ADDON_NAPI_VERSION)) {
|
|
128
|
+
throw refusal(
|
|
129
|
+
WIN32_UNSUPPORTED_CODE,
|
|
130
|
+
`the Windows addon needs N-API ${ADDON_NAPI_VERSION}; this Node offers ${versions.napi ?? "none"}`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const addonPath = path.join(dir, ADDON_FILE);
|
|
134
|
+
let bytes;
|
|
135
|
+
try {
|
|
136
|
+
bytes = fs.readFileSync(addonPath);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
throw refusal(WIN32_DRIFT_CODE, `the manifest names ${addonPath}, which cannot be read`, err);
|
|
139
|
+
}
|
|
140
|
+
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
141
|
+
if (sha256 !== manifest.sha256) {
|
|
142
|
+
throw refusal(
|
|
143
|
+
WIN32_DRIFT_CODE,
|
|
144
|
+
`${addonPath} is not the addon its manifest names (sha256 ${sha256} != ${manifest.sha256})`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
return { addonPath, sha256, napiVersion: ADDON_NAPI_VERSION };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @typedef {object} Win32NativeAddon
|
|
152
|
+
* @property {(file: string) => Buffer} readNoFollow
|
|
153
|
+
* @property {(name: string, line: string, timeoutMs: number) => Promise<{ responseLine: string, serverPid: number }>} pipeRpc
|
|
154
|
+
* @property {(name: string, timeoutMs: number) => Promise<{ serverPid: number }>} pipeProbe
|
|
155
|
+
* @property {() => { live: number, cancelledIncomplete: number }} ioStats
|
|
156
|
+
*/
|
|
157
|
+
|
|
158
|
+
/** @returns {Win32NativeAddon} */
|
|
159
|
+
export function win32Native() {
|
|
160
|
+
const held = /** @type {Record<symbol, Win32NativeAddon | undefined>} */ (/** @type {unknown} */ (process))[SLOT];
|
|
161
|
+
if (held) return held;
|
|
162
|
+
if (process.platform !== "win32" || process.arch !== "x64") {
|
|
163
|
+
throw refusal(
|
|
164
|
+
WIN32_UNSUPPORTED_CODE,
|
|
165
|
+
`the Windows native surface exists only on win32-x64, not ${process.platform}-${process.arch}`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const root = win32NativePackageRoot();
|
|
169
|
+
if (root === null) {
|
|
170
|
+
throw refusal(WIN32_UNSUPPORTED_CODE, `cannot find the ${PACKAGE_NAME} package root beside this module`);
|
|
171
|
+
}
|
|
172
|
+
const { addonPath } = verifyWin32NativeAt(root);
|
|
173
|
+
const module = { exports: {} };
|
|
174
|
+
try {
|
|
175
|
+
process.dlopen(module, addonPath);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
throw refusal(WIN32_UNSUPPORTED_CODE, `this host cannot load the verified Windows addon ${addonPath}`, err);
|
|
178
|
+
}
|
|
179
|
+
const addon = /** @type {Win32NativeAddon} */ (module.exports);
|
|
180
|
+
/** @type {Record<symbol, Win32NativeAddon | undefined>} */ (/** @type {unknown} */ (process))[SLOT] = addon;
|
|
181
|
+
return addon;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Read a record's bytes on Windows, refusing a final-component reparse point (`ELOOP`) and
|
|
186
|
+
* a non-regular entry (`ENTWURF_ENOTREG`) on the very handle the read uses. Over 16 MiB is
|
|
187
|
+
* `EFBIG`; other Win32 failures are `EWIN32_<n>`.
|
|
188
|
+
*
|
|
189
|
+
* @param {string} file
|
|
190
|
+
* @returns {Buffer}
|
|
191
|
+
*/
|
|
192
|
+
export function win32ReadNoFollow(file) {
|
|
193
|
+
const stem = path.win32.basename(file).split(".")[0].replace(/[ ]+$/, "");
|
|
194
|
+
if (DOS_DEVICE.test(stem)) {
|
|
195
|
+
throw refusal("ENTWURF_ENOTREG", `${path.win32.basename(file)} names a DOS device, never a record`);
|
|
196
|
+
}
|
|
197
|
+
return win32Native().readNoFollow(file);
|
|
198
|
+
}
|
package/run.sh
CHANGED
|
@@ -1340,6 +1340,15 @@ check_control_send_receipt() {
|
|
|
1340
1340
|
run_vitest pi-extensions/lib/control-send-receipt.test.ts
|
|
1341
1341
|
}
|
|
1342
1342
|
|
|
1343
|
+
check_win32_preview() {
|
|
1344
|
+
# MUTANT EXECUTION COORDINATE, not a discovery path — the Windows preview's (#78) loader and
|
|
1345
|
+
# platform wiring tests are found by check-tests-beside-behavior like every other beside-behaviour
|
|
1346
|
+
# lane. This case exists because the WIN32-* mutants name it as gate argv. The addon's own Win32
|
|
1347
|
+
# behaviour is measured only on Windows, by windows-native-preview.yml.
|
|
1348
|
+
section "Windows preview loader + platform wiring (mutant execution coordinate)"
|
|
1349
|
+
run_vitest pi-extensions/lib/win32-native.test.ts pi-extensions/lib/win32-wiring.test.ts
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1343
1352
|
check_entwurf_v2_native_push() {
|
|
1344
1353
|
# MUTANT EXECUTION COORDINATE, not a discovery path — the test is found by
|
|
1345
1354
|
# check-tests-beside-behavior like every other beside-behaviour lane. This case exists
|
|
@@ -2667,6 +2676,8 @@ for (const [job] of ciJobs) {
|
|
|
2667
2676
|
// to what is already bound is not a safety net; it is a restatement.
|
|
2668
2677
|
// pnpm-lock.yaml is excluded on purpose: those engine ranges belong to third-
|
|
2669
2678
|
// party dependencies and are not this repo's supported axis.
|
|
2679
|
+
// native/win32/package-lock.json (#78) is excluded for the same reason: a generated
|
|
2680
|
+
// lock of the Windows addon's build-only dependency tree (node-gyp), never shipped.
|
|
2670
2681
|
// `--cached` ONLY — deliberately not `--others`. The corpus must be the
|
|
2671
2682
|
// candidate contract (what is tracked/staged), never the operator's working
|
|
2672
2683
|
// directory: an un-ignored scratch file would otherwise turn this gate RED on
|
|
@@ -2676,7 +2687,7 @@ for (const [job] of ciJobs) {
|
|
|
2676
2687
|
// before the commit that would ship it. (Review lane, 2026-07-21.)
|
|
2677
2688
|
const tracked = execFileSync('git', ['ls-files', '--cached', '--', '*.sh', '*.json', '*.ts', '*.md', '*.yml', '*.yaml'], { encoding: 'utf8' })
|
|
2678
2689
|
.split('\n')
|
|
2679
|
-
.filter((f) => f && f !== 'pnpm-lock.yaml' && f !== 'CHANGELOG.md' && f !== 'NEXT.md');
|
|
2690
|
+
.filter((f) => f && f !== 'pnpm-lock.yaml' && f !== 'native/win32/package-lock.json' && f !== 'CHANGELOG.md' && f !== 'NEXT.md');
|
|
2680
2691
|
assert.ok(tracked.length >= 20,
|
|
2681
2692
|
`git ls-files returned ${tracked.length} first-party files — the sweep lost its corpus and would pass vacuously`);
|
|
2682
2693
|
|
|
@@ -3851,6 +3862,14 @@ check_pack() {
|
|
|
3851
3862
|
"pi/pi-durable/carrier/LICENSE"
|
|
3852
3863
|
"pi/meta-bridge-omp/entwurf-meta-omp/package.json"
|
|
3853
3864
|
"mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
|
|
3865
|
+
# #78 — the Windows preview loader. The compiled meta-session reaches it only through a
|
|
3866
|
+
# win32-only createRequire, not the import graph, so its compiled copy is pinned by name.
|
|
3867
|
+
"pi-extensions/lib/win32-native.js"
|
|
3868
|
+
"mcp/entwurf-bridge/dist/pi-extensions/lib/win32-native.js"
|
|
3869
|
+
# #78 — the MIT notice for node-gyp's win_delay_load_hook.cc, which build.mjs compiles into the
|
|
3870
|
+
# Windows addon. It rides every pack (one per-FILE entry), so the preview carrier never ships
|
|
3871
|
+
# without it; build.mjs refuses unless it equals the pinned node-gyp LICENSE.
|
|
3872
|
+
"LICENSES/node-gyp.txt"
|
|
3854
3873
|
"scripts/postinstall-chmod.cjs"
|
|
3855
3874
|
"pi/entwurf-capabilities.json"
|
|
3856
3875
|
"pi/meta-bridge/.claude-plugin/marketplace.json"
|
|
@@ -3925,6 +3944,9 @@ check_pack() {
|
|
|
3925
3944
|
'\.test\.[cm]?[jt]s$'
|
|
3926
3945
|
# Maintainer take/renderer/helper TS is source-only, including non-test implementations.
|
|
3927
3946
|
'^demo/.*\.ts$'
|
|
3947
|
+
# #78 — the Windows addon's C source, gyp file and build pin are CI build inputs, never a
|
|
3948
|
+
# consumer repair surface: only the built native/win32-x64/ carrier may ship.
|
|
3949
|
+
'^native/win32/'
|
|
3928
3950
|
)
|
|
3929
3951
|
# #141: the owned pi-durable source, its typecheck fence and the retired overlay patch are maintainer
|
|
3930
3952
|
# input. An installed consumer runs the emitted carrier JS (and its LICENSE) only — Rule 11: compiled
|
|
@@ -3957,6 +3979,20 @@ check_pack() {
|
|
|
3957
3979
|
pass=0
|
|
3958
3980
|
fi
|
|
3959
3981
|
|
|
3982
|
+
# #78 — a pack carries the Windows addon not at all, or as exactly a verified addon + manifest
|
|
3983
|
+
# pair (pack_win32_carrier_verdict, proven on owned fixtures by check-pack-win32-carrier).
|
|
3984
|
+
# "present" means the carrier verified, never that the build was approved for publication.
|
|
3985
|
+
local carrier_verdict
|
|
3986
|
+
carrier_verdict=$(pack_win32_carrier_verdict "$file_list" "$REPO_DIR")
|
|
3987
|
+
case "$carrier_verdict" in
|
|
3988
|
+
absent) echo "[check-pack] win32 addon: absent (not a Windows preview artifact)" ;;
|
|
3989
|
+
present\ *) echo "[check-pack] win32 addon: present sha256=${carrier_verdict#present } (carrier verified; not a publication approval)" ;;
|
|
3990
|
+
*)
|
|
3991
|
+
fail "[check-pack] native/win32-x64/ is not one verified addon + manifest pair: $carrier_verdict"
|
|
3992
|
+
pass=0
|
|
3993
|
+
;;
|
|
3994
|
+
esac
|
|
3995
|
+
|
|
3960
3996
|
local total
|
|
3961
3997
|
total=$(printf '%s\n' "$file_list" | wc -l | tr -d ' ')
|
|
3962
3998
|
echo "[check-pack] $total files in tarball"
|
|
@@ -3969,6 +4005,68 @@ check_pack() {
|
|
|
3969
4005
|
return 1
|
|
3970
4006
|
}
|
|
3971
4007
|
|
|
4008
|
+
# #78 — the Windows addon carrier decision, shared by check_pack (the real tarball list) and
|
|
4009
|
+
# check_pack_win32_carrier (owned fake-byte fixtures). $1 = tarball file list, $2 = the package root
|
|
4010
|
+
# holding native/win32-x64/. Prints ONE verdict: `absent`, `pair-broken`, `invalid <code>` or
|
|
4011
|
+
# `present <sha256>`. The manifest + bytes check IS the loader's own verifyWin32NativeAt (manifest
|
|
4012
|
+
# shape, N-API 8, digest), so the pack and the consumer cannot disagree about what a carrier is.
|
|
4013
|
+
pack_win32_carrier_verdict() {
|
|
4014
|
+
local list="$1" root="$2" carrier
|
|
4015
|
+
carrier=$(grep -E '^native/win32-x64/' <<<"$list" | sort || true)
|
|
4016
|
+
if [ -z "$carrier" ]; then echo "absent"; return 0; fi
|
|
4017
|
+
if [ "$carrier" != $'native/win32-x64/entwurf_win.node\nnative/win32-x64/manifest.json' ]; then echo "pair-broken"; return 0; fi
|
|
4018
|
+
node -e '
|
|
4019
|
+
import(require("node:url").pathToFileURL(process.argv[1]).href).then((m) => {
|
|
4020
|
+
try { process.stdout.write(`present ${m.verifyWin32NativeAt(process.argv[2], { napi: "8" }).sha256}`); }
|
|
4021
|
+
catch (err) { process.stdout.write(`invalid ${err.code ?? "uncoded"}`); }
|
|
4022
|
+
});' "$REPO_DIR/pi-extensions/lib/win32-native.js" "$root"
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
check_pack_win32_carrier() {
|
|
4026
|
+
section "pack: Windows addon carrier verdict (owned fake-byte fixtures)"
|
|
4027
|
+
# Fake bytes and node-written manifests, never a PE: this proves the pair/digest/ABI decision,
|
|
4028
|
+
# not a loadable addon (windows-native-preview.yml owns that, on installed bytes).
|
|
4029
|
+
local fx pair=$'native/win32-x64/entwurf_win.node\nnative/win32-x64/manifest.json' got
|
|
4030
|
+
fx=$(mktemp -d "${TMPDIR:-/tmp}/entwurf-pack-carrier.XXXXXX")
|
|
4031
|
+
trap 'rm -rf -- "$fx"' RETURN
|
|
4032
|
+
win32_carrier_cell() { # name napiVersion -> a root with a matching addon + manifest
|
|
4033
|
+
mkdir -p "$fx/$1/native/win32-x64"
|
|
4034
|
+
printf 'fake addon %s\n' "$1" >"$fx/$1/native/win32-x64/entwurf_win.node"
|
|
4035
|
+
node -e '
|
|
4036
|
+
const fs = require("node:fs"), dir = process.argv[1];
|
|
4037
|
+
const sha = require("node:crypto").createHash("sha256").update(fs.readFileSync(dir + "/entwurf_win.node")).digest("hex");
|
|
4038
|
+
fs.writeFileSync(dir + "/manifest.json", JSON.stringify({ schema: 1, platform: "win32", arch: "x64", file: "entwurf_win.node", sha256: sha, napiVersion: Number(process.argv[2]) }));
|
|
4039
|
+
' "$fx/$1/native/win32-x64" "$2"
|
|
4040
|
+
}
|
|
4041
|
+
win32_carrier_cell match 8
|
|
4042
|
+
win32_carrier_cell drift 8 && printf 'changed\n' >"$fx/drift/native/win32-x64/entwurf_win.node"
|
|
4043
|
+
win32_carrier_cell abi 7
|
|
4044
|
+
win32_carrier_cell garbled 8 && printf 'not json' >"$fx/garbled/native/win32-x64/manifest.json"
|
|
4045
|
+
local want_sha
|
|
4046
|
+
want_sha=$(sha256sum "$fx/match/native/win32-x64/entwurf_win.node" | cut -d' ' -f1)
|
|
4047
|
+
got=$(pack_win32_carrier_verdict "package.json" "$fx/match")
|
|
4048
|
+
[ "$got" = "absent" ] || { fail "[check-pack-win32-carrier] a list without native/win32-x64/ must be absent — got: $got"; return 1; }
|
|
4049
|
+
for cell in drift abi garbled; do
|
|
4050
|
+
got=$(pack_win32_carrier_verdict "$pair" "$fx/$cell")
|
|
4051
|
+
[ "$got" = "invalid ENTWURF_WIN32_NATIVE_DRIFT" ] || {
|
|
4052
|
+
fail "[QK:WIN32-PACK-DIGEST] the $cell carrier (changed byte / N-API 7 / unparsable manifest) must be invalid as drift, never present — got: $got"
|
|
4053
|
+
return 1
|
|
4054
|
+
}
|
|
4055
|
+
done
|
|
4056
|
+
# The positive cell comes after the refusals, so a weakened check dies on its own QK first.
|
|
4057
|
+
got=$(pack_win32_carrier_verdict "$pair" "$fx/match")
|
|
4058
|
+
[ "$got" = "present $want_sha" ] || { fail "[check-pack-win32-carrier] a matching pair must be present $want_sha — got: $got"; return 1; }
|
|
4059
|
+
local list
|
|
4060
|
+
for list in 'native/win32-x64/entwurf_win.node' 'native/win32-x64/manifest.json' "$pair"$'\nnative/win32-x64/x.dll'; do
|
|
4061
|
+
got=$(pack_win32_carrier_verdict "$list" "$fx/match")
|
|
4062
|
+
[ "$got" = "pair-broken" ] || {
|
|
4063
|
+
fail "[QK:WIN32-PACK-PAIR] native/win32-x64/ must carry exactly the addon + manifest pair — $(tr '\n' ' ' <<<"$list") gave: $got"
|
|
4064
|
+
return 1
|
|
4065
|
+
}
|
|
4066
|
+
done
|
|
4067
|
+
echo "[check-pack-win32-carrier] ok — absent, present, three drift shapes and three broken pairs decided by the shared verdict"
|
|
4068
|
+
}
|
|
4069
|
+
|
|
3972
4070
|
# The pin-leak filter for the install tree's .pnpm listing, shared by the real scan and its
|
|
3973
4071
|
# self-test below. TWO properties are load-bearing and each has its own cell below.
|
|
3974
4072
|
#
|
|
@@ -7378,6 +7476,9 @@ case "$cmd" in
|
|
|
7378
7476
|
check-control-send-receipt)
|
|
7379
7477
|
check_control_send_receipt
|
|
7380
7478
|
;;
|
|
7479
|
+
check-win32-preview)
|
|
7480
|
+
check_win32_preview
|
|
7481
|
+
;;
|
|
7381
7482
|
check-entwurf-v2-native-push)
|
|
7382
7483
|
check_entwurf_v2_native_push
|
|
7383
7484
|
;;
|
|
@@ -8489,6 +8590,9 @@ case "$cmd" in
|
|
|
8489
8590
|
check-pack-pin-matcher)
|
|
8490
8591
|
check_pack_pin_matcher
|
|
8491
8592
|
;;
|
|
8593
|
+
check-pack-win32-carrier)
|
|
8594
|
+
check_pack_win32_carrier
|
|
8595
|
+
;;
|
|
8492
8596
|
check-pack-consumer-roots)
|
|
8493
8597
|
check_pack_consumer_roots
|
|
8494
8598
|
;;
|
|
@@ -263,8 +263,19 @@ ok(
|
|
|
263
263
|
// call: the dir is still this adapter's own policy (ENTWURF_DIR honours the env
|
|
264
264
|
// override the pi side does not), but the join is not re-authored here.
|
|
265
265
|
ok(
|
|
266
|
-
"buildStrictPiSenderEnvelope
|
|
267
|
-
/
|
|
266
|
+
"buildStrictPiSenderEnvelope probes the canonical address via the shared grammar (controlSocketPathIn(ENTWURF_DIR, …))",
|
|
267
|
+
/controlAddressPresent\s*\(\s*socketPath\s*\)/.test(piBody) &&
|
|
268
|
+
/controlSocketPathIn\s*\(\s*ENTWURF_DIR\s*,/.test(piBody),
|
|
269
|
+
);
|
|
270
|
+
// The presence probe keeps the socket-file existsSync off Windows; on Windows (#78) the address
|
|
271
|
+
// is a named pipe, which only the owner-validated probe may touch (a stat would connect).
|
|
272
|
+
const presentBody = functionBody("controlAddressPresent");
|
|
273
|
+
ok(
|
|
274
|
+
"controlAddressPresent: existsSync(socketPath) off Windows, the owner-validated pipe probe on it",
|
|
275
|
+
/existsSync\s*\(\s*socketPath\s*\)/.test(presentBody) &&
|
|
276
|
+
/process\.platform === "win32"\)\s*return \(await probeSocketLiveness\(socketPath\)\) === "alive"/.test(
|
|
277
|
+
presentBody,
|
|
278
|
+
),
|
|
268
279
|
);
|
|
269
280
|
|
|
270
281
|
const metaBuilder = functionBody("buildMetaSenderEnvelope");
|
|
@@ -280,8 +291,8 @@ ok(
|
|
|
280
291
|
|
|
281
292
|
const selfRegion = toolRegion("entwurf_self");
|
|
282
293
|
ok(
|
|
283
|
-
"entwurf_self
|
|
284
|
-
/
|
|
294
|
+
"entwurf_self probes the pi control address (alive vs expected, no synthesized path lie)",
|
|
295
|
+
/controlAddressPresent\s*\(\s*socketPath\s*\)/.test(selfRegion),
|
|
285
296
|
);
|
|
286
297
|
|
|
287
298
|
// ── SLICE INTEGRITY (before any claim is read off the slice) ─────────────────
|
|
@@ -3488,7 +3488,7 @@ let manifestCount: number;
|
|
|
3488
3488
|
"mux-fresh-call": 65,
|
|
3489
3489
|
"mux-launcher-fence": 8,
|
|
3490
3490
|
"mux-parent-artifact": 3,
|
|
3491
|
-
"pack-install":
|
|
3491
|
+
"pack-install": 5,
|
|
3492
3492
|
"pi-package-ownership": 8,
|
|
3493
3493
|
"qualification-ledger": 42,
|
|
3494
3494
|
"mux-resume-call": 10,
|
|
@@ -3506,6 +3506,7 @@ let manifestCount: number;
|
|
|
3506
3506
|
"typing-call-fence": 1,
|
|
3507
3507
|
"v2-surface": 13,
|
|
3508
3508
|
"v2-visible-resume": 18,
|
|
3509
|
+
"win32-preview": 19,
|
|
3509
3510
|
};
|
|
3510
3511
|
const laneTally: Record<string, number> = {};
|
|
3511
3512
|
for (const man of manifests) laneTally[man.lane] = (laneTally[man.lane] ?? 0) + man.mutants.length;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"mutants": [
|
|
5
5
|
{
|
|
6
6
|
"claim": "PACK-INSTALL-PIN-MATCHER-BOUNDED",
|
|
7
|
-
"title": "the pin-leak matcher loses its version boundary and blesses substring lookalikes
|
|
7
|
+
"title": "the pin-leak matcher loses its version boundary and blesses substring lookalikes \u2014 @0.85.10 reads as the pinned 0.85.1 and the gate announces a verified tree it never verified (independent review, 2026-08-25)",
|
|
8
8
|
"subject": "run.sh",
|
|
9
9
|
"find": [" grep '^@earendil-works+' | grep -Ev '@1\\.3\\.0(_|$)' || true"],
|
|
10
10
|
"replace": [" grep '^@earendil-works+' | grep -v '@0\\.87\\.1' || true"],
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
"claim": "PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE",
|
|
18
|
-
"title": "the pin-leak matcher narrows back to the pi-* families and goes blind to a non-pi member of the same runtime closure
|
|
18
|
+
"title": "the pin-leak matcher narrows back to the pi-* families and goes blind to a non-pi member of the same runtime closure \u2014 @earendil-works/chord (new in pi 0.85.0, a runtime dependency of pi-coding-agent/pi-agent-core/pi-client/pi-protocol) floats unpinned while the gate prints a verified pin (measured 2026-09-06 on a real 0.85.1 install tree)",
|
|
19
19
|
"subject": "run.sh",
|
|
20
20
|
"find": [" grep '^@earendil-works+' | grep -Ev '@1\\.3\\.0(_|$)' || true"],
|
|
21
21
|
"replace": [" grep '^@earendil-works+pi-' | grep -Ev '@1\\.3\\.0(_|$)' || true"],
|
|
@@ -34,6 +34,32 @@
|
|
|
34
34
|
"timeoutSeconds": 120,
|
|
35
35
|
"signature": "[QK:PACK-INSTALL-CONSUMER-ROOTS-CONTAINED]",
|
|
36
36
|
"signatureSource": "run.sh"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"claim": "WIN32-PACK-PAIR",
|
|
40
|
+
"title": "the Windows carrier pair check becomes a presence test, so an addon without its manifest, or with an extra DLL beside it, is packed as a carrier",
|
|
41
|
+
"subject": "run.sh",
|
|
42
|
+
"find": [
|
|
43
|
+
" if [ \"$carrier\" != $'native/win32-x64/entwurf_win.node\\nnative/win32-x64/manifest.json' ]; then echo \"pair-broken\"; return 0; fi"
|
|
44
|
+
],
|
|
45
|
+
"replace": [" if ! grep -q 'entwurf_win.node' <<<\"$carrier\"; then echo \"pair-broken\"; return 0; fi"],
|
|
46
|
+
"gate": ["bash", "run.sh", "check-pack-win32-carrier"],
|
|
47
|
+
"timeoutSeconds": 120,
|
|
48
|
+
"signature": "[QK:WIN32-PACK-PAIR]",
|
|
49
|
+
"signatureSource": "run.sh"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"claim": "WIN32-PACK-DIGEST",
|
|
53
|
+
"title": "the pack stops verifying the carrier with the loader's own check, so changed bytes or a foreign N-API manifest are packed as `present`",
|
|
54
|
+
"subject": "run.sh",
|
|
55
|
+
"find": [
|
|
56
|
+
" try { process.stdout.write(`present ${m.verifyWin32NativeAt(process.argv[2], { napi: \"8\" }).sha256}`); }"
|
|
57
|
+
],
|
|
58
|
+
"replace": [" try { process.stdout.write(`present unverified`); }"],
|
|
59
|
+
"gate": ["bash", "run.sh", "check-pack-win32-carrier"],
|
|
60
|
+
"timeoutSeconds": 120,
|
|
61
|
+
"signature": "[QK:WIN32-PACK-DIGEST]",
|
|
62
|
+
"signatureSource": "run.sh"
|
|
37
63
|
}
|
|
38
64
|
]
|
|
39
65
|
}
|