diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f6d7d45 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +* text=auto eol=lf + +*.gif binary +*.png binary +*.jpg binary +*.jpeg binary +*.webp binary +*.onnx binary +*.sqlite binary +*.sqlite3 binary diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..7046fa8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,41 @@ +version: 2 +updates: + - package-ecosystem: pip + directory: / + schedule: + interval: weekly + day: monday + time: "04:00" + timezone: Etc/UTC + open-pull-requests-limit: 5 + groups: + python-runtime: + dependency-type: production + python-development: + dependency-type: development + + - package-ecosystem: npm + directory: /tools/evidence + schedule: + interval: weekly + day: monday + time: "04:15" + timezone: Etc/UTC + open-pull-requests-limit: 5 + groups: + evidence-renderer: + patterns: + - "*" + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + time: "04:30" + timezone: Etc/UTC + open-pull-requests-limit: 5 + groups: + actions: + patterns: + - "*" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5424554 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,39 @@ +# Changelog + +All notable repository changes are recorded here. UnitSentinel has not +published a tagged release; the entries below describe the current unreleased +portfolio baseline. + +## [Unreleased] + +### Added + +- Exact unit and affine-value semantics backed by a content-addressed registry. +- Closed, bounded computation-graph decoding and tracked Z3 verification. +- Detached positive proof certificates with independent semantic replay. +- Verified unit-annotation repair candidates that are never applied + automatically. +- Training/serving comparison with explicit plans, lineage-drift detection, and + indeterminate outcomes for underconstrained contracts. +- A closed-subset ONNX metadata adapter that validates and lowers static + contracts without executing models. +- Real CLI captures, architecture diagrams, measured bounded-scaling evidence, + result examples, and three reproducible GIF workflows. +- Python 3.11-3.14 tests, a 95% branch-coverage floor, dependency audits, + installed-wheel checks, reproducible evidence replay, and CodeQL. +- Maintainer contribution guidance and private vulnerability reporting. + +### Security + +- Generated evidence candidates remain private (mode 0600) until reviewed and + deliberately published through Git. +- Untrusted documents are subject to closed schemas, canonical encodings, + resource ceilings, and fail-closed result states. +- Evidence pipelines reject secrets, personal data, external resources, and + undeclared files. + +### Release boundary + +No license or tagged release is published until Omar records a licensing +decision. Package metadata version `0.1.0` does not represent a public GitHub +release. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a725e35 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,76 @@ +# Contributing to UnitSentinel + +UnitSentinel is maintained as a fail-closed dimensional verification system. +Changes should keep claims narrow, inputs bounded, and evidence reproducible. + +The repository is intentionally unlicensed while Omar selects and documents a +licensing boundary. Public availability does not grant reuse rights. External +contributions are not currently solicited; open an issue before preparing a +substantial patch or contributing third-party code, models, or datasets. + +## Development setup + +Use a supported Python version (3.11 through 3.14) and install the locked +evidence renderer without lifecycle scripts: + +```bash +python -m venv .venv +. .venv/bin/activate +python -m pip install --no-input -e '.[dev]' +npm --prefix tools/evidence ci --ignore-scripts +``` + +Run the core quality gates: + +```bash +ruff check . +ruff format --check . +python -m mypy +python -m coverage run --branch -m unittest discover -s tests -v +python -m coverage report --show-missing --fail-under=95 +python -m build +python -m pip_audit +npm --prefix tools/evidence audit --audit-level=high +``` + +Run every committed evidence check: + +```bash +PYTHONPATH=src python -B -m tools.evidence.onnx_evidence --check +PYTHONPATH=src python -B -m tools.evidence.generate --check +PYTHONPATH=src python -B -m tools.evidence.distribution_visuals --check +npm --prefix tools/evidence run check +PYTHONPATH=src python -B -m tools.evidence.repair_evidence --check +npm --prefix tools/evidence run check:repair +PYTHONPATH=src python -B -m tools.evidence.comparison_evidence --check +PYTHONPATH=src python -B -m tools.evidence.comparison_visuals --check +npm --prefix tools/evidence run check:comparison +``` + +## Contract and evidence changes + +- Preserve closed decoders, explicit resource limits, deterministic ordering, + exact arithmetic, and fail-closed outcomes. +- Use only fixed synthetic graphs, models, captures, and benchmark fixtures. + Never publish production data, credentials, host paths, or personal details. +- Do not hand-edit generated SVG, PNG, GIF, transcript, JSON evidence, or the + closed manifest. +- Regenerate through the narrow recorder for the affected slice, render from + the recorded sources, inspect the actual assets at original size, and rebuild + the manifest. +- Commit source changes, regenerated output, and provenance records together. +- Label measured snapshots with their environment and avoid generalizing them + into cross-machine performance claims. +- Keep `REPRODUCED`, compatibility, repair, and ONNX-import claims within the + boundaries documented in the README and format specifications. + +## Pull requests + +Keep history linear and give each commit one meaningful responsibility. Describe +the contract effect, adversarial cases, commands run, evidence changed, and any +new nonclaims. Maintainer commits must have Omar Ibrahim as both author and +committer using the repository noreply address. + +Report vulnerabilities through a +[private advisory](https://github.com/omar07ibrahim/units/security/advisories/new), +not a public pull request. diff --git a/MANIFEST.in b/MANIFEST.in index a2e49c6..12143bb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,10 +1,15 @@ global-exclude * +include .gitattributes +include .github/dependabot.yml include .github/workflows/ci.yml include .gitignore include .python-version +include CHANGELOG.md +include CONTRIBUTING.md include MANIFEST.in include README.md +include SECURITY.md include pyproject.toml include requirements-distribution.txt diff --git a/README.md b/README.md index caad9bb..0d8e9ad 100644 --- a/README.md +++ b/README.md @@ -711,7 +711,7 @@ The timing snapshot changes only through the explicit ## Local quality gates -The current suite contains 493 unit, integration, +The current suite contains 494 unit, integration, adversarial, release, and evidence tests. CI publishes the exact branch-aware coverage table and enforces a 95% combined floor on every supported Python minor. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..cae0914 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,43 @@ +# Security policy + +## Supported code + +UnitSentinel has no published release yet. Security fixes target the current +`main` branch only. + +| Version | Supported | +| --- | --- | +| `main` | Yes | +| Tagged releases | None published | + +## Report a vulnerability privately + +Use [GitHub private vulnerability reporting](https://github.com/omar07ibrahim/units/security/advisories/new). +Do not disclose a suspected vulnerability in a public issue, pull request, +terminal capture, generated diagram, or evidence record. + +Include the affected revision, the smallest synthetic graph or model that +reproduces the problem, expected and observed behavior, and the security impact. +Do not attach proprietary computation graphs, production ONNX models, training +data, credentials, host paths, or personal information. + +## Security boundaries + +Graphs, certificates, comparison plans, result documents, and ONNX files are +untrusted inputs. Decoders enforce closed schemas, byte and node limits, +canonical forms, bounded solver work, and fail-closed outcomes. The ONNX adapter +checks a closed static metadata subset and never executes a model; parsing an +untrusted format is still part of the attack surface. + +A reproduced certificate proves current semantic reproduction under the +recorded registry and limits. It does not authenticate an issuer or establish +artifact provenance. Evidence and benchmark fixtures are synthetic and must +remain free of secrets and personal data. + +## Disclosure process + +Reports are triaged through the private advisory. A coordinated fix should add +an adversarial regression test, update affected contracts or evidence, pass the +complete Python/distribution/evidence matrix, and be disclosed only after the +fix is available. Licensing decisions are tracked separately and are not +security reports. diff --git a/tests/test_evidence.py b/tests/test_evidence.py index 95b231d..ad11400 100644 --- a/tests/test_evidence.py +++ b/tests/test_evidence.py @@ -493,6 +493,16 @@ def test_evidence_reader_rejects_fifo_without_blocking(self) -> None: self.assertEqual(completed.stdout, b"") self.assertEqual(completed.stderr, b"") + def test_atomic_evidence_write_stays_private_until_review(self) -> None: + with tempfile.TemporaryDirectory(dir=ROOT) as directory: + target = Path(directory) / "candidate.txt" + + evidence_generate._atomic_write(target, b"synthetic evidence\n") + + self.assertEqual(target.read_bytes(), b"synthetic evidence\n") + self.assertEqual(stat.S_IMODE(target.stat().st_mode), 0o600) + self.assertEqual(list(target.parent.glob(".candidate.txt.*.tmp")), []) + def test_renderer_binary_override_is_explicit_and_bounded(self) -> None: with tempfile.TemporaryDirectory(dir=ROOT) as directory: executable = Path(directory) / "node" diff --git a/tools/evidence/generate.py b/tools/evidence/generate.py index 98026c4..7b45fa3 100644 --- a/tools/evidence/generate.py +++ b/tools/evidence/generate.py @@ -198,7 +198,7 @@ def _atomic_write(path: Path, payload: bytes) -> None: with suppress(OSError): os.close(descriptor) try: - os.chmod(temporary, 0o644) + # Keep generated bytes private until review and Git publication. os.replace(temporary, path) except BaseException: with suppress(OSError):