Skip to content

feat(desktop): add a validated task to exported desktop applications - #8940

Open
earakely-scale wants to merge 82 commits into
morluto:mainfrom
earakely-scale:feat/desktop-app-tasks
Open

earakely-scale wants to merge 82 commits into
morluto:mainfrom
earakely-scale:feat/desktop-app-tasks

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #8934, which is stacked on #8877, #8875 and #8874. This branch contains all four. Review only c3d079754, 27617c37a, 079760fb7, 2c2a5bef7, bced17cf2, 31e4f8d7a, 11b1340ec, 57f04ffa1 and 59cdcf22a; the merge commits only bring the earlier PRs in. Once they merge, the diff shrinks to these.

Summary

An exported desktop app can now carry one task: a prompt for a computer-use agent, and declarative checks that grade the files the agent leaves in the app. An agent writes the prompt and the checks from the change the capture recorded. REA validates the task against the capture and keeps it only when its checks fail on the seed and all pass on the captured end state. The bundle then holds the task, a verifier that needs no model, and an eval that runs it.

The spec, plan and verification are in docs/specs/desktop-app-environments/tasks/, and the user guide is docs/desktop-application-tasks.md.

Problem and expected behavior

export-desktop-application gives a computer that runs the app on its seed, but nothing for an agent to do in it and nothing to grade. The change the user made during the capture is the natural task. But REA cannot turn it into a prompt: what the user meant is a judgment, and REA runs no model.

After this change:

  • describe-desktop-application-task lays out the change: each owned file that differs between the capture's first and last version, with both views and the net change. It also drafts checks.
  • An agent writes the prompt and keeps the checks that say what the user did.
  • export-desktop-application --task task.json accepts the task only if it is a real task for this capture.

Change and scope

  • Task file (src/domain/desktop/appTask.ts): {name, prompt, checks} with 1 to 32 checks of ten kinds:

    • file_present and file_absent;
    • lines_present, lines_absent and line_count (lines equal to exact or holding contains; equals, at_least or at_most);
    • value_equals and value_absent by JSON pointer;
    • row_present, row_absent and row_count on SQLite tables.

    Refused at parse time:

    • a file outside the home;
    • the empty pointer;
    • a value that looks like an ISO 8601 date and time;
    • the placeholder prompt.
  • Evaluator (appTaskChecks.ts): evaluates the checks on the typed views the state view already produces.

    • An absent file reads as empty.
    • A file with no view fails every check except the presence checks.
    • A check decided by what a view leaves out fails when that view is truncated, as does value_equals of a whole object or array.
    • No regular expressions: a pattern run on agent-written text could stall the verifier, and the two evaluators would read some differently.
  • Verifier (appTaskPython.ts): renders verify.py, the same evaluator in Python with the checks inlined.

    • It finds the app among the computer's child environments and calls its data/get under the run's control-token grant.
    • It raises when the app cannot be reached, so the run fails instead of scoring the agent.
  • describe-desktop-application-task <capture> (new command): returns the application, its owned paths, the changed files with their net change (lines, values, keys, rows, or created, deleted, format, content, unreadable), and a draft task with suggested checks.

  • export-desktop-application --task <file>:

    • The export evaluates the checks at the capture's first version of each file (the seed) and its last (the end state).
    • It refuses the task, naming the check, when every check passes on the seed or any check fails at the end. It also refuses a check outside the owned paths, a pointer into text or into namespaced XML, or a value naming the captured home.
    • A refused task writes no bundle.
    • A file present but not retained at the baseline counts as absent on the seed, since the computer starts without it.
    • An accepted task adds tasks/<name>.json, artifacts/<name>-check/verify.py and evals/<env>.toml, and the README describes the task and a solve-rate recipe.
  • Not included:

    • running agents or measuring a solve rate (REA runs no model; the README and guide give the recipe);
    • more than one task per capture;
    • checks on anything but files the app's data/get serves.

Contract and boundary impact

  • Semantic owner and earliest changed stage: a new describe_desktop_application_task operation (predicate type rea.desktop-application-task-draft), then the task stage of export_desktop_application.
  • Contribution scope fit and linked maintainer decision: same approved desktop-environments work as feat(desktop): capture a Linux desktop application in use #8874.
  • CLI and MCP/tool-catalog contract:
    • one new command, describe-desktop-application-task;
    • one new option on export-desktop-application, --task, given at most once (a second is refused);
    • the export result gains task.
  • Provider, bridge, target-format, or platform compatibility: the bundle's task uses agentenv-framework 0.9.1431 task steps (deploy_env, deploy_agent, add_skills, start_computer_recording, prompt_agent, env_outcome_verifier, end_computer_recording).
  • Evidence, artifact, provenance, or reconstruction contract: the draft is Evidence over the capture and its state view. The export's result names the task, its number of checks and the checks failing on the seed.
  • Target selection, authority, upstream behavior, and cleanup details: unchanged. The task reads only the capture, and the verifier reads only the app's data/get.
  • Generated metadata: the CLI reference and the docs navigation list the new command and guide.

Expected effects:

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added:

    • 68 checks covering every kind against views of every format: text, truncated text, JSON with numbers, booleans, arrays and escaped pointers, a key file, truncated XML, SQLite with truncated and unreadable tables, binary, a view error, and an absent file.
    • Each check is asserted for the TypeScript evaluator and for the rendered verify.py under python3 -I with a stub protocol client. The two agree on every result and description.
    • 19 task refusals, among them a leftover regex field.
    • Draft suggestions for text, a format rewrite, key files, SQLite rows, and created and deleted files. The draft also keeps a database whose rows changed under an unchanged hash (its -wal changed), and a file whose truncated views match while it changed. It leaves out a file with one version.
    • The export with a task, plus nine refusals, none of which writes a bundle. A task file with a byte-order mark is refused with the reader's own fix.
  • Mousepad, captured fresh (~/Documents/todo.txt, "buy milk" added under "buy bread"): the draft listed the document and suggested lines_present "buy milk":

    {"name": "change", "prompt": "REPLACE: what the user asks to have done in the app, in their own words.",
     "checks": [{"id": "check-1", "kind": "lines_present", "file": "Documents/todo.txt", "lines": ["buy milk"]}]}

    A task with three checks (milk, bread, and one-milk counting exactly one "buy milk" line) was exported:

    {"name": "add-milk", "checks": 3, "failing_on_seed": ["milk", "one-milk"]}

    Tasks checking only "buy bread", or checking "buy eggs", were refused with no bundle written:

    • "Every check already passes on the seed, so the task would be done before the agent starts."
    • Check "check-1" fails on the captured end state: Documents/todo.txt has no line "buy eggs".
  • LibreCAD (four holes added to a plate, which the save rewrote from DXF R12 to 2007): the draft suggested counts of SECTION, ENDSEC and CIRCLE. A task with CIRCLE equals 9 was exported: 5 on the seed and 9 at the end.

  • Typora, captured fresh (a to-do "Buy trail snacks" and a table row "Sun | Lake swim" added to ~/Documents/trip.md): among about 25 changed files under .config/Typora, the draft suggested only the document's two new lines. A task checking both, with "- [ ] Book the cabin" kept, was exported with snacks and sunday failing on the seed. Tasks checking only the cabin, or a kayak to-do, were refused.

  • In an agentenv computer (agentenv-framework 0.9.1431, local Docker, arm64, --no-emulate):

    • agent-env run --dry-run planned the eval's one task with Claude Code as the solver.
    • A model-free probe task (deploy_env, then the exported verifier) scored 0 on the seed. It recorded milk and one-milk false and bread true.
    • On the same computer, computer_click, computer_key and computer_type added "buy milk" in Mousepad and saved it. The exported verify.py, called under the control-token grant as the step calls it, then passed all three checks.
    • A second "buy milk" line failed only one-milk. After data/reset, the checks read as on the seed again.
    • Typora's bundle, which installs Typora from its vendor repository, ran the same way. Its probe scored 0 on the seed. The capture's input sequence, sent through the computer's tools, made all three checks pass, and data/reset restored the seed verdict.
    • Both sandboxes were torn down.
  • Solve rate (the documented recipe, --agent claude-code --model anthropic/claude-sonnet-5-5, three runs per task on bundles re-exported from the final code):

    Task Runs passed Cost per run (USD)
    Mousepad add-milk 3 of 3 0.91, 0.39, 0.39
    Typora plan-sunday 3 of 3 0.77, 0.31, 0.43
    LibreCAD add-holes 3 of 3 0.81, 0.32, 0.34
    • Every run worked through the computer's input tools only, and passed every check. In LibreCAD, the agent typed circle commands into the app's command line.
    • The nine runs cost $4.67 in total, by Claude Code's own count.
    • Each task is one change made in one sitting, so Sonnet 5.5 solves all of them. They show the environments work end to end, not how hard a task can be.
  • Recordings of the runs. These are the computer's own screen recordings (10 fps, cursor shown), with each run's segments joined and nothing else edited. Below is the first run of each task; the table links all nine.

    Claude Code adds buy milk in Mousepad and saves Claude Code adds a to-do and a Sun row in Typora and saves Claude Code adds four holes to the plate in LibreCAD and saves
    Task MP4
    Mousepad add-milk run 1, run 2, run 3
    Typora plan-sunday run 1, run 2, run 3
    LibreCAD add-holes run 1, run 2, run 3
  • Remaining proof gaps:

    • The agentenv runs used the verify.py from before bced17cf2. That commit changed only line_count with contains and value_equals of whole values on truncated views, both covered by the cross-language tests.
    • SQLite and dconf checks were covered by synthetic views only.
    • The XML namespace refusal was covered by code review only.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • npm run typecheck, lint, lint:typed, format:check, build:cached, knip, jscpd, docs:check, docs:build, verify:test-discovery and verify:architecture-guards passed. There are no lint diagnostics in the touched files.
  • Desktop tests with the two Python boundary tests: 261 passed and 14 skipped on macOS, and 274 passed and 1 skipped in the Ubuntu 24.04 container.
  • CLI boundary tests (cliInputSchemaRequired, cliToolCoverage, cliStartupImports) passed.
  • The Mousepad, LibreCAD and Typora captures, exports and agentenv runs above.

The hand-written diff is about 1,300 lines of source, 1,400 of tests and fixtures, and 400 of docs. The task file, its evaluator, its Python rendering and the export's validation only make sense together, so this is kept as one PR.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. An export without --task is unchanged.
  • Real Hopper/Ghidra, browser, or OS coverage: Ubuntu 24.04 arm64; Mousepad, LibreCAD and Typora; agentenv-framework 0.9.1431.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review:
    • REA runs no model and no code the task author wrote. Checks are data, and verify.py is rendered from them with values inlined as JSON.
    • The verifier calls only the app's data/get, under the grant the run gives it.

Author readiness

Human owner: @earakely-scale

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🤖 Generated with Claude Code

earakely-scale and others added 30 commits November 3, 2026 22:37
Add `capture-desktop-application`, a CLI-only, Linux and X11 capture that
launches an installed desktop application, watches it while it is used and
writes capture Evidence plus SHA-256 addressed content:

- inventory: desktop entry, launch command, executable, package and its
  apt sources (or snap, flatpak, AppImage), Electron markers and candidate
  state directories;
- session: the launched process tree, client windows from xprop with the
  first application window time and a window match, and files held open;
- state: a stat walk of the home directory before launch, at checkpoints
  read from stdin, at stop and after exit, with created, modified, deleted
  and retyped paths and the content of changed files and state directories.

It refuses non-Linux hosts, missing DISPLAY or xprop, a non-empty output
folder and an application whose window is already open, and signals only
processes it started.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cancelled captures

- Launch through the owned provider path with a run token; release with
  SIGTERM, then the token-verified group cleanup, after every session.
  Processes without the token are never signalled; each records whether it
  carries it, and the session records whether cleanup was verified.
- Report creations and deletions only where a walk listed the nearest
  existing directory; count the rest as unconfirmed.
- Retain a file only when it is still the walked version, through owned
  no-follow reads; charge the budget only for new content; create files
  exclusively in a private output folder owned by the caller.
- Walk content roots outside the home directory, compare paths in their
  physical spelling, and run executables from the caller's directory.
- Refuse when the window list cannot be read, re-evaluate window ownership
  each sample, handle one queued request per sample with a bounded stdin
  queue, and write the capture with end reason `cancelled` on interruption.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Allow a capture cancelled before any requested checkpoint to hold only the
  baseline.
- Retry an incomplete stop once; if cleanup is still unverified, write the
  capture and fail with the run token and process group as cleanup
  resources.
- Classify path changes after a walk (ENOTDIR and other selected-file
  errors) as changed_since_walk instead of failing the capture.
- Treat a non-directory ancestor as proof that nothing existed beneath it.
- Report a before digest only for the file version it was read from.
- Derive stderr_truncated from stderr's own byte counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Content reads pass retainFailedClose, so a handle whose close fails stays
owned by the content store; the capture retries those closes after it is
written and fails with the output folder as the cleanup resource if one
still cannot be closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add inspect-desktop-application-state, a CLI command that reads a capture
folder into the application's state: every retained or changed file with
its role and reasons, a typed JSON view of each version (SQLite through the
shared worker, dconf through a private dconf dump, JSON, XML, key files,
text) and the kinds of change between versions (lines, values, keys, rows).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When both the launched processes and a retained file's handle remain
unresolved after the capture is written, one cleanup error now lists both
reasons and both resource sets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add export-desktop-application, which writes an agentenv bundle with one
computer_app environment from a capture folder and its state view: the
apt package, a boot launch matched on the captured window class with a
window ready check, owned paths from the state roles, a seed of the
baseline files, data hooks that call agentenv's add/get/reset helpers
with a typed JSON view on get, a skill, a README and a deploy smoke task.
The desktop CLI commands now load their services lazily.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Read key files, XML attributes, dconf groups and SQLite tables into
  dictionaries without inherited properties, so captured keys such as
  __proto__ stay data.
- Version databases with their write-ahead log as of each checkpoint, and
  stage only digest-verified bytes for the SQLite and dconf readers.
- Detect the format per version, report format changes, keep JSON null and
  empty containers as values, keep XML text and preserved whitespace, and
  mark truncated views (including XML depth) and comparisons of them.
- Number versions and changes by checkpoint index, decide baseline
  presence from the captured type, and keep tagged SQLite integers, text
  bytes and blobs distinct.
- Carry the capture's limitations and omitted content forward, match state
  directories by their home path, index changes once per capture, keep
  failed handle closes owned, and finish the CLI through the shared owner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cation-export

# Conflicts:
#	src/application/desktop/DesktopApplicationState.test.ts
#	src/application/desktop/DesktopApplicationState.ts
#	src/application/desktop/desktopCapture.fixture.ts
#	src/cli/desktopCaptureCommands.ts
- Read coverage flags only from formats whose readers write them, so JSON
  fields named truncated stay data.
- Fail a SQLite version whose write-ahead log existed but was not retained
  or does not match its digest, instead of reading the database alone.
- Close retained content on every exit, including cancellation, and report
  a close that still fails together with the primary failure.
- Finish the CLI with the canonical operation name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cation-export

# Conflicts:
#	src/cli/desktopCaptureCommands.ts
- Refuse packages not from the Ubuntu 24.04 archive, since the computer
  installs by name from that archive.
- Start the launch command in the captured working directory.
- Respell the home path only in files the state view read as text.
- Report files that were not retained instead of dropping them silently.
- Load captured dconf keys for apps that own no files.
- Keep home-level files as owned paths; rename reserved Python class names.
- Mark truncated SQLite and text views in the class's get view, and read
  file types the way the state view does.
- Release the output tree through a resource scope, and read seeds through
  an owner closed on every exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…L values

- Stage database bytes in a private runtime root released through a
  resource scope, so a failed removal keeps its owner and is reported with
  the read's outcome.
- Report cleanup facts and owners from desktop failures instead of
  wrapping them as a plain adapter error.
- Record the state directories as walked, in their physical spelling, and
  match roles against both spellings, so files in a symlinked state
  directory are not mistaken for selected documents.
- Keep whitespace-only XML leaf values; drop only formatting between
  elements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep whitespace-only leaf values and xml:space="preserve" text in the
generated get view; drop only formatting between elements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…captures

- Release retained-content handles whose close failed through a resource
  scope on every exit; a release that fails again reports the exact handle
  owners. A read that finished keeps its bytes when only its close failed.
- Accept captures written before state directories were recorded, and say
  in the state view's limitations that symlinked state directories may be
  misclassed for them.
- Compute each XML element's whitespace context once instead of scanning
  siblings for every text run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Try the captured working directory only when an argument is relative,
  and start the app even when that folder is missing.
- Cut the generated XML view at REA's node and depth limits, and keep one
  unreadable file from failing data/get.
- Accept Ubuntu 24.04 mirrors, not only the canonical hosts; PPAs and
  other repositories are still refused.
- Read seeds through the retained-content owner's new signature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Stage a retained -journal beside its database so the SQLite worker
  accepts an inactive journal and refuses a hot one; a -wal or -journal
  that existed but was not retained (including one omitted at the
  baseline) makes the version unreadable.
- Keep tables that read when another fails, and let cancellation and
  unfinished cleanup propagate instead of becoming view errors.
- Read text as a key file only when every line parses as one.
- Rank a file named in a window title or selected by the caller as a
  document even when the application holds it open; match titles on
  file-name boundaries; leave the display name out of dconf matching.
- Bound JSON and key file views (4 MiB, 500 levels, exact numbers) and
  report too-large, missing and altered retained content separately.
- Add no version or change while a database stays absent.
- Read staged databases in place instead of copying them again.
- Give Evidence the same limitations as the result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Load captured dconf keys without D-Bus: install dconf-cli, compile the
  existing and captured key files into the user database, and warn on
  failure instead of stopping the server.
- Start relative-argument launches in the captured working directory,
  the home included, since the framework launches from the app folder;
  respell the home itself and option values under it.
- Read views in a thread, skip files over 64 MiB, align the generated
  readers with the state view's, and close SQLite connections.
- Never own the home or a shared base folder; map a symlinked state
  directory's files back to their home spelling.
- Report omitted files and unretained opened folders.
- Name the module so it cannot shadow another, and keep the class off
  the module's own names; derive names the bundle check accepts.
- Write seeds as they are read, and remove an unpublished bundle on
  failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Ask the launch process group and token carriers to exit before
  killing them, on errors too; report incomplete cleanup with its facts.
- Count descendants of the launch as started by the capture even when
  their environment cannot be read; treat a reused pid as a new process.
- Follow symlinked desktop entries; name a flatpak's state after its
  application id; match flatpak windows by name only.
- Stop on end of input; refuse empty, duplicate and reserved checkpoint
  names; require whitespace after the checkpoint keyword.
- Stay on each walk root's device, and never walk or retain `/` or a
  directory containing the home; say so in the limitations.
- Refuse a window manager that does not publish _NET_CLIENT_LIST.
- Remove retained content when a capture fails before it is written.
- Skip reading a file larger than the remaining content budget.
- Record apt's archive origin for each package source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lication-state

# Conflicts:
#	docs/desktop-application-capture.md
#	src/cli/desktopCaptureCommands.ts
xfwm4 leaves _NET_CLIENT_LIST unset while it manages no windows, so the
capture refused an ordinary desktop with nothing open. Read
_NET_SUPPORTED with the list: an unset list means no windows when the
window manager supports it, and the capture is refused only when it
does not. `xprop`'s "no such atom" output now reads as unset too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
earakely-scale and others added 9 commits November 4, 2026 19:09
- A selected path that is not a directory brings its -wal and -journal as
  content roots, so the capture records whether they exist and retains them.
- The state view treats a file as absent only under recorded content roots
  and refuses a database whose log presence is unknown.
- JSON changes list containers too, so an array that becomes an object with
  the same members is a change.
- Key file entries escape the key as a JSON pointer segment, so [a/b] c and
  [a] b/c stay apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- An unreadable window list is unknown, not zero windows, and is named in
  the limitations.
- Before ending the session, live run-token carriers no parent link reached
  are tracked; an unreadable process table leaves the exit unknown.
- An env prefix's assignments find the program and name its XDG folders.
- flatpak run options that take a value are skipped with it, -- is honoured
  and a /arch/branch suffix is dropped from the id.
- State directories include the entry's full id.
- Path options refuse empty values; roots count toward the entry limit.
- A capture file whose close failed stays owned and is retried on discard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Sources files parse into one stanza per line or deb822 paragraph with its
  URIs and suites kept as lists, so no URI x suite product is built.
- Repository architectures and release are optional, so captures written
  before they were recorded parse; the export treats them as unrestricted
  and unknown.
- Library sources are matched against every index of a declared
  repository's URI, suite and components.
- Two failures side by side keep both causes and both cleanup owners, and a
  host file whose close fails reports a lease on its handle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nto feat/desktop-vendor-packages

# Conflicts:
#	src/domain/desktop/desktopEntry.test.ts
#	src/domain/desktop/desktopEntry.ts
…hrough members

- The draft's key file lookup uses the state view's escaped `group/key`, so a
  key holding `/` or `~` still gets its suggestion.
- JSON and XML changes now list containers too; the draft suggests an added
  or removed container through its listed members, and a container only
  when none of its members is listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A program, or an empty or relative PATH entry, is resolved from the folder the
program starts in, as the launch does, so `env PATH=. app` with the entry's
Path still finds the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/domain/desktop/appExportPython.ts Resolved
earakely-scale and others added 11 commits November 4, 2026 19:48
JSON and XML changes are found by walking both trees with one shared pointer
path, building a pointer only for a listed value, so memory follows depth
rather than every container's full path. A pointer over 4096 characters is
counted but not listed. A 256-level document with 4000-character keys went
from about 251 MiB of heap to under 1 MiB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The exported reader parses the whole text before an inexact number makes it
JSON with an error, so JSON Lines with an integer above 2^53 stay text as in
REA. Over-long integer spellings are unsafe without converting them. The
guide no longer claims large integers keep their value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CLI alias check reads `-wal` and `-journal` in help text as short flags,
so the help names the write-ahead log and rollback journal instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Source URIs lose any user name and password as they are parsed, the way apt
  splits them, and so do index-target URIs and descriptions and the sources
  apt-cache policy lists, so matching works whether or not apt prints them.
- A repository whose entry held credentials is marked
  `uri_credentials_removed`, and the export refuses it, or an older capture
  whose URI still holds them, with a reason that names no URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nto feat/desktop-vendor-packages

# Conflicts:
#	docs/desktop-application-export.md
Program paths and PATH entries are joined rather than resolved, so `..` after
a symbolic link means what it means to the launch. The lookup moves to its own
module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The exported reader only needs to know whether any number was inexact, so it
keeps a boolean instead of every rejected spelling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment on lines +58 to +61
const describe = (repository: AptRepository): string =>
repository.uri === null
? repository.source
: `${withoutCredentials(repository.uri)} ${[repository.suite, ...repository.components].join(" ")}`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Older captures expose passwords

The new credential scrub still returns raw repository.source when uri is null. Older supported captures can hold a username and password here when no sources entry matched. That password reaches export errors, or skipped and returned Evidence when another repository is usable. The no-repository error also prints raw installed.sources.

This violates the repository requirement to redact transport credentials. Normalize these older fields at the capture-reading boundary too.

How this was verified: Older source strings pass capture admission unchanged, and the raw fallback flows into export errors and Evidence without credential removal.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/domain/desktop/appExportInstall.ts
Line: 58-61

Comment:
**Older captures expose passwords**

The new credential scrub still returns raw `repository.source` when `uri` is null. Older supported captures can hold a username and password here when no sources entry matched. That password reaches export errors, or `skipped` and returned Evidence when another repository is usable. The no-repository error also prints raw `installed.sources`.

This violates the repository requirement to redact transport credentials. Normalize these older fields at the capture-reading boundary too.

**How this was verified:** Older source strings pass capture admission unchanged, and the raw fallback flows into export errors and Evidence without credential removal.

**Context Used:** AGENTS.md ([source](https://github.com/morluto/rea/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +174 to +176
for (const directory of directories) {
const candidate = resolve(startedIn, directory, program);
if (await isExecutable(candidate)) return candidate;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Relative executables fail after export

resolveProgram now accepts an executable relative to the application's working folder, but export still launches the original argv. For an apt-installed app with Path=/opt/app and Exec=./app, with no extra arguments, capture succeeds. The exported launchCommand checks only arguments after the executable when deciding whether to restore that folder. It therefore runs ./app from the environment folder and never reaches ready.

Use the resolved executable for export, or account for a relative executable when restoring the working folder.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/desktop/LinuxApplicationInventory.ts
Line: 174-176

Comment:
**Relative executables fail after export**

`resolveProgram` now accepts an executable relative to the application's working folder, but export still launches the original `argv`. For an apt-installed app with `Path=/opt/app` and `Exec=./app`, with no extra arguments, capture succeeds. The exported `launchCommand` checks only arguments after the executable when deciding whether to restore that folder. It therefore runs `./app` from the environment folder and never reaches ready.

Use the resolved executable for export, or account for a relative executable when restoring the working folder.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

earakely-scale and others added 7 commits November 4, 2026 20:25
A capture written before credentials were removed counted the credentialed
repository's libraries as installable, so the export refuses it whole and asks
for a new capture rather than declaring packages no source offers. Recorded
policy sources are redacted in export messages too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…into feat/desktop-application-state

# Conflicts:
#	src/application/desktop/DesktopCaptureRefusals.ts
…nto feat/desktop-vendor-packages

# Conflicts:
#	src/desktop/LinuxApplicationInventory.ts
A login on a real host reads as a secret to the commit scanner, so the older
capture's fixture uses apt.example.com like the other credential tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment on lines +29 to +32
: (environment.PATH?.split(delimiter) ?? []).map((directory) =>
directory === "" || directory === "."
? `${startedIn}/${program}`
: `${from(directory)}/${program}`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Snap apps become snapd

With /snap/bin/ on PATH, this join returns /snap/bin//firefox, which neither snap pattern in packageFromPath accepts. If the launcher is the standard symlink to the snapd executable, identifyPackage falls through to Debian package lookup and records snapd instead of the Firefox snap. Export can then install snapd and try to launch a missing app.

Keep the symlink-aware lookup, but make package detection accept equivalent separators and . segments without losing the snap launcher's identity.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/desktop/ProgramLookup.ts
Line: 29-32

Comment:
**Snap apps become `snapd`**

With `/snap/bin/` on `PATH`, this join returns `/snap/bin//firefox`, which neither snap pattern in `packageFromPath` accepts. If the launcher is the standard symlink to the snapd executable, `identifyPackage` falls through to Debian package lookup and records `snapd` instead of the Firefox snap. Export can then install `snapd` and try to launch a missing app.

Keep the symlink-aware lookup, but make package detection accept equivalent separators and `.` segments without losing the snap launcher's identity.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant