etch-record 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,125 @@
1
+ name: release
2
+
3
+ # Publishes tagged releases of etch-record to PyPI via Trusted Publishing
4
+ # (no stored API tokens) and generates Sigstore attestations as the
5
+ # forensic proof-of-provenance. Mirrors the world-model-mcp release
6
+ # workflow pattern so the security posture is consistent across both
7
+ # CLIs.
8
+ #
9
+ # Rationale for Trusted Publishing + attestations: the Dec 2024 PyPI
10
+ # Ultralytics attack shipped wave-1 malicious releases carrying valid
11
+ # Sigstore transparency-log entries (proving the legitimate workflow was
12
+ # compromised); wave-2 releases lacked attestations, revealing the
13
+ # bypass path. Trusted Publishing + attestations closes both attack
14
+ # surfaces:
15
+ # - No stored token to steal (OIDC exchanged per-run against PyPI).
16
+ # - Every release is signed by the specific workflow that produced
17
+ # it, so a compromise of any other release path is externally
18
+ # detectable.
19
+ #
20
+ # Prerequisites (one-time repo setup, MUST complete before first tag):
21
+ # 1. Register the Trusted Publisher on PyPI:
22
+ # https://pypi.org/manage/account/publishing/
23
+ # Owner: SaravananJaichandar
24
+ # Repository name: etch-record
25
+ # Workflow filename: release.yml
26
+ # Environment name: pypi
27
+ # (Match the environment name below EXACTLY, or PyPI refuses the
28
+ # OIDC exchange with "invalid-publisher: environment MISSING".)
29
+ # 2. Create the matching GitHub environment:
30
+ # Repo -> Settings -> Environments -> New environment -> name: pypi
31
+ #
32
+ # Trigger: any tag matching v* (e.g. v0.2.0, v0.3.0). Also supports
33
+ # workflow_dispatch for testing the workflow shape without creating a
34
+ # real tag (build job only; publish is gated on tag push).
35
+
36
+ on:
37
+ push:
38
+ tags:
39
+ - 'v*'
40
+ workflow_dispatch:
41
+
42
+ # Top-level default: read-only for most jobs. The publish job
43
+ # overrides with id-token: write for OIDC.
44
+ permissions:
45
+ contents: read
46
+
47
+ jobs:
48
+ build:
49
+ name: Build sdist + wheel
50
+ runs-on: ubuntu-latest
51
+ timeout-minutes: 10
52
+
53
+ steps:
54
+ - uses: actions/checkout@v5
55
+
56
+ - name: Set up Python
57
+ uses: actions/setup-python@v6
58
+ with:
59
+ python-version: '3.11'
60
+
61
+ - name: Install build tools
62
+ run: |
63
+ python -m pip install --upgrade pip
64
+ pip install build
65
+
66
+ - name: Build sdist + wheel
67
+ run: |
68
+ python -m build
69
+
70
+ - name: Sanity check the built artifacts
71
+ # twine check catches malformed long-description rendering
72
+ # (would silently render as raw text on the PyPI page) plus
73
+ # basic archive integrity.
74
+ run: |
75
+ pip install twine
76
+ python -m twine check dist/*
77
+
78
+ - name: Upload dist artifacts
79
+ uses: actions/upload-artifact@v7
80
+ with:
81
+ name: dist
82
+ path: dist/
83
+ retention-days: 30
84
+
85
+ publish-pypi:
86
+ name: Publish to PyPI with Sigstore attestations
87
+ needs: build
88
+ runs-on: ubuntu-latest
89
+ timeout-minutes: 10
90
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
91
+
92
+ # The PyPI Trusted Publisher registered for etch-record names
93
+ # this environment. Both sides must match exactly.
94
+ environment:
95
+ name: pypi
96
+
97
+ # OIDC token permission required for BOTH:
98
+ # - PyPI Trusted Publishing (token verified against the Trusted
99
+ # Publisher rule configured on the project).
100
+ # - Sigstore attestation generation (token used to sign the
101
+ # artifacts under the workflow's identity).
102
+ permissions:
103
+ contents: read
104
+ id-token: write
105
+
106
+ steps:
107
+ - name: Download built dist artifacts
108
+ uses: actions/download-artifact@v8
109
+ with:
110
+ name: dist
111
+ path: dist/
112
+
113
+ - name: Publish to PyPI (Trusted Publishing + Sigstore attestations)
114
+ uses: pypa/gh-action-pypi-publish@release/v1
115
+ with:
116
+ packages-dir: dist/
117
+ # Trusted Publishing is the default authentication method
118
+ # when id-token: write is granted and no token is supplied.
119
+ # Attestations = true generates + uploads Sigstore
120
+ # attestations alongside the release. This closes the
121
+ # Ultralytics-class forgery detection gap: any release that
122
+ # shows up on PyPI without a matching attestation was NOT
123
+ # produced by this workflow.
124
+ attestations: true
125
+ verbose: true
@@ -0,0 +1,7 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ build/
6
+ dist/
7
+ .venv/
@@ -0,0 +1,88 @@
1
+ # Changelog
2
+
3
+ All notable changes to `etch-record` (the CLI helper for the Etch signed
4
+ audit chain).
5
+
6
+ ## v0.2.0 — 2026-08-01
7
+
8
+ Wave 1 #1 of the Etch parallel chain roadmap ships in this release. Adds
9
+ five governance flags that attach a signed governance sub-record to any
10
+ event on the Etch parallel chain, cross-referencing the OSS event by
11
+ its ID.
12
+
13
+ ### Added
14
+
15
+ - `--policy-hash <hash>` — SHA-256 hash of the governance policy under
16
+ which the decision was evaluated. Must be `sha256:<64-hex>` shape.
17
+ - `--authority-file <path>` — path to a JSON file describing the
18
+ approving authority: `{identity, scope, expires_at}`.
19
+ - `--assumptions-file <path>` — path to a JSON array listing assumptions
20
+ accepted when reaching the decision. Each entry is
21
+ `{claim, source_ref}`.
22
+ - `--uncertainty '<confidence>:<basis>'` — inline uncertainty in
23
+ `confidence:basis` shape (e.g. `'0.87:evidence-hash-match-rate'`).
24
+ Confidence bounded to `[0.0, 1.0]`.
25
+ - `--uncertainty-file <path>` — path to a JSON object with the full
26
+ uncertainty shape: `{confidence, basis}`.
27
+ - `--invalidation-file <path>` — path to a JSON array listing
28
+ conditions under which the approval would have been invalidated.
29
+ Each entry is `{"if": ..., "then": ...}`.
30
+ - New module `etch_record.governance_client` with `record_governance`
31
+ function for programmatic use.
32
+ - New exit code `5` — governance sub-record failed. The base event
33
+ was recorded successfully; use the printed `event_id` to retry
34
+ the governance call against.
35
+
36
+ ### How the two-call sequence works
37
+
38
+ When any governance flag is set, the CLI makes two calls in sequence:
39
+
40
+ 1. `record_event` MCP call — creates the base event on the OSS chain
41
+ (existing behavior, unchanged).
42
+ 2. `record_governance` HTTP call — hashes the governance object,
43
+ POSTs to `/v1/etch-chain/governance-record` on your Etch base URL.
44
+ The Etch server signs it into the parallel chain and returns
45
+ the chain seq + governance hash.
46
+
47
+ Both calls succeed → the CLI prints two OK lines. First call succeeds
48
+ and second fails → the CLI exits with code 5 after printing the
49
+ first-call OK (so callers keep the event_id to retry against).
50
+
51
+ ### Requirements
52
+
53
+ The Etch server-side endpoint (`POST /v1/etch-chain/governance-record`,
54
+ shipped 2026-08-01 in etch commit `8676c87`) must be deployed for these
55
+ flags to work. If the endpoint is not yet available, calling one of
56
+ these flags will fail with a `transport failed` or `404` error surfaced
57
+ via `GovernanceError`.
58
+
59
+ ### Example
60
+
61
+ ```bash
62
+ etch-record "KYC decision on customer ABC" \
63
+ --tags kyc,fintech,decision \
64
+ --policy-hash sha256:9f8c... \
65
+ --authority-file /tmp/authority.json \
66
+ --uncertainty '0.87:hash-lookup-match-rate' \
67
+ --invalidation-file /tmp/invalidation_conditions.json
68
+ ```
69
+
70
+ Output (both calls succeed):
71
+
72
+ ```
73
+ OK session=etch-record-2026-08-01 event_id=abc-def-123
74
+ OK governance_seq=1 governance_hash=sha256:xyz...
75
+ ```
76
+
77
+ ### Unchanged
78
+
79
+ - All non-governance flags behave identically to v0.1.0.
80
+ - Calls without any governance flag do NOT invoke the second HTTP
81
+ endpoint; behavior is byte-identical to v0.1.0.
82
+ - Exit codes 0-4 unchanged (0 success, 1 config error, 2 MCP error,
83
+ 3 unexpected error, 4 rate limit).
84
+
85
+ ## v0.1.0 — 2026-07-27
86
+
87
+ Initial public version. Single-call `record_event` CLI for signing
88
+ any event into an Etch audit chain from the command line.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Saravanan Jaichandaran
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,147 @@
1
+ Metadata-Version: 2.4
2
+ Name: etch-record
3
+ Version: 0.1.0
4
+ Summary: CLI helper: sign any event into your Etch audit chain from the command line.
5
+ Author: Saravanan Jaichandaran
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: click>=8.1
10
+ Requires-Dist: httpx>=0.27
11
+ Description-Content-Type: text/markdown
12
+
13
+ # etch-record
14
+
15
+ Small CLI helper. Signs any event into your Etch audit chain from the command line.
16
+
17
+ Built for the marketing-agent workflow: every research call, draft generation, review decision, and outreach send emits a signed event. Also usable standalone for any local activity you want notarized.
18
+
19
+ **Latest: v0.2.0** — Wave 1 #1 governance flags (`--policy-hash`, `--authority-file`, `--assumptions-file`, `--uncertainty`, `--uncertainty-file`, `--invalidation-file`) attach a signed governance sub-record on the Etch parallel chain. See [CHANGELOG.md](CHANGELOG.md).
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install etch-record
25
+ ```
26
+
27
+ Or from a local checkout during development:
28
+
29
+ ```bash
30
+ cd ~/etch-marketing/etch-record
31
+ pip install -e .
32
+ ```
33
+
34
+ ## Configure
35
+
36
+ Set three env vars in your shell rc (`~/.zshrc` or `~/.bashrc`):
37
+
38
+ ```bash
39
+ export ETCH_PROJECT_ID="your_project_id"
40
+ export ETCH_APP_TOKEN="wm_your_app_token"
41
+ export ETCH_BASE_URL="https://etch.systems" # default; override for local dev
42
+ ```
43
+
44
+ Get `project_id` + `app_token` from your Etch signup provisioning page. `ETCH_BASE_URL` defaults to `https://etch.systems` if unset.
45
+
46
+ ## Use
47
+
48
+ ```bash
49
+ # Simple event
50
+ etch-record "posted X thread about Etch's audit chain"
51
+
52
+ # With tags + evidence
53
+ etch-record "researched contact via Gemini" \
54
+ --tags research,marketing \
55
+ --evidence-json '{"contact":"...","dossier_lines":247}'
56
+
57
+ # Load evidence from a file
58
+ etch-record "drafted 3 message variants" \
59
+ --tags draft,claude \
60
+ --evidence-file drafts_evidence.json
61
+
62
+ # Group events under a session (default = today's ISO date)
63
+ etch-record "approved draft v2" --session-id outreach-2026-07-26 --tags review,approved
64
+
65
+ # Print what would be sent without hitting the API
66
+ etch-record "dry run test" --dry-run
67
+ ```
68
+
69
+ ## Event shape
70
+
71
+ Every call becomes a signed `record_event` MCP tool call on your Etch chain:
72
+
73
+ - `event_type`: `"tool_call"` (only enum value that works for arbitrary marketing events)
74
+ - `session_id`: `--session-id` OR auto-generated as `etch-record-YYYY-MM-DD`
75
+ - `entities`: derived from `--tags`
76
+ - `description`: your quoted string (positional arg)
77
+ - `evidence`: from `--evidence-json` or `--evidence-file`
78
+ - `success`: `true` unless `--failed`
79
+
80
+ The Etch server appends to the SHA-256 Merkle chain, closes epochs at threshold (default 1024 events), hybrid-signs (Ed25519 + SLH-DSA-SHA2-128f), and optionally anchors to Sigstore Rekor + Bitcoin OpenTimestamps.
81
+
82
+ ## Verify
83
+
84
+ Every event is verifiable offline forever:
85
+
86
+ ```bash
87
+ etch-verify \
88
+ --base-url https://etch.systems \
89
+ --project-id your_project_id
90
+ ```
91
+
92
+ ## Governance metadata (Wave 1 #1, v0.2.0)
93
+
94
+ Attach a signed governance sub-record to any event. Any of the five flags below triggers a second call to `POST /v1/etch-chain/governance-record` on your Etch base URL, which hashes the governance object canonically and signs it into the Etch parallel chain.
95
+
96
+ ```bash
97
+ etch-record "KYC decision on customer ABC" \
98
+ --tags kyc,fintech,decision \
99
+ --policy-hash sha256:9f8c... \
100
+ --authority-file authority.json \
101
+ --uncertainty '0.87:hash-lookup-match-rate' \
102
+ --invalidation-file invalidation_conditions.json
103
+ ```
104
+
105
+ `authority.json`:
106
+
107
+ ```json
108
+ {
109
+ "identity": "compliance-officer@acme.example",
110
+ "scope": ["fintech-kyc-decisions"],
111
+ "expires_at": "2026-12-31T23:59:59Z"
112
+ }
113
+ ```
114
+
115
+ `invalidation_conditions.json`:
116
+
117
+ ```json
118
+ [
119
+ {"if": "SOP hash changes", "then": "re-approve required"}
120
+ ]
121
+ ```
122
+
123
+ Assumptions file uses the same shape:
124
+
125
+ ```json
126
+ [
127
+ {"claim": "SOP v3.2 is current", "source_ref": "doc_hash:xyz"}
128
+ ]
129
+ ```
130
+
131
+ Output when both calls succeed:
132
+
133
+ ```
134
+ OK session=etch-record-2026-08-01 event_id=abc-def-123
135
+ OK governance_seq=1 governance_hash=sha256:xyz...
136
+ ```
137
+
138
+ The base event was recorded via the OSS chain; the governance sub-record was signed into the Etch parallel chain and cross-references the event by ID. Both chains verify offline via `etch-verify` (OSS) and `etch-chain-verify` (Etch).
139
+
140
+ ## Exit codes
141
+
142
+ - `0` success (including two-call success when governance flags were set)
143
+ - `1` config error (missing env vars)
144
+ - `2` MCP `record_event` error
145
+ - `3` unexpected exception
146
+ - `4` rate limit (client-side sliding window)
147
+ - `5` governance sub-record failed — the base event was recorded successfully; use the printed `event_id` to retry the governance call
@@ -0,0 +1,135 @@
1
+ # etch-record
2
+
3
+ Small CLI helper. Signs any event into your Etch audit chain from the command line.
4
+
5
+ Built for the marketing-agent workflow: every research call, draft generation, review decision, and outreach send emits a signed event. Also usable standalone for any local activity you want notarized.
6
+
7
+ **Latest: v0.2.0** — Wave 1 #1 governance flags (`--policy-hash`, `--authority-file`, `--assumptions-file`, `--uncertainty`, `--uncertainty-file`, `--invalidation-file`) attach a signed governance sub-record on the Etch parallel chain. See [CHANGELOG.md](CHANGELOG.md).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install etch-record
13
+ ```
14
+
15
+ Or from a local checkout during development:
16
+
17
+ ```bash
18
+ cd ~/etch-marketing/etch-record
19
+ pip install -e .
20
+ ```
21
+
22
+ ## Configure
23
+
24
+ Set three env vars in your shell rc (`~/.zshrc` or `~/.bashrc`):
25
+
26
+ ```bash
27
+ export ETCH_PROJECT_ID="your_project_id"
28
+ export ETCH_APP_TOKEN="wm_your_app_token"
29
+ export ETCH_BASE_URL="https://etch.systems" # default; override for local dev
30
+ ```
31
+
32
+ Get `project_id` + `app_token` from your Etch signup provisioning page. `ETCH_BASE_URL` defaults to `https://etch.systems` if unset.
33
+
34
+ ## Use
35
+
36
+ ```bash
37
+ # Simple event
38
+ etch-record "posted X thread about Etch's audit chain"
39
+
40
+ # With tags + evidence
41
+ etch-record "researched contact via Gemini" \
42
+ --tags research,marketing \
43
+ --evidence-json '{"contact":"...","dossier_lines":247}'
44
+
45
+ # Load evidence from a file
46
+ etch-record "drafted 3 message variants" \
47
+ --tags draft,claude \
48
+ --evidence-file drafts_evidence.json
49
+
50
+ # Group events under a session (default = today's ISO date)
51
+ etch-record "approved draft v2" --session-id outreach-2026-07-26 --tags review,approved
52
+
53
+ # Print what would be sent without hitting the API
54
+ etch-record "dry run test" --dry-run
55
+ ```
56
+
57
+ ## Event shape
58
+
59
+ Every call becomes a signed `record_event` MCP tool call on your Etch chain:
60
+
61
+ - `event_type`: `"tool_call"` (only enum value that works for arbitrary marketing events)
62
+ - `session_id`: `--session-id` OR auto-generated as `etch-record-YYYY-MM-DD`
63
+ - `entities`: derived from `--tags`
64
+ - `description`: your quoted string (positional arg)
65
+ - `evidence`: from `--evidence-json` or `--evidence-file`
66
+ - `success`: `true` unless `--failed`
67
+
68
+ The Etch server appends to the SHA-256 Merkle chain, closes epochs at threshold (default 1024 events), hybrid-signs (Ed25519 + SLH-DSA-SHA2-128f), and optionally anchors to Sigstore Rekor + Bitcoin OpenTimestamps.
69
+
70
+ ## Verify
71
+
72
+ Every event is verifiable offline forever:
73
+
74
+ ```bash
75
+ etch-verify \
76
+ --base-url https://etch.systems \
77
+ --project-id your_project_id
78
+ ```
79
+
80
+ ## Governance metadata (Wave 1 #1, v0.2.0)
81
+
82
+ Attach a signed governance sub-record to any event. Any of the five flags below triggers a second call to `POST /v1/etch-chain/governance-record` on your Etch base URL, which hashes the governance object canonically and signs it into the Etch parallel chain.
83
+
84
+ ```bash
85
+ etch-record "KYC decision on customer ABC" \
86
+ --tags kyc,fintech,decision \
87
+ --policy-hash sha256:9f8c... \
88
+ --authority-file authority.json \
89
+ --uncertainty '0.87:hash-lookup-match-rate' \
90
+ --invalidation-file invalidation_conditions.json
91
+ ```
92
+
93
+ `authority.json`:
94
+
95
+ ```json
96
+ {
97
+ "identity": "compliance-officer@acme.example",
98
+ "scope": ["fintech-kyc-decisions"],
99
+ "expires_at": "2026-12-31T23:59:59Z"
100
+ }
101
+ ```
102
+
103
+ `invalidation_conditions.json`:
104
+
105
+ ```json
106
+ [
107
+ {"if": "SOP hash changes", "then": "re-approve required"}
108
+ ]
109
+ ```
110
+
111
+ Assumptions file uses the same shape:
112
+
113
+ ```json
114
+ [
115
+ {"claim": "SOP v3.2 is current", "source_ref": "doc_hash:xyz"}
116
+ ]
117
+ ```
118
+
119
+ Output when both calls succeed:
120
+
121
+ ```
122
+ OK session=etch-record-2026-08-01 event_id=abc-def-123
123
+ OK governance_seq=1 governance_hash=sha256:xyz...
124
+ ```
125
+
126
+ The base event was recorded via the OSS chain; the governance sub-record was signed into the Etch parallel chain and cross-references the event by ID. Both chains verify offline via `etch-verify` (OSS) and `etch-chain-verify` (Etch).
127
+
128
+ ## Exit codes
129
+
130
+ - `0` success (including two-call success when governance flags were set)
131
+ - `1` config error (missing env vars)
132
+ - `2` MCP `record_event` error
133
+ - `3` unexpected exception
134
+ - `4` rate limit (client-side sliding window)
135
+ - `5` governance sub-record failed — the base event was recorded successfully; use the printed `event_id` to retry the governance call
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "etch-record"
7
+ version = "0.1.0"
8
+ description = "CLI helper: sign any event into your Etch audit chain from the command line."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "Saravanan Jaichandaran" },
14
+ ]
15
+ dependencies = [
16
+ "httpx>=0.27",
17
+ "click>=8.1",
18
+ ]
19
+
20
+ [project.scripts]
21
+ etch-record = "etch_record.cli:main"
22
+
23
+ [tool.hatch.build.targets.wheel]
24
+ packages = ["src/etch_record"]
@@ -0,0 +1,3 @@
1
+ """etch-record: CLI helper for signing arbitrary events into your Etch chain."""
2
+
3
+ __version__ = "0.1.0"