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
108 changes: 108 additions & 0 deletions .github/workflows/ores-cli-zpkg-kind-canary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: ores-cli Zed package.kind canary

on:
pull_request:
paths:
- '.github/workflows/ores-cli-zpkg-kind-canary.yml'
- 'certification/ores-cli-zpkg-kind/**'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ores-zpkg-kind-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
certify:
runs-on: ubuntu-24.04
timeout-minutes: 25
steps:
- name: Check out exact candidate
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
fetch-depth: 1

- name: Verify exact candidate and product lint blob
shell: bash
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}"
test "$(git hash-object certification/ores-cli-zpkg-kind/src/audit/zpkg_manifest.rs)" = e78a7636df785fbf4746436325bbbc8b38d46184

- name: Install Rust 1.95
uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30
with:
toolchain: 1.95.0
components: rustfmt,clippy

- name: Certify exact ores-cli lint
working-directory: certification/ores-cli-zpkg-kind
shell: bash
run: |
set -euo pipefail
cargo +1.95.0 generate-lockfile
cargo +1.95.0 fmt --all -- --check
cargo +1.95.0 check --locked --all-targets
cargo +1.95.0 clippy --locked --all-targets -- -D warnings
cargo +1.95.0 test --locked --all-targets -- --nocapture
cargo +1.95.0 test --release --locked --all-targets

- name: Install checksum-pinned Zed v0.3.0
shell: bash
run: |
set -euo pipefail
archive="$RUNNER_TEMP/zed.tar.gz"
curl --fail --silent --show-error --location --retry 3 \
https://github.com/zed-pkg/zed-cli/releases/download/v0.3.0/zed-x86_64-unknown-linux-musl.tar.gz \
-o "$archive"
echo "e380240c96242ab3b29e0e9d14c95ba946d086f20f0732e34d0e5cf47513efc7 $archive" | sha256sum --check
mkdir -p "$RUNNER_TEMP/zed-bin"
tar -xzf "$archive" -C "$RUNNER_TEMP/zed-bin"
echo "$RUNNER_TEMP/zed-bin" >> "$GITHUB_PATH"

- name: Require Zed to accept canonical shape and reject package.kind
shell: bash
run: |
set -euo pipefail
good="$RUNNER_TEMP/zpkg-good"
bad="$RUNNER_TEMP/zpkg-bad"
mkdir -p "$good" "$bad"
cat > "$good/.zpkg.toml" <<'TOML'
[package]
org = "zed-pkg"
name = "zed-e2e"
version = "0.1.0"
description = "Zed validation fixture"
license = "MIT"
language = "typescript"

[package.repository]
vcs = "git"
url = "https://github.com/zed-pkg/zed-e2e"

[install]
adapter = "none"
dir = ".vendor/.zed"

[targets.repository]
dir = "."

[scripts]
test = "true"
TOML
cp "$good/.zpkg.toml" "$bad/.zpkg.toml"
sed -i '/version = "0.1.0"/a kind = "pub-lib-core"' "$bad/.zpkg.toml"
(cd "$good" && zed validate)
set +e
(cd "$bad" && zed validate) >"$RUNNER_TEMP/bad.out" 2>&1
status=$?
set -e
test "$status" -ne 0
grep -Ei 'kind|unknown|invalid|manifest' "$RUNNER_TEMP/bad.out"

- name: Prove certificate sources remained immutable
run: git diff --exit-code -- certification/ores-cli-zpkg-kind .github/workflows/ores-cli-zpkg-kind-canary.yml
12 changes: 12 additions & 0 deletions certification/ores-cli-zpkg-kind/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "ores-cli-zpkg-kind-certification"
version = "0.1.0"
edition = "2024"
publish = false

[dependencies]
serde_json = "1"
toml = "0.8"

[dev-dependencies]
tempfile = "3"
24 changes: 24 additions & 0 deletions certification/ores-cli-zpkg-kind/src/audit/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use std::path::{Path, PathBuf};

use crate::model::CommandReport;

mod zpkg_manifest;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RepositoryAuditOptions {
pub path: PathBuf,
pub profile: String,
pub additional_required_paths: Vec<String>,
}

#[must_use]
pub fn run(root: &Path) -> CommandReport {
zpkg_manifest::augment_zpkg_manifest_audit(
&RepositoryAuditOptions {
path: root.to_path_buf(),
profile: "baseline".to_owned(),
additional_required_paths: Vec::new(),
},
CommandReport::new("audit repo"),
)
}
154 changes: 154 additions & 0 deletions certification/ores-cli-zpkg-kind/src/audit/zpkg_manifest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use std::fs;

use serde_json::json;
use toml::Value;

use super::RepositoryAuditOptions;
use crate::model::{CommandReport, Finding};

const MANIFEST: &str = ".zpkg.toml";

/// Add conservative Zed manifest checks to repository audit.
///
/// `zed validate` remains the canonical executable validator. This static pass
/// only rejects fleet mistakes that have already proven harmful and can be
/// identified without reimplementing Zed's schema. In particular,
/// `package.kind` is not part of the canonical Zed package model; package role
/// is expressed by repository naming, targets and other supported metadata.
pub(super) fn augment_zpkg_manifest_audit(
options: &RepositoryAuditOptions,
mut report: CommandReport,
) -> CommandReport {
let path = options.path.join(MANIFEST);
let metadata = match fs::symlink_metadata(&path) {
Ok(metadata) => metadata,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return report.finalize(),
Err(error) => {
report.push(
Finding::warning(
"zpkg-manifest-unreadable",
format!("could not inspect {MANIFEST}: {error}"),
)
.with_target(MANIFEST),
);
return report.finalize();
}
};

if metadata.file_type().is_symlink() || !metadata.is_file() {
report.push(
Finding::error(
"zpkg-manifest-not-regular",
".zpkg.toml must be a repository-owned regular file",
)
.with_target(MANIFEST),
);
return report.finalize();
}

let source = match fs::read_to_string(&path) {
Ok(source) => source,
Err(error) => {
report.push(
Finding::error(
"zpkg-manifest-unreadable",
format!("could not read {MANIFEST} as UTF-8: {error}"),
)
.with_target(MANIFEST),
);
return report.finalize();
}
};
let document = match source.parse::<Value>() {
Ok(document) => document,
Err(error) => {
report.push(
Finding::error(
"zpkg-manifest-invalid",
format!("could not parse {MANIFEST}: {error}"),
)
.with_target(MANIFEST),
);
return report.finalize();
}
};

report.insert_metadata("zpkgManifestInspected", json!(true));

if document
.get("package")
.and_then(Value::as_table)
.is_some_and(|package| package.contains_key("kind"))
{
report.push(
Finding::error(
"zpkg-package-kind-noncanonical",
"`package.kind` is not canonical Zed manifest metadata; express package role through supported targets/naming and require `zed validate` for full admission",
)
.with_target(MANIFEST)
.with_detail("field", json!("package.kind")),
);
}

report.finalize()
}

#[cfg(test)]
mod tests {
use std::fs;

use tempfile::tempdir;

use super::augment_zpkg_manifest_audit;
use crate::audit::RepositoryAuditOptions;
use crate::model::CommandReport;

fn audit(source: &str) -> CommandReport {
let root = tempdir().expect("temporary repository");
fs::write(root.path().join(".zpkg.toml"), source).expect("manifest");
augment_zpkg_manifest_audit(
&RepositoryAuditOptions {
path: root.path().to_path_buf(),
profile: "baseline".to_owned(),
additional_required_paths: Vec::new(),
},
CommandReport::new("audit repo"),
)
}

#[test]
fn rejects_noncanonical_package_kind() {
let report = audit(
r#"[package]
org = "oresoftware"
name = "ores-dnd"
version = "0.1.0"
kind = "pub-lib-core"
"#,
);
assert!(report
.findings
.iter()
.any(|finding| finding.code == "zpkg-package-kind-noncanonical"));
}

#[test]
fn accepts_supported_package_shape_without_kind() {
let report = audit(
r#"[package]
org = "oresoftware"
name = "ores-dnd"
version = "0.1.0"
description = "cross-runtime drag and drop"

[targets.rust]
dir = "src/rust"
adapter = "rust"
"#,
);
assert!(!report
.findings
.iter()
.any(|finding| finding.code == "zpkg-package-kind-noncanonical"));
}
}
2 changes: 2 additions & 0 deletions certification/ores-cli-zpkg-kind/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod audit;
pub mod model;
86 changes: 86 additions & 0 deletions certification/ores-cli-zpkg-kind/src/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use std::collections::BTreeMap;

use serde_json::Value;

#[derive(Debug, Clone)]
pub struct Finding {
pub code: String,
pub message: String,
pub severity: Severity,
pub target: Option<String>,
pub details: BTreeMap<String, Value>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity {
Error,
Warning,
}

impl Finding {
pub fn error(code: impl Into<String>, message: impl Into<String>) -> Self {
Self::new(code, message, Severity::Error)
}

pub fn warning(code: impl Into<String>, message: impl Into<String>) -> Self {
Self::new(code, message, Severity::Warning)
}

fn new(code: impl Into<String>, message: impl Into<String>, severity: Severity) -> Self {
Self {
code: code.into(),
message: message.into(),
severity,
target: None,
details: BTreeMap::new(),
}
}

pub fn with_target(mut self, target: impl Into<String>) -> Self {
self.target = Some(target.into());
self
}

pub fn with_detail(mut self, key: impl Into<String>, value: Value) -> Self {
self.details.insert(key.into(), value);
self
}
}

#[derive(Debug, Clone)]
pub struct CommandReport {
pub command: String,
pub findings: Vec<Finding>,
pub metadata: BTreeMap<String, Value>,
}

impl CommandReport {
pub fn new(command: impl Into<String>) -> Self {
Self {
command: command.into(),
findings: Vec::new(),
metadata: BTreeMap::new(),
}
}

pub fn push(&mut self, finding: Finding) {
self.findings.push(finding);
}

pub fn insert_metadata(&mut self, key: impl Into<String>, value: Value) {
self.metadata.insert(key.into(), value);
}

#[must_use]
pub fn finalize(self) -> Self {
self
}

#[must_use]
pub fn issue_count(&self) -> usize {
self.findings
.iter()
.filter(|finding| finding.severity == Severity::Error)
.count()
}
}
Loading
Loading