trailsign 0.1.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.
@@ -0,0 +1,20 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write # required for PyPI Trusted Publishing (OIDC) -- no API token stored
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.13"
18
+ - run: pip install build
19
+ - run: python -m build
20
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,21 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.13"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ - run: pip install -e ".[test]"
21
+ - run: pytest -v
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .pytest_cache/
4
+ *.egg-info/
5
+ .venv/
6
+ build/
7
+ dist/
8
+
9
+ # Local-only infrastructure notes (real IPs, SSH key paths, live OCIDs) --
10
+ # must NEVER be committed. See tools/verify_oracle_vault.py, which reads
11
+ # only from CLI args/env, never from this file directly, and stays
12
+ # secret-free so it's safe to commit.
13
+ local-infra/
@@ -0,0 +1,81 @@
1
+ # CLAUDE.md
2
+
3
+ Guidance for Claude Code working in this repo. Kept short — detail
4
+ lives in `docs/design.md` and `README.md`, not here.
5
+
6
+ ## Overview
7
+
8
+ Trailsign is a small, language-independent library for resolving
9
+ application settings from a declarative, self-describing config, where
10
+ each value declares its own source (a literal, an environment variable,
11
+ a vault secret, ...) instead of the caller assuming where to look.
12
+ **Python package built out as of 2026-09-01** — `src/trailsign/` is a
13
+ real installable package (`pyproject.toml`, src layout) with a test
14
+ suite (`tests/`). MIT licensed, public on GitHub
15
+ (https://github.com/nankma/trailsign), CI (`.github/workflows/test.yml`)
16
+ runs on push/PR, `main` requires a passing PR before merge. A
17
+ `.github/workflows/publish.yml` exists for PyPI Trusted Publishing but
18
+ the project hasn't been published there yet — see "Immediate next
19
+ work". A port to a second language is the other remaining open work.
20
+
21
+ Extracted from a Telegram news-trend bot (Auguring, formerly Argus)
22
+ where this design started — see `docs/design.md`'s own "Origin" section
23
+ for why it moved here instead of staying bot-specific.
24
+
25
+ ## Landmines
26
+
27
+ - **`trailsign-resolve:` is the only dispatch key — never rename it back
28
+ to a bare `resolve`, and never let it collide with `type:` or any
29
+ other field.** Two real bugs already happened this way (an overloaded
30
+ `type:` field, then a plain `resolve:` that was still a generic
31
+ collision risk) before landing here — see `docs/design.md`'s "Fixing
32
+ an ambiguity" section before touching the dispatch key.
33
+ - **This library never constructs consumer objects, only resolves values
34
+ to plain data.** Turning a resolved config block into a live object
35
+ (an adaptor, a client, anything) is always the *consumer's* own
36
+ factory, never this library's job — see `docs/design.md`'s "Two jobs,
37
+ two owners" section. Don't add object-construction here even if it
38
+ seems convenient for a first real consumer.
39
+ - **`OracleKeyVaultResolver` uses instance-principal auth
40
+ (`_oci_secrets_client()` in `src/trailsign/settings.py`) — not a
41
+ static config file, not an explicit key.** Verified 2026-09-01 against
42
+ a real OCI Vault secret from a compute instance (see
43
+ `tools/verify_oracle_vault.py`); only works from inside an OCI compute
44
+ instance (requires a dynamic-group IAM policy granting `read
45
+ secret-bundles` — a real gap hit during that verification, not a code
46
+ bug). `credential_sources`' `region`/`vault_ocid`/`compartment_ocid`
47
+ fields are validated to exist via `source:` but are **not** actually
48
+ consumed by this auth shape — don't assume they're load-bearing if
49
+ refactoring this resolver; see `docs/design.md`'s correction note
50
+ under "The converged design".
51
+ - **`OracleKeyVaultResolver.resolve()` validates the node's own fields
52
+ (`source`, `secret_ocid`) *before* `import oci`.** This was a real bug
53
+ found while writing tests: `import oci` used to run first, so a
54
+ missing `secret_ocid` raised `ModuleNotFoundError` instead of
55
+ `SettingsError` whenever the `oci` package wasn't installed — breaking
56
+ the "no oci dependency needed unless actually used" guarantee for the
57
+ validation-error paths, not just the happy path. Keep the import after
58
+ field validation if this method is touched again.
59
+ - **Secret hygiene**: never commit real infrastructure values (VM IPs,
60
+ SSH key paths, live OCIDs). `local-infra/infrastructure.yaml` holds
61
+ them and is gitignored; `tools/verify_oracle_vault.py` takes everything
62
+ sensitive via CLI arg only and stays secret-free so it's safe to
63
+ commit. Before this repo ever goes from private to public, run the
64
+ global `audit-before-going-public` skill first.
65
+
66
+ ## Where to look
67
+
68
+ | Need | Where |
69
+ |---|---|
70
+ | Full design: data flow, diagrams, resolved/still-open questions | `docs/design.md` |
71
+ | Python reference implementation | `src/trailsign/settings.py` |
72
+ | Test suite | `tests/` (`conftest.py` has the shared fixture config) |
73
+ | Project status, what's built vs. not | `README.md` |
74
+ | How to write/extend design docs like `docs/design.md` | the `writing-system-design-docs` skill (global, not repo-local) |
75
+
76
+ ## Immediate next work
77
+
78
+ Not built yet, in rough order:
79
+ 1. First PyPI publish: register the pending Trusted Publisher on pypi.org (Owner `nankma`, repo `trailsign`, workflow `publish.yml`, environment `pypi` — see `.github/workflows/publish.yml`), then cut a GitHub Release to trigger it
80
+ 2. Resolve `docs/design.md`'s remaining "Still open" items as they come up in practice, not speculatively (a non-instance-principal OCI auth shape, for consumers running outside an OCI compute instance, is the main one left)
81
+ 3. Consider a port to a second language now that the Python package is solid, since the whole design's point is being language-independent, not just Python
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nankang Ma
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.5
2
+ Name: trailsign
3
+ Version: 0.1.0
4
+ Summary: Resolves a declarative, self-describing config into plain values
5
+ Project-URL: Homepage, https://github.com/nankma/trailsign
6
+ Project-URL: Repository, https://github.com/nankma/trailsign
7
+ Author-email: Nankang Ma <jjkkma@gmail.com>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Nankang Ma
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Operating System :: OS Independent
32
+ Classifier: Programming Language :: Python :: 3
33
+ Requires-Python: >=3.10
34
+ Requires-Dist: pyyaml>=6.0
35
+ Provides-Extra: test
36
+ Requires-Dist: pytest>=8.0; extra == 'test'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # Trailsign
40
+
41
+ A small, language-independent library for resolving application
42
+ settings from a declarative, self-describing config — where each value
43
+ states its own source (a literal, an environment variable, a vault
44
+ secret, ...) instead of the calling code assuming where to look.
45
+
46
+ ```yaml
47
+ api-key:
48
+ trailsign-resolve: environment-variable
49
+ name: GNEWS_API_KEY
50
+ ```
51
+
52
+ `trailsign-resolve:` is a reserved, namespaced key — deliberately not a
53
+ bare word like `resolve` — so it can never collide with a consuming
54
+ project's own field names. It dispatches to a pluggable resolver;
55
+ whatever it resolves to is handed to the consumer as a plain value, with
56
+ no trace of where it came from left in the shape.
57
+
58
+ ## Status
59
+
60
+ **Python package built out, as of 2026-09-01.** `src/trailsign/` is a
61
+ real installable package (`pyproject.toml`, src layout) with a test
62
+ suite covering the resolve walk, the three built-in resolvers
63
+ (`OracleKeyVaultResolver` verified against a real OCI Vault secret —
64
+ see `tools/verify_oracle_vault.py`), `validate()`'s combined-error
65
+ behavior, and the `trailsign-resolve` vs. `type` non-collision
66
+ regression. MIT licensed (see `LICENSE`). Public on GitHub; CI runs the
67
+ test suite on every push/PR. Not yet published to PyPI. A port to at
68
+ least one other language is still open, since the design's whole point
69
+ is being language-independent, not just Python.
70
+
71
+ ### Installing it
72
+
73
+ Not on PyPI yet — until then, install straight from GitHub, ideally
74
+ pinned to a tag once one exists:
75
+
76
+ ```
77
+ pip install git+https://github.com/nankma/trailsign.git@main
78
+ ```
79
+
80
+ Install for development on this repo: `pip install -e ".[test]"`, then
81
+ `pytest`.
82
+
83
+ ## Start here
84
+
85
+ - [`docs/design.md`](docs/design.md) — the core design: the config
86
+ shape, the resolve/dispatch contract (holds equally for a Go
87
+ `interface`, a Rust `trait`, or a Python `typing.Protocol`), why it's
88
+ shaped this way, two worked examples with diagrams, and what's still
89
+ undecided.
90
+ - [`src/trailsign/settings.py`](src/trailsign/settings.py) — the Python
91
+ reference implementation, matching `docs/design.md` exactly.
92
+ - [`tests/`](tests/) — the test suite; `tests/conftest.py` has a shared
93
+ fixture config mirroring `docs/design.md`'s worked examples.
94
+ - the `writing-system-design-docs` skill (global, not repo-local) — the
95
+ doc-writing convention `docs/design.md` follows, carried over
96
+ from where this project started in case future design docs here want
97
+ the same discipline (language-independent contracts, diagrams, a
98
+ "still open" section that's actually kept honest).
99
+
100
+ ## Origin
101
+
102
+ This design started inside a Telegram news-trend bot (Auguring, formerly
103
+ Argus) while building a settings abstraction so that bot could run
104
+ standalone as well as on its current cloud deployment. The design turned
105
+ out to be genuinely content-independent — nothing in it assumes anything
106
+ bot-specific — so it's being extracted into its own project rather than
107
+ staying bot-only. `docs/design.md`'s own "Origin" section has the
108
+ originating project's actual settings inventory, kept for context on why
109
+ the design has the shape it has.
110
+
111
+ ## The split that makes this portable
112
+
113
+ Two jobs, two owners, and only one of them is this library's job:
114
+
115
+ 1. **Resolving a marked value to a plain value** — Trailsign's job, and
116
+ only Trailsign's job. Nothing here knows or cares what the resolved
117
+ value is *for*.
118
+ 2. **Turning a resolved config block into a live object** — never
119
+ Trailsign's job. Each consumer owns its own small factory (a plain
120
+ name→constructor map) that builds whatever it needs from the
121
+ already-resolved values this library hands it.
122
+
123
+ Full reasoning for the split, plus two complete worked examples (a news
124
+ source's API key from an environment variable, a telemetry backend's
125
+ credential from a vault) with diagrams, is in `docs/design.md`.
126
+
127
+ ## What's not decided yet
128
+
129
+ See `docs/design.md`'s own "Still open" section for full detail:
130
+
131
+ - A non-instance-principal auth shape for `oracleKeyVault` (today it only
132
+ works from inside an OCI compute instance)
133
+ - Validation-timing default (eager vs. lazy)
134
+ - A port to a second language
@@ -0,0 +1,96 @@
1
+ # Trailsign
2
+
3
+ A small, language-independent library for resolving application
4
+ settings from a declarative, self-describing config — where each value
5
+ states its own source (a literal, an environment variable, a vault
6
+ secret, ...) instead of the calling code assuming where to look.
7
+
8
+ ```yaml
9
+ api-key:
10
+ trailsign-resolve: environment-variable
11
+ name: GNEWS_API_KEY
12
+ ```
13
+
14
+ `trailsign-resolve:` is a reserved, namespaced key — deliberately not a
15
+ bare word like `resolve` — so it can never collide with a consuming
16
+ project's own field names. It dispatches to a pluggable resolver;
17
+ whatever it resolves to is handed to the consumer as a plain value, with
18
+ no trace of where it came from left in the shape.
19
+
20
+ ## Status
21
+
22
+ **Python package built out, as of 2026-09-01.** `src/trailsign/` is a
23
+ real installable package (`pyproject.toml`, src layout) with a test
24
+ suite covering the resolve walk, the three built-in resolvers
25
+ (`OracleKeyVaultResolver` verified against a real OCI Vault secret —
26
+ see `tools/verify_oracle_vault.py`), `validate()`'s combined-error
27
+ behavior, and the `trailsign-resolve` vs. `type` non-collision
28
+ regression. MIT licensed (see `LICENSE`). Public on GitHub; CI runs the
29
+ test suite on every push/PR. Not yet published to PyPI. A port to at
30
+ least one other language is still open, since the design's whole point
31
+ is being language-independent, not just Python.
32
+
33
+ ### Installing it
34
+
35
+ Not on PyPI yet — until then, install straight from GitHub, ideally
36
+ pinned to a tag once one exists:
37
+
38
+ ```
39
+ pip install git+https://github.com/nankma/trailsign.git@main
40
+ ```
41
+
42
+ Install for development on this repo: `pip install -e ".[test]"`, then
43
+ `pytest`.
44
+
45
+ ## Start here
46
+
47
+ - [`docs/design.md`](docs/design.md) — the core design: the config
48
+ shape, the resolve/dispatch contract (holds equally for a Go
49
+ `interface`, a Rust `trait`, or a Python `typing.Protocol`), why it's
50
+ shaped this way, two worked examples with diagrams, and what's still
51
+ undecided.
52
+ - [`src/trailsign/settings.py`](src/trailsign/settings.py) — the Python
53
+ reference implementation, matching `docs/design.md` exactly.
54
+ - [`tests/`](tests/) — the test suite; `tests/conftest.py` has a shared
55
+ fixture config mirroring `docs/design.md`'s worked examples.
56
+ - the `writing-system-design-docs` skill (global, not repo-local) — the
57
+ doc-writing convention `docs/design.md` follows, carried over
58
+ from where this project started in case future design docs here want
59
+ the same discipline (language-independent contracts, diagrams, a
60
+ "still open" section that's actually kept honest).
61
+
62
+ ## Origin
63
+
64
+ This design started inside a Telegram news-trend bot (Auguring, formerly
65
+ Argus) while building a settings abstraction so that bot could run
66
+ standalone as well as on its current cloud deployment. The design turned
67
+ out to be genuinely content-independent — nothing in it assumes anything
68
+ bot-specific — so it's being extracted into its own project rather than
69
+ staying bot-only. `docs/design.md`'s own "Origin" section has the
70
+ originating project's actual settings inventory, kept for context on why
71
+ the design has the shape it has.
72
+
73
+ ## The split that makes this portable
74
+
75
+ Two jobs, two owners, and only one of them is this library's job:
76
+
77
+ 1. **Resolving a marked value to a plain value** — Trailsign's job, and
78
+ only Trailsign's job. Nothing here knows or cares what the resolved
79
+ value is *for*.
80
+ 2. **Turning a resolved config block into a live object** — never
81
+ Trailsign's job. Each consumer owns its own small factory (a plain
82
+ name→constructor map) that builds whatever it needs from the
83
+ already-resolved values this library hands it.
84
+
85
+ Full reasoning for the split, plus two complete worked examples (a news
86
+ source's API key from an environment variable, a telemetry backend's
87
+ credential from a vault) with diagrams, is in `docs/design.md`.
88
+
89
+ ## What's not decided yet
90
+
91
+ See `docs/design.md`'s own "Still open" section for full detail:
92
+
93
+ - A non-instance-principal auth shape for `oracleKeyVault` (today it only
94
+ works from inside an OCI compute instance)
95
+ - Validation-timing default (eager vs. lazy)
96
+ - A port to a second language