automatic 26.08 → 26.09

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +80 -46
  3. data/VERSION +1 -1
  4. data/assets/siteinfo/items_all.json +60300 -52138
  5. data/automatic.gemspec +4 -3
  6. data/bin/automatic +1 -1
  7. data/doc/AI_TUTORIAL.md +64 -40
  8. data/doc/BASIC_DESIGN.md +31 -0
  9. data/doc/DEPLOYMENT.md +53 -47
  10. data/doc/PLUGINS.md +231 -91
  11. data/doc/POLICY.md +149 -44
  12. data/doc/QUICKSTART.md +19 -15
  13. data/doc/RELEASING.md +20 -7
  14. data/doc/REQUIREMENTS.md +40 -10
  15. data/doc/VERSIONS +112 -54
  16. data/lib/automatic/cli.rb +40 -18
  17. data/lib/automatic/environment.rb +1 -1
  18. data/lib/automatic/feed_maker.rb +57 -18
  19. data/lib/automatic/feed_parser.rb +1 -1
  20. data/lib/automatic/http.rb +1 -1
  21. data/lib/automatic/log.rb +1 -1
  22. data/lib/automatic/pipeline.rb +15 -5
  23. data/lib/automatic/recipe.rb +45 -1
  24. data/lib/automatic/version.rb +3 -3
  25. data/lib/automatic.rb +18 -3
  26. data/plugins/custom_feed/web.rb +1 -1
  27. data/plugins/filter/absolute_uri.rb +1 -1
  28. data/plugins/filter/batch.rb +97 -0
  29. data/plugins/filter/claude.rb +1 -1
  30. data/plugins/filter/clear.rb +1 -1
  31. data/plugins/filter/description_link.rb +20 -3
  32. data/plugins/filter/full_feed.rb +28 -16
  33. data/plugins/filter/gemini.rb +1 -1
  34. data/plugins/filter/ignore.rb +1 -1
  35. data/plugins/filter/image.rb +1 -1
  36. data/plugins/filter/image_source.rb +20 -3
  37. data/plugins/filter/join.rb +4 -6
  38. data/plugins/filter/kimi.rb +216 -0
  39. data/plugins/filter/limit.rb +57 -0
  40. data/plugins/filter/open_ai.rb +1 -1
  41. data/plugins/filter/present.rb +75 -0
  42. data/plugins/filter/sakura_ai.rb +1 -1
  43. data/plugins/filter/sanitize.rb +1 -1
  44. data/plugins/filter/sort.rb +1 -1
  45. data/plugins/filter/tumblr_resize.rb +1 -1
  46. data/plugins/notify/ikachan.rb +1 -1
  47. data/plugins/provide/fluentd.rb +1 -1
  48. data/plugins/publish/amazon_s3.rb +9 -3
  49. data/plugins/publish/console.rb +1 -1
  50. data/plugins/publish/fluentd.rb +1 -1
  51. data/plugins/publish/hatena_bookmark.rb +1 -1
  52. data/plugins/publish/markdown.rb +1 -1
  53. data/plugins/publish/memcached.rb +1 -1
  54. data/plugins/store/digest.rb +1 -1
  55. data/plugins/store/file.rb +11 -3
  56. data/plugins/store/full_text.rb +9 -13
  57. data/plugins/store/permalink.rb +1 -1
  58. data/plugins/subscription/feed.rb +1 -1
  59. data/plugins/subscription/link.rb +1 -1
  60. data/plugins/subscription/text.rb +13 -4
  61. data/plugins/subscription/tumblr.rb +1 -1
  62. data/plugins/subscription/xml.rb +1 -1
  63. metadata +6 -2
data/doc/POLICY.md CHANGED
@@ -22,10 +22,22 @@ The Invariants below decide over the rest of it.
22
22
 
23
23
  ### 1.1 Purpose and scope
24
24
 
25
+ Automatic Ruby is neither system infrastructure nor a purpose-built
26
+ application. It is a general-purpose composition framework. Its purpose is to
27
+ keep a small composition mechanism stable enough that independent plugins can
28
+ be combined freely, without turning the framework into a host baseline, a
29
+ general workflow engine or a collection of hard-coded applications.
30
+
25
31
  - **Automatic Ruby is a framework, and the framework is the small part.** It
26
32
  loads a Recipe, finds classes by name and calls them in order. Nearly every
27
33
  change belongs in a plugin, and a change that adds domain knowledge to the
28
34
  framework needs a reason beyond convenience.
35
+ - **Generality comes from composition, not from accumulating framework
36
+ features.** A concrete use case that needs branching, transactional
37
+ orchestration, multi-user state, permissions, a strongly coupled domain
38
+ model or another responsibility that does not fit a short linear pipeline is
39
+ not automatically a reason to enlarge the framework. A purpose-built
40
+ application is often the correct boundary for that complexity.
29
41
  - **It is one person's tooling, run unattended from `cron`.** It is not a
30
42
  service, not multi-tenant and not a product. That premise decides several
31
43
  rules below that would otherwise look lax — the Recipe being trusted, chiefly.
@@ -55,24 +67,42 @@ general policy would otherwise ask for.
55
67
  4. **A dependency needed by one plugin is not a dependency of the framework.**
56
68
  Installing the gem must not pull in an SDK for a service the operator does
57
69
  not use. See section 9.
58
- 5. **A failure is never silent.** No `rescue` that returns an empty pipeline as
70
+ 5. **Composability is an architectural invariant, not an optimization goal.**
71
+ The small framework, independent plugins, one pipeline representation and
72
+ Recipe-level ordering are the identity of the system. They are not traded
73
+ away merely for convenience, central validation, richer orchestration or a
74
+ more application-like design.
75
+ 6. **A failure is never silent.** No `rescue` that returns an empty pipeline as
59
76
  if nothing happened, and no run that exits zero after something went wrong.
60
- 6. **The default test suite reaches no network and needs no credential.** None
77
+ 7. **The default test suite reaches no network and needs no credential.** None
61
78
  is configured in CI.
62
- 7. **A plugin that cannot work is never simulated into working.** Stubbing a
79
+ 8. **A plugin that cannot work is never simulated into working.** Stubbing a
63
80
  dead service to make a test pass, or to make the catalogue look better, is
64
81
  forbidden outright. Removing the plugin is the correct outcome. See
65
82
  section 4.
66
- 8. **No credential is committed**, in a Recipe, an example, a fixture or a test,
83
+ 9. **No credential is committed**, in a Recipe, an example, a fixture or a test,
67
84
  and none is written to the log.
68
- 9. **Historical release history is not rewritten.** Past versions and their
69
- dates in [`VERSIONS`](VERSIONS) are a record, not a thing to tidy.
70
- 10. **The licence is GPL version 3 or LGPL version 3.** Automatic Ruby is
85
+ 10. **Historical release history is not rewritten.** Past versions and their
86
+ dates in [`VERSIONS`](VERSIONS) are a record, not a thing to tidy.
87
+ 11. **The licence is GPL version 3 or LGPL version 3.** Automatic Ruby is
71
88
  dual-licensed, and a user may choose either license at their discretion. New
72
89
  files use the same dual license.
73
90
 
74
91
  ### 1.3 Design philosophy
75
92
 
93
+ The repository is maintained in three layers, and they do not receive the same
94
+ degree of conservatism.
95
+
96
+ - **Core contracts receive the strongest compatibility protection:** the
97
+ Recipe format, plugin contract, pipeline shape, plugin lookup and override
98
+ semantics, execution order and established CLI contract.
99
+ - **Framework implementation may improve within those contracts:** the CLI,
100
+ loader, pipeline implementation, logging and shared helpers are not frozen
101
+ merely because they are old.
102
+ - **Plugins are replaceable components:** they may be added, repaired,
103
+ replaced or removed as their external systems and purposes change. A dead
104
+ integration is not retained by simulation.
105
+
76
106
  - Simple comes before clever. Common work should fit in a short Recipe, and a
77
107
  new abstraction needs a concrete problem the existing plugin contract cannot
78
108
  solve.
@@ -90,7 +120,43 @@ general policy would otherwise ask for.
90
120
  - Old is not a defect. Neither is unfashionable. A defect is something that does
91
121
  not work, is not understood, or cannot be tested.
92
122
 
93
- ### 1.4 The parts and the direction of dependency
123
+ ### 1.4 Change discipline and maintenance boundary
124
+
125
+ Automatic Ruby does not inherit every maintenance rule that is appropriate
126
+ for long-lived host infrastructure. Its core contracts deserve comparable
127
+ caution because external Recipes and plugins depend on them, but the whole
128
+ repository is not treated as immutable infrastructure.
129
+
130
+ Finding a possible safety, portability, maintainability, cleanup,
131
+ modernization or refactoring improvement does not by itself authorize an
132
+ implementation change. A change is made when that change is part of the stated
133
+ work, not merely because an opportunity was noticed while editing nearby code.
134
+
135
+ Established behaviour is evaluated by layer. A Recipe or plugin contract that
136
+ has been used for years has strong compatibility value. A framework internal
137
+ implementation detail may be improved while preserving those contracts. A
138
+ shipped plugin may be replaced or removed when its external service or
139
+ interface is gone and the plugin can no longer perform real work.
140
+
141
+ Safety mechanisms are means, not goals. A guard, validation layer, sandbox,
142
+ permission model, retry layer or other defensive mechanism is justified by the
143
+ risk it materially reduces and by whether that benefit outweighs added
144
+ complexity, new failure modes and maintenance cost. This project is trusted
145
+ single-user tooling; controls appropriate to an untrusted multi-user service
146
+ are not added merely because they are generally considered safer.
147
+
148
+ Long-running operational history is evidence, especially for Recipe semantics,
149
+ plugin contracts and cron pipelines. It is not a reason to preserve a broken
150
+ integration or to freeze internal implementation that can change without
151
+ altering those contracts.
152
+
153
+ A change that would introduce branching, loops, fan-out, transactional
154
+ orchestration, multi-user state, a central plugin registry, domain knowledge in
155
+ the framework or another responsibility that changes the composition model is
156
+ an architecture change. It is not reached as a routine refactoring or as a
157
+ convenient way to satisfy one plugin or one Recipe.
158
+
159
+ ### 1.5 The parts and the direction of dependency
94
160
 
95
161
  ```text
96
162
  bin/automatic
@@ -127,7 +193,7 @@ Dependency points one way and there is no edge back up:
127
193
  to two, the boundary is wrong and is corrected, rather than the code being
128
194
  written across it.
129
195
 
130
- ### 1.5 The plugin boundary
196
+ ### 1.6 The plugin boundary
131
197
 
132
198
  - The framework's knowledge of a plugin is: its name, its file's location, its
133
199
  constructor's two arguments and its `run` method. It does not know a plugin's
@@ -142,7 +208,7 @@ Dependency points one way and there is no edge back up:
142
208
  - Shared plugin code that the framework does not use stays under `plugins/`, not
143
209
  in `lib/`. `plugins/store/database.rb` is where it is for that reason.
144
210
 
145
- ### 1.6 Configuration
211
+ ### 1.7 Configuration
146
212
 
147
213
  - A Recipe is the whole of a job's configuration. There is no second file, no
148
214
  configuration directory, and no environment-variable settings.
@@ -161,7 +227,7 @@ Dependency points one way and there is no edge back up:
161
227
  change is a setting; a value that is part of what the plugin means stays in
162
228
  the code.
163
229
 
164
- ### 1.7 Error handling
230
+ ### 1.8 Error handling
165
231
 
166
232
  - **The framework catches nothing from a plugin.** A plugin that raises ends the
167
233
  run, for the reason given in `REQUIREMENTS.md` section 12. Adding a blanket
@@ -181,8 +247,15 @@ Dependency points one way and there is no edge back up:
181
247
  and its backtrace is wanted.
182
248
  - **The library never calls `exit` or `abort`.** Exit status is decided at the
183
249
  process entry point, from the value `Automatic::CLI.run` returns.
250
+ - The result of an operation, whether later work continues, and whether a
251
+ message is emitted are separate decisions. This does not change the
252
+ framework rule above: a plugin exception still ends the run, and plugin retry
253
+ behavior remains owned by the plugin.
254
+ - A required condition whose absence makes correct completion impossible is a
255
+ failure, not a warning used to keep the run moving. A normal guard, no-op, or
256
+ inapplicable path is not a failure merely because it performs no work.
184
257
 
185
- ### 1.8 Logging and output
258
+ ### 1.9 Logging and output
186
259
 
187
260
  - **A library file never calls `puts`, `print` or `warn`.** It logs, through
188
261
  `Automatic::Log`.
@@ -190,13 +263,18 @@ Dependency points one way and there is no edge back up:
190
263
  version, subcommand results — to standard output.
191
264
  - A plugin whose purpose is to write to the terminal holds its output object in
192
265
  an instance variable defaulting to `$stdout`, so that a test can substitute it.
193
- - `info` says what a step did, `warn` says what was skipped, `error` says what
194
- failed. An error that was rescued is logged at `warn` or `error`, never at
195
- `info`.
266
+ - `info` records useful normal progress. `warn` is reserved for a degraded or
267
+ otherwise abnormal but recoverable condition the operator should know about;
268
+ a normal guard, no-op, or inapplicable path may be silent and is not a warning
269
+ merely because work was skipped. `error` says what failed. An error that was
270
+ rescued is logged at `warn` or `error`, never at `info`.
271
+ - Do not emit a line merely to prove that a normal branch was taken, and do not
272
+ duplicate a failure message at multiple layers when one responsible layer
273
+ already reports it through the established interface.
196
274
  - No log line contains a credential. A plugin that logs its own settings
197
275
  wholesale is a defect.
198
276
 
199
- ### 1.9 Filesystem access
277
+ ### 1.10 Filesystem access
200
278
 
201
279
  - The framework touches two roots: the installation directory and
202
280
  `~/.automatic`. Nothing else, and no absolute path elsewhere appears in the
@@ -211,7 +289,7 @@ Dependency points one way and there is no edge back up:
211
289
  extended, and nothing else acquires the ability to delete an operator's data.
212
290
  - A plugin writes only where its settings tell it to.
213
291
 
214
- ### 1.10 Network access
292
+ ### 1.11 Network access
215
293
 
216
294
  - **The framework reaches nothing.** No update check, no telemetry, no
217
295
  phone-home. Every request is a plugin's, on a Recipe's instruction.
@@ -233,7 +311,7 @@ Dependency points one way and there is no edge back up:
233
311
  - Every request has a connect and a read timeout. An unattended run that hangs
234
312
  is a failure mode with no upper bound on its cost.
235
313
 
236
- ### 1.11 Security and credentials
314
+ ### 1.12 Security and credentials
237
315
 
238
316
  - **A Recipe is trusted local configuration**, equivalent to a shell script the
239
317
  operator wrote. This is the trust boundary, it is stated in
@@ -251,7 +329,7 @@ Dependency points one way and there is no edge back up:
251
329
  - A change that touches authentication says in its `VERSIONS` entry what it
252
330
  changed.
253
331
 
254
- ### 1.12 Judging a change
332
+ ### 1.13 Judging a change
255
333
 
256
334
  - Does it keep the Recipe format and the plugin contract, and if not, is that
257
335
  deliberate and recorded?
@@ -261,6 +339,15 @@ Dependency points one way and there is no edge back up:
261
339
  - Is it the smallest change that does the job?
262
340
  - Does a test say it works, and does the default suite still need no network?
263
341
  - Do the documents still match the code, in the same commit?
342
+ - Which layer is changing: a core contract, framework implementation or a
343
+ replaceable plugin?
344
+ - Is a framework change solving a framework problem, or absorbing
345
+ domain-specific behaviour that belongs in a plugin or an application?
346
+ - Does a new dependency remain local to the plugin that needs it?
347
+ - Does a proposed safety mechanism materially reduce a relevant risk without
348
+ imposing disproportionate complexity on trusted single-user tooling?
349
+ - Does the change preserve the small linear composition model, and if not, has
350
+ an explicit architecture change actually been chosen?
264
351
 
265
352
  ---
266
353
 
@@ -306,7 +393,7 @@ appear in the order shown below. An executable uses this canonical form:
306
393
  #!/usr/bin/env ruby
307
394
  # -*- coding: utf-8 -*-
308
395
  # Name:: automatic
309
- # Author: id774 (More info: http://id774.net)
396
+ # Author: id774 (More info: https://id774.net)
310
397
  # Source Code:: https://github.com/id774/automaticruby
311
398
  # License:: The GPL version 3, or LGPL version 3 (Dual License).
312
399
  # Contact:: idnanashi@gmail.com
@@ -335,7 +422,7 @@ appear in the order shown below. An executable uses this canonical form:
335
422
  - A `Description::` line may be added below `Name` when the file's purpose is
336
423
  not obvious from its name. New plugins should have one.
337
424
  - The magic encoding comment is redundant on the supported Rubies. It is left in
338
- place in existing files, because removing it from forty files is a diff with
425
+ place in existing files, because removing it across existing files is a diff with
339
426
  no benefit, and it is not required in a new file.
340
427
 
341
428
  Files do not carry a per-file version history. This repository versions at the
@@ -397,7 +484,7 @@ repository level only; see section 10.
397
484
  - **Converting the pipeline into some other representation is a `Publish`
398
485
  plugin's work, and the result stays inside the plugin.** It is serialized at
399
486
  the boundary, written to the destination, and never passed along the pipeline
400
- or made known to the framework, which keeps Invariant 2 and section 1.5
487
+ or made known to the framework, which keeps Invariant 2 and section 1.6
401
488
  intact. Producing a portable document — one a person can read, ordinary tools
402
489
  can process and another program can be given, with no service behind it — is
403
490
  a destination like any other and belongs in that category.
@@ -423,7 +510,7 @@ Plugins outlive the services they talk to. The policy for what happens then:
423
510
  "does not work and never will"; a plugin in that position is removed.
424
511
  - **A dead integration is never faked into life.** No stub of a shut-down
425
512
  service, no mock that makes an integration look alive, no test that asserts
426
- against a simulation. This is Invariant 7 and it has no exceptions. If a
513
+ against a simulation. This is Invariant 8 and it has no exceptions. If a
427
514
  plugin can only be made to look supported by simulating what it talks to,
428
515
  what it needs is deletion, not a double.
429
516
  - **Unsupported code is not kept for preservation.** Git history holds every
@@ -472,7 +559,7 @@ Plugins outlive the services they talk to. The policy for what happens then:
472
559
  - **RSpec**, under `spec/`, mirroring the source tree: `spec/lib/` for the
473
560
  framework and `spec/plugins/<category>/` for plugins.
474
561
  - **The default suite reaches no network and needs no credential.** This is
475
- Invariant 6. A spec that would is not written; the integration Recipes under
562
+ Invariant 7. A spec that would is not written; the integration Recipes under
476
563
  `test/integration/` are where that belongs, and they are run by hand.
477
564
  - A framework spec covers the loader, the Recipe, the pipeline, the log and the
478
565
  CLI's exit statuses.
@@ -623,6 +710,12 @@ declared as runtime dependencies**. An operator who uses that plugin installs
623
710
  the gem. This is Invariant 4, and it is why installing this gem does not install
624
711
  an AWS SDK.
625
712
 
713
+ Dependency minimization is not a repository-wide contest to use the fewest
714
+ libraries. A plugin may be rich when its purpose requires it. The rule is that
715
+ the cost stays with the plugin that needs it: a parser, database driver, SDK or
716
+ other domain-specific dependency must not become a framework dependency merely
717
+ because one plugin uses it.
718
+
626
719
  The permanent rules of the split:
627
720
 
628
721
  - **A core dependency is one the framework itself has.** A plugin's dependency
@@ -752,10 +845,19 @@ agree rather than asserting a literal.
752
845
  file is the record of released versions, not of the construction that precedes
753
846
  the first of them, and its first entry is written when that release is made.
754
847
  - **A series of changes made on one day is one version**, not several. Do not
755
- split a day's work across version numbers.
756
- - **A version that actually existed is never merged into another**, even when it
757
- shares a date with one. `14.10.0` and `14.10.1` were both released on
758
- 2014-10-23 and both stay.
848
+ split a day's work across version numbers. This rule has no exception:
849
+ separate commits, pull requests, independent features, bug fixes, security
850
+ fixes, compatibility changes, breaking changes, or release units do not
851
+ permit a second version number on the same calendar date. A same-date change
852
+ joins that day's single entry; independence decides bullet grouping only.
853
+ - **If today's release has already been published, a correction may be
854
+ prepared but no second version, tag or package release may be published
855
+ until a later calendar date.** A published version is never overwritten,
856
+ reused or moved, and its correction is a later-dated version.
857
+ - **Historical same-date versions remain recorded as historical facts.**
858
+ `14.10.0` and `14.10.1` were both released on 2014-10-23 and both stay in
859
+ `doc/VERSIONS`, but they violate the current one-version-per-day rule and
860
+ are not precedent for future releases.
759
861
  - A documentation-only change takes no entry unless its scale makes it worth one
760
862
  line saying so.
761
863
  - Git tags carry the release version, and are created only when a release is
@@ -779,21 +881,21 @@ the version history records what it amounts to.
779
881
  - Each entry opens with `vX.YY (YYYY-MM-DD)`, or `vX.YY (Release Date: TBD)`
780
882
  while unreleased, underlined with `-`, followed by one `-` bullet per change.
781
883
  Newest first. UTF-8.
782
- - **One coherent change is one bullet on one physical line.** The entry is a
783
- list meant to be scanned, and a wrapped bullet costs it that: the eye no
784
- longer finds the changes by counting lines, and a diff no longer shows one
785
- added line per added change.
786
- - This is a deliberate exception to the wrapping the other plain text documents
787
- follow. Do not rewrap `doc/VERSIONS` to 80 columns, and do not report a long
788
- bullet there as a defect.
789
- - Aim for about 100 columns. A bullet carrying file names, module names, setting
790
- names or plugin names may run to about 120, or past it when the names it needs
791
- are that long. These figures prompt a reread, they are not a limit to enforce.
792
- A bullet that is long because the change is long is correct.
793
- - **When a bullet runs long, abstract it; never break it across lines.** Drop
794
- the implementation detail, the example, the reason and the secondary effect,
795
- and state what the change is. Keep what a reader cannot reconstruct without
796
- it: what changed, what is now observably different, what it does to
884
+ - **One coherent change is one bullet, at most two physical lines.** A single
885
+ line at or under 80 columns is preferred whenever practical. This is an
886
+ explicit limit, not a prompt to reread: a bullet that runs past two lines,
887
+ or a single line that runs past 80 columns without necessity, must be
888
+ shortened. The entry is a list meant to be scanned, and a bullet that grows
889
+ past this limit costs it that: the eye no longer finds the changes by
890
+ counting lines, and a diff no longer shows a small, bounded edit.
891
+ - A bullet carrying file names, module names, setting names or plugin names
892
+ may pass 80 columns on its one or two lines when those names cannot be
893
+ shortened without losing meaning. The two-line ceiling still applies.
894
+ - **When a bullet runs long, abstract it first.** Drop the implementation
895
+ detail, the example, the reason and the secondary effect, and state what
896
+ the change is. Wrap onto the second line only when the abstracted bullet
897
+ still exceeds 80 columns. Keep what a reader cannot reconstruct without it:
898
+ what changed, what is now observably different, what it does to
797
899
  compatibility, what it does to security, and the identifiers someone would
798
900
  search for.
799
901
  - Changes serving one purpose are described together even when they touch
@@ -806,7 +908,10 @@ the version history records what it amounts to.
806
908
  - Order within a version serves the reader, not the commit history.
807
909
  - Released entries retain their substantive history even when their wording or
808
910
  level of detail predates these rules.
809
- - `doc/VERSIONS` carries these guidelines again at its foot.
911
+ - `doc/VERSIONS` carries these guidelines again at its foot, and an entry
912
+ written into it follows the limit recorded there.
913
+ - The first entry, at the lowest version `doc/VERSIONS` reaches, reads only
914
+ `Initial release.` and nothing else.
810
915
 
811
916
  ### 10.5 The historical record
812
917
 
data/doc/QUICKSTART.md CHANGED
@@ -1,12 +1,15 @@
1
1
  # Quick Start
2
2
 
3
- This guide takes four public pages through one short Automatic Ruby pipeline and
4
- leaves what they publish as one Markdown document. It needs no account, no
5
- credential, no paid service and no database server.
6
-
7
- It does need two of the optional gems — because of the plugins the Recipe
8
- names, not because of the framework — and installing exactly those is a step of
9
- this guide rather than a footnote to it. Automatic Ruby installs what the
3
+ This guide takes the public pages listed in its Recipe through one short
4
+ Automatic Ruby pipeline and leaves what they publish as one Markdown document.
5
+ It needs no account, no credential, no paid service and no database server.
6
+
7
+ It does need optional gems because of the plugins the Recipe names, not because
8
+ of the framework. The exact requirements are derived in
9
+ [Install what the Recipe needs](#4-install-what-the-recipe-needs) from the
10
+ canonical optional-dependency table in
11
+ [`DEPLOYMENT.md`](DEPLOYMENT.md#optional-plugin-dependencies), rather than
12
+ being maintained as a separate count here. Automatic Ruby installs what the
10
13
  framework needs and leaves a plugin's gems to the operator who uses that
11
14
  plugin, so "which plugins does this Recipe name, and what do they need" is a
12
15
  question every Recipe asks. Skipping it is the usual way a first run stops half
@@ -38,7 +41,7 @@ overwritten.
38
41
  ## 3. Write the Recipe
39
42
 
40
43
  A Recipe is one job: the plugins it runs, in order, with their settings. This
41
- one reads four public index pages as HTML and makes a feed of the articles each
44
+ one reads the public index pages listed under `sites` as HTML and makes a feed of the articles each
42
45
  lists — which is what to do for a page whose feed you do not have — keeps a
43
46
  record of what it has already seen, and appends the rest to a Markdown
44
47
  document.
@@ -101,7 +104,7 @@ plugins:
101
104
  mode: append
102
105
  ```
103
106
 
104
- Three plugins, and each hands its result to the next:
107
+ Each plugin hands its result to the next:
105
108
 
106
109
  - **`CustomFeedWeb`** fetches each page and makes a feed of the article links it
107
110
  lists. `include` is what tells an article from a navigation link, `interval`
@@ -117,7 +120,8 @@ Three plugins, and each hands its result to the next:
117
120
 
118
121
  Read the Recipe you have just written, plugin by plugin, and look each one up in
119
122
  the table of optional plugin dependencies in [`DEPLOYMENT.md`](DEPLOYMENT.md).
120
- That table gives, for these three:
123
+ For the plugins in this Recipe, that table resolves the requirements as
124
+ follows:
121
125
 
122
126
  - **`CustomFeedWeb`** — `nokogiri`, which it reads the pages with. In a
123
127
  checkout, the group `html`.
@@ -221,10 +225,10 @@ bundle exec bin/automatic scaffold
221
225
  bundle exec bin/automatic -c ~/.automatic/config/web2markdown.yml
222
226
  ```
223
227
 
224
- Step 4 is the step that differs, because a checkout resolves its gems through
225
- Bundler rather than through RubyGems. Each optional gem is in a Bundler group,
226
- and the Recipe's groups — `html` and `store`, from step 4 — are selected
227
- together and installed once:
228
+ The "Install what the Recipe needs" step is the step that differs, because a
229
+ checkout resolves its gems through Bundler rather than through RubyGems. Each
230
+ optional gem is in a Bundler group, and the Recipe's groups — `html` and
231
+ `store`, from that step — are selected together and installed once:
228
232
 
229
233
  ```sh
230
234
  bundle config set --local with "html store"
@@ -233,7 +237,7 @@ bundle install
233
237
 
234
238
  `gem install nokogiri` does **not** work here: the gem installs, and the
235
239
  checkout still reports it as missing, because it is not in the bundle. That
236
- difference, the commands that show what the bundle holds, and the same three
240
+ difference, the commands that show what the bundle holds, and the same Recipe
237
241
  plugins taken step by step through choosing their groups are in
238
242
  [`DEPLOYMENT.md`](DEPLOYMENT.md) under "Working out what a Recipe needs, in a
239
243
  checkout".
data/doc/RELEASING.md CHANGED
@@ -33,8 +33,9 @@ gem --version
33
33
  bundle --version
34
34
  ```
35
35
 
36
- The release Ruby need not reproduce the complete CI matrix locally. Required
37
- GitHub Actions checks must be green on Ruby 3.3, 3.4 and 4.0 before publication.
36
+ The release Ruby need not reproduce the complete CI matrix locally. Required GitHub Actions checks must be green for every Ruby version in the
37
+ current matrix of [`.github/workflows/ci.yml`](../.github/workflows/ci.yml)
38
+ before publication.
38
39
 
39
40
  ## 2. Authentication and credentials
40
41
 
@@ -85,8 +86,12 @@ X.YY release in year X, month YY
85
86
  X.YY.PATCH correction to an earlier release in the same month
86
87
  ```
87
88
 
88
- Section 10 of `POLICY.md` is authoritative. A release updates these three
89
- places together in one release-metadata commit:
89
+ `PATCH` may correct an earlier release in the same month, but never on the same
90
+ calendar date as that release. A versioned unit has one version number per
91
+ calendar date, without exception.
92
+
93
+ Section 10 of `POLICY.md` is authoritative. A release updates these release-metadata sources together in one
94
+ release-metadata commit:
90
95
 
91
96
  - `VERSION` contains the package version;
92
97
  - `lib/automatic/version.rb` defines `Automatic::VERSION` for the CLI;
@@ -207,7 +212,7 @@ Confirm that:
207
212
  - the name is `automatic` and the version equals `VERSION`;
208
213
  - the summary and homepage describe this project;
209
214
  - `source_code_uri` is the Automatic Ruby repository;
210
- - the required Ruby version is `>= 3.3.0`;
215
+ - the required Ruby version matches `automatic.gemspec`;
211
216
  - licenses contain both `GPL-3.0-only` and `LGPL-3.0-only`;
212
217
  - the authors are correct;
213
218
  - runtime and development dependencies match `automatic.gemspec`, and the
@@ -273,7 +278,9 @@ git push origin "v$version"
273
278
  The tag identifies the source used to build the gem. Do not move or force-update
274
279
  a release tag after publication. If the tag is wrong before publication, stop
275
280
  and correct it openly. If publication has happened, preserve the source history
276
- and make the correction in a new commit and version.
281
+ and prepare the correction in a new commit. Publish the correction under a new
282
+ version only on a later calendar date; do not create or publish a second
283
+ version on the same date.
277
284
 
278
285
  The repository has no established requirement for a GitHub Release separate
279
286
  from its Git tag. If maintainers create one, create it from the same immutable
@@ -337,16 +344,22 @@ Stop on an error and diagnose it; do not bypass RubyGems security controls.
337
344
  - **Version already exists:** do not attempt to overwrite it. Determine whether
338
345
  it was already published correctly; otherwise fix the source, increment the
339
346
  version according to `POLICY.md`, rebuild and repeat every verification step.
347
+ The incremented version is published on a later calendar date than the
348
+ existing one, never on the same date.
340
349
  - **Malformed metadata or rejected gem:** fix the gemspec and release metadata,
341
350
  increment the version if that version reached RubyGems.org, then rebuild,
342
351
  inspect and test the new archive. Do not use `--force` to hide validation.
352
+ A version that reached RubyGems.org counts as that date's release, so the
353
+ incremented version waits for a later calendar date.
343
354
 
344
355
  `gem yank automatic -v X.Y.Z` removes a published version from normal index use.
345
356
  Yank only for an exceptional serious mispublication, such as exposed secrets or
346
357
  a release that must not be installed. It is not the normal correction process,
347
358
  does not make the version reusable, and does not erase every downloaded copy.
348
359
  The normal response is to correct the problem, bump the version and publish a
349
- new release. Rotate any exposed secret immediately as well as yanking.
360
+ new release on a later calendar date. Yanking does not permit a second version
361
+ on the date of the yanked release. Rotate any exposed secret immediately as
362
+ well as yanking.
350
363
 
351
364
  Publication state and Git history are separate. Never rewrite a published
352
365
  commit, force-push the release branch, or move a release tag to simulate a
data/doc/REQUIREMENTS.md CHANGED
@@ -50,6 +50,16 @@ to a plugin, and plugins are expected to come and go.
50
50
 
51
51
  - **Not an application.** It has no behaviour of its own. With no Recipe it does
52
52
  nothing, and every useful thing it does is a plugin's doing.
53
+ - **Not host infrastructure.** It is not a common system baseline installed to
54
+ provide the same foundational behaviour to every host. It is operator tooling
55
+ for assembling jobs, and the jobs, dependencies and effects are chosen by each
56
+ Recipe.
57
+ - **Not a substitute for a purpose-built application.** The fact that many jobs
58
+ can be expressed as plugin pipelines does not make every problem a framework
59
+ problem. A concrete purpose that needs branching, complex shared state,
60
+ transactions, interactive behaviour, multi-user permissions or tightly
61
+ coupled domain logic belongs in an application rather than in a larger
62
+ Automatic Ruby core.
53
63
  - **Not a daemon or a scheduler.** One invocation runs one Recipe once and
54
64
  exits. Repetition is `cron`'s job, and periodic running is deliberately left
55
65
  outside; see section 15.
@@ -88,8 +98,12 @@ several plugins shell out to Unix commands.
88
98
 
89
99
  ## 7. The two public interfaces
90
100
 
91
- Two things in this repository are interfaces that people outside it depend on,
92
- and they are treated accordingly.
101
+ Two public contracts define how external Recipes and plugins compose with the
102
+ framework, and they receive the strongest compatibility protection in this
103
+ project. That protection is deliberately narrower than treating every
104
+ implementation detail or every shipped plugin as permanent. The Recipe format
105
+ and the plugin contract are stable composition contracts; framework internals
106
+ may improve within those contracts, and plugins remain replaceable components.
93
107
 
94
108
  ### 7.1 The Recipe
95
109
 
@@ -132,6 +146,12 @@ that acquires something which is not a feed — a row of a TSV file, an API
132
146
  response, a weather report — converts it into this shape and the rest of the
133
147
  pipeline is unaffected.
134
148
 
149
+ The single pipeline shape is part of the framework's composition model. It is
150
+ protected for the same reason as the plugin contract: changing it would not
151
+ merely refactor an implementation, but would change what existing plugins can
152
+ compose with. Composability here is an architectural invariant, not an
153
+ optimization preference.
154
+
135
155
  This is the framework's one substantive constraint on plugins, and it is what
136
156
  makes them compose. Three consequences are requirements:
137
157
 
@@ -295,6 +315,13 @@ The requirements on failure:
295
315
  - **A Recipe naming a plugin that does not exist fails immediately**, before any
296
316
  plugin runs, with a message naming the plugin.
297
317
 
318
+ These execution rules describe the straight-line composition model Automatic
319
+ Ruby supports. They are not an incomplete version of a richer workflow engine.
320
+ A use case that fundamentally requires branching, resume checkpoints,
321
+ transactional coordination or cross-step state ownership should not cause the
322
+ framework to grow those features by default; it should first be judged as a
323
+ candidate for a purpose-built application.
324
+
298
325
  ## 13. The user directory
299
326
 
300
327
  `~/.automatic` is where an installation's own material lives, so that it
@@ -464,8 +491,9 @@ Two statements are made here, and they are deliberately different.
464
491
  dependency the project needs moves it, or when the version drops out of the
465
492
  distributions the project is used on.
466
493
 
467
- **The continuously validated versions** are **3.3, 3.4 and 4.0** — the ends of
468
- the range and the release in the middle.
494
+ **The continuously validated versions** are the versions in the matrix of
495
+ [`.github/workflows/ci.yml`](../.github/workflows/ci.yml). That workflow is
496
+ authoritative for the set checked on every commit.
469
497
 
470
498
  - CI runs representative versions rather than every intermediate release. The
471
499
  cost of a matrix entry is paid on every commit, and a third entry between two
@@ -476,9 +504,10 @@ the range and the release in the middle.
476
504
  - Adding a released Ruby to the matrix is how support for it becomes continuous,
477
505
  and is a small change.
478
506
 
479
- One statement of the supported range lives in the gemspec, one statement of the
480
- validated set lives in the CI matrix, and the README and the documents agree
481
- with both.
507
+ `automatic.gemspec` is authoritative for the supported Ruby requirement, and
508
+ `.github/workflows/ci.yml` is authoritative for the continuously validated
509
+ set. User-facing documents state the supported range where readers need it
510
+ and refer to the matrix rather than duplicating its current membership.
482
511
 
483
512
  ## 21. Portability
484
513
 
@@ -507,9 +536,10 @@ with both.
507
536
 
508
537
  ## 23. Simplicity
509
538
 
510
- The framework is under seven hundred lines of Ruby and is meant to stay that
511
- size. It is the small fixed part that plugins are written against, and it earns
512
- its keep by not changing.
539
+ The framework is meant to remain the small fixed part that plugins are written
540
+ against. Its responsibility boundary, rather than a duplicated current line
541
+ count, is the maintained constraint; see
542
+ [`BASIC_DESIGN.md`](BASIC_DESIGN.md).
513
543
 
514
544
  - A capability that can live in a plugin lives in a plugin.
515
545
  - A framework feature that only one plugin would use does not belong to the