Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: astral-sh/setup-uv@v8.1.0
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm check
- name: Qualify real relay in a network-isolated namespace
run: |
relay_node_executable="$(command -v node)"
sudo unshare --net -- sh -c 'ip link set lo up; exec "$@"' \
motion-relay "$relay_node_executable" scripts/qualify-relay.mjs

ios:
name: iOS build and Swift quality
Expand All @@ -37,8 +40,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 20.19
Expand Down
4 changes: 2 additions & 2 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
}
},
"server": {
"entry": ["src/server.ts"],
"project": ["src/**/*.ts"]
"entry": ["src/server.ts", "scripts/**/*.test.mjs"],
"project": ["src/**/*.ts", "scripts/**/*.mjs"]
},
"web": {
"entry": ["src/app/main.ts", "src/sdk/index.ts", "src/**/*.test.ts"],
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@
"overrides": {
"brace-expansion@>=5.0.0 <6.0.0": "5.0.12",
"nanoid@>=3.0.0 <4.0.0": "3.3.18",
"undici@^5.28.4": "6.29.0"
"miniflare>undici": "6.29.0"
},
"patchedDependencies": {
"partykit@0.0.115": "patches/partykit@0.0.115.patch"
}
}
},
"packageManager": "pnpm@10.33.2"
}
11 changes: 11 additions & 0 deletions patches/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# PartyKit runtime patch

PartyKit 0.0.115 bundles its own Undici factories. An override for Miniflare does not update those embedded bytes. This patch delegates the single bundled Undici export to the already installed Miniflare Undici 6.29.0, with an exact version guard. It adds no dependency and preserves all generated line numbers outside the changed factory line.

The old factories and source map remain in the package. Qualification verifies that all six outside callers use this entry, that executable entry exports are the actual resolved Undici object, and that the real inspector request publishes from the resolved request implementation. Retained inert bytes are not proof of an upstream update. Login, deployment and physical-device behavior remain unqualified.

Review this patch whenever PartyKit or Miniflare changes. Remove it only after the replacement proves runtime parity and resolves the embedded-runtime boundary. Do not promote source-map observations into publisher proof.

Fixture-only switches bind Workerd to loopback, choose the inspector port independently of the relay port, and use the read-only source checkout as the module root. The config, generated files and persistence stay inside the owned temporary fixture. Using the fixture directory as the module root while its entry point lives in the checkout creates escaping `..` module names that Workerd rejects. Without the qualification environment, PartyKit keeps its original host, inspector selection and module root.

The inspector request supplies its own resolved Undici Agent and closes it after consuming the response, including failure paths. Node and installed Undici share a global-dispatcher symbol; an installed fetch function alone can otherwise dispatch through an agent created by Node’s internal Undici. The qualifier observed that internal publisher before this request-specific correction. It verifies the actual external publisher afterward; login and deployment request paths remain unqualified.
50 changes: 50 additions & 0 deletions patches/partykit@0.0.115.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
diff --git a/dist/bin.mjs b/dist/bin.mjs
--- a/dist/bin.mjs
+++ b/dist/bin.mjs
@@ -33159,7 +33159,7 @@
// ../../node_modules/undici/index.js
var require_undici = __commonJS({
"../../node_modules/undici/index.js"(exports2, module2) {
- "use strict";
+ "use strict"; const motionRuntimeRequire = __require("node:module").createRequire(__require.resolve("miniflare")); if (motionRuntimeRequire("undici/package.json").version !== "6.29.0") throw new Error("Motion requires qualified Undici 6.29.0"); module2.exports = motionRuntimeRequire("undici"); return;
var Client = require_client();
var Dispatcher = require_dispatcher();
var Pool = require_pool();
@@ -94207,7 +94207,7 @@
() => config.vectorize || config.ai ? getUserDetails(config) : null,
[config]
);
- const portForRuntimeInspector = getPortForServer("runtime-inspector");
+ const portForRuntimeInspector = getPortForServer("runtime-inspector", process.env.RELAY_FIXTURE_INSPECTOR_PORT ? Number(process.env.RELAY_FIXTURE_INSPECTOR_PORT) : void 0);
const [server] = (0, import_react26.useState)(() => new MiniflareServer());
const [inspectorUrl, setInspectorUrl] = (0, import_react26.useState)(
void 0
@@ -94411,7 +94411,7 @@
https: options.https,
httpsKeyPath: options.httpsKeyPath,
httpsCertPath: options.httpsCertPath,
- host: "0.0.0.0",
+ host: process.env.RELAY_FIXTURE_DIRECTORY ? "127.0.0.1" : "0.0.0.0",
log: new Log(5, { prefix: "pk" }),
verbose: options.verbose,
inspectorPort: portForRuntimeInspector,
@@ -94514,7 +94514,7 @@
}))
],
- modulesRoot: process.cwd(),
+ modulesRoot: process.env.RELAY_FIXTURE_DIRECTORY ? process.env.RELAY_FIXTURE_SOURCE_ROOT : process.cwd(),
script: code
},
{ signal: abortController.signal }
);
@@ -94641,8 +94641,8 @@
onReady?.(event.url.hostname, parseInt(event.url.port));
try {
const jsonUrl = `http://127.0.0.1:${portForRuntimeInspector}/json`;
- const res = await (0, import_undici4.fetch)(jsonUrl);
- const body = await res.json();
+ const motionInspectorAgent = new import_undici4.Agent();
+ const body = await (async () => { try { const res = await (0, import_undici4.fetch)(jsonUrl, { dispatcher: motionInspectorAgent }); return await res.json(); } finally { await motionInspectorAgent.close(); } })();
const debuggerUrl = body?.find(
({ id }) => id.startsWith("core:user")
)?.webSocketDebuggerUrl;
11 changes: 8 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 7 additions & 8 deletions scripts/check-code-health.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";
import { summarizeDependencyAudit } from "./dependency-audit.mjs";
import { countSwiftFormatDiagnostics } from "./swift-format-result.mjs";

const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const productionPaths = ["protocol", "server/src", "web/src", "ios/Sources"];
Expand Down Expand Up @@ -216,14 +218,13 @@ function checkDuplication() {

function checkDependencies() {
const result = run("pnpm", ["audit", "--json"], { allowFailure: true });
const report = JSON.parse(result.stdout);
const severe = Object.entries(report.advisories ?? {}).filter(
([, advisory]) => ["critical", "high"].includes(advisory.severity)
const { dependencies, severe } = summarizeDependencyAudit(result);
log(
`Dependencies: ${dependencies} audited, ${severe.length} critical/high advisories.`
);
log(`Dependencies: ${severe.length} critical/high advisories.`);
if (severe.length > 0) {
throw new Error(
`Critical/high advisories: ${severe
`Critical/high advisories must be resolved before passing: ${severe
.map(([id, advisory]) => `${id}/${advisory.github_advisory_id}`)
.join(", ")}`
);
Expand Down Expand Up @@ -278,9 +279,7 @@ function checkSwiftFormat() {
["swift-format", "lint", "--strict", "--recursive", "ios/Sources"],
{ allowFailure: true }
);
const diagnostics = `${result.stdout}\n${result.stderr}`
.split("\n")
.filter((line) => line.includes("error:")).length;
const diagnostics = countSwiftFormatDiagnostics(result);
log(`Swift format debt: ${diagnostics} diagnostics.`);
// Ratcheted legacy debt: https://github.com/Significant-Hobbies/motion/issues/26
failRegressions("Swift format", { diagnostics }, { diagnostics: 4553 });
Expand Down
95 changes: 95 additions & 0 deletions scripts/dependency-audit.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
const severities = new Set(["info", "low", "moderate", "high", "critical"]);
const advisoryLevels = ["critical", "high", "moderate", "low", "info"];

export function summarizeDependencyAudit(result) {
if (![0, 1].includes(result.status)) {
throw new Error(
`Dependency audit exited unexpectedly with status ${result.status}.`
);
}

let report;
try {
report = JSON.parse(result.stdout);
} catch (error) {
throw new Error("Dependency audit returned invalid JSON.", {
cause: error,
});
}

if (
!report ||
typeof report !== "object" ||
Array.isArray(report) ||
report.error ||
!report.advisories ||
typeof report.advisories !== "object" ||
Array.isArray(report.advisories) ||
!report.metadata?.vulnerabilities ||
typeof report.metadata.vulnerabilities !== "object" ||
!Number.isInteger(report.metadata.dependencies) ||
report.metadata.dependencies < 0 ||
!Number.isInteger(report.metadata.totalDependencies) ||
report.metadata.totalDependencies < report.metadata.dependencies
) {
throw new Error(
"Dependency audit returned an incomplete or unexpected report."
);
}

for (const severity of advisoryLevels) {
if (
!Number.isInteger(report.metadata.vulnerabilities[severity]) ||
report.metadata.vulnerabilities[severity] < 0
) {
throw new Error(
"Dependency audit returned malformed vulnerability counts."
);
}
}

for (const [id, advisory] of Object.entries(report.advisories)) {
if (
!/^\d+$/u.test(id) ||
!advisory ||
typeof advisory !== "object" ||
!severities.has(advisory.severity) ||
typeof advisory.github_advisory_id !== "string"
) {
throw new Error(
`Dependency audit returned a malformed advisory (${id}).`
);
}
}

if (result.status === 1 && Object.keys(report.advisories).length === 0) {
throw new Error("Dependency audit failure has no advisory explanation.");
}

const advisoryCounts = Object.fromEntries(
advisoryLevels.map((severity) => [
severity,
Object.values(report.advisories).filter(
(advisory) => advisory.severity === severity
).length,
])
);
if (
advisoryLevels.some(
(severity) =>
report.metadata.vulnerabilities[severity] !== advisoryCounts[severity]
)
) {
throw new Error(
"Dependency audit advisory details do not match its vulnerability counts."
);
}

const severe = Object.entries(report.advisories).filter(([, advisory]) =>
["critical", "high"].includes(advisory.severity)
);
return {
dependencies: report.metadata.totalDependencies,
severe,
};
}
98 changes: 98 additions & 0 deletions scripts/dependency-audit.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { describe, expect, it } from "vitest";
import { summarizeDependencyAudit } from "./dependency-audit.mjs";

function auditResult({ status = 0, advisories = {}, error } = {}) {
const vulnerabilities = {
info: 0,
low: 0,
moderate: 0,
high: 0,
critical: 0,
};
for (const advisory of Object.values(advisories)) {
vulnerabilities[advisory.severity] += 1;
}
return {
status,
stdout: JSON.stringify({
...(error ? { error } : {}),
advisories,
metadata: {
vulnerabilities,
dependencies: 10,
totalDependencies: 10,
},
}),
};
}

describe("dependency audit gate", () => {
it("accepts a clean, valid report", () => {
expect(summarizeDependencyAudit(auditResult())).toEqual({
dependencies: 10,
severe: [],
});
});

it("accepts nonzero status for a valid advisory report and returns severe findings", () => {
const advisory = { severity: "high", github_advisory_id: "GHSA-test" };
const result = summarizeDependencyAudit(
auditResult({ status: 1, advisories: { 123: advisory } })
);

expect(result.severe).toEqual([["123", advisory]]);
});

it("rejects registry errors even when they exit nonzero", () => {
expect(() =>
summarizeDependencyAudit(
auditResult({ status: 1, error: { code: "ENOTFOUND" } })
)
).toThrow(/incomplete or unexpected report/u);
});

it.each(["not json", "{}", JSON.stringify({ advisories: {} })])(
"rejects malformed reports: %s",
(stdout) => {
expect(() => summarizeDependencyAudit({ status: 0, stdout })).toThrow();
}
);

it("rejects unexpected audit process statuses", () => {
expect(() => summarizeDependencyAudit({ status: 2, stdout: "{}" })).toThrow(
/exited unexpectedly/u
);
});

it("rejects failure status without an advisory explanation", () => {
expect(() => summarizeDependencyAudit(auditResult({ status: 1 }))).toThrow(
/no advisory explanation/u
);
});

it.each([
{ dependencies: -1, totalDependencies: 10 },
{ dependencies: 10, totalDependencies: 9 },
])("rejects invalid dependency totals: %j", (counts) => {
const result = auditResult();
const report = JSON.parse(result.stdout);
Object.assign(report.metadata, counts);
expect(() =>
summarizeDependencyAudit({ ...result, stdout: JSON.stringify(report) })
).toThrow(/incomplete or unexpected report/u);
});

it("rejects report counts that do not match advisory details", () => {
const result = auditResult({
advisories: {
123: { severity: "high", github_advisory_id: "GHSA-test" },
},
});
const report = JSON.parse(result.stdout);
report.metadata.vulnerabilities.high = 0;

expect(() =>
summarizeDependencyAudit({ ...result, stdout: JSON.stringify(report) })
).toThrow(/do not match/u);
});
});
Loading
Loading