matrixscroll 0.2.0__tar.gz → 0.2.1__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.
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/CHANGELOG.md +8 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/PKG-INFO +1 -1
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/__init__.py +1 -1
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/git.py +44 -21
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/pyproject.toml +1 -1
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_git_envelope.py +18 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/.gitignore +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/CONTRIBUTING.md +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/LICENSE +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/README.md +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/SECURITY.md +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/SPEC.md +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/controls/agentic_ai_controls.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/AGENTIC_AI_SECURITY.md +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/hardware-provider.md +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/quickstart-git.md +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/superpowers/plans/2026-06-19-ci-action-plan.md +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/superpowers/plans/2026-06-19-sdk-refactor-plan.md +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/superpowers/specs/2026-06-19-matrixscroll-git-design.md +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/yubikey-bridge.md +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/agentic_ai_evidence_manifest.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/agentic_ai_evidence_manifest.signed.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/ci/protected-branch.yml +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/commit-envelope.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/commit-envelope.signed.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/demo/agent-commit-demo.sh +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/demo/generate_signed_examples.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/release-manifest.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/release-manifest.signed.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/_core.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/canonical.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/cli.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/constants.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/errors.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/hooks/post-commit +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/hooks/pre-push +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/manifest.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/policy.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/providers/__init__.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/providers/base.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/providers/emulated.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/providers/hardware.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/providers/registry.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/providers/yubikey.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/py.typed +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/schemas/commit-envelope.v1.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/schemas/evidence-pack.v1.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/schemas/release-manifest.v1.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/__init__.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_agentic_guidance.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_canonical.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_cli.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_core.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_policy.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_release_metadata.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_vectors.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_yubikey_provider.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/README.md +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/_fixture_key.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/_generate.py +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/tampered_algorithm.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/tampered_device_id.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/tampered_field.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/tampered_nested.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/tampered_public_key.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/tampered_schema.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/tampered_signature.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/unsigned_empty_block.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/unsigned_no_block.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/valid_nested.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/valid_simple.json +0 -0
- {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/valid_unicode.json +0 -0
|
@@ -4,6 +4,14 @@ All notable changes to the Matrix Scroll Python SDK are documented here. The
|
|
|
4
4
|
format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
5
5
|
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.2.1] - 2026-06-20
|
|
8
|
+
|
|
9
|
+
Windows and cross-platform commit envelope fix.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **`parse_commit()` on Windows** — read author/committer timezone from `git cat-file commit`
|
|
13
|
+
instead of reconstructing dates with `%z` (unsupported in `git show` format on Windows).
|
|
14
|
+
|
|
7
15
|
## [0.2.0] - 2026-06-20
|
|
8
16
|
|
|
9
17
|
Agent provenance release: Git commit envelopes, SDK module split, CI scaffolding.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matrixscroll
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Open protocol for signing AI-assisted code provenance with Ed25519; shipping software root of trust with SSX360 hardware support in progress.
|
|
5
5
|
Project-URL: Homepage, https://matrixscroll.com
|
|
6
6
|
Project-URL: Documentation, https://matrixscroll.com/docs
|
|
@@ -19,7 +19,7 @@ HOOK_MARKER = "# matrixscroll-git hook\n"
|
|
|
19
19
|
DEFAULT_CONFIG = {"enforce": False, "actor_type": "human", "tool": "git-cli"}
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def _run_git(*args: str, cwd: Path | None = None) -> str:
|
|
22
|
+
def _run_git(*args: str, cwd: Path | None = None, strip: bool = True) -> str:
|
|
23
23
|
result = subprocess.run(
|
|
24
24
|
["git", *args],
|
|
25
25
|
capture_output=True,
|
|
@@ -29,7 +29,9 @@ def _run_git(*args: str, cwd: Path | None = None) -> str:
|
|
|
29
29
|
)
|
|
30
30
|
if result.returncode != 0:
|
|
31
31
|
raise RuntimeError(result.stderr.strip() or result.stdout.strip())
|
|
32
|
-
|
|
32
|
+
if strip:
|
|
33
|
+
return result.stdout.strip()
|
|
34
|
+
return result.stdout
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
def repo_root() -> Path:
|
|
@@ -102,28 +104,49 @@ def compute_commit_id(
|
|
|
102
104
|
message: str,
|
|
103
105
|
) -> str:
|
|
104
106
|
"""Compute Git commit object SHA-1 without creating the commit."""
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
parts = [
|
|
108
|
+
f"tree {tree}",
|
|
109
|
+
*[f"parent {parent}" for parent in parents],
|
|
110
|
+
f"author {author['name']} <{author['email']}> {author['date']}",
|
|
111
|
+
f"committer {committer['name']} <{committer['email']}> {committer['date']}",
|
|
112
|
+
]
|
|
113
|
+
body = ("\n".join(parts) + "\n\n").encode("utf-8") + message.encode("utf-8")
|
|
114
|
+
wrapped = f"commit {len(body)}\0".encode("ascii") + body
|
|
115
|
+
return hashlib.sha1(wrapped, usedforsecurity=False).hexdigest()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _parse_identity_line(line: str) -> dict[str, str]:
|
|
119
|
+
"""Parse ``author Name <email> timestamp tz`` from a commit object line."""
|
|
120
|
+
_, rest = line.split(" ", 1)
|
|
121
|
+
name_email, timestamp, tz = rest.rsplit(" ", 2)
|
|
122
|
+
if name_email.endswith(">") and " <" in name_email:
|
|
123
|
+
name, email = name_email.rsplit(" <", 1)
|
|
124
|
+
email = email[:-1]
|
|
125
|
+
else:
|
|
126
|
+
name, email = name_email, ""
|
|
127
|
+
return {"name": name, "email": email, "date": f"{timestamp} {tz}"}
|
|
115
128
|
|
|
116
129
|
|
|
117
130
|
def parse_commit(sha: str, root: Path | None = None) -> dict[str, Any]:
|
|
118
131
|
root = root or repo_root()
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
132
|
+
commit_sha = _run_git("rev-parse", sha, cwd=root)
|
|
133
|
+
raw = _run_git("cat-file", "commit", commit_sha, cwd=root, strip=False)
|
|
134
|
+
tree = ""
|
|
135
|
+
parents: list[str] = []
|
|
136
|
+
author: dict[str, str] | None = None
|
|
137
|
+
committer: dict[str, str] | None = None
|
|
138
|
+
for line in raw.splitlines():
|
|
139
|
+
if line.startswith("tree "):
|
|
140
|
+
tree = line.split(" ", 1)[1]
|
|
141
|
+
elif line.startswith("parent "):
|
|
142
|
+
parents.append(line.split(" ", 1)[1])
|
|
143
|
+
elif line.startswith("author "):
|
|
144
|
+
author = _parse_identity_line(line)
|
|
145
|
+
elif line.startswith("committer "):
|
|
146
|
+
committer = _parse_identity_line(line)
|
|
147
|
+
if not tree or author is None or committer is None or "\n\n" not in raw:
|
|
148
|
+
raise RuntimeError(f"unexpected commit object for {sha}")
|
|
149
|
+
body = raw.split("\n\n", 1)[1]
|
|
127
150
|
actual_id = compute_commit_id(
|
|
128
151
|
tree=tree,
|
|
129
152
|
parents=parents,
|
|
@@ -178,7 +201,7 @@ def build_commit_envelope(
|
|
|
178
201
|
if msg_file.is_file():
|
|
179
202
|
msg = msg_file.read_text(encoding="utf-8")
|
|
180
203
|
else:
|
|
181
|
-
msg = _run_git("log", "-1", "--pretty=%B", cwd=root) if parents else ""
|
|
204
|
+
msg = _run_git("log", "-1", "--pretty=%B", cwd=root, strip=False) if parents else ""
|
|
182
205
|
msg = msg or ""
|
|
183
206
|
commit_block = {
|
|
184
207
|
"expected_id": compute_commit_id(
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "matrixscroll"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "Open protocol for signing AI-assisted code provenance with Ed25519; shipping software root of trust with SSX360 hardware support in progress."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -47,6 +47,24 @@ def test_sign_and_verify_commit_envelope():
|
|
|
47
47
|
assert verify_manifest(signed)
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def test_parse_commit_matches_git(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
|
51
|
+
import subprocess
|
|
52
|
+
|
|
53
|
+
from matrixscroll.git import parse_commit
|
|
54
|
+
|
|
55
|
+
subprocess.run(["git", "init"], cwd=tmp_path, check=True, capture_output=True)
|
|
56
|
+
subprocess.run(["git", "config", "user.email", "dev@example.com"], cwd=tmp_path, check=True)
|
|
57
|
+
subprocess.run(["git", "config", "user.name", "Dev"], cwd=tmp_path, check=True)
|
|
58
|
+
sample = tmp_path / "hello.txt"
|
|
59
|
+
sample.write_text("hello\n", encoding="utf-8")
|
|
60
|
+
subprocess.run(["git", "add", "hello.txt"], cwd=tmp_path, check=True)
|
|
61
|
+
subprocess.run(["git", "commit", "-m", "init"], cwd=tmp_path, check=True)
|
|
62
|
+
sha = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=tmp_path, text=True).strip()
|
|
63
|
+
parsed = parse_commit(sha, tmp_path)
|
|
64
|
+
assert parsed["actual_id"] == sha
|
|
65
|
+
assert parsed["message"].startswith("init")
|
|
66
|
+
|
|
67
|
+
|
|
50
68
|
def test_build_commit_envelope_in_repo(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
|
51
69
|
import subprocess
|
|
52
70
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/superpowers/plans/2026-06-19-ci-action-plan.md
RENAMED
|
File without changes
|
{matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/superpowers/plans/2026-06-19-sdk-refactor-plan.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|