trooth 0.4.3 → 0.4.4
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.
- package/README.md +57 -36
- package/bin/trooth.mjs +60 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ Node 18 or newer, because the binary uses the built-in `fetch`. Zero dependencie
|
|
|
30
30
|
|
|
31
31
|
| Command | What it does |
|
|
32
32
|
|---|---|
|
|
33
|
-
| `trooth check <domain>` | Reads a company's record from the live Network and prints whether it is listed, when it was witnessed and first published, the live-probe and attestation counts, the badge id, the id of the key that signed the record, and the
|
|
33
|
+
| `trooth check <domain>` | Reads a company's record from the live Network and prints whether it is listed, when it was last witnessed and first published, the live-probe and self-attestation counts, the badge id, the id of the key that signed the record, and the three newest events in its ledger, newest first. `--json` adds the signature itself and every event the feed returns. |
|
|
34
34
|
| `trooth lint [path]` | Reads the infrastructure the given directory declares and prints those declarations plus a canonical SHA-256 digest of them. Local and offline. `path` defaults to `.`. |
|
|
35
35
|
| `trooth --help` | Help. Also `-h` and `help`. |
|
|
36
36
|
| `trooth --version` | Version. Also `-v` and `version`. |
|
|
@@ -61,27 +61,30 @@ trooth check trooth.co
|
|
|
61
61
|
Example output. The values are illustrative; the shape is what the binary prints.
|
|
62
62
|
|
|
63
63
|
```
|
|
64
|
-
Trooth Network //
|
|
64
|
+
Trooth Network // public · signed · read-only //
|
|
65
65
|
Trooth, LLC trooth.co
|
|
66
|
-
|
|
66
|
+
Listing state: listed and witnessed last witnessed 2026-08-30 first published 2026-08-01
|
|
67
67
|
|
|
68
|
-
Live probes
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
Live probes: 65 read; 64 as expected
|
|
69
|
+
Self-attestations: 35 asked; 27 attested
|
|
70
|
+
Live probes are readings Trooth took itself, from the company's public surface.
|
|
71
|
+
Self-attestations are what the company attested about itself; Trooth records them
|
|
72
|
+
and did not witness them. The two are reported apart and never added into one number.
|
|
71
73
|
Badge rw_... Key ed25519-2026-01
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
• 2026-08-
|
|
75
|
-
• 2026-08-
|
|
76
|
-
• 2026-08-
|
|
75
|
+
Latest ledger events, newest first
|
|
76
|
+
• 2026-08-30 record published to the Trooth Network
|
|
77
|
+
• 2026-08-30 reading completed
|
|
78
|
+
• 2026-08-01 record published to the Trooth Network
|
|
79
|
+
--json carries the whole ledger, with the feed's own wording for each event.
|
|
77
80
|
|
|
78
|
-
A
|
|
81
|
+
A dated, point-in-time record. Trooth issues no verdict and no single number.
|
|
79
82
|
Full record: https://trooth.co/network/trooth.co · Signing keys: https://api.trooth.co/public/keys
|
|
80
83
|
```
|
|
81
84
|
|
|
82
|
-
The line that begins `
|
|
85
|
+
The line that begins `Listing state:` gives the listing state. For a listed company it reads `listed and witnessed`, then the date of the most recent published reading (`last witnessed`) and the date the record was first published. The two counts use the same form as the record page on trooth.co: live probes (how many were read at the last reading, and how many of those returned the expected result) and self-attestations (how many the company was asked for, and how many it attested). Live probes are readings Trooth took itself; self-attestations are the company's statements about itself, which Trooth records and does not witness.
|
|
83
86
|
|
|
84
|
-
The events section prints the
|
|
87
|
+
The events section prints the three newest entries in the record's ledger, newest first, by timestamp; entries with the same timestamp keep the feed's order, the later entry first. Known event types get a plain label: `scan_completed` prints as "reading completed", `standing_published` as "record published to the Trooth Network" and `rewitnessed` as "live probes re-read". A type the CLI does not know prints with its underscores turned into spaces. The human view leaves out each event's detail text. `--json` carries every event the feed returns, with its type and detail exactly as the feed has them.
|
|
85
88
|
|
|
86
89
|
For scripting:
|
|
87
90
|
|
|
@@ -100,9 +103,9 @@ trooth check trooth.co --json
|
|
|
100
103
|
"probes": { "passed": 64, "total": 65 },
|
|
101
104
|
"attested": { "passed": 27, "total": 35 },
|
|
102
105
|
"events": [
|
|
103
|
-
{ "type": "scan_completed", "at": "2026-08-01T00:00:00Z", "detail": "64
|
|
106
|
+
{ "type": "scan_completed", "at": "2026-08-01T00:00:00Z", "detail": "65 probes read · 64 returned the expected result · 27 declarations recorded · reading signed" },
|
|
104
107
|
{ "type": "standing_published", "at": "2026-08-01T00:00:00Z", "detail": "point-in-time · published to the Trooth Network" },
|
|
105
|
-
{ "type": "scan_completed", "at": "2026-08-30T00:00:00Z", "detail": "64
|
|
108
|
+
{ "type": "scan_completed", "at": "2026-08-30T00:00:00Z", "detail": "65 probes read · 64 returned the expected result · 27 declarations recorded · reading signed" },
|
|
106
109
|
{ "type": "standing_published", "at": "2026-08-30T00:00:00Z", "detail": "point-in-time · published to the Trooth Network" }
|
|
107
110
|
],
|
|
108
111
|
"receipt_signature": "...",
|
|
@@ -112,9 +115,9 @@ trooth check trooth.co --json
|
|
|
112
115
|
}
|
|
113
116
|
```
|
|
114
117
|
|
|
115
|
-
In `probes`, `total` is how many live probes were read at the last reading and `passed` (the API's field name) is how many returned the expected result. In `attested`, `total` is how many declarations were asked for and `passed` is how many the company attested. `witnessed_at` is the date of the most recent published reading. `events` is the whole ledger, oldest first.
|
|
118
|
+
In `probes`, `total` is how many live probes were read at the last reading and `passed` (the API's field name) is how many returned the expected result. In `attested`, `total` is how many declarations were asked for and `passed` is how many the company attested. `witnessed_at` is the date of the most recent published reading. `events` is the whole ledger, oldest first, in the feed's order. Each event's `detail` is written when the event is stored and is not rewritten afterward, so older and newer events of the same type can be worded differently.
|
|
116
119
|
|
|
117
|
-
`category` and `description` are added when the record carries them. Those are the only fields `check --json` emits: anything else the feed happens to carry is dropped on the way out, so a script written against this shape keeps working. A field whose name
|
|
120
|
+
`category` and `description` are added when the record carries them. Those are the only fields `check --json` emits: anything else the feed happens to carry is dropped on the way out, so a script written against this shape keeps working. A field whose name contains `score`, `tier`, `grade`, `rank`, `rating`, `level` or `percent`, or is `rate`, is dropped no matter what the feed sends.
|
|
118
121
|
|
|
119
122
|
A company with no record exits 1 and emits `{"domain": "...", "listed": false, "record_url": "..."}`. That is not a judgment. It means the Network's public feed carries no record for that domain. A company gets a record at [trooth.co/get-started](https://trooth.co/get-started), free.
|
|
120
123
|
|
|
@@ -122,50 +125,58 @@ A company with no record exits 1 and emits `{"domain": "...", "listed": false, "
|
|
|
122
125
|
|
|
123
126
|
## `trooth lint`
|
|
124
127
|
|
|
125
|
-
`lint` reads what your infrastructure **declares** and reports it. It does not judge it. There is no verdict, no
|
|
128
|
+
`lint` reads what your infrastructure **declares** and reports it. It does not judge it. There is no verdict, no threshold, no severity and no rating, and nothing is checked against a named standard or regulation. Declaring public ingress is not a failing: a load balancer is supposed to be public. What the facts mean is your decision.
|
|
126
129
|
|
|
127
130
|
```bash
|
|
128
131
|
trooth lint ./infra
|
|
129
132
|
```
|
|
130
133
|
|
|
131
|
-
|
|
134
|
+
Output for the small fixture in this repository (`trooth lint tests/fixtures/infra`, trooth 0.4.3):
|
|
132
135
|
|
|
133
136
|
```
|
|
134
137
|
trooth lint // local · offline · declarations only //
|
|
135
|
-
infra
|
|
136
|
-
terraform
|
|
138
|
+
tests/fixtures/infra 2 declaration file(s) read
|
|
139
|
+
terraform 1 · kubernetes 1
|
|
137
140
|
|
|
138
141
|
Declared
|
|
139
|
-
Regions and zones
|
|
140
|
-
Storage declarations
|
|
141
|
-
of those declaring encryption
|
|
142
|
-
Logging declarations
|
|
143
|
-
Identity declarations
|
|
142
|
+
Regions and zones us-east-1
|
|
143
|
+
Storage declarations 1
|
|
144
|
+
of those declaring encryption 1
|
|
145
|
+
Logging declarations 0
|
|
146
|
+
Identity declarations 0
|
|
144
147
|
Open to any address (0.0.0.0/0, ::/0) 1
|
|
145
|
-
Marked public
|
|
148
|
+
Marked public 0
|
|
146
149
|
Inline credential literals 0
|
|
147
150
|
|
|
148
151
|
Most declared resource types
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
1 aws_s3_bucket
|
|
153
|
+
1 aws_s3_bucket_server_side_encryption_configuration
|
|
154
|
+
1 aws_security_group_rule
|
|
152
155
|
|
|
153
|
-
Digest sha256
|
|
156
|
+
Digest sha256:a40bf6cf27dfd0b9079ac48a326fb2d1423b076c96b405451dde1ed2d7848944
|
|
154
157
|
A SHA-256 over the facts above, in canonical form, with the timestamp excluded.
|
|
155
|
-
The same tree
|
|
156
|
-
that a state was observed without publishing the tree
|
|
158
|
+
The same tree read by the same trooth version produces the same digest, so you can
|
|
159
|
+
record it as evidence that a state was observed without publishing the tree.
|
|
160
|
+
|
|
161
|
+
How this was read
|
|
162
|
+
A pattern reader, not a Terraform evaluator: variables and modules are not resolved.
|
|
163
|
+
.tf by pattern, one resource block at a time. .tf.json and plan JSON parsed, one
|
|
164
|
+
resource at a time. Kubernetes YAML by pattern, one document at a time, by kind.
|
|
165
|
+
Dockerfiles for regions, open addresses, public markers and credential literals only.
|
|
157
166
|
|
|
158
|
-
Counts of what the files declare. Not a
|
|
167
|
+
Counts of what the files declare. Not a judgment: a public load balancer is
|
|
159
168
|
supposed to be public. Trooth issues no verdict here and checks nothing against
|
|
160
169
|
any standard. Nothing left this machine: lint opens files and opens no sockets.
|
|
161
170
|
Publish what you choose on your record at https://trooth.co/dashboard.
|
|
162
171
|
```
|
|
163
172
|
|
|
164
|
-
|
|
173
|
+
The bucket's encryption configuration is a setting on the bucket, not a second store, so the fixture has one storage declaration, and that one declares encryption.
|
|
174
|
+
|
|
175
|
+
It reads `.tf`, `.tf.json`, Kubernetes YAML (anything carrying both `apiVersion` and `kind`), `terraform show -json` plan files and Dockerfiles. It is a pattern reader, not a Terraform evaluator: variables, modules and `for_each` are never resolved, so a count can differ from what Terraform itself would plan. `.tf` files are read by pattern, one resource block at a time. `.tf.json` and plan files are parsed as JSON, one resource at a time. Kubernetes YAML is read by pattern, one document at a time, and classified by its `kind`. Dockerfiles are read for regions, open addresses, public markers and credential literals only. Storage, logging and identity are counted by a resource's type or a document's kind, never by the words around it. The output states this under "How this was read".
|
|
165
176
|
|
|
166
177
|
Nothing leaves the machine. No file name, no line, no code and no value is printed or transmitted, only the path you gave it, counts, resource type names and region strings. The credential count is a count: the literal it found is never shown.
|
|
167
178
|
|
|
168
|
-
The digest is a SHA-256 over the canonical
|
|
179
|
+
The digest is a SHA-256 over the `facts` object in canonical form. The timestamp, the path and the CLI version are outside it, so the same tree read by the same trooth version produces the same digest. A release that changes how something is counted changes the digest of the same tree: the fixture above digests differently under 0.4.2 and 0.4.3. Record the digest in CI, or on your own record, as evidence that a given state was observed, without publishing the tree it came from.
|
|
169
180
|
|
|
170
181
|
```bash
|
|
171
182
|
# Keep the fact document as a build artifact.
|
|
@@ -203,6 +214,16 @@ https://api.trooth.co/public/mcp
|
|
|
203
214
|
|
|
204
215
|
Four read-only tools, public data, no key. The pattern is written up at [trooth.co/docs/agents](https://trooth.co/docs/agents).
|
|
205
216
|
|
|
217
|
+
## Changed in 0.4.4
|
|
218
|
+
|
|
219
|
+
- `check` asks the Trooth Network for the one record it needs (`/directory/api/vendors/<domain>`) instead of downloading the whole directory list and searching it. When the API does not serve that path yet, it reads the list as 0.4.3 did. The printed output, `check --json` and the exit codes are unchanged.
|
|
220
|
+
|
|
221
|
+
## Changed in 0.4.3
|
|
222
|
+
|
|
223
|
+
- `check` labels the listing state `Listing state:` and prints the two counts in the record page's form (`65 read; 64 as expected`, `35 asked; 27 attested`). It prints the three newest ledger events, newest first, with plain labels and without the detail text. 0.4.2 printed the ledger's three oldest events under a heading that called them recent. `check --json` is unchanged.
|
|
224
|
+
- `lint` no longer counts a storage setting (a bucket's encryption configuration, a bucket policy, a volume attachment) as a second store, and a setting that declares encryption credits the store it refers to. It no longer counts `encrypted = false`, or an empty encryption setting in a plan, as declaring encryption. It classifies by resource type or Kubernetes `kind` only, parses `.tf.json` and plan JSON and counts their resource types, and reads Kubernetes YAML one document at a time. The same tree can therefore produce different counts, and a different digest, than under 0.4.2. The `lint --json` field names are unchanged.
|
|
225
|
+
- The human `lint` output states how each source was read.
|
|
226
|
+
|
|
206
227
|
## Removed in 0.4.0
|
|
207
228
|
|
|
208
229
|
Breaking, and deliberately so.
|
package/bin/trooth.mjs
CHANGED
|
@@ -40,7 +40,7 @@ import { join, relative, extname, basename } from 'node:path';
|
|
|
40
40
|
|
|
41
41
|
const API = process.env.TROOTH_API || 'https://api.trooth.co';
|
|
42
42
|
const require = createRequire(import.meta.url);
|
|
43
|
-
let VERSION = '0.4.
|
|
43
|
+
let VERSION = '0.4.4';
|
|
44
44
|
try { VERSION = require('../package.json').version; } catch {}
|
|
45
45
|
|
|
46
46
|
const EXIT = { OK: 0, FINDING: 1, USAGE: 2, UPSTREAM: 3 };
|
|
@@ -160,20 +160,27 @@ Trooth signs what it witnessed. It never signs on a company's behalf.${X}
|
|
|
160
160
|
|
|
161
161
|
/* -------------------------------------------------------------- fetch ---- */
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
/** One request to the API. Exits 3 when the API cannot be reached; any HTTP
|
|
164
|
+
* status comes back to the caller. */
|
|
165
|
+
async function requestTrooth(path, init, what) {
|
|
165
166
|
try {
|
|
166
|
-
|
|
167
|
+
return await fetch(`${API}${path}`, {
|
|
167
168
|
...init,
|
|
168
169
|
headers: { accept: 'application/json', 'user-agent': `trooth-cli/${VERSION}`, ...(init && init.headers) },
|
|
169
170
|
});
|
|
170
171
|
} catch (e) {
|
|
171
172
|
fail(EXIT.UPSTREAM, `could not reach ${what} at ${API}: ${e && e.message ? e.message : e}`);
|
|
172
173
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function failHttp(res, what) {
|
|
177
|
+
const body = await res.text().catch(() => '');
|
|
178
|
+
fail(EXIT.UPSTREAM, `${what} returned HTTP ${res.status}.${body ? ' ' + body.slice(0, 300).replace(/\s+/g, ' ') : ''}`, { http_status: res.status });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function callTrooth(path, init, what) {
|
|
182
|
+
const res = await requestTrooth(path, init, what);
|
|
183
|
+
if (!res.ok) await failHttp(res, what);
|
|
177
184
|
try { return await res.json(); }
|
|
178
185
|
catch { fail(EXIT.UPSTREAM, `${what} returned a response that is not JSON.`); }
|
|
179
186
|
}
|
|
@@ -258,15 +265,57 @@ function projectRecord(v, domain) {
|
|
|
258
265
|
return scrub(rec);
|
|
259
266
|
}
|
|
260
267
|
|
|
268
|
+
/** The whole list, searched here. This is how every release up to 0.4.3 read a
|
|
269
|
+
* company, and it is now only the fallback in readVendor below. */
|
|
270
|
+
async function readVendorFromList(domain, what) {
|
|
271
|
+
const data = await callTrooth('/directory/api/vendors', { method: 'GET' }, what);
|
|
272
|
+
const vendors = Array.isArray(data && data.vendors) ? data.vendors : [];
|
|
273
|
+
return vendors.find((v) => v && normalizeDomain(v.domain) === domain) || null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** One company's record from the directory feed, or null when the feed carries
|
|
277
|
+
* no record for that domain. It asks for that one record
|
|
278
|
+
* (/directory/api/vendors/<domain>), which returns the same object the list
|
|
279
|
+
* carries for that domain, and does not download every company to find it. */
|
|
280
|
+
async function readVendor(domain) {
|
|
281
|
+
const what = 'the Trooth Network';
|
|
282
|
+
const res = await requestTrooth(`/directory/api/vendors/${encodeURIComponent(domain)}`, { method: 'GET' }, what);
|
|
283
|
+
|
|
284
|
+
if (res.status === 404) {
|
|
285
|
+
const text = await res.text().catch(() => '');
|
|
286
|
+
let body = null;
|
|
287
|
+
try { body = JSON.parse(text); } catch {}
|
|
288
|
+
// The route answered and has no record for this domain. It says so with a
|
|
289
|
+
// JSON body carrying `listed: false`.
|
|
290
|
+
if (body && typeof body === 'object' && body.listed === false) return null;
|
|
291
|
+
// FALLBACK TO THE LIST. Any other 404 comes from a deploy of the directory
|
|
292
|
+
// worker that predates the single-record route: it answers an unknown path
|
|
293
|
+
// with a plain-text "Not found". Read the list and search it here, as 0.4.3
|
|
294
|
+
// does, so this release keeps working against a worker that has not been
|
|
295
|
+
// redeployed yet, and against a TROOTH_API that points at an older one.
|
|
296
|
+
// This fallback can go in the first release after api.trooth.co serves the
|
|
297
|
+
// route, that is, once a request for a domain with no record there returns
|
|
298
|
+
// a JSON 404 with `listed: false`.
|
|
299
|
+
return readVendorFromList(domain, what);
|
|
300
|
+
}
|
|
301
|
+
if (!res.ok) await failHttp(res, what);
|
|
302
|
+
|
|
303
|
+
let v;
|
|
304
|
+
try { v = await res.json(); }
|
|
305
|
+
catch { fail(EXIT.UPSTREAM, `${what} returned a response that is not JSON.`); }
|
|
306
|
+
if (!v || typeof v !== 'object' || Array.isArray(v) || normalizeDomain(v.domain) !== domain) {
|
|
307
|
+
fail(EXIT.UPSTREAM, `${what} returned a response that is not the record for ${domain}.`);
|
|
308
|
+
}
|
|
309
|
+
return v;
|
|
310
|
+
}
|
|
311
|
+
|
|
261
312
|
async function check() {
|
|
262
313
|
const { positional } = parseArgs('check');
|
|
263
314
|
if (positional.length > 1) fail(EXIT.USAGE, `check takes one <domain>, got: ${positional.join(' ')}`);
|
|
264
315
|
const domain = normalizeDomain(positional[0]);
|
|
265
316
|
if (!domain) fail(EXIT.USAGE, 'missing <domain>. Try: trooth check stripe.com');
|
|
266
317
|
|
|
267
|
-
const
|
|
268
|
-
const vendors = Array.isArray(data && data.vendors) ? data.vendors : [];
|
|
269
|
-
const vendor = vendors.find((v) => v && normalizeDomain(v.domain) === domain) || null;
|
|
318
|
+
const vendor = await readVendor(domain);
|
|
270
319
|
|
|
271
320
|
if (!vendor) {
|
|
272
321
|
if (asJson) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trooth",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "The Trooth Network from your terminal. Read a company's public, signed record with no key and no account: listing state, dates, live-probe and self-attestation counts, signing key id. Also reads what your own repo declares, offline. No verdict.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|