eaveswatch 0.8.0__py3-none-any.whl

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,446 @@
1
+ Metadata-Version: 2.4
2
+ Name: eaveswatch
3
+ Version: 0.8.0
4
+ Summary: AI log review that sets itself up: eaves on every host turn logs into templates and alerts, an agent (designed for hobkin, or any harness that meets its contract) reviews what changed against the code. One stdlib-only Python file; S3 or a folder in between.
5
+ Keywords: security,logs,review,agent,llm,ai,alerts,s3,observability,hobkin
6
+ Author: Frank Lynam
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ License-Expression: MIT
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Security
20
+ Classifier: Topic :: System :: Logging
21
+ License-File: LICENSE
22
+ Requires-Dist: hobkin>=0.5.7 ; extra == "hobkin"
23
+ Project-URL: Homepage, https://github.com/frank-lynam/eaveswatch
24
+ Project-URL: Issues, https://github.com/frank-lynam/eaveswatch/issues
25
+ Provides-Extra: hobkin
26
+
27
+ # eaveswatch
28
+
29
+ Logs, deploys, config and connections, joined to the code that made them, for an
30
+ AI agent to set up and review. It's designed for
31
+ [hobkin](https://github.com/frank-lynam/hobkin), and works with any agent harness
32
+ that meets its [reviewer contract](#the-reviewer-contract).
33
+
34
+ Scanners look at one thing at a time: a diff, a dependency, a log line. The
35
+ interesting problems are joins: *since Tuesday's config change that error is
36
+ 40× an hour*; *the box listens on a port nobody mentioned*. eaveswatch gathers
37
+ what's needed to see those and hands it to a reviewer agent. The agent works
38
+ out what's worth collecting and how to read it. It gets a bar for what counts as
39
+ a finding, and a ledger that traces every conclusion back to its evidence.
40
+
41
+ - **No hand-written log config.** The reviewer agent runs `discover`:
42
+ - which services run here, and what they run
43
+ - which files each one has open for writing
44
+ - which ports they listen on and talk to
45
+ - which config files they point at
46
+ - who writes to the journal
47
+
48
+ It asks you what you care about, then writes an **interpreter spec**: JSON
49
+ that names the sources and says how to read them. That means multi-line
50
+ entries, timestamps, levels, placeholders for what varies, noise to drop,
51
+ where versions come from, and which config files to watch. `interpret test`
52
+ replays the real recent lines through it; `interpret apply` makes it the
53
+ active, numbered version.
54
+ - **No model per line, and no code from the model.** The eave runs the active
55
+ spec's patterns in plain code. Patterns that backtrack forever are caught in
56
+ testing.
57
+ - **Secrets never leave**, whatever the spec says. A built-in redaction floor
58
+ always runs first. It catches private keys, AWS key ids, JWTs, bearer
59
+ tokens, `password=`/`token=` values, `PS_TOKEN`/`JSESSIONID`, `user:pass@`
60
+ in URLs and long base64 blobs. A spec can add patterns but never remove
61
+ them. Credential files and folders (`~/.ssh`, `~/.aws`, key files,
62
+ `~/.hobkins`...) are refused as sources.
63
+ - **Same module everywhere**, one standard-library Python file (3.9+), three roles:
64
+
65
+ | Role | Where | What |
66
+ |---|---|---|
67
+ | `finial` | one box, all of it | eave, ridge and reviewer together; files to S3 (the archive) and straight to the reviewer |
68
+ | `eave` | every host of a fleet | its interpreter's sources, inventory, flows → the store; answers asks |
69
+ | `ridge` | a fleet's hub | the store → a data folder for the reviewer; asks → the store |
70
+
71
+ (A finial is the ornament at the very top of a single roof.)
72
+
73
+ ## A finial: one box, watched from the inside
74
+
75
+ As **the same user the watched services run as** (with those services as user
76
+ systemd units), run:
77
+
78
+ ```sh
79
+ pipx install 'eaveswatch[hobkin]' # with hobkin, the reviewer it's designed for
80
+ eaveswatch https://openrouter.ai/api/v1 anthropic/claude-sonnet-4.5 sk-or-... # the reviewer's model API
81
+ ```
82
+
83
+ Running as that user lets discovery see the services' own open files,
84
+ sockets and environment, with no root and no extra capabilities.
85
+
86
+ The one command asks you three things:
87
+ - **Which S3 bucket** to use, showing the ones it can see.
88
+ - **Where alerts go:** an email address. It sends through the box's
89
+ `sendmail`, or through an SMTP relay if the box has none, and offers a test
90
+ message. Leave it blank and alerts are kept in the data folder for now.
91
+ - **Which folder a web server here already serves**, for the findings page.
92
+ Anyone who can reach that site sees the findings. Leave it blank and the
93
+ page is in `~/eaveswatch/ridge/report/` either way.
94
+
95
+ Everything else is decided for you:
96
+ - **Region:** the bucket's own.
97
+ - **Credentials:** the instance profile.
98
+ - **Host:** the hostname.
99
+ - **Config:** `~/.config/eaveswatch/finial.json`.
100
+ - **State:** `~/eaveswatch`. It isn't under a dot-folder on purpose: hobkin's
101
+ safe mode refuses write folders inside `~/.something`, because those are
102
+ ways out of the sandbox.
103
+
104
+ It runs `check`, installs and starts `eaveswatch` as a user service of whoever
105
+ ran it, turns on lingering (so it runs with nobody logged in), installs the
106
+ reviewer's instructions as a hobkin chore, and hands over to hobkin. You end up
107
+ in a chat with the reviewer. Run it again later and it reuses the same setup.
108
+ `--reviewer none` sets everything up without starting an agent, for a harness
109
+ of your own.
110
+
111
+ Options:
112
+ - `--bucket NAME`, `--alert-email ADDR` (with `--smtp host:port` for a relay),
113
+ `--publish-dir DIR`: answers, so nothing is asked
114
+ - `--store s3://bucket/prefix`, or a folder to try it without S3
115
+ - `--env`, `--host`
116
+ - `--no-service`, `--no-chat`
117
+ - `--force` (replace an existing setup)
118
+
119
+ The reviewer then:
120
+ 1. runs `discover`
121
+ 2. tells you in a few lines what it found ("a PeopleSoft app server domain
122
+ HRDEV, a PIA on 8000...")
123
+ 3. proposes what to collect, watch and archive, and **what should alert you at
124
+ once**, with a recommendation for each
125
+ 4. asks what you care about
126
+ 5. writes, tests and applies the interpreter, and tells you what's now collected
127
+
128
+ Discovery runs again every hour. What changed (new units, programs,
129
+ listeners, logs written but not collected) comes in its digest. It fixes and
130
+ narrows the interpreter on its own, and asks you before adding a source.
131
+
132
+ ## A system of several nodes (an autoscaling group)
133
+
134
+ Every instance runs the same thing. There's no hub to deploy, and any node can
135
+ drop out at any moment without the system losing anything, because what the
136
+ system knows lives in the store:
137
+
138
+ ```
139
+ PREFIX/systems/system=ps-hr-app/
140
+ config.json the shared config and the reviewer's model API (never its key)
141
+ interpreter/vN.json every version: a node creates vN only if no other node has (the loser is told)
142
+ charter.md the reviewer's REVIEW.md
143
+ ledger/ findings events, one object each, never overwritten: every node folds the same events
144
+ alerts/ sent/ fired alerts; notes already sent (once for the system, however many nodes saw it)
145
+ nodes/HOST.json heartbeats lease/ridge.json who does the ridge duty
146
+ jobs/ID.json review jobs state/map.json the merged map, for every node's page
147
+ ```
148
+
149
+ - **The ridge is a duty, not a box.** One node holds a lease (S3's
150
+ conditional writes, renewed every tick, taken over when it runs out). The
151
+ ridge duty is plain code with no model:
152
+ - merge every node's data into the map
153
+ - spot what's new across the fleet: a template seen nowhere before, a
154
+ deploy, a config change, a new host, new connections
155
+ - notice nodes that went silent without saying they were leaving (a warning,
156
+ sent once, and a job)
157
+ - turn all of that into **review jobs**
158
+ - **Reviews go where the evidence is.** A job names the host its evidence came
159
+ from, and that host's reviewer claims it first (it has the logs, the
160
+ processes and the code right there). If it doesn't within
161
+ `job_wait_seconds`, because it's dead or busy, any node takes it. Claims are
162
+ renewed while the reviewer works and run out if its node dies, so the job goes
163
+ back to the others. Alerts come first: a node gives back a routine review to
164
+ take one.
165
+ - **Each node's reviewer is woken only by the job it holds** (`eaveswatch
166
+ watch`). Idle nodes cost nothing. The reviewer finishes with `eaveswatch job
167
+ done ID --note "..."`, or gives a job back with `job release ID`.
168
+ - **Everything spreads within a minute:** findings recorded on any node,
169
+ dismissals, the interpreter applied anywhere, the charter. **Every node renders
170
+ the findings page** from the shared state, so a load balancer can send you to
171
+ any of them.
172
+ - **An alert is emailed once for the system:** the first node to create its
173
+ `sent/` record sends it. A failed unit is per host; an error storm seen by ten
174
+ nodes is one email.
175
+ - **Asks** between nodes go through the store, and the answer comes back to the
176
+ reviewer that asked.
177
+ - **A clean stop** (scale-in, shutdown) marks the node as left, gives the lease
178
+ and its job back, and isn't reported as gone.
179
+
180
+ **Every node boots the same way**, with no questions, from the same line in
181
+ your launch template ([examples/asg/user-data.sh](examples/asg/user-data.sh)).
182
+ Everything comes in by flag, so feed them from whatever variables or secret
183
+ store you already use:
184
+
185
+ ```sh
186
+ eaveswatch boot --store s3://my-eaves-bucket/eaveswatch --system ps-hr-app \
187
+ --model-url "$YOUR_MODEL_URL" --model "$YOUR_MODEL" --model-key "$YOUR_MODEL_KEY" \
188
+ --alert-email ops@example.gov --publish-dir /var/www/html/eaveswatch
189
+ ```
190
+
191
+ - **The system is there already:** the node joins it. It writes its own config
192
+ (its own host), and on its first tick pulls the interpreter, alert rules,
193
+ charter and findings.
194
+ - **It isn't:** the node creates it from the flags and shares its config (the
195
+ model API, alerts, publish folder; never the key). Its reviewer then runs
196
+ the setup conversation once, by `notify` as well as chat since nobody's
197
+ attached at boot. You answer by chatting with it (`hobkin chat eaveswatch`).
198
+ - **Either way** it runs `check` (sources that aren't there yet don't stop
199
+ it: the services may still be starting), installs and starts the user
200
+ service, and starts the reviewer with the key you gave and no chat. Running
201
+ it again is safe.
202
+ - `--collector` makes a collector-only node, which shares data but never leads
203
+ or reviews. `--reviewer none` brings your own agent harness. The hobkin
204
+ adapter keeps the key in hobkin's own key file, readable only by that user.
205
+
206
+ AWS access is the instance's own: eaveswatch uses whatever credentials the box
207
+ has (instance profile, environment, `~/.aws`) and only needs the bucket name.
208
+
209
+ `cluster` in the config tunes the timings: `lease_seconds` (180),
210
+ `claim_seconds` (600), `job_wait_seconds` (120), `node_gone_seconds` (300),
211
+ `job_keep_days` (7).
212
+
213
+ ## Running as a service
214
+
215
+ eaveswatch runs as a **systemd user service of whoever launches it**, like
216
+ hobkin. That's normally the user the watched services run as. `setup` and
217
+ `boot` install it; you can manage it yourself too:
218
+
219
+ ```sh
220
+ eaveswatch --config ~/.config/eaveswatch/finial.json service add # install, start now and at boot
221
+ eaveswatch --config ~/.config/eaveswatch/finial.json service status # running? since when; its last lines
222
+ eaveswatch --config ~/.config/eaveswatch/finial.json service remove # stop it and take it off boot
223
+ ```
224
+
225
+ - **The unit:** `~/.config/systemd/user/eaveswatch.service`, running this same
226
+ eaveswatch (its full path) with this config. Another config gets its own unit
227
+ (`eaveswatch-<hash>`), so two setups on one box never clash.
228
+ - **Lingering** is turned on, so it runs with nobody logged in. If the system
229
+ won't allow that without a password, it says so and gives the
230
+ `loginctl enable-linger USER` to run.
231
+ - **Failures:** a crash is restarted after 30 seconds. systemd gives up after 5
232
+ failures in 10 minutes, so a broken config can't spin. A clean stop
233
+ (`systemctl --user stop eaveswatch`, scale-in) stays stopped, and the node
234
+ hands its work back first.
235
+ - **Logs:** its own output goes to the journal
236
+ (`journalctl --user -u eaveswatch`).
237
+ - **Overwriting:** a unit that already runs a different eaveswatch or config is
238
+ left alone unless you pass `--force`. `boot` replaces it, since it owns it.
239
+ - The reviewer is its harness's service. For hobkin that's `hobkin@eaveswatch`,
240
+ which `hobkin list` shows.
241
+
242
+ ## Alerts and the findings page
243
+
244
+ - **Alerts are fast and plain code.** The interpreter's `alerts` rules (the
245
+ reviewer proposes them, you agree) are checked every tick. Each has a
246
+ cooldown and a severity (`page` or `warn`). There are three kinds:
247
+ - `match`: N entries matching a template or level in M minutes
248
+ - `unit_failed`: a named unit has failed, user or system
249
+ - `silent`: a source has written nothing for M minutes
250
+ - **When one fires, you get a terse email at once.** The reviewer is woken
251
+ (its watch runs every minute, free when quiet) and follows up within
252
+ minutes with what it means and what to do.
253
+ - **Everything for a person goes through one queue:** fired alerts, the
254
+ reviewer's notes (`eaveswatch notify TEXT`, which works from its sandbox), and
255
+ hobkin's own "I've paused". The loop sends it, at most `per_hour` (20) an
256
+ hour. With no channel set, notes are kept in `DATA/notify/unsent/`.
257
+ `eaveswatch notify --test` sends one now. SNS is the likely next channel.
258
+ - **The findings page** is rendered by the loop whenever something changes:
259
+ open findings with their evidence, recent alerts, digests, hosts and
260
+ versions, and what's collected. It's written to `DATA/report/index.html`,
261
+ and copied to the publish folder if there is one. It's static HTML with
262
+ everything escaped, because it shows text from logs that others write, and
263
+ eaveswatch serves nothing itself.
264
+
265
+ ## The interpreter
266
+
267
+ ```json
268
+ {"sources": [
269
+ {"name": "appsrv", "path": "/home/psadm2/psft/pt/8.60/appserv/HRDEV/LOGS/APPSRV_*.LOG", "service": "hr-app",
270
+ "multiline": {"start": "^PSAPPSRV\\.\\d+ "}, "placeholders": [{"name": "sql", "pattern": "'[^']*'"}]},
271
+ {"name": "pia", "path": "/home/psadm2/psft/pt/8.60/webserv/peoplesoft/servers/PIA/logs/PIA_weblogic.log",
272
+ "multiline": {"start": "^####<"}, "level": {"pattern": "^####<[^>]*> <(\\w+)>", "map": {"error": "error", "warning": "warn"}},
273
+ "timestamp": {"pattern": "^####<(\\w{3} \\d{1,2}, \\d{4} \\d{1,2}:\\d\\d:\\d\\d,\\d+ [AP]M)", "format": "%b %d, %Y %I:%M:%S,%f %p"}},
274
+ {"name": "units", "journald": {"user": true, "units": ["psappsrv.service", "pia.service"]}}],
275
+ "services": {"hr-app": {"version": {"file": "/home/psadm2/psft/pt/8.60/peopletools.properties", "pattern": "^toolsrelease=(\\S+)"},
276
+ "watch": ["/home/psadm2/psft/pt/8.60/appserv/HRDEV/psappsrv.cfg"]}}}
277
+ ```
278
+
279
+ That's the kind of spec a reviewer writes. None of it ships in eaveswatch.
280
+ `interpret init` starts from generic placeholders (times, ids, addresses,
281
+ numbers, quoted strings) and levels, and the reviewer adds the rest:
282
+
283
+ | Field | What it does |
284
+ |---|---|
285
+ | `sources` | `path` (a glob, so date-rolled logs are followed) or `journald` (`user`, `units`, `identifiers`) |
286
+ | per source | `service`, `multiline.start`, `timestamp` (pattern + strptime format), `level` (pattern + map), `placeholders`, `drop`/`keep`, `raw` (archive redacted entries), `from_start` |
287
+ | `services` | `git` (a checkout's commit) or `version` (`file` + `pattern`), and `watch`: config files hashed (never read out); a change shows in the digest |
288
+ | top level | `placeholders`, `level`, `redact` (extra patterns), `watch`, `spike`, `warmup_minutes`, `max_template`, `max_samples` |
289
+
290
+ ```sh
291
+ eaveswatch interpret init FILE # the starter
292
+ eaveswatch interpret test FILE # real recent lines through it: entries, templates, timestamps, slow patterns
293
+ eaveswatch interpret apply FILE --note "why" # the next version, active on the eave's next tick
294
+ eaveswatch interpret show | rollback [N]
295
+ ```
296
+
297
+ A source's first read starts from the end of what's already there. A file
298
+ that appears later, like tomorrow's `APPSRV_1115.LOG`, is read from its start.
299
+
300
+ ## The store
301
+
302
+ A folder, or `s3://bucket/prefix`. Hive-style prefixes, so Athena and Glue can
303
+ query the archive as it is:
304
+
305
+ ```
306
+ PREFIX/env=prod/kind=packets/dt=2026-11-14/hour=09/host=ps-hr-01/ps-hr-01_20261114T091502Z_7f3a.jsonl.gz
307
+ ```
308
+
309
+ - **Time before host:** the ridge lists one hour across every host, and each
310
+ host's files are still under its own prefix (`*/host=web-3/*`).
311
+ - **The name stands alone:** host, UTC stamp (sorts as text), a random suffix.
312
+ - **Kinds:**
313
+ - `packets`: templates, counts, samples, new and spiking
314
+ - `inventory`: versions, watched-file hashes, units, the interpreter version, the spool
315
+ - `flows`
316
+ - `raw`: redacted entries of sources marked `raw`, which is the archive
317
+ - `requests` and `answers`: asks
318
+ - `alerts`: what fired, and why
319
+ - `check`: probes, which a lifecycle rule can expire
320
+ - **Systems' shared state** sits beside the data, at
321
+ `PREFIX/systems/system=NAME/` (see "A system of several nodes").
322
+ - **Nothing is lost when the store is away.** Every file is spooled to disk
323
+ before the eave saves its place in the logs, then sent oldest first. Past
324
+ `spool_max_mb` the oldest `raw` files go first, and the gap is reported
325
+ once the store is back. Delivery is at least once.
326
+ - **Late files keep their hour.** Every `late_every` seconds the ridge looks
327
+ back `late_hours`.
328
+ - No boto3: SigV4 is signed in the standard library. Credentials come from the
329
+ environment, `~/.aws/credentials`, the ECS task role or the instance role.
330
+
331
+ ## A fleet
332
+
333
+ Every host runs an eave ([examples/fleet/](examples/fleet/)): plumbing-only
334
+ config, with its interpreter in `STATE_DIR/interpreter`. The hub runs the ridge
335
+ sync loop with the store's credentials, and the reviewer agent has none:
336
+ `ridge watch` and every command it runs read and write only the ridge's data
337
+ folder. The ridge never gets
338
+ `kind=raw`.
339
+
340
+ ## The reviewer contract
341
+
342
+ eaveswatch never imports an agent. It talks to its reviewer through commands and
343
+ files, so any harness that does these four things can be the reviewer:
344
+
345
+ 1. **Wake on `eaveswatch watch`** about once a minute, and act only when it
346
+ prints something. Silence means there's nothing to do, so it costs nothing.
347
+ 2. **Follow the instructions** printed by `eaveswatch reviewer instructions`,
348
+ filled in for this box.
349
+ 3. **Reach a person through `eaveswatch notify TEXT`**, for its own notes and for
350
+ the harness's own trouble (its model refusing, say).
351
+ 4. **Run sandboxed:** write only the data folder, no network, no credentials.
352
+ The reviewer reads logs, commit messages and requests that attackers can
353
+ write. eaveswatch keeps its own side safe (redaction before anything leaves
354
+ a host, nothing that needs credentials, asks to other hosts from a fixed
355
+ menu), but the sandbox is the harness's job.
356
+
357
+ `eaveswatch reviewer contract` prints all of that as JSON, for this box.
358
+ **hobkin** is built in as the adapter (`pip install 'eaveswatch[hobkin]'`).
359
+ `eaveswatch reviewer install hobkin` writes the instructions as a hobkin chore:
360
+ its wake, safe mode writing only the data folder, and its alert command into
361
+ `notify`. `setup` and `boot` start it. Another harness gets an adapter class
362
+ beside `HobkinReviewer` (in `REVIEWERS`), or just uses the contract.
363
+
364
+ ## What the reviewer does
365
+
366
+ The instructions (`REVIEWER_MD` in `eaveswatch.py`; `eaveswatch reviewer
367
+ instructions` prints them) cover setting up, keeping the interpreter right, and
368
+ reviewing:
369
+
370
+ - **The map** (`DATA/map/`): hosts (what runs, versions, watched hashes), edges
371
+ (listeners and who talks to whom), templates (hosts, versions, totals).
372
+ - **The digest** says what changed:
373
+ - new hosts, and hosts gone quiet
374
+ - deploys
375
+ - config changes and interpreter versions
376
+ - what changed on the box (from discovery)
377
+ - new connections
378
+ - spiking and new templates
379
+ - answers to its asks
380
+ - **Asks** go to an eave from a fixed menu, never a shell: `ask HOST lines FP`,
381
+ `ask HOST inventory`, `ask HOST flows`.
382
+ - **A finding** needs evidence, a failure scenario, an action and a
383
+ confidence. Without all four it's an observation. Before recording one, a
384
+ brownie tries to knock it down; at most three are recorded per digest.
385
+ - **The ledger** (`DATA/findings.jsonl`) is append-only. The same category and
386
+ location is the same finding (counted again, not duplicated), a dismissal
387
+ needs a reason and isn't recorded again, and each finding carries its
388
+ inputs and the charter version.
389
+
390
+ ```sh
391
+ eaveswatch finding add < finding.json
392
+ eaveswatch finding list [--all]
393
+ eaveswatch finding status F-1a2b3c4d dismissed --why "behind the VPN only"
394
+ ```
395
+
396
+ ## Check a box
397
+
398
+ `eaveswatch --config FILE check`, as the service's user, tests exactly what
399
+ its role does:
400
+ - **eave:** writes under its own host and lists its asks
401
+ - **ridge:** lists and reads what it syncs
402
+ - **finial:** only writes
403
+
404
+ It also reports where the credentials came from, the clock against S3's, the
405
+ active interpreter, every source (globs, permissions, journald), every
406
+ service's version and the watched files. Then it shows a preview of recent
407
+ lines as templates, with how many values were redacted. Each failure says what
408
+ to change, and the exit code is 1 on any failure.
409
+
410
+ ## Config
411
+
412
+ Plumbing only; what to collect is the interpreter's.
413
+
414
+ | Setting | Default | |
415
+ |---|---|---|
416
+ | `role` | `eave` | `eave`, `ridge` or `finial` |
417
+ | `env` | `prod` | the `env=` prefix |
418
+ | `host` | hostname | the `host=` prefix |
419
+ | `system` | host | the name a system's nodes share (an ASG's): its shared state in the store |
420
+ | `cluster` | | a system's timings: `lease_seconds`, `claim_seconds`, `job_wait_seconds`, `node_gone_seconds`, `job_keep_days` |
421
+ | `store` | | a folder, or `s3://bucket/prefix` |
422
+ | `region`, `endpoint` | `$AWS_REGION` | S3 region; an S3-compatible endpoint (path-style) |
423
+ | `state_dir` | `/var/lib/eaveswatch` | offsets, cursors, baselines, the spool; the ridge's data default |
424
+ | `interpreter` | | the interpreter folder; default `RIDGE_DATA/interpreter` for a finial (the reviewer writes it), else `STATE_DIR/interpreter` |
425
+ | `every` | `60` | seconds between ticks |
426
+ | `inventory_every` | `3600` | inventory and flows: on change, and at least this often |
427
+ | `discover_every` | `3600` | a finial's look at what runs here |
428
+ | `spool_max_mb` | `512` | the most kept waiting for the store |
429
+ | `notify` | | where alerts and notes go: `{"email", "sendmail"}` or `{"email", "smtp": "host:port"}`, plus `from` and `per_hour` |
430
+ | `publish` | | `{"dir": PATH}`: a folder a web server already serves, for the findings page |
431
+ | `ridge` | | `data`, `envs`, `silent_after`, `late_hours` (`24`), `late_every` (`600`) |
432
+
433
+ ## Tests
434
+
435
+ `python3 tests/test_eaveswatch.py`. No AWS and no cost. It covers:
436
+ - the redaction floor
437
+ - the interpreter: validation, credentials refused, multi-line WebLogic
438
+ records, levels, timestamps, a catastrophic pattern caught
439
+ - discovery of a real process here: its open log and its port
440
+ - date-rolled logs
441
+ - versions and watched files
442
+ - the layout and AWS's own SigV4 test vector
443
+ - full round trips on a folder, a fake S3 that checks every signature, and a finial
444
+ - the spool through an outage
445
+ - check, the ledger and the chore
446
+
@@ -0,0 +1,6 @@
1
+ eaveswatch.py,sha256=vZ5tAdMjJNJD-uqODHvXg3YJcOycX2CHhcPROXwN6DU,228387
2
+ eaveswatch-0.8.0.dist-info/entry_points.txt,sha256=8I3f04YXBPKiabvp7WnZ1sfs038CwXQw_yV--IPd-Jw,45
3
+ eaveswatch-0.8.0.dist-info/licenses/LICENSE,sha256=kS2xaDPY2aH4QtYsz220wwci9CFGNufXDQLxsTS2uLE,1068
4
+ eaveswatch-0.8.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
5
+ eaveswatch-0.8.0.dist-info/METADATA,sha256=jDL6132xWSgVQfhwArauYPNpIY1PnnsHmDMiTGvCa7c,23402
6
+ eaveswatch-0.8.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.12.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ eaveswatch=eaveswatch:cli
3
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Frank Lynam
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.