two-key 0.7.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.
- two_key-0.7.0/AGENTS.md +316 -0
- two_key-0.7.0/CHANGES.md +3832 -0
- two_key-0.7.0/CONTRIBUTING.md +53 -0
- two_key-0.7.0/LICENSE +201 -0
- two_key-0.7.0/MANIFEST.in +16 -0
- two_key-0.7.0/PKG-INFO +783 -0
- two_key-0.7.0/README.md +743 -0
- two_key-0.7.0/ROADMAP.md +724 -0
- two_key-0.7.0/SECURITY.md +158 -0
- two_key-0.7.0/docs/COMPARISON.md +45 -0
- two_key-0.7.0/docs/CONNECTING.md +519 -0
- two_key-0.7.0/docs/FIT.md +94 -0
- two_key-0.7.0/docs/HOWTO.md +2568 -0
- two_key-0.7.0/docs/JUDGES_AND_QUORUM.md +199 -0
- two_key-0.7.0/docs/PYPI.md +197 -0
- two_key-0.7.0/docs/SCANNERS.md +796 -0
- two_key-0.7.0/docs/SCOPE.md +162 -0
- two_key-0.7.0/docs/SIEM.md +337 -0
- two_key-0.7.0/docs/THREAT_MODEL.md +791 -0
- two_key-0.7.0/docs/images/two-key-banner.png +0 -0
- two_key-0.7.0/examples/agents.yaml +55 -0
- two_key-0.7.0/examples/constitution.md +18 -0
- two_key-0.7.0/examples/custom_fields.yaml +76 -0
- two_key-0.7.0/examples/hard_rules.yaml +72 -0
- two_key-0.7.0/examples/judges.yaml +115 -0
- two_key-0.7.0/examples/scanning.yaml +51 -0
- two_key-0.7.0/examples/siem/elastic/README.md +118 -0
- two_key-0.7.0/examples/siem/elastic/dashboard.ndjson +6 -0
- two_key-0.7.0/examples/siem/elastic/docker-compose.yml +35 -0
- two_key-0.7.0/examples/siem/elastic/ingest-pipeline.json +39 -0
- two_key-0.7.0/examples/siem/elastic/make-certs.sh +45 -0
- two_key-0.7.0/examples/siem/elastic/receiver.py +328 -0
- two_key-0.7.0/examples/siem/elastic/siem.yaml +21 -0
- two_key-0.7.0/llms.txt +197 -0
- two_key-0.7.0/pyproject.toml +68 -0
- two_key-0.7.0/setup.cfg +4 -0
- two_key-0.7.0/tests/test_action_claim.py +83 -0
- two_key-0.7.0/tests/test_agent_access.py +144 -0
- two_key-0.7.0/tests/test_agent_decline.py +126 -0
- two_key-0.7.0/tests/test_agent_metadata.py +184 -0
- two_key-0.7.0/tests/test_agent_prompt.py +184 -0
- two_key-0.7.0/tests/test_args_size.py +95 -0
- two_key-0.7.0/tests/test_audit_digests.py +64 -0
- two_key-0.7.0/tests/test_b5_local_proxy.py +262 -0
- two_key-0.7.0/tests/test_ballot_identity.py +60 -0
- two_key-0.7.0/tests/test_caller_facts.py +266 -0
- two_key-0.7.0/tests/test_capability_key.py +162 -0
- two_key-0.7.0/tests/test_case_exact.py +148 -0
- two_key-0.7.0/tests/test_cli_tokens.py +233 -0
- two_key-0.7.0/tests/test_cli_witness.py +141 -0
- two_key-0.7.0/tests/test_cloud_model_any_judge.py +46 -0
- two_key-0.7.0/tests/test_config_strict.py +28 -0
- two_key-0.7.0/tests/test_credential_fingerprint.py +131 -0
- two_key-0.7.0/tests/test_custom_fields.py +746 -0
- two_key-0.7.0/tests/test_data_class_sets.py +126 -0
- two_key-0.7.0/tests/test_decision_record.py +80 -0
- two_key-0.7.0/tests/test_deep_args.py +79 -0
- two_key-0.7.0/tests/test_depth_boundary.py +226 -0
- two_key-0.7.0/tests/test_derive_robustness.py +118 -0
- two_key-0.7.0/tests/test_doc_diagrams.py +23 -0
- two_key-0.7.0/tests/test_doc_toc.py +99 -0
- two_key-0.7.0/tests/test_domain_allow.py +155 -0
- two_key-0.7.0/tests/test_duplicate_judge_ids.py +52 -0
- two_key-0.7.0/tests/test_fail_closed.py +138 -0
- two_key-0.7.0/tests/test_field_decisions.py +187 -0
- two_key-0.7.0/tests/test_fields_compat.py +74 -0
- two_key-0.7.0/tests/test_gateway_boundary.py +122 -0
- two_key-0.7.0/tests/test_gateway_denied.py +271 -0
- two_key-0.7.0/tests/test_gateway_locks.py +131 -0
- two_key-0.7.0/tests/test_guard.py +135 -0
- two_key-0.7.0/tests/test_in_process_agent.py +58 -0
- two_key-0.7.0/tests/test_issue_fixes.py +264 -0
- two_key-0.7.0/tests/test_judge_agent_separation.py +578 -0
- two_key-0.7.0/tests/test_judge_connection_reuse.py +97 -0
- two_key-0.7.0/tests/test_judge_hook_spec.py +35 -0
- two_key-0.7.0/tests/test_judge_locality.py +119 -0
- two_key-0.7.0/tests/test_key_backup.py +461 -0
- two_key-0.7.0/tests/test_ledger_error_cap.py +425 -0
- two_key-0.7.0/tests/test_ledger_key_file.py +83 -0
- two_key-0.7.0/tests/test_ledger_scale.py +98 -0
- two_key-0.7.0/tests/test_ledger_shared.py +87 -0
- two_key-0.7.0/tests/test_legacy_formats.py +78 -0
- two_key-0.7.0/tests/test_lone_judge.py +55 -0
- two_key-0.7.0/tests/test_maker_case.py +115 -0
- two_key-0.7.0/tests/test_mapping_inputs.py +190 -0
- two_key-0.7.0/tests/test_mcp_docs.py +54 -0
- two_key-0.7.0/tests/test_mcp_draft.py +190 -0
- two_key-0.7.0/tests/test_mcp_proxy.py +371 -0
- two_key-0.7.0/tests/test_mcp_proxy_sdk.py +270 -0
- two_key-0.7.0/tests/test_mcp_scan.py +252 -0
- two_key-0.7.0/tests/test_measure_once.py +92 -0
- two_key-0.7.0/tests/test_netloc_allowlist.py +51 -0
- two_key-0.7.0/tests/test_no_bidi_chars.py +36 -0
- two_key-0.7.0/tests/test_origin_argument.py +139 -0
- two_key-0.7.0/tests/test_payload_paths.py +95 -0
- two_key-0.7.0/tests/test_platform.py +199 -0
- two_key-0.7.0/tests/test_problem_f.py +582 -0
- two_key-0.7.0/tests/test_proposal_duplicate_keys.py +27 -0
- two_key-0.7.0/tests/test_proposal_text.py +112 -0
- two_key-0.7.0/tests/test_recover_and_anchor.py +247 -0
- two_key-0.7.0/tests/test_required_yes_default.py +51 -0
- two_key-0.7.0/tests/test_same_agent_rule.py +569 -0
- two_key-0.7.0/tests/test_save_envelope.py +58 -0
- two_key-0.7.0/tests/test_scan.py +734 -0
- two_key-0.7.0/tests/test_scan_encrypted.py +244 -0
- two_key-0.7.0/tests/test_scanners.py +476 -0
- two_key-0.7.0/tests/test_siem.py +660 -0
- two_key-0.7.0/tests/test_siem_export.py +283 -0
- two_key-0.7.0/tests/test_siem_sinks.py +529 -0
- two_key-0.7.0/tests/test_strict_parsing.py +144 -0
- two_key-0.7.0/tests/test_tenant_empty.py +23 -0
- two_key-0.7.0/tests/test_tenant_optin.py +137 -0
- two_key-0.7.0/tests/test_text_io.py +77 -0
- two_key-0.7.0/tests/test_tmp_hygiene.py +51 -0
- two_key-0.7.0/tests/test_token_lifetime.py +123 -0
- two_key-0.7.0/tests/test_two_key.py +455 -0
- two_key-0.7.0/tests/test_upgrade_notes.py +57 -0
- two_key-0.7.0/tests/test_upstream_fold.py +24 -0
- two_key-0.7.0/tests/test_witness_pin.py +442 -0
- two_key-0.7.0/tests/test_writable_paths.py +271 -0
- two_key-0.7.0/two_key/__init__.py +13 -0
- two_key-0.7.0/two_key/__main__.py +3 -0
- two_key-0.7.0/two_key/_platform.py +597 -0
- two_key-0.7.0/two_key/action.py +199 -0
- two_key-0.7.0/two_key/agent_access.py +96 -0
- two_key-0.7.0/two_key/agent_meta.py +139 -0
- two_key-0.7.0/two_key/agents.py +455 -0
- two_key-0.7.0/two_key/audit.py +90 -0
- two_key-0.7.0/two_key/backup.py +783 -0
- two_key-0.7.0/two_key/canonical.py +181 -0
- two_key-0.7.0/two_key/capability.py +222 -0
- two_key-0.7.0/two_key/cli.py +719 -0
- two_key-0.7.0/two_key/compiler.py +55 -0
- two_key-0.7.0/two_key/constitution.py +547 -0
- two_key-0.7.0/two_key/core.py +769 -0
- two_key-0.7.0/two_key/derive.py +593 -0
- two_key-0.7.0/two_key/fields.py +577 -0
- two_key-0.7.0/two_key/gateway.py +448 -0
- two_key-0.7.0/two_key/guard.py +205 -0
- two_key-0.7.0/two_key/identity.py +1106 -0
- two_key-0.7.0/two_key/judges/__init__.py +14 -0
- two_key-0.7.0/two_key/judges/anthropic.py +80 -0
- two_key-0.7.0/two_key/judges/base.py +103 -0
- two_key-0.7.0/two_key/judges/config.py +240 -0
- two_key-0.7.0/two_key/judges/credentials.py +154 -0
- two_key-0.7.0/two_key/judges/gemini.py +56 -0
- two_key-0.7.0/two_key/judges/llm.py +368 -0
- two_key-0.7.0/two_key/judges/ollama.py +56 -0
- two_key-0.7.0/two_key/judges/openai_compat.py +101 -0
- two_key-0.7.0/two_key/judges/transport.py +152 -0
- two_key-0.7.0/two_key/keys.py +140 -0
- two_key-0.7.0/two_key/ledger.py +1268 -0
- two_key-0.7.0/two_key/mcp_proxy/__init__.py +10 -0
- two_key-0.7.0/two_key/mcp_proxy/config.py +408 -0
- two_key-0.7.0/two_key/mcp_proxy/core.py +568 -0
- two_key-0.7.0/two_key/mcp_proxy/draft.py +301 -0
- two_key-0.7.0/two_key/mcp_proxy/sdk.py +226 -0
- two_key-0.7.0/two_key/netloc.py +96 -0
- two_key-0.7.0/two_key/policy_vm.py +342 -0
- two_key-0.7.0/two_key/quorum.py +515 -0
- two_key-0.7.0/two_key/scan.py +795 -0
- two_key-0.7.0/two_key/scanners.py +557 -0
- two_key-0.7.0/two_key/siem.py +1001 -0
- two_key-0.7.0/two_key/siem_export.py +266 -0
- two_key-0.7.0/two_key/siem_sinks.py +497 -0
- two_key-0.7.0/two_key/strict.py +141 -0
- two_key-0.7.0/two_key/testing.py +72 -0
- two_key-0.7.0/two_key/writable.py +200 -0
- two_key-0.7.0/two_key.egg-info/PKG-INFO +783 -0
- two_key-0.7.0/two_key.egg-info/SOURCES.txt +172 -0
- two_key-0.7.0/two_key.egg-info/dependency_links.txt +1 -0
- two_key-0.7.0/two_key.egg-info/entry_points.txt +2 -0
- two_key-0.7.0/two_key.egg-info/requires.txt +8 -0
- two_key-0.7.0/two_key.egg-info/top_level.txt +1 -0
two_key-0.7.0/AGENTS.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for coding agents working in this clone of
|
|
4
|
+
[Insomniac-VibeLabs/two-key](https://github.com/Insomniac-VibeLabs/two-key).
|
|
5
|
+
|
|
6
|
+
This file is a short contributor guide for agents. Product and API context for
|
|
7
|
+
any LLM reader is in [llms.txt](llms.txt). Do not replace one with the other.
|
|
8
|
+
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
<!-- toc -->
|
|
12
|
+
- [Scope](#scope)
|
|
13
|
+
- [Branch and commit rules](#branch-and-commit-rules)
|
|
14
|
+
- [Install and version matrix](#install-and-version-matrix)
|
|
15
|
+
- [Tests and docs](#tests-and-docs)
|
|
16
|
+
- [Crypto and security changes](#crypto-and-security-changes)
|
|
17
|
+
- [Fail-closed defaults to preserve](#fail-closed-defaults-to-preserve)
|
|
18
|
+
<!-- /toc -->
|
|
19
|
+
|
|
20
|
+
## Scope
|
|
21
|
+
|
|
22
|
+
Core only: signed constitution, Path A bytecode, Path B judge hooks,
|
|
23
|
+
monitored-agent hooks, encrypted ledger, single-use capability token, a
|
|
24
|
+
verify-only gateway, and the SIEM export. Beta (pre-1.0), maintainer-reviewed, not independently
|
|
25
|
+
audited. Apache-2.0.
|
|
26
|
+
|
|
27
|
+
Do **not** add a scanning or antivirus engine, a SIEM or log storage, chain
|
|
28
|
+
anchoring, or seed phrases here. The items in [ROADMAP.md](ROADMAP.md) are in
|
|
29
|
+
scope: key backup (shipped in 0.3.0), an MCP adapter (0.5.0), DLP and
|
|
30
|
+
antivirus hooks that call external scanners (0.6.0), ledger export for a SIEM
|
|
31
|
+
(0.7.0), and, after 1.0, PKI with
|
|
32
|
+
certificate recovery, hybrid ML-DSA signatures, a FIPS approved mode, and a
|
|
33
|
+
local GUI. Build them in the order and with the limits stated
|
|
34
|
+
there, unless the operator says otherwise. See [docs/SCOPE.md](docs/SCOPE.md).
|
|
35
|
+
|
|
36
|
+
## Branch and commit rules
|
|
37
|
+
|
|
38
|
+
- Package 0.7.0 (package name `two-key`) is tag `v0.7.0` on `main`; 0.6.0 is
|
|
39
|
+
tag `v0.6.0`, 0.5.0 is
|
|
40
|
+
tag `v0.5.0`, and 0.4.0 (tag `v0.4.0`) was the first release from this repository. Tags `v0.3.1` and earlier are in the archived `two-key-concept` repository. There, tag `v0.3.0` stays on `e44be56`.
|
|
41
|
+
Tag `v0.2.4` stays on `fc836bb`.
|
|
42
|
+
Tag `v0.2.3` stays on `4a4352e`. Do not move `v0.7.0`, `v0.6.0`, `v0.5.0`, `v0.4.0`, `v0.3.1`, `v0.3.0`, or `v0.2.0`
|
|
43
|
+
(`2f756ac`). `v0.1.12` and `v0.1.6` stay on their trees. `working` is
|
|
44
|
+
where the next changes land.
|
|
45
|
+
- Do not push, force-push, or open PRs unless the operator asks.
|
|
46
|
+
- Never commit keys, ledgers, or credentials.
|
|
47
|
+
- Update `CHANGES.md` with every commit. Read
|
|
48
|
+
`CHANGES.md` "Upgrading from 0.1.12" before changing 0.2.0 config shapes.
|
|
49
|
+
|
|
50
|
+
## Install and version matrix
|
|
51
|
+
|
|
52
|
+
Requires Python ≥ 3.10. Package name `two-key`, import name `two_key`,
|
|
53
|
+
version `0.7.0` on PyPI (`pip install "two-key[yaml]==0.7.0"`). Tag `v0.7.0` on `main` is not moved. docs/PYPI.md is the release process. This packaging does not change the version.
|
|
54
|
+
Linux, macOS, and Windows (from 0.6.0). CI runs tests, the demo,
|
|
55
|
+
the build, bandit, and CodeQL on all three (CONTRIBUTING).
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
python3 -m venv .venv
|
|
59
|
+
. .venv/bin/activate
|
|
60
|
+
pip install -e ".[yaml]"
|
|
61
|
+
python -m unittest discover -s tests
|
|
62
|
+
python -m two_key demo
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
CI does not use that floating install. `.github/workflows/tests.yml` pins
|
|
66
|
+
`uv==0.13.0` and runs `uv sync --locked --no-build --extra yaml --extra mcp`, which
|
|
67
|
+
installs `uv.lock` (cryptography, PyYAML, the MCP SDK, and their dependencies) as wheels.
|
|
68
|
+
Regenerate `uv.lock` with that uv when a dependency range changes, and
|
|
69
|
+
commit the lock. Do not treat the lock as a release pin for consumers.
|
|
70
|
+
Build, coverage, and bandit are pinned in their workflows, not in
|
|
71
|
+
`pyproject.toml`.
|
|
72
|
+
|
|
73
|
+
| Extra | Provides | When needed |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| (none) | `cryptography>=41` | Ed25519, SHA-256, AES-256-GCM ledger |
|
|
76
|
+
| `yaml` | PyYAML | `.yaml` rules and judge configs |
|
|
77
|
+
| `mcp` | MCP Python SDK (`mcp>=2.2,<3`), PyYAML | `two-key mcp-proxy`, `two-key mcp-draft-specs` (0.5.0) |
|
|
78
|
+
|
|
79
|
+
Index install: `pip install "two-key[yaml]==0.7.0"`.
|
|
80
|
+
The unmoved tag is still `pip install "two-key[yaml] @ git+https://github.com/Insomniac-VibeLabs/two-key.git@v0.7.0"`.
|
|
81
|
+
|
|
82
|
+
## Tests and docs
|
|
83
|
+
|
|
84
|
+
- Tests: `python -m unittest discover -s tests` (no network). Keep the suite
|
|
85
|
+
network-free; use `two_key.testing` doubles only where the docs allow.
|
|
86
|
+
- When a claim changes, update `README.md`, `ROADMAP.md`, `docs/HOWTO.md`,
|
|
87
|
+
`docs/CONNECTING.md` (a new connection or hook gets a row there),
|
|
88
|
+
`docs/FIT.md`, `docs/COMPARISON.md`, `docs/THREAT_MODEL.md`,
|
|
89
|
+
`docs/SCOPE.md`, `docs/SIEM.md`, `docs/PYPI.md`, `CHANGES.md`, `SECURITY.md`,
|
|
90
|
+
`CONTRIBUTING.md`, `AGENTS.md`, and `llms.txt` as needed.
|
|
91
|
+
- When a roadmap item ships, update `docs/SCOPE.md` and `docs/THREAT_MODEL.md`
|
|
92
|
+
in the same release.
|
|
93
|
+
- Keep [docs/HOWTO.md](docs/HOWTO.md) "Known trade-offs by configuration"
|
|
94
|
+
accurate when a config trade-off changes.
|
|
95
|
+
- Tables of contents are generated: after changing a heading, run
|
|
96
|
+
`python tests/test_doc_toc.py --write` (`tests/test_doc_toc.py` fails
|
|
97
|
+
while one is stale).
|
|
98
|
+
|
|
99
|
+
## Crypto and security changes
|
|
100
|
+
|
|
101
|
+
Crypto here is Ed25519 and SHA-256 via `cryptography`, plus AES-256-GCM for
|
|
102
|
+
ledger ciphertext. The ledger key, witness key, and capability private key
|
|
103
|
+
live outside the ledger directory. The gateway must verify only; it must not
|
|
104
|
+
mint.
|
|
105
|
+
|
|
106
|
+
Before changing authorization, tokens, the gateway, the ledger, or judge ≠
|
|
107
|
+
agent identity checks: get a security review from the maintainer. Report
|
|
108
|
+
security findings privately, per [SECURITY.md](SECURITY.md).
|
|
109
|
+
|
|
110
|
+
## Fail-closed defaults to preserve
|
|
111
|
+
|
|
112
|
+
- A missing ballot is not a yes.
|
|
113
|
+
- Both paths run once the call is well-formed and within limits. The one
|
|
114
|
+
exception (maintainer's decision, 0.5.0): a call that cannot be read under
|
|
115
|
+
its spec (`derive_failed:`) is not sent to the judges unless
|
|
116
|
+
`tool_args_on_derive_deny` is set, and Path B is ledgered as
|
|
117
|
+
`skipped:derive_failed`. Mismatches and rule denies still go to the
|
|
118
|
+
judges. Do not widen the skip.
|
|
119
|
+
- `authorize_from_agent` does not execute a tool.
|
|
120
|
+
- A decline (`{"decline": ...}`) is not a call: `agent_declined` deny,
|
|
121
|
+
neither path runs, never a token. A decline that also carries a tool,
|
|
122
|
+
arguments, or a claim is `malformed_proposal` (`tests/test_agent_decline.py`).
|
|
123
|
+
- `require_path_a_first` is not a skip.
|
|
124
|
+
- JSON/YAML inputs refuse duplicate keys and YAML merge keys. Oversized or malformed calls deny
|
|
125
|
+
before either path.
|
|
126
|
+
- Default quorum needs one judge and has no diversity floors.
|
|
127
|
+
When `required_yes` is unset, it resolves to
|
|
128
|
+
`min(2, number_of_judges)` (at least one); this is **not** a majority
|
|
129
|
+
rule. `min_responding` defaults to the effective `required_yes`
|
|
130
|
+
threshold. With `required_yes: 1`, two or more judges, and
|
|
131
|
+
`min_responding` 1, `TwoKey` warns at start (`lone_judge_warning`); it
|
|
132
|
+
does not refuse. `QuorumPolicy.high_assurance` / `profile: high_assurance`
|
|
133
|
+
adds requirements for judges from two makers (`min_makers`), one
|
|
134
|
+
local judge, and a local yes. Note that
|
|
135
|
+
`QuorumPolicy.without_diversity_floors()` explicitly defaults
|
|
136
|
+
`required_yes` to `2`.
|
|
137
|
+
- `tool_specs` is required. A disagreeing claim is a deny.
|
|
138
|
+
- An omitted `data_class` is `classified`, before the floor. Do not
|
|
139
|
+
default it to the tool's floor. Classes combine as a set
|
|
140
|
+
(`join_data_class`): `financial` and `medical` replace `personal`,
|
|
141
|
+
together they are `financial+medical`, and only `classified` (claimed,
|
|
142
|
+
floored, or omitted) is `classified` (`tests/test_data_class_sets.py`).
|
|
143
|
+
A `data_class_in` rule matches any class the call carries. Do not map display names to
|
|
144
|
+
counterparty ids or fuzzy-match them. The one wider form is a signed
|
|
145
|
+
`@domain` allow entry (v3): a plain address at exactly that ASCII domain,
|
|
146
|
+
no subdomains, no wildcards, never in `deny_counterparties`
|
|
147
|
+
(`tests/test_domain_allow.py`). Do not widen it.
|
|
148
|
+
- `agent_vocabulary()` is opt-in and lists syntax, never a value: no
|
|
149
|
+
`allow` list, enum value, integer bound, rule, threshold,
|
|
150
|
+
`deny_counterparties`, description, default, or `irreversible` flag
|
|
151
|
+
(`tests/test_agent_prompt.py`). Do not add a mode that sends them.
|
|
152
|
+
Without `vocabulary=`, the agent user prompt stays byte-for-byte the 0.3.0
|
|
153
|
+
prompt.
|
|
154
|
+
- Path A evaluates five built-in structured fields (`tool`, `amount_usd`,
|
|
155
|
+
`data_class`, `counterparty`, `irreversible`) and the operator's custom
|
|
156
|
+
fields (`two_key/fields.py`). Custom field values come from argument
|
|
157
|
+
paths in the signed spec only, never from the agent's claim or
|
|
158
|
+
`Action.raw`, or, for a field declared `source: caller`, from the
|
|
159
|
+
integration's `facts=` only: written in the arguments it denies
|
|
160
|
+
(`caller_field_in_arguments`), and the gateway takes the facts again
|
|
161
|
+
(`fact_mismatch`) (`tests/test_caller_facts.py`). The agent writes the
|
|
162
|
+
arguments, so an argument field is what the call says, not a verified
|
|
163
|
+
fact, and a caller fact is as true as its hook; do not describe fields as
|
|
164
|
+
verification
|
|
165
|
+
(HOWTO "What Two-Key verifies"); `LOAD` refuses any name but a built-in field, and custom
|
|
166
|
+
fields load with `LOAD_FIELD`. A mapped field is required. Every tool
|
|
167
|
+
maps each field a `deny_if_fields` rule reads or marks it
|
|
168
|
+
`not_applicable` (then it gets the signed default); a forgotten decision
|
|
169
|
+
is refused at signing and on load, never read as the default
|
|
170
|
+
(`tests/test_field_decisions.py`). A description is never compiled, sent to a
|
|
171
|
+
judge, or used in a reason. A `case: exact` ASCII string is compared and passed to the tool as
|
|
172
|
+
written; never forward an original value after a case-folded check
|
|
173
|
+
(`tests/test_case_exact.py`). A constitution with no custom fields must
|
|
174
|
+
stay byte-for-byte v1 (`tests/test_fields_compat.py`); one without a
|
|
175
|
+
0.5.0 feature (a caller field, a `case: exact` field, a `not_applicable`
|
|
176
|
+
mark, an `@domain` allow entry) must not sign as v3. Each signs only as
|
|
177
|
+
v3, and a v1 or v2 envelope that carries one is refused.
|
|
178
|
+
- Format identifiers are `two-key-...` (0.4.0). Files with the old
|
|
179
|
+
`two-key-concept-...` formats are refused with a clear message, never
|
|
180
|
+
read (`tests/test_legacy_formats.py`). Do not change a format name
|
|
181
|
+
again without a new version suffix and a migration note. Path A evaluates
|
|
182
|
+
structured data, not English-language policy prose.
|
|
183
|
+
- No judge may be the monitored agent: a judge holding the agent's
|
|
184
|
+
credential (the same API token, or the same username and password) is
|
|
185
|
+
refused at any address, whether or not either side sends it; so are two
|
|
186
|
+
sides that send no credential on one address. Other overlaps warn
|
|
187
|
+
and are recorded. Addresses are compared as configured, without DNS.
|
|
188
|
+
- The ledger's witness public key is pinned: a head or `witness.pem` that
|
|
189
|
+
does not match the pin in the ledger, or the configured
|
|
190
|
+
`witness_public_key`, is refused. It changes only by `rotate_witness`.
|
|
191
|
+
- The gateway verifies only; it never mints.
|
|
192
|
+
- The guard (`two_key/guard.py`) uses a fixed claim and proposal per tool,
|
|
193
|
+
never the agent's; looks facts up before `authorize` and again before
|
|
194
|
+
`invoke`, under one lock; and treats a hook that raises or returns None as
|
|
195
|
+
a missing fact (deny). It does not import the MCP SDK
|
|
196
|
+
(`tests/test_guard.py`).
|
|
197
|
+
- The MCP proxy (`two_key/mcp_proxy/`): only `sdk.py` imports the MCP SDK;
|
|
198
|
+
`config.py`, `core.py`, and `draft.py` must not. Every `tools/call` goes
|
|
199
|
+
through the guard (`authorize`, then `gateway.invoke` in the same
|
|
200
|
+
process); the upstream call is the gateway's registered tool, never
|
|
201
|
+
called directly. Only signed tools are shown, with caller-supplied paths
|
|
202
|
+
removed from `inputSchema`; any other name is `tool_not_offered`,
|
|
203
|
+
ledgered. `mcp_proxy_started` is ledgered before serving. A
|
|
204
|
+
schema mismatch refuses to start unless `on_schema_change: warn`.
|
|
205
|
+
`unresolved_identity` refuses unless `allow_unresolved_identity: true`;
|
|
206
|
+
the library keeps warning. The claim comes from `proxy.yaml` only, never
|
|
207
|
+
from the agent. A deny returns a reason code, never a value. Nothing but
|
|
208
|
+
MCP on stdout. Drafts must not sign until the principal decides; server
|
|
209
|
+
text stays in ASCII comments. An input Path A reads that the upstream
|
|
210
|
+
schema leaves undescribed (no `description`, no `enum`) is a WARNING in
|
|
211
|
+
the draft, at `--constitution`, and at proxy start, for any field, never
|
|
212
|
+
a looser match (`core.undescribed_inputs`). SDK tests skip without the extra; CI installs
|
|
213
|
+
it (`--extra mcp`).
|
|
214
|
+
- Platforms (0.6.0): every key read or write, owner-only directory, lock,
|
|
215
|
+
and no-follow open goes through `two_key/_platform.py`. On POSIX it does
|
|
216
|
+
what the code did before (mode 0600/0700, `O_NOFOLLOW`, `flock`, the same
|
|
217
|
+
messages). On Windows a key is created with an access list for the
|
|
218
|
+
current user only, and one whose owner or allow entries name anyone but
|
|
219
|
+
the user, SYSTEM, Administrators, or OWNER RIGHTS is refused; a reparse
|
|
220
|
+
point is refused; the lock is `LockFileEx`. Do not accept a wider access
|
|
221
|
+
list or fall back to "no lock". Text files Two-Key writes use
|
|
222
|
+
`newline="\n"` and a named encoding (`tests/test_text_io.py`).
|
|
223
|
+
- Writable paths (0.6.0, `two_key/writable.py`): at start, `TwoKey` names
|
|
224
|
+
each file or folder of the running Python (program, import path, `.pth`
|
|
225
|
+
files, loaded modules and their compiled files) and of `check_paths`,
|
|
226
|
+
and every folder above them, that an account other than its own (or
|
|
227
|
+
root, SYSTEM, Administrators, TrustedInstaller) could change, through
|
|
228
|
+
`_platform.write_problem`. `warn` is the default; `on_writable_paths:
|
|
229
|
+
refuse` refuses; `high_assurance` always refuses and refuses `warn`
|
|
230
|
+
(maintainer's decision, 2026-10-16). A path that cannot be checked is
|
|
231
|
+
named, not passed. There is no setting that turns it off, and on POSIX a
|
|
232
|
+
group write bit counts for every group but 0; do not add either. It runs
|
|
233
|
+
before anything is written to the ledger (`tests/test_writable_paths.py`).
|
|
234
|
+
- Agent account (0.7.0, `two_key/agent_access.py`): the MCP proxy reads the
|
|
235
|
+
real user of its parent process (`_platform.parent_account`, never an
|
|
236
|
+
environment variable) before it opens the ledger, and refuses to start
|
|
237
|
+
when that account is Two-Key's own (`agent_same_account`), root
|
|
238
|
+
(`agent_account_root`), unknown (`agent_account_unknown`, Windows
|
|
239
|
+
included for now), or can change a path of the writable-path check
|
|
240
|
+
(`agent_can_write`). `on_agent_access: refuse` is the default (maintainer's
|
|
241
|
+
decision, 2026-10-16); `warn` starts; `high_assurance` refuses and does
|
|
242
|
+
not accept `warn`. Unknown is never a different account; an account whose
|
|
243
|
+
groups cannot be listed is in every group (`tests/test_agent_access.py`).
|
|
244
|
+
- Shared ledger (0.7.0): a `Ledger` that finds the file longer than it
|
|
245
|
+
left it reads and verifies the new entries under the cross-process lock
|
|
246
|
+
(chain, head, witness pin, anchor) and does not pass them to its
|
|
247
|
+
observers; a shorter file or a partial line is still refused
|
|
248
|
+
(`tests/test_ledger_shared.py`).
|
|
249
|
+
- Content scanning (0.6.0, `two_key/scan.py`): Two-Key alone starts a scan,
|
|
250
|
+
for every call to a tool whose signed spec maps the scan field, and fills
|
|
251
|
+
the verdict; nothing the agent writes turns it on or off, and `scan` in
|
|
252
|
+
`facts=` is refused (`scan_fact_supplied`). Unavailable is never clean: a
|
|
253
|
+
timeout, an exception, or an invalid result is `unavailable`.
|
|
254
|
+
Could-not-finish (`limits_exceeded`) is flagged. A declared file Two-Key
|
|
255
|
+
cannot read (`file_unreadable`), one over its `max_bytes`
|
|
256
|
+
(`file_too_large`), more than 32 files (`too_many_files`), or more than
|
|
257
|
+
100 MiB of files in one call (`call_files_too_large`) is denied before
|
|
258
|
+
the scanner and both paths under every stance, monitor included
|
|
259
|
+
(maintainer's decision, 2026-10-15); nothing past a limit is read. Every
|
|
260
|
+
declared file states `max_bytes`, with no default (base64 at most
|
|
261
|
+
196608). Do not make these denies depend on the stance or give
|
|
262
|
+
`max_bytes` a default. An encrypted file is flagged; only a tool whose
|
|
263
|
+
signed spec maps the scan field with `encrypted: pass` turns an
|
|
264
|
+
`encrypted` finding into the verdict `encrypted`, and only when it is the
|
|
265
|
+
sole finding (with another finding it is flagged; with a part not
|
|
266
|
+
scanned, unavailable). The gateway refuses that verdict for any other
|
|
267
|
+
tool (`scan_mismatch`); an adapter reads only ClamAV's
|
|
268
|
+
`Heuristics.Encrypted` name as encrypted (`tests/test_scan_encrypted.py`).
|
|
269
|
+
Do not make it a `proxy.yaml` setting or a default (maintainer's
|
|
270
|
+
decision, 2026-10-15). The stance is the signed rule. Content never reaches
|
|
271
|
+
the ledger or a reason code, and file content never reaches a judge; a
|
|
272
|
+
flagged call's arguments and proposal reach the judges only with
|
|
273
|
+
`tool_args_on_scan_flag`. The gateway never scans: it checks the token's
|
|
274
|
+
verdict and file digests (`file_changed`). Without a scanner, the judge
|
|
275
|
+
prompts, tokens, and ledger stay byte for byte as before
|
|
276
|
+
(`tests/test_scan.py`). Do not add a scanning engine; adapters
|
|
277
|
+
(`two_key/scanners.py`, standard library only) call external products,
|
|
278
|
+
never keep a threat name or matched text, and read an answer they cannot
|
|
279
|
+
parse as `unavailable`; an ICAP 200 that changes the body is flagged
|
|
280
|
+
(`tests/test_scanners.py`). A `kind: path` file stays inside its declared
|
|
281
|
+
folder (no `..`, no link below it, a regular file), opened through
|
|
282
|
+
`_platform`; do not widen that.
|
|
283
|
+
- The gateway runs in the `TwoKey` process on the `TwoKey` ledger object
|
|
284
|
+
(`TwoKey.gateway()`). A gateway in another process or host is not
|
|
285
|
+
supported (#45); do not add a gateway-only ledger role, remote gateway,
|
|
286
|
+
or cross-process protocol without the operator. Do not describe
|
|
287
|
+
verify-only as key isolation: the process holds the minting, principal,
|
|
288
|
+
witness, and ledger keys, and the registered tools run there.
|
|
289
|
+
|
|
290
|
+
- SIEM export (0.7.0, `two_key/siem.py`, `two_key/siem_sinks.py`,
|
|
291
|
+
`two_key/siem_export.py`): optional, and with no `siem=` nothing is sent
|
|
292
|
+
and nothing changes. What may leave the box is an explicit per-kind
|
|
293
|
+
allowlist (`KINDS`): every body key of every ledger kind is `sent`,
|
|
294
|
+
`hashed`, `excluded`, or extracted, and a key nobody decided fails
|
|
295
|
+
`tests/test_siem.py`. Argument values, proposal and decline text, the
|
|
296
|
+
derived amount, counterparties, custom field values, scanner threat names
|
|
297
|
+
and matched text, and credentials stay excluded, with canary values in the
|
|
298
|
+
tests; add a new ledger field to the allowlist on purpose or not at all.
|
|
299
|
+
The ledger observer only enqueues a sequence number: projecting,
|
|
300
|
+
formatting, and sending happen on the sender thread, so a collector that
|
|
301
|
+
is down, slow, or hostile never blocks, delays, or changes an
|
|
302
|
+
authorization, and a full queue drops rather than waits. TLS sets
|
|
303
|
+
TLS 1.2 as the minimum and always verifies the certificate and the host
|
|
304
|
+
name; do not add a setting that turns either off. A plaintext sink (`udp`,
|
|
305
|
+
`tcp`) is refused under `high_assurance` (`siem_plaintext_refused`) and
|
|
306
|
+
warns at start otherwise. A webhook never follows a redirect and reads its
|
|
307
|
+
authorization header and HMAC key from owner-only files through
|
|
308
|
+
`_platform`. A token the gateway refuses is still not ledgered: it only
|
|
309
|
+
increments an in-memory class counter, reported at most once an interval
|
|
310
|
+
with no token text, digest, or id (`ledgered: false`, `seq: -1`).
|
|
311
|
+
`two-key siem-export` verifies the ledger first, writes a new owner-only
|
|
312
|
+
file and refuses to overwrite, and `two-key siem-verify` re-derives every
|
|
313
|
+
event from the ledger (`tests/test_siem_export.py`). Keep the unit tests
|
|
314
|
+
network-free: loopback only, with `tests/tls.py` for certificates.
|
|
315
|
+
|
|
316
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the human contributor checklist.
|