otto 2.9.0 → 2.10.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 (96) 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 +7 -5
  12. data/AGENTS.md +22 -1
  13. data/CHANGELOG.rst +203 -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 +146 -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 +181 -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 +36 -22
  53. data/lib/otto/core/file_safety.rb +89 -31
  54. data/lib/otto/core/middleware_stack.rb +36 -36
  55. data/lib/otto/core/router.rb +62 -19
  56. data/lib/otto/env_keys.rb +20 -2
  57. data/lib/otto/mcp/auth/token.rb +10 -4
  58. data/lib/otto/mcp/core.rb +23 -5
  59. data/lib/otto/mcp/endpoint.rb +41 -0
  60. data/lib/otto/mcp/errors.rb +15 -0
  61. data/lib/otto/mcp/options.rb +292 -0
  62. data/lib/otto/mcp/protocol.rb +52 -22
  63. data/lib/otto/mcp/rate_limiting.rb +175 -97
  64. data/lib/otto/mcp/registry.rb +14 -14
  65. data/lib/otto/mcp/schema_validation.rb +20 -12
  66. data/lib/otto/mcp/server.rb +131 -32
  67. data/lib/otto/optional_dependency.rb +57 -0
  68. data/lib/otto/privacy/config.rb +10 -8
  69. data/lib/otto/security/authentication/route_auth_wrapper/role_authorization.rb +22 -5
  70. data/lib/otto/security/authentication/strategies/api_key_strategy.rb +181 -18
  71. data/lib/otto/security/authentication/strategies/permission_strategy.rb +1 -0
  72. data/lib/otto/security/authentication/strategies/role_strategy.rb +1 -0
  73. data/lib/otto/security/authentication/strategies/session_strategy.rb +1 -0
  74. data/lib/otto/security/authentication/strategy_result.rb +58 -28
  75. data/lib/otto/security/config.rb +328 -14
  76. data/lib/otto/security/configurator.rb +36 -6
  77. data/lib/otto/security/core.rb +19 -1
  78. data/lib/otto/security/middleware/ip_privacy_middleware.rb +105 -4
  79. data/lib/otto/security/middleware/rate_limit_middleware.rb +1 -6
  80. data/lib/otto/security/rate_limiter.rb +81 -46
  81. data/lib/otto/utils.rb +50 -0
  82. data/lib/otto/version.rb +1 -1
  83. data/lib/otto.rb +9 -11
  84. data/otto.gemspec +0 -2
  85. metadata +31 -41
  86. data/docs/.gitignore +0 -10
  87. data/docs/1108-STREAMING_ARCHITECTURE_ANALYSIS.md +0 -1105
  88. data/docs/1108-STREAMING_SUPPORT_SUMMARY.md +0 -376
  89. data/docs/enrichment.md +0 -128
  90. data/docs/geo-country.md +0 -181
  91. data/docs/ipaddr-encoding-quirk.md +0 -34
  92. data/docs/migrating/v2.0.0-pre1.md +0 -276
  93. data/docs/migrating/v2.0.0-pre2.md +0 -338
  94. data/docs/modern-authentication-authorization-landscape.md +0 -558
  95. data/docs/multi-strategy-authentication-design.md +0 -1401
  96. data/docs/reverse-proxy-network-services.md +0 -371
data/Gemfile.lock CHANGED
@@ -1,13 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- otto (2.9.0)
4
+ otto (2.10.0)
5
5
  concurrent-ruby (~> 1.3, < 2.0)
6
6
  logger (~> 1, < 2.0)
7
7
  loofah (~> 2.20)
8
8
  rack (~> 3.1, < 4.0)
9
- rack-parser (~> 0.7)
10
- rexml (~> 3.4)
11
9
 
12
10
  GEM
13
11
  remote: https://rubygems.org/
@@ -106,18 +104,16 @@ GEM
106
104
  date
107
105
  stringio
108
106
  racc (1.8.1)
109
- rack (3.2.6)
107
+ rack (3.2.7)
110
108
  rack-attack (6.8.0)
111
109
  rack (>= 1.0, < 4)
112
- rack-parser (0.7.0)
113
- rack
114
110
  rack-test (2.2.0)
115
111
  rack (>= 1.3)
116
112
  rackup (2.3.1)
117
113
  rack (>= 3)
118
114
  rainbow (3.1.1)
119
115
  rake (13.4.2)
120
- rbs (4.0.3)
116
+ rbs (4.1.3)
121
117
  logger
122
118
  prism (>= 1.6.0)
123
119
  tsort
@@ -148,8 +144,8 @@ GEM
148
144
  diff-lcs (>= 1.2.0, < 2.0)
149
145
  rspec-support (~> 3.13.0)
150
146
  rspec-support (3.13.7)
151
- rubocop (1.89.0)
152
- json (~> 2.3)
147
+ rubocop (1.90.0)
148
+ json (>= 2.3)
153
149
  language_server-protocol (~> 3.17.0.2)
154
150
  lint_roller (~> 1.1.0)
155
151
  parallel (>= 1.10)
@@ -162,9 +158,9 @@ GEM
162
158
  rubocop-ast (1.50.0)
163
159
  parser (>= 3.3.7.2)
164
160
  prism (~> 1.7)
165
- rubocop-performance (1.26.1)
161
+ rubocop-performance (1.27.0)
166
162
  lint_roller (~> 1.1)
167
- rubocop (>= 1.75.0, < 2.0)
163
+ rubocop (>= 1.89.0, < 2.0)
168
164
  rubocop-ast (>= 1.47.1, < 2.0)
169
165
  rubocop-rspec (3.10.2)
170
166
  lint_roller (~> 1.1)
@@ -174,7 +170,7 @@ GEM
174
170
  lint_roller (~> 1.1)
175
171
  rubocop (~> 1.72, >= 1.72.1)
176
172
  rubocop-ast (>= 1.44.0, < 2.0)
177
- ruby-lsp (0.26.10)
173
+ ruby-lsp (0.26.11)
178
174
  language_server-protocol (~> 3.17.0)
179
175
  prism (>= 1.2, < 2.0)
180
176
  rbs (>= 3, < 5)
@@ -217,17 +213,17 @@ PLATFORMS
217
213
  DEPENDENCIES
218
214
  benchmark
219
215
  debug
220
- json_schemer
216
+ json_schemer (~> 2.0)
221
217
  kramdown
222
218
  maxmind-db (~> 1.2)
223
219
  otto!
224
- rack-attack
220
+ rack-attack (~> 6.7)
225
221
  rack-test
226
222
  rackup
227
223
  rake (~> 13.4)
228
224
  reek (~> 6.5)
229
225
  rspec (~> 3.13)
230
- rubocop (~> 1.89.0)
226
+ rubocop (~> 1.90.0)
231
227
  rubocop-performance
232
228
  rubocop-rspec
233
229
  rubocop-thread_safety
data/README.md CHANGED
@@ -1,22 +1,41 @@
1
- # Otto - A Ruby Gem
1
+ # Otto - All Rack, no Pinion
2
2
 
3
- **Define your rack-apps in plain-text with built-in security.**
4
-
5
- > **v2.0.0-pre6 Available**: This pre-release includes major improvements to middleware management, logging, and request callback handling. See [changelog](CHANGELOG.rst) for details and upgrade notes.
3
+ **Define Rack apps in plain text, with privacy by default and opt-in security features.**
6
4
 
7
5
  ![Otto mascot](public/img/otto.jpg "Otto - All Rack, no Pinion")
8
6
 
9
- Otto apps have three files: a rackup file, a Ruby class, and a routes file. The routes file is just plain text that maps URLs to Ruby methods.
7
+ Otto apps have three files: a rackup file, a Ruby class, and a routes file. The routes file is plain text that maps URLs to Ruby methods.
8
+
9
+ ## Quick start
10
+
11
+ Requirements: Ruby `>= 3.2, < 4.1` and Rack `>= 3.1, < 4.0`. Ruby 3.2
12
+ remains compatibility-tested after upstream end of life, but receives no
13
+ interpreter security maintenance. See the [runtime and dependency security
14
+ policy](docs/reference/runtime-and-dependency-security.md) for support tiers and
15
+ consumer lockfile requirements.
10
16
 
11
- ```bash
12
- $ cd myapp && ls
13
- config.ru app.rb routes
17
+ Install Otto and the Rack server CLI, then create the three files shown below:
18
+
19
+ ```sh
20
+ gem install otto rackup
21
+ mkdir myapp && cd myapp
22
+ ```
23
+
24
+ After creating `routes`, `app.rb`, and `config.ru`, start the app and verify the response:
25
+
26
+ ```sh
27
+ rackup config.ru
28
+ # In another terminal:
29
+ curl -i http://127.0.0.1:9292/
30
+ # HTTP/1.1 200 OK
31
+ # ...
32
+ # <h1>Hello Otto</h1>
14
33
  ```
15
34
 
16
35
  ## Why Otto?
17
36
 
18
- - **Security by Default**: Automatic IP masking for public addresses, user agent anonymization, CSRF protection, and input validation
19
- - **Privacy First**: Masks public IPs, strips user agent versions, provides country-level geo-location only—no external APIs needed
37
+ - **Privacy by Default**: Masks public IP addresses and anonymizes user agents; country-level geo-location needs no external API
38
+ - **Opt-in Security Features**: CSRF protection, input validation, security headers, and trusted-proxy configuration
20
39
  - **Simple Routing**: Define routes in plain-text files with zero configuration overhead
21
40
  - **Built-in Authentication**: Multiple strategies including API keys, tokens, role-based access, and custom implementations
22
41
  - **Developer Friendly**: Works with any Rack server, minimal dependencies, easy testing and debugging
@@ -26,10 +45,8 @@ config.ru app.rb routes
26
45
  # routes
27
46
 
28
47
  GET / App#index
29
- POST /feedback App#receive_feedback
30
48
  GET /product/:id App#show_product
31
49
  GET /robots.txt App#robots_text
32
- GET /404 App#not_found
33
50
  ```
34
51
 
35
52
  ## Ruby Class
@@ -37,6 +54,8 @@ GET /404 App#not_found
37
54
  # app.rb
38
55
 
39
56
  class App
57
+ attr_reader :req, :res
58
+
40
59
  def initialize(req, res)
41
60
  @req, @res = req, res
42
61
  end
@@ -51,7 +70,7 @@ class App
51
70
  end
52
71
 
53
72
  def robots_text
54
- res.header['Content-Type'] = "text/plain"
73
+ res.headers['content-type'] = 'text/plain'
55
74
  rules = 'User-agent: *', 'Disallow: /private/keep/out'
56
75
  res.body = rules.join($/)
57
76
  end
@@ -63,9 +82,9 @@ end
63
82
  # config.ru
64
83
 
65
84
  require 'otto'
66
- require 'app'
85
+ require_relative 'app'
67
86
 
68
- run Otto.new("./routes")
87
+ run Otto.new('routes')
69
88
  ```
70
89
 
71
90
 
@@ -82,7 +101,27 @@ app = Otto.new("./routes", {
82
101
  })
83
102
  ```
84
103
 
85
- Security features include CSRF protection, input validation, security headers, and trusted proxy configuration.
104
+ Security features include CSRF protection, input validation, security headers, rate limiting, and trusted proxy configuration.
105
+
106
+ ### Rate limiting (`rack-attack`)
107
+
108
+ Rate limiting requires `rack-attack` 6.7.0 or newer in the 6.x series. Add the
109
+ optional dependency and mount it before Otto; `enable_rate_limiting!` configures
110
+ rules, while `Rack::Attack` enforces them:
111
+
112
+ ```ruby
113
+ # Gemfile
114
+ gem 'rack-attack', '~> 6.7'
115
+
116
+ # config.ru
117
+ use Rack::Attack
118
+ app = Otto.new('./routes')
119
+ app.enable_rate_limiting!(requests_per_minute: 50)
120
+ run app
121
+ ```
122
+
123
+ Enabling rate limiting raises `Otto::OptionalDependencyError` at configuration
124
+ time when the gem is missing or outside the supported range.
86
125
 
87
126
  ### Content Security Policy (nonce-based emission)
88
127
 
@@ -300,7 +339,7 @@ Private and localhost IPs are exempted by default for development convenience, b
300
339
  ```ruby
301
340
  otto.configure_ip_privacy(
302
341
  geo_header: 'X-Client-Country', # trusted app header, checked first
303
- geo_db_path: 'data/country.mmdb' # offline fallback (needs the maxmind-db gem)
342
+ geo_db_path: 'data/country.mmdb' # offline fallback (needs maxmind-db >= 1.2.0, < 2)
304
343
  )
305
344
  ```
306
345
 
@@ -395,8 +434,9 @@ on_demand_tls {
395
434
 
396
435
  Secure by default: the endpoint is restricted to the loopback interface (the guard
397
436
  authenticates the raw TCP peer, so a spoofed `X-Forwarded-For` cannot help), and
398
- every layer fails closed. See [docs/reverse-proxy-network-services.md](docs/reverse-proxy-network-services.md)
399
- for the design and deployment notes.
437
+ every layer fails closed. See the [Caddy TLS guide](docs/guides/caddy-tls.md)
438
+ for deployment guidance and [ADR-003](docs/adr/adr-003-caddy-tls-route-based-integration.md)
439
+ for rationale.
400
440
 
401
441
  ## Examples
402
442
 
@@ -406,7 +446,7 @@ Otto includes comprehensive examples demonstrating different features:
406
446
  - **[Advanced Routes](examples/advanced_routes/)** - Response types, CSRF exemption, logic classes, and namespaced routing
407
447
  - **[Authentication Strategies](examples/authentication_strategies/)** - Token, API key, and role-based authentication
408
448
  - **[Security Features](examples/security_features/)** - CSRF protection, input validation, file uploads, and security headers
409
- - **[MCP Demo](examples/mcp_demo/)** - JSON-RPC 2.0 endpoints for CLI automation and integrations
449
+ - **[MCP Demo](examples/mcp_demo/)** - JSON-RPC 2.0 endpoints for CLI automation and integrations (see the [MCP guide](docs/guides/mcp.md))
410
450
  - **[Caddy on-demand TLS](examples/caddy_tls_demo/)** - Reverse-proxy permission endpoint via `Otto::CaddyTLS`
411
451
 
412
452
  ### Standalone Tutorials
@@ -417,32 +457,30 @@ Otto includes comprehensive examples demonstrating different features:
417
457
 
418
458
  See the [examples/](examples/) directory for more.
419
459
 
420
- ## Requirements
421
460
 
422
- - Ruby 3.2+
423
- - Rack 3.1+
461
+ ## Documentation
424
462
 
425
- ## Installation
463
+ - **[Documentation map](docs/README.md)** - Capabilities, current guides, and the documentation structure Otto is growing toward
464
+ - **[Runtime and dependency security policy](docs/reference/runtime-and-dependency-security.md)** - Ruby compatibility, dependency-range guarantees, and consumer lockfile auditing
465
+ - **[CHANGELOG.rst](CHANGELOG.rst)** - Version history, breaking changes, and upgrade notes
426
466
 
427
- ```bash
428
- gem install otto
429
- ```
467
+ ## AI-assisted development
430
468
 
431
- ## Documentation
469
+ AI tools have contributed to Otto since v1.2.0. They are used for implementation, testing, documentation, maintenance, and additional code review.
432
470
 
433
- - **[AGENTS.md](AGENTS.md)** - Comprehensive developer guidance covering authentication architecture, configuration freezing, IP privacy, structured logging, and multi-app patterns
434
- - **[docs/](docs/)** - Technical guides and migration guides
435
- - **[CHANGELOG.rst](CHANGELOG.rst)** - Version history, breaking changes, and upgrade notes
471
+ ### Tools used
436
472
 
437
- ## AI Development Assistance
473
+ * **Claude Code CLI (Opus and Fable)** - Primary development tool for implementation, test coverage, and adversarial review
474
+ * **Greptile and GitHub Copilot** - Pull request reviews
475
+ * **Claude Desktop routines** - Unattended cloud agents that run on a cron schedule and open pull requests for maintenance chores. Nobody is at the keyboard for these, so they go through the same PR review as human work.
438
476
 
439
- Version 1.2.0's security features were developed with AI assistance:
477
+ ### How it is tracked
440
478
 
441
- * **Zed Agent (Claude Sonnet 4)** - Security implementation and testing
442
- * **Claude Desktop** - Rack 3+ compatibility and debugging
443
- * **GitHub Copilot** - Code completion
479
+ * All PR reviews are publicly visible. The `greptile-review` and `claude-review` labels are applied to PRs that have been reviewed.
480
+ * Release notes in [CHANGELOG.rst](CHANGELOG.rst) list AI-assisted work under an "AI Assistance" heading.
481
+ * [AGENTS.md](AGENTS.md) sets the conventions agents follow in this repository.
444
482
 
445
- The maintainer remains responsible for all security decisions and implementation. We believe in transparency about development tools, especially for security-focused software.
483
+ The maintainer reviews the resulting changes and remains responsible for security, implementation, and releases.
446
484
 
447
485
  ## License
448
486
 
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