diff --git a/docs/upstream-dmg-intelligence.md b/docs/upstream-dmg-intelligence.md
index 54dd139f9..e8f368a66 100644
--- a/docs/upstream-dmg-intelligence.md
+++ b/docs/upstream-dmg-intelligence.md
@@ -48,6 +48,12 @@ scripts/dev/upstream-dmg-intel.js \
--fail-on-blockers
```
+Add `--patch-preflight` to extract `app.asar` into an isolated temporary root
+and prove the required window patches plus the protected Computer Use and Read
+Aloud parity patches against the candidate. Raw upstream platform gates remain
+visible, but become non-blocking `patched-linux-parity` entries only when every
+exact owner patch reports `applied` or `already-applied`.
+
Explicit baseline comparison remains available for older known-good builds:
```bash
@@ -81,6 +87,12 @@ Each run writes:
- `plugin-map.json`: bundled plugin manifests, MCP configs, and skill files.
- `native-binary-map.json`: native candidate paths, file type output when
available, hashes, and protected string evidence.
+- `platform-gates.json`: macOS/Windows/Linux gate inventory with Linux parity,
+ unsupported platform, new capability, expected platform-native, and review
+ classifications.
+- `new-capabilities.json`: issue-candidate queue built from new plugins, MCP
+ tools, native binaries, bridge handlers, and platform-gated desktop feature
+ hints.
- `map-drift.json`: baseline/candidate structural deltas for bridge handlers,
plugin ids/files, MCP tools, native binaries, and Linux substrate gaps.
- `drift-report.json` and `drift-report.md`: machine and human drift summaries.
@@ -88,9 +100,11 @@ Each run writes:
newly discovered, patch-broken, or substrate-gap surfaces.
The CLI stdout summary includes `decision.acceptance`, `blockersCount`,
-`reviewItemsCount`, protected-surface status counts, and whether every protected
-surface is fully present. `--fail-on-blockers` exits with status `2` after
-writing the report bundle when `decision.blockersCount` is nonzero.
+`reviewItemsCount`, `linuxParityGateBlockersCount`,
+`platformGateReviewItemsCount`, `newCapabilityIssueCandidatesCount`,
+protected-surface status counts, and whether every protected surface is fully
+present. `--fail-on-blockers` exits with status `2` after writing the report
+bundle when `decision.blockersCount` is nonzero.
When a baseline is provided, the command also writes `baseline/` and
`candidate/` subdirectories with their own inventory, protected-surface,
@@ -162,6 +176,32 @@ review item before accepting the upstream DMG.
- `LINUX_SUBSTRATE_GAP`: upstream evidence exists, but the registry's required
Linux substrate path is missing.
+## Platform Gate Classifications
+
+The platform gate map is the first place to look when a feature exists in the
+bundle but disappears from Settings, `@` mentions, menus, or plugin entrypoints.
+It classifies gates separately from protected-surface drift:
+
+- `linux-parity-drift`: Linux already has a substrate or patch owner, but
+ upstream still gates the UI or query to macOS/Windows. This is a release
+ blocker; Computer Use belongs here.
+- `patched-linux-parity`: the raw upstream gate still exists, but every exact
+ candidate preflight patch for its Linux surface applied. The gate remains in
+ the report as evidence without blocking acceptance.
+- `existing-linux-feature-drift`: protected-surface movement or patch failures
+ for features this repo already mirrors. This remains represented by the
+ protected-surface classifications above.
+- `new-upstream-capability`: new plugin, MCP tool, bridge, native sidecar, or
+ desktop feature hint that needs an issue and a Linux support decision.
+- `platform-specific-unsupported`: macOS/Windows feature with no Linux substrate
+ yet, such as Office live-control app rows.
+- `expected-platform-native`: OS-native details such as titlebar, Dock, tray, or
+ window chrome behavior that should be labeled but not patched by default.
+- `already-linux-enabled`: a platform gate already includes Linux; keep it out
+ of blocker counts.
+- `needs-review`: high-signal but ambiguous feature gate that must be triaged
+ before accepting release drift.
+
## Acceptance Gate
The automated tests use synthetic `.app` fixtures and `app.asar.extracted`
@@ -192,3 +232,50 @@ navigation layer:
registry, patch, or Linux substrate action is resolved. `PATCH_REVIEW` remains
review-only unless the protected surface is also missing, partial, removed, or
has a required patch failure.
+
+## Optional Dagger MCP
+
+The Dagger module is not the primary entry point. It exists as a self-hosted
+agent convenience wrapper around the same devcontainer-backed tool:
+
+```bash
+dagger functions
+dagger call verify-dmg-intel
+dagger call inspect-upstream-dmg-url
+dagger call inspect-upstream-dmg --candidate ./Codex.dmg export --path /tmp/codex-dmg-intel-report
+```
+
+The `inspect-upstream-dmg-url` function downloads the current upstream DMG URL
+inside Dagger, compares it to repo `Codex.dmg`, and returns a compact JSON
+summary for Codex/MCP use with `decision`, `blockers`, and review-only drift.
+The `inspect-upstream-dmg` function accepts a candidate DMG file and optional
+baseline and patch-report files, then returns the generated report directory.
+The Dagger source context ignores DMGs, build outputs, generated reports,
+`target/`, and app extraction trees; pass large DMGs as explicit file
+arguments instead of baking them into the module context.
+
+To expose it as an MCP server in Codex, add the bridge to repo-local
+`.codex/config.toml` and restart the Codex session:
+
+```toml
+[mcp_servers.codex-dmg-intel-dagger]
+command = "/path/to/codex-desktop-linux/scripts/codex-dmg-intel-dagger-mcp"
+```
+
+The bridge also exposes Dagger-native agent functions such as
+`headroom-agent-review`. By default, the launcher uses a local Headroom client
+token shape with Dagger's OpenAI-compatible LLM environment and routes calls
+through Headroom. Real upstream provider keys stay on the Headroom server:
+
+```toml
+[mcp_servers.codex-dmg-intel-dagger.env]
+DAGGER_HEADROOM_PROXY_URL = "http://10.10.10.89"
+DAGGER_HEADROOM_MODEL = "openrouter/deepseek/deepseek-v4-flash"
+DAGGER_HEADROOM_API_KEY = "headroom-local-client-token"
+```
+
+Use a pinned repo env only when Codex may start outside this checkout:
+
+```toml
+CODEX_DMG_INTEL_DAGGER_REPO = "/home/kdlocpanda/second_brain/Areas/devcontainers/codex-desktop-linux"
+```
diff --git a/scripts/dev/upstream-dmg-intel.js b/scripts/dev/upstream-dmg-intel.js
index 1ed877cb2..d8d8d6ae7 100755
--- a/scripts/dev/upstream-dmg-intel.js
+++ b/scripts/dev/upstream-dmg-intel.js
@@ -24,6 +24,8 @@ Build an upstream DMG intelligence report without mutating codex-app/.
Options:
--candidate PATH Candidate Codex.dmg, extracted .app, or extracted app resources directory
+ --candidate-url URL Provenance URL for the candidate DMG
+ --patch-preflight Run required and protected Linux parity patches against a temporary extracted copy
--baseline PATH Optional known-good baseline DMG or extracted .app; defaults to ./Codex.dmg when different
--no-baseline Do a candidate-only scan even when ./Codex.dmg exists
--patch-report PATH Optional patch-report.json to fold patch blockers/review items into drift-report.json
@@ -40,6 +42,8 @@ function parseArgs(argv) {
autoBaseline: true,
baselinePath: null,
candidatePath: null,
+ candidateUrl: null,
+ patchPreflight: false,
outputDir: null,
failOnBlockers: false,
patchReportPath: null,
@@ -51,6 +55,10 @@ function parseArgs(argv) {
const arg = argv[index];
if (arg === "--candidate") {
args.candidatePath = argv[++index];
+ } else if (arg === "--candidate-url") {
+ args.candidateUrl = argv[++index];
+ } else if (arg === "--patch-preflight") {
+ args.patchPreflight = true;
} else if (arg === "--baseline") {
args.baselinePath = argv[++index];
} else if (arg === "--no-baseline") {
@@ -99,16 +107,25 @@ function buildDecision({ driftReport, protectedSurfaces }) {
const surfaceDrift = driftReport.surfaceDrift ?? [];
const blockers = surfaceDrift.filter((item) => BLOCKING_CLASSIFICATIONS.has(item.classification));
const reviewItems = surfaceDrift.filter((item) => !BLOCKING_CLASSIFICATIONS.has(item.classification));
+ const linuxParityGateBlockers = driftReport.platformGateSummary?.blockingCount ?? 0;
+ const platformGateReviewItems = driftReport.platformGateSummary?.reviewCount ?? 0;
+ const newCapabilityIssueCandidates = driftReport.newCapabilitySummary?.issueCandidateCount ?? 0;
const protectedSurfaceStatusCounts = statusCounts(protectedSurfaces.surfaces ?? []);
const allProtectedSurfacesPresent =
(protectedSurfaces.surfaces ?? []).length > 0 &&
(protectedSurfaces.surfaces ?? []).every((surface) => surface.status === "PRESENT");
- const acceptance = blockers.length > 0 ? "blocked" : (reviewItems.length > 0 ? "review" : "accepted");
+ const blockerCount = blockers.length + linuxParityGateBlockers;
+ const reviewCount = reviewItems.length + platformGateReviewItems + newCapabilityIssueCandidates;
+ const acceptance = blockerCount > 0 ? "blocked" : (reviewCount > 0 ? "review" : "accepted");
return {
acceptance,
- blockersCount: blockers.length,
- reviewItemsCount: reviewItems.length,
+ blockersCount: blockerCount,
+ reviewItemsCount: reviewCount,
+ protectedSurfaceBlockersCount: blockers.length,
+ linuxParityGateBlockersCount: linuxParityGateBlockers,
+ platformGateReviewItemsCount: platformGateReviewItems,
+ newCapabilityIssueCandidatesCount: newCapabilityIssueCandidates,
allProtectedSurfacesPresent,
protectedSurfaceStatusCounts,
blockerClassifications: [...new Set(blockers.map((item) => item.classification))].sort(),
@@ -141,6 +158,8 @@ function main(argv = process.argv.slice(2)) {
registry,
repoRoot,
timestamp: args.timestamp,
+ provenance: args.candidateUrl ? { candidate: { url: args.candidateUrl } } : null,
+ runPatchPreflight: args.patchPreflight,
});
const decision = buildDecision({
driftReport: reports.driftReport,
@@ -151,6 +170,8 @@ function main(argv = process.argv.slice(2)) {
outputDir: reports.outputDir,
inventory: path.join(reports.outputDir, "inventory.json"),
protectedSurfaces: path.join(reports.outputDir, "protected-surfaces.json"),
+ platformGates: path.join(reports.outputDir, "platform-gates.json"),
+ newCapabilities: path.join(reports.outputDir, "new-capabilities.json"),
driftReport: path.join(reports.outputDir, "drift-report.json"),
driftMarkdown: path.join(reports.outputDir, "drift-report.md"),
substrateActionPlan: path.join(reports.outputDir, "substrate-action-plan.md"),
@@ -161,7 +182,7 @@ function main(argv = process.argv.slice(2)) {
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
if (args.failOnBlockers && decision.blockersCount > 0) {
console.error(
- `Upstream DMG intelligence found ${decision.blockersCount} protected-surface acceptance blocker(s).`,
+ `Upstream DMG intelligence found ${decision.blockersCount} Linux acceptance blocker(s).`,
);
return 2;
}
diff --git a/scripts/dev/upstream-dmg-intel.test.js b/scripts/dev/upstream-dmg-intel.test.js
index ed4151802..ebd92f80c 100644
--- a/scripts/dev/upstream-dmg-intel.test.js
+++ b/scripts/dev/upstream-dmg-intel.test.js
@@ -11,7 +11,13 @@ const {
buildIntelReports,
compareProtectedSurfaces,
createInventory,
+ createNewCapabilityMap,
+ createPlatformGateMap,
+ extractVersionMetadata,
extractProtectedSurfaces,
+ findPostPatchIntegrityFindings,
+ mergeProvenance,
+ prepareRequiredPatchPreflightApp,
renderActionPlanMarkdown,
resolveBaselinePath,
} = require("../lib/upstream-dmg-intel.js");
@@ -52,38 +58,6 @@ const registry = {
requiredPaths: ["computer-use-linux/src/bin/codex-chrome-extension-host.rs"],
},
},
- {
- id: "work_louder_control_surface",
- title: "Work Louder control-surface bundle hooks",
- category: "native",
- pathPatterns: [
- "codex-micro-service.*\\.js$",
- "@worklouder/(device-kit-oai|wl-device-kit)/package\\.json$",
- "(^|/)node-hid/package\\.json$",
- "(^|/)node-hid/prebuilds/[^/]+/node-napi-v[0-9]+\\.node$",
- ],
- patchNamePatterns: ["work.*louder", "micro", "hid", "control.*surface"],
- requiredEvidence: [
- {
- id: "micro-service-entrypoint",
- pathPatterns: ["codex-micro-service.*\\.js$"],
- contentNeedles: ["@worklouder/device-kit-oai", "DeviceType", "Project2077"],
- },
- {
- id: "work-louder-device-kits",
- pathPatterns: ["@worklouder/(device-kit-oai|wl-device-kit)"],
- contentNeedles: ["@worklouder/device-kit-oai", "@worklouder/wl-device-kit", "node-hid"],
- },
- {
- id: "hid-runtime-package",
- pathPatterns: ["(^|/)node-hid/package\\.json$", "(^|/)node-hid/prebuilds/"],
- contentNeedles: ["node-hid"],
- nativeBinaryPatterns: [
- "(^|/)node-hid/prebuilds/[^/]+/node-napi-v[0-9]+\\.node$",
- ],
- },
- ],
- },
{
id: "dictation_transcript_finalization",
title: "Dictation transcript finalization",
@@ -183,59 +157,111 @@ function writeFile(filePath, content, mode) {
fs.writeFileSync(filePath, content, mode == null ? undefined : { mode });
}
-function writeWorkLouderControlSurface({ asarExtracted, includeHid = true, resources }) {
- const deviceKit = path.join(
- resources,
- "app.asar.unpacked/node_modules/@worklouder/device-kit-oai",
- );
- const wlKit = path.join(deviceKit, "node_modules/@worklouder/wl-device-kit");
- const hid = path.join(wlKit, "node_modules/node-hid");
+function writeAsar(filePath, fileContents) {
+ const root = { files: {} };
+ const payloads = [];
+ let offset = 0;
+ for (const [relativePath, content] of Object.entries(fileContents).sort(([left], [right]) => left.localeCompare(right))) {
+ const payload = Buffer.from(content);
+ const parts = relativePath.split("/");
+ let files = root.files;
+ for (const part of parts.slice(0, -1)) {
+ files[part] ??= { files: {} };
+ files = files[part].files;
+ }
+ files[parts.at(-1)] = { offset: String(offset), size: payload.length };
+ payloads.push(payload);
+ offset += payload.length;
+ }
- writeFile(
- path.join(asarExtracted, ".vite/build/codex-micro-service-fixture.js"),
- [
- "import { DeviceType, WLDeviceDiscovery } from '@worklouder/device-kit-oai';",
- "const device = DeviceType.Project2077;",
- "WLDeviceDiscovery.start(device);",
- "require('node-hid');",
- ].join("\n"),
- );
- writeJson(path.join(deviceKit, "package.json"), {
- name: "@worklouder/device-kit-oai",
- dependencies: {
- "@worklouder/wl-device-kit": "1.0.0",
- },
- });
- writeFile(
- path.join(deviceKit, "dist/index.js"),
- "export { DeviceType, WLDeviceDiscovery } from '@worklouder/wl-device-kit';",
- );
- writeJson(path.join(wlKit, "package.json"), {
- name: "@worklouder/wl-device-kit",
- dependencies: {
- "node-hid": "3.1.0",
- },
- });
- writeFile(
- path.join(wlKit, "dist/index.js"),
- [
- "const HID = require('node-hid');",
- "export const DeviceType = { Project2077: 'Project2077' };",
- "export class WLDeviceDiscovery { static start() { return HID; } }",
- ].join(" "),
- );
- if (includeHid) {
- writeJson(path.join(hid, "package.json"), {
- name: "node-hid",
- version: "3.1.0",
+ const headerJson = Buffer.from(JSON.stringify(root));
+ const picklePayloadSize = Math.ceil((4 + headerJson.length + 1) / 4) * 4;
+ const headerSize = 4 + picklePayloadSize;
+ const archive = Buffer.alloc(8 + headerSize + offset);
+ archive.writeUInt32LE(4, 0);
+ archive.writeUInt32LE(headerSize, 4);
+ archive.writeUInt32LE(picklePayloadSize, 8);
+ archive.writeUInt32LE(headerJson.length, 12);
+ headerJson.copy(archive, 16);
+ let payloadOffset = 8 + headerSize;
+ for (const payload of payloads) {
+ payload.copy(archive, payloadOffset);
+ payloadOffset += payload.length;
+ }
+ writeFile(filePath, archive);
+}
+
+test("prepares raw app.asar contents for required patch preflight", () =>
+ withTempDir((workspace) => {
+ const appDir = path.join(workspace, "Codex.app");
+ const resourcesDir = path.join(appDir, "Contents/Resources");
+ const asarPath = path.join(resourcesDir, "app.asar");
+ const targetDir = path.join(workspace, "preflight");
+ const mainSource = "let marker=`current-main-bundle`;";
+ writeAsar(asarPath, {
+ ".vite/build/main-test.js": mainSource,
+ "package.json": JSON.stringify({ name: "codex-desktop" }),
+ });
+
+ prepareRequiredPatchPreflightApp({ appDir, targetDir });
+
+ assert.equal(
+ fs.readFileSync(path.join(targetDir, ".vite/build/main-test.js"), "utf8"),
+ mainSource,
+ );
+ assert.equal(fs.existsSync(path.join(resourcesDir, "app.asar.extracted")), false);
+ assert.equal(fs.existsSync(asarPath), true);
+ }));
+
+test("inventories raw app.asar entries with normalized archive paths", () =>
+ withTempDir((workspace) => {
+ const appDir = path.join(workspace, "Codex.app");
+ writeAsar(path.join(appDir, "Contents/Resources/app.asar"), {
+ ".vite/build/main-test.js": "let marker=`current-main-bundle`;",
});
+
+ const inventory = createInventory({ sourcePath: appDir });
+
+ assert.ok(
+ inventory.files.some(
+ (file) => file.relativePath === "Contents/Resources/app.asar/.vite/build/main-test.js",
+ ),
+ );
+ assert.equal(
+ inventory.files.some((file) => file.relativePath.includes(".vite/build/.vite/build")),
+ false,
+ );
+ }));
+
+test("default Read Aloud surface protects the current assistant render contract", () =>
+ withTempDir((workspace) => {
+ const appDir = path.join(workspace, "Codex.app");
+ const assetPath = path.join(
+ appDir,
+ "Contents/Resources/app.asar.extracted/webview/assets",
+ "app-initial~app-main~onboarding-page-zcfEkMl-.js",
+ );
writeFile(
- path.join(hid, "prebuilds/HID-darwin-arm64/node-napi-v4.node"),
- "Mach-O node-hid Project2077",
- 0o755,
+ assetPath,
+ "return (0,Z.jsx)(Xa,{item:a,assistantCopyText:i,conversationId:l,renderCodeBlocksAsWritingBlocks:ge})",
);
- }
-}
+ const defaultRegistry = JSON.parse(
+ fs.readFileSync(path.join(__dirname, "upstream-dmg-protected-surfaces.json"), "utf8"),
+ );
+ const inventory = createInventory({ registry: defaultRegistry, sourcePath: appDir });
+
+ const protectedSurfaces = extractProtectedSurfaces({
+ inventory,
+ registry: defaultRegistry,
+ repoRoot: path.resolve(__dirname, "../.."),
+ });
+ const readAloud = protectedSurfaces.surfaces.find(
+ (surface) => surface.id === "read_aloud_capability",
+ );
+
+ assert.equal(readAloud.status, "PRESENT");
+ assert.deepEqual(readAloud.missingAnchors, []);
+ }));
function createFixtureApp(root, variant = "baseline") {
const appDir = path.join(root, `${variant}.app`);
@@ -269,14 +295,6 @@ function createFixtureApp(root, variant = "baseline") {
);
}
- if (variant === "candidate" || variant === "missing-work-louder-hid") {
- writeWorkLouderControlSurface({
- asarExtracted,
- includeHid: variant !== "missing-work-louder-hid",
- resources,
- });
- }
-
if (variant !== "candidate") {
writeFile(
path.join(asarExtracted, "composer/transcript.js"),
@@ -351,6 +369,48 @@ function createFixtureApp(root, variant = "baseline") {
return appDir;
}
+function addSitesPlugin(appDir) {
+ const sitesPlugin = path.join(
+ appDir,
+ "Contents/Resources/plugins/openai-bundled/plugins/sites",
+ );
+ writeJson(path.join(sitesPlugin, ".app.json"), {
+ apps: {
+ sites: {
+ id: "connector_20205bf7d4e99a89d7154bb849718324",
+ },
+ },
+ });
+ writeJson(path.join(sitesPlugin, ".codex-plugin/plugin.json"), {
+ name: "sites",
+ version: "0.1.21",
+ description: "Build and deploy websites with Sites.",
+ skills: "./skills/",
+ apps: "./.app.json",
+ interface: {
+ displayName: "Sites",
+ shortDescription: "Build and deploy websites with Sites",
+ termsOfServiceURL: "https://openai.com/policies/chatgpt-sites-terms/",
+ category: "Productivity",
+ },
+ });
+ writeFile(
+ path.join(sitesPlugin, "skills/sites-building/SKILL.md"),
+ "Use Sites to build and deploy websites.",
+ );
+}
+
+function writeBundledPlugin(appDir, pluginId, manifest = {}) {
+ const pluginRoot = path.join(appDir, "Contents/Resources/plugins/openai-bundled/plugins", pluginId);
+ writeJson(path.join(pluginRoot, ".codex-plugin/plugin.json"), {
+ id: pluginId,
+ name: pluginId,
+ version: "1.0.0",
+ ...manifest,
+ });
+ return pluginRoot;
+}
+
function findClassification(driftReport, surfaceId, classification) {
return driftReport.surfaceDrift.find(
(entry) => entry.surfaceId === surfaceId && entry.classification === classification,
@@ -438,62 +498,6 @@ test("marks Chronicle settings toggle surface partial when the Memory master tog
);
}));
-test("tracks Work Louder control-surface hooks when the service, kits, and HID runtime are bundled", () =>
- withTempDir((workspace) => {
- const appDir = createFixtureApp(workspace, "candidate");
- const protectedSurfaces = extractProtectedSurfaces({
- inventory: createInventory({ registry, sourcePath: appDir }),
- registry,
- repoRoot: process.cwd(),
- });
- const surface = protectedSurfaces.surfacesById.work_louder_control_surface;
-
- assert.equal(surface.status, "PRESENT");
- assert.equal(surface.linuxSubstrate.status, "UNKNOWN");
- assert.ok(
- surface.satisfiedAnchors.some((anchor) => anchor.id === "micro-service-entrypoint"),
- );
- assert.ok(
- surface.satisfiedAnchors.some((anchor) => anchor.id === "work-louder-device-kits"),
- );
- assert.ok(
- surface.satisfiedAnchors.some((anchor) => anchor.id === "hid-runtime-package"),
- );
- assert.ok(
- surface.evidence.some((entry) => entry.path.includes("codex-micro-service-fixture.js")),
- );
- assert.ok(
- surface.requiredAnchors
- .find((anchor) => anchor.id === "hid-runtime-package")
- .matchedPaths.some((entryPath) => entryPath.includes("node-hid/prebuilds")),
- );
- }));
-
-test("marks Work Louder control-surface hooks partial when the HID runtime disappears", () =>
- withTempDir((workspace) => {
- const appDir = createFixtureApp(workspace, "missing-work-louder-hid");
- const protectedSurfaces = extractProtectedSurfaces({
- inventory: createInventory({ registry, sourcePath: appDir }),
- registry,
- repoRoot: process.cwd(),
- });
- const surface = protectedSurfaces.surfacesById.work_louder_control_surface;
-
- assert.equal(surface.status, "PARTIAL");
- assert.ok(
- surface.satisfiedAnchors.some((anchor) => anchor.id === "micro-service-entrypoint"),
- );
- assert.ok(
- surface.satisfiedAnchors.some((anchor) => anchor.id === "work-louder-device-kits"),
- );
- assert.ok(surface.missingAnchors.some((anchor) => anchor.id === "hid-runtime-package"));
- assert.ok(
- surface.missingAnchors
- .find((anchor) => anchor.id === "hid-runtime-package")
- .missingNeedles.some((needle) => needle.startsWith("nativeBinary:")),
- );
- }));
-
test("classifies protected-surface drift from baseline to candidate", () =>
withTempDir((workspace) => {
const baselineApp = createFixtureApp(workspace, "baseline");
@@ -645,6 +649,410 @@ test("folds patch-report post-patch integrity findings into candidate-only repor
assert.equal(finding.findings[0].path, "webview/assets/settings-page-patched.js");
}));
+test("detects post-patch Computer Use gates left Darwin/Windows-only", () =>
+ withTempDir((workspace) => {
+ const candidateApp = createFixtureApp(workspace, "candidate");
+ const assetsDir = path.join(candidateApp, "Contents/Resources/webview/assets");
+ writeFile(
+ path.join(assetsDir, "use-native-apps-current.js"),
+ "function zN(e){let t=(0,BN.c)(9),{enabled:n}=e,{platform:r,isLoading:i}=pi(),a=n&&(r===`macOS`||r===`windows`),o;t[0]===Symbol.for(`react.memo_cache_sentinel`)?(o={order:`usage`},t[0]=o):o=t[0];let s;t[1]===a?s=t[2]:(s={params:o,queryConfig:{enabled:a,staleTime:m.FIVE_MINUTES,refetchOnWindowFocus:!1}},t[1]=a,t[2]=s);let c=Ne(`native-desktop-apps`,s),l;t[3]!==c||t[4]!==a?(l=a?c.data?.apps??[]:[],t[3]=c,t[4]=a,t[5]=l):l=t[5];let u=i||a&&c.isLoading,d;return t[6]!==l||t[7]!==u?(d={nativeApps:l,isLoading:u},t[6]=l,t[7]=u,t[8]=d):d=t[8],d}",
+ );
+ writeFile(
+ path.join(assetsDir, "composer-computer-use-current.js"),
+ "function z2e(){let T=s===`macOS`||s===`windows`,E=T?U2e(C):[],D=T&&l?C.filter(Tie):[],O=T&&l?C.filter(lae):[],N=D2e({chromeAppPlugins:E,computerUsePlugin:w,microsoftExcelAppPlugins:D,microsoftPowerPointAppPlugins:O,onPluginMentionInserted:M,pluginMentionLabels:x,query:r});return N}",
+ );
+ writeFile(
+ path.join(assetsDir, "computer-use-settings-current.js"),
+ "r===`linux`&&!v.availablePlugins.some(e=>e.plugin?.name===on||e.plugin?.id?.split(`@`)[0]===on)&&(v={...v,availablePlugins:[...v.availablePlugins,{marketplaceName:`openai-curated`,marketplacePath:y,logoPath:new URL(`computer-use-plugin-icon-linux.png`,import.meta.url).href,logoDarkPath:new URL(`computer-use-plugin-icon-linux.png`,import.meta.url).href,plugin:{id:on,name:on,installed:!0,enabled:!0}}]});",
+ );
+ writeFile(
+ path.join(assetsDir, "computer-use-settings-card-current.js"),
+ "function Rt(){let b=flag,r=platform,O=[getApp()],F=[];if(b&&(r===`macOS`||r===`windows`))for(let e of O){if(e.plugin==null)continue;let t=e.plugin;F.push({id:e.appControlId,label:e.toggleAriaLabel,installed:t.plugin.installed,enabled:t.plugin.enabled})}return F}",
+ );
+ writeFile(
+ path.join(assetsDir, "computer-use-native-icon-current.js"),
+ "function nI(e){let t=(0,rI.c)(10),{appPath:n}=e,{platform:r,isLoading:i}=pi(),a=(r===`macOS`||r===`windows`)&&n!=null&&n!==``,o=n??``;let u=Ne(`computer-use-native-desktop-app-icon`,l),d=a?u.data?.iconSmall??null:null;return d}",
+ );
+ writeFile(
+ path.join(candidateApp, "Contents/Resources/app.asar.extracted/.vite/build/main-computer-use.js"),
+ "var bs=[{autoInstallOptOutKey:n.js(n.ws),installWhenMissing:!0,name:n.ws,isAvailable:({features:e,platform:t})=>(t===`darwin`||t===`linux`)&&e.computerUse,migrate:Ko}];",
+ );
+
+ const inventory = createInventory({ registry, sourcePath: candidateApp });
+
+ assert.equal(
+ findPostPatchIntegrityFindings(inventory).some((finding) =>
+ finding.symbol.startsWith("computer-use-"),
+ ),
+ false,
+ );
+ const findings = findPostPatchIntegrityFindings(inventory, {
+ includeComputerUsePlatformGates: true,
+ });
+ assert.deepEqual(
+ findings.map((finding) => finding.symbol).filter((symbol) => symbol.startsWith("computer-use-")).sort(),
+ [
+ "computer-use-composer-native-app-mentions-linux-gate",
+ "computer-use-native-app-icon-linux-gate",
+ "computer-use-native-apps-linux-gate",
+ "computer-use-plugin-registration-rollout-gate",
+ "computer-use-settings-native-app-card-linux-gate",
+ "computer-use-settings-synthetic-plugin-mask",
+ ],
+ );
+ }));
+
+test("categorizes platform gates for Linux parity, unsupported features, and review candidates", () =>
+ withTempDir((workspace) => {
+ const candidateApp = createFixtureApp(workspace, "candidate");
+ const assetsDir = path.join(candidateApp, "Contents/Resources/webview/assets");
+ writeFile(
+ path.join(assetsDir, "computer-use-current.js"),
+ "function zN(e){let {enabled:n}=e,{platform:r}=pi(),a=n&&(r===`macOS`||r===`windows`);let c=Ne(`native-desktop-apps`,{queryConfig:{enabled:a}});return {nativeApps:c.data?.apps??[],computerUsePlugin:w}}",
+ );
+ writeFile(
+ path.join(assetsDir, "computer-use-settings-B1QCeMSP.js"),
+ "function settings(){let b=flag,r=platform,O=[getApp()],F=[];if(b&&(r===`macOS`||r===`windows`))for(let e of O){if(e.plugin==null)continue;let t=e.plugin;F.push({id:e.appControlId,label:e.toggleAriaLabel,installed:t.plugin.installed,enabled:t.plugin.enabled})}return F}",
+ );
+ writeFile(
+ path.join(assetsDir, "computer-use-native-icon-current.js"),
+ "function nI(e){let t=(0,rI.c)(10),{appPath:n}=e,{platform:r,isLoading:i}=pi(),a=(r===`macOS`||r===`windows`)&&n!=null&&n!==``,o=n??``;let u=Ne(`computer-use-native-desktop-app-icon`,l),d=a?u.data?.iconSmall??null:null;return d}",
+ );
+ writeFile(
+ path.join(assetsDir, "office-current.js"),
+ "function z2e(){let T=s===`macOS`||s===`windows`;return D2e({microsoftExcelAppPlugins:D,microsoftPowerPointAppPlugins:O,onPluginMentionInserted:M})}",
+ );
+ writeFile(
+ path.join(assetsDir, "hotkey-current.js"),
+ "function hotkeys(){return process.platform===`darwin`||process.platform===`win32`?{setToggleHotkey:()=>true,syncCommandKeybindings:()=>true}:null}",
+ );
+ writeFile(
+ path.join(assetsDir, "agi-skysight-supreme.js"),
+ "function supreme(){let ok=p===`macOS`||p===`windows`;return ok?`AGI Intelligence 9000 Skysight Supreme native desktop sidecar`:null}",
+ );
+ writeFile(
+ path.join(assetsDir, "titlebar-current.js"),
+ "function titlebar(){return process.platform===`darwin`||process.platform===`win32`?{titleBarStyle:`hiddenInset`,trafficLightPosition:{x:12,y:12}}:{}}",
+ );
+
+ const platformGateMap = createPlatformGateMap({
+ inventory: createInventory({ registry, sourcePath: candidateApp }),
+ });
+ const byCategory = new Map(platformGateMap.gates.map((gate) => [gate.category, gate]));
+
+ assert.ok(byCategory.get("linux-parity-drift"));
+ assert.equal(byCategory.get("linux-parity-drift").linuxSurfaceId, "computer_use_plugin");
+ assert.ok(
+ platformGateMap.gates
+ .filter((gate) => gate.path.includes("computer-use"))
+ .every((gate) => gate.category === "linux-parity-drift"),
+ );
+ assert.ok(
+ platformGateMap.gates.some(
+ (gate) =>
+ gate.feature === "Computer Use settings native app cards" &&
+ gate.patchTarget === "scripts/patches/impl/computer-use.js",
+ ),
+ );
+ assert.ok(
+ platformGateMap.gates.some(
+ (gate) =>
+ gate.feature === "Computer Use native app icons" &&
+ gate.patchTarget === "scripts/patches/impl/computer-use.js",
+ ),
+ );
+ assert.ok(byCategory.get("platform-specific-unsupported"));
+ assert.match(byCategory.get("platform-specific-unsupported").recommendation, /macOS\/Windows-only/);
+ assert.ok(
+ platformGateMap.gates.some(
+ (gate) =>
+ gate.category === "platform-specific-unsupported" &&
+ gate.feature === "Global hotkey/keybinding integration",
+ ),
+ );
+ assert.ok(byCategory.get("new-upstream-capability"));
+ assert.match(byCategory.get("new-upstream-capability").feature, /Unmapped/);
+ assert.ok(byCategory.get("expected-platform-native"));
+ assert.equal(platformGateMap.blockingCount, 3);
+ }));
+
+test("does not treat generic Computer Use mentions as Linux parity drift without exact contracts", () =>
+ withTempDir((workspace) => {
+ const candidateApp = createFixtureApp(workspace, "candidate");
+ const assetsDir = path.join(candidateApp, "Contents/Resources/webview/assets");
+ writeFile(
+ path.join(assetsDir, "computer-use-marketing-current.js"),
+ "function teaser(){let allowed=p===`macOS`||p===`windows`;return allowed?`computer use desktop preview`:null}",
+ );
+
+ const platformGateMap = createPlatformGateMap({
+ inventory: createInventory({ registry, sourcePath: candidateApp }),
+ });
+ const gate = platformGateMap.gates.find((entry) =>
+ entry.path.endsWith("computer-use-marketing-current.js"),
+ );
+
+ assert.ok(gate);
+ assert.equal(gate.category, "needs-review");
+ }));
+
+test("marks Computer Use platform gates covered only after exact parity patches apply", () =>
+ withTempDir((workspace) => {
+ const candidateApp = createFixtureApp(workspace, "candidate");
+ const assetsDir = path.join(candidateApp, "Contents/Resources/webview/assets");
+ writeFile(
+ path.join(assetsDir, "computer-use-current.js"),
+ "function zN(e){let {enabled:n}=e,{platform:r}=pi(),a=n&&(r===`macOS`||r===`windows`);let c=Ne(`native-desktop-apps`,{queryConfig:{enabled:a}});return {nativeApps:c.data?.apps??[],computerUsePlugin:w}}",
+ );
+ const patchFindings = [
+ "linux-computer-use-ui-feature",
+ "linux-computer-use-plugin-gate",
+ "linux-computer-use-native-desktop-apps",
+ "linux-computer-use-ui-availability",
+ "linux-computer-use-install-flow",
+ ].map((name) => ({ name, status: "applied" }));
+ const inventory = createInventory({ registry, sourcePath: candidateApp });
+
+ const covered = createPlatformGateMap({ inventory, patchFindings });
+ const computerUseGates = covered.gates.filter(
+ (gate) => gate.linuxSurfaceId === "computer_use_plugin",
+ );
+
+ assert.ok(computerUseGates.length > 0);
+ assert.ok(computerUseGates.every((gate) => gate.category === "patched-linux-parity"));
+ assert.equal(covered.blockingCount, 0);
+
+ const failed = createPlatformGateMap({
+ inventory,
+ patchFindings: patchFindings.map((finding) =>
+ finding.name === "linux-computer-use-install-flow"
+ ? { ...finding, status: "skipped-optional" }
+ : finding,
+ ),
+ });
+ assert.ok(
+ failed.gates.some(
+ (gate) =>
+ gate.linuxSurfaceId === "computer_use_plugin" &&
+ gate.category === "linux-parity-drift",
+ ),
+ );
+ assert.ok(failed.blockingCount > 0);
+ }));
+
+test("maps Chronicle and Skysight platform gates to the record-and-replay Linux owners", () =>
+ withTempDir((workspace) => {
+ const candidateApp = createFixtureApp(workspace, "candidate");
+ const assetsDir = path.join(candidateApp, "Contents/Resources/webview/assets");
+ writeFile(
+ path.join(assetsDir, "chronicle-settings-current.js"),
+ "function chronicle(){let allowed=p===`macOS`||p===`windows`;return allowed&&chronicleSidecarPresent&&chronicleSidecarProcessState&&rememberConsentAccepted?o.mutateAsync({enabled:!0}):chronicleDisable?.()}",
+ );
+ writeFile(
+ path.join(assetsDir, "skysight-controls-current.js"),
+ "function skysight(){return process.platform===`darwin`||process.platform===`win32`?{status:`linux-record-replay-skysight-status`,snapshot:`skysight_snapshot`,tool:`event_stream_start`}:null}",
+ );
+
+ const platformGateMap = createPlatformGateMap({
+ inventory: createInventory({ registry, sourcePath: candidateApp }),
+ });
+ const chronicleGate = platformGateMap.gates.find((entry) =>
+ entry.path.endsWith("chronicle-settings-current.js"),
+ );
+ const skysightGate = platformGateMap.gates.find((entry) =>
+ entry.path.endsWith("skysight-controls-current.js"),
+ );
+
+ assert.ok(chronicleGate);
+ assert.equal(chronicleGate.category, "linux-parity-drift");
+ assert.equal(chronicleGate.feature, "Chronicle settings toggle paths");
+ assert.equal(chronicleGate.patchTarget, "linux-features/record-and-replay/patch.js");
+
+ assert.ok(skysightGate);
+ assert.equal(skysightGate.category, "linux-parity-drift");
+ assert.equal(skysightGate.feature, "Skysight controls and bridge");
+ assert.equal(
+ skysightGate.patchTarget,
+ "linux-features/record-and-replay/patch.js and record-replay-linux/src/mcp.rs",
+ );
+ }));
+
+test("keeps review-only platform gates out of new capability candidates", () => {
+ const capabilityMap = createNewCapabilityMap({
+ mapDrift: { mode: "baselineComparison" },
+ platformGateMap: {
+ gates: [
+ {
+ id: "review-gate",
+ category: "needs-review",
+ confidence: "low",
+ feature: "Unclassified platform gate",
+ issueCandidate: false,
+ recommendation: "Review manually.",
+ patchTarget: "manual review",
+ path: "assets/review.js",
+ gate: "p===`macOS`||p===`windows`",
+ },
+ {
+ id: "new-gate",
+ category: "new-upstream-capability",
+ confidence: "medium",
+ feature: "New native capability",
+ issueCandidate: true,
+ recommendation: "Create a Linux feature issue.",
+ patchTarget: "linux-features/new-native-capability",
+ path: "assets/new.js",
+ gate: "p===`macOS`||p===`windows`",
+ },
+ ],
+ },
+ });
+
+ assert.ok(capabilityMap.capabilities.some((capability) => capability.id === "platform-gate:new-gate"));
+ assert.ok(!capabilityMap.capabilities.some((capability) => capability.id === "platform-gate:review-gate"));
+});
+
+test("keeps framework, app-shell, plugin, and dependency binaries out of feature candidates", () => {
+ const capabilityMap = createNewCapabilityMap({
+ mapDrift: {
+ mode: "baselineComparison",
+ nativeBinaryDrift: {
+ added: [
+ "Contents/Frameworks/Codex Framework.framework/Versions/150.0.7871.101/Helpers/browser_crashpad_handler",
+ "Contents/MacOS/Codex",
+ "Contents/Resources/plugins/openai-bundled/plugins/chrome/extension-host/macos/arm64/ChatGPT for Chrome",
+ "Contents/Resources/app.asar.unpacked/node_modules/example/build/Release/example.node",
+ "Contents/Resources/codex-code-mode-host",
+ ],
+ },
+ },
+ platformGateMap: { gates: [] },
+ });
+
+ assert.deepEqual(
+ capabilityMap.capabilities.filter((capability) => capability.type === "native-binary").map((capability) => capability.path),
+ ["Contents/Resources/codex-code-mode-host"],
+ );
+});
+
+test("reports Sites as a cross-platform entitlement-gated capability with a staging recommendation", () =>
+ withTempDir((workspace) => {
+ const baselineApp = createFixtureApp(workspace, "baseline");
+ const candidateApp = createFixtureApp(workspace, "candidate");
+ const outputDir = path.join(workspace, "sites-report");
+ addSitesPlugin(candidateApp);
+
+ const reports = buildIntelReports({
+ baselinePath: baselineApp,
+ candidatePath: candidateApp,
+ outputDir,
+ registry,
+ repoRoot: process.cwd(),
+ });
+ const sitesCapability = reports.newCapabilityMap.capabilities.find(
+ (capability) => capability.name === "Sites",
+ );
+ const driftMarkdown = fs.readFileSync(path.join(outputDir, "drift-report.md"), "utf8");
+
+ assert.ok(sitesCapability);
+ assert.equal(sitesCapability.category, "cross-platform-entitlement-gated");
+ assert.equal(sitesCapability.version, "0.1.21");
+ assert.equal(sitesCapability.platformLabel, "cross-platform");
+ assert.match(
+ sitesCapability.entitlementLabel,
+ /connector_20205bf7d4e99a89d7154bb849718324/,
+ );
+ assert.equal(sitesCapability.patchTarget, "scripts/lib/bundled-plugins.sh");
+ assert.match(sitesCapability.recommendation, /staging issue/i);
+ assert.match(driftMarkdown, /\| Sites \| 0\.1\.21 \| plugin \| cross-platform-entitlement-gated \|/);
+ assert.match(
+ driftMarkdown,
+ /connector_20205bf7d4e99a89d7154bb849718324/,
+ );
+ assert.doesNotMatch(driftMarkdown, /\[object Object\]/);
+ assert.match(driftMarkdown, /\| --- \| --- \| --- \| --- \|/);
+ }));
+
+test("emits app, Electron, CLI, and bundled-plugin version deltas", () => {
+ const metadata = extractVersionMetadata([
+ {
+ relativePath: "Contents/Resources/vendor/Info.plist",
+ text: "CFBundleShortVersionString 3.2.1 CFBundleVersion 99",
+ },
+ {
+ relativePath: "Contents/Resources/node_modules/canvas/package.json",
+ text: '{"version":"3.2.1","dependencies":{"electron":"40.0.0"}}',
+ },
+ {
+ relativePath: "Contents/Resources/codex",
+ nativeStrings: ["unrelated protocol version 2.0"],
+ },
+ {
+ relativePath: "Contents/Info.plist",
+ text: "CFBundleShortVersionString 1.2.3 CFBundleVersion 456",
+ },
+ {
+ relativePath: "package.json",
+ source: "asar",
+ text: '{"name":"openai-codex-electron","version":"26.623.141536","devDependencies":{"electron":"42.1.0"}}',
+ },
+ {
+ relativePath: "Contents/Resources/codex",
+ nativeStrings: ["codex-cli version 0.99.1"],
+ },
+ {
+ relativePath: "Contents/Resources/plugins/openai-bundled/plugins/sites/.codex-plugin/plugin.json",
+ text: '{"version":"0.1.21"}',
+ },
+ ]);
+
+ assert.equal(metadata.cfBundleShortVersionString, "1.2.3");
+ assert.equal(metadata.cfBundleVersion, "456");
+ assert.equal(metadata.appPackageVersion, "26.623.141536");
+ assert.equal(metadata.electronVersion, "42.1.0");
+ assert.equal(metadata.codexCliVersion, "0.99.1");
+ assert.equal(metadata.bundledPluginVersions.sites, "0.1.21");
+});
+
+test("candidate URL augments detected DMG provenance without dropping hashes", () => {
+ assert.deepEqual(
+ mergeProvenance(
+ {
+ candidate: { bytes: 123, sha256: "candidate-sha", etag: "etag", lastModified: "today", url: null },
+ baseline: { bytes: 100, sha256: "baseline-sha", etag: null, lastModified: null, url: null },
+ },
+ { candidate: { url: "https://example.test/Codex.dmg" } },
+ ),
+ {
+ candidate: {
+ bytes: 123,
+ sha256: "candidate-sha",
+ etag: "etag",
+ lastModified: "today",
+ url: "https://example.test/Codex.dmg",
+ },
+ baseline: { bytes: 100, sha256: "baseline-sha", etag: null, lastModified: null, url: null },
+ },
+ );
+});
+
+test("production registry protects Sites and exact remote-mobile contracts", () => {
+ const productionRegistry = JSON.parse(
+ fs.readFileSync(path.join(process.cwd(), "scripts/dev/upstream-dmg-protected-surfaces.json"), "utf8"),
+ );
+ const sites = productionRegistry.surfaces.find((surface) => surface.id === "sites_plugin");
+ const remoteMobile = productionRegistry.surfaces.find((surface) => surface.id === "remote_mobile_control");
+
+ assert.ok(sites);
+ assert.deepEqual(sites.pluginIds, ["sites"]);
+ assert.ok(sites.linuxSubstrate.requiredPaths.includes("scripts/lib/bundled-plugins.sh"));
+ assert.ok(remoteMobile);
+ assert.ok(remoteMobile.contentNeedles.includes("set-remote-control-connections-enabled"));
+ assert.ok(remoteMobile.contentNeedles.includes("remote_control_connections"));
+ assert.ok(!remoteMobile.contentNeedles.includes("mobile"));
+ assert.ok(!remoteMobile.contentNeedles.includes("control"));
+});
+
test("keeps drift report evidence compact and marks hashed asset churn", () => {
const baseline = {
source: { path: "baseline.app" },
@@ -812,6 +1220,8 @@ printf '00000000 T _SkyComputerUseClient\\n'
"bridge-map.json",
"plugin-map.json",
"native-binary-map.json",
+ "platform-gates.json",
+ "new-capabilities.json",
"map-drift.json",
"drift-report.json",
"drift-report.md",
@@ -833,9 +1243,16 @@ printf '00000000 T _SkyComputerUseClient\\n'
const nativeBinaryMap = JSON.parse(
fs.readFileSync(path.join(reports.outputDir, "native-binary-map.json"), "utf8"),
);
+ const platformGateMap = JSON.parse(
+ fs.readFileSync(path.join(reports.outputDir, "platform-gates.json"), "utf8"),
+ );
+ const newCapabilityMap = JSON.parse(
+ fs.readFileSync(path.join(reports.outputDir, "new-capabilities.json"), "utf8"),
+ );
const mapDrift = JSON.parse(
fs.readFileSync(path.join(reports.outputDir, "map-drift.json"), "utf8"),
);
+ const driftMarkdown = fs.readFileSync(path.join(reports.outputDir, "drift-report.md"), "utf8");
const actionPlan = fs.readFileSync(path.join(reports.outputDir, "substrate-action-plan.md"), "utf8");
const skyDrift = findClassification(driftReport, "sky_computer_use_client", "MOVED");
assert.ok(findClassification(driftReport, "chronicle_sidecar", "NEW_UPSTREAM_CAPABILITY"));
@@ -845,6 +1262,10 @@ printf '00000000 T _SkyComputerUseClient\\n'
assert.ok(skyDrift.evidenceDrift.addedPathSamples.length > 0);
assert.equal(skyDrift.evidenceDrift.addedEvidence, undefined);
assert.ok(inventory.files.every((file) => file.text == null && file.nativeStrings == null));
+ assert.ok(Array.isArray(platformGateMap.gates));
+ assert.ok(newCapabilityMap.capabilities.some((capability) => capability.type === "mcp-tool"));
+ assert.match(driftMarkdown, /## New Capability Candidates/);
+ assert.match(driftMarkdown, /## Linux Parity Drift/);
assert.ok(
nativeBinaryMap.binaries.some((binary) =>
binary.protectedStringHits.some((hit) => hit.needle === "recording_controls"),
@@ -942,7 +1363,6 @@ test("CLI loads the checked-in registry and writes the report bundle", () =>
assert.equal(protectedSurfaces.surfacesById.record_and_replay_plugin.status, "PRESENT");
assert.equal(protectedSurfaces.surfacesById.codex_chronicle.status, "PRESENT");
assert.equal(protectedSurfaces.surfacesById.chronicle_settings_toggles.status, "PRESENT");
- assert.equal(protectedSurfaces.surfacesById.work_louder_control_surface.status, "PRESENT");
}));
test("CLI exits nonzero with --fail-on-blockers when acceptance blockers are present", () =>
@@ -968,7 +1388,7 @@ test("CLI exits nonzero with --fail-on-blockers when acceptance blockers are pre
const summary = JSON.parse(result.stdout);
assert.equal(summary.decision.acceptance, "blocked");
assert.ok(summary.decision.blockersCount > 0);
- assert.match(result.stderr, /protected-surface acceptance blocker/);
+ assert.match(result.stderr, /Linux acceptance blocker/);
assert.ok(fs.existsSync(path.join(outputDir, "drift-report.json")));
}));
diff --git a/scripts/dev/upstream-dmg-protected-surfaces.json b/scripts/dev/upstream-dmg-protected-surfaces.json
index 449ebe859..7690933fd 100644
--- a/scripts/dev/upstream-dmg-protected-surfaces.json
+++ b/scripts/dev/upstream-dmg-protected-surfaces.json
@@ -2,6 +2,57 @@
"version": 1,
"description": "Protected upstream Codex Desktop macOS DMG surfaces that Linux packaging mirrors, patches, stages, or must explicitly classify before accepting a new upstream build.",
"surfaces": [
+ {
+ "id": "read_aloud_capability",
+ "title": "Read Aloud capability and staging contract",
+ "category": "feature",
+ "pathPatterns": ["webview/assets/app-initial~app-main~onboarding-page-[A-Za-z0-9_-]+\\.js$"],
+ "contentNeedles": ["assistantCopyText", "conversationId", "renderCodeBlocksAsWritingBlocks"],
+ "patchNamePatterns": ["read.*aloud"],
+ "requiredEvidence": [
+ {
+ "id": "current-assistant-render-contract",
+ "pathPatterns": ["webview/assets/app-initial~app-main~onboarding-page-[A-Za-z0-9_-]+\\.js$"],
+ "contentNeedles": ["assistantCopyText", "conversationId", "renderCodeBlocksAsWritingBlocks"]
+ }
+ ],
+ "linuxSubstrate": {"requiredPaths": ["linux-features/read-aloud", "linux-features/read-aloud/patch.js"]}
+ },
+ {
+ "id": "sites_plugin",
+ "title": "Sites plugin manifest, connector registration, and Linux staging",
+ "category": "plugin",
+ "pathPatterns": ["plugins/openai-bundled/plugins/sites/"],
+ "contentNeedles": ["connector_20205bf7d4e99a89d7154bb849718324", "chatgpt-sites-terms"],
+ "pluginIds": ["sites"],
+ "patchNamePatterns": ["sites", "bundled.*plugin"],
+ "requiredEvidence": [
+ {
+ "id": "sites-plugin-manifest",
+ "pathPatterns": ["plugins/openai-bundled/plugins/sites/\\.codex-plugin/plugin\\.json$"],
+ "pluginIds": ["sites"]
+ },
+ {
+ "id": "sites-connector-registration",
+ "pathPatterns": ["plugins/openai-bundled/plugins/sites/\\.app\\.json$"],
+ "contentNeedles": ["connector_20205bf7d4e99a89d7154bb849718324"]
+ }
+ ],
+ "linuxSubstrate": {"requiredPaths": ["scripts/lib/bundled-plugins.sh"]}
+ },
+ {
+ "id": "remote_mobile_control",
+ "title": "Remote mobile control UI, bridge, and staging contract",
+ "category": "feature",
+ "pathPatterns": ["remote-mobile-control", "remote-control-connections", "remote-connections-settings"],
+ "contentNeedles": [
+ "remote_control_connections",
+ "set-remote-control-connections-enabled",
+ "shouldLoadRemoteControlConnections"
+ ],
+ "patchNamePatterns": ["remote.*mobile", "mobile.*control"],
+ "linuxSubstrate": {"requiredPaths": ["linux-features/remote-mobile-control", "linux-features/remote-mobile-control/stage.sh"]}
+ },
{
"id": "codex_chronicle",
"title": "Chronicle sidecar binary and event bundle schema",
diff --git a/scripts/lib/upstream-dmg-intel.js b/scripts/lib/upstream-dmg-intel.js
index 1f103e46a..a4958281a 100644
--- a/scripts/lib/upstream-dmg-intel.js
+++ b/scripts/lib/upstream-dmg-intel.js
@@ -6,7 +6,7 @@ const os = require("node:os");
const path = require("node:path");
const { spawnSync } = require("node:child_process");
-const TEXT_FILE_PATTERN = /\.(cjs|css|html|js|json|mjs|md|text|ts|tsx|txt|xml|yml|yaml)$/i;
+const TEXT_FILE_PATTERN = /\.(cjs|css|html|js|json|mjs|md|plist|text|ts|tsx|txt|xml|yml|yaml)$/i;
const NATIVE_FILE_PATTERN = /(^|\/)(codex_chronicle|SkyComputerUseClient|sky\.node|node_repl|node|[^/]+\.(node|dylib))$/i;
const DEFAULT_MAX_TEXT_BYTES = 2_500_000;
const DEFAULT_MAX_INVENTORY_HASH_BYTES = 10_000_000;
@@ -29,6 +29,23 @@ const ACTIONABLE_CLASSIFICATIONS = new Set([
"PROTECTED_SURFACE_PARTIAL",
"PROTECTED_SURFACE_MISSING",
]);
+const PLATFORM_GATE_BLOCKING_CATEGORIES = new Set(["linux-parity-drift"]);
+const PLATFORM_GATE_REVIEW_CATEGORIES = new Set([
+ "new-upstream-capability",
+ "platform-specific-unsupported",
+ "needs-review",
+]);
+const LINUX_PARITY_SURFACE_PATCHES = {
+ computer_use_plugin: [
+ "linux-computer-use-ui-feature",
+ "linux-computer-use-plugin-gate",
+ "linux-computer-use-native-desktop-apps",
+ "linux-computer-use-ui-availability",
+ "linux-computer-use-install-flow",
+ ],
+};
+const PLATFORM_GATE_MARKDOWN_LIMIT = 20;
+const NEW_CAPABILITY_MARKDOWN_LIMIT = 20;
const STRING_LITERAL_PATTERN = /`([^`\\]*(?:\\.[^`\\]*)*)`|"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/g;
const BRIDGE_CHANNEL_TERM_PATTERN =
/browser[-_]trace|chrome|chronicle|computer[-_]use|desktop[-_]snapshot|dictation|event[-_]stream|focused[-_]window|global[-_]dictation|nativeMessaging|record[-_ ]?(?:and[-_ ]?)?replay|skysight|speech[-_]context|window[-_]metadata/i;
@@ -41,6 +58,22 @@ function sha256(buffer) {
return crypto.createHash("sha256").update(buffer).digest("hex");
}
+function sha256File(filePath) {
+ const hash = crypto.createHash("sha256");
+ const buffer = Buffer.allocUnsafe(1024 * 1024);
+ const fd = fs.openSync(filePath, "r");
+ try {
+ let bytesRead;
+ do {
+ bytesRead = fs.readSync(fd, buffer, 0, buffer.length, null);
+ if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
+ } while (bytesRead > 0);
+ } finally {
+ fs.closeSync(fd);
+ }
+ return hash.digest("hex");
+}
+
function readJson(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}
@@ -50,6 +83,136 @@ function writeJson(filePath, value) {
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
}
+const REQUIRED_PATCH_PREFLIGHTS = new Set([
+ "linux-window-options",
+ "linux-native-titlebar",
+ "linux-avatar-overlay-mouse-passthrough",
+ "linux-tray",
+ "main-process-ui",
+]);
+const LINUX_PARITY_PATCH_PREFLIGHTS = new Set([
+ "linux-computer-use-ui-feature",
+ "linux-computer-use-plugin-gate",
+ "linux-computer-use-native-desktop-apps",
+ "linux-computer-use-ui-availability",
+ "linux-computer-use-install-flow",
+ "feature:read-aloud:main-handler",
+ "feature:read-aloud:assistant-runtime",
+ "feature:read-aloud:settings-toggle",
+ "feature:read-aloud-mcp:linux-read-aloud-plugin-gate",
+]);
+const PATCH_PREFLIGHTS = new Set([
+ ...REQUIRED_PATCH_PREFLIGHTS,
+ ...LINUX_PARITY_PATCH_PREFLIGHTS,
+]);
+
+function patchPreflightOwner(name) {
+ if (name === "main-process-ui") {
+ return "scripts/patches/runner.js";
+ }
+ if (name.startsWith("linux-computer-use")) {
+ return "scripts/patches/impl/computer-use.js";
+ }
+ if (name.includes("read-aloud")) {
+ return name.startsWith("feature:read-aloud-mcp")
+ ? "linux-features/read-aloud-mcp/patches.js"
+ : "linux-features/read-aloud/patch.js";
+ }
+ return "scripts/patches/core/all-linux/main-process/window-shell/patch.js";
+}
+
+function patchPreflightSurfaceId(name) {
+ if (name.startsWith("linux-computer-use")) {
+ return "computer_use_plugin";
+ }
+ if (name.includes("read-aloud")) {
+ return "read_aloud_capability";
+ }
+ return null;
+}
+
+function runRequiredPatchPreflight({ inventory, repoRoot, workDir } = {}) {
+ const result = {
+ status: "not-run",
+ evidenceType: "safe-extracted-candidate-patch-preflight",
+ sourcePath: inventory?.source?.appDir ?? null,
+ findings: [],
+ };
+ if (!inventory?.source?.appDir || !fs.existsSync(path.join(repoRoot, "scripts/patch-linux-window-ui.js"))) {
+ result.status = "unknown";
+ result.reason = "patch preflight entrypoint unavailable";
+ return result;
+ }
+ const extractedCopy = path.join(workDir, "required-patch-preflight-app");
+ try {
+ prepareRequiredPatchPreflightApp({
+ appDir: inventory.source.appDir,
+ targetDir: extractedCopy,
+ });
+ } catch (error) {
+ result.status = "blocked";
+ result.reason = error.message;
+ result.findings = [...PATCH_PREFLIGHTS].map((name) => ({
+ name,
+ status: "failed-required",
+ severity: "blocker",
+ ownerPath: patchPreflightOwner(name),
+ surfaceId: patchPreflightSurfaceId(name),
+ recommendation: "Restore candidate extraction before package build.",
+ reason: error.message,
+ }));
+ return result;
+ }
+ const featuresConfigPath = path.join(workDir, "required-patch-preflight-features.json");
+ writeJson(featuresConfigPath, { enabled: ["read-aloud", "read-aloud-mcp"] });
+ const reportPath = path.join(workDir, "required-patch-preflight.json");
+ const command = spawnSync(process.execPath, [
+ path.join(repoRoot, "scripts/patch-linux-window-ui.js"),
+ "--report-json", reportPath,
+ "--enforce-critical",
+ extractedCopy,
+ ], {
+ encoding: "utf8",
+ env: {
+ ...process.env,
+ CODEX_LINUX_ENABLE_COMPUTER_USE_UI: "1",
+ CODEX_LINUX_FEATURES_CONFIG: featuresConfigPath,
+ CODEX_LINUX_FEATURES_ROOT: path.join(repoRoot, "linux-features"),
+ HOME: path.join(workDir, "home"),
+ },
+ maxBuffer: 4 * 1024 * 1024,
+ });
+ const report = fs.existsSync(reportPath) ? readJson(reportPath) : { patches: [] };
+ const patches = (report.patches ?? []).filter((entry) => PATCH_PREFLIGHTS.has(entry.name));
+ result.status = command.status === 0 && [...PATCH_PREFLIGHTS].every((name) =>
+ patches.some((entry) => entry.name === name && ["applied", "already-applied"].includes(entry.status)))
+ ? "pass"
+ : "blocked";
+ result.exitCode = command.status;
+ result.findings = patches.map((entry) => ({
+ name: entry.name,
+ status: entry.status,
+ severity: ["applied", "already-applied"].includes(entry.status) ? "none" : "blocker",
+ ownerPath: patchPreflightOwner(entry.name),
+ surfaceId: patchPreflightSurfaceId(entry.name),
+ recommendation: ["applied", "already-applied"].includes(entry.status) ? "No action." : "Restore the exact current-bundle patch contract before package build.",
+ reason: entry.reason ?? null,
+ }));
+ for (const name of PATCH_PREFLIGHTS) {
+ if (!patches.some((entry) => entry.name === name)) {
+ result.findings.push({
+ name,
+ status: "not-recorded",
+ severity: "blocker",
+ ownerPath: patchPreflightOwner(name),
+ surfaceId: patchPreflightSurfaceId(name),
+ recommendation: "Record and resolve this required patch before package build.",
+ });
+ }
+ }
+ return result;
+}
+
function normalizePath(value) {
return String(value).replace(/\\/g, "/").replace(/^\/+/, "");
}
@@ -106,6 +269,7 @@ function printableStrings(buffer, minLength = 4) {
}
function asarEntries(asarPath, prefix = "app.asar") {
+ const archivePrefix = prefix;
const archive = fs.readFileSync(asarPath);
if (archive.length < 16) {
throw new Error(`Invalid ASAR archive: ${asarPath}`);
@@ -117,9 +281,9 @@ function asarEntries(asarPath, prefix = "app.asar") {
const dataStart = 8 + headerSize;
const entries = [];
- function walk(prefix, files) {
+ function walk(directory, files) {
for (const [name, entry] of Object.entries(files ?? {})) {
- const fullPath = prefix ? `${prefix}/${name}` : name;
+ const fullPath = directory ? `${directory}/${name}` : name;
if (entry.files) {
walk(fullPath, entry.files);
} else {
@@ -128,8 +292,11 @@ function asarEntries(asarPath, prefix = "app.asar") {
const unpacked = Boolean(entry.unpacked);
const buffer = unpacked ? null : archive.subarray(dataStart + offset, dataStart + offset + size);
entries.push({
+ archivePath: fullPath,
buffer,
- relativePath: `${prefix}/${fullPath}`,
+ executable: Boolean(entry.executable),
+ link: entry.link ?? null,
+ relativePath: normalizePath(path.posix.join(archivePrefix, fullPath)),
size,
source: "asar",
unpacked,
@@ -142,6 +309,70 @@ function asarEntries(asarPath, prefix = "app.asar") {
return entries;
}
+function asarDestinationPath(targetDir, archivePath) {
+ const normalized = normalizePath(archivePath);
+ if (normalized.length === 0 || normalized === ".." || normalized.startsWith("../")) {
+ throw new Error(`Unsafe ASAR entry path: ${archivePath}`);
+ }
+ const targetRoot = path.resolve(targetDir);
+ const destination = path.resolve(targetRoot, ...normalized.split("/"));
+ if (destination !== targetRoot && !destination.startsWith(`${targetRoot}${path.sep}`)) {
+ throw new Error(`Unsafe ASAR entry path: ${archivePath}`);
+ }
+ return destination;
+}
+
+function extractAsarToDirectory(asarPath, targetDir) {
+ fs.mkdirSync(targetDir, { recursive: true });
+ for (const entry of asarEntries(asarPath, "")) {
+ if (entry.link != null) {
+ continue;
+ }
+ const destination = asarDestinationPath(targetDir, entry.archivePath);
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
+ if (entry.unpacked) {
+ const unpackedSource = path.join(`${asarPath}.unpacked`, ...entry.archivePath.split("/"));
+ if (fs.existsSync(unpackedSource)) {
+ fs.copyFileSync(unpackedSource, destination);
+ }
+ } else if (entry.buffer != null) {
+ fs.writeFileSync(destination, entry.buffer, entry.executable ? { mode: 0o755 } : undefined);
+ }
+ }
+}
+
+function copyDirectoryContents(sourceDir, targetDir) {
+ fs.mkdirSync(targetDir, { recursive: true });
+ for (const entry of fs.readdirSync(sourceDir)) {
+ fs.cpSync(path.join(sourceDir, entry), path.join(targetDir, entry), { recursive: true });
+ }
+}
+
+function prepareRequiredPatchPreflightApp({ appDir, targetDir } = {}) {
+ if (appDir == null || targetDir == null) {
+ throw new Error("appDir and targetDir are required for patch preflight");
+ }
+ fs.rmSync(targetDir, { force: true, recursive: true });
+ const resourcesDir = fs.existsSync(path.join(appDir, "Contents/Resources"))
+ ? path.join(appDir, "Contents/Resources")
+ : appDir;
+ const extractedAsarDir = path.join(resourcesDir, "app.asar.extracted");
+ const asarPath = path.join(resourcesDir, "app.asar");
+ if (fs.existsSync(extractedAsarDir)) {
+ copyDirectoryContents(extractedAsarDir, targetDir);
+ return targetDir;
+ }
+ if (fs.existsSync(asarPath)) {
+ extractAsarToDirectory(asarPath, targetDir);
+ return targetDir;
+ }
+ if (fs.existsSync(path.join(appDir, ".vite/build"))) {
+ copyDirectoryContents(appDir, targetDir);
+ return targetDir;
+ }
+ throw new Error(`Could not find app.asar or extracted app contents under ${appDir}`);
+}
+
function walkFiles(rootDir, source = "filesystem", prefix = "") {
const files = [];
const stack = [rootDir];
@@ -393,6 +624,7 @@ function createInventory({ registry = null, sourcePath, workDir = null } = {}) {
textFiles: files.filter((file) => file.type === "text" || file.type === "json").length,
},
files,
+ versionMetadata: extractVersionMetadata(files),
};
} finally {
if (cleanupScratch && scratchDir != null) {
@@ -401,9 +633,117 @@ function createInventory({ registry = null, sourcePath, workDir = null } = {}) {
}
}
+function firstVersion(value) {
+ const match = String(value ?? "").match(/\b\d+\.\d+(?:\.\d+){0,2}(?:[-+][0-9A-Za-z.-]+)?\b/);
+ return match?.[0] ?? null;
+}
+
+function parseJsonObject(text) {
+ try {
+ const parsed = JSON.parse(text);
+ return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
+ } catch {
+ return null;
+ }
+}
+
+function canonicalPlistVersions(file) {
+ const values = {
+ cfBundleShortVersionString: null,
+ cfBundleVersion: null,
+ };
+ const text = file.text ?? "";
+ for (const [field, key] of Object.entries({
+ cfBundleShortVersionString: "CFBundleShortVersionString",
+ cfBundleVersion: "CFBundleVersion",
+ })) {
+ values[field] =
+ text.match(new RegExp(`\\s*${key}\\s*\\s*\\s*([^<]+)`, "i"))?.[1]?.trim() ??
+ text.match(new RegExp(`${key}[^0-9]{0,20}([0-9][0-9A-Za-z.+-]*)`, "i"))?.[1] ??
+ null;
+ }
+ if ((values.cfBundleShortVersionString != null && values.cfBundleVersion != null) || file.absolutePath == null) {
+ return values;
+ }
+ const script = [
+ "import json, plistlib, sys",
+ "with open(sys.argv[1], 'rb') as handle:",
+ " data = plistlib.load(handle)",
+ "print(json.dumps({key: data.get(key) for key in ('CFBundleShortVersionString', 'CFBundleVersion')}))",
+ ].join("\n");
+ const parsed = spawnSync("python3", ["-c", script, file.absolutePath], {
+ encoding: "utf8",
+ maxBuffer: 1024 * 1024,
+ });
+ if (parsed.status === 0) {
+ const plist = parseJsonObject(parsed.stdout);
+ values.cfBundleShortVersionString ??= firstVersion(plist?.CFBundleShortVersionString);
+ values.cfBundleVersion ??= String(plist?.CFBundleVersion ?? "").trim() || null;
+ }
+ return values;
+}
+
+function extractVersionMetadata(files = []) {
+ const result = {
+ cfBundleShortVersionString: null,
+ cfBundleVersion: null,
+ appPackageVersion: null,
+ electronVersion: null,
+ codexCliVersion: null,
+ bundledPluginVersions: {},
+ evidence: [],
+ };
+ const record = (key, value, evidencePath) => {
+ if (value != null && result[key] == null) {
+ result[key] = value;
+ result.evidence.push({ field: key, path: evidencePath });
+ }
+ };
+ for (const file of files) {
+ const relativePath = normalizePath(file.relativePath);
+ const pathLower = relativePath.toLowerCase();
+ const text = file.text ?? (file.nativeStrings ?? []).join(" ");
+ if (!text) continue;
+ if (pathLower === "contents/info.plist") {
+ const plistVersions = canonicalPlistVersions(file);
+ record("cfBundleShortVersionString", plistVersions.cfBundleShortVersionString, relativePath);
+ record("cfBundleVersion", plistVersions.cfBundleVersion, relativePath);
+ }
+ if (
+ pathLower === "contents/resources/app.asar/package.json" ||
+ (pathLower === "package.json" && file.source === "asar")
+ ) {
+ const manifest = parseJsonObject(text);
+ if (manifest?.name === "openai-codex-electron" || manifest?.productName === "Codex") {
+ record("appPackageVersion", firstVersion(manifest.version), relativePath);
+ record(
+ "electronVersion",
+ firstVersion(manifest.devDependencies?.electron ?? manifest.dependencies?.electron),
+ relativePath,
+ );
+ }
+ }
+ if (pathLower === "contents/resources/codex" || pathLower === "contents/resources/codex.exe") {
+ const cliVersion = text.match(
+ /\bcodex-cli(?:\s+version)?\s*[:=]?\s*v?(\d+\.\d+(?:\.\d+){0,2}(?:[-+][0-9A-Za-z.-]+)?)/i,
+ )?.[1];
+ record("codexCliVersion", firstVersion(cliVersion), relativePath);
+ }
+ const pluginMatch = relativePath.match(/plugins\/openai-bundled\/plugins\/([^/]+)\/\.codex-plugin\/plugin\.json$/);
+ if (pluginMatch) {
+ const version = firstVersion(parseJsonObject(text)?.version);
+ if (version != null) result.bundledPluginVersions[pluginMatch[1]] = version;
+ }
+ }
+ return result;
+}
+
function fileEvidenceForSurface(inventory, surface) {
const evidence = [];
for (const file of inventory.files) {
+ if (/(^|\/)(legacy|fallback|previous-bundle)(\/|$)/i.test(file.relativePath)) {
+ continue;
+ }
const pathHit = matchAny(surface.pathPatterns, file.relativePath);
const contentHits = [];
const nativeHits = [];
@@ -1005,7 +1345,122 @@ function hasLocalPatchSymbolDeclaration(source, symbol) {
);
}
-function findPostPatchIntegrityFindings(inventory) {
+function findComputerUsePlatformGateFindings(inventory) {
+ const findings = [];
+ const nativeAppsQueryGatePattern =
+ /([A-Za-z_$][\w$]*)=([A-Za-z_$][\w$]*)&&\(([A-Za-z_$][\w$]*)===`macOS`\|\|\3===`windows`(?!\|\|\3===`linux`)\)/g;
+ const nativeAppMentionSectionPattern =
+ /([A-Za-z_$][\w$]*)=([A-Za-z_$][\w$]*)===`macOS`\|\|\2===`windows`(?!\|\|\2===`linux`)/g;
+ const nativeAppSettingsCardPattern =
+ /if\(([A-Za-z_$][\w$]*)&&\(([A-Za-z_$][\w$]*)===`macOS`\|\|\2===`windows`(?!\|\|\2===`linux`)\)\)for\(let ([A-Za-z_$][\w$]*) of ([A-Za-z_$][\w$]*)\)\{/g;
+ const nativeAppIconQueryGatePattern =
+ /([A-Za-z_$][\w$]*)=\(([A-Za-z_$][\w$]*)===`macOS`\|\|\2===`windows`(?!\|\|\2===`linux`)\)&&([A-Za-z_$][\w$]*)!=null&&\3!==``/g;
+ const pluginRegistrationRolloutGatePattern =
+ /(?:isEnabled|isAvailable):\(\{features:([A-Za-z_$][\w$]*),platform:([A-Za-z_$][\w$]*)\}\)=>\(\2===`darwin`\|\|\2===`linux`\)&&\1\.computerUse/g;
+ for (const file of inventory.files) {
+ if (file.text == null) {
+ continue;
+ }
+ if (file.text.includes("`native-desktop-apps`") && file.text.includes("nativeApps:")) {
+ for (const match of file.text.matchAll(nativeAppsQueryGatePattern)) {
+ const nextSource = file.text.slice(match.index + match[0].length, match.index + match[0].length + 1400);
+ if (!nextSource.includes("`native-desktop-apps`") || !nextSource.includes("nativeApps:")) {
+ continue;
+ }
+ findings.push({
+ path: file.relativePath,
+ reason: "Computer Use native app query is still gated to macOS/Windows after Linux patching",
+ snippet: textSnippet(file.text, match[0]),
+ symbol: "computer-use-native-apps-linux-gate",
+ });
+ }
+ }
+ if (
+ file.text.includes("computerUsePlugin:") &&
+ file.text.includes("chromeAppPlugins:") &&
+ file.text.includes("microsoftExcelAppPlugins:")
+ ) {
+ for (const match of file.text.matchAll(nativeAppMentionSectionPattern)) {
+ const nextSource = file.text.slice(match.index + match[0].length, match.index + match[0].length + 1600);
+ if (
+ !nextSource.includes("computerUsePlugin:") ||
+ !nextSource.includes("chromeAppPlugins:") ||
+ !nextSource.includes("microsoftExcelAppPlugins:")
+ ) {
+ continue;
+ }
+ findings.push({
+ path: file.relativePath,
+ reason: "Computer Use composer native-app mention section is still gated to macOS/Windows after Linux patching",
+ snippet: textSnippet(file.text, match[0]),
+ symbol: "computer-use-composer-native-app-mentions-linux-gate",
+ });
+ }
+ }
+ if (
+ file.text.includes("appControlId") &&
+ file.text.includes("toggleAriaLabel") &&
+ file.text.includes("plugin.installed")
+ ) {
+ for (const match of file.text.matchAll(nativeAppSettingsCardPattern)) {
+ const nextSource = file.text.slice(match.index + match[0].length, match.index + match[0].length + 1800);
+ if (
+ !nextSource.includes("appControlId") ||
+ !nextSource.includes("toggleAriaLabel") ||
+ !nextSource.includes("plugin.installed")
+ ) {
+ continue;
+ }
+ findings.push({
+ path: file.relativePath,
+ reason: "Computer Use settings native app cards are still gated to macOS/Windows after Linux patching",
+ snippet: textSnippet(file.text, match[0]),
+ symbol: "computer-use-settings-native-app-card-linux-gate",
+ });
+ }
+ }
+ if (file.text.includes("`computer-use-native-desktop-app-icon`")) {
+ for (const match of file.text.matchAll(nativeAppIconQueryGatePattern)) {
+ const nextSource = file.text.slice(match.index + match[0].length, match.index + match[0].length + 1200);
+ if (!nextSource.includes("`computer-use-native-desktop-app-icon`")) {
+ continue;
+ }
+ findings.push({
+ path: file.relativePath,
+ reason: "Computer Use native app icon query is still gated to macOS/Windows after Linux patching",
+ snippet: textSnippet(file.text, match[0]),
+ symbol: "computer-use-native-app-icon-linux-gate",
+ });
+ }
+ }
+ if (file.text.includes("installWhenMissing:!0")) {
+ for (const match of file.text.matchAll(pluginRegistrationRolloutGatePattern)) {
+ findings.push({
+ path: file.relativePath,
+ reason: "Linux Computer Use plugin registration still depends on the upstream rollout flag",
+ snippet: textSnippet(file.text, match[0]),
+ symbol: "computer-use-plugin-registration-rollout-gate",
+ });
+ }
+ }
+ if (
+ file.text.includes("computer-use-plugin-icon-linux.png") &&
+ file.text.includes("availablePlugins.some") &&
+ file.text.includes("plugin?.name") &&
+ !file.text.includes("e.plugin?.installed===!0&&e.plugin?.enabled===!0")
+ ) {
+ findings.push({
+ path: file.relativePath,
+ reason: "Synthetic Linux Computer Use settings plugin can be masked by an unavailable upstream entry",
+ snippet: textSnippet(file.text, "computer-use-plugin-icon-linux.png"),
+ symbol: "computer-use-settings-synthetic-plugin-mask",
+ });
+ }
+ }
+ return findings;
+}
+
+function findPostPatchIntegrityFindings(inventory, options = {}) {
const findings = [];
for (const file of inventory.files) {
if (file.text == null) {
@@ -1024,9 +1479,358 @@ function findPostPatchIntegrityFindings(inventory) {
});
}
}
+ if (options.includeComputerUsePlatformGates === true) {
+ findings.push(...findComputerUsePlatformGateFindings(inventory));
+ }
return findings.sort((a, b) => `${a.symbol}:${a.path}`.localeCompare(`${b.symbol}:${b.path}`));
}
+function platformGateContext(file, index, matchLength, radius = 900) {
+ const start = Math.max(0, index - radius);
+ const end = Math.min(file.text.length, index + matchLength + radius);
+ return file.text.slice(start, end);
+}
+
+function compactSnippet(value) {
+ return String(value).replace(/\s+/g, " ").trim().slice(0, 260);
+}
+
+function isComputerUsePlatformGateContext(lower, pathLower) {
+ return (
+ (pathLower.includes("computer-use") && !pathLower.includes("marketing")) ||
+ lower.includes("computer-use-native-desktop-app-icon") ||
+ lower.includes("computer-use-plugin-icon-linux") ||
+ (lower.includes("native-desktop-apps") &&
+ (lower.includes("availableplugins") || lower.includes("plugin.installed") || lower.includes("appcontrolid") ||
+ lower.includes("queryconfig") || lower.includes("computeruseplugin"))) ||
+ (lower.includes("accessibility_snapshot") && lower.includes("event_stream"))
+ );
+}
+
+function classifyPlatformGate({ file, gate, context }) {
+ const lower = context.toLowerCase();
+ const pathLower = file.relativePath.toLowerCase();
+
+ if (gate.includes("`linux`")) {
+ return {
+ category: "already-linux-enabled",
+ confidence: "high",
+ feature: "Linux-aware platform gate",
+ issueCandidate: false,
+ linuxSurfaceId: null,
+ patchTarget: "none",
+ recommendation: "No action; Linux is already part of this platform gate.",
+ };
+ }
+
+ const chronicleContract =
+ (lower.includes("chroniclesidecarpresent") && lower.includes("chroniclesidecarprocessstate") &&
+ lower.includes("rememberconsentaccepted")) ||
+ (lower.includes("skysight_snapshot") && lower.includes("event_stream_start")) ||
+ (lower.includes("linux-record-replay-skysight-status") && lower.includes("skysight"));
+ if (chronicleContract) {
+ const skysight = lower.includes("skysight") && !lower.includes("chroniclesidecar");
+ return {
+ category: "linux-parity-drift",
+ confidence: "high",
+ confidenceRationale: "Exact Chronicle/Skysight status, consent, or bridge contract matched.",
+ feature: skysight ? "Skysight controls and bridge" : "Chronicle settings toggle paths",
+ issueCandidate: false,
+ linuxSurfaceId: skysight ? "skysight_bridge" : "chronicle_settings_toggles",
+ patchTarget: skysight
+ ? "linux-features/record-and-replay/patch.js and record-replay-linux/src/mcp.rs"
+ : "linux-features/record-and-replay/patch.js",
+ linuxStatus: "existing-linux-substrate",
+ recommendation: "Expose and test the explicit stopped/not-started state, consent, enable/disable, and bridge status semantics on Linux.",
+ };
+ }
+
+ if (
+ lower.includes("appcontrolid") &&
+ lower.includes("togglearialabel") &&
+ lower.includes("plugin.installed")
+ ) {
+ return {
+ category: "linux-parity-drift",
+ confidence: "high",
+ confidenceRationale: "Exact Computer Use settings card contract matched.",
+ feature: "Computer Use settings native app cards",
+ issueCandidate: false,
+ linuxSurfaceId: "computer_use_plugin",
+ patchTarget: "scripts/patches/impl/computer-use.js",
+ recommendation: "Patch the settings native app card loop so Linux renders the existing Computer Use Any App/native app controls.",
+ linuxStatus: "existing-linux-substrate",
+ };
+ }
+
+ if (lower.includes("computer-use-native-desktop-app-icon")) {
+ return {
+ category: "linux-parity-drift",
+ confidence: "high",
+ confidenceRationale: "Exact Computer Use native icon contract matched.",
+ feature: "Computer Use native app icons",
+ issueCandidate: false,
+ linuxSurfaceId: "computer_use_plugin",
+ patchTarget: "scripts/patches/impl/computer-use.js",
+ recommendation: "Patch the native desktop app icon query gate so Linux can request native app icons for Computer Use cards and mentions.",
+ linuxStatus: "existing-linux-substrate",
+ };
+ }
+
+ if (isComputerUsePlatformGateContext(lower, pathLower)) {
+ return {
+ category: "linux-parity-drift",
+ confidence: "high",
+ confidenceRationale: "Exact Computer Use renderer contract matched.",
+ feature: "Computer Use native app UI",
+ issueCandidate: false,
+ linuxSurfaceId: "computer_use_plugin",
+ patchTarget: "scripts/patches/impl/computer-use.js",
+ recommendation: "Patch the renderer availability gate so Linux exposes the existing Computer Use backend in settings and @ mentions.",
+ linuxStatus: "existing-linux-substrate",
+ };
+ }
+
+ if (pathLower.includes("computer-use") || lower.includes("computer use") || lower.includes("computer-use")) {
+ return {
+ category: "needs-review",
+ confidence: "medium",
+ confidenceRationale: "Computer Use mention is present without an exact Linux parity contract.",
+ feature: "Computer Use generic platform mention",
+ issueCandidate: false,
+ linuxSurfaceId: "computer_use_plugin",
+ patchTarget: "scripts/patches/impl/computer-use.js",
+ linuxStatus: "existing-linux-substrate",
+ recommendation: "Review the current-bundle context before treating this generic mention as a Linux parity blocker.",
+ };
+ }
+
+ if (lower.includes("microsoftexcel") || lower.includes("microsoftpowerpoint")) {
+ return {
+ category: "platform-specific-unsupported",
+ confidence: "high",
+ confidenceRationale: "Exact Office native-app marker matched.",
+ feature: "Office live-control app mentions",
+ issueCandidate: true,
+ linuxSurfaceId: null,
+ patchTarget: "new issue or optional linux-features//",
+ recommendation: "Label as macOS/Windows-only until a Linux native app bridge exists; create a feature issue if parity is desired.",
+ linuxStatus: "unsupported-no-substrate",
+ };
+ }
+
+ if (
+ lower.includes("settogglehotkey") ||
+ lower.includes("synccommandkeybindings") ||
+ lower.includes("commandkeybindings") ||
+ lower.includes("globalhotkey")
+ ) {
+ return {
+ category: "platform-specific-unsupported",
+ confidence: "high",
+ confidenceRationale: "Exact global-hotkey marker matched.",
+ feature: "Global hotkey/keybinding integration",
+ issueCandidate: true,
+ linuxSurfaceId: null,
+ patchTarget: "new issue or optional linux-features//",
+ recommendation: "Label as macOS/Windows-only until a Linux global shortcut backend exists; create a feature issue if parity is desired.",
+ linuxStatus: "unsupported-no-substrate",
+ };
+ }
+
+ if (lower.includes("agi intelligence") || lower.includes("supreme")) {
+ return {
+ category: "new-upstream-capability",
+ confidence: "high",
+ feature: "Unmapped high-signal desktop capability",
+ issueCandidate: true,
+ linuxSurfaceId: null,
+ patchTarget: "scripts/dev/upstream-dmg-protected-surfaces.json plus a new Linux feature or backend owner",
+ recommendation: "Create an issue for the new upstream desktop capability and decide whether Linux needs a native port.",
+ };
+ }
+
+ if (lower.includes("chronicle") || lower.includes("skysight") || lower.includes("recording") || lower.includes("event_stream")) {
+ return {
+ category: "needs-review",
+ confidence: "medium",
+ feature: "Chronicle/Skysight/Record & Replay desktop capability",
+ issueCandidate: true,
+ linuxSurfaceId: lower.includes("chronicle") ? "chronicle_settings_toggles" : "record_and_replay_plugin",
+ patchTarget: "linux-features/record-and-replay and record-replay-linux",
+ recommendation: "Review whether this gate hides an existing Linux-backed feature or a new upstream desktop capability.",
+ confidenceRationale: "Generic Chronicle/Skysight text without an exact contract.",
+ };
+ }
+
+ if (
+ lower.includes("titlebar") ||
+ lower.includes("trafficlight") ||
+ lower.includes("dock") ||
+ lower.includes("tray") ||
+ lower.includes("windowbutton") ||
+ pathLower.includes("titlebar")
+ ) {
+ return {
+ category: "expected-platform-native",
+ confidence: "medium",
+ feature: "OS-native window chrome or shell integration",
+ issueCandidate: false,
+ linuxSurfaceId: null,
+ patchTarget: "none by default",
+ recommendation: "Keep labeled as platform-native unless the Linux window shell regresses.",
+ };
+ }
+
+ if (
+ lower.includes("native") ||
+ lower.includes("desktop") ||
+ lower.includes("appplugin") ||
+ lower.includes("sidecar") ||
+ lower.includes("mcp") ||
+ lower.includes("plugin")
+ ) {
+ return {
+ category: "new-upstream-capability",
+ confidence: "medium",
+ feature: "Unmapped desktop/native/plugin capability",
+ issueCandidate: true,
+ linuxSurfaceId: null,
+ patchTarget: "scripts/dev/upstream-dmg-protected-surfaces.json plus a new Linux feature or backend owner",
+ recommendation: "Create an issue, decide whether Linux needs a port or explicit unsupported label, and add a protected surface if accepted.",
+ };
+ }
+
+ return {
+ category: "needs-review",
+ confidence: "low",
+ feature: "Unclassified platform gate",
+ issueCandidate: false,
+ linuxSurfaceId: null,
+ patchTarget: "manual review",
+ recommendation: "Classify this gate as Linux parity, new capability, unsupported, or expected platform-native before accepting release drift.",
+ };
+}
+
+function createPlatformGateEntry({ file, gate, index, patternName }) {
+ const context = platformGateContext(file, index, gate.length);
+ const classification = classifyPlatformGate({ file, gate, context });
+ return {
+ id: sha256(Buffer.from(`${file.relativePath}\0${gate}\0${compactSnippet(context)}`)).slice(0, 16),
+ path: file.relativePath,
+ gate,
+ platforms: gate.includes("darwin") || gate.includes("win32") ? ["darwin", "win32"] : ["macOS", "windows"],
+ pattern: patternName,
+ snippet: compactSnippet(context),
+ evidenceType: "platform-gate-context",
+ platformLabel: classification.platformLabel ?? (gate.includes("darwin") || gate.includes("win32") ? "macOS/windows" : "unknown"),
+ entitlement: classification.entitlement ?? "unknown",
+ rollout: classification.rollout ?? "unknown",
+ linuxStatus: classification.linuxStatus ?? "unknown",
+ ownerPath: classification.patchTarget ?? "manual review",
+ recommendedAction: classification.recommendation,
+ ...classification,
+ };
+}
+
+function createPlatformGateMap({ inventory, patchFindings = [] } = {}) {
+ const gates = [];
+ const seen = new Set();
+ const gatePatterns = [
+ {
+ name: "ui-platform-macos-windows",
+ regex: /[A-Za-z_$][\w$]*===`macOS`\|\|[A-Za-z_$][\w$]*===`windows`(?:\|\|[A-Za-z_$][\w$]*===`linux`)?/g,
+ },
+ {
+ name: "process-platform-darwin-win32",
+ regex: /process\.platform===`darwin`\|\|process\.platform===`win32`|process\.platform!==`linux`&&process\.platform!==`darwin`/g,
+ },
+ ];
+
+ for (const file of inventory.files ?? []) {
+ if (file.text == null) {
+ continue;
+ }
+ for (const pattern of gatePatterns) {
+ for (const match of file.text.matchAll(pattern.regex)) {
+ const entry = createPlatformGateEntry({
+ file,
+ gate: match[0],
+ index: match.index,
+ patternName: pattern.name,
+ });
+ const key = `${entry.path}\0${entry.gate}\0${entry.category}\0${entry.feature}`;
+ if (!seen.has(key)) {
+ seen.add(key);
+ gates.push(entry);
+ }
+ }
+ }
+
+ if (
+ file.text.includes("computer-use-plugin-icon-linux.png") &&
+ file.text.includes("availablePlugins.some") &&
+ file.text.includes("plugin?.name") &&
+ !file.text.includes("e.plugin?.installed===!0&&e.plugin?.enabled===!0")
+ ) {
+ const entry = {
+ id: sha256(Buffer.from(`${file.relativePath}\0computer-use-settings-mask`)).slice(0, 16),
+ path: file.relativePath,
+ gate: "synthetic Linux Computer Use row masked by unavailable upstream plugin",
+ platforms: ["linux"],
+ pattern: "synthetic-plugin-mask",
+ snippet: textSnippet(file.text, "computer-use-plugin-icon-linux.png"),
+ category: "linux-parity-drift",
+ confidence: "high",
+ feature: "Computer Use settings row",
+ issueCandidate: false,
+ linuxSurfaceId: "computer_use_plugin",
+ patchTarget: "scripts/patches/impl/computer-use.js",
+ recommendation: "Prepend the enabled synthetic Linux Computer Use plugin unless an installed and enabled upstream entry already exists.",
+ };
+ const key = `${entry.path}\0${entry.gate}`;
+ if (!seen.has(key)) {
+ seen.add(key);
+ gates.push(entry);
+ }
+ }
+ }
+
+ const findingsByName = new Map(patchFindings.map((finding) => [finding.name, finding]));
+ for (const gate of gates) {
+ if (gate.category !== "linux-parity-drift") {
+ continue;
+ }
+ const requiredPatches = LINUX_PARITY_SURFACE_PATCHES[gate.linuxSurfaceId] ?? [];
+ if (requiredPatches.length === 0) {
+ continue;
+ }
+ const coverage = requiredPatches.map((name) => findingsByName.get(name) ?? { name, status: "not-recorded" });
+ gate.patchPreflight = coverage.map(({ name, status }) => ({ name, status }));
+ if (coverage.every((finding) => ["applied", "already-applied"].includes(finding.status))) {
+ gate.rawCategory = gate.category;
+ gate.category = "patched-linux-parity";
+ gate.linuxStatus = "patched-for-candidate";
+ gate.recommendedAction = "No action; every exact Linux parity patch applied to this candidate.";
+ gate.recommendation = gate.recommendedAction;
+ }
+ }
+
+ const categoryCounts = {};
+ for (const gate of gates) {
+ categoryCounts[gate.category] = (categoryCounts[gate.category] ?? 0) + 1;
+ }
+
+ return {
+ generatedAt: new Date().toISOString(),
+ source: inventory.source,
+ categoryCounts,
+ blockingCount: gates.filter((gate) => PLATFORM_GATE_BLOCKING_CATEGORIES.has(gate.category)).length,
+ reviewCount: gates.filter((gate) => PLATFORM_GATE_REVIEW_CATEGORIES.has(gate.category)).length,
+ gates: gates.sort((a, b) => `${a.category}:${a.feature}:${a.path}`.localeCompare(`${b.category}:${b.feature}:${b.path}`)),
+ };
+}
+
function classifySurfaceDrift({ baselineSurface, candidateSurface }) {
const baselinePresent = baselineSurface?.status === "PRESENT";
const candidatePresent = candidateSurface?.status === "PRESENT";
@@ -1476,6 +2280,176 @@ function summarizeMapDrift(mapDrift) {
return summary;
}
+function capabilityId(parts) {
+ return sha256(Buffer.from(parts.filter(Boolean).join("\0"))).slice(0, 16);
+}
+
+function capabilityFromPlatformGate(gate) {
+ if (!["new-upstream-capability", "platform-specific-unsupported"].includes(gate.category)) {
+ return null;
+ }
+ return {
+ id: `platform-gate:${gate.id}`,
+ type: "platform-gate",
+ name: gate.feature,
+ path: gate.path,
+ category: gate.category,
+ confidence: gate.confidence,
+ issueCandidate: gate.issueCandidate,
+ recommendation: gate.recommendation,
+ patchTarget: gate.patchTarget,
+ evidence: gate.gate,
+ platformLabel: gate.platformLabel,
+ entitlement: gate.entitlement,
+ rollout: gate.rollout,
+ evidenceType: gate.evidenceType,
+ confidenceRationale: gate.confidenceRationale ?? "Classifier default; manual review required.",
+ linuxStatus: gate.linuxStatus,
+ ownerPath: gate.ownerPath,
+ recommendedAction: gate.recommendation,
+ };
+}
+
+function nativeBinaryIsFeatureCandidate(binaryPath) {
+ const normalized = normalizePath(binaryPath);
+ return !(
+ normalized.startsWith("Contents/Frameworks/") ||
+ normalized.startsWith("Contents/MacOS/") ||
+ normalized.startsWith("Contents/Resources/cua_node/") ||
+ normalized.includes("/plugins/") ||
+ normalized.includes("/node_modules/")
+ );
+}
+
+function createNewCapabilityMap({ mapDrift, platformGateMap, candidatePluginMap } = {}) {
+ const capabilities = [];
+ const addCapability = (capability) => {
+ if (capability == null) {
+ return;
+ }
+ capabilities.push({
+ confidence: "medium",
+ issueCandidate: true,
+ patchTarget: "scripts/dev/upstream-dmg-protected-surfaces.json",
+ recommendation: "Create an issue, classify Linux support, and add a protected surface if this capability needs parity.",
+ platformLabel: "unknown",
+ entitlement: "unknown",
+ rollout: "unknown",
+ evidenceType: "bundle-drift",
+ confidenceRationale: "Capability discovered from current-bundle structural drift.",
+ linuxStatus: "unknown",
+ ownerPath: "scripts/dev/upstream-dmg-protected-surfaces.json",
+ recommendedAction: "Create an issue and classify Linux support.",
+ bundlePresent: true,
+ uiExposed: null,
+ serverGateObserved: false,
+ entitlementProven: false,
+ ...capability,
+ });
+ };
+
+ if (mapDrift?.mode === "baselineComparison") {
+ for (const pluginId of mapDrift?.pluginDrift?.added ?? []) {
+ addCapability({
+ id: `plugin:${pluginId}`,
+ type: "plugin",
+ name: pluginId,
+ category: "new-upstream-capability",
+ evidence: pluginId,
+ });
+ }
+
+ for (const key of mapDrift?.mcpDrift?.added ?? []) {
+ const [pluginId, serverName, toolName] = key.split(":");
+ addCapability({
+ id: `mcp:${key}`,
+ type: "mcp-tool",
+ name: toolName ?? serverName ?? key,
+ category: "new-upstream-capability",
+ evidence: key,
+ recommendation: `Review new MCP tool ${key}; add Linux backend coverage or an unsupported label.`,
+ patchTarget: pluginId ? `plugins/openai-bundled/plugins/${pluginId} or matching linux-features owner` : "matching Linux MCP owner",
+ });
+ }
+
+ for (const binaryPath of mapDrift?.nativeBinaryDrift?.added ?? []) {
+ if (!nativeBinaryIsFeatureCandidate(binaryPath)) continue;
+ addCapability({
+ id: `native:${binaryPath}`,
+ type: "native-binary",
+ name: path.posix.basename(binaryPath),
+ path: binaryPath,
+ category: "new-upstream-capability",
+ evidence: binaryPath,
+ recommendation: "Review the new native binary for Linux replacement, staging, or explicit unsupported status.",
+ patchTarget: "scripts/lib/bundled-plugins.sh or a dedicated linux-features// owner",
+ });
+ }
+
+ for (const key of mapDrift?.bridgeHandlerDrift?.added ?? []) {
+ addCapability({
+ id: `bridge:${capabilityId(["bridge", key])}`,
+ type: "bridge-handler",
+ name: key.split(":")[1] ?? key,
+ category: "new-upstream-capability",
+ evidence: key,
+ recommendation: "Review the new Electron bridge handler and decide whether Linux needs a native mirror or patch.",
+ patchTarget: "scripts/patches/core/all-linux or matching linux-features owner",
+ });
+ }
+ }
+
+ const sites = (candidatePluginMap?.plugins ?? []).find((plugin) => plugin.id === "sites");
+ if (sites != null && (mapDrift?.pluginDrift?.added ?? []).includes("sites")) {
+ const manifest = sites.manifests?.[0] ?? {};
+ addCapability({
+ id: "plugin:sites",
+ type: "plugin",
+ name: "Sites",
+ version: manifest.version ?? "unknown",
+ category: "cross-platform-entitlement-gated",
+ platformLabel: "cross-platform",
+ entitlement: "connector_20205bf7d4e99a89d7154bb849718324",
+ entitlementLabel: "connector_20205bf7d4e99a89d7154bb849718324 (server entitlement not proven)",
+ rollout: "unknown",
+ evidenceType: "plugin-manifest-and-app-registration",
+ confidence: "high",
+ confidenceRationale: "Current bundle contains the Sites manifest and connector registration; server entitlement is not proven.",
+ bundlePresent: true,
+ uiExposed: Boolean(manifest.displayName),
+ serverGateObserved: false,
+ entitlementProven: false,
+ linuxStatus: "staging-ownership-needed",
+ patchTarget: "scripts/lib/bundled-plugins.sh",
+ ownerPath: "scripts/lib/bundled-plugins.sh",
+ recommendation: "Create a staging issue for Sites and keep entitlement unknown until server evidence is available.",
+ recommendedAction: "Create a staging issue; do not infer entitlement from bundle presence.",
+ evidence: sites.files,
+ });
+ }
+
+ for (const gate of platformGateMap?.gates ?? []) {
+ addCapability(capabilityFromPlatformGate(gate));
+ }
+
+ const deduped = new Map();
+ for (const capability of capabilities) {
+ deduped.set(capability.id, capability);
+ }
+ const items = [...deduped.values()].sort((a, b) => `${a.category}:${a.type}:${a.name}`.localeCompare(`${b.category}:${b.type}:${b.name}`));
+ const categoryCounts = {};
+ for (const item of items) {
+ categoryCounts[item.category] = (categoryCounts[item.category] ?? 0) + 1;
+ }
+ return {
+ generatedAt: new Date().toISOString(),
+ mode: mapDrift?.mode ?? "unknown",
+ categoryCounts,
+ issueCandidateCount: items.filter((item) => item.issueCandidate).length,
+ capabilities: items,
+ };
+}
+
function markdownList(items) {
if (items.length === 0) {
return "- None\n";
@@ -1483,6 +2457,51 @@ function markdownList(items) {
return items.map((item) => `- ${item}`).join("\n") + "\n";
}
+function markdownCell(value) {
+ const normalized = value != null && typeof value === "object" ? JSON.stringify(value) : String(value ?? "");
+ return normalized
+ .replace(/\s+/g, " ")
+ .replace(/\|/g, "\\|")
+ .trim();
+}
+
+function markdownTable(headers, rows) {
+ if (rows.length === 0) {
+ return "None.\n";
+ }
+ const headerLine = `| ${headers.map(markdownCell).join(" | ")} |`;
+ const dividerLine = `| ${headers.map(() => "---").join(" | ")} |`;
+ const rowLines = rows.map((row) => `| ${row.map(markdownCell).join(" | ")} |`);
+ return `${[headerLine, dividerLine, ...rowLines].join("\n")}\n`;
+}
+
+function platformGateRows(gates, categories, limit = PLATFORM_GATE_MARKDOWN_LIMIT) {
+ return (gates ?? [])
+ .filter((gate) => categories.includes(gate.category))
+ .slice(0, limit)
+ .map((gate) => [
+ gate.feature,
+ gate.category,
+ gate.path,
+ gate.patchTarget,
+ gate.recommendation,
+ ]);
+}
+
+function capabilityRows(capabilities, limit = NEW_CAPABILITY_MARKDOWN_LIMIT) {
+ return (capabilities ?? [])
+ .slice(0, limit)
+ .map((capability) => [
+ capability.name,
+ capability.version ?? "",
+ capability.type,
+ capability.category,
+ `${capability.path ?? capability.evidence ?? ""}${capability.entitlementLabel ? `; ${capability.entitlementLabel}` : capability.entitlement ? `; entitlement=${capability.entitlement}` : ""}`,
+ capability.patchTarget,
+ capability.recommendation,
+ ]);
+}
+
function renderDriftMarkdown(report) {
const lines = ["# Upstream DMG Drift Report", ""];
lines.push(`Generated: ${report.generatedAt}`);
@@ -1491,6 +2510,64 @@ function renderDriftMarkdown(report) {
lines.push(`Baseline: ${report.baselineSource.path}`);
}
lines.push("");
+ lines.push("## App and bundle versions");
+ lines.push("");
+ lines.push(markdownTable(
+ ["Field", "Baseline", "Candidate", "Evidence"],
+ Object.keys(report.versionDelta ?? {}).map((field) => [
+ field,
+ report.versionDelta[field]?.baseline ?? "unknown",
+ report.versionDelta[field]?.candidate ?? "unknown",
+ report.versionDelta[field]?.evidence ?? "unknown",
+ ]),
+ ).trimEnd());
+ lines.push("");
+ lines.push("## Feature staging and runtime health");
+ lines.push("");
+ lines.push(markdownTable(
+ ["Feature", "Owner", "Staging", "Runtime status", "Action"],
+ (report.featureStaging?.features ?? []).map((feature) => [
+ feature.id,
+ feature.targetOwnership,
+ feature.stagingPresent ? "present" : "missing",
+ feature.id === "mcp-helper-reaper" ? (report.runtimeHealth?.mcpHelperReaper?.status ?? "unknown") : "not-run",
+ feature.id === "mcp-helper-reaper" ? "Supply a runtime snapshot before claiming health." : "Compare current-bundle staging against this owner.",
+ ]),
+ ).trimEnd());
+ lines.push("");
+ lines.push("## Linux Parity Drift");
+ lines.push("");
+ lines.push(markdownTable(
+ ["Feature", "Category", "Path", "Patch target", "Recommendation"],
+ platformGateRows(report.platformGates, ["linux-parity-drift"]),
+ ).trimEnd());
+ lines.push("");
+ lines.push("## Required patch preflight");
+ lines.push("");
+ lines.push(markdownTable(
+ ["Patch", "Status", "Severity", "Owner", "Recommendation"],
+ (report.requiredPatchPreflight?.findings ?? []).map((finding) => [finding.name, finding.status, finding.severity, finding.ownerPath, finding.recommendation]),
+ ).trimEnd());
+ lines.push("");
+ lines.push("## New Capability Candidates");
+ lines.push("");
+ lines.push(markdownTable(
+ ["Name", "Version", "Type", "Category", "Evidence", "Owner", "Recommendation"],
+ capabilityRows(report.newCapabilities),
+ ).trimEnd());
+ lines.push("");
+ lines.push("## Platform-Specific Labels");
+ lines.push("");
+ lines.push(markdownTable(
+ ["Feature", "Category", "Path", "Patch target", "Recommendation"],
+ platformGateRows(report.platformGates, [
+ "platform-specific-unsupported",
+ "expected-platform-native",
+ "needs-review",
+ "already-linux-enabled",
+ ]),
+ ).trimEnd());
+ lines.push("");
lines.push("## Classification Counts");
lines.push("");
lines.push(
@@ -1583,6 +2660,10 @@ function changedPayloadList(entries, maxItems = ACTION_PLAN_PATH_SAMPLE_LIMIT) {
function renderActionPlanMarkdown(driftReport, candidateProtected, mapDrift = null) {
const actionable = driftReport.surfaceDrift.filter((item) => ACTIONABLE_CLASSIFICATIONS.has(item.classification));
+ const platformBlockers = (driftReport.platformGates ?? []).filter((gate) =>
+ PLATFORM_GATE_BLOCKING_CATEGORIES.has(gate.category),
+ );
+ const capabilityCandidates = driftReport.newCapabilities ?? [];
const structuralSummary = driftReport.structuralDriftSummary ?? summarizeMapDrift(mapDrift);
const lines = ["# Linux Substrate Action Plan", ""];
lines.push(`Candidate: ${candidateProtected.source?.path ?? "unknown"}`);
@@ -1591,10 +2672,30 @@ function renderActionPlanMarkdown(driftReport, candidateProtected, mapDrift = nu
lines.push(`Structural maps: ${summaryLine}`);
}
lines.push("");
- if (actionable.length === 0) {
+ if (actionable.length === 0 && platformBlockers.length === 0 && capabilityCandidates.length === 0) {
lines.push("No protected-surface action required by this report.");
return `${lines.join("\n")}\n`;
}
+ if (platformBlockers.length > 0) {
+ lines.push("## Linux parity blockers");
+ for (const gate of platformBlockers) {
+ lines.push(`- ${gate.feature}: ${gate.recommendation}`);
+ lines.push(` Patch target: ${gate.patchTarget}`);
+ lines.push(` Evidence: ${gate.path} :: ${gate.gate}`);
+ }
+ lines.push("");
+ }
+ if (capabilityCandidates.length > 0) {
+ lines.push("## New capability / issue candidates");
+ for (const capability of capabilityCandidates.slice(0, NEW_CAPABILITY_MARKDOWN_LIMIT)) {
+ lines.push(`- ${capability.name} (${capability.category}): ${capability.recommendation}`);
+ lines.push(` Owner: ${capability.patchTarget}`);
+ }
+ if (capabilityCandidates.length > NEW_CAPABILITY_MARKDOWN_LIMIT) {
+ lines.push(`- ${capabilityCandidates.length - NEW_CAPABILITY_MARKDOWN_LIMIT} additional candidates omitted; see new-capabilities.json.`);
+ }
+ lines.push("");
+ }
for (const item of actionable) {
lines.push(`## ${item.surfaceId}`);
lines.push(`Classification: ${item.classification}`);
@@ -1657,6 +2758,31 @@ function publicInventory(inventory) {
};
}
+function createFeatureStagingInventory(repoRoot) {
+ const root = path.join(repoRoot, "linux-features");
+ const features = [];
+ if (!fs.existsSync(root)) return { generatedAt: new Date().toISOString(), features };
+ for (const id of fs.readdirSync(root).sort()) {
+ const featureRoot = path.join(root, id);
+ if (!fs.statSync(featureRoot).isDirectory()) continue;
+ const featureJson = path.join(featureRoot, "feature.json");
+ let manifest = null;
+ if (fs.existsSync(featureJson)) {
+ try { manifest = readJson(featureJson); } catch { manifest = null; }
+ }
+ const files = fs.readdirSync(featureRoot).filter((name) => !name.startsWith("."));
+ features.push({
+ id,
+ manifestPath: fs.existsSync(featureJson) ? `linux-features/${id}/feature.json` : null,
+ entrypoints: manifest?.entrypoints ?? {},
+ sourcePaths: files.map((name) => `linux-features/${id}/${name}`),
+ targetOwnership: manifest?.targetOwnership ?? `linux-features/${id}`,
+ stagingPresent: files.some((name) => /stage|patch|plugin|cleanup/i.test(name)),
+ });
+ }
+ return { generatedAt: new Date().toISOString(), features };
+}
+
function sameResolvedPath(left, right) {
try {
return fs.realpathSync(left) === fs.realpathSync(right);
@@ -1679,6 +2805,22 @@ function resolveBaselinePath({ autoBaseline = false, baselinePath = null, candid
return defaultBaselinePath;
}
+function mergeProvenance(detected = {}, supplied = null) {
+ if (supplied == null) return detected;
+ const merged = { ...detected, ...supplied };
+ for (const key of ["candidate", "baseline"]) {
+ const detectedEntry = detected?.[key] ?? null;
+ const suppliedEntry = supplied?.[key] ?? null;
+ merged[key] =
+ detectedEntry == null
+ ? suppliedEntry
+ : suppliedEntry == null
+ ? detectedEntry
+ : { ...detectedEntry, ...suppliedEntry };
+ }
+ return merged;
+}
+
function buildIntelReports({
autoBaseline = false,
baselinePath = null,
@@ -1688,6 +2830,8 @@ function buildIntelReports({
registry,
repoRoot = process.cwd(),
timestamp = null,
+ provenance = null,
+ runPatchPreflight = false,
} = {}) {
if (candidatePath == null) {
throw new Error("candidatePath is required");
@@ -1731,20 +2875,108 @@ function buildIntelReports({
});
const patchReport =
patchReportPath != null && fs.existsSync(patchReportPath) ? readJson(patchReportPath) : null;
+ const requiredPatchPreflight = runPatchPreflight
+ ? runRequiredPatchPreflight({ inventory: candidateInventory, repoRoot, workDir: scratchRoot })
+ : { status: "not-run", findings: [] };
+ const effectivePatchReport = patchReport == null && requiredPatchPreflight.status === "not-run"
+ ? null
+ : {
+ ...(patchReport ?? { patches: [] }),
+ patches: [...(patchReport?.patches ?? []), ...requiredPatchPreflight.findings.map((finding) => ({
+ name: finding.name,
+ status: finding.severity === "none" ? "already-applied" : "failed-required",
+ ciPolicy: "required-upstream",
+ reason: finding.reason ?? finding.recommendation,
+ }))],
+ };
const driftReport = compareProtectedSurfaces({
baseline: baselineProtected,
candidate: candidateProtected,
- patchReport,
+ patchReport: effectivePatchReport,
});
+ const baselineVersions = baselineInventory?.versionMetadata ?? {};
+ const candidateVersions = candidateInventory.versionMetadata ?? {};
+ const versionDelta = {};
+ const versionFields = [
+ "cfBundleShortVersionString",
+ "cfBundleVersion",
+ "appPackageVersion",
+ "electronVersion",
+ "codexCliVersion",
+ ];
+ for (const field of versionFields) {
+ versionDelta[field] = {
+ baseline: baselineVersions[field] ?? null,
+ candidate: candidateVersions[field] ?? null,
+ changed: (baselineVersions[field] ?? null) !== (candidateVersions[field] ?? null),
+ evidence: [...(baselineVersions.evidence ?? []), ...(candidateVersions.evidence ?? [])]
+ .filter((entry) => entry.field === field).map((entry) => entry.path).join(", ") || null,
+ };
+ }
+ versionDelta.bundledPluginVersions = {
+ baseline: baselineVersions.bundledPluginVersions ?? {},
+ candidate: candidateVersions.bundledPluginVersions ?? {},
+ changed: JSON.stringify(baselineVersions.bundledPluginVersions ?? {}) !== JSON.stringify(candidateVersions.bundledPluginVersions ?? {}),
+ evidence: "plugin manifests",
+ };
+ driftReport.versionDelta = versionDelta;
+ const detectedProvenance = {
+ candidate: candidatePath.endsWith(".dmg") && fs.statSync(candidatePath).isFile() ? {
+ url: process.env.CODEX_UPSTREAM_DMG_URL ?? null,
+ bytes: fs.statSync(candidatePath).size,
+ sha256: sha256File(candidatePath),
+ etag: process.env.CODEX_UPSTREAM_DMG_ETAG ?? null,
+ lastModified: process.env.CODEX_UPSTREAM_DMG_LAST_MODIFIED ?? null,
+ } : null,
+ baseline: resolvedBaselinePath?.endsWith(".dmg") && fs.statSync(resolvedBaselinePath).isFile() ? {
+ url: null,
+ bytes: fs.statSync(resolvedBaselinePath).size,
+ sha256: sha256File(resolvedBaselinePath),
+ etag: null,
+ lastModified: null,
+ } : null,
+ };
+ driftReport.provenance = mergeProvenance(detectedProvenance, provenance);
+ const featureStaging = createFeatureStagingInventory(repoRoot);
+ driftReport.featureStaging = featureStaging;
+ driftReport.runtimeHealth = {
+ mcpHelperReaper: {
+ status: "UNKNOWN",
+ evidenceType: "static-source-only",
+ message: "No runtime snapshot supplied; helper liveness, ownership, duplicate state, and cleanup were not run.",
+ ownerPath: "linux-features/mcp-helper-reaper/reaper/src/lib.rs",
+ },
+ };
+ driftReport.requiredPatchPreflight = requiredPatchPreflight;
const mapDrift = compareMaps({ baselineProtected, candidateProtected });
+ const platformGateMap = createPlatformGateMap({
+ inventory: candidateInventory,
+ patchFindings: requiredPatchPreflight.findings,
+ });
+ const newCapabilityMap = createNewCapabilityMap({ mapDrift, platformGateMap, candidatePluginMap: candidateProtected.pluginMap });
driftReport.structuralDriftSummary = summarizeMapDrift(mapDrift);
+ driftReport.platformGateSummary = {
+ categoryCounts: platformGateMap.categoryCounts,
+ blockingCount: platformGateMap.blockingCount,
+ reviewCount: platformGateMap.reviewCount,
+ };
+ driftReport.platformGates = platformGateMap.gates.slice(0, 50);
+ driftReport.newCapabilitySummary = {
+ categoryCounts: newCapabilityMap.categoryCounts,
+ issueCandidateCount: newCapabilityMap.issueCandidateCount,
+ };
+ driftReport.newCapabilities = newCapabilityMap.capabilities.slice(0, 50);
fs.mkdirSync(reportDir, { recursive: true });
writeJson(path.join(reportDir, "inventory.json"), publicInventory(candidateInventory));
+ writeJson(path.join(reportDir, "feature-staging.json"), featureStaging);
+ writeJson(path.join(reportDir, "required-patch-preflight.json"), requiredPatchPreflight);
writeJson(path.join(reportDir, "protected-surfaces.json"), candidateProtected);
writeJson(path.join(reportDir, "bridge-map.json"), candidateProtected.bridgeMap);
writeJson(path.join(reportDir, "plugin-map.json"), candidateProtected.pluginMap);
writeJson(path.join(reportDir, "native-binary-map.json"), candidateProtected.nativeBinaryMap);
+ writeJson(path.join(reportDir, "platform-gates.json"), platformGateMap);
+ writeJson(path.join(reportDir, "new-capabilities.json"), newCapabilityMap);
writeJson(path.join(reportDir, "map-drift.json"), mapDrift);
writeJson(path.join(reportDir, "drift-report.json"), driftReport);
fs.writeFileSync(path.join(reportDir, "drift-report.md"), renderDriftMarkdown(driftReport), "utf8");
@@ -1773,6 +3005,8 @@ function buildIntelReports({
protectedSurfaces: candidateProtected,
driftReport,
mapDrift,
+ platformGateMap,
+ newCapabilityMap,
};
} finally {
fs.rmSync(scratchRoot, { force: true, recursive: true });
@@ -1784,11 +3018,17 @@ module.exports = {
compareProtectedSurfaces,
createBridgeMap,
createInventory,
+ runRequiredPatchPreflight,
+ extractVersionMetadata,
+ createNewCapabilityMap,
createNativeBinaryMap,
+ createPlatformGateMap,
createPluginMap,
compareMaps,
extractProtectedSurfaces,
findPostPatchIntegrityFindings,
+ mergeProvenance,
+ prepareRequiredPatchPreflightApp,
renderActionPlanMarkdown,
renderDriftMarkdown,
resolveBaselinePath,