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.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +207 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +483 -0
  5. data/SECURITY.md +32 -0
  6. data/exe/reanimator +6 -0
  7. data/lib/reanimator/acceptance_criterion.rb +75 -0
  8. data/lib/reanimator/agent_connection.rb +62 -0
  9. data/lib/reanimator/agent_profile.rb +51 -0
  10. data/lib/reanimator/agents/adapter.rb +49 -0
  11. data/lib/reanimator/agents/capability.rb +29 -0
  12. data/lib/reanimator/agents/constraints.rb +57 -0
  13. data/lib/reanimator/agents/hosted.rb +38 -0
  14. data/lib/reanimator/agents/local.rb +46 -0
  15. data/lib/reanimator/agents/patch.rb +60 -0
  16. data/lib/reanimator/agents/repair_task.rb +51 -0
  17. data/lib/reanimator/agents/result.rb +42 -0
  18. data/lib/reanimator/agents/strategies/arithmetic_fix.rb +51 -0
  19. data/lib/reanimator/agents/strategies/base.rb +26 -0
  20. data/lib/reanimator/agents.rb +38 -0
  21. data/lib/reanimator/autonomy_level.rb +41 -0
  22. data/lib/reanimator/baseline.rb +106 -0
  23. data/lib/reanimator/baseline_runner.rb +81 -0
  24. data/lib/reanimator/bounded_command.rb +87 -0
  25. data/lib/reanimator/classifier.rb +89 -0
  26. data/lib/reanimator/cli.rb +273 -0
  27. data/lib/reanimator/cohort.rb +61 -0
  28. data/lib/reanimator/commissioning/fixture.rb +69 -0
  29. data/lib/reanimator/commissioning/report.rb +45 -0
  30. data/lib/reanimator/commissioning/test.rb +145 -0
  31. data/lib/reanimator/confidence.rb +66 -0
  32. data/lib/reanimator/configuration.rb +35 -0
  33. data/lib/reanimator/contract_file.rb +116 -0
  34. data/lib/reanimator/contract_snapshot.rb +188 -0
  35. data/lib/reanimator/criteria_stack.rb +91 -0
  36. data/lib/reanimator/criterion_result.rb +70 -0
  37. data/lib/reanimator/detectors/base.rb +49 -0
  38. data/lib/reanimator/detectors/bundler.rb +68 -0
  39. data/lib/reanimator/detectors/commands.rb +112 -0
  40. data/lib/reanimator/detectors/fork.rb +25 -0
  41. data/lib/reanimator/detectors/github_actions.rb +68 -0
  42. data/lib/reanimator/detectors/operational_proof.rb +48 -0
  43. data/lib/reanimator/detectors/rails_app.rb +51 -0
  44. data/lib/reanimator/detectors/relationships.rb +46 -0
  45. data/lib/reanimator/detectors/ruby_gem.rb +40 -0
  46. data/lib/reanimator/detectors/security_controls.rb +36 -0
  47. data/lib/reanimator/discovered_fact.rb +99 -0
  48. data/lib/reanimator/discovery.rb +48 -0
  49. data/lib/reanimator/discovery_result.rb +89 -0
  50. data/lib/reanimator/drift_detector.rb +227 -0
  51. data/lib/reanimator/drift_report.rb +86 -0
  52. data/lib/reanimator/effective_contract.rb +108 -0
  53. data/lib/reanimator/effective_policy_resolver.rb +116 -0
  54. data/lib/reanimator/errors.rb +21 -0
  55. data/lib/reanimator/evaluators/base.rb +55 -0
  56. data/lib/reanimator/evaluators/command.rb +34 -0
  57. data/lib/reanimator/evaluators/documentation_check.rb +25 -0
  58. data/lib/reanimator/evaluators/http_smoke.rb +19 -0
  59. data/lib/reanimator/evaluators/repository_integrity.rb +22 -0
  60. data/lib/reanimator/evaluators/security_evidence.rb +24 -0
  61. data/lib/reanimator/evaluators/support_matrix.rb +25 -0
  62. data/lib/reanimator/evaluators.rb +41 -0
  63. data/lib/reanimator/family/gate.rb +40 -0
  64. data/lib/reanimator/family/gate_result.rb +46 -0
  65. data/lib/reanimator/family/gate_runner.rb +91 -0
  66. data/lib/reanimator/family.rb +89 -0
  67. data/lib/reanimator/family_inference.rb +113 -0
  68. data/lib/reanimator/git.rb +72 -0
  69. data/lib/reanimator/isolation.rb +36 -0
  70. data/lib/reanimator/language_inference.rb +43 -0
  71. data/lib/reanimator/lifecycle_intent.rb +93 -0
  72. data/lib/reanimator/obligation.rb +35 -0
  73. data/lib/reanimator/permission_model.rb +61 -0
  74. data/lib/reanimator/portfolio.rb +68 -0
  75. data/lib/reanimator/portfolio_drift.rb +120 -0
  76. data/lib/reanimator/profile_inference.rb +37 -0
  77. data/lib/reanimator/profiles.rb +110 -0
  78. data/lib/reanimator/proof_of_life.rb +45 -0
  79. data/lib/reanimator/proposed_change.rb +55 -0
  80. data/lib/reanimator/provenance.rb +39 -0
  81. data/lib/reanimator/relationship.rb +62 -0
  82. data/lib/reanimator/repair_run.rb +146 -0
  83. data/lib/reanimator/reports/agents.rb +28 -0
  84. data/lib/reanimator/reports/baseline.rb +73 -0
  85. data/lib/reanimator/reports/commissioning.rb +48 -0
  86. data/lib/reanimator/reports/contract.rb +70 -0
  87. data/lib/reanimator/reports/discovery.rb +38 -0
  88. data/lib/reanimator/reports/drift.rb +63 -0
  89. data/lib/reanimator/reports/families.rb +41 -0
  90. data/lib/reanimator/reports/family_gates.rb +41 -0
  91. data/lib/reanimator/reports/github_check.rb +45 -0
  92. data/lib/reanimator/reports/inventory.rb +37 -0
  93. data/lib/reanimator/reports/permissions.rb +32 -0
  94. data/lib/reanimator/reports/portfolio_baseline.rb +68 -0
  95. data/lib/reanimator/reports/portfolio_drift.rb +107 -0
  96. data/lib/reanimator/reports/run.rb +52 -0
  97. data/lib/reanimator/reports/snapshot.rb +58 -0
  98. data/lib/reanimator/reports.rb +36 -0
  99. data/lib/reanimator/repository.rb +96 -0
  100. data/lib/reanimator/repository_metadata.rb +78 -0
  101. data/lib/reanimator/repository_state.rb +48 -0
  102. data/lib/reanimator/routing/decision.rb +35 -0
  103. data/lib/reanimator/routing/router.rb +122 -0
  104. data/lib/reanimator/run.rb +48 -0
  105. data/lib/reanimator/safety_floor.rb +81 -0
  106. data/lib/reanimator/security_control.rb +87 -0
  107. data/lib/reanimator/security_posture.rb +80 -0
  108. data/lib/reanimator/snapshot_store.rb +78 -0
  109. data/lib/reanimator/task_class.rb +63 -0
  110. data/lib/reanimator/version.rb +5 -0
  111. data/lib/reanimator/vitality_profile.rb +44 -0
  112. data/lib/reanimator/waiver.rb +74 -0
  113. data/lib/reanimator.rb +214 -0
  114. 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,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "reanimator"
5
+
6
+ Reanimator::CLI.start(ARGV)
@@ -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