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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 90 additions & 3 deletions docs/upstream-dmg-intelligence.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -81,16 +87,24 @@ 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.
- `substrate-action-plan.md`: Linux follow-up paths for moved, changed, missing,
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,
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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"
```
29 changes: 25 additions & 4 deletions scripts/dev/upstream-dmg-intel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -40,6 +42,8 @@ function parseArgs(argv) {
autoBaseline: true,
baselinePath: null,
candidatePath: null,
candidateUrl: null,
patchPreflight: false,
outputDir: null,
failOnBlockers: false,
patchReportPath: null,
Expand All @@ -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") {
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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,
Expand All @@ -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"),
Expand All @@ -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;
}
Expand Down
Loading
Loading