gitvow 0.28.3__tar.gz → 0.28.4__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 (78) hide show
  1. {gitvow-0.28.3/src/gitvow.egg-info → gitvow-0.28.4}/PKG-INFO +1 -1
  2. {gitvow-0.28.3 → gitvow-0.28.4}/pyproject.toml +1 -1
  3. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/hooks/__init__.py +14 -9
  4. {gitvow-0.28.3 → gitvow-0.28.4/src/gitvow.egg-info}/PKG-INFO +1 -1
  5. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow.egg-info/SOURCES.txt +1 -0
  6. gitvow-0.28.4/tests/test_answered_confirm_commit.py +35 -0
  7. {gitvow-0.28.3 → gitvow-0.28.4}/LICENSE +0 -0
  8. {gitvow-0.28.3 → gitvow-0.28.4}/README.md +0 -0
  9. {gitvow-0.28.3 → gitvow-0.28.4}/setup.cfg +0 -0
  10. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/__init__.py +0 -0
  11. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/__main__.py +0 -0
  12. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/adapters.py +0 -0
  13. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/claims.py +0 -0
  14. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/cli.py +0 -0
  15. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/collect.py +0 -0
  16. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/decisions.py +0 -0
  17. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/default_policy.json +0 -0
  18. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/digest.py +0 -0
  19. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/export.py +0 -0
  20. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/install.py +0 -0
  21. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/pack.py +0 -0
  22. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/paths.py +0 -0
  23. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/policy.py +0 -0
  24. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/pricing.py +0 -0
  25. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/proposals.py +0 -0
  26. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/providers.py +0 -0
  27. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/recall.py +0 -0
  28. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/redact.py +0 -0
  29. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/report.py +0 -0
  30. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/rules.py +0 -0
  31. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/safewrite.py +0 -0
  32. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/scan.py +0 -0
  33. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/selftest.py +0 -0
  34. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/serve.py +0 -0
  35. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/snapshots.py +0 -0
  36. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/state.py +0 -0
  37. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/status.py +0 -0
  38. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/sync.py +0 -0
  39. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/targets.py +0 -0
  40. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow/transcript.py +0 -0
  41. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow.egg-info/dependency_links.txt +0 -0
  42. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow.egg-info/entry_points.txt +0 -0
  43. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow.egg-info/requires.txt +0 -0
  44. {gitvow-0.28.3 → gitvow-0.28.4}/src/gitvow.egg-info/top_level.txt +0 -0
  45. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_adapters.py +0 -0
  46. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_claims.py +0 -0
  47. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_collect.py +0 -0
  48. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_command_position.py +0 -0
  49. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_commit_window.py +0 -0
  50. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_cost.py +0 -0
  51. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_decisions.py +0 -0
  52. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_digest.py +0 -0
  53. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_e2e_globalopt.py +0 -0
  54. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_export.py +0 -0
  55. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_external_adapter.py +0 -0
  56. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_git_rules.py +0 -0
  57. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_hardening.py +0 -0
  58. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_hooks.py +0 -0
  59. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_install_cli.py +0 -0
  60. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_pack.py +0 -0
  61. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_packaging.py +0 -0
  62. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_policy.py +0 -0
  63. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_policy_refresh.py +0 -0
  64. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_proposals.py +0 -0
  65. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_providers.py +0 -0
  66. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_recall.py +0 -0
  67. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_redact.py +0 -0
  68. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_report.py +0 -0
  69. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_rules.py +0 -0
  70. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_scan.py +0 -0
  71. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_serve.py +0 -0
  72. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_snapshots.py +0 -0
  73. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_status.py +0 -0
  74. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_sync.py +0 -0
  75. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_sync_http.py +0 -0
  76. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_targets.py +0 -0
  77. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_tiers.py +0 -0
  78. {gitvow-0.28.3 → gitvow-0.28.4}/tests/test_transcript_formats.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitvow
3
- Version: 0.28.3
3
+ Version: 0.28.4
4
4
  Summary: Provenance and policy gate for AI-agent coding sessions: session trailers on commits, redacted session notes in git, a tool-call gate, and a local ledger. No runtime dependencies.
5
5
  Author-email: Nikhil Bora <nikhil@wirevow.com>
6
6
  License: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gitvow"
7
- version = "0.28.3"
7
+ version = "0.28.4"
8
8
  description = "Provenance and policy gate for AI-agent coding sessions: session trailers on commits, redacted session notes in git, a tool-call gate, and a local ledger. No runtime dependencies."
9
9
  readme = "README.md"
10
10
  license = {text = "Apache-2.0"}
@@ -186,17 +186,22 @@ def pre_tool_use(h: dict[str, Any], home: str | None = None) -> tuple[int, str]:
186
186
  f = dict(d.findings[0], immediate=True)
187
187
  text = redact(f["finding"], rules) if rules is not None else f["finding"]
188
188
  prior = dec.session_answer(cwd, text)
189
- if session_scope and prior and prior.get("answer") == "accepted" and prior.get("scope") == "session":
190
- log_event(cwd, "allowed_by_session_answer", {**payload, "by": prior.get("by")})
191
- return 0, ""
192
- if prior and prior.get("answer") == "declined":
189
+ answered = bool(
190
+ session_scope and prior and prior.get("answer") == "accepted" and prior.get("scope") == "session"
191
+ )
192
+ if answered:
193
+ log_event(cwd, "allowed_by_session_answer", {**payload, "by": prior.get("by")}) # type: ignore[union-attr]
194
+ # Do not return yet: the same command may also commit (`git commit && git push`), and until 0.28.4 the
195
+ # early return here skipped the commit bookkeeping below, so the commit lost its session trailer.
196
+ elif prior and prior.get("answer") == "declined":
193
197
  log_event(cwd, "confirm_required", {**payload, "declined_by": prior.get("by")})
194
198
  return 2, f"BLOCKED: {text} was declined by {prior.get('by')} this session ({d.reason})."
195
- st = load_state(cwd)
196
- dec.add(cwd, [f], st.get("steps", 0) + 1, tool, rules)
197
- n = next((x["n"] for x in dec.open_findings(cwd) if x["finding"] == text), None)
198
- log_event(cwd, "confirm_required", {**payload, "finding": n})
199
- return 2, confirm_message(d, n, session_scope)
199
+ if not answered:
200
+ st = load_state(cwd)
201
+ dec.add(cwd, [f], st.get("steps", 0) + 1, tool, rules)
202
+ n = next((x["n"] for x in dec.open_findings(cwd) if x["finding"] == text), None)
203
+ log_event(cwd, "confirm_required", {**payload, "finding": n})
204
+ return 2, confirm_message(d, n, session_scope)
200
205
  if d.deferred or d.observed:
201
206
  st = load_state(cwd)
202
207
  new = dec.add(cwd, list(d.findings), st.get("steps", 0) + 1, tool, rules)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitvow
3
- Version: 0.28.3
3
+ Version: 0.28.4
4
4
  Summary: Provenance and policy gate for AI-agent coding sessions: session trailers on commits, redacted session notes in git, a tool-call gate, and a local ledger. No runtime dependencies.
5
5
  Author-email: Nikhil Bora <nikhil@wirevow.com>
6
6
  License: Apache-2.0
@@ -40,6 +40,7 @@ src/gitvow.egg-info/requires.txt
40
40
  src/gitvow.egg-info/top_level.txt
41
41
  src/gitvow/hooks/__init__.py
42
42
  tests/test_adapters.py
43
+ tests/test_answered_confirm_commit.py
43
44
  tests/test_claims.py
44
45
  tests/test_collect.py
45
46
  tests/test_command_position.py
@@ -0,0 +1,35 @@
1
+ """0.28.4: a command that both commits and runs a session-answered confirm still gets its commit bookkeeping.
2
+
3
+ Found on the founder's machine: after a push to main had been accepted for the session, `git commit … && git push`
4
+ in one command was allowed by the session answer and returned before the commit flag was set, so the commit carried
5
+ no session trailer. Three of four repositories lost the trailer on the same evening the same way.
6
+ """
7
+
8
+ import json
9
+
10
+ from gitvow import decisions as dec
11
+ from gitvow.hooks import pre_tool_use, session_start
12
+ from gitvow.install import _write_git_hook
13
+ from gitvow.policy import load_policy
14
+ from tests.conftest import git
15
+
16
+
17
+ def test_session_answered_push_in_the_same_command_as_a_commit_keeps_the_trailer(repo, home, payload):
18
+ _write_git_hook(str(repo / ".gitvow" / "git-hooks"))
19
+ git(repo, "config", "core.hooksPath", ".gitvow/git-hooks")
20
+ git(repo, "remote", "add", "origin", "git@example.com:acme/ledger.git")
21
+ session_start(payload("SessionStart"), str(home))
22
+ cmd = {"command": 'git commit -qm "x" && git push origin main'}
23
+ code, msg = pre_tool_use(payload("PreToolUse", "Bash", cmd), str(home))
24
+ assert code == 2 and "pushing to a protected branch" in msg
25
+ dec.decide(str(repo), "1", "accept", load_policy(str(repo), str(home)), scope="session", reason="ok", rules=[])
26
+ code, msg = pre_tool_use(payload("PreToolUse", "Bash", cmd), str(home))
27
+ assert code == 0
28
+ st = json.loads((repo / ".git" / "gitvow-session.json").read_text())
29
+ assert st.get("pending_commit") and st["steps"] == 1
30
+ (repo / "a.txt").write_text("y\n")
31
+ git(repo, "commit", "-qam", "after the answer")
32
+ body = git(repo, "log", "-1", "--format=%B")
33
+ assert "Gitvow-Session: sess-1" in body and "Gitvow-Accepted: run git (pushing to a protected branch)" in body
34
+ log = (repo / ".git" / "gitvow-hooks.log").read_text()
35
+ assert '"kind": "allowed_by_session_answer"' in log
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
File without changes
File without changes
File without changes
File without changes