sagashield 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. sagashield-0.1.0/.claude-plugin/marketplace.json +30 -0
  2. sagashield-0.1.0/.dockerignore +32 -0
  3. sagashield-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +34 -0
  4. sagashield-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
  5. sagashield-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  6. sagashield-0.1.0/.github/workflows/ci.yml +111 -0
  7. sagashield-0.1.0/.github/workflows/release-binaries.yml +102 -0
  8. sagashield-0.1.0/.github/workflows/release-pypi.yml +81 -0
  9. sagashield-0.1.0/.gitignore +8 -0
  10. sagashield-0.1.0/BENCHMARK.md +94 -0
  11. sagashield-0.1.0/CHANGELOG.md +65 -0
  12. sagashield-0.1.0/CONTRIBUTING.md +46 -0
  13. sagashield-0.1.0/Cargo.lock +804 -0
  14. sagashield-0.1.0/Cargo.toml +33 -0
  15. sagashield-0.1.0/DISTRIBUTION.md +82 -0
  16. sagashield-0.1.0/Dockerfile +39 -0
  17. sagashield-0.1.0/LICENSE-APACHE +201 -0
  18. sagashield-0.1.0/LICENSE-MIT +21 -0
  19. sagashield-0.1.0/PKG-INFO +324 -0
  20. sagashield-0.1.0/README.md +308 -0
  21. sagashield-0.1.0/RELEASING.md +35 -0
  22. sagashield-0.1.0/SECURITY.md +102 -0
  23. sagashield-0.1.0/SPEC.md +432 -0
  24. sagashield-0.1.0/claude_desktop_config.example.json +14 -0
  25. sagashield-0.1.0/evals/scenarios.rs +196 -0
  26. sagashield-0.1.0/examples/demo.rs +137 -0
  27. sagashield-0.1.0/examples/otel_export.rs +61 -0
  28. sagashield-0.1.0/examples/python_agent_demo.py +91 -0
  29. sagashield-0.1.0/examples/run_evals.rs +521 -0
  30. sagashield-0.1.0/examples/security_demo.rs +148 -0
  31. sagashield-0.1.0/integrations/claude_code.md +38 -0
  32. sagashield-0.1.0/integrations/claude_desktop.json +9 -0
  33. sagashield-0.1.0/integrations/cursor.json +10 -0
  34. sagashield-0.1.0/pyproject.toml +26 -0
  35. sagashield-0.1.0/python/sagashield/__init__.py +181 -0
  36. sagashield-0.1.0/python/sagashield/integrations/__init__.py +1 -0
  37. sagashield-0.1.0/python/sagashield/integrations/langchain.py +93 -0
  38. sagashield-0.1.0/scripts/package_local.bat +34 -0
  39. sagashield-0.1.0/scripts/package_local.sh +44 -0
  40. sagashield-0.1.0/src/audit.rs +179 -0
  41. sagashield-0.1.0/src/bin/sagashield-mcp.rs +40 -0
  42. sagashield-0.1.0/src/dispatcher.rs +446 -0
  43. sagashield-0.1.0/src/error.rs +81 -0
  44. sagashield-0.1.0/src/fsm.rs +227 -0
  45. sagashield-0.1.0/src/lib.rs +102 -0
  46. sagashield-0.1.0/src/main.rs +5 -0
  47. sagashield-0.1.0/src/mcp/mod.rs +638 -0
  48. sagashield-0.1.0/src/python.rs +340 -0
  49. sagashield-0.1.0/src/replay.rs +154 -0
  50. sagashield-0.1.0/src/security/mod.rs +5 -0
  51. sagashield-0.1.0/src/security/policy.rs +322 -0
  52. sagashield-0.1.0/src/tools/mod.rs +278 -0
  53. sagashield-0.1.0/src/traits.rs +56 -0
  54. sagashield-0.1.0/src/types.rs +192 -0
  55. sagashield-0.1.0/src/wal.rs +727 -0
  56. sagashield-0.1.0/tests/concurrency_stress_test.rs +147 -0
  57. sagashield-0.1.0/tests/crash_recovery_test.rs +109 -0
  58. sagashield-0.1.0/tests/dispatcher_integration_test.rs +131 -0
  59. sagashield-0.1.0/tests/dlq_and_human_loop_test.rs +290 -0
  60. sagashield-0.1.0/tests/fsm_guardrail_test.rs +189 -0
  61. sagashield-0.1.0/tests/idempotency_test.rs +104 -0
  62. sagashield-0.1.0/tests/mcp_protocol_test.rs +371 -0
  63. sagashield-0.1.0/tests/python_binding_test.py +147 -0
  64. sagashield-0.1.0/tests/replay_and_audit_test.rs +149 -0
  65. sagashield-0.1.0/tests/security_fuzz_test.rs +234 -0
  66. sagashield-0.1.0/tests/security_sandbox_test.rs +278 -0
  67. sagashield-0.1.0/tests/wal_rollback_test.rs +213 -0
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "sagashield-marketplace",
3
+ "description": "ACID transactional Saga runtime, Step-0 security guardrail, and MCP server for autonomous AI agents.",
4
+ "version": "0.1.0",
5
+ "owner": {
6
+ "name": "SagaShield Contributors"
7
+ },
8
+ "metadata": {
9
+ "description": "Claude Code marketplace for the sagashield MCP server (sagashield-mcp over stdio).",
10
+ "version": "0.1.0"
11
+ },
12
+ "plugins": [
13
+ {
14
+ "name": "sagashield",
15
+ "source": "./",
16
+ "description": "Transactional tool execution with SQLite WAL Saga rollback, FSM guardrails, Step-0 sandboxing, idempotency, replay and OpenTelemetry audit.",
17
+ "version": "0.1.0",
18
+ "author": {
19
+ "name": "SagaShield Contributors"
20
+ },
21
+ "mcpServers": {
22
+ "sagashield": {
23
+ "command": "sagashield-mcp",
24
+ "args": [],
25
+ "description": "Run `cargo build --release --bin sagashield-mcp` and ensure the binary is on PATH, or set an absolute command path."
26
+ }
27
+ }
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,32 @@
1
+ # Rust build artifacts
2
+ target/
3
+ # VCS
4
+ .git/
5
+ .gitignore
6
+ # Python environments and caches
7
+ .venv/
8
+ venv/
9
+ __pycache__/
10
+ *.pyc
11
+ *.pyo
12
+ *.pyd
13
+ # Generated eval artifacts (reproducible via `cargo run --example run_evals`)
14
+ evals/results/
15
+ # Local run state (sandbox output, MCP databases, demo leftovers)
16
+ workspace/
17
+ *.db
18
+ *.db-wal
19
+ *.db-shm
20
+ *.db-journal
21
+ agent_demo_output.txt
22
+ agent_test_output.txt
23
+ # Local packaging output
24
+ dist/
25
+ # Fuzz corpora artifacts (reproducible)
26
+ fuzz/artifacts/
27
+ fuzz/corpus/
28
+ # Editors / OS
29
+ .vscode/
30
+ .idea/
31
+ .DS_Store
32
+ Thumbs.db
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: Bug report
3
+ about: Something behaves differently than specified
4
+ title: "fix: "
5
+ labels: bug
6
+ ---
7
+
8
+ ## Expected behavior
9
+
10
+ What should happen, per docs / `SPEC.md` / `BENCHMARK.md`?
11
+
12
+ ## Observed behavior
13
+
14
+ What happens instead? Paste the exact error, WAL rows, or eval numbers.
15
+
16
+ ## Minimal reproduction
17
+
18
+ ```rust
19
+ // or: minimal MCP transcript / python snippet / eval scenario
20
+ ```
21
+
22
+ Steps: commands run, seed used, config (features, OS).
23
+
24
+ ## Environment
25
+
26
+ - SagaShield version (`Cargo.toml` / `pip show sagashield`):
27
+ - OS:
28
+ - Rust (`rustc --version`) / Python (`python --version`):
29
+
30
+ ## Security relevance
31
+
32
+ Does this touch `src/security/`, Step-0, rollback, or the WAL?
33
+ If it may be a vulnerability, use **Security → Report a vulnerability**
34
+ (private advisory) instead — see `SECURITY.md`.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: Feature request
3
+ about: Propose a capability with its transactional implications
4
+ title: "feat: "
5
+ labels: enhancement
6
+ ---
7
+
8
+ ## Real problem
9
+
10
+ What concrete agent failure does this address? (orphaned charges, dirty
11
+ sagas, injection class, missing observability…)
12
+
13
+ ## Proposed solution
14
+
15
+ API sketch (Rust and/or Python signatures), defaults, migration path.
16
+
17
+ ## Transactional impact (HARD vs BEST-EFFORT)
18
+
19
+ Per `README.md` guarantees: does this change a **deterministic**
20
+ property (FSM, WAL, Step-0, local compensation) or a **best-effort**
21
+ one (remote compensations, planner behavior, OS confinement)?
22
+ How is a partial failure observed (WAL status, OTel span, MCP error)?
@@ -0,0 +1,13 @@
1
+ ## Summary
2
+
3
+ What changes, and why (link issues: `Fixes #…`).
4
+
5
+ ## Contributor checklist (mandatory)
6
+
7
+ - [ ] `cargo fmt` executed (`cargo fmt --check` clean)
8
+ - [ ] `cargo clippy --all-targets --all-features -- -D warnings` clean
9
+ - [ ] Unit/integration tests added or updated — no regressions (32+ tests green)
10
+ - [ ] `docs.rs` documentation updated (`///` on new public items)
11
+ - [ ] Zero `.unwrap()` introduced in `src/` (typed `KernelError` only)
12
+ - [ ] Security surface considered (`SECURITY.md` note if the threat model changes)
13
+ - [ ] `Cargo.toml` / `pyproject.toml` versions still in sync (if touched)
@@ -0,0 +1,111 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ env:
10
+ CARGO_TERM_COLOR: always
11
+ PYO3_PYTHON: python
12
+
13
+ jobs:
14
+ lint:
15
+ name: fmt + clippy (zero tolerance)
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: dtolnay/rust-toolchain@stable
20
+ with:
21
+ components: rustfmt, clippy
22
+ - uses: Swatinem/rust-cache@v2
23
+ - run: cargo fmt --check
24
+ - run: cargo clippy --all-targets --all-features -- -D warnings
25
+
26
+ test-rust:
27
+ name: rust ${{ matrix.os }}
28
+ runs-on: ${{ matrix.os }}
29
+ strategy:
30
+ fail-fast: false
31
+ matrix:
32
+ os: [ubuntu-latest, windows-latest]
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ - uses: dtolnay/rust-toolchain@stable
36
+ - uses: Swatinem/rust-cache@v2
37
+ - run: cargo test --all-targets --all-features
38
+ - run: cargo test --doc
39
+
40
+ test-python:
41
+ name: python ${{ matrix.python }} (${{ matrix.os }})
42
+ runs-on: ${{ matrix.os }}
43
+ strategy:
44
+ fail-fast: false
45
+ matrix:
46
+ os: [ubuntu-latest, windows-latest]
47
+ python: ["3.10", "3.11", "3.12"]
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+ - uses: actions/setup-python@v5
51
+ with:
52
+ python-version: ${{ matrix.python }}
53
+ - uses: dtolnay/rust-toolchain@stable
54
+ - uses: Swatinem/rust-cache@v2
55
+ - run: python -m venv .venv
56
+ # maturin develop requires an active virtualenv: drive it explicitly
57
+ # per-OS (venv layouts differ between Unix and Windows).
58
+ - name: build + test (Unix)
59
+ if: runner.os != 'Windows'
60
+ shell: bash
61
+ run: |
62
+ .venv/bin/pip install maturin
63
+ VIRTUAL_ENV="$PWD/.venv" .venv/bin/maturin develop --features python
64
+ .venv/bin/python tests/python_binding_test.py
65
+ - name: build + test (Windows)
66
+ if: runner.os == 'Windows'
67
+ shell: pwsh
68
+ run: |
69
+ .venv/Scripts/pip install maturin
70
+ $env:VIRTUAL_ENV = "$PWD/.venv"
71
+ .venv/Scripts/maturin develop --features python
72
+ .venv/Scripts/python tests/python_binding_test.py
73
+
74
+ eval-benchmarks:
75
+ name: evals (zero residual corruption)
76
+ runs-on: ubuntu-latest
77
+ steps:
78
+ - uses: actions/checkout@v4
79
+ - uses: dtolnay/rust-toolchain@stable
80
+ - uses: Swatinem/rust-cache@v2
81
+ - run: cargo run --example run_evals --release
82
+ - name: assert zero corruption
83
+ shell: python
84
+ run: |
85
+ import csv
86
+ with open("evals/results/summary.csv") as fh:
87
+ rows = list(csv.DictReader(fh))
88
+ bad = [r for r in rows if int(r["residual_corruption"]) != 0
89
+ or int(r["breaches"]) != 0 or int(r["duplicates"]) != 0]
90
+ # Baseline is *expected* to fail; only sagashield rows must be clean.
91
+ bad = [r for r in bad if r["config"] == "sagashield"]
92
+ assert not bad, f"sagashield regressions: {bad}"
93
+ print("sagashield evals clean: 0 residual, 0 breaches, 0 duplicates")
94
+
95
+ fuzz-smoke:
96
+ name: fuzz smoke (nightly, time-boxed)
97
+ runs-on: ubuntu-latest
98
+ steps:
99
+ - uses: actions/checkout@v4
100
+ - uses: dtolnay/rust-toolchain@nightly
101
+ - uses: Swatinem/rust-cache@v2
102
+ - run: cargo install cargo-fuzz --locked
103
+ - name: build fuzz targets
104
+ working-directory: fuzz
105
+ run: cargo +nightly fuzz build -O
106
+ - name: smoke run path_guard (60s)
107
+ working-directory: fuzz
108
+ run: cargo +nightly fuzz run path_guard -- -max_total_time=60 -max_len=512 -print_final_stats=1
109
+ - name: smoke run net_guard (60s)
110
+ working-directory: fuzz
111
+ run: cargo +nightly fuzz run net_guard -- -max_total_time=60 -max_len=256 -print_final_stats=1
@@ -0,0 +1,102 @@
1
+ name: release-binaries
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ env:
8
+ CARGO_TERM_COLOR: always
9
+ BIN: sagashield-mcp
10
+
11
+ jobs:
12
+ build:
13
+ name: ${{ matrix.target }} (${{ matrix.os }})
14
+ runs-on: ${{ matrix.os }}
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ include:
19
+ - os: windows-latest
20
+ target: x86_64-pc-windows-msvc
21
+ archive: sagashield-VERSION-windows-x64.zip
22
+ bin_name: sagashield-mcp.exe
23
+ - os: ubuntu-latest
24
+ target: x86_64-unknown-linux-gnu
25
+ archive: sagashield-VERSION-linux-x64.tar.gz
26
+ bin_name: sagashield-mcp
27
+ - os: macos-14
28
+ target: aarch64-apple-darwin
29
+ archive: sagashield-VERSION-darwin-arm64.tar.gz
30
+ bin_name: sagashield-mcp
31
+ - os: macos-13
32
+ target: x86_64-apple-darwin
33
+ archive: sagashield-VERSION-darwin-x64.tar.gz
34
+ bin_name: sagashield-mcp
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+ - uses: dtolnay/rust-toolchain@stable
38
+ with:
39
+ targets: ${{ matrix.target }}
40
+ - uses: Swatinem/rust-cache@v2
41
+ with:
42
+ key: release-${{ matrix.target }}
43
+ - name: build release binary
44
+ run: cargo build --release --bin ${{ env.BIN }} --target ${{ matrix.target }}
45
+ - name: stage package dir
46
+ shell: bash
47
+ run: |
48
+ VERSION="${GITHUB_REF_NAME#v}"
49
+ ARCHIVE="$(echo "${{ matrix.archive }}" | sed "s/VERSION/${VERSION}/")"
50
+ echo "ARCHIVE=$ARCHIVE" >> "$GITHUB_ENV"
51
+ rm -rf stage && mkdir -p stage
52
+ cp "target/${{ matrix.target }}/release/${{ matrix.bin_name }}" stage/
53
+ cp README.md LICENSE-MIT stage/
54
+ - name: pack zip (Windows)
55
+ if: runner.os == 'Windows'
56
+ shell: pwsh
57
+ run: Compress-Archive -LiteralPath stage/* -DestinationPath "${{ env.ARCHIVE }}"
58
+ - name: pack tarball (Unix)
59
+ if: runner.os != 'Windows'
60
+ shell: bash
61
+ run: tar -czf "${{ env.ARCHIVE }}" -C stage .
62
+ - name: sha256
63
+ shell: bash
64
+ run: |
65
+ if command -v sha256sum >/dev/null; then
66
+ sha256sum "${{ env.ARCHIVE }}" > "${{ env.ARCHIVE }}.sha256"
67
+ else
68
+ certutil -hashfile "${{ env.ARCHIVE }}" SHA256 | sed -n '2p' | awk '{print $1 " '"${{ env.ARCHIVE }}"'"}' > "${{ env.ARCHIVE }}.sha256"
69
+ fi
70
+ cat "${{ env.ARCHIVE }}.sha256"
71
+ - uses: actions/upload-artifact@v4
72
+ with:
73
+ name: ${{ env.ARCHIVE }}
74
+ path: |
75
+ ${{ env.ARCHIVE }}
76
+ ${{ env.ARCHIVE }}.sha256
77
+
78
+ release:
79
+ name: github release + SHA256SUMS
80
+ needs: [build]
81
+ runs-on: ubuntu-latest
82
+ permissions:
83
+ contents: write
84
+ steps:
85
+ - uses: actions/download-artifact@v4
86
+ with:
87
+ path: dist
88
+ merge-multiple: true
89
+ - name: aggregate SHA256SUMS.txt
90
+ shell: bash
91
+ run: |
92
+ cd dist
93
+ cat *.sha256 > SHA256SUMS.txt
94
+ cat SHA256SUMS.txt
95
+ sha256sum -c SHA256SUMS.txt
96
+ - uses: softprops/action-gh-release@v2
97
+ with:
98
+ draft: false
99
+ prerelease: ${{ contains(github.ref_name, '-') }}
100
+ generate_release_notes: true
101
+ files: |
102
+ dist/*
@@ -0,0 +1,81 @@
1
+ name: release-pypi
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ wheels:
9
+ name: wheel ${{ matrix.os }}
10
+ runs-on: ${{ matrix.os }}
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ include:
15
+ # Windows x64 (cp38-abi3 via maturin)
16
+ - os: windows-latest
17
+ maturin-args: --release --features python --interpreter python3.10 python3.11 python3.12
18
+ # Linux x64 manylinux (cp38-abi3 via maturin)
19
+ - os: ubuntu-latest
20
+ maturin-args: --release --features python --interpreter python3.10 python3.11 python3.12
21
+ manylinux: auto
22
+ # macOS universal2 (Intel + Apple Silicon, single universal wheel)
23
+ - os: macos-latest
24
+ maturin-args: --release --features python --target universal2-apple-darwin --interpreter python3.10 python3.11 python3.12
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: dtolnay/rust-toolchain@stable
28
+ with:
29
+ # universal2 needs both Apple toolchains present
30
+ targets: ${{ matrix.os == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
31
+ - uses: Swatinem/rust-cache@v2
32
+ with:
33
+ key: pypi-${{ matrix.os }}
34
+ - name: build wheels
35
+ uses: PyO3/maturin-action@v1
36
+ with:
37
+ command: build
38
+ manylinux: ${{ matrix.manylinux || 'off' }}
39
+ args: ${{ matrix.maturin-args }}
40
+ - uses: actions/upload-artifact@v4
41
+ with:
42
+ name: wheels-${{ matrix.os }}
43
+ path: target/wheels/*
44
+
45
+ sdist:
46
+ name: source distribution
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+ - name: build sdist
51
+ uses: PyO3/maturin-action@v1
52
+ with:
53
+ command: sdist
54
+ - uses: actions/upload-artifact@v4
55
+ with:
56
+ name: wheels-sdist
57
+ path: target/wheels/*
58
+
59
+ publish:
60
+ name: publish to PyPI
61
+ needs: [wheels, sdist]
62
+ runs-on: ubuntu-latest
63
+ environment:
64
+ name: pypi
65
+ url: https://pypi.org/p/sagashield
66
+ permissions:
67
+ # Trusted Publishing (OIDC): no token needed when the PyPI project
68
+ # is configured with this repo as publisher. Falls back to PYPI_API_TOKEN.
69
+ id-token: write
70
+ contents: read
71
+ steps:
72
+ - uses: actions/download-artifact@v4
73
+ with:
74
+ path: dist
75
+ merge-multiple: true
76
+ - name: publish to PyPI (OIDC preferred, token fallback)
77
+ # If PYPI_API_TOKEN is unset, the empty password makes the action
78
+ # fall back to OIDC Trusted Publishing (id-token: write above).
79
+ uses: pypa/gh-action-pypi-publish@release/v1
80
+ with:
81
+ password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,8 @@
1
+ /target
2
+ .venv/
3
+ __pycache__/
4
+ *.pyc
5
+ *.pyd
6
+ *.so
7
+ evals/results/
8
+ dist/
@@ -0,0 +1,94 @@
1
+ # SagaShield Evals — BASELINE vs SAGASHIELD (50 scenarios, seed=42)
2
+
3
+ Reproducible benchmark in the style of SWE-bench / Statewright evals:
4
+ one command, raw data, no hidden steps.
5
+
6
+ ## Reproducibility Guide
7
+
8
+ Prerequisites: Rust 1.75+, a C compiler (for `rusqlite` bundled SQLite).
9
+
10
+ ```bash
11
+ git clone https://github.com/sebastianmechno-sys/sagashield sagashield
12
+ cd sagashield
13
+ cargo run --example run_evals --release
14
+ ```
15
+
16
+ What the command does:
17
+
18
+ 1. Generates the 50-task suite deterministically (`evals/scenarios.rs`,
19
+ xorshift64* with `EVAL_SEED = 42` — no `rand` dependency, same suite on
20
+ every machine).
21
+ 2. Runs every task twice in an isolated temp jail: once as a vanilla ReAct
22
+ agent (direct `std::fs` + in-memory ledger, exceptions caught but never
23
+ compensated), once through `AgentKernel` (Step-0 guard, FSM, SQLite WAL,
24
+ LIFO rollback, idempotency engine).
25
+ 3. Writes `evals/results/raw_eval_data.json` (per-task input, both outcomes,
26
+ timestamps, nanosecond durations) and `evals/results/summary.csv`
27
+ (per-category aggregates), and prints the ASCII table below.
28
+
29
+ ## Threat & Failure Model
30
+
31
+ Formal definitions used for grading (see `examples/run_evals.rs`):
32
+
33
+ - **Success (goal met).**
34
+ - *normal*: every step executed and every expected effect present.
35
+ - *crash*: the injected failure is reported **and** zero residuals remain.
36
+ - *adversarial*: the hostile write is rejected with nothing persisted
37
+ outside policy (rejection *is* the goal).
38
+ - *flaky*: the charge+write pair applied **exactly once** across the retry.
39
+ - **Residual Corruption (`residual`).** After a crash: any pre-crash file
40
+ still on disk, or any pre-crash charge not `REFUNDED` in the ledger.
41
+ - **Security Breach (`breach`).** A hostile payload (traversal escaping the
42
+ jail, `.env`/`credentials`/reserved-device/ADS name) accepted — i.e. not
43
+ rejected — by the agent layer. Baseline never rejects by construction.
44
+ - **Duplicate (`dupl`).** Redundant side-effect applications beyond the first
45
+ full pass (extra charges on retry).
46
+ - **Latency.** Wall time per tool step, p50/p99 in milliseconds. Includes
47
+ SQLite I/O for SagaShield, raw syscalls for baseline.
48
+
49
+ ## The Numbers
50
+
51
+ Measured with `cargo run --example run_evals --release` (Windows 11,
52
+ SQLite bundled, seed=42). Raw data: `evals/results/raw_eval_data.json`.
53
+
54
+ | category | config | tasks | succ | rate% | residual | breach | dupl | p50ms | p99ms |
55
+ |-------------|------------|------:|-----:|-------:|---------:|-------:|-----:|------:|-------:|
56
+ | normal | baseline | 15 | 15 | 100.0 | 0 | 0 | 0 | 0.359 | 0.628 |
57
+ | normal | sagashield | 15 | 15 | 100.0 | 0 | 0 | 0 | 6.953 | 11.508 |
58
+ | crash | baseline | 15 | 0 | 0.0 | 15 | 0 | 0 | 0.002 | 0.655 |
59
+ | crash | sagashield | 15 | 15 | 100.0 | 0 | 0 | 0 | 9.449 | 17.335 |
60
+ | adversarial | baseline | 10 | 0 | 0.0 | 0 | 10 | 0 | 0.346 | 0.909 |
61
+ | adversarial | sagashield | 10 | 10 | 100.0 | 0 | 0 | 0 | 0.155 | 0.198 |
62
+ | flaky | baseline | 10 | 0 | 0.0 | 0 | 0 | 10 | 0.283 | 0.976 |
63
+ | flaky | sagashield | 10 | 10 | 100.0 | 0 | 0 | 0 | 5.304 | 10.478 |
64
+ | TOTAL | baseline | 50 | 15 | 30.0 | 15 | 10 | 10 | 0.332 | 0.909 |
65
+ | TOTAL | sagashield | 50 | 50 | 100.0 | 0 | 0 | 0 | 6.554 | 16.753 |
66
+
67
+ Reading: the vanilla agent scores 30% — perfect on the happy path, zero
68
+ everywhere else (15 dirty sagas, 10 accepted attacks, 10 double charges).
69
+ SagaShield scores 100% with zero residuals, zero breaches, zero duplicates.
70
+ Note the adversarial row: rejecting in 0.16 ms is *faster* than writing,
71
+ because Step-0 blocks before any I/O.
72
+
73
+ ## Overhead Analysis
74
+
75
+ Honest cost accounting (release profile, per tool step):
76
+
77
+ - **SQLite WAL path** (`BEGIN`→`COMMITTED` + FSM transitions): p50 ≈ 6.6 ms
78
+ total vs 0.33 ms baseline — roughly one fsync-dominated SQLite transaction
79
+ per step on this machine. p99 ≈ 16.8 ms includes full LIFO rollbacks
80
+ (compensating writes + deletes), i.e. the p99 *does useful recovery work*
81
+ the baseline never performs.
82
+ - **Path screening** is sub-millisecond: adversarial rejections complete at
83
+ p50 0.155 ms / p99 0.198 ms — lexical normalization plus a handful of
84
+ substring checks, no syscalls on the hot path (canonicalization only
85
+ touches the FS best-effort for existing paths).
86
+ - **Idempotent retries** skip execution entirely (WAL lookup + cached return),
87
+ so the steady-state cost of a retry storm is one indexed `SELECT`, not a
88
+ re-execution.
89
+
90
+ Limitations: single-node SQLite (no distributed consensus — see
91
+ “Best-Effort Guarantees” in `README.md`); latencies measured on one Windows
92
+ 11 host, re-run on your hardware via the command above; the suite uses
93
+ synthetic tools (`fs.write`, `mock.pay`, `crash.tool`), not live LLM
94
+ planners, so planner-quality variance is out of scope by design.
@@ -0,0 +1,65 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased] — v0.2.0-unreleased
9
+
10
+ ### Added
11
+ - Claude Code plugin marketplace manifest (`.claude-plugin/marketplace.json`,
12
+ installable via `/plugin marketplace add`).
13
+ - `examples/otel_export.rs`: in-memory mini-saga (2 OK + 1 crash) printing
14
+ the OpenTelemetry audit document to stdout.
15
+ - `fuzz-smoke` CI job: nightly `cargo-fuzz` build plus time-boxed smoke run
16
+ of the `path_guard` / `net_guard` targets.
17
+
18
+ ## [0.1.0] — 2026-09-08
19
+
20
+ ### Added
21
+ - SQLite WAL Saga engine: `sessions`/`actions` tables, LIFO rollback,
22
+ idempotency keys with `UNIQUE(session_id, idempotency_key)` index,
23
+ `busy_timeout` + WAL mode, dangling-session crash recovery.
24
+ - FSM guardrail: deterministic `Idle → Planning → ExecutingTool →
25
+ Verifying → Completed`, `Compensating → Failed`, default-deny with
26
+ `InvalidStateTransition`.
27
+ - Tool dispatcher: `AgentKernel` unifying Step-0 security, FSM, WAL and
28
+ automatic rollback; `ToolRegistry`, real `FsWriteTool` / `MockPaymentTool`
29
+ / `CrashTool`.
30
+ - Step-0 security sandbox: lexical + symlink-aware path containment,
31
+ blocklist, Windows reserved names, 8.3 short names, ADS blocking,
32
+ trailing-dot/space normalization, domain whitelist with IP-literal
33
+ rejection.
34
+ - MCP server (`sagashield-mcp`, JSON-RPC 2.0 over stdio): `initialize`,
35
+ `tools/list`, `tools/call` (`fs_write`, `mock_pay`, `kernel_status`,
36
+ `agent_kernel_exec` gateway, `kernel_replay_session`,
37
+ `kernel_export_audit`).
38
+ - Deterministic session replay (`SessionReplay`) with formal FSM
39
+ re-validation, and OpenTelemetry audit export (`AuditExporter`).
40
+ - Python bindings (`pip install sagashield`, maturin + PyO3 abi3):
41
+ `SagaKernel`, `SecurityPolicy`, `@transactional_tool`,
42
+ `SecurityViolationError`, LangChain adapter.
43
+ - Eval suite: 50 deterministic scenarios (seed=42) with raw JSON + CSV
44
+ artifacts (`cargo run --example run_evals --release`).
45
+ - Coverage-guided fuzz targets (`fuzz/`: `path_guard`, `net_guard`) plus
46
+ a 1.300+ hostile-input corpus in `security_fuzz_test`.
47
+ - Docs: `README.md`, `SPEC.md`, `SECURITY.md`, `BENCHMARK.md`,
48
+ `CONTRIBUTING.md`, `RELEASING.md`, docs.rs API comments.
49
+
50
+ ### Changed
51
+ - `FsWriteTool` creates missing parent directories (recursive write).
52
+ - Crate re-branded from `agent-kernel` to `sagashield`
53
+ (binary `sagashield-mcp`, Python package `sagashield`).
54
+
55
+ ### Security
56
+ - Documented application-level threat model and TOCTOU assumptions
57
+ (`SECURITY.md`); responsible-disclosure procedure via private advisories.
58
+
59
+ ### Fixed
60
+ - SQLite `CURRENT_TIMESTAMP` defaults (previously inverted `datetime` args
61
+ returned `NULL` and violated `NOT NULL` constraints).
62
+ - `async fn` trait object-safety via `async-trait` for the tool registry.
63
+
64
+ [Unreleased]: https://github.com/sebastianmechno-sys/sagashield/compare/v0.1.0...HEAD
65
+ [0.1.0]: https://github.com/sebastianmechno-sys/sagashield/releases/tag/v0.1.0
@@ -0,0 +1,46 @@
1
+ # Contributing to SagaShield
2
+
3
+ ## Commit convention
4
+
5
+ [Conventional Commits](https://www.conventionalcommits.org/):
6
+
7
+ - `feat:` new user-facing capability (minor bump on `0.x`)
8
+ - `fix:` bug fix with regression test (patch bump)
9
+ - `perf:` measurable performance change (include before/after numbers)
10
+ - `docs:` docs, README, SPEC, comments only
11
+ - `chore:` tooling, CI, deps, refactors with no behavior change
12
+
13
+ Breaking changes: use `feat!:` / `fix!:` and call them out in the PR body;
14
+ they require a CHANGELOG entry and maintainer approval.
15
+
16
+ ## Non-negotiable invariants
17
+
18
+ 1. **Zero `.unwrap()` / zero `.expect()` in library code (`src/`).**
19
+ Every failure is a typed `KernelError`. Tests and examples may use
20
+ `expect` with a message, never bare `unwrap`.
21
+ 2. **Every public type and method documents itself for `docs.rs`.**
22
+ `///` comments must cover parameters, return values, and error cases.
23
+ `cargo doc --no-deps` must stay warning-free.
24
+ 3. **Every feature ships with regression tests.** New behavior without a
25
+ failing-before/passing-after test is not merged. The suite
26
+ (`cargo test`: 32 integration tests + doctests) must stay green.
27
+ 4. **`cargo clippy --all-targets --all-features -- -D warnings` is clean.**
28
+ No exceptions, no `#[allow]` without a linked issue explaining why.
29
+ 5. **Security-sensitive changes** (anything under `src/security/`,
30
+ `src/dispatcher.rs` Step-0, new tool surface) additionally require:
31
+ - hostile-case tests (traversal, blocklist, spoofing variants),
32
+ - a `SECURITY.md` impact note when the threat model changes.
33
+
34
+ ## Workflow
35
+
36
+ ```bash
37
+ cargo fmt --check # or: cargo fmt
38
+ cargo clippy --all-targets --all-features -- -D warnings
39
+ cargo test # 32 integration + doctests
40
+ cargo test --test security_fuzz_test # hostile corpus
41
+ cargo run --example run_evals --release # 0 residual corruption
42
+ ```
43
+
44
+ Python bindings: `maturin develop --features python`, then
45
+ `python tests/python_binding_test.py`. Keep `Cargo.toml` and
46
+ `pyproject.toml` versions in sync (checked at release time).