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
83 changes: 33 additions & 50 deletions .github/workflows/retrieval-sidecar-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,66 +6,46 @@ name: retrieval-sidecar-smoke
on:
pull_request:
paths:
- crates/codestory-retrieval/**
- crates/codestory-contracts/**
- crates/codestory-store/Cargo.toml
- crates/codestory-store/src/**
- crates/codestory-cli/src/retrieval.rs
- crates/codestory-cli/src/main.rs
- crates/codestory-cli/src/args.rs
- crates/codestory-cli/src/runtime.rs
- crates/codestory-cli/src/stdio_*.rs
- crates/codestory-cli/tests/fixtures/packet_search_eval/**
- crates/codestory-cli/tests/packet_search_eval.rs
- crates/codestory-cli/tests/retrieval_bootstrap_contracts.rs
- crates/codestory-cli/tests/search_json_output.rs
- crates/codestory-cli/tests/stdio_protocol_contracts.rs
- crates/codestory-runtime/src/**
- crates/codestory-indexer/Cargo.toml
- crates/codestory-indexer/src/lib.rs
- scripts/lint-retrieval-generalization.mjs
- scripts/codestory-release-evidence-gate.mjs
- scripts/tests/codestory-release-evidence-gate.test.mjs
- benchmarks/release-evidence/**
- scripts/**retrieval**
- .codex/environments/**
- .cursor/rules/**
- .github/actions/**
- .github/scripts/**
- .github/workflows/**
- benchmarks/**
- crates/**
- docker/**
- docs/architecture/language-support.md
- docs/architecture/retrieval-*
- docs/contributors/testing-matrix.md
- docs/ops/retrieval-sidecars.md
- docs/architecture/retrieval-*.md
- docs/testing/codestory-e2e-stats-log.md
- docs/testing/performance-review-playbook.md
- docs/testing/retrieval-architecture.md
- docker/retrieval-compose.yml
- .github/workflows/retrieval-sidecar-smoke.yml
- plugins/codestory/**
- scripts/**
# Base-branch runs seed caches that sibling PRs are allowed to restore.
push:
branches:
- main
- dev/codestory-next
paths:
- crates/codestory-retrieval/**
- crates/codestory-contracts/**
- crates/codestory-store/Cargo.toml
- crates/codestory-store/src/**
- crates/codestory-cli/src/retrieval.rs
- crates/codestory-cli/src/main.rs
- crates/codestory-cli/src/args.rs
- crates/codestory-cli/src/runtime.rs
- crates/codestory-cli/src/stdio_*.rs
- crates/codestory-cli/tests/fixtures/packet_search_eval/**
- crates/codestory-cli/tests/packet_search_eval.rs
- crates/codestory-cli/tests/retrieval_bootstrap_contracts.rs
- crates/codestory-cli/tests/search_json_output.rs
- crates/codestory-cli/tests/stdio_protocol_contracts.rs
- crates/codestory-runtime/src/**
- crates/codestory-indexer/Cargo.toml
- crates/codestory-indexer/src/lib.rs
- scripts/lint-retrieval-generalization.mjs
- scripts/codestory-release-evidence-gate.mjs
- scripts/tests/codestory-release-evidence-gate.test.mjs
- benchmarks/release-evidence/**
- scripts/**retrieval**
- .codex/environments/**
- .cursor/rules/**
- .github/actions/**
- .github/scripts/**
- .github/workflows/**
- benchmarks/**
- crates/**
- docker/**
- docs/architecture/language-support.md
- docs/architecture/retrieval-*
- docs/contributors/testing-matrix.md
- docs/ops/retrieval-sidecars.md
- docs/architecture/retrieval-*.md
- docs/testing/codestory-e2e-stats-log.md
- docs/testing/performance-review-playbook.md
- docs/testing/retrieval-architecture.md
- docker/retrieval-compose.yml
- .github/workflows/retrieval-sidecar-smoke.yml
- plugins/codestory/**
- scripts/**
workflow_dispatch:

concurrency:
Expand Down Expand Up @@ -115,6 +95,9 @@ jobs:
restore-keys: |
${{ runner.os }}-cargo-stable-${{ steps.rust-cache-key.outputs.version }}-${{ steps.rust-cache-key.outputs.target }}-retrieval-contracts-default-features-

- name: Generalization lint regression contracts
run: cargo test -p codestory-runtime --test retrieval_generalization_guard

- name: Runtime sidecar and packet contract tests
run: |
cargo test -p codestory-runtime --lib agent::retrieval_primary::tests
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,23 @@
project/workspace identity differs from the selected repository. `cache
identity` now exposes the lossless project, workspace, artifact, and safe
legacy-alias disposition used by that verification.
- Separated release-evidence provenance validation from the benchmark corpus
loader and expanded the generalization guard across the repository-controlled
non-Rust product and release-control boundary. Direct and adjacent/split
evaluation-corpus dependencies now fail in plugin launch/setup surfaces,
worktree/install tooling, runtime configuration, release scripts/workflows,
and the release evaluator across native separators, case variants,
direct harness commands/configuration, comment-separated JavaScript imports,
unquoted shell continuations, workflow-embedded shell continuations, and
PowerShell continuations. Comment-only JavaScript and hash-commented
shell/config text is ignored, while Markdown prose stays outside evaluation
and dependency scanning. The CI trigger and focused regression contract cover
the full protected inventory, including local composite actions and shipped
agent instruction surfaces, and missing protected paths fail closed. Exact
release-policy references to the retrieval smoke workflow are allowed only
by file, literal, and use; every direct or constructed occurrence is checked,
and constructed dependencies must span the literals that form the matched
harness marker.
- Added deterministic prior-version managed CLI upgrade proof to every native
managed-lifecycle cell. The gate now starts with a verified older install,
requires the requested packaged binary to serve status and grounding, and
Expand Down
244 changes: 244 additions & 0 deletions crates/codestory-runtime/tests/retrieval_generalization_guard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,41 @@ fn run_lint_with_prompt_script_fixture(contents: &str) -> Output {
.expect("run lint with prompt script fixture")
}

fn run_lint_with_non_rust_fixtures(fixtures: &[(&str, &str)]) -> Output {
let repo_root = workspace_root();
let script = lint_script(&repo_root);
let fixture_root = TempDir::new().expect("create fixture root");
std::fs::write(
fixture_root.path().join("neutral.rs"),
"pub fn repository_neutral_fixture() {}\n",
)
.expect("write neutral Rust fixture");
for (file_name, contents) in fixtures {
let file_path = fixture_root.path().join(file_name);
std::fs::create_dir_all(file_path.parent().expect("fixture parent"))
.expect("create non-Rust fixture parent");
std::fs::write(file_path, contents).expect("write non-Rust fixture");
}

let _guard = LINT_SCRIPT_LOCK
.get_or_init(|| Mutex::new(()))
.lock()
.expect("lock lint script subprocess");
Command::new("node")
.arg(&script)
.current_dir(&repo_root)
.env(
"CODESTORY_RETRIEVAL_GENERALIZATION_SCAN_ROOTS",
fixture_root.path(),
)
.env(
"CODESTORY_RETRIEVAL_GENERALIZATION_NON_RUST_SCAN_ROOTS",
fixture_root.path(),
)
.output()
.expect("run lint with non-Rust fixture")
}

#[test]
fn retrieval_generalization_lint_script_exits_clean_with_extra_fixture_root() {
let repo_root = workspace_root();
Expand Down Expand Up @@ -386,6 +421,215 @@ pub const LEAKED_DEPENDENCY: &str = include_str!(concat!("../../benchmarks/", "t
}
}

#[test]
fn linter_rejects_direct_and_split_non_rust_corpus_dependencies() {
let rejected = run_lint_with_non_rust_fixtures(&[
(
"leaked.ps1",
"$corpus = \"scripts\\cross-repo-\" + `\n \"sourcetrail-queries.mjs\"\n",
),
(
"leaked.sh",
"prefix=./scripts\nscript=${prefix#./}/fetch-holdout-repos.mjs\ncorpus=benchmarks/ta\\\nsks/eval-probes.json\n",
),
(
"workflow-command.yml",
"run: |2-\n node scripts/fetch-\\\n holdout-repos.mjs\n",
),
(
"surrounding-command.mjs",
"const command = \"node scripts/fetch-\" + \"holdout-repos.mjs --json\";\nconst config = \"prefix benchmarks/ta\" + \"sks/eval-probes.json suffix\";\n",
),
(
"line-continuation.mjs",
"const script = \"scripts/fetch-holdout-\\\nrepos.mjs\";\n",
),
(
"joined-shell-word.sh",
"node scripts/fetch-'holdout-repos.mjs'\n",
),
(
"joined-workflow-word.yml",
"run: |\n node scripts/fetch-'holdout-repos.mjs'\n",
),
(
"quoted-run-key.yml",
"steps:\n - \"run\": |\n node scripts/fetch-'holdout-repos.mjs'\n",
),
(
"escaped-shell-word.sh",
"node scripts/fetch\\-holdout-repos.mjs\n",
),
(
"quoted-yaml-scalar.yml",
"value: 'clean # scripts/fetch-holdout-repos.mjs'\n",
),
(
"quoted-block-scalar.yml",
"run: |\n value='clean # scripts/fetch-holdout-repos.mjs'\n",
),
(
"github-script.yml",
"uses: actions/github-script@v8\nwith:\n script: |\n const script = \"scripts/fetch-\" + \"holdout-repos.mjs\";\n",
),
(
"direct-harness-import.mjs",
"import \"./scripts/codestory-agent-ab-benchmark.mjs\";\n",
),
(
"constructed-harness-import.mjs",
"const harness = \"scripts/codestory-agent-ab-\" + \"benchmark.mjs\";\nawait import(harness);\n",
),
(
"unapproved-policy-reference.mjs",
"const workflows = [\".github/workflows/retrieval-sidecar-smoke.yml\", \"unrelated.yml\"];\n",
),
(
"plugins/codestory/skills/codestory-grounding/SKILL.md",
"Run `node scripts/fetch-holdout-repos.mjs` before grounding.\n",
),
(
".cursor/rules/codestory.mdc",
"Read benchmarks/tasks/eval-probes.json before answering.\n",
),
]);
let rejected_stderr = String::from_utf8_lossy(&rejected.stderr).to_ascii_lowercase();
assert!(
!rejected.status.success(),
"executable corpus dependencies must fail lint; stderr={rejected_stderr}"
);
for (file_name, marker) in [
("leaked.ps1", "scriptscrossreposourcetrailqueriesmjs"),
("leaked.sh", "benchmarkstasksevalprobesjson"),
("workflow-command.yml", "fetchholdoutreposmjs"),
("surrounding-command.mjs", "fetchholdoutreposmjs"),
("line-continuation.mjs", "fetchholdoutreposmjs"),
("joined-shell-word.sh", "fetch-'holdout-repos.mjs"),
("joined-workflow-word.yml", "fetch-'holdout-repos.mjs"),
("quoted-run-key.yml", "fetch-'holdout-repos.mjs"),
("escaped-shell-word.sh", "fetch\\-holdout-repos.mjs"),
("quoted-yaml-scalar.yml", "fetch-holdout-repos.mjs"),
("quoted-block-scalar.yml", "fetch-holdout-repos.mjs"),
("github-script.yml", "fetchholdoutreposmjs"),
(
"direct-harness-import.mjs",
"codestory-agent-ab-benchmark.mjs",
),
(
"constructed-harness-import.mjs",
"codestoryagentabbenchmarkmjs",
),
(
"unapproved-policy-reference.mjs",
"retrieval-sidecar-smoke.yml",
),
("skill.md", "fetch-holdout-repos.mjs"),
("codestory.mdc", "benchmarks/tasks"),
] {
assert!(
rejected_stderr.contains(file_name) && rejected_stderr.contains(marker),
"lint failure should identify {file_name} and {marker}; stderr={rejected_stderr}"
);
}

let allowed = run_lint_with_non_rust_fixtures(&[
(
"prose.md",
"The benchmark harness reads `benchmarks/tasks/eval-probes.json`; production code must not.\n",
),
(
"quoted-shell.sh",
"value='scripts/fetch-\\\nholdout-repos.mjs'\n",
),
(
"unrelated-list.yml",
"- scripts/fetch-\\\n- holdout-repos.mjs\n",
),
(
"template-comment.mjs",
"const value = `${({ clean: true }).clean /* scripts/fetch-holdout-repos.mjs */}`;\n",
),
(
"quoted-shell-comment.sh",
"value='clean\\' # scripts/fetch-holdout-repos.mjs\n",
),
(
"quoted-powershell-comment.ps1",
"$value = 'clean`' # scripts/fetch-holdout-repos.mjs\n",
),
(
"quoted-yaml-comment.yml",
"value: 'clean\\' # scripts/fetch-holdout-repos.mjs\n",
),
(
"folded-workflow.yml",
"run: >-\n node scripts/fetch-\\\n holdout-repos.mjs\n",
),
(
"comment-only.yml",
"# run: node scripts/fetch-\\\n# holdout-repos.mjs\nrun: echo clean\n",
),
(
"plain-apostrophe.yml",
"message: don't load it # scripts/fetch-holdout-repos.mjs\n",
),
(
"punctuated-apostrophe.yml",
"message: rock-'n roll # scripts/fetch-holdout-repos.mjs\n",
),
(
"doubled-single-quote.yml",
"value: 'scripts/fetch-''holdout-repos.mjs'\n",
),
]);
let allowed_stderr = String::from_utf8_lossy(&allowed.stderr);
assert!(
allowed.status.success(),
"prose, comments, and unrelated continuations must pass lint; stderr={allowed_stderr}"
);
}

#[test]
fn linter_binds_policy_allowances_to_the_exact_approved_use() {
let allowed = run_lint_with_non_rust_fixtures(&[
(
".github/scripts/route-ci-proof.mjs",
" \".github/workflows/retrieval-sidecar-smoke.yml\",\n",
),
(
".github/scripts/check-workflow-policy.mjs",
"const retrievalSidecarSmoke = path.join(workflowRoot, \"retrieval-sidecar-smoke.yml\");\nviolations.push(\"retrieval-sidecar-smoke.yml must exist\");\nviolations.push(\"retrieval-sidecar-smoke.yml Windows proof must be workflow_dispatch-only\");\n",
),
]);
let allowed_stderr = String::from_utf8_lossy(&allowed.stderr);
assert!(
allowed.status.success(),
"the exact policy and routing references must pass lint; stderr={allowed_stderr}"
);

let rejected = run_lint_with_non_rust_fixtures(&[
(
".github/scripts/route-ci-proof.mjs",
" \".github/workflows/retrieval-sidecar-smoke.yml\",\n \".github/workflows/retrieval-sidecar-smoke.yml\",\nawait import(\".github/workflows/retrieval-sidecar-smoke.yml\");\n",
),
(
".github/scripts/check-workflow-policy.mjs",
"const retrievalSidecarSmoke = path.join(workflowRoot, \"retrieval-sidecar-smoke.yml\");\nconst hostile = \".github/workflows/retrieval-\" + \"sidecar-smoke.yml\";\n",
),
]);
let rejected_stderr = String::from_utf8_lossy(&rejected.stderr).to_ascii_lowercase();
assert!(
!rejected.status.success(),
"an approved file must not hide another harness use; stderr={rejected_stderr}"
);
assert!(
rejected_stderr.contains("route-ci-proof.mjs:3:await import")
&& rejected_stderr.contains("check-workflow-policy.mjs:2:")
&& rejected_stderr.contains("githubworkflowsretrievalsidecarsmokeyml"),
"lint must identify both the hostile direct and split uses; stderr={rejected_stderr}"
);
}

#[test]
fn linter_fails_closed_when_one_prompt_corpus_entry_is_not_a_literal() {
let output = run_lint_with_prompt_script_fixture(
Expand Down
Loading