Skip to content

refactor: share artifact writing and PATH binary lookup - #499

Merged
pawellisowski merged 4 commits into
mainfrom
routine/abstractions-2026-09-07
Sep 8, 2026
Merged

refactor: share artifact writing and PATH binary lookup#499
pawellisowski merged 4 commits into
mainfrom
routine/abstractions-2026-09-07

Conversation

@pawellisowski

@pawellisowski pawellisowski commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Two duplicated helpers now have one implementation shared by every caller:

  • Artifact writing: html-report.render, ui.render, viewer-3d.render, and ifc.write now delegate to render::write_artifact. The shared result reports output-path, path, and byte size consistently; _core/ui declares the existing path alias used by the other artifact producers.
  • PATH binary lookup: runtime and sidecar discovery now delegate to crate::which, preserving the first existing answer while adding the Windows .cmd, .bat, and .com launch forms that Rust's Command can execute.

The app-manifest selector originally included here was removed from this PR after review exposed a broader identity problem between install and later discovery. That work is tracked separately in #502.

Type of change

  • Bug fix in substrate

Behaviour changes

  1. ui.render now emits path alongside output-path; no field is removed or renamed.
  2. Windows PATH discovery now recognizes executable shim extensions in addition to .exe; existing .exe resolution order is unchanged.

Verification

Run from cli/:

  • cargo fmt --all -- --check
  • cargo clippy --all-targets -- -D warnings
  • cargo test — 1,224 unit tests passed, 0 failed, 1 ignored; integration suites passed
  • Real CLI: installed _core/ui, invoked aware agent invoke ui render, and verified the reported aliases, file existence, HTML prefix, and byte count
  • Real CLI: placed aware-tekla.cmd on a temporary PATH and verified aware sidecar list --json classified it as a legacy installation
  • Final local Codex review: no actionable correctness issues

Review rounds: 3 (self-feeding: 1) — rounds: initial review found manifest-selection defects; follow-up found downstream identity drift; final narrowed-diff review found no issues; outcome: manifest-selector slice split to #502, remaining changes clean in final local Codex review

Copy link
Copy Markdown
Contributor Author

@codex review


Generated by Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-08T16:02:26.948661Z 8664245 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cc7516eae4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/src/commands/app.rs Outdated
// `resolve_validate_target` — otherwise this pre-flight validates one
// manifest and the install that follows writes another.
std::fs::read_dir(&path)?;
let src_manifest = crate::manifest::loader::find_app_manifest(&path).ok_or_else(|| {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve installs from dot-directory paths

When the user runs aware app install . (or passes ..), this now always reports that no manifest exists: find_app_manifest begins with root.file_name()?, and dot-component paths have no file name, so it returns None without scanning the directory. The previous inline read_dir scan accepted these common directory arguments; normalize the path before calling the helper or let the helper fall back to scanning when no basename is available.

Useful? React with 👍 / 👎.

Comment thread cli/src/commands/app.rs Outdated
AwareError::Internal(format!("installed app {app_id} missing .flo/.app file"))
})?;
std::fs::read_dir(&app_dir)?;
let manifest_path = crate::manifest::loader::find_app_manifest(&app_dir).ok_or_else(|| {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reuse the manifest selected before copying

When a source folder contains multiple manifests and its selected manifest declares an app id different from the source directory name, looking it up again under apps/<app_id> can select a different file because the helper's preferred <dir-name>.flo candidate has changed. For example, installing bundle/bundle.flo with app: alpha while bundle/alpha.flo is also present validates and copies the former, then this call loads the latter and writes its lockfile; subsequent installed-app discovery also sees the unintended manifest. Reuse the already loaded src_app or preserve the selected source filename rather than rerunning basename-dependent selection after the directory rename.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Both P1 findings were real and are fixed in 1158a76c. Thanks — the second one in particular I had reasoned about backwards.

P1: dot-directory paths. Confirmed: find_app_manifest opened with root.file_name()?, which is None for . and .., so it returned before reading the directory. Fixed at the helper rather than at my call sites, because resolve_validate_target was moved onto it earlier — so aware app validate . had the same defect on main and this fixes that too. The directory name now comes from std::path::absolute(root).file_name(), which resolves . to the real directory name; .. stays unresolved even then (absolute is lexical), so a path with no basename now falls through to the scan instead of short-circuiting to None.

P1: reuse the manifest selected before copying. Confirmed, and your worked example is exactly right — bundle/bundle.flo declaring app: alpha alongside bundle/alpha.flo validated and copied one file and then locked the other. The rename to apps/<app-id> is precisely what makes a second lookup a different question, so there is no second lookup any more: the installed manifest is the one already selected, under its own name.

I also made the read_dir fallback sorted. It was unpinned, which is what made the second finding reachable at all, and in the .. case above it is now the only rule left deciding what gets picked up.

Both fixes carry integration tests that I verified red on cc7516ea and green on 1158a76c:

test app_install_accepts_a_dot_path ... FAILED                    # on cc7516ea
test app_install_locks_the_manifest_it_actually_installed ... FAILED

Gates from cli/: cargo fmt --all -- --check, cargo clippy --all-targets -- -D warnings, cargo test — all green (1232 unit tests + every integration suite).

@codex review


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1158a76ccd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/src/commands/app.rs Outdated
let manifest_name = src_manifest.file_name().ok_or_else(|| {
AwareError::Internal(format!("installed app {app_id} missing .flo/.app file"))
})?;
let manifest_path = app_dir.join(manifest_name);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Persist the selected manifest for later discovery

When bundle/bundle.flo declares app: alpha beside bundle/alpha.flo, this pins only the immediate lockfile load: install_app_from_path still copies both files into apps/alpha, while run, show, and discover_apps subsequently call find_app_manifest on that directory, whose basename preference selects alpha.flo rather than the installed bundle.flo. Fresh evidence after the claimed fix is that these downstream lookups remain, while the new integration test checks only the lockfile. Ensure the installed directory permanently identifies the originally selected manifest so later commands cannot execute or report the decoy.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed, and it reproduces exactly as you describe. My fix pinned the lockfile and nothing else, and the result is arguably worse than what it replaced — the two halves of the installed state now disagree:

$ aware app install $T/bundle          # bundle.flo → app: alpha, alpha.flo → app: decoy
✓ installed alpha (lockfile written)

$ ls $AWARE_HOME/apps/alpha
alpha.flo  bundle.flo  lockfile.yaml

$ grep '^app:' $AWARE_HOME/apps/alpha/lockfile.yaml
app: alpha                              # ← the manifest that was installed

$ aware app list
warning: app "decoy" is in directory "alpha" — name and `app:` field disagree
decoy  0.1.0  1  0  linear              # ← what discovery, run and show actually see

So alpha is locked and decoy is what runs. You are right that the new integration test only checked the lockfile, which is why it went green over this.

I am not attempting a third fix, and I am not merging. Two reasons.

The first is scope. The durable fix is a design decision I should not make unattended: either the installed directory records which manifest is authoritative (a new field in lockfile.yaml that discover_apps honours), or install refuses a source folder carrying two app manifests. Both change the on-disk contract for every installed app and reach well past collapsing duplicated helpers, which is what this PR is for.

The second is that this ambiguity predates the PR. On main, discover_apps already called find_app_manifest with the same <dir-name>.flo preference, and install's inline scan picked whichever manifest read_dir happened to yield — so a two-manifest folder was already resolved inconsistently there, just non-deterministically rather than deterministically. What this PR did was make the inconsistency reliable and legible, which is how you found it.

My recommendation to the maintainer: land duplications 1 and 3 (the artifact writer and the PATH lookup — untouched by any of this), and split duplication 2 out with the manifest-identity question filed as its own issue. I have left the PR open rather than doing that split myself, since after two review rounds the next call is yours, not mine.


Generated by Claude Code

…n each

Three helpers had been written more than once and drifted. Each is now one
implementation both callers use.

1. The `output-path` artifact writer — 4 copies.
   `html-report.render`, `ui.render`, `viewer-3d.render` and `ifc.write` each
   carried a byte-for-byte copy of "write the rendered bytes to `output-path`
   on a real run, report the location and size either way"; `viewer_3d`'s
   module doc already described itself as mirroring the other two. Three
   emitted the documented `path` alias beside `output-path` and `ui.render`
   emitted only `output-path`, so `{{ node.path }}` resolved against three of
   the four artifact producers and silently against the fourth. Now
   `render::write_artifact`, with `ui`'s manifest declaring the alias its
   siblings already declared.

2. The app-manifest selector — 3 inline scans.
   `manifest::loader::find_app_manifest` prefers `<dir-name>.flo`, then any
   `.flo`, then any `.app`. `resolve_validate_target` was moved onto it and
   its comment says the rule is "shared with install" — but install kept three
   inline `read_dir` scans that took whatever the filesystem yielded first, so
   a directory holding two sources could be validated as one app and installed
   as the other inside a single `aware app install`. Each site keeps its
   `read_dir` probe, so a permissions or IO failure still surfaces as itself
   rather than as "no .flo or .app file".

3. The PATH binary lookup — 2 scans that bypassed `crate::which`.
   That module exists to be the one answer to what `Command::new` can launch,
   and its doc comment is about this exact drift. Both copies appended `.exe`
   and nothing else, so a bridge installed as a `.cmd` shim read as absent on
   Windows; `sidecar.rs` also hand-split PATH on `;`, which mishandles a quoted
   entry that `std::env::split_paths` handles. Same first answer wherever an
   `.exe` exists — strictly a superset.

Considered and left apart, being different abstractions that only look alike:
`truncate_detail` / `truncate_error_detail` (different budgets and suffix
contracts, both deliberate); the two `Envelope` and two `Hit` structs (shared
names, unrelated shapes); `app_lock::find_app_source` (wider extension set,
file-or-dir, serving `app compile`); the read-side and write-side `Provenance`
models (opposite optionality); `builder::{npm,ruby}::extract_surface` (one
signature, two language grammars); and the per-host-version reflected agents
under `20-agents/aeco/architecture/`, whose identical command docs are what
reflection produces, not drift.

Gates from `cli/`: cargo fmt --all --check, cargo clippy --all-targets
-D warnings, cargo test — all pass (1230 unit + integration tests green).
…nstalled

Both P1 findings from Codex's review of cc7516e. Collapsing install's three
inline manifest scans onto `find_app_manifest` inherited two defects from the
helper's basename preference, one of them pre-existing.

1. `aware app install .` reported "no .flo or .app file".
   `find_app_manifest` opened with `root.file_name()?`, which is `None` for
   `.` and `..`, so it returned before reading the directory at all. The
   inline scans it replaced had no such preference and worked. The directory
   name now comes from the path resolved against the working directory, and a
   path with no basename falls through to the scan instead of short-circuiting.
   This also fixes `aware app validate .`, which had the same defect on main —
   `resolve_validate_target` was moved onto this helper earlier.

2. Install could lock and discover a manifest it never installed.
   Install copies the source folder to `apps/<app-id>`, which renames the
   directory, so re-running a `<dir-name>.flo`-preferring selector afterwards
   asks a different question: a folder `bundle/` holding `bundle.flo`
   (`app: alpha`) beside an `alpha.flo` was validated and copied as
   `bundle.flo`, then re-read as `alpha.flo`. The installed manifest is now
   the one already selected, under its own name — there is no second lookup.

The `read_dir`-order fallback is also sorted now. It was unpinned, which is
what made the second defect reachable at all, and with no basename to prefer
(the `..` case above) it is the only rule left deciding what gets picked up.

Both fixes carry integration tests that fail against the code before them:
`app_install_accepts_a_dot_path` and
`app_install_locks_the_manifest_it_actually_installed`, verified red on the
parent commit and green here.

Gates from `cli/`: cargo fmt --all --check, cargo clippy --all-targets
-D warnings, cargo test — all pass (1232 unit + every integration suite).
@pawellisowski pawellisowski changed the title refactor: collapse three re-typed abstractions onto one implementation each refactor: share artifact writing and PATH binary lookup Sep 8, 2026
@pawellisowski
pawellisowski force-pushed the routine/abstractions-2026-09-07 branch from 1158a76 to 8664245 Compare September 8, 2026 15:58
@pawellisowski

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 8664245bed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@pawellisowski
pawellisowski merged commit a63836c into main Sep 8, 2026
14 checks passed
@pawellisowski
pawellisowski deleted the routine/abstractions-2026-09-07 branch September 8, 2026 16:04
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