@junghanacs/entwurf 0.38.1 → 0.40.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 (50) hide show
  1. package/BASELINE.md +11 -4
  2. package/CHANGELOG.md +323 -0
  3. package/DELIVERY.md +34 -0
  4. package/LICENSES/node-gyp.txt +24 -0
  5. package/README.md +20 -6
  6. package/VERIFY.md +23 -4
  7. package/docs/setup-clean-host.md +43 -2
  8. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +32 -9
  9. package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-socket-path.js +56 -1
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +25 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +10 -4
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +14 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +123 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +259 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-transport.js +80 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +14 -2
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-probe.js +21 -0
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/win32-native.js +182 -0
  19. package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +65 -0
  20. package/mcp/entwurf-bridge/src/index.ts +38 -9
  21. package/mcp/entwurf-bridge/tsconfig.build.json +7 -0
  22. package/native/win32-x64/entwurf_win.node +0 -0
  23. package/native/win32-x64/manifest.json +45 -0
  24. package/package.json +5 -3
  25. package/pi-extensions/entwurf-control.ts +6 -1
  26. package/pi-extensions/lib/control-socket-path.js +56 -1
  27. package/pi-extensions/lib/entwurf-control-rpc.ts +32 -0
  28. package/pi-extensions/lib/meta-mailbox-body.ts +13 -4
  29. package/pi-extensions/lib/meta-session.ts +14 -0
  30. package/pi-extensions/lib/remote-pair-receive.ts +184 -0
  31. package/pi-extensions/lib/remote-pair-send.ts +325 -0
  32. package/pi-extensions/lib/remote-pair-transport.ts +81 -0
  33. package/pi-extensions/lib/socket-discovery.ts +12 -1
  34. package/pi-extensions/lib/socket-probe.ts +20 -0
  35. package/pi-extensions/lib/win32-native.js +198 -0
  36. package/run.sh +149 -1
  37. package/scripts/check-entwurf-self-address.ts +15 -4
  38. package/scripts/check-gate-qualification.ts +8 -1
  39. package/scripts/check-remote-pair-installed.ts +242 -0
  40. package/scripts/lib/remote-pair-installed-audit.ts +25 -0
  41. package/scripts/mutants/control-send-receipt.json +2 -2
  42. package/scripts/mutants/pack-install.json +28 -2
  43. package/scripts/mutants/remote-pair-display.json +53 -0
  44. package/scripts/mutants/remote-pair-installed-audit.json +28 -0
  45. package/scripts/mutants/remote-pair-receive.json +126 -0
  46. package/scripts/mutants/remote-pair-roundtrip.json +83 -0
  47. package/scripts/mutants/remote-pair-send.json +194 -0
  48. package/scripts/mutants/remote-pair-transport.json +66 -0
  49. package/scripts/mutants/win32-preview.json +215 -0
  50. package/scripts/remote-pair-receive.ts +73 -0
package/run.sh CHANGED
@@ -328,6 +328,9 @@ Usage:
328
328
  ./run.sh meta-bridge-prune # 1.0.0 meta-bridge Phase 4: LISTING-ONLY store hygiene — classify orphan/stale/ambiguous/keep, print manual rm commands, delete NOTHING ([dir] [--ttl-days N])
329
329
  ./run.sh meta-bridge-fresh-cut # the ONE generation verb (the verb every v3-only rejection names): quiesce-check live sockets/markers/native-push conversations (refusing any surface it cannot inspect), archive meta-sessions/ + meta-mailbox/ to `<dir>.archive-<ts>`, clear dead transport residue, open an empty v3 generation. No migration, no restore — the archive is forensic only. EXIT CONTRACT (#54, `--help` prints it): 0 complete / 1 NOTHING MOVED (re-run, do not setup) / 2 usage / 3 cut transition incomplete (inspect) / 4 cut complete but residue cleanup failed (`setup` may run; re-run only before new citizen birth, otherwise remove residue manually)
330
330
  ./run.sh meta-facts # #65 owner-normalized READ-ONLY store projection: deterministic JSON {schemaVersion:1, storeDir, citizens: full v3 records sorted by gardenId, defects: {filename,message}} — THE listing contract emitted by the owner so consumers stop copying the certification. No liveness/sockets/transcript contents. EXIT: 0 readable (defects in-band; missing store = empty), 2 usage, 3 unreadable ([dir])
331
+ ./run.sh check-remote-pair-installed # #107 rail 3, CHECKOUT-ONLY: install a caller-supplied candidate tarball (--tgz <abs>) into a sandboxed consumer prefix, check its compiled remote-pair surface, run the installed `entwurf remote-pair-receive` (usage exit 2 = twin reached), run the T2 roundtrip cells against the installed bridge+entry, and audit that the subject processes resolve every module inside the consumer prefix (with a checkout control). Never repacks; no real ssh, no model
332
+ ./run.sh check-remote-pair-installed-audit # #107 rail 3: the deterministic counterpart of check-remote-pair-installed — its checkout-invisibility acceptance primitive proven on synthetic logs (real path, root boundary). In check:vitest
333
+ ./run.sh remote-pair-receive # #107 rail 1: the RECEIVING half of one operator-declared remote pair. Reads ONE JSON claim {from,fromAgentId,fromCwd,target,message,wantsReply} on stdin, refuses a from/target outside the declared pair before any mutation (a scope limit under the same-operator SSH account trust model, not authentication), then hands a match to the existing v2 dispatch. Ambient sender carriers are never read. Installed, run_ts runs its compiled twin (dist/scripts/remote-pair-receive.js). EXIT: 0 dispatched / 4 dispatched, the dispatch reported an error (delivery not inferred) / 3 pair refused / 2 invalid claim or declaration (JSON receipt) or wrong argv (usage on stderr, no receipt) / 1 uncaught dispatch or store throw (no receipt; the sender reads it as indeterminate) (--pair <absolute declaration file>)
331
334
  ./run.sh peer-facts # #116 M2-a owner-normalized READ-ONLY peer projection: the same listEntwurfFacts provider through the same renderer entwurf_peers uses, emitted as deterministic JSON {schemaVersion:1, storeDir, peers, diagnostics} so a consumer never re-implements the placement join (whose pi half is a measured vendor floor). NOT the same bytes as an entwurf_peers call and deliberately so: that surface is a human listing (observationLimit, text over the wire), this is a machine projection with UNBOUNDED observation, because a rationed row says `unobserved` and a machine cannot tell that from "nobody could look". `placement` stays a STRUCTURED tagged union ({kind:"herdr-pane",paneId} / unobserved / none / ambiguous) — never the human `herdr <pane>` string. Publishes NO socket coordinate (ENTWURF_DIR selects what is PROBED, #50 C4) and NO herdr agent_status / interactive_ready / screen text. One placement read per listing, no watcher. EXIT: 0 readable (diagnostics in-band; missing store = empty), 2 usage, 3 unreadable ([dir])
332
335
  ./run.sh meta-bridge-managed-keys # 0.10.0 meta-bridge: print the SSOT of settings keys entwurf OWNS (consumers read this to stay disjoint — keyset-owner invariant)
333
336
  ./run.sh check-keyset-overlap <fragment.json...> # 0.10.0 meta-bridge: PREVENTIVE keyset guard — fail if a consumer fragment collides with any pi-owned key (cross-repo; not in pnpm check)
@@ -1340,6 +1343,15 @@ check_control_send_receipt() {
1340
1343
  run_vitest pi-extensions/lib/control-send-receipt.test.ts
1341
1344
  }
1342
1345
 
1346
+ check_win32_preview() {
1347
+ # MUTANT EXECUTION COORDINATE, not a discovery path — the Windows preview's (#78) loader and
1348
+ # platform wiring tests are found by check-tests-beside-behavior like every other beside-behaviour
1349
+ # lane. This case exists because the WIN32-* mutants name it as gate argv. The addon's own Win32
1350
+ # behaviour is measured only on Windows, by windows-native-preview.yml.
1351
+ section "Windows preview loader + platform wiring (mutant execution coordinate)"
1352
+ run_vitest pi-extensions/lib/win32-native.test.ts pi-extensions/lib/win32-wiring.test.ts
1353
+ }
1354
+
1343
1355
  check_entwurf_v2_native_push() {
1344
1356
  # MUTANT EXECUTION COORDINATE, not a discovery path — the test is found by
1345
1357
  # check-tests-beside-behavior like every other beside-behaviour lane. This case exists
@@ -2667,6 +2679,8 @@ for (const [job] of ciJobs) {
2667
2679
  // to what is already bound is not a safety net; it is a restatement.
2668
2680
  // pnpm-lock.yaml is excluded on purpose: those engine ranges belong to third-
2669
2681
  // party dependencies and are not this repo's supported axis.
2682
+ // native/win32/package-lock.json (#78) is excluded for the same reason: a generated
2683
+ // lock of the Windows addon's build-only dependency tree (node-gyp), never shipped.
2670
2684
  // `--cached` ONLY — deliberately not `--others`. The corpus must be the
2671
2685
  // candidate contract (what is tracked/staged), never the operator's working
2672
2686
  // directory: an un-ignored scratch file would otherwise turn this gate RED on
@@ -2676,7 +2690,7 @@ for (const [job] of ciJobs) {
2676
2690
  // before the commit that would ship it. (Review lane, 2026-07-21.)
2677
2691
  const tracked = execFileSync('git', ['ls-files', '--cached', '--', '*.sh', '*.json', '*.ts', '*.md', '*.yml', '*.yaml'], { encoding: 'utf8' })
2678
2692
  .split('\n')
2679
- .filter((f) => f && f !== 'pnpm-lock.yaml' && f !== 'CHANGELOG.md' && f !== 'NEXT.md');
2693
+ .filter((f) => f && f !== 'pnpm-lock.yaml' && f !== 'native/win32/package-lock.json' && f !== 'CHANGELOG.md' && f !== 'NEXT.md');
2680
2694
  assert.ok(tracked.length >= 20,
2681
2695
  `git ls-files returned ${tracked.length} first-party files — the sweep lost its corpus and would pass vacuously`);
2682
2696
 
@@ -3376,6 +3390,17 @@ check_acp_sdk_surface() {
3376
3390
  run_vitest test/acp-sdk-surface.contract.test.ts
3377
3391
  }
3378
3392
 
3393
+ check_remote_pair_installed_audit() {
3394
+ # #107 rail 3. The deterministic counterpart of `check-remote-pair-installed`: that gate needs a
3395
+ # caller-supplied tarball, so it cannot run in the floor or carry mutants, and its
3396
+ # checkout-invisibility verdict rests on one acceptance primitive
3397
+ # (scripts/lib/remote-pair-installed-audit.ts). This proves that primitive on tiny synthetic
3398
+ # logs and files. `test/**` is not discovered by check-tests-beside-behavior, so it needs this
3399
+ # public coordinate. Zero network, zero child process.
3400
+ section "remote-pair installed-closure audit (synthetic logs)"
3401
+ run_vitest test/remote-pair-installed-audit.test.ts
3402
+ }
3403
+
3379
3404
  check_pi_queue_oracle() {
3380
3405
  # #120 P2. The PUBLIC execution coordinate for `test/pi-queue.oracle.test.ts`, and the reason it
3381
3406
  # needs one: the discovery door below selects `pi-extensions/**/*.test.ts` and
@@ -3818,6 +3843,12 @@ check_pack() {
3818
3843
  # out: the placement join it emits is the copy a herdr-side consumer would
3819
3844
  # otherwise fork, and such a consumer runs against an installed host.
3820
3845
  "mcp/entwurf-bridge/dist/scripts/peer-facts.js"
3846
+ # #107 — the receiving host's entry the sender's ssh runs (run_ts twin), plus the three
3847
+ # remote-pair libs the bridge's entwurf_v2 handler and that entry import.
3848
+ "mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js"
3849
+ "mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js"
3850
+ "mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js"
3851
+ "mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-transport.js"
3821
3852
  # #87 Bundle B — omp-receive-doctor drives this operator projection through
3822
3853
  # run.sh, so installed node_modules must have the compiled twin too.
3823
3854
  "mcp/entwurf-bridge/dist/scripts/omp-receive-facts.js"
@@ -3851,6 +3882,14 @@ check_pack() {
3851
3882
  "pi/pi-durable/carrier/LICENSE"
3852
3883
  "pi/meta-bridge-omp/entwurf-meta-omp/package.json"
3853
3884
  "mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
3885
+ # #78 — the Windows preview loader. The compiled meta-session reaches it only through a
3886
+ # win32-only createRequire, not the import graph, so its compiled copy is pinned by name.
3887
+ "pi-extensions/lib/win32-native.js"
3888
+ "mcp/entwurf-bridge/dist/pi-extensions/lib/win32-native.js"
3889
+ # #78 — the MIT notice for node-gyp's win_delay_load_hook.cc, which build.mjs compiles into the
3890
+ # Windows addon. It rides every pack (one per-FILE entry), so the preview carrier never ships
3891
+ # without it; build.mjs refuses unless it equals the pinned node-gyp LICENSE.
3892
+ "LICENSES/node-gyp.txt"
3854
3893
  "scripts/postinstall-chmod.cjs"
3855
3894
  "pi/entwurf-capabilities.json"
3856
3895
  "pi/meta-bridge/.claude-plugin/marketplace.json"
@@ -3925,6 +3964,9 @@ check_pack() {
3925
3964
  '\.test\.[cm]?[jt]s$'
3926
3965
  # Maintainer take/renderer/helper TS is source-only, including non-test implementations.
3927
3966
  '^demo/.*\.ts$'
3967
+ # #78 — the Windows addon's C source, gyp file and build pin are CI build inputs, never a
3968
+ # consumer repair surface: only the built native/win32-x64/ carrier may ship.
3969
+ '^native/win32/'
3928
3970
  )
3929
3971
  # #141: the owned pi-durable source, its typecheck fence and the retired overlay patch are maintainer
3930
3972
  # input. An installed consumer runs the emitted carrier JS (and its LICENSE) only — Rule 11: compiled
@@ -3957,6 +3999,20 @@ check_pack() {
3957
3999
  pass=0
3958
4000
  fi
3959
4001
 
4002
+ # #78 — a pack carries the Windows addon not at all, or as exactly a verified addon + manifest
4003
+ # pair (pack_win32_carrier_verdict, proven on owned fixtures by check-pack-win32-carrier).
4004
+ # "present" means the carrier verified, never that the build was approved for publication.
4005
+ local carrier_verdict
4006
+ carrier_verdict=$(pack_win32_carrier_verdict "$file_list" "$REPO_DIR")
4007
+ case "$carrier_verdict" in
4008
+ absent) echo "[check-pack] win32 addon: absent (not a Windows preview artifact)" ;;
4009
+ present\ *) echo "[check-pack] win32 addon: present sha256=${carrier_verdict#present } (carrier verified; not a publication approval)" ;;
4010
+ *)
4011
+ fail "[check-pack] native/win32-x64/ is not one verified addon + manifest pair: $carrier_verdict"
4012
+ pass=0
4013
+ ;;
4014
+ esac
4015
+
3960
4016
  local total
3961
4017
  total=$(printf '%s\n' "$file_list" | wc -l | tr -d ' ')
3962
4018
  echo "[check-pack] $total files in tarball"
@@ -3969,6 +4025,68 @@ check_pack() {
3969
4025
  return 1
3970
4026
  }
3971
4027
 
4028
+ # #78 — the Windows addon carrier decision, shared by check_pack (the real tarball list) and
4029
+ # check_pack_win32_carrier (owned fake-byte fixtures). $1 = tarball file list, $2 = the package root
4030
+ # holding native/win32-x64/. Prints ONE verdict: `absent`, `pair-broken`, `invalid <code>` or
4031
+ # `present <sha256>`. The manifest + bytes check IS the loader's own verifyWin32NativeAt (manifest
4032
+ # shape, N-API 8, digest), so the pack and the consumer cannot disagree about what a carrier is.
4033
+ pack_win32_carrier_verdict() {
4034
+ local list="$1" root="$2" carrier
4035
+ carrier=$(grep -E '^native/win32-x64/' <<<"$list" | sort || true)
4036
+ if [ -z "$carrier" ]; then echo "absent"; return 0; fi
4037
+ if [ "$carrier" != $'native/win32-x64/entwurf_win.node\nnative/win32-x64/manifest.json' ]; then echo "pair-broken"; return 0; fi
4038
+ node -e '
4039
+ import(require("node:url").pathToFileURL(process.argv[1]).href).then((m) => {
4040
+ try { process.stdout.write(`present ${m.verifyWin32NativeAt(process.argv[2], { napi: "8" }).sha256}`); }
4041
+ catch (err) { process.stdout.write(`invalid ${err.code ?? "uncoded"}`); }
4042
+ });' "$REPO_DIR/pi-extensions/lib/win32-native.js" "$root"
4043
+ }
4044
+
4045
+ check_pack_win32_carrier() {
4046
+ section "pack: Windows addon carrier verdict (owned fake-byte fixtures)"
4047
+ # Fake bytes and node-written manifests, never a PE: this proves the pair/digest/ABI decision,
4048
+ # not a loadable addon (windows-native-preview.yml owns that, on installed bytes).
4049
+ local fx pair=$'native/win32-x64/entwurf_win.node\nnative/win32-x64/manifest.json' got
4050
+ fx=$(mktemp -d "${TMPDIR:-/tmp}/entwurf-pack-carrier.XXXXXX")
4051
+ trap 'rm -rf -- "$fx"' RETURN
4052
+ win32_carrier_cell() { # name napiVersion -> a root with a matching addon + manifest
4053
+ mkdir -p "$fx/$1/native/win32-x64"
4054
+ printf 'fake addon %s\n' "$1" >"$fx/$1/native/win32-x64/entwurf_win.node"
4055
+ node -e '
4056
+ const fs = require("node:fs"), dir = process.argv[1];
4057
+ const sha = require("node:crypto").createHash("sha256").update(fs.readFileSync(dir + "/entwurf_win.node")).digest("hex");
4058
+ fs.writeFileSync(dir + "/manifest.json", JSON.stringify({ schema: 1, platform: "win32", arch: "x64", file: "entwurf_win.node", sha256: sha, napiVersion: Number(process.argv[2]) }));
4059
+ ' "$fx/$1/native/win32-x64" "$2"
4060
+ }
4061
+ win32_carrier_cell match 8
4062
+ win32_carrier_cell drift 8 && printf 'changed\n' >"$fx/drift/native/win32-x64/entwurf_win.node"
4063
+ win32_carrier_cell abi 7
4064
+ win32_carrier_cell garbled 8 && printf 'not json' >"$fx/garbled/native/win32-x64/manifest.json"
4065
+ local want_sha
4066
+ want_sha=$(sha256sum "$fx/match/native/win32-x64/entwurf_win.node" | cut -d' ' -f1)
4067
+ got=$(pack_win32_carrier_verdict "package.json" "$fx/match")
4068
+ [ "$got" = "absent" ] || { fail "[check-pack-win32-carrier] a list without native/win32-x64/ must be absent — got: $got"; return 1; }
4069
+ for cell in drift abi garbled; do
4070
+ got=$(pack_win32_carrier_verdict "$pair" "$fx/$cell")
4071
+ [ "$got" = "invalid ENTWURF_WIN32_NATIVE_DRIFT" ] || {
4072
+ fail "[QK:WIN32-PACK-DIGEST] the $cell carrier (changed byte / N-API 7 / unparsable manifest) must be invalid as drift, never present — got: $got"
4073
+ return 1
4074
+ }
4075
+ done
4076
+ # The positive cell comes after the refusals, so a weakened check dies on its own QK first.
4077
+ got=$(pack_win32_carrier_verdict "$pair" "$fx/match")
4078
+ [ "$got" = "present $want_sha" ] || { fail "[check-pack-win32-carrier] a matching pair must be present $want_sha — got: $got"; return 1; }
4079
+ local list
4080
+ for list in 'native/win32-x64/entwurf_win.node' 'native/win32-x64/manifest.json' "$pair"$'\nnative/win32-x64/x.dll'; do
4081
+ got=$(pack_win32_carrier_verdict "$list" "$fx/match")
4082
+ [ "$got" = "pair-broken" ] || {
4083
+ fail "[QK:WIN32-PACK-PAIR] native/win32-x64/ must carry exactly the addon + manifest pair — $(tr '\n' ' ' <<<"$list") gave: $got"
4084
+ return 1
4085
+ }
4086
+ done
4087
+ echo "[check-pack-win32-carrier] ok — absent, present, three drift shapes and three broken pairs decided by the shared verdict"
4088
+ }
4089
+
3972
4090
  # The pin-leak filter for the install tree's .pnpm listing, shared by the real scan and its
3973
4091
  # self-test below. TWO properties are load-bearing and each has its own cell below.
3974
4092
  #
@@ -4472,6 +4590,12 @@ _check_pack_install_impl() {
4472
4590
  "mcp/entwurf-bridge/dist/scripts/meta-facts.js"
4473
4591
  # #116 M2-a — the owner-normalized peer projection (see check-pack).
4474
4592
  "mcp/entwurf-bridge/dist/scripts/peer-facts.js"
4593
+ # #107 — the receiving host's entry the sender's ssh runs (run_ts twin), plus the three
4594
+ # remote-pair libs the bridge's entwurf_v2 handler and that entry import.
4595
+ "mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js"
4596
+ "mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js"
4597
+ "mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js"
4598
+ "mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-transport.js"
4475
4599
  # #87 Bundle B — omp-receive-doctor reaches this through the installed dispatcher.
4476
4600
  "mcp/entwurf-bridge/dist/scripts/omp-receive-facts.js"
4477
4601
  # 0.12.5 — node_modules-safe plugin hook + lib (see check-pack). The installed
@@ -7378,6 +7502,9 @@ case "$cmd" in
7378
7502
  check-control-send-receipt)
7379
7503
  check_control_send_receipt
7380
7504
  ;;
7505
+ check-win32-preview)
7506
+ check_win32_preview
7507
+ ;;
7381
7508
  check-entwurf-v2-native-push)
7382
7509
  check_entwurf_v2_native_push
7383
7510
  ;;
@@ -8311,6 +8438,21 @@ case "$cmd" in
8311
8438
  shift || true
8312
8439
  run_ts scripts/meta-facts.ts "$@"
8313
8440
  ;;
8441
+ check-remote-pair-installed)
8442
+ # #107 rail 3, CHECKOUT-ONLY install gate: installs a caller-supplied candidate tarball into a
8443
+ # sandboxed consumer prefix and drives that install's bridge and entry through the T2 scene,
8444
+ # auditing that the subject processes resolve nothing outside the prefix. Never repacks.
8445
+ shift || true
8446
+ run_ts scripts/check-remote-pair-installed.ts "$@"
8447
+ ;;
8448
+ remote-pair-receive)
8449
+ # #107 rail 1: the receiving host's entry for one declared remote pair. The pair
8450
+ # check runs before the v2 dispatch, so a claim outside the pair mutates nothing;
8451
+ # a match reaches the same record/owner/deliverability checks every local send does.
8452
+ # Installed, run_ts runs its compiled twin; the sending host's ssh reaches it through the bin.
8453
+ shift || true
8454
+ run_ts scripts/remote-pair-receive.ts "$@"
8455
+ ;;
8314
8456
  peer-facts)
8315
8457
  # #116 M2-a READ-ONLY projection of the OBSERVED peer listing: one provider
8316
8458
  # (`listEntwurfFacts`) through one renderer (`renderEntwurfPeers().payload`), so
@@ -8380,6 +8522,9 @@ case "$cmd" in
8380
8522
  check-pi-queue-oracle)
8381
8523
  check_pi_queue_oracle
8382
8524
  ;;
8525
+ check-remote-pair-installed-audit)
8526
+ check_remote_pair_installed_audit
8527
+ ;;
8383
8528
  check-demo-reproducibility)
8384
8529
  check_demo_reproducibility
8385
8530
  ;;
@@ -8489,6 +8634,9 @@ case "$cmd" in
8489
8634
  check-pack-pin-matcher)
8490
8635
  check_pack_pin_matcher
8491
8636
  ;;
8637
+ check-pack-win32-carrier)
8638
+ check_pack_win32_carrier
8639
+ ;;
8492
8640
  check-pack-consumer-roots)
8493
8641
  check_pack_consumer_roots
8494
8642
  ;;
@@ -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 existsSync-probes the canonical socket via the shared grammar (controlSocketPathIn(ENTWURF_DIR, …))",
267
- /existsSync\s*\(/.test(piBody) && /controlSocketPathIn\s*\(\s*ENTWURF_DIR\s*,/.test(piBody),
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 existsSync-probes the pi socket (alive vs expected, no synthesized path lie)",
284
- /existsSync\s*\(/.test(selfRegion),
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": 3,
3491
+ "pack-install": 5,
3492
3492
  "pi-package-ownership": 8,
3493
3493
  "qualification-ledger": 42,
3494
3494
  "mux-resume-call": 10,
@@ -3497,6 +3497,12 @@ let manifestCount: number;
3497
3497
  "omp-receive": 11,
3498
3498
  "probe-ordering": 1,
3499
3499
  "release-gate": 21,
3500
+ "remote-pair-display": 3,
3501
+ "remote-pair-installed-audit": 2,
3502
+ "remote-pair-receive": 8,
3503
+ "remote-pair-roundtrip": 5,
3504
+ "remote-pair-send": 12,
3505
+ "remote-pair-transport": 4,
3500
3506
  "retired-sent-surface": 2,
3501
3507
  "resume-args": 6,
3502
3508
  "resume-launch-identity": 7,
@@ -3506,6 +3512,7 @@ let manifestCount: number;
3506
3512
  "typing-call-fence": 1,
3507
3513
  "v2-surface": 13,
3508
3514
  "v2-visible-resume": 18,
3515
+ "win32-preview": 19,
3509
3516
  };
3510
3517
  const laneTally: Record<string, number> = {};
3511
3518
  for (const man of manifests) laneTally[man.lane] = (laneTally[man.lane] ?? 0) + man.mutants.length;
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-remote-pair-installed — #107 rail 3: the remote pair on an INSTALLED package.
4
+ *
5
+ * `./run.sh check-remote-pair-installed --tgz <absolute candidate tarball>`
6
+ *
7
+ * Why this is a scripts/ gate and not a source-adjacent vitest test: its subject is an ARTIFACT.
8
+ * It installs a caller-supplied tarball into a fresh consumer prefix with npm and then drives
9
+ * that install's own processes (its bridge `start.sh` → compiled dist, its `run.sh` → run_ts →
10
+ * compiled entry). A test beside the source can only reach the source.
11
+ *
12
+ * What it does, once, with every writable root (HOME, npm cache/userconfig, XDG, PI agent dir)
13
+ * inside one temp sandbox:
14
+ * 1. copies the given tarball into the sandbox and installs THAT copy (never repacks; the copy's
15
+ * sha256 is printed). npm reads the package's declared dependencies from the registry into the
16
+ * sandbox cache — a network read, but no model, no operator config and no ssh;
17
+ * 2. checks the installed package carries the receive entry's compiled twin and the remote libs;
18
+ * 3. runs the installed `entwurf remote-pair-receive` with wrong argv: usage on stderr, exit 2 —
19
+ * the compiled twin was reached through run_ts, not a TS crossing;
20
+ * 4. runs the existing T2 scene (pi-extensions/lib/remote-pair-roundtrip.test.ts) with its subject
21
+ * switched to this install: real installed bridge, stand-in ssh → installed run.sh → installed
22
+ * entry → production dispatch, with the same mailbox/provenance/receipt/reverse-dispatch oracles;
23
+ * 5. audits checkout invisibility: a preload resolve hook records every module each SUBJECT
24
+ * process resolves; every file must sit (by real path) inside the consumer prefix. The
25
+ * orchestrator (vitest + its fixture writer) is this checkout and is not audited;
26
+ * 6. control: the same hook on this checkout's own `run.sh remote-pair-receive` must report a
27
+ * checkout path, so the audit can see the thing it refuses.
28
+ *
29
+ * The subject switch (ENTWURF_REMOTE_PAIR_INSTALLED_PKG and the resolve-hook variables) is set
30
+ * here and only here: it changes what the T2 cells measure, so it must not be set ambiently.
31
+ *
32
+ * Mutation proof covers only the audit's acceptance primitive (scripts/lib/remote-pair-installed-audit.ts);
33
+ * the install, compiled-surface, twin and roundtrip cells are actual-runtime evidence with the
34
+ * checkout control, not mutation-qualified.
35
+ *
36
+ * Not claimed: the published npm artifact (this is whatever tarball the caller passes), real sshd,
37
+ * a real remote host, a durable host reading or replying, or the check-pack/check-pack-install floor.
38
+ */
39
+
40
+ import { spawnSync } from "node:child_process";
41
+ import { createHash } from "node:crypto";
42
+ import * as fs from "node:fs";
43
+ import * as os from "node:os";
44
+ import * as path from "node:path";
45
+ import { fileURLToPath } from "node:url";
46
+ import { filesOutsideRoot as outside } from "./lib/remote-pair-installed-audit.ts";
47
+
48
+ const LABEL = "check-remote-pair-installed";
49
+ const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
50
+
51
+ const args = process.argv.slice(2);
52
+ if (args.length !== 2 || args[0] !== "--tgz" || !path.isAbsolute(args[1])) {
53
+ console.error(`usage: ./run.sh ${LABEL} --tgz <absolute candidate tarball>`);
54
+ process.exit(2);
55
+ }
56
+ const tgz = args[1];
57
+ if (!fs.lstatSync(tgz).isFile()) {
58
+ console.error(`${LABEL}: --tgz must be a regular file (${tgz})`);
59
+ process.exit(2);
60
+ }
61
+
62
+ let passed = 0;
63
+ let failed = 0;
64
+ function ok(label: string, cond: boolean, detail = ""): void {
65
+ if (cond) {
66
+ passed++;
67
+ console.log(` ok ${label}`);
68
+ } else {
69
+ failed++;
70
+ console.log(` FAIL ${label}${detail ? `\n ${detail}` : ""}`);
71
+ }
72
+ }
73
+
74
+ const sandbox = fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-remote-pair-installed-"));
75
+ const consumer = path.join(sandbox, "consumer");
76
+ const npmHome = path.join(sandbox, "npm-home");
77
+ const logs = path.join(sandbox, "resolve-logs");
78
+ const hook = path.join(sandbox, "resolve-hook.mjs");
79
+ for (const d of [consumer, npmHome, logs]) fs.mkdirSync(d, { recursive: true });
80
+
81
+ function sandboxEnv(home: string): NodeJS.ProcessEnv {
82
+ return {
83
+ PATH: process.env.PATH ?? "",
84
+ HOME: home,
85
+ XDG_CONFIG_HOME: path.join(home, ".config"),
86
+ XDG_DATA_HOME: path.join(home, ".local", "share"),
87
+ XDG_STATE_HOME: path.join(home, ".local", "state"),
88
+ XDG_CACHE_HOME: path.join(home, ".cache"),
89
+ PI_CODING_AGENT_DIR: path.join(home, ".pi", "agent"),
90
+ };
91
+ }
92
+
93
+ fs.writeFileSync(
94
+ hook,
95
+ [
96
+ 'import { appendFileSync } from "node:fs";',
97
+ 'import { registerHooks } from "node:module";',
98
+ "const log = process.env.ENTWURF_REMOTE_PAIR_RESOLVE_LOG;",
99
+ "if (log) {",
100
+ "\tregisterHooks({",
101
+ "\t\tresolve(specifier, context, next) {",
102
+ "\t\t\tconst r = next(specifier, context);",
103
+ '\t\t\tappendFileSync(log, r.url + "\\n");',
104
+ "\t\t\treturn r;",
105
+ "\t\t},",
106
+ "\t});",
107
+ "}",
108
+ "",
109
+ ].join("\n"),
110
+ );
111
+
112
+ function hookEnv(label: string): NodeJS.ProcessEnv {
113
+ return { NODE_OPTIONS: `--import=${hook}`, ENTWURF_REMOTE_PAIR_RESOLVE_LOG: path.join(logs, `${label}.log`) };
114
+ }
115
+
116
+ // The artifact is a snapshot: copy the caller's file into the sandbox once, then hash and install
117
+ // THAT copy, so what is measured is exactly what is installed. Never repacked.
118
+ const snapshot = path.join(sandbox, "artifact", path.basename(tgz));
119
+ fs.mkdirSync(path.dirname(snapshot), { recursive: true });
120
+ fs.copyFileSync(tgz, snapshot);
121
+ const sha = createHash("sha256").update(fs.readFileSync(snapshot)).digest("hex");
122
+ console.log(
123
+ `[${LABEL}] caller tarball ${tgz}\n[${LABEL}] snapshot ${snapshot}\n[${LABEL}] snapshot sha256 ${sha}\n[${LABEL}] sandbox ${sandbox}`,
124
+ );
125
+
126
+ try {
127
+ // 1. Install as-is.
128
+ fs.writeFileSync(
129
+ path.join(consumer, "package.json"),
130
+ JSON.stringify({ name: "remote-pair-consumer", private: true }),
131
+ );
132
+ const npm = spawnSync("npm", ["install", snapshot, "--no-audit", "--no-fund"], {
133
+ cwd: consumer,
134
+ encoding: "utf8",
135
+ env: {
136
+ ...sandboxEnv(npmHome),
137
+ npm_config_cache: path.join(sandbox, "npm-cache"),
138
+ npm_config_userconfig: path.join(npmHome, ".npmrc"),
139
+ },
140
+ });
141
+ ok(
142
+ "[QK:RPI-INSTALLED] npm installs the given tarball into a fresh consumer prefix",
143
+ npm.status === 0,
144
+ npm.stderr.slice(-800),
145
+ );
146
+ const pkg = path.join(consumer, "node_modules", "@junghanacs", "entwurf");
147
+ const pkgStat = fs.existsSync(pkg) ? fs.lstatSync(pkg) : null;
148
+ ok(
149
+ "[QK:RPI-INSTALLED] the package is a real directory inside the consumer (not a link to a checkout)",
150
+ pkgStat !== null && pkgStat.isDirectory() && !pkgStat.isSymbolicLink(),
151
+ );
152
+ if (failed) throw new Error("install failed; later cells would not measure the artifact");
153
+
154
+ // 2. The compiled surface is in the package.
155
+ for (const rel of [
156
+ "mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js",
157
+ "mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js",
158
+ "mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js",
159
+ "mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-transport.js",
160
+ ]) {
161
+ ok(`[QK:RPI-COMPILED-SURFACE] the installed package carries ${rel}`, fs.existsSync(path.join(pkg, rel)));
162
+ }
163
+
164
+ // 3. The installed bin reaches the compiled twin.
165
+ const home = path.join(sandbox, "usage-home");
166
+ const usage = spawnSync(path.join(pkg, "run.sh"), ["remote-pair-receive"], {
167
+ encoding: "utf8",
168
+ env: { ...sandboxEnv(home), ...hookEnv("installed-usage") },
169
+ });
170
+ ok(
171
+ "[QK:RPI-INSTALLED-ENTRY-TWIN] the installed `entwurf remote-pair-receive` answers wrong argv with usage on stderr, exit 2, no receipt",
172
+ usage.status === 2 && /usage: entwurf remote-pair-receive/.test(usage.stderr) && usage.stdout === "",
173
+ `status=${usage.status} stdout=${usage.stdout.slice(0, 200)} stderr=${usage.stderr.slice(0, 300)}`,
174
+ );
175
+
176
+ // 4. The T2 scene against the install.
177
+ const vitest = spawnSync(
178
+ path.join(REPO, "node_modules", ".bin", "vitest"),
179
+ ["run", "pi-extensions/lib/remote-pair-roundtrip.test.ts"],
180
+ {
181
+ cwd: REPO,
182
+ encoding: "utf8",
183
+ env: {
184
+ ...process.env,
185
+ ENTWURF_REMOTE_PAIR_INSTALLED_PKG: pkg,
186
+ ENTWURF_REMOTE_PAIR_RESOLVE_HOOK: hook,
187
+ ENTWURF_REMOTE_PAIR_RESOLVE_LOG_DIR: logs,
188
+ },
189
+ },
190
+ );
191
+ const summary = (vitest.stdout.match(/Tests\s+.*$/m) ?? [""])[0].trim();
192
+ ok(
193
+ "[QK:RPI-ROUNDTRIP-ON-INSTALL] the T2 cells (A→B, reverse dispatch, opt-in absent off, other host refuses, both socket dirs) pass against the installed bridge and entry",
194
+ vitest.status === 0 && /^Tests\s+5 passed\b/.test(summary),
195
+ `${summary}\n${vitest.stdout
196
+ .split("\n")
197
+ .filter((l) => /×|FAIL|Error/.test(l))
198
+ .slice(0, 10)
199
+ .join("\n")}`,
200
+ );
201
+
202
+ // 5. Checkout invisibility of the subject processes.
203
+ const all = fs.readdirSync(logs).map((n) => path.join(logs, n));
204
+ const bridgeLogs = all.filter((f) => path.basename(f).startsWith("bridge-"));
205
+ const entryLogs = all.filter((f) => path.basename(f).startsWith("entry-"));
206
+ const subjectLogs = [...bridgeLogs, ...entryLogs, ...all.filter((f) => path.basename(f) === "installed-usage.log")];
207
+ ok(
208
+ "[QK:RPI-AUDIT-SAW-SUBJECTS] the resolve hook recorded the installed bridge processes and the installed entry processes",
209
+ bridgeLogs.some((f) => fs.statSync(f).size > 0) && entryLogs.some((f) => fs.statSync(f).size > 0),
210
+ `bridge logs=${bridgeLogs.length} entry logs=${entryLogs.length}`,
211
+ );
212
+ const audit = outside(subjectLogs, consumer);
213
+ ok(
214
+ `[QK:RPI-CHECKOUT-INVISIBLE] every module the installed subject processes resolved (${audit.total}) sits inside the consumer prefix`,
215
+ audit.total > 0 && audit.outside.length === 0,
216
+ audit.outside.slice(0, 10).join("\n "),
217
+ );
218
+
219
+ // 6. Control: the audit sees a checkout when there is one.
220
+ const control = spawnSync(path.join(REPO, "run.sh"), ["remote-pair-receive"], {
221
+ encoding: "utf8",
222
+ env: { ...sandboxEnv(path.join(sandbox, "control-home")), ...hookEnv("control-checkout") },
223
+ });
224
+ const controlAudit = outside([path.join(logs, "control-checkout.log")], consumer);
225
+ const realRepo = fs.realpathSync(REPO);
226
+ ok(
227
+ "[QK:RPI-AUDIT-CONTROL] the same audit on this checkout's own entry reports checkout paths (the detector is live)",
228
+ control.status === 2 && controlAudit.outside.some((p) => p.startsWith(`${realRepo}${path.sep}`)),
229
+ `status=${control.status} outside=${controlAudit.outside.length}`,
230
+ );
231
+ } catch (err) {
232
+ ok(`[${LABEL}] gate ran to completion`, false, err instanceof Error ? err.message : String(err));
233
+ } finally {
234
+ // Synthetic fixture data only. Kept on failure so the resolve logs and npm output can be read.
235
+ if (failed === 0) fs.rmSync(sandbox, { recursive: true, force: true });
236
+ else console.log(`[${LABEL}] kept the sandbox for diagnosis: ${sandbox}`);
237
+ }
238
+
239
+ console.log(
240
+ `\n${LABEL}: ${passed} passed, ${failed} failed — installed artifact ${sha.slice(0, 12)}; no real ssh, no model`,
241
+ );
242
+ process.exit(failed === 0 ? 0 : 1);
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The acceptance primitive of `check-remote-pair-installed`'s checkout-invisibility cell (#107 rail 3),
3
+ * as a pure function over resolve-hook logs so it can be proven on tiny synthetic files
4
+ * (test/remote-pair-installed-audit.test.ts). Each log line is one URL a subject process resolved.
5
+ * A `file:` URL counts and is judged by its REAL path; anything else (node: builtins) is ignored.
6
+ */
7
+ import * as fs from "node:fs";
8
+ import * as path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ /** Every file the logs name that is NOT inside `root` (both compared by real path), and how many file URLs were read. */
12
+ export function filesOutsideRoot(logFiles: readonly string[], root: string): { total: number; outside: string[] } {
13
+ const realRoot = fs.realpathSync(root);
14
+ const out = new Set<string>();
15
+ let total = 0;
16
+ for (const f of logFiles) {
17
+ for (const url of fs.readFileSync(f, "utf8").split("\n").filter(Boolean)) {
18
+ if (!url.startsWith("file:")) continue;
19
+ total++;
20
+ const real = fs.realpathSync(fileURLToPath(url));
21
+ if (real !== realRoot && !real.startsWith(`${realRoot}${path.sep}`)) out.add(real);
22
+ }
23
+ }
24
+ return { total, outside: [...out].sort() };
25
+ }
@@ -166,10 +166,10 @@
166
166
  "title": "the pi queue oracle's public coordinate is dropped from `check:vitest`, so the file that proves every claim the receipt makes about a receiver's queues is green only when somebody runs `pnpm test` by hand — the discovery door deliberately does not select `test/**`, so nothing else would notice",
167
167
  "subject": "package.json",
168
168
  "find": [
169
- "\"check:vitest\": \"./run.sh check-mux-fresh-call && ./run.sh check-acp-sdk-surface && ./run.sh check-pi-queue-oracle && ./run.sh check-tests-beside-behavior\""
169
+ "\"check:vitest\": \"./run.sh check-mux-fresh-call && ./run.sh check-acp-sdk-surface && ./run.sh check-pi-queue-oracle && ./run.sh check-tests-beside-behavior && ./run.sh check-remote-pair-installed-audit\""
170
170
  ],
171
171
  "replace": [
172
- "\"check:vitest\": \"./run.sh check-mux-fresh-call && ./run.sh check-acp-sdk-surface && ./run.sh check-tests-beside-behavior\""
172
+ "\"check:vitest\": \"./run.sh check-mux-fresh-call && ./run.sh check-acp-sdk-surface && ./run.sh check-tests-beside-behavior && ./run.sh check-remote-pair-installed-audit\""
173
173
  ],
174
174
  "gate": ["bash", "run.sh", "check-tests-beside-behavior"],
175
175
  "timeoutSeconds": 600,
@@ -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 — @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)",
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 — @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)",
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
  }