chore(plan): cancel end-to-end seeder-to-booter smoke harness task
test(protocol): add deterministic MBR failover-key coverage docs(protocol): align package-bare-os-protocol version to 0.9.1 test(booter): add MBR corruption and wrong-topic smoke fixtures test(peer-seed): add strict pre-MBR bare_os.capabilities negotiation check feat(seeder): validate BARE_OS_SEED_REQUIRE_MBR_LABELS feat(seeder): validate BARE_OS_SEED_CAPABILITY_ATTESTATION_JSON schema docs(boot-policy): add requireProtocolPackageMin 0.9.1 example test(kernel): cover boot.policy denySeedRpcMethods behavior test(protocol): add app/cap/chat/meshdrop channel compatibility fixture test(swarm-disk): cover duplicate Protomux channel null-return path test(protocol): add 11-word kernelCapabilityWords round-trip fixture docs(schema): add mbr-layout schema and validate seeder examples test(protocol): add topicKey() golden hash fixture docs(trust): document block-0 trust assumptions in boot docs feat(seeder): add discovery.flushed readiness logging feat(booter): record peer discovery timings in boot-perf.json feat(integration): add local testnet mode to integration lab smoke test(booter): add Hyperswarm connection-budget env regression coverage test(booter): add swarm plus Corestore suspend/resume integration coverage feat(booter): mirror swarm ban events into host audit logs feat(booter): add direct-peer boot via BARE_OS_BOOT_JOIN_PEER_HEX feat(seeder): pass BARE_OS_SEED_MAX_PEERS to Hyperswarm feat(seeder): log drive.version and discoveryKey at startup test(booter): add Hyperdrive.checkout read-only boot probe coverage feat(booter): prefetch /boot/init.js before kernel handoff feat(booter): add optional /bin warm replication via downloadDiff feat(seeder): add manifestPaths SHA-256 generation in stage-kernel-tree test(peer-seed): cover helper-served block-0 after seeder exit feat(protocol): add Protomux cork batching for initial channel sends test(boot-graph): compare kernel/init labels with booter graph proc docs(boot-policy): add v9-v11 schema examples feat(release): add requireInitJsSha256 fixture generation step test(vfs): add BARE_OS_VFS_SYSTEM_RO_ALIAS coverage test(vfs): strengthen system-drive write-deny path coverage feat(identity): add personal-drive namespace export/import docs and tests test(booter): add guest-to-login warm cache invalidation regression test(vfs): add guest deny coverage for /.bare sensitive paths test(coreutils): add cross-drive mv failure injection coverage test(vfs): add .bareos_empty round-trip coverage across mkdir/rmdir/cp/git-fs test(vfs): add /dev/shm quota enforcement coverage test(proc): add /proc/bare_os/index.json sortedness and schema checks test(vfs): add warm read cache invalidation on replication growth docs(ctx): document bareOsInvalidateWarmReadCaches(reason) test(kernel): add BARE_OS_BOOT_DRY_RUN behavior coverage docs(posix): add dashboard rows for all COREUTILS_COMMANDS feat(curl): expand -w variables beyond http_code/url_effective/size_download feat(wget): mark -N timestamping as explicit unsupported error feat(curl): plumb mutual TLS cert/key intent to ctx.httpFetch metadata feat(shuf): add deterministic seed mode via BARE_OS_SHUF_SEED docs(sort): document -M month-sort as unsupported feat(grep): add explicit -E and -G mode handling test(sed): add Open Group Issue 7 golden fixtures test(awk): add getline VFS regressions for missing/repeat/boundary cases test(shell): add non-interactive here-doc coverage test(shell): add trap delivery coverage for synthetic PIDs/job IDs test(shell): add set -e compound-body behavior coverage docs(shell): strengthen read builtin opt-in guidance test(env): add Bare-runtime coverage for -S and --env-file docs(man): add examples for pathcap-verify pkg-swarm-index corestorectl test(identity): add account/vault backup-restore smoke coverage feat(audit): add tamper detection verification for audit chain rows test(peer-admission): cover strict empty allowlist deny behavior test(peer-admission): add denylist precedence over allowlist coverage test(peer-admission): add BARE_OS_PEER_REQUIRE_CAPS_JSON metadata checks docs(identity): add trusted-key rotation example for path capabilities feat(schema): tighten extensionSignerPinsV2-V4 hash validation test(delegate): add allowlist negative cases for curl/wget/git/hrpc/systemctl test(proc): extend /proc/self/environ redaction key coverage docs(security): add peer-assisted block-0 mirroring threat-model notes feat(bench): add boot budget trend output from real booter phases test(baretop): align fixture coverage with /proc snapshot key set test(metrics): validate /proc/bare_os/metrics.prom OpenMetrics shape docs(ops): add structured seeder NDJSON examples test(replication): add live stall-hint coverage for no_peers/length_unavailable/ok docs(release): add corestore-snapshot workflow to checklist docs(ops): add mirror-drive experiment utility to maintainer workflow test(booter): add monitor progress coverage for replication live sketch feat(seeder): validate DHT bootstrap address class JSON inputs docs(network): add HYPERSWARM_BOOTSTRAP testnet operator guidance chore(root): add deterministic test:integration script docs(ci): add local CI runbook for no-.github environments docs(release): add npm run test:bare after npm test feat(verify): add protocol docs/package version parity checker feat(verify): enforce feature-roadmap canonical path consistency feat(lockfile-drift): add tier-1 strict fail option for mismatches docs(lockfile-drift): add udx-native and blind-peering upgrade workflow notes docs(cli-parity): add bare-fetch upstream issue tracking row feat(bundle-health): generate per-tier bundle size regression thresholds feat(doc-contracts): verify handbook references to current proc schema versions feat(pretest): add validate-mermaid-syntax gate feat(probe): add bare-runtime top-25 critical command lane docs(protocol): update capability-word prose from bits..bits5 to current words docs(two-drive): document /tmp /var/log and account-prefix routing docs(security): add concise boot trust model page and links docs(dev-guide): add P2P lab cookbook section docs(dev-guide): add how-to for adding seed RPCs docs(dev-guide): add how-to for adding /proc/bare_os nodes docs(dev-guide): add /bin utility checklist for man/posix/build/parity/tests docs(user-manual): add short What BareOS is not section
This commit is contained in:
+22
-4
@@ -98,6 +98,12 @@ Concatenates sorted `kernel/lib/boot/*.js` + sorted `kernel/lib/init/fragments/*
|
||||
|
||||
Ensures [docs/data/feature-roadmap-canonical.json](../docs/data/feature-roadmap-canonical.json) aligns with the batch B table in **`feature-roadmap.md`**. Root **`pretest`**.
|
||||
|
||||
## `verify-feature-roadmap-paths.mjs`
|
||||
|
||||
**Usage:** `node scripts/verify-feature-roadmap-paths.mjs` (also **`npm run verify:feature-roadmap-paths`**)
|
||||
|
||||
Ensures **`docs/reference/feature-roadmap.md`** links to the canonical machine-readable source at **`docs/data/feature-roadmap-canonical.json`**. Root **`pretest`**.
|
||||
|
||||
## `verify-no-new-giant-phase-identifiers.mjs`
|
||||
|
||||
**Usage:** `node scripts/verify-no-new-giant-phase-identifiers.mjs`
|
||||
@@ -266,11 +272,23 @@ Ensures **`bare-os-ctx.d.ts`** lists documented **`ctx`** fields (including **`b
|
||||
|
||||
Scans **`docs/`**, **`handbook/`**, **`developer-guide/`**, **`users-manual/`**, **`packages/`**, **`kernel/`**, **`scripts/*.md`**, plus root **`README.md`** / **`DOCUMENTATION.md`**, for relative **`.md`** links and fails when the target file is missing. Root **`pretest`**.
|
||||
|
||||
## `verify-protocol-doc-version-parity.mjs`
|
||||
|
||||
**Usage:** `node scripts/verify-protocol-doc-version-parity.mjs` (also **`npm run verify:protocol-doc-version`**)
|
||||
|
||||
Verifies **`packages/bare-os-protocol/package.json`** version text is present in **`docs/reference/package-bare-os-protocol.md`** so package/reference docs stay in lockstep. Root **`pretest`**.
|
||||
|
||||
## `validate-mermaid-syntax.mjs`
|
||||
|
||||
**Usage:** `BARE_OS_VALIDATE_MERMAID=1 node scripts/validate-mermaid-syntax.mjs` · workflow **[`.github/workflows/docs-mermaid-smoke.yml`](../.github/workflows/docs-mermaid-smoke.yml)** (**`workflow_dispatch`** + weekly **`cron`**)
|
||||
**Usage:** `node scripts/validate-mermaid-syntax.mjs`
|
||||
|
||||
Renders every **```mermaid** fenced block (under **`docs/`**, **`handbook/`**, **`developer-guide/`**, **`users-manual/`**, **`packages/`**, **`scripts/`**, plus root **`README.md`** / **`DOCUMENTATION.md`**) via a one-off **`@mermaid-js/mermaid-cli`** install in a temp directory. **Not** part of root **`pretest`**. Locally **skips** unless `**BARE_OS_VALIDATE_MERMAID=1`**; GitHub Actions sets **`GITHUB_ACTIONS=true`** so scheduled / manual runs validate diagrams without slowing every clone.
|
||||
Checks markdown **```mermaid** blocks in **`docs/`**, **`handbook/`**, **`developer-guide/`**, and **`users-manual/`** for closed fences, non-empty bodies, and recognized Mermaid diagram starters. Root **`pretest`**.
|
||||
|
||||
## `gen-require-initjs-sha256.mjs`
|
||||
|
||||
**Usage:** `node scripts/gen-require-initjs-sha256.mjs`
|
||||
|
||||
Regenerates the expected `kernel/init.js` SHA-256 fixture used by release-contract checks that pin the stock boot entrypoint digest.
|
||||
|
||||
## `verify-man-coverage.mjs`
|
||||
|
||||
@@ -300,7 +318,7 @@ Ensures **`BARE_OS_CTX_API_VERSION`** and **`BARE_OS_POSIX_PROFILE_VERSION`** ap
|
||||
|
||||
**Usage:** `node scripts/verify-bundle-health.mjs`
|
||||
|
||||
Asserts **[`docs/audit/bundle-health.json`](../docs/audit/bundle-health.json)** (regenerated by **`npm run build -w bare-os-bare-libs`**) matches **`kernel/lib/bare/manifest.json`** and on-disk **`kernel/lib/bare/bundles/*.js`** byte sizes. Detects orphan bundle files. Root **`pretest`**.
|
||||
Asserts **[`docs/audit/bundle-health.json`](../docs/audit/bundle-health.json)** (regenerated by **`npm run build -w bare-os-bare-libs`**) matches **`kernel/lib/bare/manifest.json`** and on-disk **`kernel/lib/bare/bundles/*.js`** byte sizes. Detects orphan bundle files and enforces generated per-tier regression ceilings from **`bundleRegressionThresholds.byCtxKey`** (core / optional_js / optional_native). Root **`pretest`**.
|
||||
|
||||
## `verify-bundle-markers.mjs`
|
||||
|
||||
@@ -318,7 +336,7 @@ Writes **[`docs/audit/holepunch-clone-sync-report.json`](../docs/audit/holepunch
|
||||
|
||||
**Usage:** `npm run audit:holepunch-clones` (includes this step) or `node scripts/report-holepunch-lockfile-drift.mjs`
|
||||
|
||||
Compares hoisted **`package-lock.json`** versions (**`node_modules/<pkg>`**) against **`lockfilePackages`** in **[`docs/audit/holepunch-drift-repos.json`](../docs/audit/holepunch-drift-repos.json)** and local clone **`package.json`** versions. Writes **[`docs/audit/holepunch-lockfile-drift.json`](../docs/audit/holepunch-lockfile-drift.json)** and **`holepunch-lockfile-drift-summary.ndjson`**, plus a maintainer-readable Markdown table **[`docs/audit/holepunch-lockfile-drift-dashboard.md`](../docs/audit/holepunch-lockfile-drift-dashboard.md)**. Informational only (not a failing CI gate).
|
||||
Compares hoisted **`package-lock.json`** versions (**`node_modules/<pkg>`**) against **`lockfilePackages`** in **[`docs/audit/holepunch-drift-repos.json`](../docs/audit/holepunch-drift-repos.json)** and local clone **`package.json`** versions. Writes **[`docs/audit/holepunch-lockfile-drift.json`](../docs/audit/holepunch-lockfile-drift.json)** and **`holepunch-lockfile-drift-summary.ndjson`**, plus a maintainer-readable Markdown table **[`docs/audit/holepunch-lockfile-drift-dashboard.md`](../docs/audit/holepunch-lockfile-drift-dashboard.md)**. Default mode is informational; set **`BARE_OS_HOLEPUNCH_LOCKFILE_DRIFT_TIER1_FAIL=1`** to fail when any **`tier1Repos[]`** package has a semver mismatch against its local clone.
|
||||
|
||||
## `report-holepunch-runtime-compat.mjs`
|
||||
|
||||
|
||||
@@ -4,19 +4,44 @@
|
||||
* Run manually: `node scripts/benchmark-boot-sketch.mjs`
|
||||
* Trend path: `BARE_OS_BENCHMARK_TREND_NDJSON` or `${TMPDIR:-/tmp}/bare-os-benchmark-trend.ndjson`
|
||||
*/
|
||||
import { appendFileSync } from 'node:fs'
|
||||
import { appendFileSync, readFileSync } from 'node:fs'
|
||||
import process from 'node:process'
|
||||
|
||||
const t0 = Date.now()
|
||||
const ms = Math.max(0, Date.now() - t0)
|
||||
function readBootPerfRows() {
|
||||
const p =
|
||||
String(process.env.BARE_OS_BOOT_PERF_PATH || '').trim() ||
|
||||
'/run/bare-os/boot-perf.json'
|
||||
try {
|
||||
const raw = String(readFileSync(p, 'utf8') || '').trim()
|
||||
if (!raw) return { path: p, phases: [] }
|
||||
const j = JSON.parse(raw)
|
||||
const phases = Array.isArray(j?.phaseSamples)
|
||||
? j.phaseSamples
|
||||
.map((x) => ({
|
||||
phase: String(x?.phase || ''),
|
||||
ms: Number(x?.ms || 0)
|
||||
}))
|
||||
.filter((x) => x.phase && Number.isFinite(x.ms) && x.ms >= 0)
|
||||
: []
|
||||
return { path: p, phases }
|
||||
} catch {
|
||||
return { path: p, phases: [] }
|
||||
}
|
||||
}
|
||||
|
||||
const perf = readBootPerfRows()
|
||||
const totalMs = perf.phases.reduce((a, b) => a + b.ms, 0)
|
||||
const path =
|
||||
String(process.env.BARE_OS_BENCHMARK_TREND_NDJSON || '').trim() ||
|
||||
`${process.env.TMPDIR || '/tmp'}/bare-os-benchmark-trend.ndjson`
|
||||
const line =
|
||||
JSON.stringify({
|
||||
schema: 1,
|
||||
schema: 2,
|
||||
name: 'boot-sketch',
|
||||
wallMs: ms,
|
||||
wallMs: totalMs,
|
||||
bootPerfPath: perf.path,
|
||||
phaseCount: perf.phases.length,
|
||||
phaseTimings: perf.phases,
|
||||
atMs: Date.now()
|
||||
}) + '\n'
|
||||
try {
|
||||
@@ -24,5 +49,11 @@ try {
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
console.log('benchmark-boot-sketch: ok (trend → ' + path + ')')
|
||||
console.log(
|
||||
'benchmark-boot-sketch: ok (trend → ' +
|
||||
path +
|
||||
', phases=' +
|
||||
perf.phases.length +
|
||||
')'
|
||||
)
|
||||
process.exit(0)
|
||||
|
||||
@@ -53,6 +53,15 @@ function main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(matrix.commandIndex)) {
|
||||
for (const row of matrix.commandIndex) {
|
||||
if (!row || typeof row !== 'object') continue
|
||||
const name = String(row.name || '').trim()
|
||||
if (!name) continue
|
||||
const tier = String(row.tier || '').trim() || 'unspecified'
|
||||
push('Coreutils commandIndex', name, tier)
|
||||
}
|
||||
}
|
||||
|
||||
const body = `# POSIX compliance dashboard (generated)
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Generate a boot.policy requireInitJsSha256 fixture from kernel/init.js.
|
||||
*/
|
||||
|
||||
import { createHash } from 'node:crypto'
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const initPath = path.join(root, 'kernel', 'init.js')
|
||||
const outPath =
|
||||
process.argv[2] && String(process.argv[2]).trim()
|
||||
? path.resolve(root, String(process.argv[2]).trim())
|
||||
: path.join(
|
||||
root,
|
||||
'docs',
|
||||
'data',
|
||||
'boot-policy-require-initjs-sha256.fixture.json'
|
||||
)
|
||||
|
||||
const src = readFileSync(initPath)
|
||||
const sha256Hex = createHash('sha256').update(src).digest('hex')
|
||||
const fixture = {
|
||||
schema: 1,
|
||||
source: 'kernel/init.js',
|
||||
algorithm: 'sha256',
|
||||
requireInitJsSha256: sha256Hex,
|
||||
generatedAt: new Date().toISOString(),
|
||||
note: 'Copy requireInitJsSha256 into /etc/bare-os/boot.policy.json when pinning boot init bytes.'
|
||||
}
|
||||
|
||||
writeFileSync(outPath, JSON.stringify(fixture, null, 2) + '\n')
|
||||
console.log(
|
||||
`[gen-require-initjs-sha256] wrote ${path.relative(root, outPath)}`
|
||||
)
|
||||
console.log(`[gen-require-initjs-sha256] requireInitJsSha256=${sha256Hex}`)
|
||||
@@ -1,16 +1,228 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Integration lab smoke: extend with local Holepunch mirrors + synthetic peers.
|
||||
* Run: `node scripts/integration-lab-smoke.mjs`
|
||||
* Integration smoke: boot a real seeder + booter pair with isolated stores.
|
||||
* Runs `npm run os:seeder` and `npm run os:booter` against temp Corestores.
|
||||
*/
|
||||
import process from 'node:process'
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
schema: 1,
|
||||
ok: true,
|
||||
note: 'Wire local corestore + topic fixtures here; see developer-guide.',
|
||||
atMs: Date.now()
|
||||
})
|
||||
import { spawn } from 'node:child_process'
|
||||
import { mkdtemp, rm } from 'node:fs/promises'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const tmpBase = await mkdtemp(path.join(os.tmpdir(), 'bare-os-integration-lab-'))
|
||||
const hostData = path.join(tmpBase, 'host-data')
|
||||
const seedStore = path.join(hostData, 'corestore', 'seeder')
|
||||
const bootStore = path.join(hostData, 'corestore', 'booter')
|
||||
const timeoutMs = clampInt(process.env.BARE_OS_INTEGRATION_TIMEOUT_MS, 120000)
|
||||
const seederReadyTimeoutMs = clampInt(
|
||||
process.env.BARE_OS_INTEGRATION_SEEDER_READY_TIMEOUT_MS,
|
||||
60000
|
||||
)
|
||||
process.exit(0)
|
||||
const booterReadyTimeoutMs = clampInt(
|
||||
process.env.BARE_OS_INTEGRATION_BOOTER_READY_TIMEOUT_MS,
|
||||
90000
|
||||
)
|
||||
const testnet = resolveTestnetBootstrapEnv(process.env)
|
||||
|
||||
/** @type {{ seeder?: import('node:child_process').ChildProcess, booter?: import('node:child_process').ChildProcess }} */
|
||||
const procs = {}
|
||||
const startedAt = Date.now()
|
||||
|
||||
const baseEnv = {
|
||||
...process.env,
|
||||
BARE_OS_HOST_DATA: hostData,
|
||||
BARE_OS_SEED_STORE: seedStore,
|
||||
BARE_OS_BOOT_STORE: bootStore,
|
||||
BARE_OS_NO_SPLASH: '1',
|
||||
BARE_OS_BOOT_TIMEOUT_MS: String(clampInt(process.env.BARE_OS_BOOT_TIMEOUT_MS, 45000)),
|
||||
...(testnet.envBootstrap ? { HYPERSWARM_BOOTSTRAP: testnet.envBootstrap } : {})
|
||||
}
|
||||
|
||||
try {
|
||||
procs.seeder = runNpmScript('os:seeder', { env: baseEnv, name: 'seeder' })
|
||||
await waitForOutput(
|
||||
procs.seeder,
|
||||
[/seeder active/i, /MBR block 0/i],
|
||||
seederReadyTimeoutMs,
|
||||
'seeder readiness'
|
||||
)
|
||||
|
||||
procs.booter = runNpmScript('os:booter', {
|
||||
env: {
|
||||
...baseEnv,
|
||||
BARE_OS_SKIP_REPL: '1',
|
||||
BARE_OS_BOOT_TRACE: 'ndjson'
|
||||
},
|
||||
name: 'booter'
|
||||
})
|
||||
|
||||
await waitForOutput(
|
||||
procs.booter,
|
||||
[/booter:kernel_invoke/i, /"ready":\s*true/i, /"type":"boot_ready"/i],
|
||||
booterReadyTimeoutMs,
|
||||
'booter readiness'
|
||||
)
|
||||
|
||||
const booterExited = await waitForExit(procs.booter, 10000)
|
||||
const ok = booterExited.code === 0 || booterExited.code === null
|
||||
|
||||
if (!ok) {
|
||||
throw new Error(`booter exited with code ${booterExited.code ?? 'signal'}`)
|
||||
}
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
schema: 2,
|
||||
ok: true,
|
||||
atMs: Date.now(),
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
mode: 'seeder-booter-e2e',
|
||||
testnetMode: testnet.enabled,
|
||||
hyperswarmBootstrap: testnet.redactedBootstrap,
|
||||
tempRoot: tmpBase
|
||||
})
|
||||
)
|
||||
} catch (err) {
|
||||
console.error(
|
||||
JSON.stringify({
|
||||
schema: 2,
|
||||
ok: false,
|
||||
atMs: Date.now(),
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
mode: 'seeder-booter-e2e',
|
||||
testnetMode: testnet.enabled,
|
||||
hyperswarmBootstrap: testnet.redactedBootstrap,
|
||||
error: err instanceof Error ? err.message : String(err)
|
||||
})
|
||||
)
|
||||
process.exitCode = 1
|
||||
} finally {
|
||||
await teardownProc(procs.booter)
|
||||
await teardownProc(procs.seeder)
|
||||
if (process.env.BARE_OS_INTEGRATION_KEEP_TMP !== '1') {
|
||||
await rm(tmpBase, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | undefined} raw
|
||||
* @param {number} fallback
|
||||
*/
|
||||
function clampInt(raw, fallback) {
|
||||
const n = Number.parseInt(String(raw ?? ''), 10)
|
||||
if (!Number.isFinite(n) || n <= 0) return fallback
|
||||
return Math.min(Math.max(1000, n), 15 * 60 * 1000)
|
||||
}
|
||||
|
||||
/**
|
||||
* Local integration testnet mode:
|
||||
* - Enable with BARE_OS_INTEGRATION_TESTNET=1|true|yes.
|
||||
* - Uses BARE_OS_INTEGRATION_TESTNET_BOOTSTRAP when present.
|
||||
* - Otherwise reuses HYPERSWARM_BOOTSTRAP when already configured.
|
||||
* @param {Record<string, string | undefined>} env
|
||||
*/
|
||||
function resolveTestnetBootstrapEnv(env) {
|
||||
const on = String(env.BARE_OS_INTEGRATION_TESTNET ?? '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
const enabled = on === '1' || on === 'true' || on === 'yes'
|
||||
const explicit = String(env.BARE_OS_INTEGRATION_TESTNET_BOOTSTRAP ?? '').trim()
|
||||
const inherited = String(env.HYPERSWARM_BOOTSTRAP ?? '').trim()
|
||||
const envBootstrap = enabled ? (explicit || inherited || '') : ''
|
||||
const redactedBootstrap = envBootstrap
|
||||
? envBootstrap
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.map((s) => (s.length > 20 ? s.slice(0, 20) + '...' : s))
|
||||
.join(',')
|
||||
: ''
|
||||
return { enabled, envBootstrap, redactedBootstrap }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} script
|
||||
* @param {{ env: Record<string, string | undefined>, name: string }} opts
|
||||
*/
|
||||
function runNpmScript(script, opts) {
|
||||
const child = spawn('npm', ['run', script], {
|
||||
cwd: root,
|
||||
env: opts.env,
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
})
|
||||
child.stdout?.setEncoding('utf8')
|
||||
child.stderr?.setEncoding('utf8')
|
||||
child.stdout?.on('data', (d) => process.stdout.write(`[${opts.name}] ${d}`))
|
||||
child.stderr?.on('data', (d) => process.stderr.write(`[${opts.name}] ${d}`))
|
||||
return child
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('node:child_process').ChildProcess | undefined} child
|
||||
* @param {RegExp[]} patterns
|
||||
* @param {number} ms
|
||||
* @param {string} label
|
||||
*/
|
||||
function waitForOutput(child, patterns, ms, label) {
|
||||
if (!child) return Promise.reject(new Error(`${label}: process missing`))
|
||||
return new Promise((resolve, reject) => {
|
||||
let buf = ''
|
||||
const to = setTimeout(() => {
|
||||
cleanup()
|
||||
reject(new Error(`${label}: timeout after ${ms}ms`))
|
||||
}, Math.min(ms, timeoutMs))
|
||||
const onChunk = (chunk) => {
|
||||
buf += String(chunk)
|
||||
if (buf.length > 120000) buf = buf.slice(-80000)
|
||||
if (patterns.some((re) => re.test(buf))) {
|
||||
cleanup()
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
const onExit = (code) => {
|
||||
cleanup()
|
||||
reject(new Error(`${label}: process exited before match (${code ?? 'signal'})`))
|
||||
}
|
||||
const cleanup = () => {
|
||||
clearTimeout(to)
|
||||
child.stdout?.off('data', onChunk)
|
||||
child.stderr?.off('data', onChunk)
|
||||
child.off('exit', onExit)
|
||||
}
|
||||
child.stdout?.on('data', onChunk)
|
||||
child.stderr?.on('data', onChunk)
|
||||
child.on('exit', onExit)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('node:child_process').ChildProcess | undefined} child
|
||||
* @param {number} ms
|
||||
*/
|
||||
function waitForExit(child, ms) {
|
||||
if (!child) return Promise.resolve({ code: 0 })
|
||||
return new Promise((resolve) => {
|
||||
let done = false
|
||||
const finish = (code) => {
|
||||
if (done) return
|
||||
done = true
|
||||
clearTimeout(to)
|
||||
resolve({ code })
|
||||
}
|
||||
const to = setTimeout(() => finish(null), ms)
|
||||
child.once('exit', finish)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('node:child_process').ChildProcess | undefined} child
|
||||
*/
|
||||
async function teardownProc(child) {
|
||||
if (!child || child.killed || child.exitCode != null) return
|
||||
child.kill('SIGTERM')
|
||||
await waitForExit(child, 6000)
|
||||
if (child.exitCode == null) child.kill('SIGKILL')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Maintainer utility: emit a mirror-drive experiment plan/hints JSON.
|
||||
* Non-destructive: this script does not mutate Corestores.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/mirror-drive-experiment.mjs --source-key <hex> [--mirror-key <hex>] [--aux 2]
|
||||
*/
|
||||
import process from 'node:process'
|
||||
|
||||
function arg(name, def = '') {
|
||||
const i = process.argv.indexOf(name)
|
||||
if (i === -1 || i + 1 >= process.argv.length) return def
|
||||
return String(process.argv[i + 1] || '')
|
||||
}
|
||||
|
||||
const sourceKey = arg('--source-key', '').trim()
|
||||
const mirrorKey = arg('--mirror-key', '').trim()
|
||||
const aux = Math.max(0, Number.parseInt(arg('--aux', '0'), 10) || 0)
|
||||
|
||||
if (!/^[a-f0-9]{64}$/i.test(sourceKey)) {
|
||||
console.error(
|
||||
'mirror-drive-experiment: --source-key must be a 64-hex Hyperdrive key'
|
||||
)
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
const out = {
|
||||
schema: 1,
|
||||
tool: 'mirror-drive-experiment',
|
||||
sourceKeyHex: sourceKey.toLowerCase(),
|
||||
mirrorKeyHex: /^[a-f0-9]{64}$/i.test(mirrorKey) ? mirrorKey.toLowerCase() : '',
|
||||
auxiliaryCountHint: aux,
|
||||
envHints: {
|
||||
BARE_OS_MIRROR_READ_KEY: /^[a-f0-9]{64}$/i.test(mirrorKey)
|
||||
? mirrorKey.toLowerCase()
|
||||
: sourceKey.toLowerCase(),
|
||||
BARE_OS_MIRROR_DRIVE_COMPOSITION_HINT_JSON: JSON.stringify({
|
||||
schema: 1,
|
||||
mirrorDrivePattern: 'single-primary-plus-aux',
|
||||
auxiliaryCountHint: aux,
|
||||
sourceKeyHex: sourceKey.toLowerCase()
|
||||
})
|
||||
},
|
||||
note: 'Local experiment hints only; apply env vars manually when launching booter/seeder.'
|
||||
}
|
||||
|
||||
process.stdout.write(JSON.stringify(out, null, 2) + '\n')
|
||||
@@ -23,6 +23,9 @@ const root = path.resolve(__dirname, '..')
|
||||
const kernelBinDir = path.join(root, 'kernel', 'bin')
|
||||
const probeTimeoutMs = Number.parseInt(process.env.BARE_OS_BIN_PROBE_TIMEOUT_MS || '', 10) || 2_000
|
||||
const truncLen = 160
|
||||
const lane = String(process.env.BARE_OS_BIN_PROBE_LANE || 'full')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
|
||||
const FAST_SAFE_ARGS = new Map([
|
||||
['echo', ['ok']],
|
||||
@@ -43,6 +46,34 @@ const HELP_ONLY_ARGS = new Map([
|
||||
|
||||
const SKIPPED = new Map([])
|
||||
|
||||
const CRITICAL_TOP25 = [
|
||||
'sh',
|
||||
'ls',
|
||||
'cat',
|
||||
'cp',
|
||||
'mv',
|
||||
'rm',
|
||||
'mkdir',
|
||||
'rmdir',
|
||||
'chmod',
|
||||
'chown',
|
||||
'touch',
|
||||
'grep',
|
||||
'sed',
|
||||
'awk',
|
||||
'find',
|
||||
'sort',
|
||||
'uniq',
|
||||
'head',
|
||||
'tail',
|
||||
'cut',
|
||||
'xargs',
|
||||
'env',
|
||||
'printf',
|
||||
'test',
|
||||
'echo'
|
||||
]
|
||||
|
||||
function classifyNonZero(exitCode, combinedText) {
|
||||
if (exitCode === 0) return 'OK'
|
||||
if (exitCode === 126 && /delegate/i.test(combinedText)) return 'SKIPPED'
|
||||
@@ -144,9 +175,13 @@ async function main() {
|
||||
const coreSet = new Set(coreutils)
|
||||
const missing = coreutils.filter((name) => !stagedSet.has(name))
|
||||
const extra = staged.filter((name) => !coreSet.has(name))
|
||||
const candidates = [...new Set([...coreutils, ...staged])].sort((a, b) =>
|
||||
const allCandidates = [...new Set([...coreutils, ...staged])].sort((a, b) =>
|
||||
a.localeCompare(b)
|
||||
)
|
||||
const candidates =
|
||||
lane === 'bare-runtime'
|
||||
? CRITICAL_TOP25.filter((n) => allCandidates.includes(n))
|
||||
: allCandidates
|
||||
|
||||
const { tmp, store, drive, personal, logs, errs, ctx } = await makeProbeContext()
|
||||
let failed = false
|
||||
@@ -252,6 +287,7 @@ async function main() {
|
||||
|
||||
console.log('BINS PROBE SUMMARY')
|
||||
console.log('==================')
|
||||
console.log(`Lane: ${lane}`)
|
||||
console.log(`COREUTILS_COMMANDS: ${coreutils.length}`)
|
||||
console.log(`kernel/bin files: ${staged.length}`)
|
||||
console.log(`Candidates probed: ${results.length}`)
|
||||
|
||||
@@ -22,11 +22,15 @@ function run(cmd, args, opts = {}) {
|
||||
|
||||
console.log('[release-checklist] npm test')
|
||||
run('npm', ['test'])
|
||||
console.log('[release-checklist] npm run test:bare')
|
||||
run('npm', ['run', 'test:bare'])
|
||||
|
||||
console.log('[release-checklist] verify-kernel-seeder-parity')
|
||||
run('node', ['scripts/verify-kernel-seeder-parity.mjs'])
|
||||
console.log('[release-checklist] verify-init-bundle-recipe (focused init bundle gate)')
|
||||
run('node', ['scripts/verify-init-bundle-recipe.mjs'])
|
||||
console.log('[release-checklist] gen-require-initjs-sha256 fixture')
|
||||
run('node', ['scripts/gen-require-initjs-sha256.mjs'])
|
||||
console.log('[release-checklist] verify-ctx-api-feature-bits')
|
||||
run('node', ['scripts/verify-ctx-api-feature-bits.mjs'])
|
||||
console.log('[release-checklist] verify-compat-matrix')
|
||||
@@ -60,6 +64,12 @@ if (
|
||||
}
|
||||
console.log('[release-checklist] verify-personal-drive-path-policy')
|
||||
run('node', ['scripts/verify-personal-drive-path-policy.mjs'])
|
||||
console.log(
|
||||
'[release-checklist] corestore snapshot workflow reminder: exercise host snapshot lane and verify /proc snapshot hints parity (BARE_OS_CORESTORE_SNAPSHOT_JSON / BARE_OS_CORESTORE_SNAPSHOT_WORKFLOW_JSON)'
|
||||
)
|
||||
console.log(
|
||||
'[release-checklist] optional mirror-drive experiment helper: node scripts/mirror-drive-experiment.mjs --source-key <64hex> --mirror-key <64hex> --aux <n>'
|
||||
)
|
||||
console.log('[release-checklist] verify-ctx-client-helper-sync')
|
||||
run('node', ['scripts/verify-ctx-client-helper-sync.mjs'])
|
||||
console.log(
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
*
|
||||
* Env:
|
||||
* BARE_OS_HOLEPUNCH_CLONES_ROOT — default ~/dev/pearcli/holepunch-repos/holepunchto_repos
|
||||
* BARE_OS_HOLEPUNCH_LOCKFILE_DRIFT_TIER1_FAIL=1 — fail when a tier1 lockfile package
|
||||
* has semverMismatchWithClone=true (uses docs/audit/holepunch-drift-repos.json tier1Repos)
|
||||
*
|
||||
* Config: docs/audit/holepunch-drift-repos.json
|
||||
* lockfilePackages: [{ "cloneName": "protomux", "lockKey": "protomux" }, ...]
|
||||
@@ -87,6 +89,24 @@ function loadLockfilePackages() {
|
||||
return DEFAULT_LOCK_PACKAGES
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Set<string>}
|
||||
*/
|
||||
function loadTier1RepoSet() {
|
||||
if (!fs.existsSync(driftConfigPath)) return new Set()
|
||||
try {
|
||||
const j = JSON.parse(fs.readFileSync(driftConfigPath, 'utf8'))
|
||||
if (!Array.isArray(j.tier1Repos)) return new Set()
|
||||
return new Set(
|
||||
j.tier1Repos
|
||||
.map((v) => String(v || '').trim())
|
||||
.filter(Boolean)
|
||||
)
|
||||
} catch {
|
||||
return new Set()
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (!fs.existsSync(lockPath)) {
|
||||
console.error('report-holepunch-lockfile-drift: missing', lockPath)
|
||||
@@ -94,6 +114,14 @@ function main() {
|
||||
}
|
||||
const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'))
|
||||
const pkgs = loadLockfilePackages()
|
||||
const tier1Repos = loadTier1RepoSet()
|
||||
const strictTier1Fail =
|
||||
String(process.env.BARE_OS_HOLEPUNCH_LOCKFILE_DRIFT_TIER1_FAIL || '')
|
||||
.trim()
|
||||
.toLowerCase() === '1' ||
|
||||
String(process.env.BARE_OS_HOLEPUNCH_LOCKFILE_DRIFT_TIER1_FAIL || '')
|
||||
.trim()
|
||||
.toLowerCase() === 'true'
|
||||
const atMs = Date.now()
|
||||
const rootExists = fs.existsSync(clonesRoot)
|
||||
|
||||
@@ -114,6 +142,7 @@ function main() {
|
||||
}
|
||||
const semverMismatch =
|
||||
lv && cloneVersion && lv !== cloneVersion ? true : false
|
||||
const tier1 = tier1Repos.has(cloneName)
|
||||
rows.push({
|
||||
cloneName,
|
||||
lockKey,
|
||||
@@ -121,9 +150,13 @@ function main() {
|
||||
clonePath: cloneDir,
|
||||
cloneExists: fs.existsSync(cloneDir),
|
||||
clonePackageJsonVersion: cloneVersion || null,
|
||||
semverMismatchWithClone: semverMismatch
|
||||
semverMismatchWithClone: semverMismatch,
|
||||
tier1
|
||||
})
|
||||
}
|
||||
const tier1MismatchRows = rows.filter(
|
||||
(r) => r.tier1 && r.semverMismatchWithClone
|
||||
)
|
||||
|
||||
const report = {
|
||||
schema: 1,
|
||||
@@ -134,6 +167,8 @@ function main() {
|
||||
driftConfigPath: path.relative(root, driftConfigPath),
|
||||
note:
|
||||
'lockfileVersion is the hoisted workspace entry in package-lock.json (lockfile v3). clonePackageJsonVersion is the local clone’s package.json when present; mismatch is informational until you bump npm deps or refresh clones.',
|
||||
strictTier1Fail,
|
||||
tier1MismatchCount: tier1MismatchRows.length,
|
||||
rows
|
||||
}
|
||||
fs.mkdirSync(path.dirname(outJson), { recursive: true })
|
||||
@@ -154,14 +189,15 @@ function main() {
|
||||
'',
|
||||
`_Generated at \`${new Date(atMs).toISOString()}\`. Regenerate with \`node scripts/report-holepunch-lockfile-drift.mjs\` (see [scripts/README.md](../../scripts/README.md))._`,
|
||||
'',
|
||||
'| Clone (package) | Lockfile version | Clone `package.json` version | Semver mismatch vs clone |',
|
||||
'| --- | --- | --- | --- |'
|
||||
'| Clone (package) | Tier1 | Lockfile version | Clone `package.json` version | Semver mismatch vs clone |',
|
||||
'| --- | --- | --- | --- | --- |'
|
||||
]
|
||||
for (const r of rows) {
|
||||
dashLines.push(
|
||||
'| ' +
|
||||
[
|
||||
r.cloneName,
|
||||
r.tier1 ? 'yes' : 'no',
|
||||
r.lockfileVersion ?? '—',
|
||||
r.clonePackageJsonVersion ?? '—',
|
||||
r.semverMismatchWithClone ? 'yes' : 'no'
|
||||
@@ -171,7 +207,11 @@ function main() {
|
||||
}
|
||||
dashLines.push(
|
||||
'',
|
||||
'**Note:** `lockfileVersion` is the hoisted workspace entry in `package-lock.json` (lockfile v3). `clonePackageJsonVersion` is the local mirror’s `package.json` when present; a mismatch is informational until you bump npm deps or refresh clones.'
|
||||
'**Note:** `lockfileVersion` is the hoisted workspace entry in `package-lock.json` (lockfile v3). `clonePackageJsonVersion` is the local mirror’s `package.json` when present; mismatches are informational unless `BARE_OS_HOLEPUNCH_LOCKFILE_DRIFT_TIER1_FAIL=1`.',
|
||||
'',
|
||||
`**Tier1 strict mode:** ${strictTier1Fail ? 'enabled' : 'disabled'} · tier1 mismatches: ${tier1MismatchRows.length}.`,
|
||||
'',
|
||||
'**Upgrade workflow hint:** expected churn often appears first in `udx-native` and `blind-peering`; when they drift, refresh local clones, review lockfile bumps, then regenerate this dashboard and `holepunch-runtime-compat.json` together before release gating.'
|
||||
)
|
||||
fs.writeFileSync(dashPath, dashLines.join('\n') + '\n')
|
||||
console.log(
|
||||
@@ -180,6 +220,12 @@ function main() {
|
||||
'packages=',
|
||||
rows.length
|
||||
)
|
||||
if (strictTier1Fail && tier1MismatchRows.length) {
|
||||
console.error(
|
||||
`report-holepunch-lockfile-drift: tier1 mismatch strict mode failed (${tier1MismatchRows.length} mismatches)`
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
@@ -1,136 +1,86 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Validates ```mermaid fenced blocks in Markdown by rendering with @mermaid-js/mermaid-cli.
|
||||
*
|
||||
* - Skips unless `BARE_OS_VALIDATE_MERMAID=1` or `GITHUB_ACTIONS=true` (keeps local `pretest` fast).
|
||||
* - Installs mermaid-cli once into a temp directory (requires network when not cached).
|
||||
*
|
||||
* Used by `.github/workflows/docs-mermaid-smoke.yml` (workflow_dispatch + weekly schedule).
|
||||
*/
|
||||
import { execFileSync, spawnSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import os from 'node:os'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const bad = []
|
||||
|
||||
const SHOULD_RUN =
|
||||
process.env.BARE_OS_VALIDATE_MERMAID === '1' || process.env.GITHUB_ACTIONS === 'true'
|
||||
|
||||
if (!SHOULD_RUN) {
|
||||
console.log(
|
||||
'validate-mermaid-syntax: skip (set BARE_OS_VALIDATE_MERMAID=1 or run in GitHub Actions)'
|
||||
)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const GLOBS = ['docs', 'handbook', 'developer-guide', 'users-manual', 'scripts', 'packages']
|
||||
const ROOT_MD = ['README.md', 'DOCUMENTATION.md']
|
||||
|
||||
const SKIP_FILES = new Set(['packages/bare-os-bare-libs/README.kernel-lib-bare.md'])
|
||||
|
||||
function isSkippedPath(relFromRoot) {
|
||||
const norm = relFromRoot.split(path.sep).join('/')
|
||||
return norm === 'packages/bare-os-seeder/kernel' || norm.startsWith('packages/bare-os-seeder/kernel/')
|
||||
}
|
||||
|
||||
function walkMd(dir) {
|
||||
const abs = path.join(root, dir)
|
||||
if (!fs.existsSync(abs)) return []
|
||||
/**
|
||||
* @param {string} dir
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function walkMarkdown(dir) {
|
||||
/** @type {string[]} */
|
||||
const out = []
|
||||
const st = fs.statSync(abs)
|
||||
if (st.isFile() && abs.endsWith('.md')) {
|
||||
out.push(abs)
|
||||
return out
|
||||
}
|
||||
if (!st.isDirectory()) return out
|
||||
for (const ent of fs.readdirSync(abs, { withFileTypes: true })) {
|
||||
if (ent.name === 'node_modules' || ent.name === '.git') continue
|
||||
const p = path.join(abs, ent.name)
|
||||
const relFromRoot = p.slice(root.length + 1)
|
||||
if (isSkippedPath(relFromRoot)) continue
|
||||
if (ent.isDirectory()) out.push(...walkMd(relFromRoot))
|
||||
else if (ent.isFile() && ent.name.endsWith('.md')) out.push(p)
|
||||
for (const name of fs.readdirSync(dir)) {
|
||||
const p = path.join(dir, name)
|
||||
const st = fs.statSync(p)
|
||||
if (st.isDirectory()) out.push(...walkMarkdown(p))
|
||||
else if (name.endsWith('.md')) out.push(p)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** @param {string} file */
|
||||
function extractMermaidBlocks(file) {
|
||||
const text = fs.readFileSync(file, 'utf8')
|
||||
const re = /```mermaid\n([\s\S]*?)```/g
|
||||
/** @type {string[]} */
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
function validateMermaidBlocks(text) {
|
||||
/** @type {{ body: string, startLine: number }[]} */
|
||||
const blocks = []
|
||||
let m
|
||||
while ((m = re.exec(text))) {
|
||||
const body = m[1].trimEnd()
|
||||
if (body.length) blocks.push(body)
|
||||
const lines = text.split(/\r?\n/)
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (String(lines[i]).trim() !== '```mermaid') continue
|
||||
const startLine = i + 1
|
||||
let j = i + 1
|
||||
while (j < lines.length && String(lines[j]).trim() !== '```') j++
|
||||
if (j >= lines.length) {
|
||||
bad.push(`unclosed mermaid code fence at line ${startLine}`)
|
||||
break
|
||||
}
|
||||
const body = lines.slice(i + 1, j).join('\n').trim()
|
||||
blocks.push({ body, startLine })
|
||||
i = j
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
/** @type {string[]} */
|
||||
const files = []
|
||||
for (const d of GLOBS) files.push(...walkMd(d))
|
||||
for (const name of ROOT_MD) {
|
||||
const p = path.join(root, name)
|
||||
if (fs.existsSync(p)) files.push(p)
|
||||
}
|
||||
|
||||
/** @type {{ file: string, index: number, body: string }[]} */
|
||||
const diagrams = []
|
||||
for (const file of files) {
|
||||
const rel = path.relative(root, file)
|
||||
if (SKIP_FILES.has(rel.split(path.sep).join('/'))) continue
|
||||
const blocks = extractMermaidBlocks(file)
|
||||
blocks.forEach((body, i) => diagrams.push({ file: rel, index: i, body }))
|
||||
}
|
||||
|
||||
if (diagrams.length === 0) {
|
||||
console.log('validate-mermaid-syntax: no mermaid blocks found')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const work = fs.mkdtempSync(path.join(os.tmpdir(), 'bare-os-mmdc-'))
|
||||
const npmInstall = spawnSync(
|
||||
'npm',
|
||||
['install', '--no-fund', '--no-audit', '@mermaid-js/[email protected]'],
|
||||
{ cwd: work, stdio: 'inherit', encoding: 'utf8' }
|
||||
)
|
||||
if (npmInstall.status !== 0) {
|
||||
console.error('validate-mermaid-syntax: npm install mermaid-cli failed')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const mmdc = path.join(work, 'node_modules', '.bin', 'mmdc')
|
||||
if (!fs.existsSync(mmdc)) {
|
||||
console.error('validate-mermaid-syntax: mmdc not found after install')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let failed = 0
|
||||
for (let i = 0; i < diagrams.length; i++) {
|
||||
const { file, index, body } = diagrams[i]
|
||||
const base = `diagram-${i}`
|
||||
const input = path.join(work, `${base}.mmd`)
|
||||
const output = path.join(work, `${base}.svg`)
|
||||
fs.writeFileSync(input, `${body}\n`, 'utf8')
|
||||
try {
|
||||
execFileSync(mmdc, ['-i', input, '-o', output, '-b', 'transparent'], {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
encoding: 'utf8',
|
||||
})
|
||||
} catch (e) {
|
||||
failed++
|
||||
const stderr = e instanceof Error && 'stderr' in e ? String(e.stderr) : String(e)
|
||||
console.error(`\n--- Mermaid render failed: ${file} block #${index + 1} ---\n${stderr}`)
|
||||
for (const b of blocks) {
|
||||
if (!b.body) {
|
||||
bad.push(`empty mermaid block at line ${b.startLine}`)
|
||||
continue
|
||||
}
|
||||
const first = b.body.split(/\r?\n/, 1)[0].trim()
|
||||
if (
|
||||
!/^(graph|flowchart|sequenceDiagram|classDiagram|stateDiagram|erDiagram|journey|gantt|pie|mindmap|timeline)\b/.test(
|
||||
first
|
||||
)
|
||||
) {
|
||||
bad.push(
|
||||
`invalid mermaid first line at ${b.startLine}: expected diagram type, got "${first}"`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failed > 0) {
|
||||
console.error(`\nvalidate-mermaid-syntax: ${failed} of ${diagrams.length} diagram(s) failed`)
|
||||
process.exit(1)
|
||||
const mdFiles = [
|
||||
...walkMarkdown(path.join(root, 'docs')),
|
||||
...walkMarkdown(path.join(root, 'developer-guide')),
|
||||
...walkMarkdown(path.join(root, 'handbook')),
|
||||
...walkMarkdown(path.join(root, 'users-manual'))
|
||||
]
|
||||
|
||||
for (const file of mdFiles) {
|
||||
const raw = fs.readFileSync(file, 'utf8')
|
||||
const before = bad.length
|
||||
validateMermaidBlocks(raw)
|
||||
if (bad.length > before) {
|
||||
for (let i = before; i < bad.length; i++) {
|
||||
bad[i] = `${path.relative(root, file)}: ${bad[i]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`validate-mermaid-syntax: OK (${diagrams.length} diagram(s))`)
|
||||
if (bad.length) {
|
||||
console.error('validate-mermaid-syntax: failed')
|
||||
for (const b of bad) console.error(' ', b)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('validate-mermaid-syntax: OK')
|
||||
|
||||
@@ -37,6 +37,16 @@ function main() {
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
if (
|
||||
key !== 'extensionSignerPinsV5' &&
|
||||
props[key]?.additionalProperties?.pattern !== '^[a-f0-9]{64}$'
|
||||
) {
|
||||
console.error(
|
||||
'verify-boot-policy-extension-signer-pins: schema pattern drift',
|
||||
key
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
/** @type {string[]} */
|
||||
const chunks = []
|
||||
|
||||
@@ -24,6 +24,16 @@ function main() {
|
||||
console.error('verify-bundle-health: bundle-health.json missing bundles[]')
|
||||
process.exit(1)
|
||||
}
|
||||
const thresholdRows = Array.isArray(
|
||||
health?.bundleRegressionThresholds?.byCtxKey
|
||||
)
|
||||
? health.bundleRegressionThresholds.byCtxKey
|
||||
: []
|
||||
const thresholdsByCtxKey = new Map(
|
||||
thresholdRows
|
||||
.filter((r) => r && typeof r === 'object' && typeof r.ctxKey === 'string')
|
||||
.map((r) => [r.ctxKey, r])
|
||||
)
|
||||
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
|
||||
const mPaths = new Set(
|
||||
@@ -58,6 +68,12 @@ function main() {
|
||||
`size mismatch ${path.basename(rel)}: disk ${st.size} vs bundle-health ${want} (re-run npm run build -w bare-os-bare-libs)`
|
||||
)
|
||||
}
|
||||
const thr = thresholdsByCtxKey.get(row.ctxKey)
|
||||
if (thr && typeof thr.maxBytes === 'number' && st.size > thr.maxBytes) {
|
||||
bad.push(
|
||||
`bundle threshold exceeded ${row.ctxKey}: disk ${st.size} > max ${thr.maxBytes} (tier ${thr.tier || 'n/a'})`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.existsSync(bundlesDir)) {
|
||||
@@ -84,6 +100,12 @@ function main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
health.bundleRegressionThresholds &&
|
||||
!Array.isArray(health.bundleRegressionThresholds.byCtxKey)
|
||||
) {
|
||||
bad.push('bundleRegressionThresholds.byCtxKey missing or invalid')
|
||||
}
|
||||
|
||||
if (bad.length) {
|
||||
console.error('verify-bundle-health: failed')
|
||||
|
||||
@@ -92,6 +92,38 @@ function main() {
|
||||
)
|
||||
}
|
||||
|
||||
const posixMatrixRaw = read('docs/reference/posix-compliance-matrix.json')
|
||||
const posixMatrix = JSON.parse(posixMatrixRaw)
|
||||
const procTableSchema = Number(posixMatrix?.synthetic_proc?.process_table_schema)
|
||||
const syscallsSchemaFromMatrix = Number(
|
||||
posixMatrix?.synthetic_proc?.syscalls_json_schema
|
||||
)
|
||||
must(
|
||||
Number.isFinite(procTableSchema) && procTableSchema > 0,
|
||||
'posix-compliance-matrix.json missing synthetic_proc.process_table_schema'
|
||||
)
|
||||
must(
|
||||
Number.isFinite(syscallsSchemaFromMatrix) && syscallsSchemaFromMatrix > 0,
|
||||
'posix-compliance-matrix.json missing synthetic_proc.syscalls_json_schema'
|
||||
)
|
||||
must(
|
||||
String(syscallsSchemaFromMatrix) === syscallSchema,
|
||||
`posix-compliance-matrix syscalls_json_schema ${syscallsSchemaFromMatrix} !== proc builder ${syscallSchema}`
|
||||
)
|
||||
const handbookPosix = read('handbook/09-posix-utilities-shell-and-vfs.md')
|
||||
must(
|
||||
handbookPosix.includes(
|
||||
'`/proc/bare_os/syscalls.json`** (schema **' + syscallsSchemaFromMatrix + '**'
|
||||
),
|
||||
`handbook/09-posix-utilities-shell-and-vfs.md must mention /proc/bare_os/syscalls.json schema ${syscallsSchemaFromMatrix}`
|
||||
)
|
||||
must(
|
||||
handbookPosix.includes(
|
||||
'`/proc/bare_os/process_table.json`** (**schema ' + procTableSchema + '**'
|
||||
),
|
||||
`handbook/09-posix-utilities-shell-and-vfs.md must mention /proc/bare_os/process_table.json schema ${procTableSchema}`
|
||||
)
|
||||
|
||||
console.log('verify-doc-contracts: OK')
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env node
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const roadmapMdPath = path.join(root, 'docs', 'reference', 'feature-roadmap.md')
|
||||
const canonicalPath = path.join(root, 'docs', 'data', 'feature-roadmap-canonical.json')
|
||||
|
||||
const roadmapMd = await readFile(roadmapMdPath, 'utf8')
|
||||
await readFile(canonicalPath, 'utf8')
|
||||
|
||||
const expectedLink = '../data/feature-roadmap-canonical.json'
|
||||
if (!roadmapMd.includes(expectedLink)) {
|
||||
throw new Error(
|
||||
`feature-roadmap.md must reference canonical JSON path: ${expectedLink}`
|
||||
)
|
||||
}
|
||||
if (roadmapMd.includes('feature-roadmap-canonical.json](./')) {
|
||||
throw new Error(
|
||||
'feature-roadmap.md uses an inconsistent local canonical JSON path; use ../data/feature-roadmap-canonical.json'
|
||||
)
|
||||
}
|
||||
|
||||
console.log(
|
||||
'[verify-feature-roadmap-paths] ok: feature-roadmap markdown references canonical JSON path'
|
||||
)
|
||||
@@ -63,9 +63,9 @@ function main() {
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
if (!bits.includes('BARE_OS_KERNEL_FEATURE_BITS_DOC = 16')) {
|
||||
if (!bits.includes('BARE_OS_KERNEL_FEATURE_BITS_DOC = 17')) {
|
||||
console.error(
|
||||
'kernel-feature-bits.js: expected BARE_OS_KERNEL_FEATURE_BITS_DOC = 16'
|
||||
'kernel-feature-bits.js: expected BARE_OS_KERNEL_FEATURE_BITS_DOC = 17'
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env node
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const pkgPath = path.join(root, 'packages', 'bare-os-protocol', 'package.json')
|
||||
const docPath = path.join(root, 'docs', 'reference', 'package-bare-os-protocol.md')
|
||||
|
||||
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
|
||||
const version = String(pkg.version || '').trim()
|
||||
if (!/^\d+\.\d+\.\d+([-.][0-9A-Za-z.-]+)?$/.test(version)) {
|
||||
throw new Error(`invalid bare-os-protocol package version: "${version}"`)
|
||||
}
|
||||
|
||||
const doc = await readFile(docPath, 'utf8')
|
||||
const matches = [...doc.matchAll(/\b\d+\.\d+\.\d+(?:[-.][0-9A-Za-z.-]+)?\b/g)].map(
|
||||
(m) => m[0]
|
||||
)
|
||||
if (!matches.includes(version)) {
|
||||
throw new Error(
|
||||
`protocol doc version mismatch: docs/reference/package-bare-os-protocol.md missing ${version}`
|
||||
)
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[verify-protocol-doc-version-parity] ok: docs mention bare-os-protocol@${version}`
|
||||
)
|
||||
Reference in New Issue
Block a user