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
54 changes: 35 additions & 19 deletions .github/workflows/release-plz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,18 @@ on:
# `docs/architecture/release-automation-plan.md` §8 (Path B row) and
# `just/build.just` `release-pr` / `release-tag`.
#
# push: # ← Path B: disabled by design (manual releases)
# branches: [main]
# push.branches:[main] stays disabled by design (Path B — manual
# releases; see the trigger-history comment above). Instead a narrow
# tag trigger drives the OIDC `crates-io-publish` job below: it fires
# exactly when a release tag is cut (the same `v*` tags `release.yml`
# builds binaries for), carrying the crates.io publish along with
# every `just ship` release — no long-lived token, no manual step.
# The release-pr / release jobs stay workflow_dispatch-only (they are
# gated to `github.event_name == 'workflow_dispatch'`), so a tag push
# only runs the publish job.
push:
tags:
- 'v*'
workflow_dispatch:

# Default to ZERO permissions; each job grants only what it needs.
Expand Down Expand Up @@ -303,8 +313,10 @@ jobs:

# Skip on forks (fork PRs can't access the repo's secrets and would
# produce noisy "permission denied" runs). Matches the convention
# release-plz docs recommend.
if: github.repository_owner == 'skyllc-ai'
# release-plz docs recommend. Restricted to workflow_dispatch: the
# `v*` tag trigger is reserved for the crates-io-publish job, so the
# release-pr job must not run on a tag push.
if: github.repository_owner == 'skyllc-ai' && github.event_name == 'workflow_dispatch'

# Push branch + open/update PR. Granted at job level so the
# release job below can run with a tighter (no pull-requests)
Expand Down Expand Up @@ -375,7 +387,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15

if: github.repository_owner == 'skyllc-ai'
# workflow_dispatch only — the `v*` tag trigger is reserved for the
# crates-io-publish job; this release job must not fire on a tag push
# (release.yml already builds binaries from the tag).
if: github.repository_owner == 'skyllc-ai' && github.event_name == 'workflow_dispatch'

# Tag push only — no PR operations from this job.
permissions:
Expand Down Expand Up @@ -436,13 +451,14 @@ jobs:
done

# ─────────────────────────────────────────────────────────────────
# R7: OIDC Trusted Publishing scaffolding (crates.io)
# OIDC Trusted Publishing to crates.io (ACTIVE)
# ─────────────────────────────────────────────────────────────────
#
# Phase R7 — OIDC trusted publisher scaffolding. This job is gated
# by the repo variable `ENABLE_CRATES_IO_PUBLISH` (unset → dormant)
# until Phase R8 (first dress rehearsal). It sets up the OIDC token
# exchange with crates.io for passwordless, short-lived credentials.
# Fires on every `v*` release tag (gated by the repo variable
# `ENABLE_CRATES_IO_PUBLISH`), minting a short-lived crates.io token
# via OIDC — no long-lived registry token is ever stored. This is
# what carries the crates.io publish along with each `just ship`
# release tag.
#
# A repo-variable gate is used instead of a literal `if: false` for
# two reasons: (1) actionlint rejects constant `if:` conditions, and
Expand All @@ -468,20 +484,20 @@ jobs:
# See: docs/architecture/release-automation-plan.md §Phase R7/R8
#
crates-io-publish:
name: crates.io / OIDC publish (R7 scaffolding)
name: crates.io / OIDC publish
runs-on: ubuntu-latest
timeout-minutes: 15
needs: release-plz-release

# Two gates, both required:
# 1. Repo variable ENABLE_CRATES_IO_PUBLISH=true — the R9 master
# 1. Repo variable ENABLE_CRATES_IO_PUBLISH=true — the master
# switch (unset / any-other-value keeps the job dormant).
# 2. releases_created — `release-plz-release` runs on EVERY push
# to main and no-ops internally when HEAD isn't a release-PR
# merge; without this gate the publish job would fire (and
# page the environment reviewer) on every push, then fail
# republishing the already-live version.
if: ${{ vars.ENABLE_CRATES_IO_PUBLISH == 'true' && needs.release-plz-release.outputs.releases_created == 'true' }}
# 2. A `v*` release-tag push — the ship pipeline (and release.yml)
# cut exactly one such tag per release, so publish fires once
# per released version and never on ordinary pushes. The
# checkout below resolves to the tag ref, so cargo publishes the
# freshly-released version. Re-running against an already-live
# version is a harmless no-op-error from crates.io.
if: ${{ vars.ENABLE_CRATES_IO_PUBLISH == 'true' && startsWith(github.ref, 'refs/tags/v') }}

environment: crates.io-publish
permissions:
Expand Down
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ which = "8.0.4"
strsim = "0.11.1"

# ───── Hashing ─────
rustc-hash = "2.1.2"
rustc-hash = "2.1.3"

# ───── Formatting / Encoding ─────
itoa = "1.0.18"
Expand All @@ -314,7 +314,7 @@ reqwest = { version = "0.12.28", default-features = false, features = [

# ───── Parallelism ─────
rayon = "1.12.0"
crossbeam-channel = "0.5.15"
crossbeam-channel = "0.5.16"

# ───── Memory ─────
mimalloc = "0.1.52"
Expand Down
2 changes: 1 addition & 1 deletion crates/uffs-bench/src/host/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl ProcOutput {
/// Implemented by [`SystemHost`] (real OS) and [`MockHost`] (in-memory, for
/// tests). Methods are intentionally low-level wrappers; higher-level logic in
/// `state`, `restore`, and `fingerprint` composes them and maps their
/// [`io::Error`]s into [`crate::error::BenchError`] with path context.
/// [`std::io::Error`]s into [`crate::error::BenchError`] with path context.
pub trait Host {
/// Read the entire contents of a file.
///
Expand Down
6 changes: 3 additions & 3 deletions crates/uffs-client/src/shmem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ static SHMEM_COUNTER: AtomicU64 = AtomicU64::new(0);
///
/// # Errors
///
/// Returns [`io::Error`] if the directory cannot be created.
/// Returns [`std::io::Error`] if the directory cannot be created.
fn shmem_dir() -> io::Result<PathBuf> {
let base = dirs_next::data_local_dir()
.unwrap_or_else(|| PathBuf::from(if cfg!(windows) { r"C:\temp" } else { "/tmp" }));
Expand All @@ -168,7 +168,7 @@ fn shmem_dir() -> io::Result<PathBuf> {
///
/// # Errors
///
/// Returns [`io::Error`] if the shmem directory cannot be created.
/// Returns [`std::io::Error`] if the shmem directory cannot be created.
fn unique_shmem_path() -> io::Result<PathBuf> {
let dir = shmem_dir()?;
let pid = std::process::id();
Expand Down Expand Up @@ -567,7 +567,7 @@ pub(crate) const STREAM_CHUNK_BYTES: usize = 4 * 1024 * 1024;
///
/// ## Error pinpointing
///
/// Every failure path attaches a step-specific [`io::Error`] kind +
/// Every failure path attaches a step-specific [`std::io::Error`] kind +
/// message identifying which stage broke (`open`, `metadata`,
/// `mmap`, `write_all`) together with the blob byte size and, for
/// write failures, the byte offset reached. This converts opaque
Expand Down
15 changes: 0 additions & 15 deletions crates/uffs-client/src/stdout_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,6 @@ pub fn write_stdout_buffer(buf: &[u8]) -> std::io::Result<()> {
/// well-formed UTF-8. `WriteConsoleW` cannot represent invalid UTF-8
/// anyway, so surfacing the error up-front is strictly better than a
/// mangled console write.
#[expect(
clippy::std_instead_of_core,
reason = "core::io::Error is not yet stable — see rust-lang/rust#103765. \
Remove this expect once `error_in_core` stabilises."
)]
pub fn utf8_to_utf16(buf: &[u8]) -> std::io::Result<Vec<u16>> {
let utf8 = core::str::from_utf8(buf)
.map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))?;
Expand Down Expand Up @@ -346,11 +341,6 @@ mod platform_windows {
/// a pipe/file/NUL stdout would fail at `WriteConsoleW` with
/// `ERROR_INVALID_HANDLE`.
#[expect(unsafe_code, reason = "FFI to GetStdHandle + WriteConsoleW")]
#[expect(
clippy::std_instead_of_core,
reason = "core::io::Error is not yet stable — see rust-lang/rust#103765. \
Remove this expect once `error_in_core` stabilises."
)]
pub(super) fn write_to_console_w(buf: &[u8]) -> std::io::Result<()> {
let utf16 = super::utf8_to_utf16(buf)?;
if utf16.is_empty() {
Expand Down Expand Up @@ -533,11 +523,6 @@ mod shared_tests {
/// Invalid UTF-8 must surface as `InvalidData`, not silently
/// produce mojibake on the console.
#[test]
#[expect(
clippy::std_instead_of_core,
reason = "core::io::ErrorKind is not yet stable — see rust-lang/rust#103765. \
Remove this expect once `error_in_core` stabilises."
)]
fn utf8_to_utf16_invalid_utf8_is_invalid_data_error() {
// 0xFF is never valid as a standalone UTF-8 byte.
let err = utf8_to_utf16(&[0xFF_u8]).expect_err("must reject invalid UTF-8");
Expand Down
6 changes: 3 additions & 3 deletions crates/uffs-core/src/compact_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ pub fn compact_runtime_root() -> PathBuf {
///
/// # Errors
///
/// Returns an [`io::Error`] if the parent directory cannot be created
/// Returns an [`std::io::Error`] if the parent directory cannot be created
/// or if the owner-only permissions cannot be applied (Unix `0o700`,
/// Windows owner-only DACL).
fn compact_runtime_tempfile_path(
Expand Down Expand Up @@ -550,9 +550,9 @@ pub fn deserialize_compact(
///
/// # Errors
///
/// Returns an [`io::Error`] if:
/// Returns an [`std::io::Error`] if:
/// * the cache bytes are truncated, wrong magic, or use a stale/unsupported
/// version (parse error lifted via [`io::Error::other`]);
/// version (parse error lifted via `std::io::Error::other`);
/// * `runtime_dir.create_owner_only` fails (path collision, permissions, parent
/// missing);
/// * writing the layout into the tempfile fails (I/O / disk-full);
Expand Down
2 changes: 1 addition & 1 deletion crates/uffs-core/src/compact_mmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ impl RuntimeLayout {
///
/// # Errors
///
/// Forwards any [`io::Error`] from `set_len`, `seek`, `write_all`,
/// Forwards any [`std::io::Error`] from `set_len`, `seek`, `write_all`,
/// `flush`, or `sync_all`.
///
/// # Examples
Expand Down
7 changes: 0 additions & 7 deletions crates/uffs-daemon/src/cache/cursor_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,6 @@ impl super::journal_loop::CursorStore for DiskCursorStore {
/// as "start from journal head" which is the correct fallback
/// for a never-saved or corrupted cursor — better to re-replay
/// than to silently start from a stale or invalid USN.
#[expect(
clippy::std_instead_of_core,
reason = "`core::io::ErrorKind` is not yet stable — see \
rust-lang/rust#103765. Mirrors the same pattern \
used in `crate::config::Config::load_from_path`. \
Remove this expect once `error_in_core` stabilises."
)]
fn load(&self, letter: uffs_mft::platform::DriveLetter) -> u64 {
let path = self.cursor_path(letter);
match std::fs::read(&path) {
Expand Down
7 changes: 0 additions & 7 deletions crates/uffs-daemon/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,13 +380,6 @@ impl Config {
/// All other I/O errors (permission denied, EISDIR on a path
/// that exists as a directory, etc.) propagate as
/// [`ConfigError::Io`].
#[expect(
clippy::std_instead_of_core,
reason = "`core::io::ErrorKind` is not yet stable — see \
rust-lang/rust#103765. Mirrors the same pattern \
used in `crate::index::aggregation`. Remove this \
expect once `error_in_core` stabilises."
)]
pub(crate) fn load_from_path(path: &Path) -> Result<Self, ConfigError> {
match std::fs::read_to_string(path) {
Ok(body) => Self::from_toml(&body),
Expand Down
10 changes: 0 additions & 10 deletions crates/uffs-daemon/src/index/aggregation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,6 @@ impl DaemonFileReader<'_> {
}

impl FileReader for DaemonFileReader<'_> {
#[expect(
clippy::std_instead_of_core,
reason = "core::io::Error is not yet stable — see rust-lang/rust#103765. \
Remove this expect once `error_in_core` stabilises."
)]
fn read_first_bytes(
&self,
record_idx: usize,
Expand All @@ -81,11 +76,6 @@ impl FileReader for DaemonFileReader<'_> {
Ok(buf)
}

#[expect(
clippy::std_instead_of_core,
reason = "core::io::Error is not yet stable — see rust-lang/rust#103765. \
Remove this expect once `error_in_core` stabilises."
)]
fn read_all(&self, record_idx: usize, drive_ordinal: u8) -> std::io::Result<Vec<u8>> {
let path = self
.resolve_path(record_idx, drive_ordinal)
Expand Down
5 changes: 0 additions & 5 deletions crates/uffs-mft/src/index/storage/file_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,6 @@ impl MftIndex {
/// # Errors
///
/// Returns an error if file reading, decryption, or deserialization fails.
#[expect(
clippy::std_instead_of_core,
reason = "core::io::Error is not yet stable — see rust-lang/rust#103765. \
Remove this expect once `error_in_core` stabilises."
)]
pub fn load_from_file(
path: &std::path::Path,
) -> Result<(Self, IndexHeader), Box<dyn core::error::Error>> {
Expand Down
13 changes: 8 additions & 5 deletions crates/uffs-mft/src/io/parser/fragment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ use crate::ntfs::{
)]
#[expect(
clippy::indexing_slicing,
clippy::missing_asserts_for_indexing,
reason = "the only `[]` indexing that remains is into internal arena vectors \
(fragment.links / fragment.streams / fragment.records / fragment.frs_to_idx) \
whose indices are produced by this code, not by untrusted on-disk bytes. \
Expand Down Expand Up @@ -175,8 +174,10 @@ pub fn parse_record_to_fragment(
.and_then(|end| data.get(name_bytes_offset..end))
{
let name_u16: Vec<u16> = name_bytes
.chunks_exact(2)
.map(|pair| u16::from_le_bytes([pair[0], pair[1]]))
.as_chunks::<2>()
.0
.iter()
.map(|pair| u16::from_le_bytes(*pair))
.collect();
let name = crate::io::parser::unified::decode_name_u16(&name_u16).0;
let parent_frs = file_reference_to_frs(fn_attr.parent_directory);
Expand Down Expand Up @@ -272,8 +273,10 @@ pub fn parse_record_to_fragment(
.and_then(|end| data.get(name_offset..end))
{
let name_u16: SmallVec<[u16; 64]> = name_bytes
.chunks_exact(2)
.map(|pair| u16::from_le_bytes([pair[0], pair[1]]))
.as_chunks::<2>()
.0
.iter()
.map(|pair| u16::from_le_bytes(*pair))
.collect();
let stream_name = crate::io::parser::unified::decode_name_u16(&name_u16).0;
// ALL named $DATA streams create regular
Expand Down
12 changes: 8 additions & 4 deletions crates/uffs-mft/src/io/parser/fragment_extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,10 @@ pub(super) fn parse_extension_to_fragment(
.and_then(|end| data.get(name_start..end))
{
let name_u16: SmallVec<[u16; 64]> = name_bytes
.chunks_exact(2)
.map(|pair| <[u8; 2]>::try_from(pair).map_or(0, u16::from_le_bytes))
.as_chunks::<2>()
.0
.iter()
.map(|pair| u16::from_le_bytes(*pair))
.collect();
let name = crate::io::parser::unified::decode_name_u16(&name_u16).0;
let parent_frs = fn_attr.parent_directory & 0x0000_FFFF_FFFF_FFFF;
Expand Down Expand Up @@ -188,8 +190,10 @@ pub(super) fn parse_extension_to_fragment(
.and_then(|end| data.get(name_offset..end))
{
let name_u16: SmallVec<[u16; 64]> = name_bytes
.chunks_exact(2)
.map(|pair| <[u8; 2]>::try_from(pair).map_or(0, u16::from_le_bytes))
.as_chunks::<2>()
.0
.iter()
.map(|pair| u16::from_le_bytes(*pair))
.collect();
let stream_name = crate::io::parser::unified::decode_name_u16(&name_u16).0;
// ALL named $DATA streams create regular
Expand Down
Loading
Loading