Metadata-Version: 2.4
Name: ndaguard
Version: 0.1.0
Summary: Descriptor-driven NDA metadata, Git protection, and safe bundle filtering
Author: Ellie Hartung
License-Expression: MIT
Keywords: nda,confidentiality,git,metadata,bundles
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: history
Requires-Dist: git-filter-repo>=2.47; extra == "history"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: build>=1; extra == "dev"
Requires-Dist: twine>=6; extra == "dev"
Requires-Dist: ruff>=0.9; extra == "dev"
Dynamic: license-file

# NDA Guard

Python 3.11+ library and CLI for NDA metadata, Git protection, and public bundles.
Works with any project on Linux and Windows. The core has no Python dependencies;
Git integration requires the `git` executable on `PATH`.

## Install

Install from PyPI after the first release:

```sh
python -m pip install ndaguard
```

Run commands from the project you want to protect, or pass `--root PATH`:

```sh
python -m ndaguard init vendor-private --agreement NDA-001 --owner hardware-team
python -m ndaguard validate
python -m ndaguard scan --nda-only
python -m ndaguard explain vendor-private/design.pdf
```

Existing documents need descriptors before publication checks can protect them.
`init DIRECTORY` protects that entire directory, including future descendants.
Without a directory, `init` protects the whole policy root. It never replaces an
existing descriptor. Global options precede commands:
`ndaguard --root PATH --json scan --nda-only`.

## Descriptors

Place `.nda.toml` or `.nda.json` in relevant directories. Patterns use `/` and
are relative to the descriptor's directory. Both formats can coexist.

```toml
version = 1

[[rules]]
id = "vendor-material"
include = ["private/**", "designs/**/confidential-*.pdf"]
exclude = ["private/public-release/**"]
classification = "restricted"
agreement = "NDA-001"
owner = "hardware-team"
description = "Vendor documents"
tags = ["vendor", "hardware"]
audiences = ["approved-engineers"]
purposes = ["internal-design"]
expires = "2027-12-31"

[rules.metadata]
review_ticket = "LEGAL-123"
```

To protect an entire directory, its descriptor can simply contain:

```json
{
  "version": 1,
  "defaults": {
    "classification": "restricted",
    "agreement": "NDA-001",
    "owner": "hardware-team"
  }
}
```

- The strongest classification wins: `restricted > confidential > public`.
  Confidential and restricted content is always blocked from Git and bundles.
- Parent restrictions cannot be overridden by children. `defaults` applies to
  the whole directory and supplies omitted rule metadata. Use explicit rules
  with exclusions when some descendants should remain public.
- Exclusions affect only their own rule. Expiry is advisory and never releases
  content automatically. Audiences and purposes describe policy, not export permissions.
- `*.pdf` matches directly in scope; `**/*.pdf` matches any depth; `private/`
  and `private/**` cover descendants. `*`, `?`, and character classes are supported.
- Rule classification defaults to `restricted`, and include defaults to `["**"]`.
  Public rules can independently set `deny_git` or `deny_bundle` to `true`.
- Unknown fields, malformed descriptors, unsafe patterns, and traversal fail
  closed. Custom metadata must be JSON-serializable.

Unmatched files are public by default. Use `--unclassified restricted` for an
allowlist, then explicitly classify approved paths as public. Installed hooks
retain this option. The library accepts `Catalog(root, unclassified="restricted")`.

## Library and bundles

```python
from ndaguard import Catalog, export_zip
from ndaguard.git import GitRepository

catalog = Catalog("/path/to/project")
info = catalog.inspect("vendor-private/design.pdf")
print(info.classification, info.agreements, info.tags, info.descriptors)
metadata = info.to_dict()  # Includes applicable rules, owners, expiry, and custom metadata.
safe_inputs = catalog.filter_paths(["src/main.py", "vendor-private/design.pdf"])
inventory = catalog.scan(nda_only=True, agreement="NDA-001")
plan = catalog.bundle_plan(safe_inputs)
export_zip(catalog, "/path/outside/project/public.zip", paths=safe_inputs)
violations = GitRepository(catalog.root, catalog=catalog).check_staged()
```

`Catalog` is a snapshot; call `refresh()` after descriptor edits. `inspect_blob`
evaluates virtual/Git content with `content=bytes` or a trusted `sha256=` digest.
`can_include(path, purpose="git" | "bundle")` checks a single path.

```powershell
python -m ndaguard --json bundle ../public.zip          # plan
python -m ndaguard bundle ../public.zip --apply         # export
```

ZIP exports exclude restricted files, descriptors, links/junctions, and generated
directories. Output must be outside the policy root; replacing output requires
`--overwrite`. Exports are reproducible, recheck policy and exact bytes, retain
initial restrictions, and publish atomically. Use a stable source tree; this is
not a sandbox against malicious concurrent changes. Internal plans can expose
restricted filenames and metadata.

Discovery/bundles skip `.git`, `.hg`, `.svn`, `.venv`, `venv`, `node_modules`,
`__pycache__`, `.pytest_cache`, `.mypy_cache`, `.ruff_cache`, `build`, and `dist`.
Keep policies outside those directories. Git checks still inspect all tracked
descriptors. Windows matching is case-insensitive by default; `case_sensitive=`
can override it.

## Copies and fingerprints

```powershell
python -m ndaguard fingerprint vendor-private/design.pdf
python -m ndaguard fingerprint vendor-private/design.pdf --staged
```

Add the resulting digest to a rule's `sha256 = ["64-character-hex-digest"]`.
Use `include = []` for fingerprint-only rules. Fingerprints match identical bytes
anywhere in the tree, regardless of descriptor scope or exclusions. Path and
fingerprint matches combine with OR semantics. Git also tracks restricted blob
identities to detect unchanged copies/renames without explicit fingerprints.
For normalized text, record working-tree and staged hashes or disable Git text
normalization. Edited/re-encoded/embedded copies need additional rules or hashes.

## Git protection and purge

Run from the full root of an existing, non-bare Git repository:

```powershell
git add -f vendor-private/.nda.toml
python -m ndaguard git install-hooks
python -m ndaguard git untrack                         # plan
python -m ndaguard git untrack --apply                 # keep local files
python -m ndaguard check --staged
python -m ndaguard check --history
```

Pre-commit checks the entire prospective commit against working, staged, and
HEAD policies using index bytes. Pre-push checks every reachable tree of pushed
tips, including deleted files and historical descriptors. Historical restrictions
combine conservatively; deleting a descriptor does not release old content.
Shallow history, unmerged index entries, symlinks/submodules, and standalone
blob/tree refs are refused.

Hooks respect `core.hooksPath`, refuse to overwrite foreign hooks, and refresh
local ignore rules on checkout/merge. `git sync-excludes` manually refreshes the
managed `.git/info/exclude` block. Ignore rules conservatively omit rule exceptions;
the checks apply exact policy. Descriptors are allowed in Git to distribute policy,
so keep confidential content out of them. Force-add descriptors inside ignored
directories. `git uninstall-hooks` removes owned hooks but leaves ignore rules.
Reinstall hooks after replacing/moving their Python environment.

Untracking preserves local documents but does not remove old commits. To purge:

```powershell
python -m pip install 'ndaguard[history]'
python -m ndaguard --json git purge
# Execute in a clean, disposable fresh clone. For local cloning use --no-local.
python -m ndaguard git purge --apply --backup ../sensitive-original.bundle
```

Purge removes every version of selected paths using `git-filter-repo`, including
unchanged copies. It requires an external, nonexistent backup path, full history,
a clean index/worktree, and a single attached worktree. The fresh-clone safeguard
stays enabled. Local regular documents are preserved; links cannot be preserved.
Purge appends a persistent path/fingerprint deny list to root `.nda.json`; review
and commit it so other clones inherit protection. Collected agreement IDs are
associated conservatively with that deny list. Existing descriptor rules remain.

The backup contains original NDA content. Rewriting changes commit IDs and may
invalidate signatures; `git-filter-repo` normally removes `origin`. No automatic
push occurs. Remote copies, old clones, caches, and hosting-provider retention
require separate handling. See [git-filter-repo's documentation](https://github.com/newren/git-filter-repo/blob/main/Documentation/git-filter-repo.txt).

Local hooks are bypassable, so require `python -m ndaguard check --history` in
release/merge CI after fetching full history. CI can install a pinned release
with `python -m pip install ndaguard==0.1.0`. For hostile contributions, use a trusted checker and policy
outside the proposed changes. CI cannot retract content already uploaded.
See [Git hooks](https://git-scm.com/docs/githooks) and [ignore rules](https://git-scm.com/docs/gitignore).

All commands support global `--json`. Exit codes: `0` success, `1` Git policy
violation, `2` invalid policy/path or operational failure. Use `--help` for
inventory filters and remaining options.

## Development

Clone or open this standalone project, then create a virtual environment:

```sh
python -m venv .venv
```

Activate it on Linux with `source .venv/bin/activate`, or in PowerShell with
`.\.venv\Scripts\Activate.ps1`. On systems where Python is named `python3`, use
`python3 -m venv .venv`; the activated environment provides `python`.

```powershell
python -m pip install -e '.[dev,history]'
python -m ruff check .
python -m ruff format --check .
python -m pytest -q
python -m build
python -m twine check dist/*
```

Source lives in `src/ndaguard/`. Tests run against the installed package, and use
synthetic content and disposable repositories, including real hooks, history
rewriting, and backup recovery. GitHub Actions runs the suite on Ubuntu and Windows
with Python 3.11 through 3.14, and validates the release distributions.

## Publishing

The package is prepared for PyPI but must be uploaded before `pip install ndaguard`
works. The release version has one source: `src/ndaguard/_version.py`. Update it
and `CHANGELOG.md` for subsequent releases; use a new version for every upload.

From a clean checkout, install `.[dev,history]`, run the checks above, and build
into an empty `dist/` directory. The build creates a source archive and a universal
Python wheel, including the MIT license and `py.typed` marker.

First upload to TestPyPI:

```sh
python -m twine upload --repository testpypi dist/*
```

Check installation in a separate virtual environment:

```sh
python -m pip install --index-url https://test.pypi.org/simple/ --no-deps ndaguard==0.1.0
python -m ndaguard --version
ndaguard --help
```

Then upload the same artifacts to PyPI:

```sh
python -m twine upload dist/*
```

Use your PyPI account's API token when Twine prompts for credentials (username
`__token__`). TestPyPI and PyPI have separate accounts and tokens. Check that you
can register the `ndaguard` name before the first release. See the
[Python Packaging User Guide](https://packaging.python.org/en/latest/tutorials/packaging-projects/)
for account setup and upload details.
