gargoyle-receipts 0.21.0.dev0__tar.gz → 0.22.0.dev0__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.
- {gargoyle_receipts-0.21.0.dev0/gargoyle_receipts.egg-info → gargoyle_receipts-0.22.0.dev0}/PKG-INFO +5 -3
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/README.md +4 -2
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/cli.py +13 -2
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/doctor.py +11 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/evidence.py +25 -8
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/reconcile.py +14 -3
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0/gargoyle_receipts.egg-info}/PKG-INFO +5 -3
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/pyproject.toml +1 -1
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_doctor.py +7 -3
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_evidence.py +22 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_witness.py +14 -1
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/LICENSE +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/NOTICE +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/__init__.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/__main__.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/adapters.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/approval.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/approver_cli.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/approver_trust.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/approvers.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/claude_hook.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/consumer.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/emilia.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/emilia_runner.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/entra_login.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/envelope.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/execution.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/fs_server.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/globs.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/holdstore.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/holdtext.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/identity.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/issuer.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/issuer_service.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/jws.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/key_record_source.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/mcp_server.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/notify.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/oauth_broker.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/opa.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/operations.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/pending_store.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/plans.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/policy.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/published_record_source.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/remote.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/replay_cache.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/revert.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/rulecheck.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/rules.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/sidedoors.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/specs/graph-reference.json +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/state.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/static/approve.html +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/static/approve.js +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/verifier.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/witness.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle_receipts.egg-info/SOURCES.txt +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle_receipts.egg-info/dependency_links.txt +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle_receipts.egg-info/entry_points.txt +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle_receipts.egg-info/requires.txt +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle_receipts.egg-info/top_level.txt +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/setup.cfg +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_annotations.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_approval_views.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_approver_cli.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_approvers.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_canonical.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_claude_hook.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_cli.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_console_js.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_consumer.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_declines.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_emilia_interop.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_emilia_runner.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_entra_identity.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_entra_login.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_env_file.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_execution.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_fs_server.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_globs.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_graph.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_hardening_2026_11_01.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_historical.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_identity.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_issuer_service.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_jws.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_mcp_proxy.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_mcp_self_issue.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_mcp_server.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_notify.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_oauth_broker.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_opa.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_operability.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_operations.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_plans.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_policy.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_published_record.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_record_follow.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_remote.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_revert.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_review_fixes.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_rulecheck.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_rules.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_sidedoors.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_state.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_state_text.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_stdio_encoding.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_threshold.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_typescript_conformance.py +0 -0
- {gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_wire_spec.py +0 -0
{gargoyle_receipts-0.21.0.dev0/gargoyle_receipts.egg-info → gargoyle_receipts-0.22.0.dev0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gargoyle-receipts
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.22.0.dev0
|
|
4
4
|
Summary: Signed, single-use, argument-bound receipts and human-approval holds for agent tool calls.
|
|
5
5
|
Author: Justin LaPorte
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -64,7 +64,7 @@ by whom, and what changed.
|
|
|
64
64
|
|
|
65
65
|
## Status
|
|
66
66
|
|
|
67
|
-
Pre-release `0.
|
|
67
|
+
Pre-release `0.22.0.dev0` on PyPI as `gargoyle-receipts`. Wire format
|
|
68
68
|
`gargoyle/3`, evidence log `gargoyle-evidence/4`, witness
|
|
69
69
|
`gargoyle-witness/1`; all three are specified in [SPEC.md](SPEC.md) and may
|
|
70
70
|
still change before a first release.
|
|
@@ -91,7 +91,7 @@ against a fake Graph server); the OAuth broker for hosted connectors
|
|
|
91
91
|
(deployed, metadata and redirect checks verified, no human sign-in through a
|
|
92
92
|
connector yet).
|
|
93
93
|
|
|
94
|
-
**Assurance.**
|
|
94
|
+
**Assurance.** 565 Python tests on Linux and Windows across Python 3.12,
|
|
95
95
|
3.13 and 3.14, at dependency floors and at a lock file; a TypeScript suite
|
|
96
96
|
that recomputes the conformance vectors byte for byte; two outside,
|
|
97
97
|
model-driven security reviews on 2026-10-20 (ten findings, all fixed); the
|
|
@@ -682,6 +682,8 @@ keys (both at once when a pinned key is also set).
|
|
|
682
682
|
| `GARGOYLE_APPROVERS_PATH`, `GARGOYLE_APPROVER_ROLE`, `GARGOYLE_APPROVER_GROUPS` | where enrolled approvers persist (default `approvers.json` beside the keys, with a `revoked` list, re-read when its content changes) and which Entra app role (default `Approver`) or groups may enroll, see every hold and the witness |
|
|
683
683
|
| `GARGOYLE_HOLDS_PATH` | SQLite file for holds, grants, declines and plans, so a restart loses nothing a person is acting on (`gargoyle issuer` sets `holds.sqlite` beside the keys); `GET /health` reports `persistent` |
|
|
684
684
|
| `GARGOYLE_MAX_OPEN_HOLDS` | how many held receipts and plans one principal may have waiting at once (default 20); the call over the cap is `REF_ISSUER_TOO_MANY_HOLDS` and nothing is stored |
|
|
685
|
+
| `GARGOYLE_DECLINE_LIMIT`, `GARGOYLE_DECLINE_WINDOW_HOURS` | the decline cooldown: after this many declines of the same (principal, tool, path) within the window (defaults 2 and 24), the issuer refuses the next ask with `REF_ISSUER_DECLINED_RECENTLY` instead of holding it again, so an approver is not asked the same question a third time in a day; `0` disables |
|
|
686
|
+
| `GARGOYLE_RECEIPT_TTL_SECONDS` | how long an issued receipt stays presentable (default 300); a held receipt's hold lives up to six times that, capped at an hour |
|
|
685
687
|
| `GARGOYLE_ALLOW_SELF_APPROVAL` | `1` lets a person release their own call; by default the approver may be neither the hold's principal nor the party acted for (`REF_ISSUER_SELF_APPROVAL`). A grant for an expired hold is `REF_ISSUER_HOLD_EXPIRED` and the hold is dropped |
|
|
686
688
|
| `GARGOYLE_WITNESS_PATH` | the issuer's signed, hash-chained record of every decision (`gargoyle issuer` sets `witness.jsonl` beside the keys) |
|
|
687
689
|
| `GARGOYLE_HOLD_WEBHOOK_URL`, `GARGOYLE_APPROVE_URL` | POST a notice of every new hold (Slack incoming-webhook shape, `text` plus the hold) with a link to the console; notice only, the grant still needs the approver's key |
|
|
@@ -26,7 +26,7 @@ by whom, and what changed.
|
|
|
26
26
|
|
|
27
27
|
## Status
|
|
28
28
|
|
|
29
|
-
Pre-release `0.
|
|
29
|
+
Pre-release `0.22.0.dev0` on PyPI as `gargoyle-receipts`. Wire format
|
|
30
30
|
`gargoyle/3`, evidence log `gargoyle-evidence/4`, witness
|
|
31
31
|
`gargoyle-witness/1`; all three are specified in [SPEC.md](SPEC.md) and may
|
|
32
32
|
still change before a first release.
|
|
@@ -53,7 +53,7 @@ against a fake Graph server); the OAuth broker for hosted connectors
|
|
|
53
53
|
(deployed, metadata and redirect checks verified, no human sign-in through a
|
|
54
54
|
connector yet).
|
|
55
55
|
|
|
56
|
-
**Assurance.**
|
|
56
|
+
**Assurance.** 565 Python tests on Linux and Windows across Python 3.12,
|
|
57
57
|
3.13 and 3.14, at dependency floors and at a lock file; a TypeScript suite
|
|
58
58
|
that recomputes the conformance vectors byte for byte; two outside,
|
|
59
59
|
model-driven security reviews on 2026-10-20 (ten findings, all fixed); the
|
|
@@ -644,6 +644,8 @@ keys (both at once when a pinned key is also set).
|
|
|
644
644
|
| `GARGOYLE_APPROVERS_PATH`, `GARGOYLE_APPROVER_ROLE`, `GARGOYLE_APPROVER_GROUPS` | where enrolled approvers persist (default `approvers.json` beside the keys, with a `revoked` list, re-read when its content changes) and which Entra app role (default `Approver`) or groups may enroll, see every hold and the witness |
|
|
645
645
|
| `GARGOYLE_HOLDS_PATH` | SQLite file for holds, grants, declines and plans, so a restart loses nothing a person is acting on (`gargoyle issuer` sets `holds.sqlite` beside the keys); `GET /health` reports `persistent` |
|
|
646
646
|
| `GARGOYLE_MAX_OPEN_HOLDS` | how many held receipts and plans one principal may have waiting at once (default 20); the call over the cap is `REF_ISSUER_TOO_MANY_HOLDS` and nothing is stored |
|
|
647
|
+
| `GARGOYLE_DECLINE_LIMIT`, `GARGOYLE_DECLINE_WINDOW_HOURS` | the decline cooldown: after this many declines of the same (principal, tool, path) within the window (defaults 2 and 24), the issuer refuses the next ask with `REF_ISSUER_DECLINED_RECENTLY` instead of holding it again, so an approver is not asked the same question a third time in a day; `0` disables |
|
|
648
|
+
| `GARGOYLE_RECEIPT_TTL_SECONDS` | how long an issued receipt stays presentable (default 300); a held receipt's hold lives up to six times that, capped at an hour |
|
|
647
649
|
| `GARGOYLE_ALLOW_SELF_APPROVAL` | `1` lets a person release their own call; by default the approver may be neither the hold's principal nor the party acted for (`REF_ISSUER_SELF_APPROVAL`). A grant for an expired hold is `REF_ISSUER_HOLD_EXPIRED` and the hold is dropped |
|
|
648
650
|
| `GARGOYLE_WITNESS_PATH` | the issuer's signed, hash-chained record of every decision (`gargoyle issuer` sets `witness.jsonl` beside the keys) |
|
|
649
651
|
| `GARGOYLE_HOLD_WEBHOOK_URL`, `GARGOYLE_APPROVE_URL` | POST a notice of every new hold (Slack incoming-webhook shape, `text` plus the hold) with a link to the console; notice only, the grant still needs the approver's key |
|
|
@@ -817,6 +817,12 @@ def cmd_audit(args) -> int:
|
|
|
817
817
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
818
818
|
from gargoyle.execution import verify_chain
|
|
819
819
|
|
|
820
|
+
# A missing log is a usage error with the path named, not a traceback: a
|
|
821
|
+
# client whose evidence log was rotated or has not been written yet.
|
|
822
|
+
if getattr(args, "log", None) and not Path(args.log).is_file():
|
|
823
|
+
print("evidence log not found: %s (nothing recorded there yet, or the log was rotated under another name)"
|
|
824
|
+
% Path(args.log).resolve(), file=sys.stderr)
|
|
825
|
+
return 2
|
|
820
826
|
out = _audit_trust_dir(args)
|
|
821
827
|
if out is None:
|
|
822
828
|
print("public-keys.json not found in %s%s: pass --out DIR, the folder that holds public-keys.json (and approvers.json), "
|
|
@@ -908,8 +914,9 @@ def cmd_audit(args) -> int:
|
|
|
908
914
|
r = reconcile(entries, read_witness(args.witness), principal=principal, target_url=target)
|
|
909
915
|
older = [f for f in r["findings"] if f["finding"] == "PREDATES_WITNESS"]
|
|
910
916
|
before_log = [f for f in r["findings"] if f["finding"] == "PREDATES_EVIDENCE"]
|
|
917
|
+
in_flight = [f for f in r["findings"] if f["finding"] == "IN_FLIGHT"]
|
|
911
918
|
for f in r["findings"]:
|
|
912
|
-
if f["finding"] in ("PREDATES_WITNESS", "PREDATES_EVIDENCE"):
|
|
919
|
+
if f["finding"] in ("PREDATES_WITNESS", "PREDATES_EVIDENCE", "IN_FLIGHT"):
|
|
913
920
|
continue
|
|
914
921
|
print(" FINDING %-22s %s" % (f["finding"], " ".join("%s=%s" % (k, str(v)[:40]) for k, v in f.items() if k != "finding")))
|
|
915
922
|
if older:
|
|
@@ -920,7 +927,11 @@ def cmd_audit(args) -> int:
|
|
|
920
927
|
print(" note: %d receipt(s) the issuer issued to this principal predate this evidence log (first entry %s; %d of them held): "
|
|
921
928
|
"a log that began later cannot be asked about them. If the log was not meant to start then, that is the finding."
|
|
922
929
|
% (len(before_log), (r.get("log_start") or "?")[:19], held_n))
|
|
923
|
-
|
|
930
|
+
if in_flight:
|
|
931
|
+
print(" note: %d receipt(s) issued after this log's last entry (%s) have no row yet: in flight when the log was read "
|
|
932
|
+
"(this audit's own gated command, a call still held). Audit again later; one still missing then is MISSING_EVIDENCE."
|
|
933
|
+
% (len(in_flight), (r.get("log_end") or "?")[:19]))
|
|
934
|
+
hard = len(r["findings"]) - len(older) - len(before_log) - len(in_flight)
|
|
924
935
|
print("reconciled: %d issued by the issuer, %d in evidence, %d finding(s)%s"
|
|
925
936
|
% (r["issued"], r["evidence_with_receipt"], hard,
|
|
926
937
|
" (%d issued for other targets, not this gate's)" % r["issued_elsewhere"] if r.get("issued_elsewhere") else ""))
|
|
@@ -177,6 +177,17 @@ def check_issuer(env: Dict[str, str], ctx: Context) -> Dict[str, Any]:
|
|
|
177
177
|
if isinstance(head, dict):
|
|
178
178
|
detail += ", witness seq %s" % head.get("seq")
|
|
179
179
|
date = None
|
|
180
|
+
try:
|
|
181
|
+
date = r.headers.get("Date") if hasattr(r, "headers") else None
|
|
182
|
+
except Exception:
|
|
183
|
+
date = None
|
|
184
|
+
if h.get("enrolled_approvers") == 1:
|
|
185
|
+
# One browser key between every hold and its release: lost, re-enrolled
|
|
186
|
+
# or on leave, nothing is released until a second approver exists.
|
|
187
|
+
return {"check": Check("issuer", WARN, detail,
|
|
188
|
+
"one approver enrolled: a lost or re-enrolled browser key, or an absence, leaves every hold waiting; enroll a second approver (a deputy role) and test a release by each"),
|
|
189
|
+
"health": h, "date": date}
|
|
190
|
+
date = None
|
|
180
191
|
try:
|
|
181
192
|
date = r.headers.get("Date") if hasattr(r, "headers") else None
|
|
182
193
|
except Exception:
|
|
@@ -127,29 +127,46 @@ def build_entry(*, seq: int, prev_sha256: str, kind: str, tool: str, args: Any,
|
|
|
127
127
|
return entry
|
|
128
128
|
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
LOCK_WAIT_SECONDS = 60.0 # a lock not acquired in this long means a stuck holder; the caller fails closed
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def locked(lock_path: Any, wait_seconds: float = LOCK_WAIT_SECONDS):
|
|
134
|
+
"""An exclusive lock on `lock_path` (POSIX flock, Windows byte-range
|
|
135
|
+
lock), released on exit. Processes that share a file on one workstation,
|
|
136
|
+
the hook's PreToolUse and PostToolUse among them, hold it across their
|
|
137
|
+
read-modify-write so neither loses the other's update. The wait is
|
|
138
|
+
bounded: a holder that never lets go (a hung process) raises OSError
|
|
139
|
+
after `wait_seconds` rather than hanging every later call, and a gate
|
|
140
|
+
that cannot record does not let the call through."""
|
|
135
141
|
import contextlib
|
|
142
|
+
import time as _time
|
|
136
143
|
|
|
137
144
|
@contextlib.contextmanager
|
|
138
145
|
def hold():
|
|
139
146
|
with open(lock_path, "a+b") as lock:
|
|
147
|
+
deadline = _time.monotonic() + wait_seconds
|
|
140
148
|
try:
|
|
141
149
|
import fcntl
|
|
142
|
-
|
|
150
|
+
while True:
|
|
151
|
+
try:
|
|
152
|
+
fcntl.flock(lock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
153
|
+
break
|
|
154
|
+
except OSError:
|
|
155
|
+
if _time.monotonic() >= deadline:
|
|
156
|
+
raise OSError("lock not acquired within %.0f s: %s (a stuck holder?)" % (wait_seconds, lock_path))
|
|
157
|
+
_time.sleep(0.02)
|
|
143
158
|
unlock = lambda: fcntl.flock(lock.fileno(), fcntl.LOCK_UN)
|
|
144
159
|
except ImportError: # Windows
|
|
145
160
|
import msvcrt
|
|
146
161
|
lock.seek(0)
|
|
147
162
|
while True:
|
|
148
163
|
try:
|
|
149
|
-
msvcrt.locking(lock.fileno(), msvcrt.
|
|
164
|
+
msvcrt.locking(lock.fileno(), msvcrt.LK_NBLCK, 1)
|
|
150
165
|
break
|
|
151
166
|
except OSError:
|
|
152
|
-
|
|
167
|
+
if _time.monotonic() >= deadline:
|
|
168
|
+
raise OSError("lock not acquired within %.0f s: %s (a stuck holder?)" % (wait_seconds, lock_path))
|
|
169
|
+
_time.sleep(0.02)
|
|
153
170
|
unlock = lambda: (lock.seek(0), msvcrt.locking(lock.fileno(), msvcrt.LK_UNLCK, 1))
|
|
154
171
|
try:
|
|
155
172
|
yield
|
|
@@ -64,6 +64,12 @@ def reconcile(evidence: Iterable[Any], witness: Iterable[Any], *, principal: Opt
|
|
|
64
64
|
# a log missing its head is a chain failure, and what the head would
|
|
65
65
|
# have held stays MISSING_EVIDENCE.
|
|
66
66
|
log_start = _parse(first.get("at"))
|
|
67
|
+
# The log's last entry: an issuance newer than it is a call whose evidence
|
|
68
|
+
# row the executor had not written when this log was read (the audit's own
|
|
69
|
+
# gated command, a call still waiting for its approver). Reported as
|
|
70
|
+
# IN_FLIGHT, information, not failure; the next audit settles it.
|
|
71
|
+
last = next((e for e in reversed(evidence) if isinstance(e, dict)), None)
|
|
72
|
+
log_end: Optional[datetime] = _parse(last.get("at")) if last is not None else None
|
|
67
73
|
for e in evidence:
|
|
68
74
|
if not isinstance(e, dict):
|
|
69
75
|
continue
|
|
@@ -115,13 +121,18 @@ def reconcile(evidence: Iterable[Any], witness: Iterable[Any], *, principal: Opt
|
|
|
115
121
|
at_w = _parse(w.get("at"))
|
|
116
122
|
# Every issuance precedes its own evidence entry by a little; only one
|
|
117
123
|
# older than the log's first entry by more than the window predates it.
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
if log_start is not None and at_w is not None and (log_start - at_w).total_seconds() > window_seconds:
|
|
125
|
+
kind = "PREDATES_EVIDENCE"
|
|
126
|
+
elif log_end is not None and at_w is not None and at_w > log_end:
|
|
127
|
+
kind = "IN_FLIGHT"
|
|
128
|
+
else:
|
|
129
|
+
kind = "MISSING_EVIDENCE"
|
|
120
130
|
findings.append({"finding": kind, "decision_id": did, "tool": w.get("tool"), "principal": w.get("principal"),
|
|
121
131
|
"held": bool(w.get("held")), "at": w.get("at")})
|
|
122
|
-
hard = [f for f in findings if f["finding"] not in ("PREDATES_WITNESS", "PREDATES_EVIDENCE")]
|
|
132
|
+
hard = [f for f in findings if f["finding"] not in ("PREDATES_WITNESS", "PREDATES_EVIDENCE", "IN_FLIGHT")]
|
|
123
133
|
return {"accepted": not hard, "findings": findings, "horizon": horizon.isoformat() if horizon else None,
|
|
124
134
|
"log_start": log_start.isoformat() if log_start else None,
|
|
135
|
+
"log_end": log_end.isoformat() if log_end else None,
|
|
125
136
|
"issued": len(issued), "evidence_with_receipt": len(seen_decisions), "issued_elsewhere": elsewhere}
|
|
126
137
|
|
|
127
138
|
|
{gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0/gargoyle_receipts.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gargoyle-receipts
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.22.0.dev0
|
|
4
4
|
Summary: Signed, single-use, argument-bound receipts and human-approval holds for agent tool calls.
|
|
5
5
|
Author: Justin LaPorte
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -64,7 +64,7 @@ by whom, and what changed.
|
|
|
64
64
|
|
|
65
65
|
## Status
|
|
66
66
|
|
|
67
|
-
Pre-release `0.
|
|
67
|
+
Pre-release `0.22.0.dev0` on PyPI as `gargoyle-receipts`. Wire format
|
|
68
68
|
`gargoyle/3`, evidence log `gargoyle-evidence/4`, witness
|
|
69
69
|
`gargoyle-witness/1`; all three are specified in [SPEC.md](SPEC.md) and may
|
|
70
70
|
still change before a first release.
|
|
@@ -91,7 +91,7 @@ against a fake Graph server); the OAuth broker for hosted connectors
|
|
|
91
91
|
(deployed, metadata and redirect checks verified, no human sign-in through a
|
|
92
92
|
connector yet).
|
|
93
93
|
|
|
94
|
-
**Assurance.**
|
|
94
|
+
**Assurance.** 565 Python tests on Linux and Windows across Python 3.12,
|
|
95
95
|
3.13 and 3.14, at dependency floors and at a lock file; a TypeScript suite
|
|
96
96
|
that recomputes the conformance vectors byte for byte; two outside,
|
|
97
97
|
model-driven security reviews on 2026-10-20 (ten findings, all fixed); the
|
|
@@ -682,6 +682,8 @@ keys (both at once when a pinned key is also set).
|
|
|
682
682
|
| `GARGOYLE_APPROVERS_PATH`, `GARGOYLE_APPROVER_ROLE`, `GARGOYLE_APPROVER_GROUPS` | where enrolled approvers persist (default `approvers.json` beside the keys, with a `revoked` list, re-read when its content changes) and which Entra app role (default `Approver`) or groups may enroll, see every hold and the witness |
|
|
683
683
|
| `GARGOYLE_HOLDS_PATH` | SQLite file for holds, grants, declines and plans, so a restart loses nothing a person is acting on (`gargoyle issuer` sets `holds.sqlite` beside the keys); `GET /health` reports `persistent` |
|
|
684
684
|
| `GARGOYLE_MAX_OPEN_HOLDS` | how many held receipts and plans one principal may have waiting at once (default 20); the call over the cap is `REF_ISSUER_TOO_MANY_HOLDS` and nothing is stored |
|
|
685
|
+
| `GARGOYLE_DECLINE_LIMIT`, `GARGOYLE_DECLINE_WINDOW_HOURS` | the decline cooldown: after this many declines of the same (principal, tool, path) within the window (defaults 2 and 24), the issuer refuses the next ask with `REF_ISSUER_DECLINED_RECENTLY` instead of holding it again, so an approver is not asked the same question a third time in a day; `0` disables |
|
|
686
|
+
| `GARGOYLE_RECEIPT_TTL_SECONDS` | how long an issued receipt stays presentable (default 300); a held receipt's hold lives up to six times that, capped at an hour |
|
|
685
687
|
| `GARGOYLE_ALLOW_SELF_APPROVAL` | `1` lets a person release their own call; by default the approver may be neither the hold's principal nor the party acted for (`REF_ISSUER_SELF_APPROVAL`). A grant for an expired hold is `REF_ISSUER_HOLD_EXPIRED` and the hold is dropped |
|
|
686
688
|
| `GARGOYLE_WITNESS_PATH` | the issuer's signed, hash-chained record of every decision (`gargoyle issuer` sets `witness.jsonl` beside the keys) |
|
|
687
689
|
| `GARGOYLE_HOLD_WEBHOOK_URL`, `GARGOYLE_APPROVE_URL` | POST a notice of every new hold (Slack incoming-webhook shape, `text` plus the hold) with a link to the console; notice only, the grant still needs the approver's key |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gargoyle-receipts"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.22.0.dev0"
|
|
8
8
|
description = "Signed, single-use, argument-bound receipts and human-approval holds for agent tool calls."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -53,7 +53,7 @@ class Http:
|
|
|
53
53
|
|
|
54
54
|
ISSUER = "https://192.168.56.101:8443"
|
|
55
55
|
HEALTH = {"ok": True, "policy": {"policy_id": "gargoyle.rules/1", "policy_sha256": "71145c21" + "0" * 56}, "entra": True,
|
|
56
|
-
"persistent": True, "pending_receipts": 0, "enrolled_approvers":
|
|
56
|
+
"persistent": True, "pending_receipts": 0, "enrolled_approvers": 2, "witness_head": {"seq": 533, "entry_sha256": "ab" * 32}}
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def _env(tmp_path, **over):
|
|
@@ -131,10 +131,14 @@ def test_issuer_tls_failure_points_at_requests_ca_bundle(tmp_path):
|
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
def test_issuer_health_detail_and_date(tmp_path):
|
|
134
|
+
http = Http({ISSUER + "/health": Resp(200, dict(HEALTH, enrolled_approvers=1), {"Date": "Sun, 01 Nov 2026 20:35:12 GMT"})})
|
|
135
|
+
r = check_issuer(_env(tmp_path), _ctx(tmp_path, http))
|
|
136
|
+
# One enrolled approver is a warning: one browser key between every hold and its release.
|
|
137
|
+
assert r["check"].status == WARN and "rules 71145c21" in r["check"].detail and "witness seq 533" in r["check"].detail
|
|
138
|
+
assert "second approver" in r["check"].fix and r["date"].startswith("Sun")
|
|
134
139
|
http = Http({ISSUER + "/health": Resp(200, HEALTH, {"Date": "Sun, 01 Nov 2026 20:35:12 GMT"})})
|
|
135
140
|
r = check_issuer(_env(tmp_path), _ctx(tmp_path, http))
|
|
136
|
-
assert r["check"].status == PASS and
|
|
137
|
-
assert r["date"].startswith("Sun")
|
|
141
|
+
assert r["check"].status == PASS and r["date"].startswith("Sun")
|
|
138
142
|
|
|
139
143
|
|
|
140
144
|
def test_clock_skew_levels(tmp_path):
|
|
@@ -209,3 +209,25 @@ def test_entries_name_the_client_and_older_versions_still_verify(tmp_path):
|
|
|
209
209
|
assert not _well_formed(v4_missing)
|
|
210
210
|
with pytest.raises(ValueError):
|
|
211
211
|
build_entry(seq=1, prev_sha256=GENESIS, kind=REFUSED, tool="t", args={}, principal=PRINCIPAL, reason="x", client=7)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_lock_wait_is_bounded(tmp_path):
|
|
215
|
+
# A holder that never lets go must not hang every later hook; the wait ends
|
|
216
|
+
# in an OSError the caller turns into a refusal.
|
|
217
|
+
import threading
|
|
218
|
+
from gargoyle.evidence import locked
|
|
219
|
+
lock_path = tmp_path / "audit.jsonl.lock"
|
|
220
|
+
held = threading.Event(); release = threading.Event()
|
|
221
|
+
|
|
222
|
+
def holder():
|
|
223
|
+
with locked(lock_path):
|
|
224
|
+
held.set(); release.wait(5)
|
|
225
|
+
t = threading.Thread(target=holder); t.start(); held.wait(5)
|
|
226
|
+
try:
|
|
227
|
+
with pytest.raises(OSError, match="not acquired"):
|
|
228
|
+
with locked(lock_path, wait_seconds=0.2):
|
|
229
|
+
pass
|
|
230
|
+
finally:
|
|
231
|
+
release.set(); t.join()
|
|
232
|
+
with locked(lock_path, wait_seconds=0.2):
|
|
233
|
+
pass
|
|
@@ -114,6 +114,15 @@ def test_honest_evidence_reconciles_and_tampering_is_found(tmp_path):
|
|
|
114
114
|
page = c.get("/witness?limit=1", headers=approver_h).json()
|
|
115
115
|
assert [e["seq"] for e in page["entries"]] == [4] and page["seq"] == 4
|
|
116
116
|
assert c.get("/witness?after=4", headers=approver_h).json() == {"entries": [], "seq": 4}
|
|
117
|
+
# An issuance newer than the log's last entry is in flight (the audit's own
|
|
118
|
+
# gated command, a call still held): information, settled by a later audit.
|
|
119
|
+
late = dict(witness[1], decision_id="just-issued", at=(datetime.now(timezone.utc) + timedelta(seconds=30)).isoformat())
|
|
120
|
+
r = reconcile(evidence, witness + [late], principal=PRINCIPAL)
|
|
121
|
+
assert r["accepted"] and [f["finding"] for f in r["findings"]] == ["IN_FLIGHT"] and r["log_end"]
|
|
122
|
+
# The same issuance older than the log's last entry is missing: its row had its chance.
|
|
123
|
+
early = dict(late, at=(datetime.now(timezone.utc) - timedelta(seconds=1)).isoformat())
|
|
124
|
+
r = reconcile(evidence, witness + [early], principal=PRINCIPAL)
|
|
125
|
+
assert not r["accepted"] and [f["finding"] for f in r["findings"]] == ["MISSING_EVIDENCE"]
|
|
117
126
|
# Evidence from before the witness began is information, not a finding.
|
|
118
127
|
old = dict(evidence[0], at="2026-01-01T00:00:00+00:00", receipt=dict(evidence[0]["receipt"], decision_id="before-the-witness"))
|
|
119
128
|
r = reconcile(evidence + [old], witness, principal=PRINCIPAL) # beside the real rows, not in place of one
|
|
@@ -139,7 +148,8 @@ def test_honest_evidence_reconciles_and_tampering_is_found(tmp_path):
|
|
|
139
148
|
at=(datetime.fromisoformat(evidence[0]["at"]) - timedelta(days=2)).isoformat())
|
|
140
149
|
r = reconcile(evidence, witness + [ancient], principal=PRINCIPAL)
|
|
141
150
|
assert r["accepted"] and [f["finding"] for f in r["findings"]] == ["PREDATES_EVIDENCE"] and r["log_start"] == evidence[0]["at"]
|
|
142
|
-
|
|
151
|
+
# Inside the log's span (before its last entry) and unrecorded: missing, not in flight.
|
|
152
|
+
later = dict(ancient, decision_id="d-later", at=(datetime.fromisoformat(evidence[-1]["at"]) - timedelta(microseconds=1)).isoformat())
|
|
143
153
|
r = reconcile(evidence, witness + [later], principal=PRINCIPAL)
|
|
144
154
|
assert not r["accepted"] and [f["finding"] for f in r["findings"]] == ["MISSING_EVIDENCE"]
|
|
145
155
|
|
|
@@ -211,6 +221,9 @@ def test_audit_names_a_missing_witness_or_folder_plainly(tmp_path, capsys):
|
|
|
211
221
|
assert "pass --log" in capsys.readouterr().err
|
|
212
222
|
assert main(["audit", "--out", str(out), "--check-tree", str(tmp_path)]) == 2
|
|
213
223
|
assert "pass --log" in capsys.readouterr().err
|
|
224
|
+
# A log that is not there (rotated, or never written) is named, not a traceback.
|
|
225
|
+
assert main(["audit", "--log", str(tmp_path / "gone.jsonl"), "--out", str(out)]) == 2
|
|
226
|
+
assert "evidence log not found" in capsys.readouterr().err
|
|
214
227
|
assert main(["audit", "--out", str(out)]) == 2
|
|
215
228
|
assert "nothing to verify" in capsys.readouterr().err
|
|
216
229
|
# The issuer creates the witness file at start, before any decision.
|
|
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
|
{gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/key_record_source.py
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
|
{gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/published_record_source.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/specs/graph-reference.json
RENAMED
|
File without changes
|
|
File without changes
|
{gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/gargoyle/static/approve.html
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
|
{gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_approval_views.py
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
|
{gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_emilia_interop.py
RENAMED
|
File without changes
|
|
File without changes
|
{gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_entra_identity.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_hardening_2026_11_01.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_issuer_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_mcp_self_issue.py
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
|
{gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_published_record.py
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
|
{gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_stdio_encoding.py
RENAMED
|
File without changes
|
|
File without changes
|
{gargoyle_receipts-0.21.0.dev0 → gargoyle_receipts-0.22.0.dev0}/tests/test_typescript_conformance.py
RENAMED
|
File without changes
|
|
File without changes
|