Metadata-Version: 2.4
Name: nlock
Version: 0.1.0
Summary: A tiny, zero-dependency gate: enforce that a step happened before an action, with a tamper-evident receipt.
Author: James Ray Hawkins
License: MIT License
        
        Copyright (c) 2026 James Ray Hawkins
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/Jayhawk314/nlock
Project-URL: Repository, https://github.com/Jayhawk314/nlock
Keywords: gate,precondition,audit,compliance,tamper-evident,workflow,guardrail
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Security
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# nlock

**Enforce that a step happened before an action — with tamper-evident proof.**

A tiny, **zero-dependency** gate. Declare that an action requires certain steps to
be completed first; record steps as they happen; then gate the action:

- **AUTHORIZE** — every required step recorded and passing
- **HOLD** — a required step hasn't happened yet
- **BLOCK** — a required step was recorded as FAILED

Every record and decision is sealed into an append-only, hash-chained ledger, so
the audit trail is tamper-evident. Pure Python stdlib — no numpy, no models, no
network. **Microseconds per check.**

```python
from nlock import Nlock

gate = Nlock("audit.jsonl")
gate.require("deploy_to_prod", ["tests_passed", "change_reviewed"])

gate.record("tests_passed", evidence="CI #4823: 312 passed")
gate.check("deploy_to_prod")            # HOLD — awaiting: change_reviewed

gate.record("change_reviewed", evidence="CR-114 approved by alice")
gate.check("deploy_to_prod")            # AUTHORIZE

gate.verify()                           # True — the ledger has not been altered
```

## Why

Some actions must not happen until a prerequisite step is *demonstrably* done —
and, in regulated or high-consequence settings, until you can *prove* it wasn't
skipped. "If it isn't documented, it didn't happen." nlock is that control, in
~120 lines you can read, with no infrastructure to stand up.

## Install / vendor

Zero dependencies, so you can `pip install .` or simply copy the `nlock/`
package folder into your project. Python 3.10+.

```
pip install .          # from this directory
python examples/quickstart.py
python -m pytest -q
```

## API

| Call | What it does |
|---|---|
| `Nlock(ledger_path=None)` | Create a gate. With a path, the ledger persists and rehydrates across processes; without, it's in-memory. |
| `.require(action, steps)` | Declare the prerequisite steps for an action (config; re-declare each run). |
| `.record(step, ok=True, evidence=None)` | Record that a step happened. `ok=False` marks it FAILED. Evidence is hashed into the ledger, not stored. |
| `.check(action) -> Decision` | Gate the action → `Decision(outcome, missing, failed, reason)`. Sealed into the ledger. |
| `.guard(action, requires=, mode=, on_blocked=)` | Decorator to gate an existing function. `mode="observe"` logs but never blocks (safe rollout); `mode="enforce"` blocks. |
| `.progress(action) -> dict` | `{done, remaining, failed}` — render a checklist instead of a bare "denied". |
| `.verify() -> bool` | Re-derive the hash chain; `False` if any entry was altered. |
| `.ledger` | The sealed entries (for export to an auditor). |

**Adopting it without over-gating:** see [ADOPTION.md](ADOPTION.md) — the 4
questions for what to gate, the observe→enforce rollout, and turning a HOLD into a
next step instead of a wall.

## Not this

nlock enforces *that a step was recorded*, not that the step was done
correctly — it certifies procedure, not truth. It's a deterministic control, not
a judge. That's the point: it can't be talked out of a required step, and its
verdict is a receipt you can open.
