agentconfigsafe 0.1.1__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.
- agentconfigsafe-0.1.1/.github/workflows/release.yml +41 -0
- agentconfigsafe-0.1.1/.gitignore +9 -0
- agentconfigsafe-0.1.1/AGENTS.md +349 -0
- agentconfigsafe-0.1.1/CLAUDE.md +363 -0
- agentconfigsafe-0.1.1/LICENSE +21 -0
- agentconfigsafe-0.1.1/PKG-INFO +93 -0
- agentconfigsafe-0.1.1/README.md +60 -0
- agentconfigsafe-0.1.1/docs/agents/domain.md +6 -0
- agentconfigsafe-0.1.1/docs/agents/issue-tracker.md +13 -0
- agentconfigsafe-0.1.1/docs/agents/triage-labels.md +9 -0
- agentconfigsafe-0.1.1/pyproject.toml +59 -0
- agentconfigsafe-0.1.1/src/agentsafe/__init__.py +12 -0
- agentconfigsafe-0.1.1/src/agentsafe/cli.py +89 -0
- agentconfigsafe-0.1.1/src/agentsafe/config.py +62 -0
- agentconfigsafe-0.1.1/src/agentsafe/exceptions.py +17 -0
- agentconfigsafe-0.1.1/src/agentsafe/kms/__init__.py +46 -0
- agentconfigsafe-0.1.1/src/agentsafe/kms/base.py +46 -0
- agentconfigsafe-0.1.1/src/agentsafe/kms/oci_provider.py +88 -0
- agentconfigsafe-0.1.1/src/agentsafe/sdk.py +77 -0
- agentconfigsafe-0.1.1/src/agentsafe/store.py +112 -0
- agentconfigsafe-0.1.1/tests/test_cli.py +24 -0
- agentconfigsafe-0.1.1/tests/test_kms_contract.py +22 -0
- agentconfigsafe-0.1.1/tests/test_kms_oci.py +63 -0
- agentconfigsafe-0.1.1/tests/test_sdk.py +22 -0
- agentconfigsafe-0.1.1/tests/test_store.py +35 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions: {}
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
name: Build distribution
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
- run: python -m pip install --upgrade build
|
|
19
|
+
- run: python -m build
|
|
20
|
+
- run: python -m pip install --upgrade twine
|
|
21
|
+
- run: twine check dist/*
|
|
22
|
+
- uses: actions/upload-artifact@v4
|
|
23
|
+
with:
|
|
24
|
+
name: python-package-distributions
|
|
25
|
+
path: dist/
|
|
26
|
+
|
|
27
|
+
publish-to-pypi:
|
|
28
|
+
name: Publish Python distribution to PyPI
|
|
29
|
+
needs: build
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
environment:
|
|
32
|
+
name: pypi
|
|
33
|
+
url: https://pypi.org/p/agentconfigsafe
|
|
34
|
+
permissions:
|
|
35
|
+
id-token: write
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/download-artifact@v4
|
|
38
|
+
with:
|
|
39
|
+
name: python-package-distributions
|
|
40
|
+
path: dist/
|
|
41
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# agentsafe
|
|
2
|
+
|
|
3
|
+
## What this is
|
|
4
|
+
|
|
5
|
+
`agentsafe` is a Python library for securely storing and retrieving configuration
|
|
6
|
+
parameters (API keys, connection strings, credentials, etc.) used by AI agent
|
|
7
|
+
applications. It never invents or holds its own encryption keys — every
|
|
8
|
+
encrypt/decrypt operation is delegated to a customer-managed key in a KMS
|
|
9
|
+
backend. **OCI Key Management Service (KMS)** is the first backend; AWS KMS,
|
|
10
|
+
GCP Cloud KMS, and Azure Key Vault are supported as pluggable alternatives
|
|
11
|
+
(see "KMS provider plugin architecture"). Encrypted values are persisted to a
|
|
12
|
+
local file named `appconfig`.
|
|
13
|
+
|
|
14
|
+
Agent developers use `agentsafe` two ways:
|
|
15
|
+
- **CLI** — `init`, `set`/`store`, `get`/`retrieve`, `remove` config parameters
|
|
16
|
+
from a terminal or setup script.
|
|
17
|
+
- **SDK** — import `agentsafe` in a Python application to fetch decrypted
|
|
18
|
+
config values at runtime.
|
|
19
|
+
|
|
20
|
+
> Status: this repo is currently empty. This file is both the working
|
|
21
|
+
> architecture guide and the spec to scaffold against. All decisions below
|
|
22
|
+
> were made deliberately (see rationale inline) — update this doc, don't
|
|
23
|
+
> silently drift from it, if a decision changes during implementation.
|
|
24
|
+
|
|
25
|
+
## Core concepts
|
|
26
|
+
|
|
27
|
+
- **KMS-backed encryption, no local keys.** `agentsafe` calls the configured
|
|
28
|
+
provider's Encrypt/Decrypt API for every operation. It stores key/vault
|
|
29
|
+
identifiers, never key material.
|
|
30
|
+
- **`appconfig` file.** The on-disk artifact holding all encrypted parameters
|
|
31
|
+
for a project — a **JSON** document mapping each config name to a
|
|
32
|
+
provider-tagged ciphertext envelope (see schema below). No plaintext value
|
|
33
|
+
is ever written into this file. Chosen over YAML/TOML because it needs no
|
|
34
|
+
extra dependency and the file is machine-written/machine-read only — no one
|
|
35
|
+
hand-edits a file full of opaque ciphertext.
|
|
36
|
+
- **OCI profile-based auth (for the OCI provider).** Relies on the standard
|
|
37
|
+
OCI SDK/CLI config file (`~/.oci/config`) and profiles — `agentsafe` does
|
|
38
|
+
not implement its own auth. Each provider owns its own auth mechanism (see
|
|
39
|
+
below); "profile" is an OCI-specific concept, not a cross-provider one.
|
|
40
|
+
- **Four required OCI settings**, independently configurable by the end
|
|
41
|
+
user (not hardcoded): **profile name**, **KMS compartment OCID** (OCID
|
|
42
|
+
only for v1 — no name-to-OCID resolution; see rationale below), **KMS vault
|
|
43
|
+
crypto endpoint** (the per-vault URL used for Encrypt/Decrypt, distinct
|
|
44
|
+
from the KMS management endpoint), and **KMS key OCID**. The key OCID is
|
|
45
|
+
required for Encrypt; it is also recorded in each OCI ciphertext envelope
|
|
46
|
+
so the correct key can be used for Decrypt.
|
|
47
|
+
- **Pluggable KMS backend via entry-point plugins.** See the dedicated
|
|
48
|
+
section below — this is a first-class, public-API-level design constraint,
|
|
49
|
+
not an implementation detail.
|
|
50
|
+
|
|
51
|
+
## KMS provider plugin architecture
|
|
52
|
+
|
|
53
|
+
OCI KMS is the first backend, not the only one, and the plugin model is
|
|
54
|
+
**fully open to third parties** — someone can `pip install
|
|
55
|
+
agentsafe-kms-hashicorp-vault` and it registers itself without any agentsafe
|
|
56
|
+
core change. This is a deliberate, documented public API, not just an
|
|
57
|
+
internal abstraction:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
# kms/base.py
|
|
61
|
+
class KMSProvider(Protocol):
|
|
62
|
+
def encrypt(self, plaintext: str) -> EncryptedBlob: ...
|
|
63
|
+
def decrypt(self, blob: EncryptedBlob) -> str: ...
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Discovery mechanism: `importlib.metadata` entry points**, group name
|
|
67
|
+
`agentsafe.kms_providers`. Each entry point maps a provider name (`"oci"`,
|
|
68
|
+
`"aws"`, ...) to a class implementing `KMSProvider`.
|
|
69
|
+
|
|
70
|
+
- **OCI is the only bundled and registered provider in v1.**
|
|
71
|
+
`oci_provider.py` lives inside `agentsafe/kms/` and registers in the
|
|
72
|
+
`agentsafe.kms_providers` entry-point group via an entry declared in
|
|
73
|
+
`agentsafe`'s own `pyproject.toml` — dogfooding the exact mechanism a
|
|
74
|
+
third party would use. AWS, GCP, and Azure are future providers and have no
|
|
75
|
+
v1 modules or entry points.
|
|
76
|
+
- **Lazy loading — a provider SDK import happens only when that provider is selected.**
|
|
77
|
+
Discovery (`importlib.metadata.entry_points(group="agentsafe.kms_providers")`)
|
|
78
|
+
only reads entry-point *names*; it never calls `.load()` until that
|
|
79
|
+
specific provider is chosen (via `AGENTSAFE_KMS_PROVIDER` or equivalent).
|
|
80
|
+
Selecting OCI without its optional SDK installed raises a clear
|
|
81
|
+
`ConfigError` (for example, `"provider 'oci' requires oci: pip install
|
|
82
|
+
agentconfigsafe[oci]"`) at selection time.
|
|
83
|
+
- **Name collisions are a hard error at discovery time.** If two distinct
|
|
84
|
+
entry points claim the same provider name (e.g. a third-party package
|
|
85
|
+
also registers `"aws"`), raise a `ConfigError` naming both conflicting
|
|
86
|
+
packages and require the user to uninstall one. Never silently shadow one
|
|
87
|
+
provider with another — this is a security-critical selection, not a
|
|
88
|
+
cosmetic conflict, so it must never resolve by implicit import order.
|
|
89
|
+
- **Each provider owns its own auth/config.** OCI: profile + compartment +
|
|
90
|
+
crypto endpoint + key OCID. AWS: profile/region + key ARN. GCP: service account/ADC +
|
|
91
|
+
key resource name. Azure: `DefaultAzureCredential` chain + vault URL +
|
|
92
|
+
key name. `store.py`/`sdk.py`/`cli.py` never know these details.
|
|
93
|
+
- **The OCI SDK is an optional extra**: `agentconfigsafe[oci]`. Installing bare
|
|
94
|
+
`agentsafe` pulls in no cloud-provider SDK. Future providers will add their
|
|
95
|
+
own optional extras when implemented.
|
|
96
|
+
- **Adding a backend (built-in or third-party) never requires touching**
|
|
97
|
+
`store.py`, `sdk.py`, or `cli.py` — only a new module implementing
|
|
98
|
+
`KMSProvider` plus an entry-point registration.
|
|
99
|
+
|
|
100
|
+
### `EncryptedBlob` schema (the plugin contract)
|
|
101
|
+
|
|
102
|
+
Each `appconfig` entry is a **provider-owned opaque envelope** —
|
|
103
|
+
`store.py` never inspects or validates provider-specific fields, only routes
|
|
104
|
+
by the `provider` tag:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"schema_version": 1,
|
|
109
|
+
"entries": {
|
|
110
|
+
"OPENAI_API_KEY": {
|
|
111
|
+
"provider": "oci",
|
|
112
|
+
"ciphertext": "<base64>",
|
|
113
|
+
"metadata": { "key_id": "...", "key_version": "..." }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- `schema_version` is a top-level field on the *file*, independent of any
|
|
120
|
+
provider's `metadata` shape. V1 records this version but does not perform
|
|
121
|
+
whole-file or provider-envelope schema validation; providers remain the
|
|
122
|
+
authority for their own opaque envelopes. Future releases may use it for
|
|
123
|
+
explicit migration or compatibility handling.
|
|
124
|
+
- `metadata` is a free-form dict whose shape is entirely up to the provider
|
|
125
|
+
that wrote it (OCI: `key_id`/`key_version`; AWS: `key_arn`; GCP: `key_name`;
|
|
126
|
+
Azure: `key_id`/`key_version`). Centralizing a typed schema per provider in
|
|
127
|
+
core was rejected — it would mean every new provider, including
|
|
128
|
+
third-party ones, requires a core code change, defeating the plugin model.
|
|
129
|
+
|
|
130
|
+
## Architecture
|
|
131
|
+
|
|
132
|
+
Target package layout (`src/` layout):
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
agentsafe/
|
|
136
|
+
__init__.py # public SDK surface: AgentSafe class + convenience functions
|
|
137
|
+
config.py # resolves agentsafe's own settings: profile, compartment, crypto endpoint, key ID, kms provider
|
|
138
|
+
store.py # appconfig file: JSON schema, atomic write (temp file + os.replace), advisory file lock
|
|
139
|
+
sdk.py # AgentSafe class: init(), set(key, value), get(key), remove(key), list_keys()
|
|
140
|
+
cli.py # Typer CLI: init, set/store, get/retrieve, remove/rm, list
|
|
141
|
+
exceptions.py # AgentSafeError hierarchy
|
|
142
|
+
kms/
|
|
143
|
+
__init__.py # entry-point discovery + factory: get_provider(name) -> KMSProvider
|
|
144
|
+
base.py # KMSProvider Protocol + EncryptedBlob type
|
|
145
|
+
oci_provider.py # OCI KMS implementation (oci.key_management + oci.kms_crypto), extra: agentconfigsafe[oci]
|
|
146
|
+
tests/
|
|
147
|
+
test_kms_contract.py # fake in-memory KMSProvider; exercises store.py/sdk.py/cli.py logic, no real crypto
|
|
148
|
+
test_kms_oci.py # unittest.mock.patch on the oci client; verifies request/response mapping only
|
|
149
|
+
test_store.py # appconfig schema/round-trip, concurrency/locking
|
|
150
|
+
test_sdk.py
|
|
151
|
+
test_cli.py
|
|
152
|
+
pyproject.toml
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Data flow for a `get`:
|
|
156
|
+
`SDK/CLI call` → `store.py` reads `appconfig`, finds the entry for the key →
|
|
157
|
+
`kms/__init__.py` resolves the entry's `provider` tag to a `KMSProvider`
|
|
158
|
+
(loading its entry point lazily) → `provider.decrypt(blob)` calls the
|
|
159
|
+
backend's decrypt API using that provider's own credentials/endpoint →
|
|
160
|
+
plaintext returned to the caller only, never written back to disk or logged.
|
|
161
|
+
|
|
162
|
+
## Settings & file locations
|
|
163
|
+
|
|
164
|
+
- **`~/.agentsafe/config`** (INI-style) — machine-wide default settings:
|
|
165
|
+
`kms_provider`, `profile`, `compartment`, `crypto_endpoint`, `key_id`, and future
|
|
166
|
+
per-provider settings. Written by `agentsafe init`, analogous to
|
|
167
|
+
`~/.oci/config`/`~/.aws/config`.
|
|
168
|
+
- **No project-local settings file in v1.** A project needing a different
|
|
169
|
+
provider/profile/compartment than the machine default overrides via env
|
|
170
|
+
vars or CLI flags — no second settings-file tier to design/maintain until
|
|
171
|
+
real usage shows env vars aren't enough.
|
|
172
|
+
- **`appconfig` is per-project, cwd-based** (e.g. `./appconfig`) — every
|
|
173
|
+
project keeps its own secret set. The global settings file only supplies
|
|
174
|
+
*which KMS to talk to*; it never determines *which secrets* are visible.
|
|
175
|
+
|
|
176
|
+
Settings resolution order (first match wins):
|
|
177
|
+
1. Explicit constructor/CLI arguments
|
|
178
|
+
2. Environment variables: `AGENTSAFE_KMS_PROVIDER`, `AGENTSAFE_PROFILE`, `AGENTSAFE_COMPARTMENT`, `AGENTSAFE_CRYPTO_ENDPOINT`, `AGENTSAFE_KEY_ID`
|
|
179
|
+
3. `~/.agentsafe/config`
|
|
180
|
+
4. For `kms_provider` only, select `oci` when it remains unspecified.
|
|
181
|
+
For every other required setting, raise a clear `ConfigError` — never
|
|
182
|
+
silently fall back to defaults for security-relevant settings.
|
|
183
|
+
|
|
184
|
+
## SDK usage (target shape)
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from agentsafe import AgentSafe
|
|
188
|
+
|
|
189
|
+
safe = AgentSafe(
|
|
190
|
+
kms_provider="oci", # selected by default when omitted
|
|
191
|
+
profile="DEFAULT",
|
|
192
|
+
compartment="ocid1.compartment.oc1..xxxx", # OCID only, no name resolution (see below)
|
|
193
|
+
crypto_endpoint="https://<vault>-crypto.kms.<region>.oraclecloud.com",
|
|
194
|
+
key_id="ocid1.key.oc1..<key-ocid>",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
safe.set("OPENAI_API_KEY", "sk-...") # value: str only (see "Value types")
|
|
198
|
+
value = safe.get("OPENAI_API_KEY") # raises KeyNotFoundError if absent
|
|
199
|
+
safe.remove("OPENAI_API_KEY")
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`profile`/`compartment`/`crypto_endpoint`/`key_id` are OCI-provider settings; other
|
|
203
|
+
providers take their own equivalent kwargs (e.g. AWS: `region`, `key_arn`).
|
|
204
|
+
`AgentSafe.__init__` forwards whatever settings are relevant to
|
|
205
|
+
`kms.get_provider(kms_provider, **settings)` rather than hardcoding OCI's
|
|
206
|
+
argument names into the public SDK surface.
|
|
207
|
+
|
|
208
|
+
**Value types: strings only.** `set(key, value)` requires `value: str`.
|
|
209
|
+
Structured secrets (JSON blobs, etc.) are the caller's responsibility to
|
|
210
|
+
serialize first. This matches the primary use case (API keys, tokens,
|
|
211
|
+
connection strings) and avoids a serialization-format decision and CLI
|
|
212
|
+
argument-parsing question that arbitrary JSON values would force.
|
|
213
|
+
|
|
214
|
+
**Config names: arbitrary non-empty strings.** `set`, `get`, and `remove`
|
|
215
|
+
accept any non-empty string as a key name; names need not be valid environment
|
|
216
|
+
variable identifiers.
|
|
217
|
+
|
|
218
|
+
**Missing key: raise, don't return `None`.** `get()` on an absent key raises
|
|
219
|
+
`KeyNotFoundError`. A silent `None` for a missing secret is exactly the kind
|
|
220
|
+
of thing that turns into a confusing downstream failure (e.g. an agent
|
|
221
|
+
calling an API with `api_key=None`) instead of a clear error where the
|
|
222
|
+
problem actually is.
|
|
223
|
+
|
|
224
|
+
**Compartment: OCID only, no name resolution in v1.** Resolving a friendly
|
|
225
|
+
name would need an extra `oci.identity` `list_compartments` call, IAM
|
|
226
|
+
permissions the caller might not have, and is ambiguous across nested
|
|
227
|
+
compartments. The OCID is what the Encrypt/Decrypt calls need anyway, and
|
|
228
|
+
it's a one-time copy from the OCI console during `init`.
|
|
229
|
+
|
|
230
|
+
## CLI usage (target shape)
|
|
231
|
+
|
|
232
|
+
Built with **Typer** (type-hint-driven, built on Click so hidden-input
|
|
233
|
+
prompts etc. are still available; fits a type-hint-first codebase with less
|
|
234
|
+
boilerplate than raw Click).
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
agentsafe init --profile DEFAULT --compartment <ocid> --crypto-endpoint <url> --key-id <ocid>
|
|
238
|
+
agentsafe set OPENAI_API_KEY [VALUE] # prompts (hidden input) if VALUE omitted
|
|
239
|
+
agentsafe get OPENAI_API_KEY
|
|
240
|
+
agentsafe remove OPENAI_API_KEY
|
|
241
|
+
agentsafe list # key names only — see below
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`init` writes the resolved settings into `~/.agentsafe/config` and creates an
|
|
245
|
+
empty `appconfig` in the current directory. It fails if either target already
|
|
246
|
+
exists; it never overwrites an existing configuration or secret store. It does not create cloud
|
|
247
|
+
resources (vault, key, compartment) — those are assumed to already exist and
|
|
248
|
+
be reachable via the given profile/credentials.
|
|
249
|
+
|
|
250
|
+
**`list` shows key names only, never decrypted values.** It does not call
|
|
251
|
+
Decrypt at all — fast, and it never puts plaintext secrets on a
|
|
252
|
+
terminal/screen-recording/CI log just because someone wanted to see what's
|
|
253
|
+
configured.
|
|
254
|
+
|
|
255
|
+
## Security requirements (non-negotiable for any change)
|
|
256
|
+
|
|
257
|
+
- Plaintext values must never be written to disk, logs, shell history files,
|
|
258
|
+
or exception messages/tracebacks.
|
|
259
|
+
- `appconfig` contains ciphertext and metadata only — never plaintext, never
|
|
260
|
+
raw key material.
|
|
261
|
+
- Decryption happens only in memory at the moment the SDK/CLI caller requests
|
|
262
|
+
a value; do not cache decrypted plaintext beyond that call's return.
|
|
263
|
+
- Key material and key versions live only in the KMS backend (whichever
|
|
264
|
+
provider is configured); `agentsafe` stores only identifiers (key
|
|
265
|
+
OCID/ARN/resource name, vault/endpoint), never key bytes, regardless of
|
|
266
|
+
provider.
|
|
267
|
+
- `set`/`store` should support a hidden-input prompt or stdin piping as an
|
|
268
|
+
alternative to a bare CLI argument, since arguments are visible via `ps`
|
|
269
|
+
and shell history — document this tradeoff wherever the argument form is
|
|
270
|
+
offered.
|
|
271
|
+
- Never swallow a KMS provider's authentication/authorization errors — wrap
|
|
272
|
+
them in a typed `KMSError`/`ConfigError` using exception chaining, retaining
|
|
273
|
+
the original provider exception as the cause. Do not fall back to an
|
|
274
|
+
insecure path. This applies uniformly across providers, not just OCI.
|
|
275
|
+
- `list` never decrypts (see CLI usage above).
|
|
276
|
+
- Provider name collisions are a hard error, never silently resolved (see
|
|
277
|
+
plugin architecture above).
|
|
278
|
+
- `~/.agentsafe/config`, `appconfig`, temporary files used to replace
|
|
279
|
+
`appconfig`, and lock files must be created owner-readable/writable only
|
|
280
|
+
(mode `0600`) where the operating system supports POSIX file permissions.
|
|
281
|
+
|
|
282
|
+
## Out of scope for v1 (deliberately deferred)
|
|
283
|
+
|
|
284
|
+
- **Key rotation / re-encryption.** No `agentsafe rotate` command. Decrypt
|
|
285
|
+
already works regardless of which key version encrypted a given entry (as
|
|
286
|
+
long as that version isn't disabled/deleted), so this is a hygiene
|
|
287
|
+
improvement to add later once there's real usage to inform the design, not
|
|
288
|
+
a correctness blocker now.
|
|
289
|
+
- **Bulk import/export** (e.g. `.env` file import, bulk env-var export).
|
|
290
|
+
Single-key `set`/`get`/`remove` covers the core use case; bulk import also
|
|
291
|
+
raises its own security UX question (a plaintext `.env` source file is
|
|
292
|
+
exactly what agentsafe exists to replace) that shouldn't block v1.
|
|
293
|
+
- **Compartment name → OCID resolution.** OCID only for now (see SDK usage
|
|
294
|
+
section).
|
|
295
|
+
- **Project-local settings file.** Global `~/.agentsafe/config` plus env
|
|
296
|
+
vars/CLI flags only (see "Settings & file locations").
|
|
297
|
+
|
|
298
|
+
## Concurrency & durability
|
|
299
|
+
|
|
300
|
+
- **Atomic writes**: write to a temp file, then `os.replace()` — never leave
|
|
301
|
+
`appconfig` half-written even on a crash mid-write.
|
|
302
|
+
- **Advisory file lock** (e.g. the `filelock` library) held across the full
|
|
303
|
+
read-modify-write cycle of any `set`/`remove`, so two concurrent CLI/SDK
|
|
304
|
+
writers (e.g. a CLI `set` racing an SDK write from a running app) can't
|
|
305
|
+
silently clobber each other's change.
|
|
306
|
+
|
|
307
|
+
## Dependencies (expected)
|
|
308
|
+
|
|
309
|
+
- `oci` — the OCI Python SDK (`oci.key_management` + `oci.kms_crypto`). Used
|
|
310
|
+
by `kms/oci_provider.py` only, behind the `agentconfigsafe[oci]` extra.
|
|
311
|
+
- Future provider SDKs are added as optional extras only when their providers
|
|
312
|
+
are implemented (for example, `boto3` for AWS).
|
|
313
|
+
- `typer` — CLI framework.
|
|
314
|
+
- `filelock` — advisory locking for `appconfig` writes.
|
|
315
|
+
- No custom cryptography implementation — all encrypt/decrypt is delegated to
|
|
316
|
+
the configured KMS provider.
|
|
317
|
+
- PyPI distribution name **`agentconfigsafe`**; the Python import package and
|
|
318
|
+
CLI command remain **`agentsafe`**.
|
|
319
|
+
- **Python 3.10+** — modern `X | Y` union hints without `__future__` imports,
|
|
320
|
+
and the clean keyword-arg form of `importlib.metadata.entry_points(group=...)`.
|
|
321
|
+
|
|
322
|
+
## Dev commands
|
|
323
|
+
|
|
324
|
+
Fill these in as the project is scaffolded; keep this section accurate.
|
|
325
|
+
|
|
326
|
+
- Install (editable, with dev deps): `pip install -e ".[dev]"`
|
|
327
|
+
- Run tests: `pytest`
|
|
328
|
+
- Lint/format: `ruff check .` and `ruff format .`
|
|
329
|
+
- Type-check: `mypy src/agentsafe`
|
|
330
|
+
|
|
331
|
+
## Conventions
|
|
332
|
+
|
|
333
|
+
- Type hints on all public functions/methods; docstrings on the public SDK
|
|
334
|
+
surface (`AgentSafe` and its methods).
|
|
335
|
+
- Raise typed exceptions from `agentsafe.exceptions` (`KeyNotFoundError`,
|
|
336
|
+
`KMSError`, `ConfigError`, ...) rather than bare `Exception`/`ValueError`.
|
|
337
|
+
- **Test the provider-agnostic logic once, against the `KMSProvider`
|
|
338
|
+
contract** (`test_kms_contract.py`), using a trivial fake in-memory
|
|
339
|
+
provider — no real crypto, no cloud-specific mocking library. Each
|
|
340
|
+
concrete provider then gets its own thin test file that only verifies it
|
|
341
|
+
maps correctly to/from its cloud SDK's request/response shape
|
|
342
|
+
(`unittest.mock.patch` on that SDK's client — no `moto` or other
|
|
343
|
+
provider-specific mocking framework needed).
|
|
344
|
+
- Reserve real KMS calls for a separate integration test suite gated behind
|
|
345
|
+
an explicit marker/env var (e.g. `AGENTSAFE_RUN_INTEGRATION=1`), since
|
|
346
|
+
those require live cloud credentials and a real vault/key.
|
|
347
|
+
- Keep `store.py` (file format) and `kms/` (provider calls) decoupled from
|
|
348
|
+
`sdk.py`/`cli.py` (user-facing surface) so either can be tested and
|
|
349
|
+
evolved independently.
|