Skip to content
Draft
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
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,31 @@ jobs:
manifest-path: ${{ matrix.manifest }}
command: check advisories bans licenses sources

wasm:
name: wasm32 build
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Install wasm32 target
run: rustup target add wasm32-unknown-unknown

- name: Check library on wasm32
run: cargo check --locked --target wasm32-unknown-unknown

semver:
name: SemVer compatibility
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Check public API against the latest published release
uses: obi1kenobi/cargo-semver-checks-action@v2

msrv:
name: Rust 1.88 MSRV
runs-on: ubuntu-latest
Expand Down
15 changes: 15 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# CLAUDE.md

Guidance for AI assistants working in this repository.

## Attribution

Never attribute work to Claude or any AI assistant, anywhere in this
repository:

- No `Co-Authored-By` trailers naming an AI in commit messages.
- No claude.ai or session links in commit messages, PR titles or bodies,
issues, or comments.
- Commit author and committer identity must be the repository owner's git
identity (`jskoiz <brinakdorser@gmail.com>`), never an AI or bot identity.
- Do not mention AI assistance in code comments, docs, or changelogs.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ include = [
"/docs/README.md",
"/docs/SUMMARY.md",
"/docs/getting-started.md",
"/docs/conformance.md",
"/docs/ROADMAP.md",
"/docs/cookbook.md",
"/docs/schema-modes.md",
"/docs/diagnostics.md",
Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,20 @@ hand. saneyaml is serde-first **and** YAML 1.2-correct.
anchors, ordering, and untouched bytes.
- **Benchmarked** — real-world config corpus runs are tracked against
`serde_yaml`, `yaml-rust2`, and `saphyr`; see [BENCHMARKS.md](docs/BENCHMARKS.md).
- **Conformance-tracked** — all 402 upstream yaml-test-suite cases run against
this crate and its alternatives, published as a
[live conformance matrix](https://jskoiz.github.io/saneyaml/conformance/index.html);
see [docs/conformance.md](docs/conformance.md).

## Status

Pre-1.0 (`0.3.0`), MSRV Rust 1.88, and actively maintained. The public API is a
preview surface but is treated as SemVer-visible: breaking changes and MSRV
bumps are explicit, documented release decisions. The road to 1.0 is about
locking the surface down, not expanding it — stability is the goal.
bumps are explicit, documented release decisions, and every PR is gated by
`cargo semver-checks` against the published release. The road to 1.0 is about
locking the surface down, not expanding it — see the
[stability gates](docs/ROADMAP.md). The library also builds for
`wasm32-unknown-unknown` (kept green in CI).

## Documentation

Expand All @@ -101,7 +108,8 @@ everything else.
[Diagnostics](docs/diagnostics.md) · [Untrusted input](docs/untrusted-input.md)
· [Editing files](docs/editing.md) · [Streaming](docs/streaming.md)
- **Migrating** — [from `serde_yaml`](docs/MIGRATION.md)
- **Reference** — [Compatibility](docs/COMPATIBILITY.md) ·
- **Reference** — [Conformance](docs/conformance.md) ·
[Compatibility](docs/COMPATIBILITY.md) ·
[Architecture](docs/ARCHITECTURE.md) · [Benchmarks](docs/BENCHMARKS.md) ·
[API docs](https://docs.rs/saneyaml)
- **Project** — [Security](SECURITY.md) · [Contributing](CONTRIBUTING.md) ·
Expand Down
20 changes: 20 additions & 0 deletions contrib/oss-fuzz/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################

FROM gcr.io/oss-fuzz-base/base-builder-rust
RUN git clone --depth 1 https://github.com/jskoiz/saneyaml "$SRC/saneyaml"
WORKDIR $SRC/saneyaml
COPY build.sh $SRC/
43 changes: 43 additions & 0 deletions contrib/oss-fuzz/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# OSS-Fuzz integration (staging copy)

This directory is a ready-to-submit [OSS-Fuzz](https://github.com/google/oss-fuzz)
project definition for saneyaml. The canonical copy lives in the
`google/oss-fuzz` repository once accepted; this staging copy is kept in-tree
so changes to the fuzz harness and the OSS-Fuzz config can be reviewed
together.

It builds all 11 libFuzzer targets under [`fuzz/`](../../fuzz) with debug
assertions enabled (the targets assert parser invariants, not just
crash-freedom) and ships each target's checked-in corpus as its seed corpus.

## Submitting

1. Fork and clone `https://github.com/google/oss-fuzz`.
2. Copy this directory to `projects/saneyaml/` in the fork.
3. Verify locally (requires Docker):

```sh
python infra/helper.py build_image saneyaml
python infra/helper.py build_fuzzers --sanitizer address saneyaml
python infra/helper.py check_build saneyaml
python infra/helper.py run_fuzzer saneyaml parse_bytes -- -max_total_time=60
```

4. Open a PR titled `[saneyaml] Initial integration`. In the description,
cover what OSS-Fuzz acceptance reviewers ask about: what the project is, who
uses it (YAML parsing of untrusted input; serde_yaml replacement path), and
that the primary contact is the maintainer.

## Notes

- `primary_contact` in `project.yaml` must be an email associated with a
Google account to receive ClusterFuzz crash reports and access
https://oss-fuzz.com. Additional maintainers can be added later via
`auto_ccs`.
- The Dockerfile clones `main`, so OSS-Fuzz always fuzzes the current default
branch; no release coupling.
- New fuzz targets added under `fuzz/fuzz_targets/` are picked up
automatically by `cargo fuzz list` in `build.sh`.
- OSS-Fuzz requires the Apache-2.0 header on `Dockerfile` and `build.sh`
(Google copyright line included per their contribution guidelines); it does
not affect saneyaml's MIT licensing.
30 changes: 30 additions & 0 deletions contrib/oss-fuzz/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash -eu
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################

cd "$SRC/saneyaml/fuzz"

# The fuzz targets assert parser invariants (round-trip stability, span
# bounds, limit enforcement), so debug assertions stay enabled in the
# optimized build.
cargo fuzz build -O --debug-assertions

for target in $(cargo fuzz list); do
cp "target/x86_64-unknown-linux-gnu/release/$target" "$OUT/"
if [ -d "corpus/$target" ]; then
zip -j -q "$OUT/${target}_seed_corpus.zip" "corpus/$target"/*
fi
done
8 changes: 8 additions & 0 deletions contrib/oss-fuzz/project.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
homepage: "https://github.com/jskoiz/saneyaml"
main_repo: "https://github.com/jskoiz/saneyaml"
language: rust
primary_contact: "brinakdorser@gmail.com"
fuzzing_engines:
- libfuzzer
sanitizers:
- address
5 changes: 5 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ in the repo and open a topic page when you hit it.

### Reference

- **[Conformance](conformance.md)** — all 402 yaml-test-suite cases, with an
[interactive matrix](https://jskoiz.github.io/saneyaml/conformance/index.html)
against other crates.
- **[Road to 1.0](ROADMAP.md)** — stability gates, MSRV and deprecation
policy, and what 1.0 will and won't promise.
- **[Compatibility](COMPATIBILITY.md)** — scalar resolution table, divergences,
threat model.
- **[Architecture](ARCHITECTURE.md)** — crate layout and design decisions.
Expand Down
46 changes: 46 additions & 0 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Road to 1.0

saneyaml is pre-1.0 (`0.3.x`). The public API is treated as SemVer-visible
today: breaking changes ship only in `0.minor` bumps and are called out in the
[changelog](../CHANGELOG.md). The road to 1.0 is about locking the surface
down, not expanding it.

## What 1.0 means here

Releasing 1.0 is a commitment, not a milestone badge:

- **No breaking API changes** outside a 2.0, including diagnostics text
guarded by tests and the documented `serde_yaml` rename-compatibility
surface in [COMPATIBILITY.md](COMPATIBILITY.md).
- **MSRV policy**: MSRV bumps are minor-version events, documented in the
changelog, and never exceed the oldest Rust release of the prior six
months.
- **Deprecation policy**: anything removed in a future 2.0 is marked
`#[deprecated]` for at least one minor release first.

## Gates

Each gate is a verifiable artifact, not an intention:

| gate | status |
|---|---|
| Full yaml-test-suite imported and classified (402/402) | done — [conformance](conformance.md) |
| Public conformance matrix vs other crates | done — [matrix](https://jskoiz.github.io/saneyaml/conformance/index.html) |
| `cargo semver-checks` gating every PR against the published release | done — CI `semver` job |
| Public API snapshot tracked in-repo | done — [PUBLIC_API.txt](PUBLIC_API.txt) |
| Continuous fuzzing of all targets | staged — [contrib/oss-fuzz](https://github.com/jskoiz/saneyaml/tree/main/contrib/oss-fuzz), pending upstream acceptance |
| API review pass over every `pub` item (naming, sealedness, `#[non_exhaustive]`) | open |
| Real-world migration validation (downstream crates' suites run against saneyaml) | in progress — five crates verified clean |
| `wasm32-unknown-unknown` library build kept green | done — CI `wasm` job |

When the open gates close, the then-current `0.x` is re-tagged 1.0-rc with a
soak window for migration feedback, then released as 1.0 unchanged unless an
rc bug forces a change.

## What 1.0 does not promise

- Byte-identical emitter output across versions outside the documented
`byte_compatible()` corpus.
- Stability of `#[doc(hidden)]` items, including `__unstable_event_serde`.
- Wall-clock or resident-memory guarantees from the resource limits (see
[Untrusted input](untrusted-input.md)).
2 changes: 2 additions & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

# Reference

- [Conformance](conformance.md)
- [Road to 1.0](ROADMAP.md)
- [Compatibility](COMPATIBILITY.md)
- [Architecture](ARCHITECTURE.md)
- [Benchmarks](BENCHMARKS.md)
52 changes: 52 additions & 0 deletions docs/conformance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Conformance

saneyaml imports **all 402 cases** of the upstream
[yaml-test-suite](https://github.com/yaml/yaml-test-suite) (no skipped or
unselected cases) and classifies every one in
`tests/fixtures/yaml-test-suite/manifest.toml`. The
**[interactive conformance matrix](https://jskoiz.github.io/saneyaml/conformance/index.html)**
(generated into `docs/conformance/index.html`) shows the per-case results side
by side with `serde_yaml`, `yaml-rust2`, and `saphyr`.

## Headline scores

The neutral axis is the 400 spec-derived accept / syntax-error cases: a
library passes a case when a full tree/`Value` load accepts a valid document
or rejects an invalid one. The 2 remaining *tree-error* cases are valid YAML
event streams that saneyaml's duplicate-key policy intentionally rejects at
tree loading; they are scored separately so policy differences don't distort
the spec axis.

| library | spec accept/reject | too strict | too lax | duplicate-key policy |
|---|---:|---:|---:|---:|
| `saneyaml` | **400/400 (100%)** | 0 | 0 | 2/2 |
| `serde_yaml` 0.9.34 | 333/400 (83.25%) | 17 | 50 | 2/2 |
| `yaml-rust2` 0.11.0 | 400/400 (100%) | 0 | 0 | 2/2 |
| `saphyr` 0.0.6 | 400/400 (100%) | 0 | 0 | 0/2 |

`yaml-rust2` and `saphyr` are strong YAML 1.2 parsers on this axis — the
difference is that they hand you a node tree, while saneyaml pairs the same
acceptance behavior with Serde-first loading, diagnostics, and resource
limits. `serde_yaml`'s gap is the libyaml-era YAML 1.1 acceptance behavior
described in [Compatibility](COMPATIBILITY.md).

## Reproducing

Both the matrix page and the numbers above are generated by running the full
corpus, not transcribed by hand:

```sh
# Regenerate docs/conformance/index.html and print the summary table
cargo run --locked --example conformance_matrix_html

# Terminal-only head-to-head with per-case mismatch listings
cargo run --locked --example conformance_compare

# Manifest/coverage invariants (case counts, parity ledgers, divergences)
cargo test --locked --test conformance_dashboard -- --nocapture
```

The selection manifest, upstream pin, per-case policies, and divergence
records all live under `tests/fixtures/yaml-test-suite/` and
`tests/fixtures/divergences/`; `tests/conformance_dashboard.rs` fails CI if
the documented counts drift from the fixtures.
Loading
Loading