@homericintelligence/athena-opencode 0.5.0 → 0.5.1
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.
- package/README.md +30 -17
- package/package.json +2 -2
- package/plugin.js +20 -5
- package/skills/TECHNICAL_ENGLISH.md +78 -0
- package/skills/_cli.py +26 -18
- package/skills/_support/docs/dependency-resolution.md +192 -0
- package/skills/_support/docs/policies/development.md +117 -0
- package/skills/_support/docs/policies/evidence-integrity.md +40 -0
- package/skills/_support/docs/principles/README.md +542 -0
- package/skills/_support/docs/principles/details/p001-kiss.md +114 -0
- package/skills/_support/docs/principles/details/p002-yagni.md +109 -0
- package/skills/_support/docs/principles/details/p003-dry.md +126 -0
- package/skills/_support/docs/principles/details/p004-solid.md +165 -0
- package/skills/_support/docs/principles/details/p005-modularity.md +123 -0
- package/skills/_support/docs/principles/details/p006-principle-of-least-astonishment.md +115 -0
- package/skills/_support/docs/principles/details/p007-subtraction-over-addition.md +113 -0
- package/skills/_support/docs/principles/details/p008-understand-before-subtracting.md +115 -0
- package/skills/_support/docs/principles/details/p009-general-mechanisms-over-special-cases.md +108 -0
- package/skills/_support/docs/principles/details/p010-scope-fidelity.md +117 -0
- package/skills/_support/docs/principles/details/p011-minimal-coherent-change.md +128 -0
- package/skills/_support/docs/principles/details/p012-evidence-before-modification.md +115 -0
- package/skills/_support/docs/principles/details/p013-avoid-hasty-abstractions.md +112 -0
- package/skills/_support/docs/principles/details/p014-preserve-unrequested-behavior.md +110 -0
- package/skills/_support/docs/principles/details/p015-architecture-conformance.md +124 -0
- package/skills/_support/docs/principles/details/p016-separation-of-concerns.md +125 -0
- package/skills/_support/docs/principles/details/p017-high-cohesion-low-coupling.md +130 -0
- package/skills/_support/docs/principles/details/p018-information-hiding.md +128 -0
- package/skills/_support/docs/principles/details/p019-explicit-contracts.md +134 -0
- package/skills/_support/docs/principles/details/p020-executable-architecture.md +130 -0
- package/skills/_support/docs/principles/details/p021-evolutionary-and-reversible-design.md +126 -0
- package/skills/_support/docs/principles/details/p022-test-behavior-not-implementation.md +129 -0
- package/skills/_support/docs/principles/details/p023-parameterized-table-driven-testing.md +129 -0
- package/skills/_support/docs/principles/details/p024-boundary-value-testing.md +128 -0
- package/skills/_support/docs/principles/details/p025-property-based-testing-for-invariants.md +133 -0
- package/skills/_support/docs/principles/details/p026-regression-before-repair.md +134 -0
- package/skills/_support/docs/principles/details/p027-deterministic-and-hermetic-tests.md +128 -0
- package/skills/_support/docs/principles/details/p028-test-failure-paths.md +133 -0
- package/skills/_support/docs/principles/details/p029-generalize-error-policy-preserve-specific-cause.md +131 -0
- package/skills/_support/docs/principles/details/p030-nearest-responsible-error-boundary.md +133 -0
- package/skills/_support/docs/principles/details/p031-propagate-rather-than-swallow.md +131 -0
- package/skills/_support/docs/principles/details/p032-handle-once-preserve-causality.md +137 -0
- package/skills/_support/docs/principles/details/p033-state-safe-failure-semantics.md +140 -0
- package/skills/_support/docs/principles/details/p034-fail-fast.md +132 -0
- package/skills/_support/docs/principles/details/p035-fail-secure-fail-closed.md +137 -0
- package/skills/_support/docs/principles/details/p036-graceful-degradation.md +137 -0
- package/skills/_support/docs/principles/details/p037-idempotency-before-retry.md +139 -0
- package/skills/_support/docs/principles/details/p038-bounded-retry.md +145 -0
- package/skills/_support/docs/principles/details/p039-bounded-waiting.md +151 -0
- package/skills/_support/docs/principles/details/p040-bounded-resources.md +138 -0
- package/skills/_support/docs/principles/details/p041-backpressure-and-load-shedding.md +141 -0
- package/skills/_support/docs/principles/details/p042-fault-isolation-bulkheads.md +140 -0
- package/skills/_support/docs/principles/details/p043-circuit-breakers.md +148 -0
- package/skills/_support/docs/principles/details/p044-atomicity-where-possible.md +141 -0
- package/skills/_support/docs/principles/details/p045-compensation-where-atomicity-is-impossible.md +163 -0
- package/skills/_support/docs/principles/details/p046-resumability.md +138 -0
- package/skills/_support/docs/principles/details/p047-observability-is-part-of-correctness.md +134 -0
- package/skills/_support/docs/principles/details/p048-secure-by-design.md +132 -0
- package/skills/_support/docs/principles/details/p049-secure-by-default.md +126 -0
- package/skills/_support/docs/principles/details/p050-least-privilege.md +123 -0
- package/skills/_support/docs/principles/details/p051-complete-mediation.md +124 -0
- package/skills/_support/docs/principles/details/p052-separation-of-duties.md +129 -0
- package/skills/_support/docs/principles/details/p053-validate-at-trust-boundaries.md +126 -0
- package/skills/_support/docs/principles/details/p054-defense-in-depth.md +135 -0
- package/skills/_support/docs/principles/details/p055-minimize-attack-surface.md +133 -0
- package/skills/_support/docs/principles/details/p056-secrets-stay-out-of-code-and-context.md +137 -0
- package/skills/_support/docs/principles/details/p057-supply-chain-integrity.md +135 -0
- package/skills/_support/docs/principles/details/p058-bounded-agent-authority.md +137 -0
- package/skills/_support/docs/principles/details/p059-data-is-not-instruction.md +130 -0
- package/skills/_support/docs/principles/details/p060-constrain-sub-agents.md +136 -0
- package/skills/_support/docs/principles/details/p061-separate-decision-from-high-impact-execution.md +131 -0
- package/skills/_support/docs/principles/details/p062-human-approval-for-irreversible-or-high-risk-actions.md +129 -0
- package/skills/_support/docs/principles/details/p063-requirement-to-code-traceability.md +116 -0
- package/skills/_support/docs/principles/details/p064-requirement-to-test-traceability.md +116 -0
- package/skills/_support/docs/principles/details/p065-verify-before-claiming-completion.md +129 -0
- package/skills/_support/docs/principles/details/p066-preserve-existing-work.md +114 -0
- package/skills/_support/docs/principles/details/p067-no-test-cheating.md +126 -0
- package/skills/_support/docs/principles/details/p068-no-validation-bypass.md +119 -0
- package/skills/_support/docs/principles/details/p069-independent-review-for-high-risk-changes.md +129 -0
- package/skills/_support/docs/principles/details/p070-code-health-must-not-regress.md +116 -0
- package/skills/_support/docs/principles/details/p071-consistency-over-personal-preference.md +117 -0
- package/skills/_support/docs/principles/details/p072-technical-evidence-over-preference.md +122 -0
- package/skills/_support/docs/principles/details/p073-optimize-only-with-evidence.md +123 -0
- package/skills/_support/docs/principles/details/p074-prefer-existing-mechanisms.md +121 -0
- package/skills/_support/docs/principles/details/p075-make-invalid-states-hard-to-represent.md +128 -0
- package/skills/_support/docs/principles/details/p076-parse-then-validate-then-operate.md +133 -0
- package/skills/_support/docs/principles/details/p077-separate-policy-from-mechanism.md +112 -0
- package/skills/_support/docs/principles/details/p078-single-source-of-truth.md +131 -0
- package/skills/_support/docs/principles/details/p079-explicit-ownership-and-lifetimes.md +124 -0
- package/skills/_support/docs/principles/details/p080-make-concurrency-deliberate.md +122 -0
- package/skills/_support/docs/principles/details/p081-forward-progress-with-safety.md +125 -0
- package/skills/_support/docs/principles/details/p082-design-for-cancellation.md +133 -0
- package/skills/_support/docs/principles/details/p083-irreversible-actions-last.md +124 -0
- package/skills/_support/docs/principles/details/p084-prefer-local-reasoning.md +131 -0
- package/skills/_support/docs/principles/details/p085-explicit-is-better-than-implicit.md +123 -0
- package/skills/_support/docs/principles/details/p086-readability-counts.md +113 -0
- package/skills/_support/docs/principles/details/p087-comments-explain-why-code-explains-what.md +120 -0
- package/skills/_support/docs/principles/details/p088-delete-dead-code.md +125 -0
- package/skills/_support/docs/principles/details/p089-delete-obsolete-configuration-and-dependencies.md +125 -0
- package/skills/_support/docs/principles/details/p090-prefer-negative-code.md +134 -0
- package/skills/_support/docs/principles/details/p091-test-driven-development.md +149 -0
- package/skills/_support/docs/review/README.md +59 -0
- package/skills/_support/docs/review/behavior-first-testing.md +73 -0
- package/skills/_support/docs/review/common.md +365 -0
- package/skills/_support/docs/review/design-docs.md +74 -0
- package/skills/_support/docs/review/issue-planning.md +220 -0
- package/skills/_support/docs/review/language-routing.md +141 -0
- package/skills/_support/docs/review/repository-scorecard.md +170 -0
- package/skills/advise/SKILL.md +93 -67
- package/skills/advise/scripts/list_retrievable_skills.py +3 -3
- package/skills/brainstorm/SKILL.md +160 -127
- package/skills/change-review/SKILL.md +91 -71
- package/skills/change-review/references/scope-resolution.md +69 -46
- package/skills/change-review/scripts/resolve_scope.py +119 -73
- package/skills/finalize-plan/SKILL.md +198 -139
- package/skills/git-worktrees/SKILL.md +100 -80
- package/skills/git-worktrees/scripts/prepare_worktree.py +16 -10
- package/skills/issue-review/SKILL.md +136 -79
- package/skills/learn/SKILL.md +276 -186
- package/skills/myrmidon-swarm/SKILL.md +126 -86
- package/skills/plan-issue/SKILL.md +122 -81
- package/skills/pr-review/SKILL.md +164 -114
- package/skills/pr-review/references/criteria.md +58 -37
- package/skills/pr-review/references/delivery.md +156 -98
- package/skills/pr-review/references/evidence.md +265 -148
- package/skills/pr-review/references/prevalidated.md +93 -61
- package/skills/pr-review/scripts/collect_evidence.py +211 -140
- package/skills/pr-review/scripts/diff_context.py +6 -4
- package/skills/pr-review/scripts/materialize_snapshot.py +112 -120
- package/skills/pr-review/scripts/pr_identity.py +27 -15
- package/skills/pr-review/scripts/resolve_pr.py +53 -31
- package/skills/repo-review/SKILL.md +202 -131
- package/skills/systematic-debugging/SKILL.md +210 -192
- package/skills/systematic-debugging/scripts/repository_evidence.py +9 -4
- package/skills/test-driven-development/SKILL.md +126 -96
- package/skills/tidy/SKILL.md +92 -59
- package/skills/tidy/scripts/run_tidy.py +3 -3
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# P027 — Deterministic and Hermetic Tests
|
|
2
|
+
|
|
3
|
+
## Definition
|
|
4
|
+
|
|
5
|
+
Use controlled inputs and isolation from undeclared external influences to make tests repeatable.
|
|
6
|
+
Such influences include time, random values, environment variables, file state, networks, locale,
|
|
7
|
+
concurrency, and shared services.
|
|
8
|
+
|
|
9
|
+
Tests must give the same result for each execution order.
|
|
10
|
+
|
|
11
|
+
**Aliases:** isolated tests, reproducible tests, hermetic testing.
|
|
12
|
+
|
|
13
|
+
## Provenance
|
|
14
|
+
|
|
15
|
+
**Classification:** established principle.
|
|
16
|
+
|
|
17
|
+
Software test guidance includes determinism and isolation. Large build and test systems use the term
|
|
18
|
+
"hermetic." No one source gives this full formulation.
|
|
19
|
+
|
|
20
|
+
## Decision rule
|
|
21
|
+
|
|
22
|
+
If an external input can change a result, the test must declare or control that input. The test must
|
|
23
|
+
not share mutable state between executions.
|
|
24
|
+
|
|
25
|
+
## How to apply
|
|
26
|
+
|
|
27
|
+
- If a clock, random generator, environment, or external client can change a result, inject that input.
|
|
28
|
+
- Use temporary resources for each test and deterministic cleanup.
|
|
29
|
+
- Use controlled local substitutes, not live networks. Use live networks only in specified
|
|
30
|
+
integration or acceptance suites.
|
|
31
|
+
- Record seeds and schedules for generated or concurrent tests. Preserve each counterexample.
|
|
32
|
+
- Run tests individually and in different orders.
|
|
33
|
+
- If the test harness can run tests in parallel, use parallel execution.
|
|
34
|
+
|
|
35
|
+
## Diagram
|
|
36
|
+
|
|
37
|
+
```mermaid
|
|
38
|
+
flowchart LR
|
|
39
|
+
Test["Test case"] --> Inputs["Declare or control inputs"]
|
|
40
|
+
Inputs --> Isolate["Isolate mutable resources"]
|
|
41
|
+
Isolate --> Execute["Run many times"]
|
|
42
|
+
Execute --> Same{"Same result?"}
|
|
43
|
+
Same -->|Yes| Valid["Repeatable test"]
|
|
44
|
+
Same -->|No| Influence["Find undeclared influence"]
|
|
45
|
+
Influence --> Inputs
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Language examples
|
|
49
|
+
|
|
50
|
+
The two examples inject the same clock value and do not read the wall clock.
|
|
51
|
+
|
|
52
|
+
Python:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
def expired(expires_at: int, now: int) -> bool:
|
|
56
|
+
return now >= expires_at
|
|
57
|
+
|
|
58
|
+
def test_expiry_with_controlled_time() -> None:
|
|
59
|
+
assert expired(100, 100)
|
|
60
|
+
assert not expired(101, 100)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Rust:
|
|
64
|
+
|
|
65
|
+
```rust
|
|
66
|
+
fn expired(expires_at: u64, now: u64) -> bool {
|
|
67
|
+
now >= expires_at
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[test]
|
|
71
|
+
fn expiry_with_controlled_time() {
|
|
72
|
+
assert!(expired(100, 100));
|
|
73
|
+
assert!(!expired(101, 100));
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Boundaries and tensions
|
|
78
|
+
|
|
79
|
+
Deterministic and hermetic are related but different. If a fixed external service gives the same
|
|
80
|
+
input, a nonhermetic test can be deterministic. A hermetic test can use uncontrolled random values.
|
|
81
|
+
|
|
82
|
+
Full isolation can hide integration failures. Keep specified nonhermetic suites in
|
|
83
|
+
controlled environments. Retries can diagnose flaky behavior, but they cannot make an unreliable
|
|
84
|
+
test reliable.
|
|
85
|
+
|
|
86
|
+
## Examples
|
|
87
|
+
|
|
88
|
+
### Positive application
|
|
89
|
+
|
|
90
|
+
A cache-expiry test receives a fake clock and a new temporary directory. The test controls clock
|
|
91
|
+
changes. Machine time zone and test order cannot change the result.
|
|
92
|
+
|
|
93
|
+
### Misuse or counterexample
|
|
94
|
+
|
|
95
|
+
A test uses a public web service and retries until success. Availability, remote data, DNS, and
|
|
96
|
+
rate limits control the result.
|
|
97
|
+
|
|
98
|
+
### Athena or agent workflow
|
|
99
|
+
|
|
100
|
+
A repository validator test creates an isolated fixture tree and supplies each environment value.
|
|
101
|
+
The helper does not use a user checkout or network credentials.
|
|
102
|
+
|
|
103
|
+
## Related principles
|
|
104
|
+
|
|
105
|
+
- [P022 — Test Behavior, Not Implementation](p022-test-behavior-not-implementation.md)
|
|
106
|
+
- [P025 — Property-Based Testing for Invariants](p025-property-based-testing-for-invariants.md)
|
|
107
|
+
- [P028 — Test Failure Paths, Not Just Success Paths](p028-test-failure-paths.md)
|
|
108
|
+
|
|
109
|
+
## References
|
|
110
|
+
|
|
111
|
+
### Source information
|
|
112
|
+
|
|
113
|
+
- [Google Testing Blog, "Hermetic Servers" (2012)](https://testing.googleblog.com/2012/10/hermetic-servers.html)
|
|
114
|
+
gives information about isolation from live network dependencies in end-to-end tests.
|
|
115
|
+
|
|
116
|
+
### Applicable information
|
|
117
|
+
|
|
118
|
+
- [Bazel, "Hermeticity"](https://bazel.build/basics/hermeticity)
|
|
119
|
+
gives declared inputs, fixed environment properties, and isolation for deterministic results.
|
|
120
|
+
- [Microsoft, ".NET unit testing best practices"](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices)
|
|
121
|
+
gives isolation and repeatability as core test characteristics.
|
|
122
|
+
|
|
123
|
+
### More information
|
|
124
|
+
|
|
125
|
+
- [Google Testing Blog, "Flaky Tests at Google and How We Mitigate Them" (2016)](https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html)
|
|
126
|
+
gives causes and operational costs of nondeterministic test results.
|
|
127
|
+
|
|
128
|
+
[Back to the engineering principles catalog](../README.md#p027)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# P028 — Test Failure Paths, Not Just Success Paths
|
|
2
|
+
|
|
3
|
+
## Definition
|
|
4
|
+
|
|
5
|
+
Verify system behavior for malformed inputs, failed dependencies, and failed operations. Include
|
|
6
|
+
cancellation, timeout, progress before failure, unavailability, retry, cleanup,
|
|
7
|
+
authorization, and resource exhaustion.
|
|
8
|
+
|
|
9
|
+
Verify success behavior with its own baseline.
|
|
10
|
+
|
|
11
|
+
**Aliases:** negative testing, robustness testing, error-path testing.
|
|
12
|
+
|
|
13
|
+
## Provenance
|
|
14
|
+
|
|
15
|
+
**Classification:** established principle.
|
|
16
|
+
|
|
17
|
+
Sources for negative tests and robustness tests include reliability, security, and protocol work.
|
|
18
|
+
No one source gives all failure conditions in this principle.
|
|
19
|
+
|
|
20
|
+
## Decision rule
|
|
21
|
+
|
|
22
|
+
Find failures that can occur for each material dependency or state transition. Verify the contract
|
|
23
|
+
result, preserved invariants, cleanup, and diagnostic evidence.
|
|
24
|
+
|
|
25
|
+
## How to apply
|
|
26
|
+
|
|
27
|
+
- Use contracts and architecture to find failures. Do not use coverage percentages.
|
|
28
|
+
- Inject dependency errors, timeouts, and cancellations.
|
|
29
|
+
- After an operation makes progress, inject a stop.
|
|
30
|
+
- After failure, assert caller-visible error semantics and durable state.
|
|
31
|
+
- Verify resource release. Keep retries and compensation in their budgets.
|
|
32
|
+
- Include authorization denial and malformed untrusted input without live operations that are not safe.
|
|
33
|
+
|
|
34
|
+
## Diagram
|
|
35
|
+
|
|
36
|
+
```mermaid
|
|
37
|
+
flowchart LR
|
|
38
|
+
Boundary["Dependency or state transition"] --> Failures["Find possible failures"]
|
|
39
|
+
Failures --> Inject["Inject controlled fault"]
|
|
40
|
+
Inject --> Result["Assert error contract"]
|
|
41
|
+
Result --> State["Verify state and cleanup"]
|
|
42
|
+
State --> Budget["Verify retry or compensation budget"]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Language examples
|
|
46
|
+
|
|
47
|
+
The two examples simulate a write failure, preserve the previous data, and return the cause.
|
|
48
|
+
|
|
49
|
+
Python:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from collections.abc import Callable
|
|
53
|
+
def replace(current: str, write: Callable[[], str]) -> tuple[str, Exception | None]:
|
|
54
|
+
try:
|
|
55
|
+
return write(), None
|
|
56
|
+
except OSError as cause:
|
|
57
|
+
return current, cause
|
|
58
|
+
def test_failure_preserves_current() -> None:
|
|
59
|
+
cause = OSError("disk full")
|
|
60
|
+
def fail() -> str:
|
|
61
|
+
raise cause
|
|
62
|
+
value, error = replace("old", fail)
|
|
63
|
+
assert value == "old" and error is cause
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Rust:
|
|
67
|
+
|
|
68
|
+
```rust
|
|
69
|
+
fn replace<F>(current: &str, write: F) -> (String, Option<&'static str>)
|
|
70
|
+
where F: FnOnce() -> Result<String, &'static str> {
|
|
71
|
+
match write() {
|
|
72
|
+
Ok(value) => (value, None),
|
|
73
|
+
Err(cause) => (current.to_owned(), Some(cause)),
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
#[test]
|
|
77
|
+
fn failure_preserves_current() {
|
|
78
|
+
let result = replace("old", || Err("disk full"));
|
|
79
|
+
assert_eq!(result, ("old".to_owned(), Some("disk full")));
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Boundaries and tensions
|
|
84
|
+
|
|
85
|
+
Do not cause destructive production failures for test evidence. Use controlled substitutes,
|
|
86
|
+
sandboxes, fault injection, or staged environments that the approved risk policy specifies.
|
|
87
|
+
|
|
88
|
+
Mock failures can be different from dependency behavior in production. When mock behavior changes
|
|
89
|
+
the contract, add contract or integration evidence. Do not show sensitive internal details in caller-visible errors.
|
|
90
|
+
|
|
91
|
+
## Examples
|
|
92
|
+
|
|
93
|
+
### Positive application
|
|
94
|
+
|
|
95
|
+
A file replacement test injects a write failure after the write operation creates temporary output. It verifies that
|
|
96
|
+
the previous file is correct. It also verifies cleanup and cause preservation.
|
|
97
|
+
|
|
98
|
+
### Misuse or counterexample
|
|
99
|
+
|
|
100
|
+
A client has many success-path tests but no test for a timeout after a committed write. An automatic
|
|
101
|
+
retry can duplicate the operation.
|
|
102
|
+
|
|
103
|
+
### Athena or agent workflow
|
|
104
|
+
|
|
105
|
+
A skill test simulates a missing necessary capability. It verifies a clear, safe failure response.
|
|
106
|
+
The test verifies that the skill does not skip work or return success without evidence.
|
|
107
|
+
|
|
108
|
+
## Related principles
|
|
109
|
+
|
|
110
|
+
- [P022 — Test Behavior, Not Implementation](p022-test-behavior-not-implementation.md)
|
|
111
|
+
- [P027 — Deterministic and Hermetic Tests](p027-deterministic-and-hermetic-tests.md)
|
|
112
|
+
- [P030 — Handle Errors at the Nearest Responsible Boundary](p030-nearest-responsible-error-boundary.md)
|
|
113
|
+
|
|
114
|
+
## References
|
|
115
|
+
|
|
116
|
+
### Source information
|
|
117
|
+
|
|
118
|
+
- [NIST, "An Approach for Analyzing the Robustness of Windows NT Software" (1998)](https://csrc.nist.gov/files/pubs/conference/1998/10/08/proceedings-of-the-21st-nissc-1998/final/docs/paperf8.pdf)
|
|
119
|
+
gives robustness tests with accepted inputs, rejected inputs, and exception conditions.
|
|
120
|
+
|
|
121
|
+
### Applicable information
|
|
122
|
+
|
|
123
|
+
- [Google Engineering Practices, "What to look for in a code review"](https://google.github.io/eng-practices/review/reviewer/looking-for.html)
|
|
124
|
+
says tests must detect broken behavior.
|
|
125
|
+
- [OWASP Web Security Testing Guide, "Testing for Error Handling"](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/08-Testing_for_Error_Handling/README)
|
|
126
|
+
gives security tests for incorrect error responses and stack disclosure.
|
|
127
|
+
|
|
128
|
+
### More information
|
|
129
|
+
|
|
130
|
+
- [OWASP, "Business Logic Security Cheat Sheet"](https://cheatsheetseries.owasp.org/cheatsheets/Business_Logic_Security_Cheat_Sheet.html)
|
|
131
|
+
adds adversarial cases for incorrect order, steps that occur again, concurrency, and rule bypass.
|
|
132
|
+
|
|
133
|
+
[Back to the engineering principles catalog](../README.md#p028)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# P029 — Generalize Error Policy; Preserve Specific Cause
|
|
2
|
+
|
|
3
|
+
## Definition
|
|
4
|
+
|
|
5
|
+
Use a small taxonomy for stable errors at architectural boundaries. Preserve the initial cause,
|
|
6
|
+
stack, and safe structured context for diagnosis.
|
|
7
|
+
|
|
8
|
+
A general policy helps callers select a response. A specific cause helps operators diagnose the
|
|
9
|
+
failed event.
|
|
10
|
+
|
|
11
|
+
**Aliases:** error translation with cause preservation, layered error taxonomy.
|
|
12
|
+
|
|
13
|
+
## Provenance
|
|
14
|
+
|
|
15
|
+
**Classification:** Athena synthesis.
|
|
16
|
+
|
|
17
|
+
Sources are different for exception chains, causal errors, and protocol problem taxonomies.
|
|
18
|
+
No historical source gives this full rule.
|
|
19
|
+
|
|
20
|
+
## Decision rule
|
|
21
|
+
|
|
22
|
+
At a boundary that owns caller policy, translate a low-level failure. Map the failure to stable
|
|
23
|
+
caller policy. Attach the initial cause with non-sensitive diagnostic context.
|
|
24
|
+
|
|
25
|
+
## How to apply
|
|
26
|
+
|
|
27
|
+
- Give a bounded error set that lets callers select responses, for example rejected input, unavailable,
|
|
28
|
+
or conflict.
|
|
29
|
+
- Translate vendor and transport failures at the boundary that owns the public contract.
|
|
30
|
+
- Preserve causal links and structured fields for operators.
|
|
31
|
+
- Keep retry status and user messages different from exception text that can change.
|
|
32
|
+
- Remove secrets and internal details from public results. Keep safe internal evidence.
|
|
33
|
+
|
|
34
|
+
## Diagram
|
|
35
|
+
|
|
36
|
+
```mermaid
|
|
37
|
+
flowchart LR
|
|
38
|
+
Failure["Low-level failure"] --> Boundary{"Boundary owns caller policy?"}
|
|
39
|
+
Boundary -->|No| Propagate["Preserve and propagate cause"]
|
|
40
|
+
Boundary -->|Yes| Policy["Map to stable error policy"]
|
|
41
|
+
Policy --> Public["Return safe public error"]
|
|
42
|
+
Policy --> Evidence["Keep specific internal cause"]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Language examples
|
|
46
|
+
|
|
47
|
+
The two examples map a storage timeout to an unavailable error and preserve the specific cause.
|
|
48
|
+
|
|
49
|
+
Python:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
class StorageTimeout(RuntimeError):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
class UnavailableError(RuntimeError):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def load(fetch):
|
|
59
|
+
try:
|
|
60
|
+
return fetch()
|
|
61
|
+
except StorageTimeout as cause:
|
|
62
|
+
raise UnavailableError("storage unavailable") from cause
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Rust:
|
|
66
|
+
|
|
67
|
+
```rust
|
|
68
|
+
#[derive(Debug)]
|
|
69
|
+
struct StorageTimeout;
|
|
70
|
+
|
|
71
|
+
#[derive(Debug)]
|
|
72
|
+
struct UnavailableError { message: &'static str, source: StorageTimeout }
|
|
73
|
+
|
|
74
|
+
fn load(result: Result<String, StorageTimeout>) -> Result<String, UnavailableError> {
|
|
75
|
+
result.map_err(|source| UnavailableError { message: "storage unavailable", source })
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Boundaries and tensions
|
|
80
|
+
|
|
81
|
+
One `operation failed` result is too general when callers must select different responses. Each
|
|
82
|
+
dependency exception is too specific and couples consumers to internal details.
|
|
83
|
+
|
|
84
|
+
Cause preservation does not authorize disclosure of stacks, paths, queries, credentials, or
|
|
85
|
+
personal data. Use approved decisions to change stable taxonomies. Do not add one type for
|
|
86
|
+
each event.
|
|
87
|
+
|
|
88
|
+
## Examples
|
|
89
|
+
|
|
90
|
+
### Positive application
|
|
91
|
+
|
|
92
|
+
A storage timeout becomes a public `temporarily-unavailable` problem. The response includes a
|
|
93
|
+
correlation ID and retry policy. The internal record preserves the driver error and operation context.
|
|
94
|
+
|
|
95
|
+
### Misuse or counterexample
|
|
96
|
+
|
|
97
|
+
A handler converts each exception to a `null` response with success status. This conversion erases the
|
|
98
|
+
error policy and cause. A second handler sends the full database stack to the client.
|
|
99
|
+
|
|
100
|
+
### Athena or agent workflow
|
|
101
|
+
|
|
102
|
+
A dependency helper returns one specified unavailable status. Its diagnostic record identifies
|
|
103
|
+
the checkout, command, exit status, and causal error. The record excludes secrets.
|
|
104
|
+
|
|
105
|
+
## Related principles
|
|
106
|
+
|
|
107
|
+
- [P019 — Explicit Contracts](p019-explicit-contracts.md)
|
|
108
|
+
- [P028 — Test Failure Paths, Not Just Success Paths](p028-test-failure-paths.md)
|
|
109
|
+
- [P030 — Handle Errors at the Nearest Responsible Boundary](p030-nearest-responsible-error-boundary.md)
|
|
110
|
+
|
|
111
|
+
## References
|
|
112
|
+
|
|
113
|
+
### Source information
|
|
114
|
+
|
|
115
|
+
- [PEP 3134, "Exception Chaining and Embedded Tracebacks" (2005)](https://peps.python.org/pep-3134/)
|
|
116
|
+
records explicit causal links and context for a new exception after a second exception.
|
|
117
|
+
|
|
118
|
+
### Applicable information
|
|
119
|
+
|
|
120
|
+
- [RFC 9457, "Problem Details for HTTP APIs" (2023)](https://www.rfc-editor.org/rfc/rfc9457.html)
|
|
121
|
+
gives stable problem types and event-specific details. The RFC gives a warning about exposure of
|
|
122
|
+
implementation details.
|
|
123
|
+
- [Python documentation, "Exception context"](https://docs.python.org/3/library/exceptions.html#exception-context)
|
|
124
|
+
gives implicit and explicit exception chain semantics.
|
|
125
|
+
|
|
126
|
+
### More information
|
|
127
|
+
|
|
128
|
+
- [OWASP, "Error Handling Cheat Sheet"](https://cheatsheetseries.owasp.org/cheatsheets/Error_Handling_Cheat_Sheet.html)
|
|
129
|
+
shows the difference between safe external errors and detailed internal diagnostics for investigation.
|
|
130
|
+
|
|
131
|
+
[Back to the engineering principles catalog](../README.md#p029)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# P030 — Handle Errors at the Nearest Responsible Boundary
|
|
2
|
+
|
|
3
|
+
## Definition
|
|
4
|
+
|
|
5
|
+
Handle an error only where policy and context are sufficient for correct recovery, retry,
|
|
6
|
+
compensation, translation, or termination. A lower layer frequently detects a failure first.
|
|
7
|
+
|
|
8
|
+
When the lower layer cannot select the correct result, it must propagate the failure.
|
|
9
|
+
|
|
10
|
+
**Aliases:** catch where you can handle, responsible error boundary.
|
|
11
|
+
|
|
12
|
+
## Provenance
|
|
13
|
+
|
|
14
|
+
**Classification:** practitioner heuristic.
|
|
15
|
+
|
|
16
|
+
Exception systems use stack-based handler selection. Language communities gave related rules
|
|
17
|
+
about error propagation. No one source first gave this formulation.
|
|
18
|
+
|
|
19
|
+
## Decision rule
|
|
20
|
+
|
|
21
|
+
At the first boundary that can satisfy the caller's contract and preserve correct state, handle the
|
|
22
|
+
failure. If the boundary cannot satisfy the contract or preserve correct state, add applicable safe
|
|
23
|
+
context. After the boundary adds the context, propagate the failure.
|
|
24
|
+
|
|
25
|
+
## How to apply
|
|
26
|
+
|
|
27
|
+
- Detection, cleanup, translation, recovery, and caller presentation are different operations.
|
|
28
|
+
- When a layer owns a recovery or contract decision, the layer can catch the error.
|
|
29
|
+
- Use deterministic cleanup constructs. Cleanup does not recover the operation.
|
|
30
|
+
- After translation, preserve the cause. Do not record the same failure at each layer.
|
|
31
|
+
- Put retries at the boundary that knows idempotency, deadlines, and dependency semantics.
|
|
32
|
+
|
|
33
|
+
## Diagram
|
|
34
|
+
|
|
35
|
+
```mermaid
|
|
36
|
+
flowchart LR
|
|
37
|
+
Detect["Layer detects failure"] --> Decide{"Boundary owns result policy?"}
|
|
38
|
+
Decide -->|No| Context["Add applicable safe context"]
|
|
39
|
+
Context --> Propagate["Propagate failure"]
|
|
40
|
+
Decide -->|Yes| Policy["Apply boundary policy"]
|
|
41
|
+
Policy --> Action["Recover, retry, translate, or terminate"]
|
|
42
|
+
Action --> State["Preserve correct state"]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Language examples
|
|
46
|
+
|
|
47
|
+
The two examples put retry policy in the caller of a low-level read operation that propagates its
|
|
48
|
+
error.
|
|
49
|
+
|
|
50
|
+
Python:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
def read_once(client):
|
|
54
|
+
return client.read()
|
|
55
|
+
|
|
56
|
+
def read_with_retry(client):
|
|
57
|
+
try:
|
|
58
|
+
return read_once(client)
|
|
59
|
+
except TimeoutError:
|
|
60
|
+
return read_once(client)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Rust:
|
|
64
|
+
|
|
65
|
+
```rust
|
|
66
|
+
trait Client { fn read(&mut self) -> Result<String, TimeoutError>; }
|
|
67
|
+
struct TimeoutError;
|
|
68
|
+
fn read_once(client: &mut impl Client) -> Result<String, TimeoutError> {
|
|
69
|
+
client.read()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn read_with_retry(client: &mut impl Client) -> Result<String, TimeoutError> {
|
|
73
|
+
match read_once(client) {
|
|
74
|
+
Ok(value) => Ok(value),
|
|
75
|
+
Err(_) => read_once(client),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Boundaries and tensions
|
|
81
|
+
|
|
82
|
+
The first `catch` site is not always the responsible boundary. A top-level boundary can convert an
|
|
83
|
+
unhandled error to a process exit, protocol response, or job status.
|
|
84
|
+
|
|
85
|
+
A lower-level operation can do cleanup and preserve the failure. Security policy can make a generic
|
|
86
|
+
public result and restricted internal diagnostics necessary.
|
|
87
|
+
|
|
88
|
+
## Examples
|
|
89
|
+
|
|
90
|
+
### Positive application
|
|
91
|
+
|
|
92
|
+
A repository propagates a database timeout. The service knows that the operation is read-only and
|
|
93
|
+
has a deadline. It makes one bounded retry.
|
|
94
|
+
|
|
95
|
+
The API boundary converts an exhausted retry budget to a public error with an unavailable status.
|
|
96
|
+
|
|
97
|
+
### Misuse or counterexample
|
|
98
|
+
|
|
99
|
+
A low-level parser catches each exception, records it, and returns an empty object. Callers cannot
|
|
100
|
+
tell permitted empty input from corrupt input.
|
|
101
|
+
|
|
102
|
+
### Athena or agent workflow
|
|
103
|
+
|
|
104
|
+
A helper returns a typed capability failure. The skill owns fallback policy. It selects the
|
|
105
|
+
specified degraded path or a safe stop and returns the result.
|
|
106
|
+
|
|
107
|
+
## Related principles
|
|
108
|
+
|
|
109
|
+
- [P028 — Test Failure Paths, Not Just Success Paths](p028-test-failure-paths.md)
|
|
110
|
+
- [P029 — Generalize Error Policy; Preserve Specific Cause](p029-generalize-error-policy-preserve-specific-cause.md)
|
|
111
|
+
- [P019 — Explicit Contracts](p019-explicit-contracts.md)
|
|
112
|
+
|
|
113
|
+
## References
|
|
114
|
+
|
|
115
|
+
### Source information
|
|
116
|
+
|
|
117
|
+
- [Goodenough, "Exception Handling: Issues and a Proposed Notation" (1975)](https://doi.org/10.1145/361227.361230)
|
|
118
|
+
gives a full treatment from 1975 of exception detection, handler selection, and recovery.
|
|
119
|
+
|
|
120
|
+
### Applicable information
|
|
121
|
+
|
|
122
|
+
- [Microsoft, ".NET best practices for exceptions"](https://learn.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions)
|
|
123
|
+
recommends a catch for recovery or cleanup. When code at the boundary cannot recover, the guidance
|
|
124
|
+
recommends propagation.
|
|
125
|
+
- [Microsoft, "Exceptions and Exception Handling"](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/)
|
|
126
|
+
says that a handler rethrows if it cannot keep the application in a known state.
|
|
127
|
+
|
|
128
|
+
### More information
|
|
129
|
+
|
|
130
|
+
- [Go Blog, "Error handling and Go"](https://go.dev/blog/error-handling-and-go)
|
|
131
|
+
shows explicit propagation and caller-related context without loss of the failure.
|
|
132
|
+
|
|
133
|
+
[Back to the engineering principles catalog](../README.md#p030)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# P031 — Propagate Rather Than Swallow
|
|
2
|
+
|
|
3
|
+
## Definition
|
|
4
|
+
|
|
5
|
+
If a layer cannot satisfy all specified postconditions after recovery, it must propagate the
|
|
6
|
+
failure to a responsible boundary. The layer can use its specified interface to return, throw, or
|
|
7
|
+
signal the failure.
|
|
8
|
+
|
|
9
|
+
The layer must not give a success result, return an empty value, or continue work after a failure.
|
|
10
|
+
The contract must show the difference between success and a failure that the caller must handle.
|
|
11
|
+
|
|
12
|
+
**Aliases:** error propagation, no silent catch, surface unrecovered failures
|
|
13
|
+
|
|
14
|
+
## Provenance
|
|
15
|
+
|
|
16
|
+
**Classification:** established principle.
|
|
17
|
+
|
|
18
|
+
Authors wrote about structured exception handling for many years. Athena cannot identify one source
|
|
19
|
+
for this phrase. Athena uses it as a language-neutral decision rule.
|
|
20
|
+
|
|
21
|
+
## Decision rule
|
|
22
|
+
|
|
23
|
+
If this boundary can satisfy its specified postconditions or give a specified alternative
|
|
24
|
+
result, handle the failure. If the boundary cannot do this, propagate the failure and keep its cause.
|
|
25
|
+
|
|
26
|
+
## How to apply
|
|
27
|
+
|
|
28
|
+
- Give failure behavior in return types, exceptions, result objects, exit statuses, or
|
|
29
|
+
protocol responses.
|
|
30
|
+
- When the layer has the necessary policy context, it can retry, compensate, translate, or stop
|
|
31
|
+
after a failure.
|
|
32
|
+
- Keep the initial cause during error translation. Add stable context that helps the caller.
|
|
33
|
+
- Give fallback values in the interface. When the contract includes an alternative outcome, give
|
|
34
|
+
its fallback as a success result.
|
|
35
|
+
- Make sure that dependency failures give a public failure outcome and keep correct state.
|
|
36
|
+
|
|
37
|
+
## Diagram
|
|
38
|
+
|
|
39
|
+
```mermaid
|
|
40
|
+
flowchart TD
|
|
41
|
+
A["An operation detects a failure"] --> B{"Can this boundary satisfy the contract?"}
|
|
42
|
+
B -- "Yes" --> C["Recover or return a specified alternative result"]
|
|
43
|
+
B -- "No" --> D["Add safe context and keep the cause"]
|
|
44
|
+
D --> E["Propagate the failure to a responsible boundary"]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Language examples
|
|
48
|
+
|
|
49
|
+
Each example adds caller context and keeps a failure outcome.
|
|
50
|
+
|
|
51
|
+
### Python
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
def load_record(store, key):
|
|
55
|
+
try:
|
|
56
|
+
return store.read(key)
|
|
57
|
+
except StorageError as error:
|
|
58
|
+
raise RecordLoadError(key) from error
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Rust
|
|
62
|
+
|
|
63
|
+
```rust
|
|
64
|
+
fn load_record(store: &Store, key: &str) -> Result<Record, RecordLoadError> {
|
|
65
|
+
store
|
|
66
|
+
.read(key)
|
|
67
|
+
.map_err(|source| RecordLoadError::new(key, source))
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Boundaries and tensions
|
|
72
|
+
|
|
73
|
+
A boundary can translate an internal error into a stable taxonomy. It must keep the causal chain.
|
|
74
|
+
Use [P032](p032-handle-once-preserve-causality.md) for cause preservation.
|
|
75
|
+
|
|
76
|
+
[P036](p036-graceful-degradation.md) gives conditions for a safe reduced mode. The mode is
|
|
77
|
+
applicable only when the contract includes it as an alternative outcome.
|
|
78
|
+
|
|
79
|
+
Use [P035](p035-fail-secure-fail-closed.md) for security uncertainty. Do not show secrets or
|
|
80
|
+
credentials. If the caller contract does not include internal information, do not show it. Keep
|
|
81
|
+
necessary cause data in protected diagnostics.
|
|
82
|
+
|
|
83
|
+
## Examples
|
|
84
|
+
|
|
85
|
+
### Positive application
|
|
86
|
+
|
|
87
|
+
A repository adapter cannot read a necessary record. It attaches the record identifier to the
|
|
88
|
+
storage error and returns the error. The service boundary maps the failure to its specified API
|
|
89
|
+
error. Protected diagnostics keep the initial cause.
|
|
90
|
+
|
|
91
|
+
### Misuse or counterexample
|
|
92
|
+
|
|
93
|
+
A configuration loader catches each parse error and returns an empty configuration. Startup gives
|
|
94
|
+
a success result. The service operates with invalid values. Engineers cannot find the initial
|
|
95
|
+
failure.
|
|
96
|
+
|
|
97
|
+
### Athena or agent workflow
|
|
98
|
+
|
|
99
|
+
If a necessary validation command returns a nonzero status, an Athena workflow records the command
|
|
100
|
+
and its results. The workflow does not give a success result because other checks had correct results.
|
|
101
|
+
|
|
102
|
+
## Related principles
|
|
103
|
+
|
|
104
|
+
- [P032 — Handle Once; Preserve Causality](p032-handle-once-preserve-causality.md)
|
|
105
|
+
- [P033 — State-Safe Failure Semantics](p033-state-safe-failure-semantics.md)
|
|
106
|
+
- [P034 — Fail Fast](p034-fail-fast.md)
|
|
107
|
+
- [P036 — Graceful Degradation](p036-graceful-degradation.md)
|
|
108
|
+
|
|
109
|
+
## References
|
|
110
|
+
|
|
111
|
+
### Source information
|
|
112
|
+
|
|
113
|
+
- [John B. Goodenough, “Structured Exception Handling” (1975)](https://doi.org/10.1145/512976.512997)
|
|
114
|
+
— a 1975 analysis of exception conditions and control transfer to a handler with the necessary
|
|
115
|
+
context. This source is not the source of Athena's phrase.
|
|
116
|
+
|
|
117
|
+
### Applicable information
|
|
118
|
+
|
|
119
|
+
- [The Rust Programming Language: Propagating Errors](https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#propagating-errors)
|
|
120
|
+
— official guidance for a callee that returns an error when its caller has the necessary decision
|
|
121
|
+
context.
|
|
122
|
+
- [C++ Core Guidelines, Error Handling](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-errors)
|
|
123
|
+
— guidance that recommends a specified error strategy. It recommends no handler in each
|
|
124
|
+
function.
|
|
125
|
+
|
|
126
|
+
### More information
|
|
127
|
+
|
|
128
|
+
- [P029 — Generalize Error Policy; Preserve Specific Cause](../README.md#p029) — the catalog rule
|
|
129
|
+
for error translation that keeps diagnostic specificity.
|
|
130
|
+
|
|
131
|
+
[Back to the engineering principles catalog](../README.md#p031)
|