reanimator 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +207 -0
- data/LICENSE.txt +21 -0
- data/README.md +483 -0
- data/SECURITY.md +32 -0
- data/exe/reanimator +6 -0
- data/lib/reanimator/acceptance_criterion.rb +75 -0
- data/lib/reanimator/agent_connection.rb +62 -0
- data/lib/reanimator/agent_profile.rb +51 -0
- data/lib/reanimator/agents/adapter.rb +49 -0
- data/lib/reanimator/agents/capability.rb +29 -0
- data/lib/reanimator/agents/constraints.rb +57 -0
- data/lib/reanimator/agents/hosted.rb +38 -0
- data/lib/reanimator/agents/local.rb +46 -0
- data/lib/reanimator/agents/patch.rb +60 -0
- data/lib/reanimator/agents/repair_task.rb +51 -0
- data/lib/reanimator/agents/result.rb +42 -0
- data/lib/reanimator/agents/strategies/arithmetic_fix.rb +51 -0
- data/lib/reanimator/agents/strategies/base.rb +26 -0
- data/lib/reanimator/agents.rb +38 -0
- data/lib/reanimator/autonomy_level.rb +41 -0
- data/lib/reanimator/baseline.rb +106 -0
- data/lib/reanimator/baseline_runner.rb +81 -0
- data/lib/reanimator/bounded_command.rb +87 -0
- data/lib/reanimator/classifier.rb +89 -0
- data/lib/reanimator/cli.rb +273 -0
- data/lib/reanimator/cohort.rb +61 -0
- data/lib/reanimator/commissioning/fixture.rb +69 -0
- data/lib/reanimator/commissioning/report.rb +45 -0
- data/lib/reanimator/commissioning/test.rb +145 -0
- data/lib/reanimator/confidence.rb +66 -0
- data/lib/reanimator/configuration.rb +35 -0
- data/lib/reanimator/contract_file.rb +116 -0
- data/lib/reanimator/contract_snapshot.rb +188 -0
- data/lib/reanimator/criteria_stack.rb +91 -0
- data/lib/reanimator/criterion_result.rb +70 -0
- data/lib/reanimator/detectors/base.rb +49 -0
- data/lib/reanimator/detectors/bundler.rb +68 -0
- data/lib/reanimator/detectors/commands.rb +112 -0
- data/lib/reanimator/detectors/fork.rb +25 -0
- data/lib/reanimator/detectors/github_actions.rb +68 -0
- data/lib/reanimator/detectors/operational_proof.rb +48 -0
- data/lib/reanimator/detectors/rails_app.rb +51 -0
- data/lib/reanimator/detectors/relationships.rb +46 -0
- data/lib/reanimator/detectors/ruby_gem.rb +40 -0
- data/lib/reanimator/detectors/security_controls.rb +36 -0
- data/lib/reanimator/discovered_fact.rb +99 -0
- data/lib/reanimator/discovery.rb +48 -0
- data/lib/reanimator/discovery_result.rb +89 -0
- data/lib/reanimator/drift_detector.rb +227 -0
- data/lib/reanimator/drift_report.rb +86 -0
- data/lib/reanimator/effective_contract.rb +108 -0
- data/lib/reanimator/effective_policy_resolver.rb +116 -0
- data/lib/reanimator/errors.rb +21 -0
- data/lib/reanimator/evaluators/base.rb +55 -0
- data/lib/reanimator/evaluators/command.rb +34 -0
- data/lib/reanimator/evaluators/documentation_check.rb +25 -0
- data/lib/reanimator/evaluators/http_smoke.rb +19 -0
- data/lib/reanimator/evaluators/repository_integrity.rb +22 -0
- data/lib/reanimator/evaluators/security_evidence.rb +24 -0
- data/lib/reanimator/evaluators/support_matrix.rb +25 -0
- data/lib/reanimator/evaluators.rb +41 -0
- data/lib/reanimator/family/gate.rb +40 -0
- data/lib/reanimator/family/gate_result.rb +46 -0
- data/lib/reanimator/family/gate_runner.rb +91 -0
- data/lib/reanimator/family.rb +89 -0
- data/lib/reanimator/family_inference.rb +113 -0
- data/lib/reanimator/git.rb +72 -0
- data/lib/reanimator/isolation.rb +36 -0
- data/lib/reanimator/language_inference.rb +43 -0
- data/lib/reanimator/lifecycle_intent.rb +93 -0
- data/lib/reanimator/obligation.rb +35 -0
- data/lib/reanimator/permission_model.rb +61 -0
- data/lib/reanimator/portfolio.rb +68 -0
- data/lib/reanimator/portfolio_drift.rb +120 -0
- data/lib/reanimator/profile_inference.rb +37 -0
- data/lib/reanimator/profiles.rb +110 -0
- data/lib/reanimator/proof_of_life.rb +45 -0
- data/lib/reanimator/proposed_change.rb +55 -0
- data/lib/reanimator/provenance.rb +39 -0
- data/lib/reanimator/relationship.rb +62 -0
- data/lib/reanimator/repair_run.rb +146 -0
- data/lib/reanimator/reports/agents.rb +28 -0
- data/lib/reanimator/reports/baseline.rb +73 -0
- data/lib/reanimator/reports/commissioning.rb +48 -0
- data/lib/reanimator/reports/contract.rb +70 -0
- data/lib/reanimator/reports/discovery.rb +38 -0
- data/lib/reanimator/reports/drift.rb +63 -0
- data/lib/reanimator/reports/families.rb +41 -0
- data/lib/reanimator/reports/family_gates.rb +41 -0
- data/lib/reanimator/reports/github_check.rb +45 -0
- data/lib/reanimator/reports/inventory.rb +37 -0
- data/lib/reanimator/reports/permissions.rb +32 -0
- data/lib/reanimator/reports/portfolio_baseline.rb +68 -0
- data/lib/reanimator/reports/portfolio_drift.rb +107 -0
- data/lib/reanimator/reports/run.rb +52 -0
- data/lib/reanimator/reports/snapshot.rb +58 -0
- data/lib/reanimator/reports.rb +36 -0
- data/lib/reanimator/repository.rb +96 -0
- data/lib/reanimator/repository_metadata.rb +78 -0
- data/lib/reanimator/repository_state.rb +48 -0
- data/lib/reanimator/routing/decision.rb +35 -0
- data/lib/reanimator/routing/router.rb +122 -0
- data/lib/reanimator/run.rb +48 -0
- data/lib/reanimator/safety_floor.rb +81 -0
- data/lib/reanimator/security_control.rb +87 -0
- data/lib/reanimator/security_posture.rb +80 -0
- data/lib/reanimator/snapshot_store.rb +78 -0
- data/lib/reanimator/task_class.rb +63 -0
- data/lib/reanimator/version.rb +5 -0
- data/lib/reanimator/vitality_profile.rb +44 -0
- data/lib/reanimator/waiver.rb +74 -0
- data/lib/reanimator.rb +214 -0
- metadata +262 -0
data/README.md
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
# Reanimator
|
|
2
|
+
|
|
3
|
+
**Turn repository intent into a verified, low-friction stewardship contract.**
|
|
4
|
+
|
|
5
|
+
An [Autogenetica](https://autogenetica.ai) system for self-maintaining software.
|
|
6
|
+
|
|
7
|
+
> The first successful Reanimator run should prove something useful without
|
|
8
|
+
> changing a single line of code.
|
|
9
|
+
|
|
10
|
+
Reanimator reads a repository, infers what *healthy* means for it, and proves
|
|
11
|
+
that health **read-only** — before any coding agent or write permission is ever
|
|
12
|
+
involved. It classifies lifecycle intent, discovers a vitality contract from
|
|
13
|
+
evidence, resolves an effective policy against a non-bypassable safety floor,
|
|
14
|
+
and produces a proof-of-life baseline.
|
|
15
|
+
|
|
16
|
+
This gem implements the read-only foundation of the
|
|
17
|
+
[Onboarding & Configuration Specification](#specification-coverage): delivery
|
|
18
|
+
slices 1–2 (inventory & intent, discovery & baseline) plus the canonical schema
|
|
19
|
+
and effective-policy resolver. It is the "trustworthy heartbeat" the spec says
|
|
20
|
+
to ship before building an autonomous surgeon.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# Gemfile
|
|
26
|
+
gem "reanimator"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```console
|
|
30
|
+
$ bundle install
|
|
31
|
+
# or, standalone:
|
|
32
|
+
$ gem install reanimator
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires Ruby >= 3.3.
|
|
36
|
+
|
|
37
|
+
## The onboarding path, on the command line
|
|
38
|
+
|
|
39
|
+
Every command is **read-only by default** and maps to an onboarding stage.
|
|
40
|
+
|
|
41
|
+
### 1. Classify before acting — `inventory`
|
|
42
|
+
|
|
43
|
+
```console
|
|
44
|
+
$ reanimator inventory ~/src/*
|
|
45
|
+
Portfolio inventory — 12 repositories
|
|
46
|
+
Recommended for onboarding: agentic, claude_memory, eliza
|
|
47
|
+
|
|
48
|
+
Active original repositories → suggest maintain:
|
|
49
|
+
- agentic — Active original repository with recognizable structure.
|
|
50
|
+
Forks → suggest upstream:
|
|
51
|
+
- langchainrb — The repository is a fork; defaulting to upstream observation.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Reanimator never preselects every repository — it recommends a small,
|
|
55
|
+
high-confidence set that can reach a useful baseline quickly.
|
|
56
|
+
|
|
57
|
+
### 2. Find proof before asking for policy — `discover`
|
|
58
|
+
|
|
59
|
+
```console
|
|
60
|
+
$ reanimator discover
|
|
61
|
+
Suggested profile: ruby-gem (high) — A gemspec indicates a published gem.
|
|
62
|
+
|
|
63
|
+
ecosystem:
|
|
64
|
+
[high] Packaged and released as a Ruby gem. ← widget.gemspec (file_present)
|
|
65
|
+
commands:
|
|
66
|
+
[high] Tests run with RSpec. ← .rspec (command_inference): bundle exec rspec
|
|
67
|
+
support matrix:
|
|
68
|
+
[medium] Declared Ruby support: >= 3.1.0. ← widget.gemspec (version_pin): >= 3.1.0
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Every discovered fact carries its **provenance and confidence**, so the tool can
|
|
72
|
+
always answer *"Why does Reanimator believe this?"*
|
|
73
|
+
|
|
74
|
+
### 3. Review the effective contract — `contract`
|
|
75
|
+
|
|
76
|
+
```console
|
|
77
|
+
$ reanimator contract
|
|
78
|
+
Effective contract — codenamev/widget
|
|
79
|
+
Intent: maintain (provisional)
|
|
80
|
+
Profile: ruby-gem (inferred) — A published Ruby gem: resolves, tests, lints, and builds.
|
|
81
|
+
Autonomy: observe=automatic diagnose=automatic repair=disabled submit=disabled merge=disabled
|
|
82
|
+
|
|
83
|
+
Security posture: balanced
|
|
84
|
+
Approved package-registry network, hosted agents allowed, patch submission permitted, no major automatic repair.
|
|
85
|
+
Required: none
|
|
86
|
+
Advisory: dependency review, secret scanning, security policy
|
|
87
|
+
|
|
88
|
+
Criteria (* = required):
|
|
89
|
+
* tests: Test suite passes. — bundle exec rspec
|
|
90
|
+
security-dependency-review: Dependency risk is reviewed automatically. — security_evidence
|
|
91
|
+
...
|
|
92
|
+
Protected paths (elevated review to change):
|
|
93
|
+
- .reanimator.yml
|
|
94
|
+
- .github/workflows/**
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Export a minimal, round-trippable `.reanimator.yml`:
|
|
98
|
+
|
|
99
|
+
```console
|
|
100
|
+
$ reanimator contract --export
|
|
101
|
+
version: 1
|
|
102
|
+
intent: maintain
|
|
103
|
+
profile: ruby-gem
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 4. Measure a proof-of-life baseline — `baseline`
|
|
107
|
+
|
|
108
|
+
Read-only by default (native checks run; commands are *planned*):
|
|
109
|
+
|
|
110
|
+
```console
|
|
111
|
+
$ reanimator baseline
|
|
112
|
+
State: UNPROVEN — A claim exists but sufficient executable evidence is absent or stale.
|
|
113
|
+
Mode: read-only (planned)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Opt in to bounded execution to actually prove life:
|
|
117
|
+
|
|
118
|
+
```console
|
|
119
|
+
$ reanimator baseline --execute
|
|
120
|
+
State: ALIVE — All due obligations have fresh passing evidence and no unapproved blocking risk.
|
|
121
|
+
Mode: executed (bounded, read-only)
|
|
122
|
+
Verified Vitality Rate: 100% (3/3 required)
|
|
123
|
+
✓* tests: Test suite passes. — bundle exec rspec (1.69s)
|
|
124
|
+
✓* build: Gem builds. — gem build widget.gemspec (0.32s)
|
|
125
|
+
|
|
126
|
+
NO SOURCE CHANGES WERE MADE
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Other formats: `--format check` (the GitHub Check surface) and `--format json`.
|
|
130
|
+
|
|
131
|
+
### 5. Prove an agent is ready — `agents` and `commission`
|
|
132
|
+
|
|
133
|
+
The agent is a replaceable life-force adapter: a repair provider is *commissioned*
|
|
134
|
+
against a disposable fixture and graded independently — **without any repository
|
|
135
|
+
write**.
|
|
136
|
+
|
|
137
|
+
```console
|
|
138
|
+
$ reanimator agents
|
|
139
|
+
Coding agents:
|
|
140
|
+
✓ local (available) [plan, patch, repository_edit, sandbox_exec]
|
|
141
|
+
◦ codex (unavailable) [...] — No credentials found (set OPENAI_ACCESS_TOKEN); connect the codex provider first.
|
|
142
|
+
|
|
143
|
+
$ reanimator commission
|
|
144
|
+
Commissioning — local
|
|
145
|
+
Readiness: READY
|
|
146
|
+
Objective: arithmetic (at risk → alive)
|
|
147
|
+
Cost: $0.00 Latency: 0.001s Tools: repository_edit, patch Network: none
|
|
148
|
+
A connected profile is proven ready. NO REPOSITORY WRITES WERE REQUIRED.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The agent proposes a **patch as data**; the harness applies it to a separate,
|
|
152
|
+
clean copy and grades it with Reanimator's own evaluators. A patch that reaches
|
|
153
|
+
for a protected or verifier path is refused — the anti-gaming rule, enforced.
|
|
154
|
+
|
|
155
|
+
### 6. Bounded repair — `repair`
|
|
156
|
+
|
|
157
|
+
A repair is found, applied in an isolated workspace, and **re-verified
|
|
158
|
+
independently** before anything is proposed. The result is a *draft PR
|
|
159
|
+
artifact* — never a write, never a submission.
|
|
160
|
+
|
|
161
|
+
```console
|
|
162
|
+
$ reanimator repair # on a healthy repo
|
|
163
|
+
Outcome: NO-OP (healthy)
|
|
164
|
+
|
|
165
|
+
$ reanimator repair # repair not yet enabled in the contract
|
|
166
|
+
Outcome: ESCALATION
|
|
167
|
+
Reason: Repair is not enabled for patch_dependency (max autonomy diagnose); enable repair to proceed.
|
|
168
|
+
|
|
169
|
+
$ reanimator repair # repair enabled, a fixable failure present
|
|
170
|
+
Outcome: DRAFT PR (proposed, not submitted)
|
|
171
|
+
Evidence: at_risk → alive
|
|
172
|
+
Proposed draft PR:
|
|
173
|
+
branch: reanimator/repair-arithmetic-901cec8a
|
|
174
|
+
title: Fix: add(2, 2) equals 4.
|
|
175
|
+
NO SOURCE WAS WRITTEN TO THE REPOSITORY.
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Every outcome is recorded as an immutable run-ledger entry (`no_op`, `report`,
|
|
179
|
+
`draft_pr`, `escalation`, `safe_stop`). Submitting a proposed change to GitHub is
|
|
180
|
+
a deliberate, separate step — not something repair does on its own.
|
|
181
|
+
|
|
182
|
+
### Repository families — `families` and `gates`
|
|
183
|
+
|
|
184
|
+
Related projects are a compatibility graph, not isolated targets. Families are
|
|
185
|
+
inferred from portfolio structure (a project and its `-examples` suite,
|
|
186
|
+
`-example-*` deployment samples, `-docs`, or a `<host>-<project>` plugin):
|
|
187
|
+
|
|
188
|
+
```console
|
|
189
|
+
$ reanimator families ~/src/*
|
|
190
|
+
Repository families — 2 families (4 repositories that must stay alive together)
|
|
191
|
+
|
|
192
|
+
agentic:
|
|
193
|
+
- agentic (library)
|
|
194
|
+
- agentic-examples (compatibility suite)
|
|
195
|
+
edge: agentic -> agentic-examples (compatibility suite)
|
|
196
|
+
gate: agentic requires its own proof command in agentic-examples
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`gates` proves each edge by running the downstream member's own check — read-only,
|
|
200
|
+
bounded, in a disposable copy, so an upstream change triggers downstream checks
|
|
201
|
+
**without opening anything downstream**. A failure names the exact edge:
|
|
202
|
+
|
|
203
|
+
```console
|
|
204
|
+
$ reanimator gates ~/src/* --execute
|
|
205
|
+
Family gates — slimer
|
|
206
|
+
✗ slimer -> slimer-example-docker (deployment example) — bin/smoke failed.
|
|
207
|
+
BROKEN EDGE: slimer -> slimer-example-docker (deployment example)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
A repository can also declare its membership (`family: {id:, role:}`), and a full
|
|
211
|
+
section 10 family policy with explicit `members:` and `gates:` is parsed as-is.
|
|
212
|
+
Declaration always wins over inference.
|
|
213
|
+
|
|
214
|
+
### Configuration drift — `drift`
|
|
215
|
+
|
|
216
|
+
A contract is only trustworthy while it still describes the repository. Drift
|
|
217
|
+
detection compares what a repository resolved to when you recorded it against
|
|
218
|
+
what its evidence resolves to now, and sorts every difference by one question:
|
|
219
|
+
**is less being proven than before?**
|
|
220
|
+
|
|
221
|
+
```console
|
|
222
|
+
$ reanimator drift # what am I required to prove right now?
|
|
223
|
+
Contract snapshot — codenamev/widget
|
|
224
|
+
Intent: maintain Profile: ruby-gem Security: balanced posture
|
|
225
|
+
Proves: 4 criteria (2 required)
|
|
226
|
+
Evidence: runtimes 3.3, 3.4 · security gates dependabot
|
|
227
|
+
|
|
228
|
+
Nothing was written. Record this with `reanimator drift --record` so later drift has something to be measured against.
|
|
229
|
+
|
|
230
|
+
$ reanimator drift --record
|
|
231
|
+
Recorded contract snapshot for codenamev/widget → .reanimator-snapshot.json
|
|
232
|
+
|
|
233
|
+
$ reanimator drift # now there is something to compare against
|
|
234
|
+
Configuration drift — codenamev/widget
|
|
235
|
+
|
|
236
|
+
Policy changes (review required) — 2
|
|
237
|
+
! supported runtime removed: 3.2
|
|
238
|
+
The runtime matrix narrowed: a version that used to be exercised no longer is.
|
|
239
|
+
! security control removed: dependabot
|
|
240
|
+
A security gate was removed; it is no longer part of the proof.
|
|
241
|
+
|
|
242
|
+
Benign changes — 1
|
|
243
|
+
· criterion tests command: bundle exec rspec → bin/rspec
|
|
244
|
+
The same obligation is proven by a differently spelled command.
|
|
245
|
+
|
|
246
|
+
REVIEW REQUIRED: 2 policy changes — less is being proven than before.
|
|
247
|
+
|
|
248
|
+
Intended? Re-record with: reanimator drift --record
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
A renamed test command is benign — the obligation did not move, only its
|
|
252
|
+
spelling. A narrowed matrix or a removed gate is a policy change nobody
|
|
253
|
+
necessarily decided on, so it is never buried in the benign list. The command
|
|
254
|
+
exits non-zero on policy drift, so CI can gate on it (and says how to accept
|
|
255
|
+
drift you meant). The snapshot captures
|
|
256
|
+
*discovered* evidence too — drift that never touches `.reanimator.yml` is
|
|
257
|
+
still caught.
|
|
258
|
+
|
|
259
|
+
The snapshot lives **in the repository**, committed as
|
|
260
|
+
`.reanimator-snapshot.json` beside `.reanimator.yml`. That is deliberate:
|
|
261
|
+
the contract and the record of what it last required are the same kind of
|
|
262
|
+
thing, and keeping both under review means a policy change arrives as a diff in
|
|
263
|
+
a pull request rather than as state hidden inside a tool.
|
|
264
|
+
|
|
265
|
+
#### Across a portfolio
|
|
266
|
+
|
|
267
|
+
Pass several paths and drift becomes a triage list. Each repository is compared
|
|
268
|
+
against its own snapshot, and one unreadable member never stops the rest:
|
|
269
|
+
|
|
270
|
+
```console
|
|
271
|
+
$ reanimator drift ~/src/*
|
|
272
|
+
REANIMATOR DRIFT
|
|
273
|
+
Portfolio: portfolio
|
|
274
|
+
Mode: read-only
|
|
275
|
+
|
|
276
|
+
REVIEW REQUIRED — 1 repository
|
|
277
|
+
codenamev/widget
|
|
278
|
+
1 policy change
|
|
279
|
+
! security control removed: dependabot
|
|
280
|
+
A security gate was removed; it is no longer part of the proof.
|
|
281
|
+
|
|
282
|
+
CHANGED, BENIGN — 1 repository
|
|
283
|
+
codenamev/doodad
|
|
284
|
+
· criterion tests command: bundle exec rake spec → bundle exec rspec
|
|
285
|
+
|
|
286
|
+
UNCHANGED — 3 repositories
|
|
287
|
+
codenamev/gadget
|
|
288
|
+
codenamev/gizmo
|
|
289
|
+
codenamev/thing
|
|
290
|
+
|
|
291
|
+
NOT RECORDED — 1 repository
|
|
292
|
+
codenamev/fresh
|
|
293
|
+
nothing to compare against yet
|
|
294
|
+
|
|
295
|
+
REVIEW REQUIRED: 1 policy change across 1 repository — less is being proven than before.
|
|
296
|
+
Intended? Re-record with: reanimator drift --record
|
|
297
|
+
|
|
298
|
+
NO FILES WERE WRITTEN. NO SOURCE CHANGES WERE MADE.
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
For repositories you cannot commit a file into, `--snapshots DIR` keeps them
|
|
302
|
+
side by side keyed by repository instead:
|
|
303
|
+
|
|
304
|
+
```console
|
|
305
|
+
$ reanimator drift ~/src/* --snapshots ~/.reanimator/snapshots --record
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Three outcomes are deliberately distinct. A repository with **nothing
|
|
309
|
+
recorded** is named, not counted as fine, and does not fail the gate — a first
|
|
310
|
+
run has nothing to compare against. A repository whose snapshot **cannot be
|
|
311
|
+
read** does fail it: "we could not tell" is not "nothing changed", and
|
|
312
|
+
corrupting a snapshot must never be a way to pass. Benign change never fails
|
|
313
|
+
it at all.
|
|
314
|
+
|
|
315
|
+
### Security posture — what it actually binds
|
|
316
|
+
|
|
317
|
+
The posture is a named decision with consequences, not a label. It decides how
|
|
318
|
+
much security evidence a repository has to show; the project's shape does not.
|
|
319
|
+
|
|
320
|
+
```yaml
|
|
321
|
+
# .reanimator.yml
|
|
322
|
+
version: 1
|
|
323
|
+
intent: maintain
|
|
324
|
+
profile: ruby-gem
|
|
325
|
+
security:
|
|
326
|
+
posture: strict # strict | balanced | local | custom
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
| Posture | Blocks without evidence | Reported, not blocking |
|
|
330
|
+
| --- | --- | --- |
|
|
331
|
+
| `strict` (default for private repositories) | security policy, dependency review, secret scanning | static analysis, ownership |
|
|
332
|
+
| `balanced` (default for public repositories) | — | dependency review, secret scanning, security policy |
|
|
333
|
+
| `local` | secret scanning | dependency review |
|
|
334
|
+
| `custom` | — | — |
|
|
335
|
+
|
|
336
|
+
Controls are named by **obligation, not by tool**: "dependency risk is reviewed"
|
|
337
|
+
is satisfied by Dependabot, `bundler-audit`, or `actions/dependency-review-action`
|
|
338
|
+
alike, so a repository that already solved the problem its own way is credited
|
|
339
|
+
rather than asked to adopt ours. A control with no evidence is reported as
|
|
340
|
+
*missing evidence* with the remedy attached — never as a silent pass:
|
|
341
|
+
|
|
342
|
+
```console
|
|
343
|
+
• security-secret-scanning: Committed secrets are scanned for.
|
|
344
|
+
— No secret scanning found. Add gitleaks or trufflehog to CI.
|
|
345
|
+
✓ security-policy: A security policy says how to report a vulnerability.
|
|
346
|
+
— A security policy is published (SECURITY.md).
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Under `strict` and `local` those gaps are blocking, so a repository with no
|
|
350
|
+
security evidence is **Unproven**, not Alive. When the gap is a deliberate,
|
|
351
|
+
tracked decision, accept it with a waiver rather than weakening the posture.
|
|
352
|
+
|
|
353
|
+
`custom` implies nothing in either direction: "no hidden relaxation from a
|
|
354
|
+
preset" means Reanimator adds no control you did not ask for, either.
|
|
355
|
+
|
|
356
|
+
### Accepted risk — waivers
|
|
357
|
+
|
|
358
|
+
Sometimes a known failure is a deliberate, tracked decision, not a gap. A waiver
|
|
359
|
+
records it as **accepted risk** — visible, attributable, and kept out of the
|
|
360
|
+
vitality rate — without pretending it passed:
|
|
361
|
+
|
|
362
|
+
```yaml
|
|
363
|
+
# .reanimator.yml
|
|
364
|
+
version: 1
|
|
365
|
+
intent: maintain
|
|
366
|
+
profile: ruby-gem
|
|
367
|
+
waivers:
|
|
368
|
+
- target: boot # the acceptance-criterion id
|
|
369
|
+
reason: "hardware-only smoke; tracked in OPS-7"
|
|
370
|
+
expires: 2030-01-01 # optional; past this it fails closed
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The baseline then reports `boot` under "Accepted risk (waived)" instead of as a
|
|
374
|
+
blocker, and the repository can be **Alive** with the risk shown separately. An
|
|
375
|
+
expired waiver stops applying and the obligation is blocking again.
|
|
376
|
+
|
|
377
|
+
### Resolved autonomy — `permissions`
|
|
378
|
+
|
|
379
|
+
```console
|
|
380
|
+
$ reanimator permissions
|
|
381
|
+
Permissions — codenamev/widget (autonomy: observe=automatic diagnose=automatic repair=disabled …)
|
|
382
|
+
Patch dependency update: max=diagnose | observe=auto diagnose=auto repair=off submit=off merge=off …
|
|
383
|
+
CI or verifier modification: max=diagnose | … [elevated review]
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Task-class defaults (section 11) are intersected with the repository's *enabled*
|
|
387
|
+
autonomy and the safety floor: with repair/submit opt-in and off, everything
|
|
388
|
+
clamps to diagnose; merge is always off in v1.
|
|
389
|
+
|
|
390
|
+
### The portfolio baseline (Appendix A)
|
|
391
|
+
|
|
392
|
+
```console
|
|
393
|
+
$ reanimator portfolio ~/src/* --name codenamev
|
|
394
|
+
REANIMATOR BASELINE
|
|
395
|
+
Portfolio: codenamev
|
|
396
|
+
Mode: read-only
|
|
397
|
+
|
|
398
|
+
ALIVE
|
|
399
|
+
agentic
|
|
400
|
+
UNPROVEN
|
|
401
|
+
ups
|
|
402
|
+
- Boot smoke against /up needs a running service; not established in a read-only baseline.
|
|
403
|
+
|
|
404
|
+
NO SOURCE CHANGES WERE MADE
|
|
405
|
+
No coding agent was required.
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## Library API
|
|
409
|
+
|
|
410
|
+
The CLI is a thin shell over a plain Ruby API — the same "80% path" in one call:
|
|
411
|
+
|
|
412
|
+
```ruby
|
|
413
|
+
require "reanimator"
|
|
414
|
+
|
|
415
|
+
Reanimator.inventory(paths) #=> Reanimator::Portfolio
|
|
416
|
+
Reanimator.discover(path) #=> Reanimator::DiscoveryResult
|
|
417
|
+
Reanimator.contract(path) #=> Reanimator::EffectiveContract
|
|
418
|
+
Reanimator.baseline(path, execute: true) #=> Reanimator::Baseline
|
|
419
|
+
Reanimator.snapshot(path) #=> Reanimator::ContractSnapshot
|
|
420
|
+
Reanimator.drift(path, since: file) #=> Reanimator::DriftReport
|
|
421
|
+
Reanimator.portfolio_drift(paths) #=> Reanimator::PortfolioDrift
|
|
422
|
+
|
|
423
|
+
baseline = Reanimator.baseline(".")
|
|
424
|
+
baseline.state.to_sym #=> :unproven
|
|
425
|
+
baseline.verified_vitality_rate #=> 0.0
|
|
426
|
+
baseline.to_h # fully serializable
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
## Core model
|
|
430
|
+
|
|
431
|
+
The nouns that stay stable while implementations evolve (spec section 02):
|
|
432
|
+
|
|
433
|
+
| Concept | Class |
|
|
434
|
+
| --- | --- |
|
|
435
|
+
| Lifecycle intent | `LifecycleIntent` (maintain, monitor, preserve, incubate, retire, upstream, ignore) |
|
|
436
|
+
| Vitality profile | `VitalityProfile` / `Profiles` (ruby-gem, ruby-cli, rails-app, rails-legacy, …) |
|
|
437
|
+
| Discovered fact | `DiscoveredFact` (+ `Provenance`, `Confidence`) |
|
|
438
|
+
| Effective contract | `EffectiveContract` (+ `EffectivePolicyResolver`, `SafetyFloor`) |
|
|
439
|
+
| Baseline | `Baseline` (+ `BaselineRunner`, `Evaluators`, `ProofOfLife`) |
|
|
440
|
+
| Repository state | `RepositoryState` (alive, at risk, needs you, recovering, unproven, sleeping, diverged, retired) |
|
|
441
|
+
|
|
442
|
+
Two rules are **enforced**, not just documented:
|
|
443
|
+
|
|
444
|
+
- An owner-declared contract wins over inference.
|
|
445
|
+
- Neither a declaration nor discovery may loosen the safety floor — merge stays
|
|
446
|
+
off in v1, verifier and contract paths stay protected, and a task that
|
|
447
|
+
modifies the rules of proof cannot use them to approve itself.
|
|
448
|
+
|
|
449
|
+
## Specification coverage
|
|
450
|
+
|
|
451
|
+
| Spec area | Status |
|
|
452
|
+
| --- | --- |
|
|
453
|
+
| 01–02 Model, goals, constraints | ✅ Implemented |
|
|
454
|
+
| 04–05 Portfolio intake, lifecycle intent | ✅ `Portfolio`, `Classifier`, `Cohort` |
|
|
455
|
+
| 06 Discovery by confirmation & drift | ✅ `Detectors::*`, provenance, confidence, `DriftDetector`, `PortfolioDrift` |
|
|
456
|
+
| 07 Configuration & precedence | ✅ `EffectivePolicyResolver`, `SafetyFloor` |
|
|
457
|
+
| 09 Acceptance criteria, verifier isolation & waivers | ✅ `AcceptanceCriterion`, `Evaluators`, `Waiver` (accepted risk) |
|
|
458
|
+
| 14–15 Baseline & proof-of-life | ✅ `BaselineRunner`, `ProofOfLife`, `Reports` |
|
|
459
|
+
| 08 Agent connections, commissioning, routing | ✅ `Agents::*`, `Commissioning`, `Routing::Router` |
|
|
460
|
+
| 11 Autonomy ladder & permission model | ✅ `AutonomyLevel`, `TaskClass`, `PermissionModel` |
|
|
461
|
+
| 13 / 17 Bounded repair & run ledger | ✅ `RepairRun`, `Obligation`, `ProposedChange`, `Run` (no writes) |
|
|
462
|
+
| 18 MVP slices 1–4 | ✅ Inventory→baseline→commissioning→bounded repair |
|
|
463
|
+
| 10 Repository families & compatibility graphs | ✅ `Relationship`, `Family`, `FamilyInference`, `Family::GateRunner` |
|
|
464
|
+
| 12 Security controls | ✅ `SecurityControl`, `SecurityPosture#obligations`, `Evaluators::SecurityEvidence` (network egress still advisory) |
|
|
465
|
+
| Draft-PR *submission* to GitHub | ⏳ Deliberate opt-in; artifact is ready, the write is not wired |
|
|
466
|
+
| GitHub App, web UI | ⏳ Explicitly later (spec sections 03, 18) |
|
|
467
|
+
|
|
468
|
+
See [`.architecture/decisions`](.architecture/decisions) for the reasoning
|
|
469
|
+
behind key choices.
|
|
470
|
+
|
|
471
|
+
## Development
|
|
472
|
+
|
|
473
|
+
```console
|
|
474
|
+
$ bin/setup # install dependencies
|
|
475
|
+
$ bundle exec rake # run specs and StandardRB
|
|
476
|
+
$ bin/console # an IRB session with the gem loaded
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Releases are manual and credential-free — see [RELEASING.md](RELEASING.md).
|
|
480
|
+
|
|
481
|
+
## License
|
|
482
|
+
|
|
483
|
+
Released under the [MIT License](LICENSE.txt).
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
Security is part of Reanimator's design, not a hidden settings page. The tool
|
|
4
|
+
treats repository content, issues, logs, pull requests, and external
|
|
5
|
+
documentation as **untrusted input**, and treats coding-agent responses as
|
|
6
|
+
**untrusted proposals** until they are independently evaluated.
|
|
7
|
+
|
|
8
|
+
## Reporting a vulnerability
|
|
9
|
+
|
|
10
|
+
Please report suspected vulnerabilities privately to **v@codenamev.com** rather
|
|
11
|
+
than opening a public issue. Include a description, reproduction steps, and the
|
|
12
|
+
affected version. You will receive an acknowledgement, and a coordinated fix and
|
|
13
|
+
disclosure timeline.
|
|
14
|
+
|
|
15
|
+
## Design guarantees relevant to security
|
|
16
|
+
|
|
17
|
+
- **Read-only by default.** Inventory, classification, discovery, and baseline
|
|
18
|
+
require no write permission and never modify repository source.
|
|
19
|
+
- **Disposable-workspace execution.** When checks execute, they run inside a
|
|
20
|
+
throwaway copy of the repository, never the original working tree, so a build
|
|
21
|
+
or test cannot mutate the real checkout (no stray artifacts).
|
|
22
|
+
- **Bounded execution.** Any executed check is opt-in (per run and globally) and
|
|
23
|
+
runs under a hard wall-clock bound that terminates the entire process group,
|
|
24
|
+
so a spawned child cannot outlive the bound.
|
|
25
|
+
- **Known limitation.** Execution is not yet OS-sandboxed — network egress, user,
|
|
26
|
+
and resource isolation (spec section 12) are planned but not implemented. Treat
|
|
27
|
+
`--execute` on untrusted repositories accordingly; it is off by default.
|
|
28
|
+
- **Non-bypassable safety floor.** No configuration layer may loosen verifier
|
|
29
|
+
isolation, remove protected paths (`.reanimator.yml`, workflows, CODEOWNERS),
|
|
30
|
+
or enable automatic merge in v1.
|
|
31
|
+
- **Anti-gaming.** A task that modifies the rules of proof cannot use those
|
|
32
|
+
modified rules to approve itself.
|
data/exe/reanimator
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reanimator
|
|
4
|
+
# A single statement that must remain true for a repository, together with
|
|
5
|
+
# how it is proven (specification section 09). The acting agent may attempt
|
|
6
|
+
# the work but may never grade itself, so a criterion names an *evaluator*
|
|
7
|
+
# rather than trusting a self-reported result.
|
|
8
|
+
class AcceptanceCriterion
|
|
9
|
+
# Effect on review and autonomy. Changes to verifier definitions, security
|
|
10
|
+
# posture, protected paths, or merge authority are :elevated and receive
|
|
11
|
+
# extra scrutiny even when technically allowed.
|
|
12
|
+
RISK_CLASSES = %i[low normal elevated].freeze
|
|
13
|
+
|
|
14
|
+
# @return [Symbol] stable identity used in reports and history
|
|
15
|
+
attr_reader :id
|
|
16
|
+
# @return [String] human-readable statement of what must remain true
|
|
17
|
+
attr_reader :claim
|
|
18
|
+
# @return [Symbol] native evaluator name, GitHub check, or :command
|
|
19
|
+
attr_reader :evaluator
|
|
20
|
+
# @return [String, nil] the command to run when evaluator is :command
|
|
21
|
+
attr_reader :run
|
|
22
|
+
# @return [String, nil] target for evaluators that need one (e.g. /up)
|
|
23
|
+
attr_reader :target
|
|
24
|
+
# @return [Boolean] whether failure blocks advancement
|
|
25
|
+
attr_reader :required
|
|
26
|
+
# @return [Hash] supported environment, paths, services, or family scope
|
|
27
|
+
attr_reader :scope
|
|
28
|
+
# @return [String, nil] how recently evidence must have been produced
|
|
29
|
+
attr_reader :freshness
|
|
30
|
+
# @return [String] who may change or waive the criterion
|
|
31
|
+
attr_reader :owner
|
|
32
|
+
# @return [Symbol] one of {RISK_CLASSES}
|
|
33
|
+
attr_reader :risk_class
|
|
34
|
+
# @return [Provenance, nil] where this criterion came from, when discovered
|
|
35
|
+
attr_reader :provenance
|
|
36
|
+
|
|
37
|
+
def initialize(id:, claim:, evaluator:, run: nil, target: nil, required: true,
|
|
38
|
+
scope: {}, freshness: nil, owner: "repository", risk_class: :normal, provenance: nil)
|
|
39
|
+
@id = id.to_sym
|
|
40
|
+
@claim = claim
|
|
41
|
+
@evaluator = evaluator.to_sym
|
|
42
|
+
@run = run
|
|
43
|
+
@target = target
|
|
44
|
+
@required = required
|
|
45
|
+
@scope = scope
|
|
46
|
+
@freshness = freshness
|
|
47
|
+
@owner = owner
|
|
48
|
+
@risk_class = risk_class.to_sym
|
|
49
|
+
unless RISK_CLASSES.include?(@risk_class)
|
|
50
|
+
raise UnknownError, "Unknown risk class #{risk_class.inspect}. Known: #{RISK_CLASSES.join(", ")}."
|
|
51
|
+
end
|
|
52
|
+
@provenance = provenance
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def required? = @required
|
|
56
|
+
|
|
57
|
+
# Verifier and contract-touching criteria carry elevated risk by policy.
|
|
58
|
+
def elevated? = risk_class == :elevated
|
|
59
|
+
|
|
60
|
+
def to_h
|
|
61
|
+
{
|
|
62
|
+
id: id,
|
|
63
|
+
claim: claim,
|
|
64
|
+
evaluator: evaluator,
|
|
65
|
+
run: run,
|
|
66
|
+
target: target,
|
|
67
|
+
required: required,
|
|
68
|
+
scope: scope,
|
|
69
|
+
freshness: freshness,
|
|
70
|
+
owner: owner,
|
|
71
|
+
risk_class: risk_class
|
|
72
|
+
}.compact
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Reanimator
|
|
4
|
+
# A credentialed provider integration available to an account or organization
|
|
5
|
+
# (specification section 08 credential model). Credentials are connected at
|
|
6
|
+
# the account level and **never committed to a repository** - this object
|
|
7
|
+
# holds only a *reference* to where the secret lives (an environment variable
|
|
8
|
+
# name), plus the policy envelope the connection operates within: which
|
|
9
|
+
# repositories it may touch, its code-egress posture, its budget, models, and
|
|
10
|
+
# execution mode.
|
|
11
|
+
class AgentConnection
|
|
12
|
+
# Code-egress postures a connection can declare.
|
|
13
|
+
EGRESS = %i[none approved_providers any].freeze
|
|
14
|
+
EXECUTION_MODES = %i[hosted local].freeze
|
|
15
|
+
|
|
16
|
+
attr_reader :provider, :display_name, :credentials_ref, :permitted_repositories,
|
|
17
|
+
:code_egress, :max_cost_usd, :models, :tools, :execution_mode
|
|
18
|
+
|
|
19
|
+
def initialize(provider:, credentials_ref: nil, display_name: nil, permitted_repositories: ["*"],
|
|
20
|
+
code_egress: :approved_providers, max_cost_usd: 8.0, models: [], tools: [], execution_mode: :hosted)
|
|
21
|
+
@provider = provider.to_sym
|
|
22
|
+
@display_name = display_name || @provider.to_s
|
|
23
|
+
@credentials_ref = credentials_ref
|
|
24
|
+
@permitted_repositories = Array(permitted_repositories)
|
|
25
|
+
@code_egress = code_egress.to_sym
|
|
26
|
+
@max_cost_usd = max_cost_usd
|
|
27
|
+
@models = Array(models)
|
|
28
|
+
@tools = Array(tools)
|
|
29
|
+
@execution_mode = execution_mode.to_sym
|
|
30
|
+
validate!
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# A local, self-hosted connection: no credentials, no egress, code and
|
|
34
|
+
# execution stay on user-controlled runners (section 12 "local" posture).
|
|
35
|
+
def self.local(**overrides)
|
|
36
|
+
new(provider: :local, code_egress: :none, execution_mode: :local,
|
|
37
|
+
display_name: "Local rule-based agent", **overrides)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def local? = execution_mode == :local || provider == :local
|
|
41
|
+
|
|
42
|
+
# Whether this connection is permitted to act on a repository full name.
|
|
43
|
+
def permits?(full_name)
|
|
44
|
+
permitted_repositories.any? { |glob| File.fnmatch?(glob, full_name.to_s, File::FNM_PATHNAME) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_h
|
|
48
|
+
{
|
|
49
|
+
provider: provider, display_name: display_name, credentials_ref: credentials_ref,
|
|
50
|
+
permitted_repositories: permitted_repositories, code_egress: code_egress,
|
|
51
|
+
max_cost_usd: max_cost_usd, models: models, execution_mode: execution_mode
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def validate!
|
|
58
|
+
raise UnknownError, "Unknown code_egress #{code_egress.inspect}." unless EGRESS.include?(code_egress)
|
|
59
|
+
raise UnknownError, "Unknown execution_mode #{execution_mode.inspect}." unless EXECUTION_MODES.include?(execution_mode)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|