pushkin 0.1.1

Schema-first enforcement harness that gates AI coding agents' file writes against project contracts
# pushkin

Schema-first enforcement harness that gates AI coding agents' file writes
against project contracts, deterministically. An agent write that violates a
contract, touches protected surface, or edits a committed test suite is
denied at the hook — with the reason, the fix hint, and an auditable event
trail. Humans stay un-gated where it matters; everything fails open loudly,
never silently.

Spec: `DESIGN-FINDINGS.md` · builder conduct: `AGENT-INSTRUCTIONS.md` ·
progress: `PHASE-LOG.md` · agent integration: `PUSHKIN-AGENT-INTEGRATION.md`.

## Install

```sh
cargo install pushkin
```

Or from source:

```sh
cargo install --path crates/pushkin-cli
```

Requires the binary on `PATH` (hooks resolve `pushkin` from `PATH` by
design — nothing bakes an absolute path). `pushkin doctor` names it if
resolution would fail.

## Quick start

```sh
cd your-repo                  # carrying a pushkin.toml manifest
pushkin init                  # Claude Code hooks + versioned consent
pushkin init --agent git     # native pre-commit shim (no other tools needed)
pushkin doctor                # verify the install; --repair fixes what it owns
```

Adapters: `--agent claude | codex | auggie | hermes | opencode`, plus two
pre-commit floors and a managed instructions block (`--agent agents-md`).
`--remove-agent <name>` uninstalls only what pushkin owns.

## The pre-commit floors

Two interchangeable floors run `pushkin check --staged --json` before every
commit; both carry the same guard: **fail open only on positively probed
absence** (no binary, or no `pushkin.toml`), with a loud actionable notice —
a teammate who never installed pushkin is not blocked by a hook they never
opted into, and a check that RUNS is never second-guessed.

- `pushkin init --agent lefthook` — merges a marker-bracketed block into
  `lefthook.yml`, preserving every foreign command byte-for-byte. Any prior
  pushkin marker generation upgrades in place; uninstall leaves no litter.
- `pushkin init --agent git` — writes `.git/hooks/pre-commit` directly, no
  hook-manager dependency. Detects `core.hooksPath` overrides and foreign
  hooks and prints the integration snippet instead of clobbering.

## Team enforcement: CI is the backstop

The hooks above bind only machines that opted in. A teammate who never
installed pushkin (or lefthook) commits ungated — the fail-open floor is
deliberate, so local hooks alone cannot enforce the gate team-wide. The
backstop is CI: run the gate as a required status check with the
`pushkin-gate` composite action this repo ships
(`.github/actions/pushkin-gate`), and unprovisioned pushes are caught at
the pull request instead of landing silently.

```yaml
name: pushkin-gate
on: [pull_request]
jobs:
  gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0            # affected mode needs history (F19)
      - uses: acoletti/pushkin/.github/actions/pushkin-gate@<TAG-OR-SHA>
        with:
          mode: affected            # or `check` for the full repo
          base-ref: origin/main
          pushkin-ref: <TAG-OR-SHA> # pin; a moving ref is non-reproducible
```

`affected` gates only the diff against `base-ref`; `check` sweeps every
mapped file. Optional `db-drift` / `db-rls` inputs add the schema gates
(see the action's own docs for their prerequisites). Make the job a
required check on the default branch and local provisioning becomes a
latency optimization, not the enforcement boundary.

## What the gate enforces

- **Contract conformance** at mapped boundaries (`contract.boundary.*`).
- **Protected paths** (`pushkin.protected_path`): the manifest, schemas,
  CI, the floors — agent-denied at write time, unwaivable by construction.
  Staged protected changes draw a loud advisory at the floor, never a block
  (the floor runs for humans too).
- **Committed tests are read-only** (`pushkin.read_only_path`): files in
  git HEAD under `read_only_paths` globs deny agent edits; NEW test files
  and uncommitted iteration flow freely. Unwaivable.
- **Suppression comments** (`pushkin.suppression.new`) per the manifest.
- **Waivers** (`pushkin waive`): scoped, expiring, decision-logged — for
  rules that allow them; doctor lints stale grants.

## The schema-only tier (no JS runtime)

Gate-time enforcement is pure Rust. A repo that commits its canonical
schemas needs **no bun, node, or JS toolchain** to be gated: install the
binary, run `pushkin init`, done. Bun is a *compile-time* tool only —
`pushkin compile` regenerates canonical schemas and bindings from Zod
sources, and repos that author schemas directly (or vendor them) never
need it. If you never run `compile`, you never need a JS runtime.

## Doctor

`pushkin doctor` verifies every installed surface (adapters, both floors,
binding epochs) and exits 1 on findings; `--repair` regenerates only what
pushkin owns — foreign configs are reported, never rewritten, and
advisory findings (like an unresolvable binary) never trigger a rewrite.
Repair exit code: 0 when every repair that could run succeeded; deferral
messages name the manual remedy without failing the run.

## Release status

`pushkin` is published on crates.io — `cargo install pushkin` installs the
latest release. All four crates version in lockstep with exact internal pins
(`=0.1.0`), and `Cargo.lock` is a release artifact: `cargo install --locked`
reproduces the exact dependency graph a release was tested against.

Published versions are immutable. A correction to shipped content — including
this README, which renders on the crate page from the published tarball —
requires a new patch release; it cannot be fixed by editing `develop`.

Versioning policy:

- **Patch** (`0.1.x`) — fixes that do not change emitted output: docs, CI, the
  gate action, internal refactors.
- **Minor** (`0.x.0`) — new schema capability or changed emitter output. Any
  change under `crates/pushkin-compiler/src/**` is epoch-sensitive and bumps
  `schema_epoch` in `pushkin.toml`, so it is at least a minor.
- **Tags** — every published version carries an annotated `vX.Y.Z` tag on the
  exact commit whose tree was published.

## Phase 0 reference implementation

The original Bun/TS spike (Claude Code gate) still lives in `src/` and
`tests/`: `make test && make lint`. The Rust workspace is the product;
`make floor` runs its full mechanical floor (fmt, clippy, tests, deny).