-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathPOLICY
More file actions
1609 lines (1443 loc) · 85.5 KB
/
Copy pathPOLICY
File metadata and controls
1609 lines (1443 loc) · 85.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Implementation Policies
This document defines implementation policies shared across Shell Scripts,
Python, and Ruby, followed by language-specific rules. The goal is to minimize
redundancy while making the design intent explicit and consistent for all
contributors.
This document stands on its own. It is the whole implementation policy of this
repository, and the repository-wide rules it needs are stated here. A subject
it does not cover is a gap in this document, to be filled here.
The **Common Policy** section defines rules that apply to *all* languages.
Each language section then specifies only what is unique to that language.
These policies are means to produce correct, safe, portable, maintainable, and
useful software; compliance with a rule is not an end in itself. When a
general guideline would prevent required functionality or create an
unreasonable operational cost, preserve the purpose of the rule and the
intended behavior rather than applying its wording mechanically.
This document's own wording is held to the same standard. `must`, `always`,
and `never` are reserved for an invariant that admits no reasonable
exception; an ordinary design preference, recommendation, or situational
judgment is written with `prefer`, `should`, `when appropriate`, or similar
language that shows its actual weight. A rule is not written so that a
contributor, an AI, or a checker applying its wording literally is driven to
an unreasonable result, and where a rule's wording and its purpose conflict,
the purpose and the intended behavior defined above govern.
For repository-specific choices left open by this policy, an explicit
maintainer decision is final. General guidance informs those decisions but
does not override a maintainer decision on a choice that this policy leaves
open. Release version numbers are always selected by the maintainer.
A decision belongs in this policy when it is reusable across scripts or future
changes. A decision caused only by one script's interface, environment, or
implementation stays with that script in its header, comments, or
configuration documentation.
---
## 0. Governing Principle
**Simplicity is robustness.**
This is the governing principle for every rule below. It does not authorize
dropping required behavior, compatibility, or safety; it requires meeting
them with the least complexity justified by the actual requirement.
Complexity is itself a source of failure, compatibility risk, operational
risk, and maintenance cost. Do not add a branch, state variable, helper,
abstraction, dependency, retry, fallback, validation, or defensive check
unless it is required by the specification or addresses a realistic failure
mode with meaningful operational consequence. Do not re-check a condition
already guaranteed by a preceding successful operation or an established
invariant.
When two designs satisfy the same requirements, choose the one with less
control flow, less state, fewer dependencies, and fewer moving parts.
Simplicity is judged by necessary concepts and behavior, not by line count
alone.
## 1. Common Policy (All Languages)
This section defines rules that apply uniformly to all languages.
Each language-specific section intentionally describes only deviations from
or additions to this common policy, in order to avoid redundancy.
### 1.1 Design and Repository
#### 1.1.1 Design Philosophy
- Prioritize clarity, portability, and explicit control over convenience.
- Favor predictable behavior and long-term maintainability.
- Avoid implicit behavior; make control flow, errors, and side effects explicit.
#### 1.1.2 Repository Layout
- Top level: the scripts a user runs directly, in Shell Script, Python, and
Ruby.
- `installer/`: setup and installation scripts. They follow the same header,
logging, CLI, and exit code rules as the top level, and
`test/check_scripts.sh` exercises them through their `-h` option.
- `cron/bin/`: job scripts deployed as `/etc/cron.exec`.
`cron/etc/`: their configuration files, deployed as `/etc/cron.config`.
- `etc/`: configuration files and data files read by the top-level scripts.
- `dot_files/`: dot files deployed into a user's home directory.
- `test/`: the test suite.
- `doc/`: the user-facing feature reference, this policy, the repository version
history, and the license texts.
- Placement says what a file is for, not which rules apply to it. Every
executable in the repository follows the Common Policy.
#### 1.1.3 Decision Priorities and Maintainer Judgment
Where there are several ways to do something and this document does not settle
which, weigh competing concerns in this order:
1. Compatibility: keep intended and valid existing observable behavior
working, and keep the documented supported execution environment, the
Shell Script portability target, and the Python and Ruby supported
versions working.
2. Safety: do not destroy, overwrite, or expose what the run was not asked to
touch, and prefer state changes that remain safe when repeated.
3. Efficiency: avoid unnecessary processing, unnecessary process creation,
unnecessary I/O, unnecessary network access, and unnecessary resource
cost; meet the required behavior at the smallest reasonable runtime and
operational cost. Efficiency matters, but not at the cost of
Compatibility or Safety.
The items below are the concrete decision material that gives those three
concerns substance in this repository. They stand alongside the ordering
above, not as a second, separate priority order:
- Do not destroy or overwrite what the run was not asked to touch.
- Do not put a credential where another user of the host can read it, and do
not commit one.
- Prefer state changes that remain safe when the same operation is repeated.
- Preserve existing observable behavior, including control flow,
processing order, which later operations are attempted after a
failure, side effects, options, exit status, output, and
configuration file format, so that an unchanged invocation keeps
behaving as before.
- Stay portable: POSIX for a Shell Script, and the stated minimum version for
Python and Ruby.
- Branch on what the environment provides, not on what it is called.
- Stay fit for unattended execution: report a condition the operator can act
on, and do not repeat one they cannot.
- Prefer the standard library when it provides the required behavior without
unreasonable implementation or operational cost.
- Add no option, no configuration value, and no script that is not needed.
- Prefer the smaller, simpler, less dependent implementation when it
preserves required behavior, Compatibility, and Safety.
These priorities are decision guidance, not mechanical goals that justify
breaking required functionality or established observable behavior.
The observable-behavior item above preserves behavior that is intended and
valid. A clear defect, regression, unintended side effect, or otherwise
broken behavior is not preserved merely because it is what the code
currently does, and correcting it is not treated as an incidental
compatibility break. When the intended
behavior is unclear, weigh the implementation, the documented interface, the
history, and the maintenance intent together, as described in 1.6.1.
A decision that remains specific to one script is recorded with that script.
Add it to this policy only when it becomes a reusable repository-wide
principle.
#### 1.1.4 Change Discipline for Established Infrastructure
Finding a possible safety, portability, maintainability, cleanup, or
refactoring improvement does not by itself authorize an implementation
change.
Documentation work, policy work, review, diagnostics, cleanup, or another
task whose stated scope does not include an implementation change must not
expand into one merely because an improvement opportunity is discovered.
A safety-motivated change is not automatically justified by Safety having
priority over Efficiency. Added complexity can itself create new failure
modes, compatibility risk, operational risk, and maintenance cost. Evaluate
the proposed change as a whole.
Before changing established behavior, weigh the probability and impact of the
failure being prevented against the added complexity, new failure modes,
compatibility risk, operational cost, maintenance cost, and rollback cost.
Behavior that has operated reliably over time is evidence and has value of
its own. Do not refactor established infrastructure merely because another
design appears cleaner, more modern, or more defensive.
An implementation change is made only when that change has been explicitly
chosen as part of the work being performed. Before deployment, validate it in
the supported and representative environments affected by the change. A
successful check in one convenient environment is not sufficient evidence for
a change whose compatibility risk extends to other supported environments.
Scope discipline does not mean leaving documentation inconsistent with an
implementation change that is already authorized by the stated work. When an
implementation change alters an interface or behavior described by a header,
configuration document, feature reference, README, or release history, update
the directly affected documentation as part of that same change unless the
stated scope explicitly excludes documentation changes. This does not
authorize unrelated cleanup or correction merely because the same file or
repository is already being edited.
### 1.2 Implementation Fundamentals
#### 1.2.1 Control Flow Rules
- Within a function or other internal control flow, use explicit termination
(`exit`, `sys.exit`, `exit()`) **only for abnormal or forced termination**.
- Normal internal control flow must return normally and propagate status
explicitly rather than cutting execution short with a process-level `exit`.
- The top-level program entry point converting the status that `main()` (or
`main`) returned into the OS process exit status, such as
`sys.exit(main())` in Python or `exit(main) if __FILE__ == $0` in Ruby, is
normal entry-point behavior, not the abnormal or forced termination this
rule constrains.
- Do not rely on implicit termination or language-specific shortcuts
(e.g. `set -e` in a shell script, bare `except` in Python). For a Shell
Script, this rule constrains internal control-flow termination the same
way; it does not apply to the top-level process simply completing.
- A change whose stated purpose is portability, diagnostics, error
handling, cleanup, refactoring, modernization, or standards
conformance must preserve existing observable behavior unless a
behavior change is explicitly part of the task.
- Detecting or reporting a failure must not by itself change whether
later independent operations are attempted.
- Changing continue-on-failure into fail-fast, or fail-fast into
continue-on-failure, is an observable behavior change. Do not
introduce either as an incidental error-handling improvement.
- Treat the operation result, the continuation decision, and the reporting
decision as separate interfaces. A failure does not automatically require
fail-fast handling, and continuing later work does not automatically require
warning-level output.
- Stop the affected logical operation when a required prerequisite is missing,
correct completion is no longer possible, or continuing can produce an
invalid, inconsistent, destructive, or otherwise unsafe result. Do not turn
such a condition into a warning merely to keep the run moving.
- Continue later work only when that work is independent, the established
contract permits continuation, and the remaining work can still complete to
a coherent valid state.
#### 1.2.2 Dependencies and Optional Capabilities
- Minimize dependencies when doing so does not compromise required
functionality or make the implementation unreasonable. Dependency
reduction is a design preference, not an end in itself.
- `check_commands` declares what a script requires for its normal main
execution. A capability the script can work without is not declared there;
it is detected where it is used.
- Detection asks whether the capability is usable, not only whether the
command exists. A command may be present while the subcommand or the option
the script needs is not.
- Decide before using an optional capability whether its absence selects an
alternative, skips that operation, or refuses the run.
- An unattended script in an environment that will never supply an optional
capability should avoid repeatedly reporting a condition that the operator
cannot act on. A failure the operator can act on is still reported.
- Do not add a check whose only purpose is to reconfirm a state already
guaranteed by an invariant established earlier in the same run or by the
contract of a preceding operation that completed successfully. Such a check
adds a branch without distinguishing a supported runtime state.
- Add a separate check only when the condition can vary independently in a
supported environment and its result changes safe control flow, diagnostics,
fallback behavior, or another observable choice. This applies to commands,
files, directories, services, configuration state, and other capabilities.
- For example, when a successful package installation contractually provides a
command before its first use, do not immediately check that command again.
Use it when needed; an unexpected inconsistency is handled by the consuming
operation's existing failure path.
- When omitting a check is non-obvious because an earlier operation or package
contract establishes the required state, leave a concise adjacent code
comment if a future maintainer could otherwise reasonably reintroduce the
redundant check. The comment explains the invariant that makes the extra
check unnecessary; it does not merely restate the code.
#### 1.2.3 Environment Differences
- Branch on what the environment provides, not on what it is called. A
distribution name, a release number, and a desktop session name each answer a
question the script is not asking. The question is whether the command, the
file, the service, or the configuration format it needs is there.
- Keep that detection in one place. The same question answered separately in
several places drifts apart as environments change.
#### 1.2.4 Comments and Language
- Comments must be written in **English** only.
- Use concise imperative phrasing as the normal English comment style. The
grammatical form is not itself the purpose of a comment.
- Do not restate code whose operation is already obvious. Use comments to
preserve reasons, constraints, non-obvious intent, and decisions that a
later change could accidentally undo.
- Where a decision looks arbitrary, such as a portable substitute for a
construct that some implementation gets wrong, a retention period, or a
hard-coded fallback, state the reason.
- Name a thing by what it is, not by a part of it. A shell script is a shell
script: the shell is the interpreter that runs it, and naming the script
after the interpreter is as loose as calling a USB memory stick a USB. The
same holds wherever a shorthand reaches for the interface, the format, or
the container instead of the thing itself. This applies to the headers, the
documents, and the commit messages as much as to the comments.
#### 1.2.5 Shared Helper Names
- A helper name reused across scripts, or copied between id774 repositories as
the same helper, is a shared interface. Within the same language and helper
family, the same name means the same responsibility, arguments, return or
exit semantics, side effects, and implementation contract.
- Do not specialize a shared helper in place for one script. Keep the common
helper contract unchanged and put caller-specific handling at the call site,
or give the specialized helper a different name that describes its narrower
contract.
- When two existing helpers intentionally require different behavior, preserve
both behaviors and rename the non-common helper instead of forcing their
implementations to match.
- A rename under this rule must not change control flow, processing order,
output, exit status, side effects, supported environments, dependencies, or
configuration semantics.
- Conventional entry points, class or protocol methods, and domain-local APIs
are not shared helpers merely because the same structural name appears in
more than one program.
### 1.3 Command-Line Interface and Output
#### 1.3.1 CLI Conventions
- Every command-line tool must let the user display help and query version
information.
- Provide `-h` and `--help` as the standard help options. A command-line tool
without a help interface is not accepted.
- Provide `-v` and `--version` as the standard version options when those
names do not conflict with parser-defined or established option semantics.
When a short option conflicts, preserve the version-query capability under
the non-conflicting form and document the actual interface.
- Help or version output represents a successful, user-requested termination.
- Invalid or unsupported options must result in usage output.
- Returning `0` after successfully displaying usage is the repository
standard, including when an invalid option triggered that display. A tool
that returns `1` for an invalid option is accepted as well. Keep the chosen
behavior consistent within the tool and document it in the header.
- Do not change an existing tool between these accepted behaviors merely to
make its exit status match another tool.
##### 1.3.1.1 Recommended Option Names
- The following names are recommendations, not requirements. A script that has
a reason to differ may differ; a script that has none should reuse the
established name rather than invent a synonym for it.
- `--dry-run`: report what would be done, and change nothing.
- `-q`, `--quiet`: suppress non-error messages.
- `--force`: process every matched entry, skipping the checks that normally
narrow the work.
#### 1.3.2 Error Handling and Exit Codes
- Detect command failures and unmet prerequisites early.
- Make an error observable at the layer responsible for reporting it, naming
the reason and affected target when that context is useful. Do not require a
lower layer to emit a duplicate message when its caller already reports the
same failure through the established interface.
- Exit code semantics must follow widely accepted UNIX/GNU/Linux conventions
and remain consistent across the repository.
##### 1.3.2.1 Exit Code Conventions
- **0: Success**
The operation completed successfully.
This includes cases where the program terminates after displaying help or
usage information without encountering an error.
- **1: General failure**
The default failure code.
Use for invalid arguments, missing required resources, processing errors, or
any failure that does not require explicit classification. For an invalid or
unsupported CLI option specifically, the exit status follows the
established usage behavior defined in Section 1.3.1 instead: both the
repository-standard `0` and the existing accepted `1` are valid, and this
entry does not override that Section 1.3.1 usage behavior.
- **126: Command or script exists but is not executable**
Reserved by the shell. Do not redefine.
- **127: Command or script not found**
Reserved by the shell. Do not redefine.
- **128 and above: Signal-related termination**
Reserved by convention (128 + signal number). Do not use for
application-defined errors.
> If finer-grained classification is truly required, `sysexits` codes (64–78)
> may be used, but must be explicitly documented in the script or program
> header.
#### 1.3.3 Logging and Output
- Use unified log prefixes: `[INFO]`, `[WARN]`, `[ERROR]`.
- Prefer plain natural-language log messages when ordinary prose expresses the
same meaning clearly.
- Do not add punctuation, symbols, status glyphs, separators, arrows, or other
visual markers solely for decoration, emphasis, or status styling.
- This is not a prohibition on punctuation or symbols. Use them when they are
part of normal prose, technical notation, code, paths, data, established
formats, or when they convey a relationship more clearly than prose.
- Do not mechanically remove existing symbols or rewrite clear technical
notation merely to avoid symbols.
- A normal no-op or skip is not a warning merely because no work was performed.
It may be silent. Use `[INFO]` when the normal result is useful to the
operator, `[WARN]` for a degraded or otherwise abnormal but recoverable
condition the operator should know about, and `[ERROR]` when the affected
logical operation cannot complete correctly.
- Log level and process status are separate signals. `[WARN]` may accompany
exit status `0` when a condition is noteworthy but the requested operation
still satisfies its contract.
- Informational messages go to standard output.
- Error messages always go to standard error.
- Log messages must be human-readable and suitable for cron execution.
- Timestamped progress logs are intended only for major progress points in
long-running operations.
- Do not add timestamps to every log message.
- The purpose of timestamped progress logs is to make long-running steps
observable in cron logs and administrative emails, not to convert all output
into structured logs.
- Timestamped progress logs should be used for major start and finish points of
external I/O, synchronization, recursive scans, recursive modifications,
cleanup operations, and other steps whose duration is operationally
significant.
- Short validation checks, simple branch decisions, static configuration
messages, and immediate return-code messages should normally use standard log
prefixes without timestamps.
- When a timestamp is needed, append it naturally at the end of the message.
- Timestamped log helpers must generate the timestamp at call time, not at
initialization time.
- For automated emails (cron, admin jobs), use **filter-friendly tags**
(e.g. `[cron]`, `[admin]`) in the subject line so recipients can easily
classify, filter, or route messages.
### 1.4 Configuration and External Resources
#### 1.4.1 Configuration Files
- A script that needs site-specific values reads them from a file under `etc/`,
named after the script (e.g. `etc/dashcam_sync.conf`), instead of carrying
them as constants.
- A top-level script resolves the file relative to its own directory and falls
back to the parent's `etc/`, so that it works both inside the repository and
from a deployed location: `$SCRIPT_DIR/etc/NAME.conf`, then
`$SCRIPT_DIR/../etc/NAME.conf`. A `cron/bin/` job reads its deployed path
under `/etc/cron.config/` directly, since it only ever runs from
`/etc/cron.exec`.
- A missing file, and a required value left unset, are both refused before any
work starts, each with its own exit status and an `[ERROR]` message naming
what is missing.
- Every such file opens with the same header block used by scripts: a title
line naming the file, a sentence saying which script reads it and what it
decides, and a `Rules` section describing its format. It carries no shebang
and is not executable.
- Two kinds exist, and the `Rules` section says which one the file is.
##### 1.4.1.1 Sourced Configuration Files
- Sourced by the script, so the file must be POSIX sh compatible. Its `Rules`
section states that blank lines are allowed, that lines starting with `#` are
comments, that the file must be POSIX sh compatible, and that it carries no
shebang because it is sourced rather than executed.
- A `Variables` section names every variable, what it decides, and whether it
is required. A file that also defines a function documents it under
`Functions`.
- The variables a script requires are named in two places that must agree: the
`Variables` section of the configuration file, and the header of the script
that reads it.
##### 1.4.1.2 List Files
- Read line by line rather than sourced, so the file is data and not shell
code: `etc/apache_ignore.list` and `cron/etc/clamscan.conf` are of this kind.
- Its `Rules` section states that blank lines are ignored, that lines starting
with `#` are comments, that the file is read line by line rather than
sourced, and what one non-comment line means.
- An `Examples` section shows the accepted line forms.
#### 1.4.2 Credentials and Secrets
- A credential that authenticates against a remote service is read from a
configuration file or from the environment. It is never passed as a command
line argument, because a command line is readable by every user of the host.
- A credential is never logged and never quoted in an error message.
Report it as present or absent.
- Output added while a fault is being chased is removed before the change is
committed. A diagnostic that prints a configuration value, a command line, or
a response body is the one most likely to print a credential with it.
- No configuration file under `etc/` carries a credential. Those files are
committed with real values, so a setting that must stay secret does not
belong in them.
- Exception: a secret that the script itself generates for local use, that
authenticates nothing remote, and that exists in order to be handed to the
operator, may be passed to a local command and printed. `send_files.sh`
generates an archive password this way; the password is stored locally and
is never sent together with the archive.
#### 1.4.3 Network Operations
- Judge reachability from the network operation the script actually needs to
perform. Do not require a separate ping, curl, or similar preflight probe
when that probe tests a different path or protocol from the requested
operation.
- Design unattended network operations so that they do not wait indefinitely.
Use an explicit timeout when the command or API provides a suitable timeout
mechanism and doing so preserves the intended operation.
- A foreground manual script may rely on caller interruption when that is its
established interface.
- Report failure of the requested network operation and name the affected
target.
### 1.5 Safety and Side Effects
#### 1.5.1 Destructive Operations
- The following are recommendations. A script that has a reason to skip a check
may skip it, but should say why in a comment.
- Validate existence, type, and permissions before a destructive operation, and
validate a path before modifying it.
- Offer `--dry-run` on a script that renames, moves, or deletes.
- Do not widen the scope of a deletion in order to recover from unexpected
input.
The checks below are not among those recommendations. They hold whatever the
language, because each of them is a way of operating on a target the run was
never asked to touch.
- Examine the value that names the target before it reaches the command. An
empty string, an unset or undefined variable, and a pattern that matched
nothing each turn a deletion or an overwrite into one over something else.
The Shell Script policy states how this is done there; the requirement is
not peculiar to that language.
- Do not let `/`, a home directory, or an empty target become the thing
operated on. A path assembled from a value that turned out to be empty
resolves to one of them.
- `--dry-run` and the real run resolve their targets by the same code, so that
what the report listed is what the run acts on.
- These rules apply to implementation changes whose stated scope includes the
affected destructive behavior. Editing the same script for an unrelated
reason does not by itself authorize changing established destructive
behavior. When an authorized implementation change affects that behavior,
keep the directly related implementation and documentation consistent with
these rules.
#### 1.5.2 Idempotency, State Checks, and No-op Results
- Prefer state-changing operations to remain safe when repeated over the same
input. This is especially useful for installers, setup tools, cleanup tools,
uninstallers, and scheduled jobs.
- Judge success against the operation's contract and required postcondition,
not merely by whether the run changed something.
- For a state-converging operation whose purpose is to ensure, clear, remove,
disable, configure, or uninstall a state, treat the run as a successful
no-op when the required postcondition already holds and the invocation's
required prerequisites have been satisfied. Do not report failure merely
because no mutation was necessary.
- Idempotency does not mean that every run with zero work is successful. A
batch, synchronization, extraction, monitoring, transfer, conversion, or
other input-driven operation may legitimately treat zero matching inputs as
failure when its contract requires work or input to be present.
- Distinguish required resources from optional or state-owned targets.
Absence of a required input, configuration, source, or other prerequisite
is a failure. Absence of a target may be success when the operation's
required postcondition is precisely that the target be absent or require
no change.
- A successful no-op may be silent or informational according to its
operational significance. Use warning level only when the no-op also exposes
a degraded or otherwise abnormal condition that the operator should know
about. Do not make the absence of work itself a warning or an error.
- A successful no-op does not waive invocation prerequisites. A supported
environment, required configuration, required privilege, and other
prerequisites that belong to a valid invocation are still checked according
to the tool's contract even when the state-changing branch turns out to be
unnecessary.
- In a Shell Script whose contract requires sudo privileges, `check_sudo`
remains part of normal invocation even when the requested state already
holds. Do not move, skip, or conditionalize `check_sudo` merely to make a
no-op path avoid the privilege check.
- This no-op policy does not by itself authorize moving, deleting, or
weakening `check_commands`, system checks, configuration checks, or other
established startup prerequisites. Change such prerequisite semantics only
when that is explicitly part of the work.
- After required invocation prerequisites have been validated, a successful
no-op should not create temporary files, rewrite unchanged files, restart
or reload services, prompt for confirmation that exists only to authorize
the unnecessary state change, or perform another state-changing side
effect merely to report that no change was needed.
- Check current state before changing it when that prevents duplicate,
conflicting, or corrupted persistent state. Appending a line, creating a
link, enabling a service, and installing a package commonly benefit from
such checks.
- Do not distort required behavior solely to make every side effect strictly
idempotent. An operation whose purpose is to produce a new event, message,
report, transfer, or similar effect may legitimately produce a new result
each time it runs.
#### 1.5.3 Least Privilege
- A script runs with the privileges its work needs and no more. One that needs
a raised privilege for a step takes it for that step; it does not run its
whole body under it.
- This holds whatever the language. `check_sudo` is how a Shell Script
establishes that the privilege is available, and is described under the
Shell Script policy; a Python or Ruby script that raises a privilege is
bound by the same rule.
#### 1.5.4 Installer and Setup Execution Contracts
Scripts under `installer/` do not share one execution model merely because
they are installers or setup tools.
Each script has its own execution contract, derived from its responsibility,
the relationship between its operations, its documented interface, its
history, and its established operational use.
Do not force a script into the execution model of another installer merely
because their names, locations, or individual commands are similar.
Existing installer behaviors are examples of valid contracts, not a closed
set of installer categories.
##### 1.5.4.1 Contract Is Defined by Responsibility
Before changing an installer or setup tool, establish the contract of that
script itself.
Do not infer failure handling, continuation, skip behavior, exit status, or
reporting solely from:
- the `installer/` directory;
- an `install_` or `setup_` file name;
- the number of commands it runs;
- similarity to another installer;
- a general preference for fail-fast or continue-on-failure behavior.
When the current contract is unclear, establish it from the script's purpose,
header, implementation, history, callers, and operational intent before
changing behavior.
A clear defect or regression is not preserved merely because it appears in
the current implementation.
##### 1.5.4.2 Required Contract Dimensions
For each installer or setup tool, distinguish the following aspects.
- Which prerequisites are required for the run as a whole.
- Which capabilities are required only by individual operations.
- Which operations are independent of one another.
- Which operations depend on the successful result of earlier operations.
- Which unavailable targets or capabilities are valid skips.
- Which conditions make an operation fail.
- Which failures stop only the affected logical operation.
- Which failures stop the entire run.
- Which later operations remain meaningful after an earlier failure.
- What constitutes successful completion of the script as a whole.
- What exit status represents that completion contract.
- What information is useful enough to report to the operator.
These aspects are separate decisions. Do not derive one mechanically from
another.
In particular:
- reporting a failure does not automatically require terminating the run;
- continuing the run does not automatically require returning failure later;
- skipping work does not automatically constitute failure;
- a non-zero child result does not automatically determine the parent's final
status;
- reaching the end of a script does not automatically mean that every
operation succeeded.
##### 1.5.4.3 Continuation and Dependency Boundaries
Continue later work when it is independent, remains meaningful, and the
script's contract permits continuation.
Stop a dependent logical operation when a required earlier stage has failed
and the later stage can no longer produce a correct or safe result.
Do not continue into validation, activation, reload, replacement, or another
dependent stage using an incomplete intermediate result.
Do not introduce fail-fast behavior across independent operations merely as an
error-handling improvement.
Likewise, do not introduce continue-on-failure behavior across dependent
operations merely to make a script traverse more steps.
Keep the continuation boundary at the smallest logical operation that
preserves the script's intended behavior.
Changing an installer's interaction model is an observable behavior change and
must not be introduced as an incidental safety improvement. Do not add
interactive confirmation to an established ordinary installation, package
update, configuration, or repeatable setup workflow merely as a precaution.
##### 1.5.4.4 Skips and Partial Applicability
An installer may legitimately operate in an environment where some optional or
nonessential targets are absent.
When absence affects only one optional or independent operation, the script may
skip that operation according to its own contract and continue.
A normal skip is not a failure merely because no work was performed.
Do not promote an optional absence into a run-level prerequisite solely to make
preflight validation more complete.
Likewise, do not silently skip a resource that the script's contract requires
for the requested operation.
The distinction between required and skippable conditions belongs to the
individual script contract.
##### 1.5.4.5 Output and Reporting
Keep installer output proportional to what an operator needs to understand the
run.
Do not add detailed result aggregation merely because individual operation
statuses are available.
Do not add failure counters, warning counters, skipped-item tables, result
objects, summary frameworks, progress frameworks, retry frameworks, or similar
machinery unless the script's actual operational contract requires them.
A normal skip may be silent. When later review benefits from knowing what was
not applied, use a short informational message.
Do not report the same child failure again at the parent layer merely to state
that execution will continue.
The repository-wide logging rules in Section 1.3.3 apply. Prefer natural
language over symbol-heavy status fragments when prose is clearer, but retain
punctuation and symbols when they carry technical meaning or improve clarity.
Do not add symbols solely for visual decoration or status styling.
##### 1.5.4.6 Established Contract and New Installers
Preserve an established installer's continuation, skip, interaction, output,
exit-status, ordering, and side-effect behavior unless changing that behavior
is explicitly part of the work.
When adding a new installer, define its execution contract from the operation
being implemented rather than copying another installer by default.
If an existing execution pattern is suitable, reuse it.
If the new installer has materially different operational requirements, define
the contract that fits those requirements instead of forcing it into an
existing pattern.
Do not expand this policy with a new installer category merely because one new
script differs from existing scripts. Add a repository-wide rule only when the
rule is reusable beyond that individual script.
An uninstaller must preserve log files unless the individual tool's explicit
contract states otherwise.
### 1.6 Documentation
Documentation in this repository has distinct roles. Do not copy the same
level of detail into every document merely to keep them uniform.
#### 1.6.1 Documentation Roles and Sources of Truth
- The implementation establishes what the executable currently does. It is
factual evidence of current behavior, not automatic proof that the behavior
is the intended specification.
- The structured header of an executable is the user-facing interface
documentation for using that executable as a black box.
- `FEATURES.md` is the user-facing index for discovering what capabilities
exist and which executable to inspect.
- The README explains the repository's purpose, installation, initial usage,
and layout.
- This policy records design and development principles that are reusable
across the repository.
- `doc/VERSIONS` records release-level changes rather than implementation
history.
- When implementation and documentation disagree, first establish the current
behavior from the implementation, then compare it with the intended
interface and history. If the implementation is wrong, fix the
implementation. If the implementation is correct, fix the documentation.
Do not make documentation follow accidental behavior merely because that
behavior exists in the current code.
- The roles defined above are the documentation structure of this `scripts`
repository itself.
#### 1.6.2 Executable Header Purpose and Structure
- The header contains the information a user or caller needs to operate the
executable without reading its implementation. Do not use it as a catalogue
of internal implementation details.
- Every executable must contain a structured header block, delimited above and
below by a separator line of 20 or more `#` characters.
- The header contains the following sections, in this order:
`Description`, the standard `Author`, `Source Code`, `License`, `Contact`
block, `Usage`, `Options` (scripts that take options only), `Requirements`,
`Exit Status` (scripts that expose more than one process exit status to the
user or caller), `Version History`.
- `Description` says what the script is, and the identifying block follows it
because it says whose it is. `Usage`, `Options`, and `Exit Status` say how
the script is driven, and come after both.
- Optional sections such as `Notes`, `Example`, `Features`, `Design Notes`,
and configuration-file information are placed where they read best between
`Usage` and `Version History`.
- `Requirements` lists conditions the user must satisfy before execution.
Do not list resources the script creates for itself, purely internal
implementation details, or optional capabilities that the normal run does
not require.
- For Python and Ruby, `Requirements` names the minimum language version that
the executable itself technically requires. That minimum is separate from
the repository-wide fully supported version range.
- A shell script may omit `Requirements` when its external command
prerequisites are already declared by `check_commands` and no user-facing
prerequisite remains to be documented.
- `Exit Status` documents process exit codes whose meanings are part of the
user-facing or caller-facing interface. It does not document return values
used only inside the implementation.
- Name the section that lists process exit codes `Exit Status`. Do not use
`Error Conditions`, `Exit Codes`, or another synonym for new or corrected
header documentation.
- Every line inside the header block is a comment line. A line that would be
blank is written as a bare `#`, never as an empty line and never as `##`.
`check_header_doc.py` checks this formatting rule.
- Header indentation:
- Title line: 1 space after `#`
- Other lines: 2 spaces after `#`
- `Version History` entries are written as `vX.Y YYYY-MM-DD`, newest first,
each followed by an indented description of what changed.
- Each description is at most two lines, and a single line at or under 80
columns is preferred whenever practical.
- The first entry, at the lowest version the file's own history reaches,
reads only `Initial release.` and nothing else.
- "Test Cases" must not be included in production scripts.
- "Test Cases" is used only in dedicated test code.
- Documentation must remain consistent with the intended behavior.
#### 1.6.3 Documentation Update Guidance
Use each document according to its role when deciding whether a change affects
documentation:
- An observable executable-interface change calls for reviewing the executable
header because that header describes the black-box interface.
- A new or changed user-facing capability calls for reviewing `FEATURES.md`
because that file is the discovery index.
- A repository-wide reusable design principle calls for reviewing this policy.
- A change to repository purpose, installation, initial usage, or layout calls
for reviewing the README.
- A release-level change calls for considering `doc/VERSIONS`.
- A configuration-format or configuration-semantics change calls for reviewing
the configuration documentation and the executable header that exposes that
configuration to the user.
This is guidance for choosing the document whose role is affected. It is not a
mechanical update matrix. Do not modify a document merely because another
document changed.
#### 1.6.4 Document Format
- The format of a document is decided by its name, its history, the paths
already published for it, the references that point at it, the
compatibility those references demand, and the role it plays in the
repository. It is not decided by whether its text happens to parse as
Markdown.
- A document that carries `.md` is a Markdown document.
- The extensionless documents here are plain text documents on purpose:
`POLICY`, `LICENSE`, `COPYING`, `COPYING.LESSER`, and `VERSIONS`. Several
of them use headings, bullets, emphasis, or links that a Markdown renderer
would accept, and that changes nothing. Readable structure is not a
declaration of format.
- That a document would render as Markdown is therefore not a reason to
rename it. Neither is uniformity: making the extensions or the appearance
of the documents match each other is not on its own a reason to rename
anything.
#### 1.6.5 Document File Naming
- A document written in Markdown takes a `.md` extension when it is newly
created. This holds for the documents under `doc/` as well: a repository
created from now on names its policy `doc/POLICY.md`.
- The rule applies at creation. It is not applied backwards to documents that
already exist, and an existing document is not brought into line with it.
- The licence files are the exception. `COPYING`, `COPYING.LESSER`, and
`LICENSE` keep the extensionless names by which they are recognised, whether
they are new or not.
- A document that is not Markdown takes no extension, or `.txt`.
- An existing document is not renamed merely to add or change an extension.
Its current path is a published interface referenced by the README and by
external pages, and changing that path can break references that cannot all
be discovered or repaired.
- `doc/POLICY` and `doc/VERSIONS` therefore keep their current names. Naming
uniformity does not outweigh compatibility with their established published
paths.
- Rename an existing document only when a concrete problem caused by its
current name outweighs the compatibility cost of changing its published
path, and only after examining the references that can be identified.
GitHub displaying `doc/POLICY` as plain text is an accepted consequence of
preserving that path.
#### 1.6.6 Document File Attributes
- `.gitattributes` gives `diff=markdown` to `*.md` and to the extensionless
documents that use the Markdown diff driver, so that a diff hunk header
names the section it falls in.
- `diff=markdown` is a diff aid and nothing else. It teaches `git diff` to
recognise headings when it picks a hunk header. It does not change a file's
format, does not change its name, and does not change how GitHub displays
it.
- Giving an extensionless document `diff=markdown` is therefore correct, and
is not evidence that the document should carry `.md`. Reading a diff
through the Markdown driver and maintaining a document as Markdown are
separate decisions.
- A document created with `.md` is covered by the `*.md` line and needs no
entry of its own.
- No file is given `linguist-language`. Nothing in `.gitattributes` makes GitHub
render a document that carries no extension, and setting that attribute
changed how `doc/POLICY` and `doc/LICENSE` are displayed on the web, for the
worse. How a document reads on GitHub is decided by its name, not by an
attribute.
- `doc/VERSIONS` is excluded. It is underlined plain text, and `diff=markdown`
empties the hunk headers that otherwise name the version.
- `doc/COPYING` and `doc/COPYING.LESSER` are excluded as the licence texts.
- The extensionless documents are listed one path at a time. A glob over the
files that carry no extension would catch the `#` comment lines of the
scripts and the configuration files.
#### 1.6.7 Plain Text Document Line Length
- A plain text document is meant to be read with nothing between the reader
and the file, on a fixed-width terminal of 80 columns included. Ordinary
prose is therefore wrapped near 80 columns wherever that is practical.
- The figure is a guide for keeping the text readable, not a check to run
against the file. A line past 80 columns is not by itself a defect, and is
not by itself something to fix.
- Lines that are not ordinary prose may exceed it: URLs, legal wording quoted
as it stands, commands, identifiers, tables, and any line that is clearer
left whole.
- Where wrapping would cost more than it gains, do not wrap. It costs more
when it splits a unit of meaning, when it makes the diff harder to follow,
when it hides a phrase from a search, or when it makes a command awkward to
copy.
- In `doc/LICENSE`, `doc/COPYING`, and `doc/COPYING.LESSER` the exact wording
and the references it carries come first. Those texts are reproduced as
they are issued and are not rewrapped.
- `doc/VERSIONS` follows the rule stated under doc/VERSIONS Structure above.
There, the two-line, 80-column bullet limit applies instead of this
general guidance.
### 1.7 Versioning
#### 1.7.1 When to Bump an Executable Version
- The following rules apply to the version history in each executable's
structured header.
- Repository release versions and Git tags follow the separate rules below.
- Do not bump the version mechanically every time a file is touched. Decide
based on the nature of the change:
- Documentation-only, comment-only, and formatting-only changes (help text,
README/POLICY/VERSIONS wording, whitespace and layout, etc. with no effect
on behavior) do not bump the version.
- A version-only change, such as updating a dependency version, language
runtime version, tool version, or other version number without changing
the surrounding behavior or design, does not by itself require an
additional executable version bump.
- An executable version moves on a version-worthy behavior or
specification change, and the calendar date bounds how many versions may
result. A versioned unit must not have more than one version number for
the same calendar date. This rule has no exception.
- If another version-worthy change is made on a date already used by the
newest entry, do not create another version. Keep one version for that
date and rewrite the existing entry to describe the completed state,
whether or not the new change is related to the one already recorded.
- Separate commits, pull requests, independent features, bug fixes,
security fixes, compatibility changes, breaking changes, or release
units do not permit another version number on the same date. Coherence
and independence may decide pull request scope or bullet grouping, but
they never override the one-version-per-calendar-day rule.
- On a later date, follow-up work that completes, corrects, or hardens the
same newest change unit stays in that version even when it is made in
another commit or pull request. Update the entry date to the date the
completed unit reached its current form and rewrite the description to
summarize the whole change. A later-date change that is independent of
the newest entry starts a new version. The same-date rule above always
applies first.
- An internal cleanup or refactor that preserves observable behavior does
not by itself bump the version.
- If an independent version has intervened, a later fix to an older change
is a new change unit rather than a retroactive rewrite across that
intervening version.
- Finalizing only the release date of an already-added `Version History`
entry (e.g. changing `TBD` to the actual release date) is not by itself
a new change. Classify that entry as version-only or as containing real
changes based on what the entry actually contains, not on the date edit.
#### 1.7.2 Executable Version Numbering
- Versions use a two-level `major.minor` scheme.
- When incrementing `minor` would reach `10`, roll over instead: increment
`major` by 1 and reset `minor` to `0` (e.g. `v0.9` -> `v1.0`,
`v1.9` -> `v2.0`, `v2.9` -> `v3.0`).
- Do not continue `minor` past `9` as in standard semantic versioning
(e.g. do not use `v1.10`, `v1.11`, ...).
- Raising `major` for a reason other than the rollover is a decision the
maintainer makes, not one this document derives from the change.
- Removing or renaming an option, changing what an existing argument means,
changing a default so that an unchanged invocation does something else, and
changing how a path or a configuration value is resolved are all
incompatible changes. Say so in the `Version History` entry, so that the
number the change is released under can be chosen knowing that.
#### 1.7.3 Repository Versioning
- Repository release versions are independent of individual executable versions.
- The maintainer has final authority over the repository release version.
Change classification and the conventions below inform that decision but do
not mechanically determine the final version number.
- Record repository release versions in `doc/VERSIONS` and use the same versions
for Git tags.
- The one-version-per-calendar-day rule of 1.7.1 applies to repository release
versions as well: `doc/VERSIONS` never carries more than one version for the
same calendar date, whatever the independence of the changes released.
- Repository release versions may use a three-level `major.minor.patch` scheme.
- Work that is not released yet takes no version of its own: it belongs to the
entry already standing at the top of `doc/VERSIONS`.
- An unreleased entry carries `(Release Date: TBD)`, and its version number
stays provisional until it ships. An entry opened as
`v1.7.3 (Release Date: TBD)` may be released under a different number once
what accumulated in it is known; which number it takes is decided then.
- Replacing `TBD` with the actual release date is the release itself, not a
change to record in the entry.
- A repository that has not yet made its first release is in its initial
construction stage, and that stage takes no entry here. Typically this is the
state while `v1.0` is the first release and the repository still stands below
it, or `v1.0` itself is unreleased. The changes made while building up to that
release are not accumulated in `doc/VERSIONS` one by one: the file is the
record of released versions, not of the construction that precedes the first
of them, and its first entry is written when that release is made.
- A documentation-only change normally takes no `doc/VERSIONS` entry. The
maintainer decides whether a documentation change is significant enough to
be recorded at release level.
- A version-only change, such as updating a dependency version, language