Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e182e6c
chore(release): 0.22.0 — SDK via-edge + WAL durability + ADR-068 wire
maltsev-dev Oct 9, 2026
1dd7bbd
fix(transport): stop losing the WAL on the recovery path
maltsev-dev Oct 3, 2026
a739334
fix(transport): classify 4xx before quarantining, and stop the breake…
maltsev-dev Oct 3, 2026
9b2f0cb
fix(transport): settle a 200 per item — a 2xx is not delivery
maltsev-dev Oct 3, 2026
dccd5b0
feat(wal): make the WAL's guarantees explicit, private, and observable
maltsev-dev Oct 3, 2026
719d1af
fix(transport): four ways a refused event lost its evidence
maltsev-dev Oct 3, 2026
fe5b527
ci: reject a Co-Authored-By trailer on this branch
maltsev-dev Oct 3, 2026
7dc3e1a
fix(sdk): a held refusal must survive kill -9, and a long Retry-After
maltsev-dev Oct 3, 2026
0958838
fix(track): a recorded CONSUME_OVERBUDGET is delivered, not quarantined
maltsev-dev Oct 4, 2026
4a10e1b
fix(track): read the 422 overage fields the backend actually sends
maltsev-dev Oct 4, 2026
d5aa28a
fix(gate): stop sending {"kind":"none"} -- the gate has no such variant
maltsev-dev Oct 5, 2026
fc7c049
fix(gate): map all 12 unmapped backend codes, drop the dead throttle arm
maltsev-dev Oct 7, 2026
bc03e51
feat(track): send `provider`, or the backend cannot price the cached …
maltsev-dev Oct 8, 2026
8d5deae
feat(edge): SDK via-edge — the box is the gate, the cloud is not
maltsev-dev Oct 8, 2026
86378bc
test(edge): the SDK against a live box, no mocks
maltsev-dev Oct 8, 2026
1e72d91
test(edge): let the WAL gate run on a single cpu
maltsev-dev Oct 8, 2026
acda85c
fix(edge): the zero-remainder assertion tested zero with `or`
maltsev-dev Oct 8, 2026
73b352e
feat(track): stop putting a price on the wire — send tokens only
maltsev-dev Oct 9, 2026
3b96c90
ci: ship the commit-msg hook the trailer check assumes
maltsev-dev Oct 9, 2026
af123e2
fix(edge): type the env helpers as Mapping, not dict
maltsev-dev Oct 9, 2026
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
53 changes: 53 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,59 @@ on:
branches: [main, master]

jobs:
commit-message:
# The commits in this repository are authored solely by the repo owner.
# That rule is in CLAUDE.md and was still broken twice, so it is checked
# here rather than trusted.
#
# Scope is `origin/master..HEAD`, not `--all`. A whole-history grep counts
# trailers on the 200+ commits that are already published and are nobody's
# to rewrite; the meaningful question is only whether THIS branch adds
# one, and that is the number worth a red build.
#
# This checks the trailer form — a `Co-Authored-By:` line at the start of
# a line. A commit body that discusses the rule, or a CHANGELOG entry
# that credits a person, is not a violation and must not fail the build.
#
# A `commit-msg` hook is the stronger place for this (it fails before the
# commit exists rather than after), and one is configured in
# `.pre-commit-config.yaml`. This step is the backstop for the case the
# hook cannot cover: a contributor with no pre-commit installed, or a
# `--no-commit` push. Two mechanisms fail differently, which is the
# point — one rule enforced one way is a rule enforced once.
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
with:
# Full history, because the range check needs the merge base with
# origin/master, and `fetch-depth: 0` is what makes that base
# exist. A shallow clone has no origin/master to diff against and
# the range would silently be empty — a check that always passes
# is worse than no check, because it reads as enforcement.
fetch-depth: 0
- name: Reject a Co-Authored-By trailer on this branch
run: |
set -euo pipefail
git fetch --no-tags --depth=1 origin master || true
range=origin/master..HEAD
if ! git rev-parse --verify origin/master >/dev/null 2>&1; then
echo "::warning::origin/master unavailable; trailer check skipped"
exit 0
fi
offenders="$(
git log "$range" --format=%B \
| grep -inE '^[[:space:]]*co-authored-by[[:space:]]*:' || true
)"
if [ -n "$offenders" ]; then
echo "::error::Co-Authored-By trailer found in $range"
echo "$offenders"
echo "Remove the trailer and amend. See CLAUDE.md."
exit 1
fi
echo "no Co-Authored-By trailer in $range"

test:
runs-on: ubuntu-latest
permissions:
Expand Down
14 changes: 13 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,16 @@ repos:
rev: v1.8.0
hooks:
- id: mypy
additional_dependencies: [types-all]
additional_dependencies: [types-all]

# Project rule, not a third-party check. Lives here rather than in
# .git/hooks so it is versioned, shared, and runs in CI — a hook installed
# only on one machine is the same convention that already failed twice.
- repo: local
hooks:
- id: reject-coauthored-trailer
name: reject Co-Authored-By trailer
entry: scripts/reject_coauthor_trailer.sh
language: script
stages: [commit-msg]
always_run: true
327 changes: 327 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,8 +422,13 @@ What remains is specific, and it is not "use https". It is: **an on-path respond

So the honest boundary is: server-authoritative enforcement is authoritative against a *client* and against a *network observer*, not against an attacker who terminates TLS inside your trust store. If you operate on such a network, exclude `api.nullrun.io` from interception and check that it stays excluded — that is an operational control, not something the SDK can do for you.

### What does fail open
### What a long outage costs

An execution is bound server-side for **24 hours**. Reservations expire on a TTL too. An outage longer than that does not resume when the backend returns: every event buffered during it is refused with `EXECUTION_NOT_BOUND`, and the backend cannot re-mint the binding, because that would produce a *new* execution and double-count against the budget. The SDK parks those events in the dead-letter queue on the first refusal rather than retrying, which is correct — retrying is not slow there, it is pointless.

The part that costs money: the spend is real and the period-bound counter did not account for it. **The control center under-reports what you spent by exactly the amount buffered during the outage, and the DLQ is the record of the gap.** If your agents can run unattended for more than a day against an unreachable backend, budget on the assumption that it is spent and unaccounted, not pending. Full details, including how to inspect and replay the DLQ: [docs/wal-and-dlq.md](docs/wal-and-dlq.md).

### What does fail open
Fail-open here is narrow and deliberate, and the authoritative table lives in `runtime.py` (ADR-008). In short: a **transport** failure on the check path is open, so an unreachable backend cannot freeze your agent; a **wire response that names an enforcement failure** is closed, because the backend made a decision and the SDK will not overrule it; a **body that is not a verdict at all** — no `decision`, no `decision_source`, or an unrecognised value in either — is closed, because something answered and what it said was not a decision, and reading that as permission would let a non-NullRun responder authorise a call no policy engine evaluated; a **401** is closed, because no retry fixes a revoked key; and the `/execute` path is closed by default (`FallbackMode.STRICT`).

---
Expand Down
Loading
Loading