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.
- etch_record-0.1.0/.github/workflows/release.yml +125 -0
- etch_record-0.1.0/.gitignore +7 -0
- etch_record-0.1.0/CHANGELOG.md +88 -0
- etch_record-0.1.0/LICENSE +21 -0
- etch_record-0.1.0/PKG-INFO +147 -0
- etch_record-0.1.0/README.md +135 -0
- etch_record-0.1.0/pyproject.toml +24 -0
- etch_record-0.1.0/src/etch_record/__init__.py +3 -0
- etch_record-0.1.0/src/etch_record/cli.py +619 -0
- etch_record-0.1.0/src/etch_record/config.py +47 -0
- etch_record-0.1.0/src/etch_record/governance_client.py +146 -0
- etch_record-0.1.0/src/etch_record/mcp_client.py +178 -0
- etch_record-0.1.0/src/etch_record/rate_limit.py +111 -0
- etch_record-0.1.0/tests/test_cli.py +142 -0
- etch_record-0.1.0/tests/test_cli_hardening.py +399 -0
- etch_record-0.1.0/tests/test_governance_flags.py +373 -0
- etch_record-0.1.0/tests/test_mcp_client.py +197 -0
- etch_record-0.1.0/tests/test_rate_limit.py +187 -0
|
@@ -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,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"]
|