hypermind 0.11.0__py3-none-any.whl

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 (181) hide show
  1. hmctl/__init__.py +7 -0
  2. hmctl/cli.py +947 -0
  3. hypermind/__init__.py +208 -0
  4. hypermind/_deprecations.py +108 -0
  5. hypermind/agent.py +2896 -0
  6. hypermind/agent_card.py +127 -0
  7. hypermind/api/__init__.py +32 -0
  8. hypermind/api/app.py +686 -0
  9. hypermind/capability.py +923 -0
  10. hypermind/consult.py +493 -0
  11. hypermind/consult_bias.py +112 -0
  12. hypermind/contract_net.py +219 -0
  13. hypermind/crypto/__init__.py +21 -0
  14. hypermind/crypto/cose_encrypt.py +126 -0
  15. hypermind/crypto/dkg.py +246 -0
  16. hypermind/crypto/domain.py +59 -0
  17. hypermind/crypto/frost.py +771 -0
  18. hypermind/crypto/hashing.py +38 -0
  19. hypermind/crypto/hlc.py +163 -0
  20. hypermind/crypto/hpke.py +277 -0
  21. hypermind/crypto/kms.py +196 -0
  22. hypermind/crypto/kms_backends/__init__.py +56 -0
  23. hypermind/crypto/kms_backends/aws.py +128 -0
  24. hypermind/crypto/kms_backends/azure.py +114 -0
  25. hypermind/crypto/kms_backends/gcp.py +97 -0
  26. hypermind/crypto/kms_backends/vault.py +120 -0
  27. hypermind/crypto/namespace_root.py +204 -0
  28. hypermind/crypto/pq.py +152 -0
  29. hypermind/crypto/room_epoch.py +97 -0
  30. hypermind/crypto/signing.py +245 -0
  31. hypermind/deliberation.py +355 -0
  32. hypermind/did_web.py +256 -0
  33. hypermind/dispute.py +858 -0
  34. hypermind/errors.py +218 -0
  35. hypermind/eval/__init__.py +49 -0
  36. hypermind/eval/calibration.py +307 -0
  37. hypermind/eval/comparison.py +251 -0
  38. hypermind/eval/replay.py +202 -0
  39. hypermind/eval/swarm_iq.py +718 -0
  40. hypermind/knowledge/__init__.py +17 -0
  41. hypermind/knowledge/citation_graph.py +141 -0
  42. hypermind/knowledge/correlated_agreement.py +167 -0
  43. hypermind/knowledge/embedding_registry.py +115 -0
  44. hypermind/knowledge/kernel_store.py +197 -0
  45. hypermind/knowledge/rekor.py +173 -0
  46. hypermind/knowledge/reputation.py +364 -0
  47. hypermind/knowledge/swarm_memory.py +523 -0
  48. hypermind/knowledge/transparency.py +409 -0
  49. hypermind/mcp/__init__.py +60 -0
  50. hypermind/mcp/bridge.py +151 -0
  51. hypermind/mcp/client.py +142 -0
  52. hypermind/mcp/provenance.py +214 -0
  53. hypermind/mcp/relata_tools.py +152 -0
  54. hypermind/mcp/server.py +248 -0
  55. hypermind/mcp/transport.py +206 -0
  56. hypermind/mind/__init__.py +150 -0
  57. hypermind/mind/active.py +234 -0
  58. hypermind/mind/belief.py +369 -0
  59. hypermind/mind/deliberator.py +625 -0
  60. hypermind/mind/diversity.py +144 -0
  61. hypermind/mind/evolve.py +184 -0
  62. hypermind/mind/federation.py +154 -0
  63. hypermind/mind/goals.py +117 -0
  64. hypermind/mind/integration.py +190 -0
  65. hypermind/mind/mediator.py +74 -0
  66. hypermind/mind/memory_store.py +129 -0
  67. hypermind/mind/policy.py +307 -0
  68. hypermind/mind/records.py +260 -0
  69. hypermind/mind/roles.py +190 -0
  70. hypermind/mind/sybil_guard.py +41 -0
  71. hypermind/mind/world_model.py +166 -0
  72. hypermind/namespace.py +515 -0
  73. hypermind/namespace_policy.py +254 -0
  74. hypermind/observability/__init__.py +25 -0
  75. hypermind/observability/asgi.py +214 -0
  76. hypermind/observability/cost.py +277 -0
  77. hypermind/observability/otel_export.py +200 -0
  78. hypermind/observability/recorder.py +344 -0
  79. hypermind/observability/swarm_trace.py +438 -0
  80. hypermind/pin_policy.py +116 -0
  81. hypermind/py.typed +0 -0
  82. hypermind/responders/__init__.py +87 -0
  83. hypermind/responders/anthropic.py +159 -0
  84. hypermind/responders/base.py +162 -0
  85. hypermind/responders/openai.py +199 -0
  86. hypermind/responders/router.py +254 -0
  87. hypermind/responders/structured.py +287 -0
  88. hypermind/responders/tools.py +444 -0
  89. hypermind/revocation.py +270 -0
  90. hypermind/rule_ids.py +135 -0
  91. hypermind/schemas/encrypted_statement.cddl +28 -0
  92. hypermind/schemas/namespace_accept.cddl +20 -0
  93. hypermind/schemas/namespace_create.cddl +25 -0
  94. hypermind/schemas/namespace_founding_attest.cddl +30 -0
  95. hypermind/schemas/namespace_invite.cddl +22 -0
  96. hypermind/schemas/wire-v0.1.cddl +73 -0
  97. hypermind/simlab/__init__.py +15 -0
  98. hypermind/simlab/_persona_registry.py +93 -0
  99. hypermind/simlab/_scenario_impl.py +2778 -0
  100. hypermind/simlab/app.py +2538 -0
  101. hypermind/simlab/backends/__init__.py +27 -0
  102. hypermind/simlab/backends/anchor.py +88 -0
  103. hypermind/simlab/backends/local.py +32 -0
  104. hypermind/simlab/backends/server.py +79 -0
  105. hypermind/simlab/cli_command.py +108 -0
  106. hypermind/simlab/config.py +106 -0
  107. hypermind/simlab/deployment.py +26 -0
  108. hypermind/simlab/knowledge.py +716 -0
  109. hypermind/simlab/learning.py +295 -0
  110. hypermind/simlab/modes/__init__.py +115 -0
  111. hypermind/simlab/modes/_eval_real_llm.py +373 -0
  112. hypermind/simlab/modes/aar.py +611 -0
  113. hypermind/simlab/modes/backtest.py +610 -0
  114. hypermind/simlab/modes/binary_forecast.py +69 -0
  115. hypermind/simlab/modes/conformance.py +1869 -0
  116. hypermind/simlab/modes/evaluation.py +2271 -0
  117. hypermind/simlab/modes/governance.py +648 -0
  118. hypermind/simlab/modes/redteam.py +534 -0
  119. hypermind/simlab/modes/tabletop.py +797 -0
  120. hypermind/simlab/modes/tournament.py +448 -0
  121. hypermind/simlab/modes/whatif.py +523 -0
  122. hypermind/simlab/namespace.py +125 -0
  123. hypermind/simlab/personas.py +477 -0
  124. hypermind/simlab/prompt_generator.py +172 -0
  125. hypermind/simlab/question_sets/geopolitics_q20.json +122 -0
  126. hypermind/simlab/question_sets/governance_q5.json +67 -0
  127. hypermind/simlab/question_sets/msft_outlook_q10.json +62 -0
  128. hypermind/simlab/question_sets/whatif_q3.json +38 -0
  129. hypermind/simlab/registry.py +325 -0
  130. hypermind/simlab/runner.py +239 -0
  131. hypermind/simlab/scenario.py +147 -0
  132. hypermind/simlab/swarms.py +180 -0
  133. hypermind/simlab/tool_handlers/__init__.py +4 -0
  134. hypermind/simlab/tool_handlers/alpha_vantage.py +39 -0
  135. hypermind/simlab/tool_handlers/brave.py +46 -0
  136. hypermind/simlab/tool_handlers/newsapi.py +50 -0
  137. hypermind/simlab/tool_handlers/openweather.py +46 -0
  138. hypermind/simlab/tool_handlers/pinecone.py +52 -0
  139. hypermind/simlab/tool_handlers/qdrant.py +57 -0
  140. hypermind/simlab/tool_handlers/query_knowledge_base.py +21 -0
  141. hypermind/simlab/tool_handlers/relata_recall.py +61 -0
  142. hypermind/simlab/tool_handlers/retrieve_document.py +21 -0
  143. hypermind/simlab/tool_handlers/serper.py +46 -0
  144. hypermind/simlab/tool_handlers/tavily.py +53 -0
  145. hypermind/simlab/tool_handlers/weaviate.py +65 -0
  146. hypermind/simlab/tool_handlers/wikipedia.py +64 -0
  147. hypermind/simlab/tool_handlers/wolfram.py +48 -0
  148. hypermind/simlab/tool_handlers/yahoo_finance.py +49 -0
  149. hypermind/simlab/tool_registry.py +568 -0
  150. hypermind/simlab/topic_adapter.py +338 -0
  151. hypermind/simlab/topic_questions.py +259 -0
  152. hypermind/storage/__init__.py +69 -0
  153. hypermind/storage/backend.py +71 -0
  154. hypermind/storage/relata.py +245 -0
  155. hypermind/storage/sqlite.py +258 -0
  156. hypermind/sync.py +191 -0
  157. hypermind/tal.py +175 -0
  158. hypermind/tasks.py +334 -0
  159. hypermind/testing.py +16 -0
  160. hypermind/tools/__init__.py +32 -0
  161. hypermind/tools/registry.py +61 -0
  162. hypermind/transport/__init__.py +22 -0
  163. hypermind/transport/anti_entropy.py +708 -0
  164. hypermind/transport/bus.py +206 -0
  165. hypermind/transport/knows_delta.py +204 -0
  166. hypermind/transport/libp2p.py +298 -0
  167. hypermind/transport/placement.py +58 -0
  168. hypermind/transport/tcp.py +797 -0
  169. hypermind/transport/tls_profile.py +417 -0
  170. hypermind/types.py +59 -0
  171. hypermind/uncertainty.py +222 -0
  172. hypermind/wire.py +897 -0
  173. hypermind/workflow/__init__.py +72 -0
  174. hypermind/workflow/engine.py +655 -0
  175. hypermind/workflow/plan.py +182 -0
  176. hypermind/workflow/repair.py +203 -0
  177. hypermind-0.11.0.dist-info/METADATA +524 -0
  178. hypermind-0.11.0.dist-info/RECORD +181 -0
  179. hypermind-0.11.0.dist-info/WHEEL +4 -0
  180. hypermind-0.11.0.dist-info/entry_points.txt +2 -0
  181. hypermind-0.11.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,56 @@
1
+ """Cloud KMS backends for the v0.10.4 production profile (T6-03).
2
+
3
+ Each backend implements :class:`hypermind.crypto.kms.Signer` and lives in
4
+ its own module so the cloud SDK stays an *optional* import. Calling
5
+ :class:`AWSKMSSigner` / :class:`GCPKMSSigner` / :class:`AzureKMSSigner` /
6
+ :class:`VaultTransitSigner` without the matching extra installed raises
7
+ :class:`KMSBackendNotInstalled` with the exact ``pip install`` invocation.
8
+
9
+ Backends:
10
+
11
+ * AWS — ``hypermind[kms-aws]`` → ``boto3``
12
+ * GCP — ``hypermind[kms-gcp]`` → ``google-cloud-kms``
13
+ * Azure — ``hypermind[kms-azure]`` → ``azure-keyvault-keys``, ``azure-identity``
14
+ * Vault — ``hypermind[kms-vault]`` → ``hvac``
15
+
16
+ Real cloud credentials are explicitly out of scope for v0.10.4 — see
17
+ ``§External & human deliverables`` in ROADMAP.md. Each backend raises
18
+ ``KMSCredentialsMissing`` early when the documented env vars are absent
19
+ so production wiring fails fast and clearly.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+
25
+ class KMSBackendNotInstalled(RuntimeError):
26
+ """Raised when a cloud SDK extra is missing.
27
+
28
+ Inherits :class:`RuntimeError` (not :class:`ImportError`) so the
29
+ ``hypermind.crypto.kms_backends`` package itself imports cleanly even
30
+ when no cloud SDK is installed — only *use* of a backend fails.
31
+ """
32
+
33
+ def __init__(self, backend: str, extra: str) -> None:
34
+ super().__init__(
35
+ f"{backend} KMS backend requires an optional dependency; install via "
36
+ f"`pip install 'hypermind[{extra}]'` (or `pip install 'hypermind[kms-all]'`)"
37
+ )
38
+ self.backend = backend
39
+ self.extra = extra
40
+
41
+
42
+ class KMSCredentialsMissing(RuntimeError):
43
+ """Raised when a backend is constructed without required credentials.
44
+
45
+ The message names the missing env var(s) so operators can self-resolve.
46
+ """
47
+
48
+ def __init__(self, backend: str, missing: list[str]) -> None:
49
+ super().__init__(
50
+ f"{backend} KMS backend missing required credentials: {', '.join(missing)}"
51
+ )
52
+ self.backend = backend
53
+ self.missing = missing
54
+
55
+
56
+ __all__ = ["KMSBackendNotInstalled", "KMSCredentialsMissing"]
@@ -0,0 +1,128 @@
1
+ """AWS KMS Signer (T6-03).
2
+
3
+ Lazy-imports ``boto3``. The HyperMind library MUST be importable without
4
+ ``boto3`` installed; only constructing :class:`AWSKMSSigner` triggers the
5
+ import (and raises :class:`KMSBackendNotInstalled` if ``boto3`` is absent).
6
+
7
+ Production wiring (real AWS credentials, KMS key ARN, signature DER
8
+ unwrap) is out of scope for v0.10.4 — see ROADMAP §External & human
9
+ deliverables. This file implements the contract and the fast-fail path
10
+ so downstream code can program against the same Signer Protocol it uses
11
+ for ``LocalSigner``.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ from typing import Any
18
+
19
+ from hypermind.crypto.kms_backends import (
20
+ KMSBackendNotInstalled,
21
+ KMSCredentialsMissing,
22
+ )
23
+ from hypermind.crypto.signing import ALG_ED25519
24
+
25
+
26
+ def _try_import_boto3() -> Any:
27
+ try:
28
+ import boto3 # type: ignore[import-not-found]
29
+
30
+ return boto3
31
+ except ImportError as exc:
32
+ raise KMSBackendNotInstalled("AWS", "kms-aws") from exc
33
+
34
+
35
+ class AWSKMSSigner:
36
+ """Signer backed by AWS KMS ``Sign`` API.
37
+
38
+ Construction parameters
39
+ -----------------------
40
+ key_id
41
+ AWS KMS Key ID or ARN (``arn:aws:kms:<region>:<acct>:key/<uuid>``).
42
+ published_kid
43
+ The 32-byte Ed25519 public-key half pinned at construction. Pulled
44
+ out-of-band from ``GetPublicKey`` and stored alongside the agent's
45
+ published identity. The Signer refuses to ``sign()`` if rotation
46
+ invalidates this pin.
47
+ region
48
+ Optional AWS region. Falls back to the standard boto3 resolution
49
+ chain (``AWS_REGION`` env var, ``~/.aws/config``).
50
+ boto3_session
51
+ Optional pre-built :class:`boto3.Session`; useful for tests and
52
+ for SDK consumers that already manage a session.
53
+
54
+ Required env vars (when no explicit session is supplied)
55
+ --------------------------------------------------------
56
+ Either ``AWS_ACCESS_KEY_ID`` + ``AWS_SECRET_ACCESS_KEY`` or
57
+ ``AWS_PROFILE``. ``KMSCredentialsMissing`` is raised at *first sign()*
58
+ when neither is configured — construction stays cheap so unit tests
59
+ can introspect the Signer without hitting AWS.
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ *,
65
+ key_id: str,
66
+ published_kid: bytes,
67
+ region: str | None = None,
68
+ boto3_session: Any | None = None,
69
+ alg: int = ALG_ED25519,
70
+ ) -> None:
71
+ if len(published_kid) != 32:
72
+ raise ValueError("published_kid must be 32 bytes (Ed25519 public)")
73
+ self._key_id = key_id
74
+ self._kid = published_kid
75
+ self._alg = alg
76
+ self._region = region
77
+ self._session = boto3_session
78
+ self._client: Any | None = None
79
+
80
+ @property
81
+ def public_key(self) -> bytes:
82
+ return self._kid
83
+
84
+ @property
85
+ def kid(self) -> bytes:
86
+ return self._kid
87
+
88
+ @property
89
+ def alg(self) -> int:
90
+ return self._alg
91
+
92
+ def _client_or_create(self) -> Any:
93
+ if self._client is not None:
94
+ return self._client
95
+ boto3 = _try_import_boto3()
96
+ if self._session is None:
97
+ missing: list[str] = []
98
+ if not (
99
+ os.getenv("AWS_ACCESS_KEY_ID") and os.getenv("AWS_SECRET_ACCESS_KEY")
100
+ ) and not os.getenv("AWS_PROFILE"):
101
+ missing.append("AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY (or AWS_PROFILE)")
102
+ if missing:
103
+ raise KMSCredentialsMissing("AWS", missing)
104
+ self._session = boto3.Session(region_name=self._region)
105
+ self._client = self._session.client("kms")
106
+ return self._client
107
+
108
+ def sign(self, message: bytes) -> bytes: # pragma: no cover - real AWS path
109
+ client = self._client_or_create()
110
+ response = client.sign(
111
+ KeyId=self._key_id,
112
+ Message=message,
113
+ MessageType="RAW",
114
+ SigningAlgorithm="EDDSA",
115
+ )
116
+ sig = response["Signature"]
117
+ # AWS returns DER-wrapped ECDSA but raw Ed25519; defensive normalise.
118
+ if not isinstance(sig, (bytes, bytearray)):
119
+ raise RuntimeError("AWS KMS returned non-bytes Signature")
120
+ return bytes(sig)
121
+
122
+ def __repr__(self) -> str:
123
+ return (
124
+ f"AWSKMSSigner(key_id={self._key_id!r}, kid={self._kid.hex()[:16]}..., alg={self._alg})"
125
+ )
126
+
127
+
128
+ __all__ = ["AWSKMSSigner"]
@@ -0,0 +1,114 @@
1
+ """Azure Key Vault Signer (T6-03).
2
+
3
+ Lazy-imports ``azure-keyvault-keys`` and ``azure-identity``.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ from typing import Any
10
+
11
+ from hypermind.crypto.kms_backends import (
12
+ KMSBackendNotInstalled,
13
+ KMSCredentialsMissing,
14
+ )
15
+ from hypermind.crypto.signing import ALG_ED25519
16
+
17
+
18
+ def _try_import_azure() -> tuple[Any, Any, Any]:
19
+ try:
20
+ from azure.identity import DefaultAzureCredential # type: ignore[import-not-found]
21
+ from azure.keyvault.keys.crypto import ( # type: ignore[import-not-found]
22
+ CryptographyClient,
23
+ SignatureAlgorithm,
24
+ )
25
+
26
+ return DefaultAzureCredential, CryptographyClient, SignatureAlgorithm
27
+ except ImportError as exc:
28
+ raise KMSBackendNotInstalled("Azure", "kms-azure") from exc
29
+
30
+
31
+ class AzureKMSSigner:
32
+ """Signer backed by Azure Key Vault ``sign`` operation.
33
+
34
+ Construction parameters
35
+ -----------------------
36
+ key_id
37
+ Full Azure Key Vault key identifier —
38
+ ``https://<vault>.vault.azure.net/keys/<name>/<version>``.
39
+ published_kid
40
+ Pinned 32-byte Ed25519 public-key half.
41
+
42
+ Required env vars
43
+ -----------------
44
+ Standard Azure auth chain — at least one of:
45
+ * ``AZURE_TENANT_ID`` + ``AZURE_CLIENT_ID`` + ``AZURE_CLIENT_SECRET``
46
+ * managed-identity / workload-identity environment
47
+ Validated at first ``sign()`` call.
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ *,
53
+ key_id: str,
54
+ published_kid: bytes,
55
+ credential: Any | None = None,
56
+ alg: int = ALG_ED25519,
57
+ ) -> None:
58
+ if len(published_kid) != 32:
59
+ raise ValueError("published_kid must be 32 bytes (Ed25519 public)")
60
+ self._key_id = key_id
61
+ self._kid = published_kid
62
+ self._alg = alg
63
+ self._credential = credential
64
+ self._client: Any | None = None
65
+ self._sig_alg: Any | None = None
66
+
67
+ @property
68
+ def public_key(self) -> bytes:
69
+ return self._kid
70
+
71
+ @property
72
+ def kid(self) -> bytes:
73
+ return self._kid
74
+
75
+ @property
76
+ def alg(self) -> int:
77
+ return self._alg
78
+
79
+ def _client_or_create(self) -> Any:
80
+ if self._client is not None:
81
+ return self._client
82
+ DefaultAzureCredential, CryptographyClient, SignatureAlgorithm = _try_import_azure()
83
+ if self._credential is None:
84
+ if not (
85
+ os.getenv("AZURE_TENANT_ID")
86
+ or os.getenv("AZURE_CLIENT_ID")
87
+ or os.getenv("MSI_ENDPOINT")
88
+ or os.getenv("IDENTITY_ENDPOINT")
89
+ ):
90
+ raise KMSCredentialsMissing(
91
+ "Azure",
92
+ ["AZURE_TENANT_ID/AZURE_CLIENT_ID or managed-identity"],
93
+ )
94
+ self._credential = DefaultAzureCredential()
95
+ self._client = CryptographyClient(self._key_id, credential=self._credential)
96
+ # Azure exposes EdDSA via SignatureAlgorithm.eddsa.
97
+ self._sig_alg = getattr(SignatureAlgorithm, "eddsa", None)
98
+ return self._client
99
+
100
+ def sign(self, message: bytes) -> bytes: # pragma: no cover - real Azure path
101
+ client = self._client_or_create()
102
+ if self._sig_alg is None:
103
+ raise RuntimeError("Azure SDK does not expose SignatureAlgorithm.eddsa")
104
+ result = client.sign(self._sig_alg, message)
105
+ sig = result.signature
106
+ if not isinstance(sig, (bytes, bytearray)):
107
+ raise RuntimeError("Azure Key Vault returned non-bytes signature")
108
+ return bytes(sig)
109
+
110
+ def __repr__(self) -> str:
111
+ return f"AzureKMSSigner(key_id={self._key_id!r}, kid={self._kid.hex()[:16]}..., alg={self._alg})"
112
+
113
+
114
+ __all__ = ["AzureKMSSigner"]
@@ -0,0 +1,97 @@
1
+ """GCP Cloud KMS Signer (T6-03).
2
+
3
+ Lazy-imports ``google-cloud-kms``. See ``aws.py`` for the lazy-import
4
+ discipline rationale.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from typing import Any
11
+
12
+ from hypermind.crypto.kms_backends import (
13
+ KMSBackendNotInstalled,
14
+ KMSCredentialsMissing,
15
+ )
16
+ from hypermind.crypto.signing import ALG_ED25519
17
+
18
+
19
+ def _try_import_kms() -> Any:
20
+ try:
21
+ from google.cloud import kms # type: ignore[import-not-found]
22
+
23
+ return kms
24
+ except ImportError as exc:
25
+ raise KMSBackendNotInstalled("GCP", "kms-gcp") from exc
26
+
27
+
28
+ class GCPKMSSigner:
29
+ """Signer backed by GCP Cloud KMS ``AsymmetricSign`` RPC.
30
+
31
+ Construction parameters
32
+ -----------------------
33
+ key_uri
34
+ GCP key version resource name —
35
+ ``projects/<p>/locations/<l>/keyRings/<r>/cryptoKeys/<k>/cryptoKeyVersions/<v>``.
36
+ published_kid
37
+ Pinned 32-byte Ed25519 public-key half (see ``aws.py``).
38
+
39
+ Required env vars
40
+ -----------------
41
+ ``GOOGLE_APPLICATION_CREDENTIALS`` pointing at a service-account JSON,
42
+ or a workload-identity environment (GKE, Cloud Run). Validated at
43
+ first ``sign()`` call.
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ *,
49
+ key_uri: str,
50
+ published_kid: bytes,
51
+ client: Any | None = None,
52
+ alg: int = ALG_ED25519,
53
+ ) -> None:
54
+ if len(published_kid) != 32:
55
+ raise ValueError("published_kid must be 32 bytes (Ed25519 public)")
56
+ self._key_uri = key_uri
57
+ self._kid = published_kid
58
+ self._alg = alg
59
+ self._client = client
60
+
61
+ @property
62
+ def public_key(self) -> bytes:
63
+ return self._kid
64
+
65
+ @property
66
+ def kid(self) -> bytes:
67
+ return self._kid
68
+
69
+ @property
70
+ def alg(self) -> int:
71
+ return self._alg
72
+
73
+ def _client_or_create(self) -> Any:
74
+ if self._client is not None:
75
+ return self._client
76
+ kms = _try_import_kms()
77
+ if not (os.getenv("GOOGLE_APPLICATION_CREDENTIALS") or os.getenv("GOOGLE_CLOUD_PROJECT")):
78
+ raise KMSCredentialsMissing(
79
+ "GCP",
80
+ ["GOOGLE_APPLICATION_CREDENTIALS or workload-identity (GOOGLE_CLOUD_PROJECT)"],
81
+ )
82
+ self._client = kms.KeyManagementServiceClient()
83
+ return self._client
84
+
85
+ def sign(self, message: bytes) -> bytes: # pragma: no cover - real GCP path
86
+ client = self._client_or_create()
87
+ response = client.asymmetric_sign(request={"name": self._key_uri, "data": message})
88
+ sig = response.signature
89
+ if not isinstance(sig, (bytes, bytearray)):
90
+ raise RuntimeError("GCP KMS returned non-bytes signature")
91
+ return bytes(sig)
92
+
93
+ def __repr__(self) -> str:
94
+ return f"GCPKMSSigner(key_uri={self._key_uri!r}, kid={self._kid.hex()[:16]}..., alg={self._alg})"
95
+
96
+
97
+ __all__ = ["GCPKMSSigner"]
@@ -0,0 +1,120 @@
1
+ """HashiCorp Vault Transit Signer (T6-03).
2
+
3
+ Lazy-imports ``hvac``. Uses the Transit Secrets Engine ``sign/<key>``
4
+ endpoint with Ed25519 keys.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import base64
10
+ import os
11
+ from typing import Any
12
+
13
+ from hypermind.crypto.kms_backends import (
14
+ KMSBackendNotInstalled,
15
+ KMSCredentialsMissing,
16
+ )
17
+ from hypermind.crypto.signing import ALG_ED25519
18
+
19
+
20
+ def _try_import_hvac() -> Any:
21
+ try:
22
+ import hvac # type: ignore[import-not-found]
23
+
24
+ return hvac
25
+ except ImportError as exc:
26
+ raise KMSBackendNotInstalled("Vault", "kms-vault") from exc
27
+
28
+
29
+ class VaultTransitSigner:
30
+ """Signer backed by HashiCorp Vault Transit Secrets Engine.
31
+
32
+ Construction parameters
33
+ -----------------------
34
+ vault_addr
35
+ Vault server URL (``https://vault.internal:8200``). Falls back to
36
+ the ``VAULT_ADDR`` env var.
37
+ key_name
38
+ Transit key name (the ``<key>`` in ``transit/keys/<key>``).
39
+ published_kid
40
+ Pinned 32-byte Ed25519 public-key half.
41
+ transit_mount
42
+ Transit mount path (default: ``transit``).
43
+ token
44
+ Vault token. Falls back to ``VAULT_TOKEN``.
45
+
46
+ Required env vars (when not supplied via constructor)
47
+ -----------------------------------------------------
48
+ ``VAULT_ADDR`` and ``VAULT_TOKEN`` (or an AppRole / k8s auth token
49
+ pre-configured on the supplied ``hvac.Client``). Validated at first
50
+ ``sign()`` call.
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ *,
56
+ key_name: str,
57
+ published_kid: bytes,
58
+ vault_addr: str | None = None,
59
+ transit_mount: str = "transit",
60
+ token: str | None = None,
61
+ client: Any | None = None,
62
+ alg: int = ALG_ED25519,
63
+ ) -> None:
64
+ if len(published_kid) != 32:
65
+ raise ValueError("published_kid must be 32 bytes (Ed25519 public)")
66
+ self._key_name = key_name
67
+ self._kid = published_kid
68
+ self._alg = alg
69
+ self._vault_addr = vault_addr
70
+ self._mount = transit_mount
71
+ self._token = token
72
+ self._client = client
73
+
74
+ @property
75
+ def public_key(self) -> bytes:
76
+ return self._kid
77
+
78
+ @property
79
+ def kid(self) -> bytes:
80
+ return self._kid
81
+
82
+ @property
83
+ def alg(self) -> int:
84
+ return self._alg
85
+
86
+ def _client_or_create(self) -> Any:
87
+ if self._client is not None:
88
+ return self._client
89
+ hvac = _try_import_hvac()
90
+ addr = self._vault_addr or os.getenv("VAULT_ADDR")
91
+ token = self._token or os.getenv("VAULT_TOKEN")
92
+ missing: list[str] = []
93
+ if not addr:
94
+ missing.append("VAULT_ADDR")
95
+ if not token:
96
+ missing.append("VAULT_TOKEN")
97
+ if missing:
98
+ raise KMSCredentialsMissing("Vault", missing)
99
+ self._client = hvac.Client(url=addr, token=token)
100
+ return self._client
101
+
102
+ def sign(self, message: bytes) -> bytes: # pragma: no cover - real Vault path
103
+ client = self._client_or_create()
104
+ response = client.secrets.transit.sign_data(
105
+ name=self._key_name,
106
+ hash_input=base64.b64encode(message).decode("ascii"),
107
+ mount_point=self._mount,
108
+ )
109
+ # Vault returns "vault:v1:<base64-sig>".
110
+ raw = response["data"]["signature"]
111
+ prefix, _, b64 = raw.rpartition(":")
112
+ if not prefix.startswith("vault:"):
113
+ raise RuntimeError(f"unexpected Vault signature envelope: {raw!r}")
114
+ return base64.b64decode(b64)
115
+
116
+ def __repr__(self) -> str:
117
+ return f"VaultTransitSigner(key={self._key_name!r}, kid={self._kid.hex()[:16]}..., alg={self._alg})"
118
+
119
+
120
+ __all__ = ["VaultTransitSigner"]