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.
- checksums.yaml +4 -4
- data/README.md +80 -46
- data/VERSION +1 -1
- data/assets/siteinfo/items_all.json +60300 -52138
- data/automatic.gemspec +4 -3
- data/bin/automatic +1 -1
- data/doc/AI_TUTORIAL.md +64 -40
- data/doc/BASIC_DESIGN.md +31 -0
- data/doc/DEPLOYMENT.md +53 -47
- data/doc/PLUGINS.md +231 -91
- data/doc/POLICY.md +149 -44
- data/doc/QUICKSTART.md +19 -15
- data/doc/RELEASING.md +20 -7
- data/doc/REQUIREMENTS.md +40 -10
- data/doc/VERSIONS +112 -54
- data/lib/automatic/cli.rb +40 -18
- data/lib/automatic/environment.rb +1 -1
- data/lib/automatic/feed_maker.rb +57 -18
- data/lib/automatic/feed_parser.rb +1 -1
- data/lib/automatic/http.rb +1 -1
- data/lib/automatic/log.rb +1 -1
- data/lib/automatic/pipeline.rb +15 -5
- data/lib/automatic/recipe.rb +45 -1
- data/lib/automatic/version.rb +3 -3
- data/lib/automatic.rb +18 -3
- data/plugins/custom_feed/web.rb +1 -1
- data/plugins/filter/absolute_uri.rb +1 -1
- data/plugins/filter/batch.rb +97 -0
- data/plugins/filter/claude.rb +1 -1
- data/plugins/filter/clear.rb +1 -1
- data/plugins/filter/description_link.rb +20 -3
- data/plugins/filter/full_feed.rb +28 -16
- data/plugins/filter/gemini.rb +1 -1
- data/plugins/filter/ignore.rb +1 -1
- data/plugins/filter/image.rb +1 -1
- data/plugins/filter/image_source.rb +20 -3
- data/plugins/filter/join.rb +4 -6
- data/plugins/filter/kimi.rb +216 -0
- data/plugins/filter/limit.rb +57 -0
- data/plugins/filter/open_ai.rb +1 -1
- data/plugins/filter/present.rb +75 -0
- data/plugins/filter/sakura_ai.rb +1 -1
- data/plugins/filter/sanitize.rb +1 -1
- data/plugins/filter/sort.rb +1 -1
- data/plugins/filter/tumblr_resize.rb +1 -1
- data/plugins/notify/ikachan.rb +1 -1
- data/plugins/provide/fluentd.rb +1 -1
- data/plugins/publish/amazon_s3.rb +9 -3
- data/plugins/publish/console.rb +1 -1
- data/plugins/publish/fluentd.rb +1 -1
- data/plugins/publish/hatena_bookmark.rb +1 -1
- data/plugins/publish/markdown.rb +1 -1
- data/plugins/publish/memcached.rb +1 -1
- data/plugins/store/digest.rb +1 -1
- data/plugins/store/file.rb +11 -3
- data/plugins/store/full_text.rb +9 -13
- data/plugins/store/permalink.rb +1 -1
- data/plugins/subscription/feed.rb +1 -1
- data/plugins/subscription/link.rb +1 -1
- data/plugins/subscription/text.rb +13 -4
- data/plugins/subscription/tumblr.rb +1 -1
- data/plugins/subscription/xml.rb +1 -1
- 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. **
|
|
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
|
-
|
|
77
|
+
7. **The default test suite reaches no network and needs no credential.** None
|
|
61
78
|
is configured in CI.
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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`
|
|
194
|
-
|
|
195
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
the
|
|
795
|
-
|
|
796
|
-
|
|
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
|
|
4
|
-
leaves what they publish as one Markdown document.
|
|
5
|
-
credential, no paid service and no database server.
|
|
6
|
-
|
|
7
|
-
It does need
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
Bundler rather than through RubyGems. Each
|
|
226
|
-
and the Recipe's groups — `html` and
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
92
|
-
and they
|
|
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
|
|
468
|
-
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
|
511
|
-
|
|
512
|
-
|
|
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
|