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.
Files changed (72) hide show
  1. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/CHANGELOG.md +8 -0
  2. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/PKG-INFO +1 -1
  3. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/__init__.py +1 -1
  4. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/git.py +44 -21
  5. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/pyproject.toml +1 -1
  6. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_git_envelope.py +18 -0
  7. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/.gitignore +0 -0
  8. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/CONTRIBUTING.md +0 -0
  9. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/LICENSE +0 -0
  10. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/README.md +0 -0
  11. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/SECURITY.md +0 -0
  12. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/SPEC.md +0 -0
  13. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/controls/agentic_ai_controls.json +0 -0
  14. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/AGENTIC_AI_SECURITY.md +0 -0
  15. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/hardware-provider.md +0 -0
  16. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/quickstart-git.md +0 -0
  17. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/superpowers/plans/2026-06-19-ci-action-plan.md +0 -0
  18. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/superpowers/plans/2026-06-19-sdk-refactor-plan.md +0 -0
  19. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/superpowers/specs/2026-06-19-matrixscroll-git-design.md +0 -0
  20. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/docs/yubikey-bridge.md +0 -0
  21. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/agentic_ai_evidence_manifest.json +0 -0
  22. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/agentic_ai_evidence_manifest.signed.json +0 -0
  23. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/ci/protected-branch.yml +0 -0
  24. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/commit-envelope.json +0 -0
  25. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/commit-envelope.signed.json +0 -0
  26. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/demo/agent-commit-demo.sh +0 -0
  27. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/demo/generate_signed_examples.py +0 -0
  28. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/release-manifest.json +0 -0
  29. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/examples/release-manifest.signed.json +0 -0
  30. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/_core.py +0 -0
  31. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/canonical.py +0 -0
  32. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/cli.py +0 -0
  33. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/constants.py +0 -0
  34. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/errors.py +0 -0
  35. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/hooks/post-commit +0 -0
  36. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/hooks/pre-push +0 -0
  37. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/manifest.py +0 -0
  38. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/policy.py +0 -0
  39. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/providers/__init__.py +0 -0
  40. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/providers/base.py +0 -0
  41. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/providers/emulated.py +0 -0
  42. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/providers/hardware.py +0 -0
  43. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/providers/registry.py +0 -0
  44. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/providers/yubikey.py +0 -0
  45. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/matrixscroll/py.typed +0 -0
  46. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/schemas/commit-envelope.v1.json +0 -0
  47. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/schemas/evidence-pack.v1.json +0 -0
  48. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/schemas/release-manifest.v1.json +0 -0
  49. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/__init__.py +0 -0
  50. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_agentic_guidance.py +0 -0
  51. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_canonical.py +0 -0
  52. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_cli.py +0 -0
  53. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_core.py +0 -0
  54. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_policy.py +0 -0
  55. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_release_metadata.py +0 -0
  56. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_vectors.py +0 -0
  57. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/tests/test_yubikey_provider.py +0 -0
  58. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/README.md +0 -0
  59. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/_fixture_key.json +0 -0
  60. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/_generate.py +0 -0
  61. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/tampered_algorithm.json +0 -0
  62. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/tampered_device_id.json +0 -0
  63. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/tampered_field.json +0 -0
  64. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/tampered_nested.json +0 -0
  65. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/tampered_public_key.json +0 -0
  66. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/tampered_schema.json +0 -0
  67. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/tampered_signature.json +0 -0
  68. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/unsigned_empty_block.json +0 -0
  69. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/unsigned_no_block.json +0 -0
  70. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/valid_nested.json +0 -0
  71. {matrixscroll-0.2.0 → matrixscroll-0.2.1}/vectors/valid_simple.json +0 -0
  72. {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.0
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
@@ -38,7 +38,7 @@ from ._core import (
38
38
  verify_manifest,
39
39
  )
40
40
 
41
- __version__ = "0.2.0"
41
+ __version__ = "0.2.1"
42
42
 
43
43
  __all__ = [
44
44
  "ALGORITHM",
@@ -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
- return result.stdout.strip()
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
- lines = [f"tree {tree}"]
106
- for parent in parents:
107
- lines.append(f"parent {parent}")
108
- lines.append(f"author {author['name']} <{author['email']}> {author['date']}")
109
- lines.append(f"committer {committer['name']} <{committer['email']}> {committer['date']}")
110
- lines.append("")
111
- lines.append(message.rstrip("\n"))
112
- body = "\n".join(lines).encode("utf-8")
113
- header = f"commit {len(body)}\0".encode("ascii") + body
114
- return hashlib.sha1(header, usedforsecurity=False).hexdigest()
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
- fmt = "%H%x00%T%x00%P%x00%an%x00%ae%x00%at%x00%cn%x00%ce%x00%ct%x00%B"
120
- parts = _run_git("show", "-s", f"--format={fmt}", sha, cwd=root).split("\0")
121
- if len(parts) < 10:
122
- raise RuntimeError(f"unexpected git show output for {sha}")
123
- commit_sha, tree, parents_raw, an, ae, at, cn, ce, ct, body = parts[:10]
124
- parents = [p for p in parents_raw.split() if p]
125
- author = {"name": an, "email": ae, "date": _git_date(int(at))}
126
- committer = {"name": cn, "email": ce, "date": _git_date(int(ct))}
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.0"
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