seo_geo 0.1.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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/AGENTS.md +244 -0
  3. data/CHANGELOG.md +40 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +428 -0
  6. data/app/controllers/seo_geo/index_now_controller.rb +13 -0
  7. data/app/controllers/seo_geo/llms_controller.rb +29 -0
  8. data/app/controllers/seo_geo/og_images_controller.rb +67 -0
  9. data/app/controllers/seo_geo/robots_controller.rb +14 -0
  10. data/app/controllers/seo_geo/sitemaps_controller.rb +16 -0
  11. data/app/jobs/seo_geo/index_now_job.rb +21 -0
  12. data/app/views/seo_geo/fallback/_body.html.erb +21 -0
  13. data/app/views/seo_geo/fallback/_faq.html.erb +12 -0
  14. data/config/routes.rb +17 -0
  15. data/lib/generators/seo_geo/install/install_generator.rb +138 -0
  16. data/lib/generators/seo_geo/install/templates/home.yml.tt +14 -0
  17. data/lib/generators/seo_geo/install/templates/initializer.rb.tt +52 -0
  18. data/lib/seo_geo/audit/checks.rb +107 -0
  19. data/lib/seo_geo/audit/html_document.rb +132 -0
  20. data/lib/seo_geo/audit/report.rb +85 -0
  21. data/lib/seo_geo/audit/runner.rb +327 -0
  22. data/lib/seo_geo/audit/static_scan.rb +60 -0
  23. data/lib/seo_geo/audit.rb +14 -0
  24. data/lib/seo_geo/bot_verifier.rb +278 -0
  25. data/lib/seo_geo/configuration.rb +832 -0
  26. data/lib/seo_geo/controller.rb +72 -0
  27. data/lib/seo_geo/crawler_fallback.rb +45 -0
  28. data/lib/seo_geo/crawlers.rb +79 -0
  29. data/lib/seo_geo/engine.rb +123 -0
  30. data/lib/seo_geo/index_now.rb +187 -0
  31. data/lib/seo_geo/json_ld.rb +259 -0
  32. data/lib/seo_geo/llms_txt.rb +183 -0
  33. data/lib/seo_geo/markdown/document.rb +163 -0
  34. data/lib/seo_geo/markdown/negotiation.rb +49 -0
  35. data/lib/seo_geo/markdown/path_suffix.rb +72 -0
  36. data/lib/seo_geo/markdownable.rb +141 -0
  37. data/lib/seo_geo/meta.rb +382 -0
  38. data/lib/seo_geo/model.rb +95 -0
  39. data/lib/seo_geo/og_image/card.html.erb +22 -0
  40. data/lib/seo_geo/og_image/ferrum_backend.rb +72 -0
  41. data/lib/seo_geo/og_image/renderer.rb +163 -0
  42. data/lib/seo_geo/page.rb +201 -0
  43. data/lib/seo_geo/path_match.rb +36 -0
  44. data/lib/seo_geo/publication.rb +68 -0
  45. data/lib/seo_geo/robots/evaluator.rb +85 -0
  46. data/lib/seo_geo/robots.rb +105 -0
  47. data/lib/seo_geo/robots_directives.rb +170 -0
  48. data/lib/seo_geo/robots_path.rb +125 -0
  49. data/lib/seo_geo/rspec.rb +197 -0
  50. data/lib/seo_geo/safe_regexp.rb +50 -0
  51. data/lib/seo_geo/sitemap/registry.rb +239 -0
  52. data/lib/seo_geo/sitemap/xml.rb +138 -0
  53. data/lib/seo_geo/testing/environment.rb +31 -0
  54. data/lib/seo_geo/testing.rb +111 -0
  55. data/lib/seo_geo/undeclared_host.rb +40 -0
  56. data/lib/seo_geo/version.rb +5 -0
  57. data/lib/seo_geo.rb +80 -0
  58. data/lib/tasks/seo_geo.rake +23 -0
  59. data/llms.txt +27 -0
  60. metadata +224 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ec8b0f1b5e2f745e00c47a2d2f9a15e2eda6ca9d0357948feca1a22548c6ec25
4
+ data.tar.gz: 8eb32a70fd40779db958d83a6927229c7b09ba156e630472cb4325759cdbec91
5
+ SHA512:
6
+ metadata.gz: 01636e3e7a699db78db0765973b16652cd6130c2ed1aea1c463632c3b8b55cd1576bdbedfcbf20c0f0f95139ee64c9579a735d3129bc7af1815960a94881389d
7
+ data.tar.gz: 33f4593adfa66ddebdf586bdc21473749eb60df802d242da5c2b8be2fc983a48af5338da4ab55f65b46272768070e02cafeb870cf3c896888fd30120d4ac7c63
data/AGENTS.md ADDED
@@ -0,0 +1,244 @@
1
+ # AGENTS.md — seo_geo (Ruby / Rails)
2
+
3
+ Guidance for AI coding agents that add this gem to an application or work on this repository. Humans: the
4
+ [README](README.md) has the same facts with more explanation.
5
+
6
+ ## What this is
7
+
8
+ A Rails engine for SEO and GEO. GEO is Generative Engine Optimization: visibility in AI search and assistants
9
+ (ChatGPT, Claude, Perplexity, Gemini). It is **not** a geolocation, GeoIP or maps gem. One initializer and one
10
+ layout line produce the head (title, description, canonical, hreflang, Open Graph, JSON-LD), per-host
11
+ `robots.txt`, `sitemap.xml`, `llms.txt`/`llms-full.txt`, Markdown responses for agents, IndexNow pings, OG
12
+ images and an audit task.
13
+
14
+ ## When to use it
15
+
16
+ **Use it when** a Rails 7.2–8.1 app (ERB, Hotwire or Inertia) needs search and AI-crawler visibility managed
17
+ in one place: several hostnames folded onto one canonical origin, a crawler policy by purpose (training vs
18
+ search vs user-triggered fetch), FAQ copy that must match its JSON-LD, or Inertia/SPA pages that crawlers see as
19
+ empty.
20
+
21
+ **Do not use it when**
22
+
23
+ - you need geolocation (IP to country, maps) — this gem does not do that;
24
+ - the app is not Rails (plain Rack, Sinatra, Hanami) — the engine needs Railties and ActionPack;
25
+ - you want to promise rich results or rankings — the gem emits valid markup, nothing more;
26
+ - a CDN or framework already owns `/robots.txt`, `/sitemap.xml` and the `<head>`; run one source, not two.
27
+
28
+ ## Integration recipe
29
+
30
+ 1. `bundle add seo_geo`, then `bin/rails g seo_geo:install https://www.example.com`. The generator writes
31
+ `config/initializers/seo_geo.rb` and `config/seo_geo/pages/home.yml`. It mounts the engine as the last
32
+ route, before a top-level catch-all if there is one.
33
+ 2. Delete `public/robots.txt`, `public/sitemap.xml` and `public/llms.txt` if they exist: static files win over
34
+ the engine routes.
35
+ 3. Configure hosts, crawler purposes, the sitemap and llms.txt:
36
+
37
+ <!-- example: quickstart/config/initializers/seo_geo.rb -->
38
+ ```ruby
39
+ # config/initializers/seo_geo.rb
40
+ SeoGeo.configure do |seo|
41
+ seo.site.name = "Example"
42
+ seo.site.description = "Example helps small teams plan their week."
43
+ seo.canonical_origin = "https://www.example.com"
44
+ seo.canonical_aliases = ["example.com"] # folded onto www.example.com
45
+ seo.host "www.example.com", disallow: ["/admin"] # every crawler group repeats this
46
+ seo.crawlers.training = :deny # GPTBot, ClaudeBot, CCBot, ... => Disallow: /
47
+ end
48
+
49
+ SeoGeo.sitemap.draw do
50
+ add "/", priority: 1.0
51
+ add "/pricing"
52
+ end
53
+
54
+ SeoGeo.config.llms_txt.page :home, path: "/"
55
+ ```
56
+
57
+ 4. Replace the app's `<title>`, meta description, canonical link and JSON-LD with one line. `seo_geo_head`
58
+ emits exactly one `<title>`; remove any other.
59
+
60
+ <!-- example: quickstart/app/views/layouts/application.html.erb -->
61
+ ```erb
62
+ <%# app/views/layouts/application.html.erb %>
63
+ <!DOCTYPE html>
64
+ <html lang="<%= seo_geo_html_lang %>">
65
+ <head>
66
+ <meta charset="utf-8">
67
+ <%= seo_geo_head %>
68
+ </head>
69
+ <body>
70
+ <main><%= yield %></main>
71
+ </body>
72
+ </html>
73
+ ```
74
+
75
+ 5. Set per-page values in controllers: `seo title:, description:` for one-off pages, `seo_geo_page(:key)` for
76
+ pages with registered copy. Include `SeoGeo::Markdownable` where agents should get Markdown.
77
+
78
+ <!-- example: quickstart/app/controllers/pages_controller.rb -->
79
+ ```ruby
80
+ # app/controllers/pages_controller.rb
81
+ class PagesController < ApplicationController
82
+ include SeoGeo::Markdownable # Accept: text/markdown and /pricing.md
83
+
84
+ def home
85
+ @page = seo_geo_page(:home) # title, description and FAQPage JSON-LD from home.yml
86
+ end
87
+
88
+ def pricing
89
+ seo title: "Pricing", description: "Free for up to three people; paid plans for larger teams, billed monthly."
90
+ end
91
+ end
92
+ ```
93
+
94
+ 6. **Inertia** (React/Vue/Svelte): pass `seo_geo_page(:key).props` as a prop and render the visible copy from
95
+ it. In the layout, render `seo_geo_fallback_body(page)` after `yield`. After the client app mounts, call
96
+ `window.SeoGeo?.mounted()`. Use `seo_geo_markdown page: :key` so Markdown comes from the copy rather than the
97
+ empty `#app` div:
98
+
99
+ <!-- example: inertia/app/controllers/pages_controller.rb -->
100
+ ```ruby
101
+ # app/controllers/pages_controller.rb (inertia_rails)
102
+ class PagesController < ApplicationController
103
+ include SeoGeo::Markdownable
104
+ seo_geo_markdown only: :home, page: :home # Markdown from the page copy, not the empty #app div
105
+
106
+ def home
107
+ render inertia: "Home", props: { seo: seo_geo_page(:home).props }
108
+ end
109
+ end
110
+ ```
111
+
112
+ <!-- example: inertia/app/views/layouts/application.html.erb -->
113
+ ```erb
114
+ <%# app/views/layouts/application.html.erb (Inertia) %>
115
+ <!DOCTYPE html>
116
+ <html lang="<%= seo_geo_html_lang %>">
117
+ <head>
118
+ <meta charset="utf-8">
119
+ <%= seo_geo_head %>
120
+ </head>
121
+ <body>
122
+ <%= yield %>
123
+ <% if (page = seo_geo_state.page) %>
124
+ <%= seo_geo_fallback_body(page) %>
125
+ <% end %>
126
+ </body>
127
+ </html>
128
+ ```
129
+
130
+ 7. Add integration tests with the gem's assertions. They run the audit's own checks:
131
+
132
+ <!-- example: quickstart/test/integration/seo_test.rb -->
133
+ ```ruby
134
+ # test/integration/seo_test.rb
135
+ require "test_helper"
136
+ require "seo_geo/testing"
137
+
138
+ class SeoTest < ActionDispatch::IntegrationTest
139
+ include SeoGeo::Testing::Assertions
140
+
141
+ test "one title and a folded canonical" do
142
+ assert_seo_geo_single_title "/"
143
+ assert_seo_geo_canonical "/pricing", host: "example.com", expected: "https://www.example.com/pricing"
144
+ end
145
+
146
+ test "FAQ JSON-LD matches the visible text" do
147
+ assert_seo_geo_faq_matches_visible "/"
148
+ end
149
+
150
+ test "production robots.txt and sitemap" do
151
+ with_seo_geo_environment("production") do
152
+ refute_seo_geo_robots_allows "GPTBot", "/", host: "www.example.com"
153
+ assert_seo_geo_robots_allows "OAI-SearchBot", "/pricing", host: "www.example.com"
154
+ refute_seo_geo_robots_allows "OAI-SearchBot", "/admin", host: "www.example.com"
155
+ assert_seo_geo_sitemap host: "www.example.com"
156
+ assert_seo_geo_no_sitemap host: "example.com"
157
+ end
158
+ end
159
+ end
160
+ ```
161
+
162
+ RSpec: `require "seo_geo/rspec"` gives `have_seo_geo_canonical`, `have_seo_geo_single_title`,
163
+ `have_seo_geo_faq_matching_visible_text`, `be_seo_geo_valid_sitemap(origin:)` and `seo_geo_robots_allow(ua, path)`.
164
+
165
+ ## Invariants — do not violate
166
+
167
+ 1. **Hosts fail closed.** Every hostname that serves the app is either `canonical_origin`'s host, listed in
168
+ `canonical_aliases`/`canonical_exclude_hosts`, or declared with `seo.host`. Undeclared hosts (staging,
169
+ `*.onrender.com`) get `Disallow: /` and no sitemap. Never "fix" a closed host by allowing everything.
170
+ 2. **One copy source per page.** Text that appears in FAQPage JSON-LD comes from `SeoGeo.page(:key)` and is
171
+ rendered from the same object (`seo_geo_faq`, `seo_geo_fallback_body`, `Page#props`). Never hand-write a
172
+ second copy in a view or component, and never add JSON-LD for content that is not visible.
173
+ 3. **The User-Agent is not identity.** Do not branch access, content or rate limits on `request.user_agent`
174
+ (`rake seo_geo:audit` warns: `code.user_agent_branch`). To trust a bot, add
175
+ `config.middleware.use SeoGeo::BotVerifier::Middleware` and require `request.env["seo_geo.bot"][:verified] == true`.
176
+ 4. **Never invent `lastmod`.** No `Time.now`, `Date.today` or `updated_at` of an unrelated record. Use a real
177
+ column (`lastmod: :updated_at`) or omit it. No made-up prices, ratings or counts in JSON-LD either.
178
+ 5. **Noindex in one place.** A page that must stay out of search goes in `noindex_paths` or
179
+ `sitemap.add path, noindex: true`, which keeps it out of sitemap.xml and llms.txt and renders robots
180
+ noindex. A request-time `seo noindex: true` alone cannot reach sitemap.xml.
181
+ 6. **Mount last.** `mount SeoGeo::Engine, at: "/"` stays below the app's routes and above any catch-all.
182
+ 7. **`:full_access` is opt-in.** `crawlers.override "Mediapartners-Google", :full_access` ignores every host
183
+ `disallow` for that token. Never use it for AI crawlers or as a way to open a path; use `allow:` instead.
184
+ 8. **Collections are lambdas.** `add -> { Post.published }, path: ...`. A scope passed directly loads the model
185
+ at boot and keeps a stale class across development reloads. `select` must include `id` (records are read
186
+ with `find_each`).
187
+
188
+ ## Check commands
189
+
190
+ ```sh
191
+ bundle exec rake test # this repository: unit, docs examples, both dummy apps
192
+ bin/rails seo_geo:audit # your app: exits 1 on errors; FORMAT=json HOSTS=a,b MAX_PAGES=200
193
+ SEO_GEO_AUDIT=1 bin/rails runner 'puts SeoGeo::Audit::Runner.new(app: Rails.application, root: Rails.root).run'
194
+ ```
195
+
196
+ The rake task audits as production even in development: it sets `SEO_GEO_AUDIT=1` itself, so the engine adds
197
+ the configured hosts to `config.hosts` for that run (`seo_geo audit: temporarily allowing ... in config.hosts`).
198
+ A runner needs the variable before boot. Production `config.hosts` is never widened.
199
+
200
+ With the app running (`bin/rails s`), check what crawlers and agents receive. robots.txt and sitemap.xml only
201
+ open up in `indexable_environments` (default production):
202
+
203
+ <!-- curl: quickstart -->
204
+ ```sh
205
+ curl -s -H "Host: www.example.com" http://localhost:3000/robots.txt
206
+ curl -s -H "Host: unknown.example.net" http://localhost:3000/robots.txt # Disallow: /
207
+ curl -s -H "Host: www.example.com" http://localhost:3000/sitemap.xml
208
+ curl -s -H "Host: example.com" http://localhost:3000/sitemap.xml # => 404 (alias)
209
+ curl -s -H "Host: www.example.com" http://localhost:3000/llms.txt
210
+ curl -si -H "Host: www.example.com" -H "Accept: text/markdown" http://localhost:3000/pricing
211
+ curl -si -H "Host: www.example.com" http://localhost:3000/index.md
212
+ ```
213
+
214
+ ## Troubleshooting
215
+
216
+ See the README's [Troubleshooting](README.md#troubleshooting) table (exact error messages, causes and fixes).
217
+
218
+ ## Working on this repository
219
+
220
+ - Verify with `bundle exec rake test`: unit tests, docs examples, then app tests against `test/dummy` (ERB)
221
+ and `test/dummy_inertia`, each dummy in its own process. Other Rails versions:
222
+ `BUNDLE_GEMFILE=gemfiles/rails_7_2.gemfile bundle exec rake test` (also `rails_8_0`, `rails_8_1`).
223
+ - [docs/DESIGN.md](docs/DESIGN.md) is the contract. Names and signatures in § Public API and § Shared contract
224
+ are fixed; changing one means updating DESIGN.md in the same change. New configuration keys get a default, a
225
+ YARD comment, an `errors` entry when they can be invalid, and a test in `test/unit/configuration_test.rb`.
226
+ - Shared files have append-only zones: `lib/seo_geo.rb` (require list), `lib/seo_geo/engine.rb` (one named
227
+ initializer per topic), `config/routes.rb` and `CHANGELOG.md` (one bullet per change under Unreleased).
228
+ - Reuse the shared primitives instead of re-implementing them: `SeoGeo::RobotsPath` (RFC 9309 path
229
+ normalization), `SeoGeo::RobotsDirectives`, `SeoGeo::SafeRegexp`, `SeoGeo::Publication.publishable?`, and
230
+ `config.canonical_url` / `config.host_policy`.
231
+ - Tests: `test/unit/**` boots no Rails app; `test/app/**` runs against both dummies (`inertia_dummy?` to branch);
232
+ `test/docs/**` boots `docs/examples/` apps in subprocesses and checks README/AGENTS code blocks against them.
233
+ A README or AGENTS code block in Ruby, ERB, YAML or JS needs a `<!-- example: path -->` marker and a file
234
+ under `docs/examples/`; output blocks use `<!-- output: ... -->` and are compared with real responses.
235
+ - Every Ruby file under `lib/` and `app/` starts with `# frozen_string_literal: true`; public methods carry YARD
236
+ comments.
237
+
238
+ ## Related packages
239
+
240
+ - [meta-tags](https://github.com/kpumuk/meta-tags): the tag renderer seo_geo builds on; `set_meta_tags` values
241
+ still apply.
242
+ - [inertia_rails](https://github.com/inertiajs/inertia-rails): pair with `Page#props` and `seo_geo_fallback_body`.
243
+ - [webmcp](https://github.com/seunghan91/webmcp): lets in-browser AI agents call your app's endpoints as tools.
244
+ It complements seo_geo, which is about being read rather than being operated.
data/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.1.0 - 2026-10-14
6
+
7
+ First release.
8
+
9
+ - Configuration: one `SeoGeo.configure` (also `config.seo_geo`) validated at boot — production server boots raise,
10
+ production generator/rake tasks and non-production boots warn; purpose-based crawler table
11
+ (`crawlers.training/search/user`, per-token `override` including opt-in `:full_access`); per-host policies with
12
+ fail-closed unknown hosts (logged once per host in indexable environments); alias hosts inherit the canonical
13
+ host's policy; canonical URL folding (`canonical_aliases`, `canonical_exclude_hosts`, `canonical_keep_params`).
14
+ - Head: `seo_geo_head` (single `<title>` via `title_template`, description truncation, folded canonical, hreflang +
15
+ x-default, robots noindex for non-indexable hosts/environments, `default_noindex` and declared noindex paths,
16
+ verification metas, Open Graph/Twitter with `og:image` size only when known, `og:locale` from one locale rule,
17
+ Markdown alternate link, JSON-LD `@graph` with CSP nonce and `json_escape`); controller `seo(...)` and
18
+ `seo_geo_page`; `SeoGeo::JsonLd` builder with required-field checks; crawler fallback body (`seo_geo_fallback_body`
19
+ with an extra slot, `seo_geo_faq`) and `window.SeoGeo.mounted()`.
20
+ - Page copy: `SeoGeo.page(key)` from Ruby, I18n or YAML — one source for crawler HTML, FAQPage JSON-LD and
21
+ Inertia props (`Page#props`, `intro` as String or Array).
22
+ - Crawler files: per-host `GET /robots.txt` (RFC 9309; every named group carries the host rules; Content-Signal,
23
+ optional AIPREF Content-Usage, Daum PIN), `GET /sitemap.xml` with a sitemap index above 50,000 URLs and lazy
24
+ collection sources (`add -> { scope }`, `batch: false`), `GET /llms.txt` and `/llms-full.txt`.
25
+ - One publication rule (`SeoGeo::Publication`) behind sitemap.xml, llms.txt and the head: robots disallow,
26
+ exclusions, `noindex_paths` / `sitemap.add ..., noindex: true`, `default_noindex` + `index: true` (with `match:`
27
+ for collections). Paths compare in RFC 9309 form (`SeoGeo::RobotsPath`); configuration Regexps run with a timeout
28
+ (`SeoGeo::SafeRegexp`).
29
+ - Markdown for agents: `SeoGeo::Markdownable` (Accept negotiation and `.md` URLs — `markdown.path_suffix` turns the
30
+ URL form off — YAML frontmatter, JSON-LD fences, `Vary: Accept`, `Content-Signal`, robots directives carried into
31
+ `X-Robots-Tag`), page-copy Markdown for Inertia mount points.
32
+ - IndexNow: `SeoGeo::IndexNow.submit`, `SeoGeo::IndexNowJob`, dry run outside indexable environments,
33
+ `/<key>.txt`, model macro `seo_geo_indexnow`.
34
+ - OG images: `GET /og/:key.png` with a pluggable backend or the optional ferrum renderer, routed only when
35
+ configured and only for known keys, constrained params, cache TTL, concurrency cap and fallback redirect.
36
+ - Audit and testing: `rake seo_geo:audit` (in-process, works with a development `config.hosts`), `rails g
37
+ seo_geo:install` (mounts the engine after the app's routes), Minitest assertions and RSpec matchers
38
+ (`with_seo_geo_environment`), `SeoGeo::Robots::Evaluator`, opt-in `SeoGeo::BotVerifier`.
39
+ - CI: Ruby 3.2–3.4 × Rails 7.2/8.0/8.1, each against an ERB app and an Inertia-style app; README and AGENTS.md
40
+ examples run in the test suite.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 seunghan Kim
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.