OverviewFilesCommitsIssuesDocsReleasesPeers

pgh-sidecar commits / 19a73d0

0.2.49: pin bare-runtime exactly + guard against sibling-install drift
Counterpart to pgh 0.1.127. node_modules/pgh here is a SYMLINK to ../../pgh, and `pgh publish` builds .pgh-lock.json by walking the physically installed tree, so the walk crosses that symlink. Both repos declared bare-runtime@^1.28.5, were npm-installed at different times, and drifted to 1.28.7 (here) vs 1.28.6 (there) -- putting two ~90MB runtimes in one bootstrap tarball. Pinned exactly, matching pgh, with an overrides entry that agrees with the direct dep (npm EOVERRIDEs an override that contradicts one). test/runtime-dedupe.test.js is the drift guard. An exact pin only holds if BOTH repos are repinned together on an upgrade; repin one and the duplicate silently returns and costs 90 MiB again. The guard walks the resolved tree THROUGH the file:-dep symlinks (as the publish walk does) and fails if any bare-runtime package appears at two versions. It also asserts the pin is exact rather than a range, that the overrides entry agrees with the direct dep, and that the sibling pgh checkout pins the same version. Verified it actually detects the bug: the guard FAILED before `npm install` in pgh (2/3) and passes after (3/3). A guard never seen red is not a guard. package-lock.json synced with --package-lock-only so the lock's declared range and overrides match package.json; node_modules was left untouched (already 1.28.7, so nothing to reinstall here). test: 3/3 runtime-dedupe. Full suite 30/31 -- the one failure is in sidecar.test.js's hyperident-vault identity assertion, which the test's own comment documents as machine-dependent and which reproduces with these changes set aside. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
19a73d0384856fd913d621433b260c0809bc5157
by plankatron <fleeky@gmail.com> · 2026-07-30T00:39:28.000Z

Changed files (3) +112 -22

package-lock.json +15 -19
……@@ -1,12 +1,12 @@
11{
22 "name": "pgh-sidecar",
3 "version": "0.2.45",
3 "version": "0.2.48",
44 "lockfileVersion": 3,
55 "requires": true,
66 "packages": {
77 "": {
88 "name": "pgh-sidecar",
9 "version": "0.2.45",
9 "version": "0.2.48",
1010 "license": "AGPL-3.0",
1111 "dependencies": {
1212 "b4a": "^1.6.0",
……@@ -16,7 +16,7 @@
1616 "bare-os": "^3.0.0",
1717 "bare-path": "^3.0.0",
1818 "bare-process": "^4.0.0",
19 "bare-runtime": "^1.28.5",
19 "bare-runtime": "1.28.7",
2020 "bare-stream": "^2.0.0",
2121 "bare-subprocess": "^6.0.0",
2222 "bare-url": "^2.0.0",
……@@ -44,7 +44,7 @@
4444 }
4545 },
4646 "../hyper-deploy": {
47 "version": "0.4.9",
47 "version": "0.4.10",
4848 "dependencies": {
4949 "compact-encoding": "^3.2.0",
5050 "corestore": "^7.0.0",
……@@ -61,41 +61,37 @@
6161 }
6262 },
6363 "../hyper-overlay": {
64 "version": "0.2.14",
64 "version": "0.3.12",
6565 "license": "AGPL-3.0",
6666 "dependencies": {
67 "@hyperswarm/testnet": "^3.1.4",
6867 "b4a": "^1.0.0",
6968 "bare-fs": "^4.7.2",
70 "bare-http1": "^4.5.5",
71 "bare-native": "^0.1.2",
7269 "bare-os": "^3.8.7",
7370 "bare-path": "^3.0.0",
74 "bare-process": "^4.4.1",
7571 "bare-zlib": "^1.3.3",
76 "blind-pairing": "^2.3.1",
7772 "compact-encoding": "^3.2.0",
7873 "corestore": "^7.0.0",
7974 "hyperbee": "^2.0.0",
8075 "hypercore-crypto": "^3.0.0",
81 "hyperswarm": "^4.0.0",
82 "localwatch": "^1.4.3",
8376 "protomux": "^3.0.0",
84 "quickbit-universal": "^2.0.0",
8577 "ready-resource": "^1.0.0",
78 "sodium-universal": "^5.0.1",
8679 "streamx": "^2.0.0"
8780 },
8881 "devDependencies": {
89 "brittle": "^3.0.0"
82 "@hyperswarm/testnet": "^3.1.4",
83 "brittle": "^3.0.0",
84 "hyperswarm": "^4.0.0"
9085 },
91 "optionalDependencies": {
92 "fuse-native": "^2.2.6"
86 "engines": {
87 "bare": ">=1.16.0"
9388 }
9489 },
9590 "../hyper-svc": {
96 "version": "0.1.5",
91 "version": "0.1.7",
9792 "dependencies": {
9893 "b4a": "^1.6.0",
94 "bare-abstract-socket": "^1.0.0",
9995 "bare-events": "^2.0.0",
10096 "bare-fs": "^4.7.2",
10197 "bare-http1": "^4.0.0",
……@@ -123,7 +119,7 @@
123119 }
124120 },
125121 "../pgh": {
126 "version": "0.1.109",
122 "version": "0.1.126",
127123 "license": "AGPL-3.0",
128124 "dependencies": {
129125 "autobase": "^7.0.0",
……@@ -137,7 +133,7 @@
137133 "bare-os": "^3.0.0",
138134 "bare-path": "^3.0.0",
139135 "bare-process": "^4.0.0",
140 "bare-runtime": "^1.28.5",
136 "bare-runtime": "1.28.7",
141137 "bare-stream": "^2.0.0",
142138 "bare-subprocess": "^6.0.0",
143139 "bare-url": "^2.0.0",
package.json +4 -3
……@@ -1,6 +1,6 @@
11{
22 "name": "pgh-sidecar",
3 "version": "0.2.48",
3 "version": "0.2.49",
44 "description": "Small optional user-level daemon for pgh \u2014 shared identity vault, OTA polling, device linking, bootstrap seed, NameRegistry host",
55 "type": "module",
66 "main": "lib/index.js",
……@@ -38,7 +38,7 @@
3838 "bare-os": "^3.0.0",
3939 "bare-path": "^3.0.0",
4040 "bare-process": "^4.0.0",
41 "bare-runtime": "^1.28.5",
41 "bare-runtime": "1.28.7",
4242 "bare-stream": "^2.0.0",
4343 "bare-subprocess": "^6.0.0",
4444 "bare-url": "^2.0.0",
……@@ -104,6 +104,7 @@
104104 }
105105 },
106106 "overrides": {
107 "udx-native": "^1.20.3"
107 "udx-native": "^1.20.3",
108 "bare-runtime": "1.28.7"
108109 }
109110}
test/runtime-dedupe.test.js +93 -0
……@@ -0,0 +1,93 @@
1import test from 'brittle'
2import { readdirSync, readFileSync, existsSync, statSync } from '#node:fs'
3import { join, dirname } from '#node:path'
4import { fileURLToPath } from '#node:url'
5
6// Drift guard for the duplicated-runtime bug.
7//
8// `pgh publish` builds .pgh-lock.json by WALKING the physically installed
9// node_modules (lib/publish.js: "Walks node_modules/ of an app dir, computes a
10// tree hash per top-level dep") -- it does not re-resolve version ranges. So the
11// tarball contains whatever is on disk at publish time.
12//
13// node_modules/pgh is a SYMLINK to the sibling ../../pgh repo, which has its own
14// separately-installed node_modules. Both repos declared bare-runtime@^1.28.5 and
15// were installed at different times, so they drifted apart:
16//
17// pgh-sidecar/node_modules → bare-runtime@1.28.7
18// pgh/node_modules → bare-runtime@1.28.6
19//
20// The publish walk followed the symlink and picked up BOTH, putting two ~90MB
21// runtimes in one bootstrap tarball. Measured cost: 90.0 MiB of a 786.1 MiB tree,
22// 53% of all reclaimable duplication across 47 duplicated packages.
23//
24// Both repos now pin bare-runtime EXACTLY. An exact pin only holds if BOTH are
25// repinned together on an upgrade -- repin one and the duplicate silently returns.
26// This test is what makes that failure loud instead of silent.
27
28const here = dirname(fileURLToPath(import.meta.url))
29const root = join(here, '..')
30
31// Collect name@version for every bare-runtime* package reachable from a tree,
32// following the file:-dep symlinks that the publish walk also follows.
33function collectRuntimes (nmDir, found = new Map(), depth = 0) {
34 if (depth > 2 || !existsSync(nmDir)) return found
35 let entries = []
36 try { entries = readdirSync(nmDir) } catch { return found }
37 for (const entry of entries) {
38 if (entry.startsWith('.')) continue
39 const dir = join(nmDir, entry)
40 const pkgPath = join(dir, 'package.json')
41 if (existsSync(pkgPath)) {
42 try {
43 const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
44 if (pkg && /^bare-runtime/.test(pkg.name || '') && pkg.version) {
45 if (!found.has(pkg.name)) found.set(pkg.name, new Set())
46 found.get(pkg.name).add(pkg.version)
47 }
48 } catch {}
49 }
50 // Recurse into nested node_modules AND through symlinked file: deps —
51 // the publish walk resolves both, so the guard must too.
52 let isDir = false
53 try { isDir = statSync(dir).isDirectory() } catch {}
54 if (isDir) collectRuntimes(join(dir, 'node_modules'), found, depth + 1)
55 }
56 return found
57}
58
59test('bare-runtime is pinned to an exact version (not a floating range)', (t) => {
60 const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
61 const direct = (pkg.dependencies || {})['bare-runtime']
62 const override = (pkg.overrides || {})['bare-runtime']
63 t.ok(direct, 'declared as a direct dependency')
64 t.absent(/[\^~*x]|\s-\s/.test(direct || ''), `direct dep "${direct}" is exact, not a range`)
65 t.is(override, direct, 'overrides entry agrees with the direct dep (npm EOVERRIDEs if they conflict)')
66})
67
68test('the installed tree resolves bare-runtime to exactly ONE version', (t) => {
69 const found = collectRuntimes(join(root, 'node_modules'))
70 t.ok(found.size > 0, 'found bare-runtime packages in the installed tree')
71 const dupes = [...found].filter(([, versions]) => versions.size > 1)
72 for (const [name, versions] of dupes) {
73 t.fail(`${name} resolved to ${versions.size} versions: ${[...versions].sort().join(', ')} — ` +
74 'the publish walk will put all of them in the bootstrap tarball (~90MB each). ' +
75 'Repin bare-runtime to the SAME exact version in BOTH pgh and pgh-sidecar, then npm install in each.')
76 }
77 t.is(dupes.length, 0, 'no bare-runtime package is duplicated across the tree')
78})
79
80test('the sibling pgh repo pins the same exact version', (t) => {
81 // The sidecar symlinks node_modules/pgh -> ../../pgh, so a mismatch here is
82 // precisely how the duplicate gets reintroduced on the next upgrade.
83 const siblingPkg = join(root, '..', 'pgh', 'package.json')
84 if (!existsSync(siblingPkg)) {
85 t.pass('sibling pgh checkout not present — skipped')
86 return
87 }
88 const mine = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
89 const theirs = JSON.parse(readFileSync(siblingPkg, 'utf8'))
90 const a = (mine.dependencies || {})['bare-runtime']
91 const b = (theirs.dependencies || {})['bare-runtime']
92 t.is(b, a, `pgh pins "${b}", pgh-sidecar pins "${a}" — they MUST match or publish ships both`)
93})