sanning-proof 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. sanning_proof-0.4.0/.github/workflows/ci.yml +73 -0
  2. sanning_proof-0.4.0/.github/workflows/release-ts.yml +71 -0
  3. sanning_proof-0.4.0/.github/workflows/release.yml +45 -0
  4. sanning_proof-0.4.0/.gitignore +11 -0
  5. sanning_proof-0.4.0/CHANGELOG.md +108 -0
  6. sanning_proof-0.4.0/CLAUDE.md +116 -0
  7. sanning_proof-0.4.0/LICENSE +21 -0
  8. sanning_proof-0.4.0/PKG-INFO +202 -0
  9. sanning_proof-0.4.0/README.md +154 -0
  10. sanning_proof-0.4.0/cross-kernel/.gitignore +5 -0
  11. sanning_proof-0.4.0/cross-kernel/PIN +8 -0
  12. sanning_proof-0.4.0/cross-kernel/README.md +108 -0
  13. sanning_proof-0.4.0/cross-kernel/accept-set.json +133 -0
  14. sanning_proof-0.4.0/cross-kernel/generate_cases.py +352 -0
  15. sanning_proof-0.4.0/cross-kernel/generate_export_cases.py +98 -0
  16. sanning_proof-0.4.0/cross-kernel/go-verifier/accept_set.go +117 -0
  17. sanning_proof-0.4.0/cross-kernel/go-verifier/go.mod +13 -0
  18. sanning_proof-0.4.0/cross-kernel/go-verifier/go.sum +12 -0
  19. sanning_proof-0.4.0/cross-kernel/go-verifier/main.go +140 -0
  20. sanning_proof-0.4.0/cross-kernel/run.sh +43 -0
  21. sanning_proof-0.4.0/cross-kernel/run_export.sh +33 -0
  22. sanning_proof-0.4.0/cross-kernel/ts_export_leg.mjs +93 -0
  23. sanning_proof-0.4.0/cross-kernel/ts_leg.mjs +140 -0
  24. sanning_proof-0.4.0/cross-kernel/vendor-agent/go.mod +8 -0
  25. sanning_proof-0.4.0/cross-kernel/vendor-agent/pkg/proof/LICENSE +27 -0
  26. sanning_proof-0.4.0/cross-kernel/vendor-agent/pkg/proof/VENDORING.md +25 -0
  27. sanning_proof-0.4.0/cross-kernel/vendor-agent/pkg/proof/proof.go +398 -0
  28. sanning_proof-0.4.0/pyproject.toml +48 -0
  29. sanning_proof-0.4.0/specs/architecture.md +177 -0
  30. sanning_proof-0.4.0/specs/envelope-spec.md +358 -0
  31. sanning_proof-0.4.0/specs/evidence-bundle.md +215 -0
  32. sanning_proof-0.4.0/specs/evidence-export.md +498 -0
  33. sanning_proof-0.4.0/specs/governance.md +66 -0
  34. sanning_proof-0.4.0/src/sanning_proof/__init__.py +96 -0
  35. sanning_proof-0.4.0/src/sanning_proof/bundle.py +141 -0
  36. sanning_proof-0.4.0/src/sanning_proof/canonicalize.py +97 -0
  37. sanning_proof-0.4.0/src/sanning_proof/envelope.py +325 -0
  38. sanning_proof-0.4.0/src/sanning_proof/evidence.py +1269 -0
  39. sanning_proof-0.4.0/src/sanning_proof/hash.py +15 -0
  40. sanning_proof-0.4.0/src/sanning_proof/merkle.py +127 -0
  41. sanning_proof-0.4.0/src/sanning_proof/rsa_pss.py +202 -0
  42. sanning_proof-0.4.0/src/sanning_proof/sign.py +36 -0
  43. sanning_proof-0.4.0/src/sanning_proof/verify.py +24 -0
  44. sanning_proof-0.4.0/test-vectors/CORPUS-v2.md +124 -0
  45. sanning_proof-0.4.0/test-vectors/README.md +65 -0
  46. sanning_proof-0.4.0/test-vectors/VENDORING.md +50 -0
  47. sanning_proof-0.4.0/test-vectors/envelope-asset-missing-01.json +45 -0
  48. sanning_proof-0.4.0/test-vectors/envelope-asset-registered-01.json +41 -0
  49. sanning_proof-0.4.0/test-vectors/envelope-key-retired-01.json +37 -0
  50. sanning_proof-0.4.0/test-vectors/envelope-policy-changed-01.json +40 -0
  51. sanning_proof-0.4.0/test-vectors/envelope-tamper-detected-01.json +48 -0
  52. sanning_proof-0.4.0/test-vectors/envelope-verification-checkpoint-01.json +50 -0
  53. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-bundle.golden.json +332 -0
  54. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-malformed-rsa-key.json +278 -0
  55. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-malformed-signature-alg.json +278 -0
  56. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-positive-01.json +300 -0
  57. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-source-bundle-ref.json +174 -0
  58. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-attestation-signature.json +300 -0
  59. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-body-hash.json +278 -0
  60. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-data-hash-binding.json +300 -0
  61. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-disclosed-content.json +300 -0
  62. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-mis-salt.json +300 -0
  63. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-operator-binding.json +296 -0
  64. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-source-linkage.json +300 -0
  65. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-subject-ref.json +300 -0
  66. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-verdict-disagreement.json +300 -0
  67. sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-wrapper-signature.json +278 -0
  68. sanning_proof-0.4.0/test-vectors/evidence-export/negatives/attestation-exponent-forgery.json +267 -0
  69. sanning_proof-0.4.0/test-vectors/evidence-export/negatives/deep-nesting.json +415 -0
  70. sanning_proof-0.4.0/test-vectors/evidence-export/rsa-pss-attestation.golden.json +26 -0
  71. sanning_proof-0.4.0/test-vectors/merkle-tree-00-leaves.json +8 -0
  72. sanning_proof-0.4.0/test-vectors/merkle-tree-01-leaves.json +31 -0
  73. sanning_proof-0.4.0/test-vectors/merkle-tree-02-leaves.json +56 -0
  74. sanning_proof-0.4.0/test-vectors/merkle-tree-03-leaves.json +81 -0
  75. sanning_proof-0.4.0/test-vectors/merkle-tree-07-leaves.json +152 -0
  76. sanning_proof-0.4.0/test-vectors/merkle-tree-1024-leaves.json +17463 -0
  77. sanning_proof-0.4.0/test-vectors/merkle-tree-16-leaves.json +309 -0
  78. sanning_proof-0.4.0/test-vectors/negatives/negative-lone-surrogate-00.json +10 -0
  79. sanning_proof-0.4.0/test-vectors/negatives/negative-malformed-minor-00.json +10 -0
  80. sanning_proof-0.4.0/test-vectors/negatives/negative-malformed-minor-01.json +10 -0
  81. sanning_proof-0.4.0/test-vectors/negatives/negative-malformed-minor-02.json +10 -0
  82. sanning_proof-0.4.0/test-vectors/negatives/negative-missing-payload-hash-00.json +10 -0
  83. sanning_proof-0.4.0/test-vectors/sanning.events-v1/events-checkpoint-01.json +168 -0
  84. sanning_proof-0.4.0/test-vectors/sanning.events-v1/events-checkpoint-chain-01.json +188 -0
  85. sanning_proof-0.4.0/test-vectors/sanning.events-v1/events-event-01.json +50 -0
  86. sanning_proof-0.4.0/test-vectors/sanning.events-v1/events-event-02.json +47 -0
  87. sanning_proof-0.4.0/tests/test_accept_set.py +108 -0
  88. sanning_proof-0.4.0/tests/test_attestation_exponent_forgery.py +50 -0
  89. sanning_proof-0.4.0/tests/test_bundle.py +175 -0
  90. sanning_proof-0.4.0/tests/test_canonicalization_depth.py +71 -0
  91. sanning_proof-0.4.0/tests/test_canonicalize.py +45 -0
  92. sanning_proof-0.4.0/tests/test_conformance.py +472 -0
  93. sanning_proof-0.4.0/tests/test_envelope.py +393 -0
  94. sanning_proof-0.4.0/tests/test_evidence.py +198 -0
  95. sanning_proof-0.4.0/tests/test_evidence_export.py +307 -0
  96. sanning_proof-0.4.0/tests/test_hash.py +18 -0
  97. sanning_proof-0.4.0/tests/test_merkle.py +89 -0
  98. sanning_proof-0.4.0/tests/test_rsa_pss.py +112 -0
  99. sanning_proof-0.4.0/tests/test_sign_verify.py +60 -0
  100. sanning_proof-0.4.0/tools/gen-vectors/.gitignore +0 -0
  101. sanning_proof-0.4.0/tools/gen-vectors/LICENSE +28 -0
  102. sanning_proof-0.4.0/tools/gen-vectors/README.md +58 -0
  103. sanning_proof-0.4.0/tools/gen-vectors/export_vectors.py +221 -0
  104. sanning_proof-0.4.0/tools/gen-vectors/gen_exponent_forgery_vector.py +204 -0
  105. sanning_proof-0.4.0/tools/gen-vectors/gen_export_vectors.py +173 -0
  106. sanning_proof-0.4.0/tools/gen-vectors/gen_vectors.py +982 -0
  107. sanning_proof-0.4.0/tools/gen-vectors/pyproject.toml +10 -0
  108. sanning_proof-0.4.0/ts/CHANGELOG.md +107 -0
  109. sanning_proof-0.4.0/ts/LICENSE +21 -0
  110. sanning_proof-0.4.0/ts/README.md +168 -0
  111. sanning_proof-0.4.0/ts/package-lock.json +1480 -0
  112. sanning_proof-0.4.0/ts/package.json +59 -0
  113. sanning_proof-0.4.0/ts/scripts/gen-export-fixture.mjs +387 -0
  114. sanning_proof-0.4.0/ts/scripts/gen-rsa-attestation-fixture.mjs +88 -0
  115. sanning_proof-0.4.0/ts/src/agent-proof.ts +229 -0
  116. sanning_proof-0.4.0/ts/src/cli.ts +619 -0
  117. sanning_proof-0.4.0/ts/src/compose.ts +189 -0
  118. sanning_proof-0.4.0/ts/src/crypto.ts +243 -0
  119. sanning_proof-0.4.0/ts/src/evidence.ts +1491 -0
  120. sanning_proof-0.4.0/ts/src/index.ts +105 -0
  121. sanning_proof-0.4.0/ts/src/merkle.ts +125 -0
  122. sanning_proof-0.4.0/ts/src/types.ts +72 -0
  123. sanning_proof-0.4.0/ts/src/verifier.ts +340 -0
  124. sanning_proof-0.4.0/ts/test/accept-set.test.ts +109 -0
  125. sanning_proof-0.4.0/ts/test/agent-proof.test.ts +148 -0
  126. sanning_proof-0.4.0/ts/test/attestation-exponent-forgery.test.ts +95 -0
  127. sanning_proof-0.4.0/ts/test/canonicalization-depth.test.ts +65 -0
  128. sanning_proof-0.4.0/ts/test/cli-content-e2e.test.ts +298 -0
  129. sanning_proof-0.4.0/ts/test/cli-e2e.test.ts +411 -0
  130. sanning_proof-0.4.0/ts/test/cli.test.ts +185 -0
  131. sanning_proof-0.4.0/ts/test/compose-cli-e2e.test.ts +162 -0
  132. sanning_proof-0.4.0/ts/test/compose-export.test.ts +221 -0
  133. sanning_proof-0.4.0/ts/test/conformance.test.ts +480 -0
  134. sanning_proof-0.4.0/ts/test/crypto.test.ts +32 -0
  135. sanning_proof-0.4.0/ts/test/evidence-content.test.ts +421 -0
  136. sanning_proof-0.4.0/ts/test/evidence-export.test.ts +443 -0
  137. sanning_proof-0.4.0/ts/test/evidence-golden.test.ts +200 -0
  138. sanning_proof-0.4.0/ts/test/evidence.test.ts +323 -0
  139. sanning_proof-0.4.0/ts/test/external-commitment.test.ts +164 -0
  140. sanning_proof-0.4.0/ts/test/fixtures/anchor-trace-bundle.golden.json +110 -0
  141. sanning_proof-0.4.0/ts/test/fixtures/evidence-export-bundle.golden.json +332 -0
  142. sanning_proof-0.4.0/ts/test/fixtures/rsa-pss-attestation.golden.json +26 -0
  143. sanning_proof-0.4.0/ts/test/rsa-pss-attestation.test.ts +132 -0
  144. sanning_proof-0.4.0/ts/test/verifier.test.ts +178 -0
  145. sanning_proof-0.4.0/ts/tsconfig.build.json +15 -0
  146. sanning_proof-0.4.0/ts/tsconfig.json +17 -0
  147. sanning_proof-0.4.0/ts/vitest.config.ts +12 -0
@@ -0,0 +1,73 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.10", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
16
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - name: Install
20
+ run: pip install -e .[dev]
21
+ - name: Format check
22
+ run: black --check src tests
23
+ - name: Conformance + unit tests
24
+ run: pytest -q
25
+
26
+ ts:
27
+ # The TypeScript kernel (ts/) — same conformance gate as the Python kernel,
28
+ # against the one authoritative test-vectors/ corpus. SHA-pinned actions
29
+ # per this repo's supply-chain discipline.
30
+ runs-on: ubuntu-latest
31
+ defaults:
32
+ run:
33
+ working-directory: ts
34
+ steps:
35
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
36
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
37
+ with:
38
+ node-version: "24"
39
+ - run: npm ci
40
+ - name: Build (dist ESM + declarations — the publishable shape)
41
+ run: npm run build
42
+ - name: Typecheck
43
+ run: npm run typecheck
44
+ - name: Conformance + verifier tests (vectors byte-for-byte)
45
+ run: npm test
46
+
47
+ cross-kernel:
48
+ # Python ⇄ TS ⇄ Go agreement gate over the corpus + adversarial cases
49
+ # (cross-kernel/). The three kernels must return identical verdicts on
50
+ # identical bytes. Python+TS always run; the Go leg builds sanning-io/agent
51
+ # pkg/proof at cross-kernel/PIN when the agent source is fetchable, and
52
+ # SKIPS loudly otherwise (public PRs without private-repo access).
53
+ runs-on: ubuntu-latest
54
+ steps:
55
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
56
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
57
+ with:
58
+ python-version: "3.13"
59
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
60
+ with:
61
+ node-version: "24"
62
+ - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
63
+ with:
64
+ go-version: "1.25.0"
65
+ - name: Install Python kernel + node deps
66
+ run: |
67
+ pip install -e .[dev]
68
+ (cd ts && npm ci)
69
+ - name: Cross-kernel agreement (Python + TS + Go)
70
+ # Go leg builds the VENDORED sanning-io/agent pkg/proof (cross-kernel/
71
+ # vendor-agent, pinned in cross-kernel/PIN) — no private-repo access,
72
+ # so all three legs run on public and fork PRs.
73
+ run: bash cross-kernel/run.sh
@@ -0,0 +1,71 @@
1
+ # Publishes ts/ (@sanning/proof) to npm via OIDC trusted publishing — no
2
+ # long-lived npm tokens anywhere (the npm mirror of this repo's PyPI
3
+ # Trusted-Publishing flow in release.yml). This repo is PUBLIC, so npm
4
+ # provenance works from day one: --provenance is ON.
5
+ #
6
+ # npmjs.com side (one-time, package settings for @sanning/proof →
7
+ # "Trusted Publisher" → GitHub Actions):
8
+ # organization: sanning-io
9
+ # repository: proof
10
+ # workflow: release-ts.yml (filename must match exactly)
11
+ # environment: (leave blank)
12
+ #
13
+ # Real publish: push a `ts-vX.Y.Z` tag whose version matches ts/package.json
14
+ # (the guard step enforces it).
15
+ # Dry-run: workflow_dispatch — runs the ENTIRE path including
16
+ # `npm publish --dry-run`. Lesson from the v1.2.0 cosign failure: a dry-run
17
+ # that skips the publish step validates nothing.
18
+ #
19
+ # Tag namespace: ts-v* (TS). Does not overlap v* (Python, release.yml) or
20
+ # test-vectors-v* (corpus).
21
+
22
+ name: release-ts
23
+
24
+ on:
25
+ push:
26
+ tags: ['ts-v*.*.*']
27
+ workflow_dispatch: # full dry-run: build, test, pack — publishes nothing
28
+
29
+ permissions:
30
+ contents: read
31
+ id-token: write # npm OIDC trusted publishing
32
+
33
+ jobs:
34
+ publish:
35
+ runs-on: ubuntu-latest
36
+ defaults:
37
+ run:
38
+ working-directory: ts
39
+ steps:
40
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
41
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
42
+ with:
43
+ node-version: '24'
44
+ registry-url: 'https://registry.npmjs.org'
45
+ - name: Pin npm to a trusted-publishing-capable version
46
+ # OIDC trusted publishing needs npm >= 11.5.1; Node images float their
47
+ # bundled npm. Floor it explicitly (a floating tool default broke the
48
+ # first v1.2.0 release cut — don't repeat that with npm).
49
+ run: |
50
+ npm install -g npm@^11.5.1
51
+ npm --version
52
+ - run: npm ci
53
+ - name: Build (exports point at dist/)
54
+ run: npm run build
55
+ - run: npm run typecheck
56
+ - name: Conformance + verifier tests (vectors byte-for-byte)
57
+ run: npm test
58
+ - name: Tag ↔ package version guard
59
+ if: github.event_name == 'push'
60
+ run: |
61
+ PKG="$(node -p "require('./package.json').version")"
62
+ TAG="${GITHUB_REF_NAME#ts-v}"
63
+ if [ "$PKG" != "$TAG" ]; then
64
+ echo "::error::tag ts-v${TAG} does not match ts/package.json version ${PKG}"
65
+ exit 1
66
+ fi
67
+ - name: Publish to npm (dry-run on dispatch)
68
+ # --provenance ON: this repo is public, so npm provenance attests the
69
+ # build from source. On workflow_dispatch this runs --dry-run (packs +
70
+ # validates, publishes nothing); on a ts-v* tag push it publishes.
71
+ run: npm publish --provenance --access public ${{ github.event_name == 'workflow_dispatch' && '--dry-run' || '' }}
@@ -0,0 +1,45 @@
1
+ name: release
2
+
3
+ # Publishes to PyPI via Trusted Publishing (OIDC) — no token secrets.
4
+ # Trigger: a maintainer pushes a v* tag (e.g. `git tag v0.1.0 && git push origin v0.1.0`).
5
+ # One-time setup on pypi.org: add a "pending publisher" for project sanning-proof
6
+ # with owner=sanning-io, repo=proof, workflow=release.yml, environment=pypi.
7
+
8
+ on:
9
+ push:
10
+ tags: ["v*"]
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
17
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
18
+ with:
19
+ python-version: "3.12"
20
+ - name: Run the conformance gate before anything ships
21
+ run: |
22
+ pip install -e .[dev]
23
+ black --check src tests
24
+ pytest -q
25
+ - name: Build sdist + wheel
26
+ run: |
27
+ pip install build
28
+ python -m build
29
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
30
+ with:
31
+ name: dist
32
+ path: dist/
33
+
34
+ publish:
35
+ needs: build
36
+ runs-on: ubuntu-latest
37
+ environment: pypi
38
+ permissions:
39
+ id-token: write
40
+ steps:
41
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
42
+ with:
43
+ name: dist
44
+ path: dist/
45
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+
9
+ # TS kernel (ts/)
10
+ node_modules/
11
+ *.tsbuildinfo
@@ -0,0 +1,108 @@
1
+ # Changelog — `sanning-proof` (Python kernel)
2
+
3
+ All notable changes to the Python verification kernel. Format loosely follows
4
+ [Keep a Changelog](https://keepachangelog.com/); this project is pre-1.0, so
5
+ minor versions may include behavior changes.
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+
11
+ - **Cross-kernel accept-set agreement.** The tri-kernel gate now probes the
12
+ accepted-`spec_version` registry itself: `cross-kernel/accept-set.json`
13
+ transcribes the ratified accept-set (envelope-spec §4 registry + §5
14
+ accepted-majors discipline) and `cross-kernel/generate_cases.py` builds one
15
+ correctly signed probe per identifier, in the profile's registered binding
16
+ mode, plus the profile-conditional signed-scope dialects (32 → 48 cases).
17
+ Until now every case in the set carried `sanning.agent/v1` or
18
+ `sanning.events/v1`, so three kernels could hold three different registries
19
+ and the gate stayed green. Each of the three registries is additionally held
20
+ to the declaration by **set equality**, because a verdict probe can only
21
+ disagree about identifiers someone declared — a kernel admitting a profile
22
+ nobody wrote down has no probe to fail: `tests/test_accept_set.py` for the
23
+ Python `ACCEPTED_SPEC_VERSIONS`, `ts/test/accept-set.test.ts` for the TS
24
+ `ACCEPTED_SPEC_MAJORS`, and `cross-kernel/go-verifier/accept_set.go` for Go's
25
+ `SupportedSpecMajor` (inside the gate, since Go has no unit-test leg here).
26
+ No kernel behavior changes: the registry's contents are untouched.
27
+
28
+ ### Known gaps
29
+
30
+ - **`sanning.mlflow/v1` admission is divergent and escalated, not fixed.** The
31
+ ratified accept-set is `{ sanning.agent/v1, sanning.mlflow/v1,
32
+ sanning.events/v1 }` (§5, amendment A5 — "in all three kernels"); the Python
33
+ kernel carries all three, while TS `ACCEPTED_SPEC_MAJORS` and Go
34
+ `SupportedSpecMajor` carry two. A second divergence sits behind it: TS and Go
35
+ do not implement the mlflow profile's underscore-annotation signed-scope
36
+ strip, so an `_tx_id`-carrying envelope would split the verdict even once the
37
+ identifier is admitted. Both are quarantined in `accept-set.json` and asserted
38
+ in both directions (the divergence must persist unchanged; resolving it fails
39
+ the gate until the row is deleted). Widening a kernel's accept-set changes
40
+ which envelopes are admitted — a founder decision, not a kernel edit.
41
+
42
+ ## [0.4.0] — 2026-08-05
43
+
44
+ First release of the Python verification kernel under this name, carrying the
45
+ July 2026 security fixes.
46
+
47
+ ### Security
48
+
49
+ Merged to `main` during the July 2026 adversarial pass; **this is the first
50
+ release that publishes them.** Both are normative cross-kernel invariants — the
51
+ TypeScript kernel carries the identical guards.
52
+
53
+ - **RSA public-exponent guard (`sanning_proof.rsa_pss`).** `verify_rsa_pss_sha256`
54
+ now rejects any operator key whose public exponent is not `65537`
55
+ (`RSA_PUBLIC_EXPONENT`), returning `False` — a **FAILED** attestation, exit 1 —
56
+ rather than delegating the decision to the crypto library. The operator-address
57
+ binding is `base64url(SHA-256(n))`, which commits to the **modulus alone**; with
58
+ `e = 1`, RSA verification degenerates to the identity function, so anyone holding
59
+ a public modulus could forge an attestation that binds to the legitimate
60
+ operator's address without ever holding the private key. A malformed `e`
61
+ (unparseable or empty) still routes to `MalformedRsaError` → exit 2, so
62
+ "forged" and "garbage" stay distinguishable.
63
+ - **Canonicalization depth bound (`sanning_proof.canonicalize`).**
64
+ `MAX_CANONICAL_DEPTH = 128`, enforced by an **iterative** (never recursive)
65
+ `exceeds_depth` walk that runs *before* canonicalization on already-parsed,
66
+ possibly hostile input. Container nesting past the bound raises
67
+ `CanonicalDepthError`, bucketed as **malformed** (exit 2), not as a failed
68
+ verification. Without it, an attacker-chosen nesting depth splits the verdict
69
+ across kernels — bytes one kernel canonicalizes on a large native stack and
70
+ another rejects with `RecursionError` — which is a verdict-integrity break, not
71
+ merely a crash.
72
+
73
+ ### Changed
74
+
75
+ - **Family namespace.** Every family
76
+ wire string moves namespace with the MAJOR unchanged at `v1` (a new namespace,
77
+ not a new revision): `sanning.agent/v1`, `sanning.mlflow/v1`, `sanning.events/v1`
78
+ (`ACCEPTED_SPEC_VERSIONS`), `sanning.evidence/v1` (`ACCEPTED_EVIDENCE_MAJORS`),
79
+ `sanning.anchor.trace/v1` and `sanning.evidence.export/v1` (`body_type`),
80
+ `sanning.evidence.verdict/v1` (`VERDICT_SCHEMA_VERSION`), and
81
+ `sanning.agent.proof/v1` (`BUNDLE_SPEC_VERSION`). Envelopes and bundles emitted
82
+ outside the accept-set do not verify.
83
+ - **BREAKING — conformance corpus re-cut to `test-vectors-v2.0`.** Every vector was
84
+ regenerated under the migrated namespace, so all signatures, Merkle leaves, roots
85
+ and audit paths were re-derived; the manifest moved `CORPUS-v1.md` → `CORPUS-v2.md`
86
+ and the profile directory is `sanning.events-v1/`. A byte change
87
+ to existing vectors is a **major** cut per `specs/governance.md` §4; the 30-day RFC
88
+ was waived by founder call (recorded in §7). **41 manifest rows, up from 39** — the
89
+ two `evidence-export/negatives/` files (the RSA `e=1` exponent forgery and the
90
+ canonicalization-depth bundle) were never pinned in any manifest and are now listed.
91
+ A `v1.x` pin cannot be upgraded in place; downstreams re-pin deliberately.
92
+ - The `e=1` attestation-forgery negative gained a generator
93
+ (`tools/gen-vectors/gen_exponent_forgery_vector.py`), closing the CORP1 violation it
94
+ carried since the July security pass. It asserts on every run that raw `e=1` RSA
95
+ verification *accepts* the forged signature, so the vector cannot decay into one that
96
+ fails for an unrelated reason while the exponent guard stops being exercised.
97
+
98
+ ### Known gaps
99
+
100
+ - The vendored Go leg (`cross-kernel/vendor-agent/` @ `cross-kernel/PIN`) still carries
101
+ the pre-migration accept-set, so `cross-kernel/run.sh`'s Go leg is expected-red until
102
+ the `agent` repo migrates. The Python⇄TS legs (including the byte-identical export
103
+ verdict gate) are green.
104
+ - Both gaps clear **after** this release, not before it: `sanning-io/anchor` can only
105
+ re-cut the golden bundle once it depends on a published `@sanning/proof`, and the Go
106
+ leg needs the `agent` migration. Neither blocks 0.4.0 — re-cutting the fixture locally
107
+ to make the skips green would destroy the cross-repo emit↔verify guarantee the fixture
108
+ exists to hold.
@@ -0,0 +1,116 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What this is
6
+
7
+ `sanning-io/proof` — the **polyglot verification home** of the Sanning evidence plane: the family
8
+ specs (`specs/`), the one authoritative conformance corpus (`test-vectors/`), and two conformant
9
+ kernels that reproduce it byte-for-byte:
10
+
11
+ - **Python** (`sanning_proof`, repo root) — extracted from the MLflow plugin's proof engine; this is
12
+ the kernel most of this CLAUDE.md is about.
13
+ - **TypeScript** (`@sanning/proof`, [`ts/`](ts/)) — moved here 2026-06-11 from the browser verifier
14
+ app (an app never owns a kernel — same principle that extracted the Python kernel from mlflow).
15
+
16
+ Both implement exactly the five kernel primitives from
17
+ [`specs/architecture.md`](specs/architecture.md) §3: RFC 8785 (JCS) canonicalization, SHA-256, Ed25519
18
+ sign/verify, RFC 9162 binary Merkle (incl. inclusion-proof bundles), and the
19
+ accepted-`spec_version` registry. **No I/O, no networking, no key lifecycle** — those belong to
20
+ producers (`agent`, `mlflow`), not the kernel. The Go reference stays MIT-carved in
21
+ `agent/pkg/proof` (sanning-io).
22
+
23
+ ### Working in the TS kernel (`ts/`)
24
+
25
+ Standalone npm package, separate from the Python tooling:
26
+
27
+ ```bash
28
+ cd ts && npm ci
29
+ npm run build # tsc -p tsconfig.build.json → dist/ (ESM + .d.ts; the published shape)
30
+ npm run typecheck # tsc --noEmit
31
+ npm test # vitest — conformance vs ../test-vectors (the same corpus), byte-for-byte
32
+ ```
33
+
34
+ CI runs a dedicated `ts` job (`ci.yml`). Releases go through `release-ts.yml` (npm OIDC trusted
35
+ publishing, provenance on, tags `ts-v*` — distinct from Python's `v*` / `release.yml`). Source
36
+ relative imports MUST carry explicit `.js` extensions (Node-ESM requirement; `moduleResolution:
37
+ bundler` won't add them — this bit `0.1.1`, fixed in `0.1.2`). Kernel changes are spec-layer
38
+ changes: byte-for-byte conformance is the gate, and the Python and Go kernels must stay in
39
+ lockstep (escalate divergences per `specs/governance.md` §6).
40
+
41
+ **Bundle verify + the CLI live HERE, not in `@ar.io/anchor`** (the kernel is the producer-neutral
42
+ verifier — an auditor must not need the write SDK). `src/evidence.ts` = `verifyEvidenceBundle`
43
+ (signed `sanning.evidence/v1` / `sanning.anchor.trace/v1` bundles); `src/agent-proof.ts` =
44
+ `verifyAgentProofBundle` (`sanning.agent.proof/v1`); `src/cli.ts` = the `bin: proof` →
45
+ `npx @sanning/proof verify <bundle> [gateways]` (sniffs `spec_version`, so one verb covers anchor +
46
+ agent; pinned exit codes `0/1/2/3`). Bundle *emit* (`toEvidenceBundle`) lives in `@ar.io/anchor`.
47
+ Implemented on `main`; the `bin` ships on the next `ts-v*` tag. Cross-repo round-trip is pinned by
48
+ `ts/test/fixtures/anchor-trace-bundle.golden.json` (a real @ar.io/anchor bundle, frozen).
49
+
50
+ ## Commands
51
+
52
+ ```bash
53
+ python3 -m venv .venv && .venv/bin/pip install -e .[dev] # one-time setup
54
+ .venv/bin/pytest -q # all tests (conformance is the contract)
55
+ .venv/bin/pytest tests/test_conformance.py -q # corpus gate only
56
+ .venv/bin/black src tests # format (CI runs black --check)
57
+ ```
58
+
59
+ ## The conformance contract
60
+
61
+ `test-vectors/` is the authoritative home of the family conformance corpus (the current
62
+ cut is the DL-22 `sanning.*` re-cut declared in `CORPUS-v2.md` — a MAJOR cut per
63
+ `specs/governance.md` §4, tagged **`test-vectors-v2.0`** → commit `3a9ccf5`.
64
+ Regeneration is an ordered ceremony: see `test-vectors/README.md`. Provenance:
65
+ `test-vectors/VENDORING.md`; downstreams like `agent` (sanning-io) vendor byte-for-byte
66
+ from here). `tests/test_conformance.py`
67
+ asserts, for every vector: SHA-256 corpus integrity against the CORPUS-v2.md table, JCS-canonical
68
+ payload bytes, `payload_hash`, envelope-for-signature bytes, deterministic Ed25519 signatures, and
69
+ Merkle roots / audit paths. **If the kernel disagrees with a vector, the kernel is wrong — never
70
+ the vector.** Never edit vector files; re-sync only at a published corpus tag per
71
+ `VENDORING.md`.
72
+
73
+ ## Spec pins (do not drift)
74
+
75
+ - `envelope-spec.md` **v1.3 (ratified v1.0 2026-06-10, amended through 2026-06-15 — additive,
76
+ no wire change)** — the family contract. Ratified accept-set (§4 registry + §5): `sanning.agent/v1`
77
+ (inline payload), `sanning.mlflow/v1` (external commitment), `sanning.events/v1` (external
78
+ commitment, Minimal disclosure). Fail-closed on unknown majors. **The kernels do not all
79
+ implement that set today:** `sanning.mlflow/v1` is carried by the Python kernel only — the TS
80
+ (`ACCEPTED_SPEC_MAJORS`) and Go (`SupportedSpecMajor`) registries omit it, as does their
81
+ signed-scope handling of the mlflow `_*` annotation dialect. That gap is an **escalated
82
+ divergence**, pinned by the cross-kernel gate and awaiting a founder admission decision — see
83
+ [`cross-kernel/README.md`](cross-kernel/README.md) § Known divergences and
84
+ [`cross-kernel/accept-set.json`](cross-kernel/accept-set.json). Do not close it with a one-line
85
+ registry edit: widening an accept-set changes which envelopes verify.
86
+ - Signed scope = the envelope minus **`signature`**, minus the reserved **`co_signatures`**
87
+ (envelope-spec §7.1), and — **profile-conditional** — minus underscore-prefixed annotation
88
+ keys for `sanning.mlflow/v1` + legacy envelopes ONLY (mlflow convention, e.g. `_tx_id`). The
89
+ `sanning.agent/v1` and `sanning.events/v1` scopes are minus-signature/co_signatures only, matching
90
+ Go: an injected `_*` key on an agent or events envelope MUST fail verification. The corpus can
91
+ catch neither the co_signatures strip nor the profile-conditionality — unit tests in
92
+ `tests/test_envelope.py`
93
+ are the only guard; do not remove them because conformance still passes without them.
94
+ - Merkle: RFC 9162 §2.1 domain separation (`0x00` leaf / `0x01` node), empty-tree root =
95
+ `SHA-256("")`. Never the Bitcoin duplicate-last-leaf variant.
96
+ - Ed25519 is strict RFC 8032 (libsodium via PyNaCl) — matches Go `crypto/ed25519` and the JS
97
+ sibling's `zip215: false`.
98
+
99
+ ## Working rules
100
+
101
+ - Verifiers must never raise on adversarial input — malformed envelopes return a failed
102
+ `VerificationResult`, they don't throw.
103
+ - Each commit lands one primitive, CI-green, with tests in the same commit.
104
+ - Dependencies are pinned to two load-bearing packages: `PyNaCl` and `jcs`. Adding a dependency
105
+ is a design decision, not a convenience.
106
+ - Source siblings for cross-checks (read-only): Go reference `agent/pkg/{proof,merkle}` (sanning-io),
107
+ origin kernel `mlflow` (sanning-io).
108
+
109
+ ## Release
110
+
111
+ `release.yml` publishes to PyPI via Trusted Publishing (OIDC) on a `v*` tag pushed by a
112
+ maintainer. Version lives in `pyproject.toml` + `src/sanning_proof/__init__.py`; bump both together.
113
+ The TS kernel is a **third** version site — `ts/package.json`, released separately by a `ts-v*`
114
+ tag through `release-ts.yml`. All three move in lockstep; a bump in one and not the others is the
115
+ classic release-day failure, and neither Python workflow guards it (`release.yml` has no
116
+ tag↔version check — PyPI publishes whatever `pyproject.toml` says).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sanning Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,202 @@
1
+ Metadata-Version: 2.5
2
+ Name: sanning-proof
3
+ Version: 0.4.0
4
+ Summary: Verification kernel for the Sanning verification stack: RFC 8785 canonicalization, SHA-256, Ed25519 envelope sign/verify, RFC 9162 Merkle inclusion proofs, and RSA-PSS attested-evidence-export verification.
5
+ Project-URL: Repository, https://github.com/sanning-io/proof
6
+ Project-URL: Issues, https://github.com/sanning-io/proof/issues
7
+ Author: Sanning Inc.
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Sanning Inc.
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: arweave,ed25519,jcs,merkle,provenance,rfc8785,rfc9162,sanning,verification
31
+ Classifier: Development Status :: 4 - Beta
32
+ Classifier: Intended Audience :: Developers
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.10
36
+ Classifier: Programming Language :: Python :: 3.11
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: Programming Language :: Python :: 3.13
39
+ Classifier: Topic :: Security :: Cryptography
40
+ Requires-Python: >=3.10
41
+ Requires-Dist: cryptography>=41.0.0
42
+ Requires-Dist: jcs>=0.2.1
43
+ Requires-Dist: pynacl>=1.5.0
44
+ Provides-Extra: dev
45
+ Requires-Dist: black>=24.0; extra == 'dev'
46
+ Requires-Dist: pytest>=7.0; extra == 'dev'
47
+ Description-Content-Type: text/markdown
48
+
49
+ # Sanning Proof
50
+
51
+ Offline verification for the [Sanning](https://console.sanning.io) evidence plane: standalone, dependency-light verification kernels in **Python** and **TypeScript**, plus the ratified specs and the conformance corpus both kernels are gated against, byte for byte. No network, no accounts, no vendor code in the trust path — anyone can check a piece of anchored evidence with nothing but this package.
52
+
53
+ - **Python** — `sanning_proof` (PyPI: [`sanning-proof`](https://pypi.org/project/sanning-proof/)), at the repo root.
54
+ - **TypeScript** — [`@sanning/proof`](https://www.npmjs.com/package/@sanning/proof) (npm), in [`ts/`](ts/) — browser + Node ≥ 20, ESM.
55
+
56
+ Verify a signed envelope fetched from any Arweave gateway:
57
+
58
+ ```python
59
+ import json
60
+ import urllib.request
61
+
62
+ from sanning_proof import verify_envelope
63
+
64
+ raw = urllib.request.urlopen("https://arweave.net/raw/<tx_id>").read()
65
+ result = verify_envelope(json.loads(raw))
66
+
67
+ assert result.ok # spec_version + payload binding + Ed25519 signature
68
+ ```
69
+
70
+ ```ts
71
+ import { verifyEnvelope } from "@sanning/proof";
72
+
73
+ const env = await (await fetch("https://arweave.net/raw/<tx_id>")).json();
74
+ const result = await verifyEnvelope(env);
75
+ result.ok; // spec_version + payload binding + Ed25519 signature
76
+ ```
77
+
78
+ > [!NOTE]
79
+ > Verification never needs an account or a server. If you are *producing* evidence — anchoring agent steps, running a fleet under continuous tamper-evidence — the hosted plane is [console.sanning.io](https://console.sanning.io) and the write SDK is [`@sanning/anchor`](https://github.com/sanning-io/anchor).
80
+
81
+ ## Install
82
+
83
+ ```bash
84
+ pip install sanning-proof # Python
85
+ npm install @sanning/proof # TypeScript
86
+ ```
87
+
88
+ ## What a verdict proves — and what it doesn't
89
+
90
+ `result.ok` proves exactly this: *the holder of the private key matching the envelope's `public_key` signed exactly these bytes, and the payload binding holds.*
91
+
92
+ It deliberately does **not** prove:
93
+
94
+ - **Whose key that is.** The key ↔ identity binding comes from out of band — for example an enrollment roster. The kernel checks cryptography, not identity.
95
+ - **That the envelope is on-chain.** Fetch the transaction from a gateway yourself and re-verify — which is exactly what the example above does.
96
+ - **When it happened.** `signed_at` is the signer's claim; witnessed time comes from the Arweave block the envelope landed in.
97
+ - **What the raw data was.** Envelopes are content-blind: they commit to hashes. Raw bytes never pass through this kernel unless the producer explicitly disclosed them alongside the proof.
98
+
99
+ For a verification tool, the guarantee boundary is the product — a verifier that overclaims is worse than no verifier at all.
100
+
101
+ ## Verify more things
102
+
103
+ **Bind an artifact you hold** to the provenance an envelope commits to (reverse lookup):
104
+
105
+ ```python
106
+ import hashlib
107
+
108
+ artifact_hash = hashlib.sha256(open("model.pkl", "rb").read()).hexdigest()
109
+ result = verify_envelope(envelope, expected_content_hash=artifact_hash)
110
+ print(result.content_hash_ok, result.content_role) # True, "asset"
111
+ ```
112
+
113
+ **Verify an external-commitment envelope** (`sanning.mlflow/v1`) against the committed bytes:
114
+
115
+ ```python
116
+ result = verify_envelope(envelope, payload_bytes=canonical_bytes)
117
+ ```
118
+
119
+ **Verify an inclusion-proof bundle** — proves a leaf event was in a signed checkpoint:
120
+
121
+ ```python
122
+ from sanning_proof import verify_proof_bundle
123
+
124
+ bundle = json.load(open("proof-bundle.json"))
125
+ result = verify_proof_bundle(bundle)
126
+ assert result.ok and result.inclusion_ok
127
+ ```
128
+
129
+ The TypeScript kernel has full parity, including the RFC 9162 Merkle primitives (`leafHash` / `merkleRoot` / `auditPath` / `verifyInclusion`) — see [`ts/README.md`](ts/README.md).
130
+
131
+ ## The CLI
132
+
133
+ The TypeScript package ships a turnkey CLI. Pinned exit codes — `0` verified · `1` failed · `2` malformed · `3` gateway-unavailable — safe to gate CI on.
134
+
135
+ **Verify** any evidence or agent-proof bundle, fully offline; optionally re-fetch checkpoints on-chain by passing gateways:
136
+
137
+ ```bash
138
+ npx @sanning/proof verify <bundle.json> [gateway1,gateway2,...] [--logs <logs.json>]
139
+ ```
140
+
141
+ `--logs` binds disclosed raw logs to their committed hashes (evidence bundles only).
142
+
143
+ **Compose an attested export** — turn a source trace bundle plus operator attestation records into one signed, offline-verifiable `sanning.evidence/v1` bundle that verifies like any other:
144
+
145
+ ```bash
146
+ npx @sanning/proof export <source-bundle.json> --attestations <att.json> --key <exporter.hex> -o export.json
147
+ npx @sanning/proof verify export.json
148
+ ```
149
+
150
+ Programmatic equivalent: `composeExport(sourceBundle, attestations, { privateKey })`. Full format: [`specs/evidence-export.md`](specs/evidence-export.md).
151
+
152
+ ## Signing (producers)
153
+
154
+ Most producers never call the kernel directly — the write SDK ([`@sanning/anchor`](https://github.com/sanning-io/anchor)), the agent daemon, and the MLflow plugin all sign through it. For a custom producer:
155
+
156
+ ```python
157
+ from sanning_proof import sign_envelope, signing_key_from_seed_hex
158
+
159
+ key = signing_key_from_seed_hex("<32-byte seed hex>")
160
+ envelope = sign_envelope({...}, key) # the envelope minus `signature`, per the spec
161
+ ```
162
+
163
+ ## The standard
164
+
165
+ This repo is the authoritative home of the evidence plane's **standards layer** — the contract is public alongside the reference verifiers:
166
+
167
+ - [`specs/envelope-spec.md`](specs/envelope-spec.md) — the producer-neutral **Verifiable Event Envelope** family contract (ratified v1.0, amended through **v1.4**, 2026-07-15).
168
+ - [`specs/evidence-bundle.md`](specs/evidence-bundle.md) — the `sanning.evidence/v1` report wrapper.
169
+ - [`specs/evidence-export.md`](specs/evidence-export.md) — the attested-export format behind `proof export`.
170
+ - [`specs/architecture.md`](specs/architecture.md) — the kernel / producer / connector / transport factoring standard.
171
+ - [`specs/governance.md`](specs/governance.md) — who decides, and how.
172
+ - [`test-vectors/`](test-vectors/) — the conformance corpus (current cut: tag **`test-vectors-v2.0`**, per-file SHA-256 in [`CORPUS-v2.md`](test-vectors/CORPUS-v2.md)); generated by [`tools/gen-vectors/`](tools/gen-vectors/), never hand-edited.
173
+
174
+ Three envelope **profiles** are registered against the family contract: `sanning.agent/v1` (the agent daemon's inline-payload profile), `sanning.mlflow/v1` (the MLflow plugin's external-commitment profile), and `sanning.events/v1` (the anchor SDK's minimal-disclosure profile). The Python kernel accepts all three; the TypeScript kernel accepts `sanning.agent/v1` and `sanning.events/v1` (the mlflow dialect is Python-only). Additive minors are accepted within a major; unknown majors and malformed versions fail closed.
175
+
176
+ **Conformance discipline:** both kernels reproduce the corpus byte for byte — JCS-canonical bytes, payload hashes, envelope-for-signature bytes, deterministic signatures, Merkle roots, audit paths. If this package disagrees with a vector, the package is wrong — never the vector.
177
+
178
+ ## Kernel scope
179
+
180
+ The kernel is deliberately small: canonicalization (RFC 8785), SHA-256 hashing, Ed25519 sign/verify, RFC 9162 binary Merkle inclusion proofs, and the profile registry — **no I/O, no networking, no key lifecycle**. Gateway fetching, attestation polling, and key storage belong to the products that import it. Dependencies are deliberately minimal: Python has exactly two ([`PyNaCl`](https://pypi.org/project/PyNaCl/), [`jcs`](https://pypi.org/project/jcs/)); TypeScript has exactly two ([`@noble/ed25519`](https://www.npmjs.com/package/@noble/ed25519), [`canonicalize`](https://www.npmjs.com/package/canonicalize)).
181
+
182
+ ## Development
183
+
184
+ ```bash
185
+ python3 -m venv .venv && .venv/bin/pip install -e .[dev]
186
+ .venv/bin/pytest -q # the conformance gate is the contract
187
+ .venv/bin/black src tests
188
+ ```
189
+
190
+ The TypeScript kernel lives in [`ts/`](ts/) and is conformance-gated in CI against the same `test-vectors/` directory.
191
+
192
+ ## Contributing
193
+
194
+ Issues and pull requests are welcome. If you believe a kernel disagrees with a spec or a corpus vector, open an issue titled `contract conflict: <spec> §<section>` with the smallest reproduction — see [`specs/governance.md`](specs/governance.md) §6.
195
+
196
+ ## Security
197
+
198
+ Please report vulnerabilities privately via GitHub Security Advisories ("Report a vulnerability" under this repo's Security tab) and do **not** open a public issue.
199
+
200
+ ## License
201
+
202
+ [MIT](LICENSE). The verifier is deliberately open-licensed so anyone can audit it and verify evidence independently of Sanning.