otto 2.9.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +5 -0
  3. data/.github/workflows/ci.yml +11 -8
  4. data/.github/workflows/claude-code-review.yml +38 -13
  5. data/.github/workflows/claude.yml +10 -8
  6. data/.github/workflows/code-smells.yml +5 -5
  7. data/.github/workflows/release-gem.yml +2 -2
  8. data/.github/workflows/ruby-lint.yml +3 -3
  9. data/.github/workflows/yardoc.yml +5 -5
  10. data/.gitignore +1 -5
  11. data/.rubocop_todo.yml +8 -5
  12. data/AGENTS.md +40 -1
  13. data/CHANGELOG.rst +235 -0
  14. data/Gemfile +3 -3
  15. data/Gemfile.lock +11 -15
  16. data/README.md +75 -37
  17. data/docs/README.md +166 -0
  18. data/docs/adr/README.md +16 -0
  19. data/docs/adr/adr-001-route-authentication-at-handler-boundary.md +38 -0
  20. data/docs/adr/adr-002-multi-strategy-authentication-and-authorization.md +50 -0
  21. data/docs/adr/adr-003-caddy-tls-route-based-integration.md +48 -0
  22. data/docs/adr/adr-004-separate-compatibility-from-security-maintenance.md +58 -0
  23. data/docs/guides/authentication.md +377 -0
  24. data/docs/guides/caddy-tls.md +205 -0
  25. data/docs/guides/configuration_freezing.md +157 -0
  26. data/docs/guides/enrichment.md +161 -0
  27. data/docs/guides/forwarded-authority.md +249 -0
  28. data/docs/guides/geo-country.md +168 -0
  29. data/docs/guides/ip_privacy.md +39 -0
  30. data/docs/guides/ipaddr-encoding-quirk.md +56 -0
  31. data/docs/guides/mcp.md +282 -0
  32. data/docs/guides/privacy.md +193 -0
  33. data/docs/guides/routing.md +293 -0
  34. data/docs/guides/structured_logging.md +281 -0
  35. data/docs/guides/testing-guide.md +391 -0
  36. data/docs/maintainers/github-actions.md +41 -0
  37. data/docs/maintainers/investigations/.gitignore +2 -0
  38. data/docs/migrating/v2.0.0.md +337 -0
  39. data/docs/reference/authentication.md +290 -0
  40. data/docs/reference/route-syntax.md +181 -0
  41. data/docs/reference/runtime-and-dependency-security.md +86 -0
  42. data/examples/advanced_routes/README.md +43 -57
  43. data/examples/authentication_strategies/README.md +37 -196
  44. data/examples/basic/README.md +24 -39
  45. data/examples/basic/config.ru +0 -1
  46. data/examples/caddy_tls_demo/README.md +8 -2
  47. data/examples/lambda_handlers/README.md +11 -2
  48. data/examples/mcp_demo/README.md +75 -161
  49. data/examples/mcp_demo/config.ru +1 -0
  50. data/examples/security_features/README.md +36 -234
  51. data/lib/otto/caddy_tls/localhost_guard.rb +22 -24
  52. data/lib/otto/core/configuration.rb +40 -23
  53. data/lib/otto/core/error_handler.rb +40 -2
  54. data/lib/otto/core/file_safety.rb +105 -31
  55. data/lib/otto/core/middleware_stack.rb +36 -36
  56. data/lib/otto/core/router.rb +94 -18
  57. data/lib/otto/core/static_mounts.rb +172 -0
  58. data/lib/otto/core.rb +1 -0
  59. data/lib/otto/env_keys.rb +20 -2
  60. data/lib/otto/mcp/auth/token.rb +10 -4
  61. data/lib/otto/mcp/core.rb +23 -5
  62. data/lib/otto/mcp/endpoint.rb +41 -0
  63. data/lib/otto/mcp/errors.rb +15 -0
  64. data/lib/otto/mcp/options.rb +292 -0
  65. data/lib/otto/mcp/protocol.rb +52 -22
  66. data/lib/otto/mcp/rate_limiting.rb +175 -97
  67. data/lib/otto/mcp/registry.rb +14 -14
  68. data/lib/otto/mcp/schema_validation.rb +20 -12
  69. data/lib/otto/mcp/server.rb +131 -32
  70. data/lib/otto/optional_dependency.rb +57 -0
  71. data/lib/otto/privacy/config.rb +10 -8
  72. data/lib/otto/security/authentication/route_auth_wrapper/role_authorization.rb +22 -5
  73. data/lib/otto/security/authentication/strategies/api_key_strategy.rb +181 -18
  74. data/lib/otto/security/authentication/strategies/permission_strategy.rb +1 -0
  75. data/lib/otto/security/authentication/strategies/role_strategy.rb +1 -0
  76. data/lib/otto/security/authentication/strategies/session_strategy.rb +1 -0
  77. data/lib/otto/security/authentication/strategy_result.rb +58 -28
  78. data/lib/otto/security/config.rb +328 -14
  79. data/lib/otto/security/configurator.rb +36 -6
  80. data/lib/otto/security/core.rb +19 -1
  81. data/lib/otto/security/middleware/ip_privacy_middleware.rb +105 -4
  82. data/lib/otto/security/middleware/rate_limit_middleware.rb +1 -6
  83. data/lib/otto/security/rate_limiter.rb +81 -46
  84. data/lib/otto/static.rb +38 -0
  85. data/lib/otto/utils.rb +50 -0
  86. data/lib/otto/version.rb +1 -1
  87. data/lib/otto.rb +130 -13
  88. data/otto.gemspec +0 -2
  89. metadata +32 -41
  90. data/docs/.gitignore +0 -10
  91. data/docs/1108-STREAMING_ARCHITECTURE_ANALYSIS.md +0 -1105
  92. data/docs/1108-STREAMING_SUPPORT_SUMMARY.md +0 -376
  93. data/docs/enrichment.md +0 -128
  94. data/docs/geo-country.md +0 -181
  95. data/docs/ipaddr-encoding-quirk.md +0 -34
  96. data/docs/migrating/v2.0.0-pre1.md +0 -276
  97. data/docs/migrating/v2.0.0-pre2.md +0 -338
  98. data/docs/modern-authentication-authorization-landscape.md +0 -558
  99. data/docs/multi-strategy-authentication-design.md +0 -1401
  100. data/docs/reverse-proxy-network-services.md +0 -371
data/docs/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # Otto documentation
2
+
3
+ Otto is a Rack router whose application model starts with a plain-text route
4
+ file and Ruby handlers. It also provides opt-in security controls,
5
+ privacy-preserving request handling, authentication hooks, and small
6
+ integrations for network services.
7
+
8
+ This page is the documentation map. It is intentionally a map, not an
9
+ exhaustive API reference: public classes, method signatures, and implementation
10
+ details remain in the code and its generated API documentation. Guides should
11
+ explain when to use a capability, its security and operational constraints, and
12
+ the smallest complete configuration that uses it safely.
13
+
14
+ ## Capabilities at a glance
15
+
16
+ | Area | What Otto provides | Reader-facing guide or next destination |
17
+ | --- | --- | --- |
18
+ | Application model | Plain-text routes; class, instance, Logic-class, and registered lambda handlers; response selection | [Routing](guides/routing.md) and [route syntax](reference/route-syntax.md) |
19
+ | Request lifecycle | Rack integration, middleware ordering, helper registration, lifecycle hooks, and boot-time configuration | `guides/application-lifecycle.md` |
20
+ | Authentication and authorization | Named authentication strategies, ordered multi-strategy fallback, terminal failures, route roles, and resource-level authorization | [Authentication](guides/authentication.md) |
21
+ | Security | CSRF enforcement, request validation, rate limiting, security headers, CSP, error handling, and trusted proxies | `guides/security.md`; [forwarded host authority](guides/forwarded-authority.md) |
22
+ | Runtime and dependencies | Ruby compatibility tiers, upstream security-maintenance limits, dependency-range guarantees, and consumer lockfile auditing | [Runtime and dependency security policy](reference/runtime-and-dependency-security.md) |
23
+ | Privacy and network identity | IP privacy profiles, privacy-safe client signals, country resolution, ASN lookup, and anonymizer classification | [Privacy](guides/privacy.md), [geo-country](guides/geo-country.md), and [enrichment](guides/enrichment.md) |
24
+ | Internationalization | Locale configuration and request locale resolution | `guides/locales.md` |
25
+ | Operations | Structured logging, safe error reporting, static files, and testing Otto applications | `guides/operations.md` |
26
+ | Integrations | Model Context Protocol (MCP) endpoints and Caddy on-demand TLS permission checks | [MCP](guides/mcp.md) and [Caddy TLS](guides/caddy-tls.md) |
27
+
28
+ ## Start here today
29
+
30
+ - [Project README](../README.md) — installation and a minimal Rack app.
31
+ - [Runtime and dependency security policy](reference/runtime-and-dependency-security.md)
32
+ — choose a maintained Ruby and keep the application's resolved bundle audited.
33
+ - [Routing guide](guides/routing.md) — choose a handler style and response
34
+ contract.
35
+ - [Authentication guide](guides/authentication.md) — protect routes and separate
36
+ authentication from authorization.
37
+ - [Privacy guide](guides/privacy.md) — privacy profiles and request-safe client
38
+ signals.
39
+ - [Geo-country resolution](guides/geo-country.md) — trusted headers, local MMDB
40
+ fallback, and the privacy model.
41
+ - [ASN and anonymizer enrichment](guides/enrichment.md) — opt-in network signals
42
+ and their database contracts.
43
+ - [Forwarded host authority](guides/forwarded-authority.md) — trust-gated
44
+ handling of `X-Forwarded-Host` and `Forwarded`, and the process-global Rack
45
+ forwarding family.
46
+ - [MCP guide](guides/mcp.md) — enable the JSON-RPC endpoint, require bearer
47
+ tokens, and tune rate limits.
48
+ - [Caddy TLS integration](guides/caddy-tls.md) — deploy the loopback-only
49
+ permission endpoint.
50
+ - [Migration guides](migrating/) — version-specific behavior changes.
51
+ - [Changelog](../CHANGELOG.rst) — release history and upgrade-impacting changes.
52
+
53
+ The [architecture decision records](adr/) preserve durable technical rationale.
54
+ They are **not** the primary entry point for implementing an application:
55
+
56
+ - [ADR-001: Route authentication at the handler boundary](adr/adr-001-route-authentication-at-handler-boundary.md)
57
+ - [ADR-002: Multi-strategy authentication and authorization](adr/adr-002-multi-strategy-authentication-and-authorization.md)
58
+ - [ADR-003: Caddy TLS route-based integration](adr/adr-003-caddy-tls-route-based-integration.md)
59
+ - [ADR-004: Compatibility support and security maintenance](adr/adr-004-separate-compatibility-from-security-maintenance.md)
60
+ - [Ruby `IPAddr#to_s` encoding note](guides/ipaddr-encoding-quirk.md)
61
+
62
+ ## Target structure
63
+
64
+ The documentation should grow by reader task and stability, not by the order in
65
+ which implementation work occurred:
66
+
67
+ ```text
68
+ docs/
69
+ ├── README.md # this map and current entry point
70
+ ├── getting-started.md # first app after the README example
71
+ ├── guides/ # stable, task-oriented application guides
72
+ │ ├── routing.md
73
+ │ ├── application-lifecycle.md
74
+ │ ├── authentication.md
75
+ │ ├── security.md
76
+ │ ├── privacy.md
77
+ │ ├── locales.md
78
+ │ └── operations.md
79
+ ├── integrations/ # deployment-specific contracts
80
+ │ ├── mcp.md
81
+ │ └── caddy-tls.md
82
+ ├── reference/ # compact, stable contracts—not a code mirror
83
+ │ ├── route-syntax.md
84
+ │ ├── configuration.md
85
+ │ ├── request-and-response.md
86
+ │ ├── errors.md
87
+ │ └── runtime-and-dependency-security.md
88
+ ├── migrating/ # release-specific upgrade guides
89
+ ├── adr/ # accepted architecture decision records
90
+ └── maintainers/
91
+ └── investigations/ # local working notes; untracked by design
92
+ ```
93
+
94
+ ### What belongs where
95
+
96
+ - **Getting started** gets a reader from an installed gem to a running app and
97
+ links to the next common task.
98
+ - **Guides** answer a single application task. They include safe defaults,
99
+ prerequisites, a compact example, verification, and links to the relevant
100
+ compact reference.
101
+ - **Integrations** document an external system's HTTP or deployment contract,
102
+ including trust boundaries and failure behavior.
103
+ - **Reference** records only contracts that must be precise across multiple
104
+ guides: route grammar, configuration names, request/response helpers, error
105
+ behavior, and environment keys. It links to code rather than repeating every
106
+ method.
107
+ - **Migration** documents a release-bound action and its before/after behavior.
108
+ It does not become a general guide.
109
+ - **Architecture decision records** preserve the context, decision, and
110
+ consequences of durable technical choices without asking application developers
111
+ to infer the current contract from a proposal.
112
+ - **Maintainer investigations** preserve unfinished exploration separately from
113
+ accepted decisions and application documentation.
114
+
115
+ ## Migration plan for the current directory
116
+
117
+ This is a classification plan, not a request to rewrite every document now.
118
+ Move or replace a document only when its destination guide is ready. Some
119
+ working-tree documents are currently ignored by `docs/.gitignore`; reconcile
120
+ and explicitly track them before treating them as published documentation.
121
+
122
+ | Current material | Target disposition | Reason |
123
+ | --- | --- | --- |
124
+ | `geo-country.md`, `enrichment.md` | Fold into `guides/privacy.md`; retain focused pages while they remain useful | They are current, task-oriented, and contain important privacy constraints. |
125
+ | `authentication.md`, `AUTH_STRATEGIES.txt` | Replace with `guides/authentication.md` and `reference/route-syntax.md` | The guide should reflect current strategy results, role rules, ordered fallback, and terminal failures in one maintained place. |
126
+ | `ADVANCED_ROUTES.txt` | Replace with `guides/routing.md` and `reference/route-syntax.md` | Route grammar and target kinds are a public contract; the current quick reference is incomplete for modern handler types and security-gating validation. |
127
+ | `ip_privacy.md`, `structured_logging.md`, `configuration_freezing.md` | Reconcile against current behavior, then fold into privacy, operations, and lifecycle guides | They describe durable concepts, but must be verified before being promoted as canonical documentation. |
128
+ | `reverse-proxy-network-services.md` | Extract `guides/caddy-tls.md`; retain its decision as [ADR-003](adr/adr-003-caddy-tls-route-based-integration.md) | Operators need a concise deployment guide; implementation rationale should remain separately discoverable. |
129
+ | `MCP_IMPLEMENTATION.md` | Done: published as [guides/mcp.md](guides/mcp.md); implementation notes belong under `maintainers/` | The protocol user and the maintainer have different questions. |
130
+ | `multi-strategy-authentication-design.md`, `route-auth-wrapper-resolution.md` | Retain the accepted decisions as [ADR-002](adr/adr-002-multi-strategy-authentication-and-authorization.md) and [ADR-001](adr/adr-001-route-authentication-at-handler-boundary.md) | They explain durable architecture choices rather than the current application contract. |
131
+ | Dated architecture, hardening, enhancement, and streaming files | Move to `maintainers/investigations/`; keep only active proposals in the main tree | Dates and working notes are valuable history but should not compete with supported guides. |
132
+ | `testing-guide.md` | Reconcile with current test support, then publish as `guides/operations.md` or `guides/testing.md` | Testing is an application task, not an implementation design. |
133
+
134
+ ## Documentation rules for future changes
135
+
136
+ 1. Add a capability to this map only when a stable guide exists, or when the
137
+ README provides the complete safe first-use example.
138
+ 2. Update the affected guide and its compact reference in the same change as a
139
+ public behavior change. Use the changelog for the release record, not as a
140
+ substitute for instructions.
141
+ 3. Record security defaults, trust boundaries, configuration-freezing timing,
142
+ and failure behavior where they affect use. Those are part of the contract.
143
+ 4. Keep a proposal separate from its outcome. When work lands, add a short
144
+ status/outcome note and link from the guide to the decision only when the
145
+ rationale helps a maintainer.
146
+ 5. Prefer one canonical page for each task. Other pages should link to it rather
147
+ than copy examples or option lists.
148
+ 6. Validate every new command and example against the supported Ruby and Rack
149
+ versions before publishing it as runnable documentation.
150
+
151
+ ## First documentation milestone
152
+
153
+ A useful first milestone is intentionally small:
154
+
155
+ 1. Publish this map and make it the `README` documentation destination. **Done.**
156
+ 2. Write `reference/route-syntax.md` from `Otto::RouteDefinition` and route
157
+ handler behavior, including handler kinds and fail-fast `auth=`, `role=`, and
158
+ `csrf=` option syntax. **Done.**
159
+ 3. Publish `guides/routing.md`, `guides/authentication.md`, `guides/privacy.md`,
160
+ and `guides/caddy-tls.md` by reconciling the existing material with the
161
+ current code and specs. **Initial guides done.**
162
+ 4. Move completed designs and working investigations behind `maintainers/` so
163
+ the top-level reader journey remains stable. **Done.**
164
+
165
+ That sequence makes Otto's current shape visible quickly while leaving room for
166
+ new capabilities without turning `docs/` into a second, drifting codebase.
@@ -0,0 +1,16 @@
1
+ # Architecture decision records
2
+
3
+ Architecture decision records (ADRs) document durable technical decisions that
4
+ shape Otto's architecture. They preserve the context, decision, and consequences
5
+ without replacing the supported application documentation.
6
+
7
+ ## Records
8
+
9
+ - [ADR-001: Enforce route authentication at the handler boundary](adr-001-route-authentication-at-handler-boundary.md)
10
+ - [ADR-002: Use ordered authentication strategy chains and two-layer authorization](adr-002-multi-strategy-authentication-and-authorization.md)
11
+ - [ADR-003: Provide Caddy on-demand TLS as a route-based feature integration](adr-003-caddy-tls-route-based-integration.md)
12
+ - [ADR-004: Separate compatibility support from security maintenance](adr-004-separate-compatibility-from-security-maintenance.md)
13
+
14
+ New records use the next three-digit sequence number and describe their status,
15
+ context, decision, and consequences. Update a record's status when a later ADR
16
+ supersedes it; do not rewrite accepted decisions to reflect later changes.
@@ -0,0 +1,38 @@
1
+ # ADR-001: Enforce route authentication at the handler boundary
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2025-10-10
5
+
6
+ ## Context
7
+
8
+ Route declarations carry their `auth=` requirements. Authentication middleware
9
+ runs before routing, so it cannot reliably inspect the matched route or enforce
10
+ that requirement. That left route-level authentication dependent on application
11
+ code instead of Otto's routing contract.
12
+
13
+ ## Decision
14
+
15
+ Otto enforces authentication with `RouteAuthWrapper`, which wraps a route handler
16
+ after Otto has resolved the route and before the application handler runs. The
17
+ wrapper reads the route definition, executes its configured authentication
18
+ strategy or strategy chain, and stores the resulting `StrategyResult` in the
19
+ request environment.
20
+
21
+ Routes without `auth=` receive an anonymous `StrategyResult`. This gives Logic
22
+ classes a consistent context while preserving public-route behavior.
23
+
24
+ ## Consequences
25
+
26
+ - `auth=` is enforced at the point where the route definition is available.
27
+ - Authentication remains handler-level architecture, not a global middleware
28
+ concern.
29
+ - Logic classes and handlers can rely on `env['otto.strategy_result']` being set
30
+ when authentication is configured.
31
+ - Authentication strategy registration must finish before the first request,
32
+ when Otto freezes configuration.
33
+
34
+ ## Related documentation
35
+
36
+ - [Authentication and authorization guide](../guides/authentication.md)
37
+ - [Route syntax reference](../reference/route-syntax.md)
38
+ - [ADR-002: Multi-strategy authentication and authorization](adr-002-multi-strategy-authentication-and-authorization.md)
@@ -0,0 +1,50 @@
1
+ # ADR-002: Use ordered authentication strategy chains and two-layer authorization
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2025-11
5
+
6
+ ## Context
7
+
8
+ An Otto route may need to accept more than one credential mechanism, such as a
9
+ browser session and an API key. Authentication must support that without making
10
+ invalid explicit credentials silently fall back to anonymous access. It must also
11
+ separate broad route access checks from authorization that depends on a loaded
12
+ resource.
13
+
14
+ ## Decision
15
+
16
+ Use a comma-separated `auth=` value for an ordered, OR-based strategy chain:
17
+
18
+ ```text
19
+ GET /api/data Api::Data#show auth=session,api_key response=json
20
+ ```
21
+
22
+ Otto validates all named strategies before it executes the chain. Strategies run
23
+ left to right, and the first authenticated result wins. Plain failures allow the
24
+ next strategy to run. An anonymous result, such as `noauth`, is held as a
25
+ fallback until the chain completes. A terminal `AuthFailure` stops the chain and
26
+ returns an authentication failure; it is reserved for explicitly presented
27
+ credentials that were examined and rejected.
28
+
29
+ Use `role=` for broad route-level authorization after authentication. Multiple
30
+ roles use OR logic. Perform ownership, relationship, and other resource-specific
31
+ authorization in a Logic class's `raise_concerns` method; raise
32
+ `Otto::Security::AuthorizationError` when access is denied.
33
+
34
+ ## Consequences
35
+
36
+ - A route can accept multiple credential types without duplicate handlers.
37
+ - Strategy declaration order determines the order of ordinary attempts, but a
38
+ terminal failure always fails closed.
39
+ - Unknown strategy names fail before any configured strategy runs, preventing a
40
+ partially configured route from serving traffic.
41
+ - Missing authentication results in `401`; a valid subject denied by a strategy,
42
+ role check, or resource check results in `403`.
43
+ - Applications should configure inexpensive, common strategies first and mark a
44
+ failure terminal only when explicit credentials were rejected.
45
+
46
+ ## Related documentation
47
+
48
+ - [Authentication and authorization guide](../guides/authentication.md)
49
+ - [Route syntax reference](../reference/route-syntax.md)
50
+ - [ADR-001: Route authentication at the handler boundary](adr-001-route-authentication-at-handler-boundary.md)
@@ -0,0 +1,48 @@
1
+ # ADR-003: Provide Caddy on-demand TLS as a route-based feature integration
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-07
5
+
6
+ ## Context
7
+
8
+ Caddy's on-demand TLS permission check has a fixed HTTP contract, but each
9
+ application previously had to implement routing, input validation, caller
10
+ restriction, response semantics, and failure handling around its domain-allow
11
+ policy. A manually wired handler and guard makes it possible to expose the
12
+ certificate-issuance endpoint without its required protection.
13
+
14
+ ## Decision
15
+
16
+ Provide `Otto::CaddyTLS` as an opt-in, feature-named integration. Calling
17
+ `enable_caddy_tls!` registers the permission route and, by default, its
18
+ path-scoped `LocalhostGuard`; the application supplies only the domain allow or
19
+ deny decision.
20
+
21
+ The guard authorizes a direct loopback socket peer, not a client address derived
22
+ from forwarding headers. It also rejects forwarding headers for the protected
23
+ path. The endpoint therefore remains loopback-only even in cross-host
24
+ deployments: run a small permission app alongside Caddy and let the application
25
+ callback use its existing trusted data channel.
26
+
27
+ The integration fails closed: a missing or invalid `domain` is rejected, a falsey
28
+ or exception-raising permission callback denies the request, and enabling the
29
+ integration without a callback raises an error. The default endpoint is
30
+ `/_caddy/tls-permission`.
31
+
32
+ ## Consequences
33
+
34
+ - Caddy TLS setup has one code-side entry point that bundles the route, guard,
35
+ and decision callback.
36
+ - The endpoint cannot be reached successfully through a public reverse-proxy
37
+ path merely because the proxy connects to Otto over loopback.
38
+ - `localhost_only: false` is an explicit opt-out; the deployment must then
39
+ provide network-level isolation.
40
+ - A generic network-services registry is not introduced. Future integrations
41
+ should use a feature-specific namespace and promote only mechanisms shared by
42
+ more than one concrete feature.
43
+
44
+ ## Related documentation
45
+
46
+ - [Caddy on-demand TLS guide](../guides/caddy-tls.md)
47
+ - [Caddy TLS example](../../examples/caddy_tls_demo/README.md)
48
+ - [`Otto::CaddyTLS::LocalhostGuard`](../../lib/otto/caddy_tls/localhost_guard.rb)
@@ -0,0 +1,58 @@
1
+ # ADR-004: Separate compatibility support from security maintenance
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-09-04
5
+
6
+ ## Context
7
+
8
+ Otto declares Ruby and dependency version ranges so that applications can resolve
9
+ a compatible bundle. Those declarations can be mistaken for security guarantees:
10
+ a Ruby version may remain compatible with Otto after upstream security maintenance
11
+ ends, and a gem version may satisfy Otto's dependency range after a new advisory
12
+ makes that version unsafe.
13
+
14
+ Otto's own lockfile and CI matrix test Otto's development resolution and API
15
+ compatibility. They cannot determine or secure the final dependency graph resolved
16
+ by every application that installs Otto. The current Ruby support matrix,
17
+ dependency constraints, and audit procedures also change over time, so they belong
18
+ in maintained reference documentation rather than in a historical decision record.
19
+
20
+ ## Decision
21
+
22
+ Otto treats compatibility support as distinct from upstream security maintenance.
23
+ Compatibility testing means that Otto is expected to work on a runtime or with a
24
+ dependency version; it does not mean that the runtime or dependency still receives
25
+ security fixes.
26
+
27
+ The version ranges in `otto.gemspec` express API compatibility. Their lower bounds
28
+ are not guaranteed security floors, and installation success does not certify a
29
+ resolved bundle as free of known vulnerabilities.
30
+
31
+ Applications that consume Otto own the security auditing and maintenance of their
32
+ resolved lockfiles. Otto may constrain a known-bad version when warranted, but
33
+ maintainer constraints and CI do not replace application-level auditing.
34
+
35
+ Otto continues to test Ruby 3.2 as a compatibility target despite its upstream end
36
+ of life. This preserves compatibility for users who still need that runtime without
37
+ representing Ruby 3.2 as security-maintained.
38
+
39
+ ## Consequences
40
+
41
+ - Otto can retain useful compatibility coverage without implying that it provides
42
+ upstream interpreter or dependency security maintenance.
43
+ - Applications with security-maintenance requirements must select an upstream-
44
+ maintained Ruby, audit their complete resolved bundles, and deploy audited
45
+ lockfiles.
46
+ - Dependency lower bounds remain stable compatibility baselines unless an API or
47
+ security issue requires a targeted constraint change.
48
+ - Ruby 3.2 compatibility failures remain release-relevant while it is a blocking
49
+ target, even though applications should not infer that Ruby 3.2 receives security
50
+ fixes.
51
+ - The current support matrix, compatibility ranges, audit commands, and consumer
52
+ procedures may change in the canonical reference without rewriting this ADR. A
53
+ later architectural decision may supersede this record if the policy changes.
54
+
55
+ ## Related documentation
56
+
57
+ - [Runtime and dependency security policy](../reference/runtime-and-dependency-security.md)
58
+ — canonical current matrix, compatibility policy, and consumer audit procedures