Core¶
The core layer owns everything that happens around the cryptography: which adapter runs, whether the caller can afford the query, and what gets recorded.
Manager¶
manager
¶
Unified entry point for every cryptographic operation.
The manager owns adapter lifecycles and translates the request envelope into an adapter call. Adapters are built lazily and cached: constructing one can be expensive, and a backend that is absent from the environment should only break the operation that needs it rather than preventing the service from starting.
CryptographyManager
¶
CryptographyManager(
config: Config | None = None,
config_file: str | None = None,
)
Routes cryptographic requests to the appropriate backend adapter.
Initialize the Cryptography Manager.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
Config | None
|
Optional configuration object. |
None
|
config_file
|
str | None
|
Optional path to a configuration file. |
None
|
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If the configuration file cannot be loaded. |
CryptographyManagerError
|
If initialization otherwise fails. |
Source code in src/cryptography_manager/core/manager.py
differential_privacy
¶
Return the DP adapter, building it on first use.
Source code in src/cryptography_manager/core/manager.py
encryption
¶
Return the encryption adapter, building it on first use.
Source code in src/cryptography_manager/core/manager.py
adapter_for
¶
adapter_for(operation: Operation) -> 'CryptographicAdapter'
Return the adapter servicing an operation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operation
|
Operation
|
The requested operation. |
required |
Returns:
| Type | Description |
|---|---|
'CryptographicAdapter'
|
The adapter instance. |
Raises:
| Type | Description |
|---|---|
OperationNotImplementedError
|
If the operation is recognised but has no adapter yet. |
Source code in src/cryptography_manager/core/manager.py
backend_for
¶
Return the backend library servicing an operation.
Source code in src/cryptography_manager/core/manager.py
execute
¶
execute(
request: CryptographyRequest,
) -> tuple[Any, dict[str, Any]]
Run a request against its backend.
Budget accounting and auditing are handled by the workflow layer, not here, so that this method stays usable directly from library code.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
CryptographyRequest
|
The validated request envelope. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Any, dict[str, Any]]
|
A |
Raises:
| Type | Description |
|---|---|
OperationNotImplementedError
|
If the operation has no adapter. |
AdapterError
|
If the backend fails. |
Source code in src/cryptography_manager/core/manager.py
Workflow¶
workflow
¶
Request workflow: validate, pre-check, execute, audit, respond.
This is where the pieces are sequenced. Every path through it -- including every rejection -- ends in exactly one audit record, so a request that was refused is as traceable as one that succeeded.
The ordering deviates from the obvious "check the budget first" in one place: a differential privacy request's budget cost lives in its parameters, so those parameters have to be validated before the budget can be checked against them. Validating first also means a malformed request never touches budget state.
WorkflowOutcome
¶
WorkflowOutcome(
status: WorkflowStatus, response: CryptographyResponse
)
CryptographyWorkflow
¶
CryptographyWorkflow(
manager: CryptographyManager,
audit: AuditLogger,
budget_store: BudgetStore,
)
Sequences a single cryptographic request end to end.
Initialise the workflow.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
manager
|
CryptographyManager
|
Dispatches requests to backend adapters. |
required |
audit
|
AuditLogger
|
Records every outcome. |
required |
budget_store
|
BudgetStore
|
Per-user privacy budget accounting. |
required |
Source code in src/cryptography_manager/core/workflow.py
reject
¶
reject(
status: WorkflowStatus,
user_id: str | None = None,
error: str | None = None,
operation: str | None = None,
backend: str | None = None,
) -> WorkflowOutcome
Record a rejection that happened before execution could start.
Used for failures the request never survives long enough to reach the main path, such as a body too large to parse or one that does not fit the schema. It still writes an audit record, because a refused request has to be as traceable as one that ran.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
status
|
WorkflowStatus
|
The workflow outcome. |
required |
user_id
|
str | None
|
Authenticated user, if known at this point. |
None
|
error
|
str | None
|
Human-readable reason for the rejection. |
None
|
operation
|
str | None
|
Requested operation, if it could be determined. |
None
|
backend
|
str | None
|
Requested backend, if it could be determined. |
None
|
Returns:
| Type | Description |
|---|---|
WorkflowOutcome
|
The outcome, carrying both status and response body. |
Source code in src/cryptography_manager/core/workflow.py
run
¶
run(
request: CryptographyRequest | None,
user_id: str | None,
auth_error: str | None = None,
) -> WorkflowOutcome
Execute one request and produce its response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
CryptographyRequest | None
|
The parsed request, or |
required |
user_id
|
str | None
|
Authenticated user, or |
required |
auth_error
|
str | None
|
Why authorization failed, if it did. |
None
|
Returns:
| Type | Description |
|---|---|
WorkflowOutcome
|
The workflow outcome, carrying both status and response body. |
Source code in src/cryptography_manager/core/workflow.py
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 | |
Privacy budgets¶
budget
¶
Privacy budget accounting.
Differential privacy guarantees only hold if the total epsilon spent against a dataset is bounded. The remaining budget is retrieved per user before each query runs, which means accounting has to outlive a single adapter instance -- hence a store keyed by user rather than a counter living on the adapter.
Two rejection states are tracked separately: a budget already exhausted (nothing left at all) and one merely insufficient for the requested query. They map to different HTTP responses, and the distinction is useful to a caller deciding whether to retry with a cheaper query or give up entirely.
PrivacyBudget
dataclass
¶
Epsilon accounting for a single dataset or session.
can_afford
¶
spend
¶
Consume epsilon from the remaining budget.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
epsilon
|
float
|
Privacy cost of the query. |
required |
Raises:
| Type | Description |
|---|---|
DifferentialPrivacyError
|
If the cost is non-positive or exceeds the remaining budget. |
Source code in src/cryptography_manager/core/budget.py
reset
¶
info
¶
Return the current accounting state.
Source code in src/cryptography_manager/core/budget.py
BudgetStore
¶
Bases: ABC
Per-user privacy budget accounting.
Initialise the store.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
total_epsilon
|
float
|
Budget granted to each user. |
required |
delta
|
float | None
|
Privacy leakage probability recorded alongside the budget. |
None
|
Source code in src/cryptography_manager/core/budget.py
remaining
¶
spend
¶
Deduct epsilon from a user's budget.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
str
|
Identifier of the requesting user. |
required |
epsilon
|
float
|
Privacy cost of the query. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The remaining budget after the deduction. |
Raises:
| Type | Description |
|---|---|
DifferentialPrivacyError
|
If the cost is non-positive or exceeds the remaining budget. |
Source code in src/cryptography_manager/core/budget.py
is_exhausted
¶
Whether a user has no usable budget left.
Uses the same tolerance as spending, so a budget drained by fractional steps is reported as exhausted rather than as an unusable sliver.
Source code in src/cryptography_manager/core/budget.py
reset
¶
Restore a user's full budget. Only valid for a new dataset.
info
¶
Return a user's accounting state, for response metadata.
Source code in src/cryptography_manager/core/budget.py
InMemoryBudgetStore
¶
Bases: BudgetStore
Budget store backed by a process-local dictionary.
Budgets are lost on restart, and are not shared across workers. Suitable for tests, examples and single-process development only.
Source code in src/cryptography_manager/core/budget.py
remaining
¶
spend
¶
Deduct epsilon from a user's budget.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
str
|
Identifier of the requesting user. |
required |
epsilon
|
float
|
Privacy cost of the query. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The remaining budget after the deduction. |
Raises:
| Type | Description |
|---|---|
DifferentialPrivacyError
|
If the cost is non-positive or exceeds the remaining budget. |
Source code in src/cryptography_manager/core/budget.py
is_exhausted
¶
Whether a user has no usable budget left.
Uses the same tolerance as spending, so a budget drained by fractional steps is reported as exhausted rather than as an unusable sliver.
Source code in src/cryptography_manager/core/budget.py
reset
¶
Restore a user's full budget. Only valid for a new dataset.
info
¶
Return a user's accounting state, for response metadata.
Source code in src/cryptography_manager/core/budget.py
FileBudgetStore
¶
Bases: BudgetStore
Budget store backed by a JSON file.
Survives restarts, which matters because a budget that resets when the
container restarts is not a privacy guarantee. Writes are atomic via
os.replace. This is still single-node: a multi-replica deployment
needs a shared backend, and this class is the seam to swap it in at.
Source code in src/cryptography_manager/core/budget.py
remaining
¶
spend
¶
Deduct epsilon from a user's budget.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
str
|
Identifier of the requesting user. |
required |
epsilon
|
float
|
Privacy cost of the query. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The remaining budget after the deduction. |
Raises:
| Type | Description |
|---|---|
DifferentialPrivacyError
|
If the cost is non-positive or exceeds the remaining budget. |
Source code in src/cryptography_manager/core/budget.py
is_exhausted
¶
Whether a user has no usable budget left.
Uses the same tolerance as spending, so a budget drained by fractional steps is reported as exhausted rather than as an unusable sliver.
Source code in src/cryptography_manager/core/budget.py
reset
¶
Restore a user's full budget. Only valid for a new dataset.
info
¶
Return a user's accounting state, for response metadata.
Source code in src/cryptography_manager/core/budget.py
Audit trail¶
audit
¶
Audit record construction, sanitisation and delivery.
Every request produces exactly one audit record, whether it succeeded, was rejected before execution, or failed inside a backend -- a rejected request that leaves no trace is precisely the one an investigator will want later.
Two rules shape this module. Sensitive material never enters a record: not key material, not plaintext, not the dataset, not secret shares. And delivery never fails a request: an unreachable downstream service is logged and the record is still written locally, because losing the operation's result because the audit trail was briefly unavailable would be a worse outcome than a delayed record.
AuditLogger
¶
AuditLogger(
log_path: str | Path = "./audit/crm-audit.jsonl",
targets: dict[str, str] | None = None,
timeout: float = 5.0,
)
Builds audit records, writes them locally and forwards them onward.
Initialise the logger.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_path
|
str | Path
|
JSON-lines file receiving every record. |
'./audit/crm-audit.jsonl'
|
targets
|
dict[str, str] | None
|
Downstream services to forward to, keyed by name. |
None
|
timeout
|
float
|
Per-request timeout when forwarding. |
5.0
|
Source code in src/cryptography_manager/core/audit.py
build
¶
build(
*,
status: str,
user_id: str | None = None,
operation: str | None = None,
backend: str | None = None,
execution_time: float = 0.0,
parameters: dict[str, Any] | None = None,
error_message: str | None = None,
) -> AuditRecord
Construct a sanitised audit record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
status
|
str
|
Workflow outcome. |
required |
user_id
|
str | None
|
Identifier of the requesting user, if known. |
None
|
operation
|
str | None
|
Requested cryptographic operation. |
None
|
backend
|
str | None
|
Library used. |
None
|
execution_time
|
float
|
Duration of execution, in seconds. |
0.0
|
parameters
|
dict[str, Any] | None
|
Raw parameters; sanitised before being stored. |
None
|
error_message
|
str | None
|
Failure detail, if any. |
None
|
Returns:
| Type | Description |
|---|---|
AuditRecord
|
The record, ready to be emitted. |
Source code in src/cryptography_manager/core/audit.py
emit
¶
emit(record: AuditRecord) -> AuditRecord
Persist a record locally and schedule it for forwarding.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
AuditRecord
|
The record to emit. |
required |
Returns:
| Type | Description |
|---|---|
AuditRecord
|
The same record, for convenience. |
Source code in src/cryptography_manager/core/audit.py
record
¶
record(**kwargs: Any) -> AuditRecord
sanitize
¶
Strip sensitive material from a parameter structure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
Arbitrary parameter data. |
required |
_depth
|
int
|
Internal recursion guard. |
0
|
Returns:
| Type | Description |
|---|---|
Any
|
A copy safe to persist and forward. |
Source code in src/cryptography_manager/core/audit.py
Workflow status¶
status
¶
Workflow status codes and their HTTP mapping.
The CrM distinguishes between the workflow status -- the outcome of the internal auth -> pre-check -> validate -> execute -> audit pipeline -- and the HTTP status eventually returned to the caller. Keeping them separate matters because every workflow outcome, including the rejected ones, still produces an audit record.
WorkflowStatus
¶
Bases: str, Enum
Outcome of a cryptographic workflow.
NOT_IMPLEMENTED covers operations that are accepted by the schema but
have no adapter yet, so a caller can tell "you asked for something that
does not exist" apart from "you asked for something not built yet".
http_status_for
¶
http_status_for(status: WorkflowStatus) -> int
Map a workflow status onto its HTTP response code.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
status
|
WorkflowStatus
|
The workflow outcome to translate. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The HTTP status code to return to the caller. |