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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,6 @@ jobs:
shell: pwsh
run: .\legacy/scripts/tests/test-hospital-trust-contract.ps1

- name: DAG facade path and parity tests
shell: pwsh
run: .\legacy/scripts/tests/test-dag-facade.ps1

- name: Scoped Repowise trust-boundary tests
shell: pwsh
run: |
Expand Down Expand Up @@ -300,12 +296,6 @@ jobs:
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}

- name: Run commit contract tests
shell: pwsh
run: |
& pwsh -NoProfile -File .\legacy/scripts/tests/test-run-commit-contract.ps1
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Package
shell: pwsh
run: |
Expand Down
72 changes: 71 additions & 1 deletion crates/code-intel-cli/src/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ use std::env;
use std::error::Error;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

type Result<T> = std::result::Result<T, Box<dyn Error>>;

/// The environment variable that names the artifact root when no flag does.
pub(crate) const ARTIFACT_ROOT_ENV: &str = "CODE_INTEL_ARTIFACT_ROOT";

#[derive(Debug)]
struct ResumeSummary {
repo: PathBuf,
Expand Down Expand Up @@ -283,7 +287,7 @@ pub(crate) fn resolve_artifact_root(explicit: Option<&Path>) -> Result<PathBuf>
if let Some(path) = explicit {
return Ok(path.to_path_buf());
}
if let Ok(value) = env::var("CODE_INTEL_ARTIFACT_ROOT") {
if let Ok(value) = env::var(ARTIFACT_ROOT_ENV) {
if !value.trim().is_empty() {
return Ok(PathBuf::from(value));
}
Expand All @@ -300,6 +304,72 @@ pub(crate) fn resolve_artifact_root(explicit: Option<&Path>) -> Result<PathBuf>
.join("artifacts"))
}

/// Composes a DAG staging directory the way `resume` and `artifact index` read
/// runs back: `<artifact root>/<repo name>/<run>`. The PowerShell launcher owned
/// this composition until it was retired, and the failure it existed to prevent
/// is repeating the repository name inside the path, which strands a run where
/// no reader looks. Only the repository directory is created here; the run
/// directory is left absent because `dag_run::execute_dag` claims it
/// exclusively and must keep failing when it already exists.
pub(crate) fn compose_dag_staging_dir(
artifact_root: Option<&Path>,
repo: &Path,
) -> Result<PathBuf> {
let repo = absolute_existing_dir(repo)?;
let artifact_root = resolve_artifact_root(artifact_root)?;
let repo_name = repo
.file_name()
.and_then(|name| name.to_str())
.ok_or("repo path has no final directory name")?;
let repo_artifacts = artifact_root.join(repo_name);
fs::create_dir_all(&repo_artifacts)?;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|error| format!("system clock is before the unix epoch: {error}"))?;
let stem = utc_run_stamp(now.as_secs());
let mut nonce = u64::from(now.subsec_nanos());
for _ in 0..64 {
let candidate = repo_artifacts.join(format!("{stem}.dag-staging-{nonce:012x}"));
if !candidate.exists() {
return Ok(candidate);
}
nonce = nonce.wrapping_add(1);
}
Err(format!(
"no free DAG staging directory under {}",
repo_artifacts.display()
)
.into())
}

/// `yyyyMMdd-HHmmss` in UTC, by Howard Hinnant's `civil_from_days`. Nothing
/// parses this stem back, but `latest_run_dir` sorts run directories by name,
/// so a run written today must still sort after one the launcher wrote.
fn utc_run_stamp(seconds: u64) -> String {
let days = (seconds / 86_400) as i64;
let second_of_day = seconds % 86_400;
let shifted = days + 719_468;
let era = shifted.div_euclid(146_097);
let day_of_era = shifted.rem_euclid(146_097);
let year_of_era =
(day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
let month_position = (5 * day_of_year + 2) / 153;
let day = day_of_year - (153 * month_position + 2) / 5 + 1;
let month = if month_position < 10 {
month_position + 3
} else {
month_position - 9
};
let year = year_of_era + era * 400 + i64::from(month <= 2);
format!(
"{year:04}{month:02}{day:02}-{:02}{:02}{:02}",
second_of_day / 3_600,
(second_of_day % 3_600) / 60,
second_of_day % 60
)
}

fn absolute_existing_dir(path: &Path) -> Result<PathBuf> {
if !path.is_dir() {
return Err(format!("repo path is not a directory: {}", path.display()).into());
Expand Down
51 changes: 46 additions & 5 deletions crates/code-intel-cli/src/run_cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::env;
use std::path::PathBuf;

use crate::artifacts::{self, ARTIFACT_ROOT_ENV};
use crate::dag_run::{self, DagExecutionRequest};
use crate::execution_kernel;
use crate::execution_policy::{ExecutionPolicy, RunMode, RunProfile, SkipFlags, WorkingTreePolicy};
Expand Down Expand Up @@ -42,7 +44,8 @@ enum RunCommand {
struct Cli {
command: RunCommand,
repo: PathBuf,
out: PathBuf,
out: Option<PathBuf>,
artifact_root: Option<PathBuf>,
authority_root: Option<PathBuf>,
final_name: Option<String>,
manifest: Option<PathBuf>,
Expand All @@ -62,6 +65,7 @@ impl Cli {
};
let mut repo = None;
let mut out = None;
let mut artifact_root = None;
let mut authority_root = None;
let mut final_name = None;
let mut manifest = None;
Expand All @@ -87,6 +91,7 @@ impl Cli {
flag,
"--repo"
| "--out"
| "--artifact-root"
| "--authority-root"
| "--final-name"
| "--manifest"
Expand Down Expand Up @@ -118,6 +123,9 @@ impl Cli {
"--out" if out.replace(PathBuf::from(value)).is_some() => {
return Err("duplicate --out".into())
}
"--artifact-root" if artifact_root.replace(PathBuf::from(value)).is_some() => {
return Err("duplicate --artifact-root".into())
}
"--authority-root" if authority_root.replace(PathBuf::from(value)).is_some() => {
return Err("duplicate --authority-root".into())
}
Expand Down Expand Up @@ -233,8 +241,30 @@ impl Cli {
.into(),
);
}
// The two routes disagree about who owns the path, so accepting
// both would silently honour one and drop the other.
if out.is_some() && artifact_root.is_some() {
return Err(
"--out and --artifact-root are mutually exclusive; --out names the staging directory, --artifact-root composes one"
.into(),
);
}
if out.is_none()
&& artifact_root.is_none()
&& env::var_os(ARTIFACT_ROOT_ENV).is_none()
{
return Err(format!(
"run dag-coordinate requires --out, --artifact-root, or {ARTIFACT_ROOT_ENV}"
));
}
Comment on lines +252 to +259

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject an empty artifact-root environment value.

env::var_os(ARTIFACT_ROOT_ENV).is_some() accepts an empty or non-UTF-8 value. resolve_artifact_root ignores that value and selects LOCALAPPDATA or HOME instead. This can write the DAG run outside the user-selected artifact-root route.

Make this validation use the same non-empty UTF-8 rule as resolve_artifact_root. Add an integration test for CODE_INTEL_ARTIFACT_ROOT="".

Proposed fix
+                let has_artifact_root_env = env::var(ARTIFACT_ROOT_ENV)
+                    .ok()
+                    .is_some_and(|value| !value.trim().is_empty());
                 if out.is_none()
                     && artifact_root.is_none()
-                    && env::var_os(ARTIFACT_ROOT_ENV).is_none()
+                    && !has_artifact_root_env
                 {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if out.is_none()
&& artifact_root.is_none()
&& env::var_os(ARTIFACT_ROOT_ENV).is_none()
{
return Err(format!(
"run dag-coordinate requires --out, --artifact-root, or {ARTIFACT_ROOT_ENV}"
));
}
let has_artifact_root_env = env::var(ARTIFACT_ROOT_ENV)
.ok()
.is_some_and(|value| !value.trim().is_empty());
if out.is_none()
&& artifact_root.is_none()
&& !has_artifact_root_env
{
return Err(format!(
"run dag-coordinate requires --out, --artifact-root, or {ARTIFACT_ROOT_ENV}"
));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/code-intel-cli/src/run_cli.rs` around lines 252 - 259, Update the
validation in the dag-coordinate run path to accept ARTIFACT_ROOT_ENV only when
it contains a non-empty UTF-8 value, matching resolve_artifact_root’s selection
rule; otherwise return the existing missing-artifact-root error. Add an
integration test covering CODE_INTEL_ARTIFACT_ROOT="" and verifying the command
rejects it.

}
RunCommand::Execute => {
if artifact_root.is_some() {
return Err(
"--artifact-root is available only for run dag-coordinate; run execute publishes through --authority-root"
.into(),
);
}
if diagnosis_inputs.is_some() || seed_artifact_root.is_some() {
return Err(
"run execute does not accept diagnosis-only inputs; use run dag-coordinate for the non-authoritative compatibility primitive"
Expand Down Expand Up @@ -267,10 +297,14 @@ impl Cli {
doctor_require_understand,
doctor_tool_path_prefix,
);
if command == RunCommand::Execute && out.is_none() {
return Err("--out is required".into());
}
Ok(Self {
command,
repo,
out: out.ok_or("--out is required")?,
out,
artifact_root,
authority_root,
final_name,
manifest,
Expand All @@ -284,7 +318,7 @@ impl Cli {
}

fn usage() -> String {
"usage: run <dag-coordinate|execute> --repo <repo-root> --out <run-staging-directory> [--authority-root <publication-root> --final-name <name>] [--profile <default|strict|offline>] [--mode <lite|normal|full>] [--skip-repowise <true|false>] [--skip-sentrux <true|false>] [--require-understand-graph <true|false>] [--manifest <integrations.json>] [--max-concurrency <n>] [--working-tree-policy <head_only|explicit_overlay>] [--scope <relative-path>]... [--session-evidence <session-evidence.json>] [--diagnosis-inputs <artifact-refs.json> --seed-artifact-root <root>] [--doctor-tool-path-prefix <directory>] [--doctor-require-repowise <true|false>] [--doctor-require-understand <true|false>]".into()
"usage: run <dag-coordinate|execute> --repo <repo-root> <--out <run-staging-directory> | --artifact-root <root> (dag-coordinate only; also read from CODE_INTEL_ARTIFACT_ROOT)> [--authority-root <publication-root> --final-name <name>] [--profile <default|strict|offline>] [--mode <lite|normal|full>] [--skip-repowise <true|false>] [--skip-sentrux <true|false>] [--require-understand-graph <true|false>] [--manifest <integrations.json>] [--max-concurrency <n>] [--working-tree-policy <head_only|explicit_overlay>] [--scope <relative-path>]... [--session-evidence <session-evidence.json>] [--diagnosis-inputs <artifact-refs.json> --seed-artifact-root <root>] [--doctor-tool-path-prefix <directory>] [--doctor-require-repowise <true|false>] [--doctor-require-understand <true|false>]".into()
}

fn parse_bool_flag(flag: &str, value: &str) -> Result<bool, String> {
Expand All @@ -298,9 +332,16 @@ fn parse_bool_flag(flag: &str, value: &str) -> Result<bool, String> {
fn execute_cli(cli: Cli) -> Result<CliResult, RunError> {
match cli.command {
RunCommand::DagCoordinate => {
let out = match cli.out {
Some(out) => out,
None => artifacts::compose_dag_staging_dir(cli.artifact_root.as_deref(), &cli.repo)
.map_err(|error| {
RunError::io(format!("compose DAG staging directory: {error}"))
})?,
};
let result = dag_run::execute_dag(DagExecutionRequest {
repo: cli.repo,
out: cli.out,
out,
manifest: cli.manifest,
max_concurrency: cli.max_concurrency,
policy: cli.policy,
Expand All @@ -316,7 +357,7 @@ fn execute_cli(cli: Cli) -> Result<CliResult, RunError> {
RunCommand::Execute => {
let result = execution_kernel::execute(execution_kernel::RunRequest {
repo: cli.repo,
staging_root: cli.out,
staging_root: cli.out.expect("validated execute staging root"),
authority_root: cli
.authority_root
.expect("validated execute authority root"),
Expand Down
102 changes: 102 additions & 0 deletions crates/code-intel-cli/tests/dag_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1058,3 +1058,105 @@ fn baselined_repository_that_regresses_still_fails_the_architecture_gate() {

let _ = fs::remove_dir_all(root);
}

/// `test-dag-facade.ps1` guarded the artifact-path composition the PowerShell
/// launcher owned: a run lands directly under `<artifact root>/<repo name>`,
/// the repository name never repeats inside the path, and the explicit route
/// and the environment default produce the same evidence. `run dag-coordinate`
/// owns that composition now, so the guard lives here. The run outcome is
/// deliberately not asserted — the host toolchain decides it, and
/// `production_run_route_executes_snapshot_then_inventory` covers the outcome
/// with the doctor configured.
#[test]
fn artifact_root_routes_runs_where_readers_look_and_matches_the_environment_default() {
let root = temp_dir();
// The launcher fixture carried a space, an ampersand and non-ASCII, because
// each of those has broken a path round trip in this pipeline before.
let repo_name = "repo & 文";
let repo = root.join(repo_name);
fs::create_dir_all(&repo).unwrap();
fs::write(repo.join("README & 文.md"), "fixture").unwrap();

let run = |base: &Path, explicit: bool| -> PathBuf {
let mut command = Command::new(env!("CARGO_BIN_EXE_code-intel"));
command.args(["run", "dag-coordinate", "--repo"]).arg(&repo);
if explicit {
command.arg("--artifact-root").arg(base);
} else {
command.env("CODE_INTEL_ARTIFACT_ROOT", base);
}
// A nonzero exit means a node failed on this host, not that the path
// composition failed, so the run is judged from disk below.
let output = command.output().unwrap();
let repo_artifacts = base.join(repo_name);
let runs: Vec<PathBuf> = fs::read_dir(&repo_artifacts)
.unwrap_or_else(|error| {
panic!(
"no artifacts under {}: {error}; stderr={}",
repo_artifacts.display(),
String::from_utf8_lossy(&output.stderr)
)
})
.map(|entry| entry.unwrap().path())
.collect();
assert_eq!(
runs.len(),
1,
"a run must be a direct child of the repository artifact root: {runs:?}"
);
assert!(
!repo_artifacts.join(repo_name).exists(),
"the repository name must not repeat inside the artifact path"
);
assert!(
runs[0]
.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| name.contains(".dag-staging-")),
"run directory={:?}",
runs[0]
);
runs[0].clone()
};

let explicit = run(&root.join("explicit artifacts"), true);
let default = run(&root.join("default artifacts"), false);

let manifest: Value =
serde_json::from_slice(&fs::read(explicit.join("run-manifest.json")).unwrap()).unwrap();
assert_eq!(manifest["schema"], "code-intel-run-manifest.v1");
assert_eq!(
fs::read(explicit.join("inventory.rg/files.txt")).unwrap(),
fs::read(default.join("inventory.rg/files.txt")).unwrap(),
"the explicit and default artifact roots disagreed about the inventory"
);

let _ = fs::remove_dir_all(root);
}

/// The two routes disagree about who owns the staging path, so accepting both
/// would silently honour one and drop the other.
#[test]
fn out_and_artifact_root_cannot_both_name_the_staging_directory() {
let root = temp_dir();
let repo = root.join("repo");
fs::create_dir_all(&repo).unwrap();

let output = Command::new(env!("CARGO_BIN_EXE_code-intel"))
.args(["run", "dag-coordinate", "--repo"])
.arg(&repo)
.arg("--out")
.arg(root.join("run"))
.arg("--artifact-root")
.arg(root.join("artifacts"))
.output()
.unwrap();
assert_eq!(output.status.code(), Some(64));
assert!(
String::from_utf8_lossy(&output.stderr).contains("mutually exclusive"),
"stderr={}",
String::from_utf8_lossy(&output.stderr)
);

let _ = fs::remove_dir_all(root);
}
26 changes: 11 additions & 15 deletions crates/code-intel-cli/tests/internalization_record.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#[path = "../src/authority.rs"]
mod authority;
#[path = "../src/content_contract.rs"]
mod content_contract;
#[path = "../src/internalization_record.rs"]
mod internalization_record;

Expand Down Expand Up @@ -657,23 +659,17 @@ fn assert_signed_out_of_scope_record_projects(record: &Value, expected_id: &str)
assert_checked_schema(record, "code-intel-internalization-record.v1.schema.json");
}

/// Digests are recomputed in process rather than through `Get-FileHash`. Every
/// operation trace pins two files, so shelling out once per pin spawned dozens
/// of concurrent `pwsh` processes; the macOS runner killed them with
/// `Stack overflow.`, which surfaced here as 26 of 40 records failing at once
/// and read exactly like real digest drift. SHA256 over the same bytes is the
/// same digest either way, so nothing about what this asserts changes.
fn recompute_sha(relative: &str) -> String {
let path = root().join(relative);
let mut command = Command::new("pwsh");
command.args([
"-NoProfile",
"-CommandWithArgs",
"(Get-FileHash -LiteralPath $args[0] -Algorithm SHA256).Hash.ToLowerInvariant()",
path.to_str().unwrap(),
]);
let output = run_command_with_timeout(&mut command);
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
let digest = String::from_utf8(output.stdout).unwrap();
let digest = digest.trim().to_string();
let bytes = fs::read(&path)
.unwrap_or_else(|error| panic!("conformance source is unreadable: {relative}: {error}"));
let digest = content_contract::sha256_hex(&bytes);
assert_eq!(digest.len(), 64);
digest
}
Expand Down
Loading
Loading