Geofeed tuner
Create and improve public IP geolocation feed CSV files by validating RFC 8805 structure, making them well-formed and real-world useful, applying opinionated tuning checks to operator-supplied data, using bundled ISO and territory data, and generating an HTML tuning report.
When to invoke
- "Validate this RFC 8805 geofeed CSV."
- "Tune our IP geolocation feed for better accuracy."
- "Create a self-published geofeed for these public prefixes."
- "Check geofeed country, region, city, and postal code fields."
- "Publish a geofeed for an ISP, carrier, cloud provider, IXP, hosting provider, or satellite provider."
Prerequisites and context
- Python 3 is required.
- Use this skill only for publicly routable IP addresses. Do not use it for private or internal IP address management.
- Accepted input: pasted subnet rows, a local CSV file, or a remote URL pointing to a CSV file.
- If the user has not supplied IP subnets or ranges (
inetnum or inet6num), ask for them before processing.
Progressive disclosure and bundled resources
Use progressive-disclosure: bundled resources are read-only distribution content, while run/ contains agent-generated files.
| Path |
Purpose |
Rule |
assets/ |
Static ISO code and example data. |
Read-only; never create, modify, or delete files here. |
assets/iso3166-1.json |
ISO 3166-1 alpha-2 country codes. |
Use for country validation. |
assets/iso3166-2.json |
ISO 3166-2 subdivision codes. |
Use for region validation. |
assets/small-territories.json |
Small-territory tuning data. |
Use for region/city granularity suggestions. |
references/rfc8805.txt |
Full RFC text. |
Read only for edge cases or standards questions. |
references/snippets-python3.md |
Python snippets. |
Read when writing phase scripts. |
references/extended-guide.md |
Extended guidance beyond the main file. |
Read when deeper phase details are needed. |
scripts/ |
Distribution scripts and HTML templates. |
Read-only distribution files. |
scripts/templates |
HTML report templates. |
Use for generated reports if needed. |
run/ |
Generated scripts and working files. |
Agent-created, may be cleared between sessions. |
run/data/ |
Downloaded CSV files and report-data.json. |
Store remote downloads and JSON output here. |
run/report/ |
Generated HTML reports. |
Store final HTML tuning report here. |
Execute generated scripts from the skill root directory, the directory containing SKILL.md, so relative paths such as assets/iso3166-1.json and ./run/data/report-data.json resolve. Do not cd into ./run/ before running scripts.
RFC 8805 feed rules
| Column |
Field |
Required |
Rule |
| 1 |
ip_prefix |
Yes |
CIDR notation; IPv4 or IPv6; must be a network address. |
| 2 |
alpha2code |
No |
ISO 3166-1 alpha-2 country code; empty or ZZ means do-not-geolocate. |
| 3 |
region |
No |
ISO 3166-2 subdivision code such as US-CA. |
| 4 |
city |
No |
Free-text city name; no authoritative validation set. |
| 5 |
postal_code |
No |
Deprecated; leave empty or absent. |
Structural rules:
- Files may contain comment lines beginning with
#, including the header when present.
- Header row is optional; if present, treat it as a comment when it starts with
#.
- Files must be UTF-8.
- Subnet host bits must not be set;
192.168.1.1/24 is invalid and should be 192.168.1.0/24.
- Feed rows apply only to globally routable unicast addresses, not private, loopback, link-local, or multicast space.
- An empty
alpha2code or case-insensitive ZZ, regardless of region/city, is an explicit do-not-geolocate signal.
- Postal or ZIP codes are deprecated by RFC 8805 Section 2.1.1.5 because they are too fine-grained and create privacy concerns.
Procedure
Before each phase, print a visible TODO checklist, update it as steps complete, and do not skip phases.
| Phase |
Name |
Output |
| 1 |
Understand the Standard |
Use the RFC 8805 summary above; read references/rfc8805.txt only for edge cases. |
| 2 |
Gather Input |
UTF-8 normalized local working data, with remote URLs downloaded to ./run/data/. |
| 3 |
Checks & Suggestions |
A separate generated script writes ./run/data/report-data.json. |
| 4 |
Tuning Data Lookup |
Use Fastah's MCP tool or available tuning data to add TunedEntry to each Entries object. |
| 5 |
Generate Tuning Report |
HTML report in ./run/report/. |
| 6 |
Final Review |
Consistency and completeness verification. |
Phase 2 input handling:
- Remote URL: download to
./run/data/. On 4xx, 5xx, timeout, or redirect loop, stop with Feed URL is not reachable: HTTP {status_code}. Please verify the URL is publicly accessible.
- Local file: process directly without downloading.
- Encoding: try UTF-8, then
utf-8-sig, then latin-1; on UnicodeDecodeError, continue to the next encoding; re-encode and rewrite the working copy as UTF-8. If decoding fails, stop with Unable to decode input file. Please save it as UTF-8 and try again.
Phase 3 execution rules:
- Generate a script for Phase 3.
- Do not combine Phase 3 with other phases.
- Do not precompute future-phase data.
- Store output exactly at
./run/data/report-data.json.
- Keep Phase 3 schema IMMUTABLE; Phase 4 may add only
TunedEntry to each object in Entries.
Phase 3 JSON schema
Phase 3 MUST preserve each per-entry, entry-level, human-readable field. Messages.Checked is auto-tunable when the reference table has Tunable: true; checked report controls use checked, and non-tunable controls may render disabled. The schema includes non-empty Messages when a rule fires and preserves examples such as "1101", "3301", "ERROR", "WARNING", "SUGGESTION", and "ZZ".
JSON keys map directly to template placeholders such as {{.CountryCode}} and {{.HasError}}.
{
"InputFile": "",
"Timestamp": 0,
"TotalEntries": 0,
"IpV4Entries": 0,
"IpV6Entries": 0,
"InvalidEntries": 0,
"Errors": 0,
"Warnings": 0,
"OK": 0,
"Suggestions": 0,
"CityLevelAccuracy": 0,
"RegionLevelAccuracy": 0,
"CountryLevelAccuracy": 0,
"DoNotGeolocate": 0,
"Entries": [
{
"Line": 0,
"IPPrefix": "",
"CountryCode": "",
"RegionCode": "",
"City": "",
"Status": "",
"IPVersion": "",
"Messages": [
{"ID": "", "Type": "", "Text": "", "Checked": false}
],
"HasError": false,
"HasWarning": false,
"HasSuggestion": false,
"DoNotGeolocate": false,
"GeocodingHint": "",
"Tunable": false
}
]
}
Top-level fields: InputFile, Timestamp, TotalEntries, IpV4Entries, IpV6Entries, InvalidEntries, Errors, Warnings, OK, Suggestions, CityLevelAccuracy, RegionLevelAccuracy, CountryLevelAccuracy, and DoNotGeolocate. Entry fields: Line, IPPrefix, CountryCode, RegionCode, City, Status, IPVersion, Messages, HasError, HasWarning, HasSuggestion, DoNotGeolocate, GeocodingHint, and Tunable.
Status severity is ERROR > WARNING > SUGGESTION > OK. Line is 1-based and counts comments and blanks. IPVersion is "IPv4" or "IPv6". GeocodingHint is always "" in Phase 3. Tunable is true when any message has Checked: true.
Validation rules reference
| ID |
Type |
Text |
Checked |
Condition |
1101 |
ERROR |
IP prefix is empty |
false |
Empty IP prefix. |
1102 |
ERROR |
Invalid IP prefix: unable to parse as IPv4 or IPv6 network |
false |
Invalid syntax. |
1103 |
ERROR |
Non-public IP range is not allowed in an RFC 8805 feed |
false |
Non-public and non-public range. |
3101 |
SUGGESTION |
IPv4 prefix is unusually large and may indicate a typo |
false |
IPv4 prefix smaller than /22. |
3102 |
SUGGESTION |
IPv6 prefix is unusually large and may indicate a typo |
false |
IPv6 prefix smaller than /64. |
1201 |
ERROR |
Invalid country code: not a valid ISO 3166-1 alpha-2 value |
true |
Invalid country. |
1301 |
ERROR |
Invalid region format; expected COUNTRY-SUBDIVISION (e.g., US-CA) |
true |
Bad region format. |
1302 |
ERROR |
Invalid region code: not a valid ISO 3166-2 subdivision |
true |
Unknown region. |
1303 |
ERROR |
Region code does not match the specified country code |
true |
Country/region mismatch. |
1401 |
ERROR |
Invalid city name: placeholder value is not allowed |
false |
Placeholder city. |
1402 |
ERROR |
Invalid city name: abbreviated or code-based value detected |
true |
City abbreviation. |
2401 |
WARNING |
City name formatting is inconsistent; consider normalizing the value |
true |
City formatting. |
1501 |
ERROR |
Postal codes are deprecated by RFC 8805 and must be removed for privacy reasons |
true |
Postal code present. |
3301 |
SUGGESTION |
Region is usually unnecessary for small territories; consider removing the region value |
true |
Small territory region. |
3402 |
SUGGESTION |
City-level granularity is usually unnecessary for small territories; consider removing the city value |
true |
Small territory city. |
3303 |
SUGGESTION |
Region code is recommended when a city is specified; choose a region from the dropdown |
true |
Missing region with city. |
3104 |
SUGGESTION |
Confirm whether this subnet is intentionally marked as do-not-geolocate or missing location data |
true |
Unspecified geolocation. |
Populate messages exactly from the table:
entry["Messages"].append({
"ID": "1201",
"Type": "ERROR",
"Text": "Invalid country code: not a valid ISO 3166-1 alpha-2 value",
"Checked": True,
})
entry["HasError"] = any(m["Type"] == "ERROR" for m in entry["Messages"])
entry["HasWarning"] = any(m["Type"] == "WARNING" for m in entry["Messages"])
entry["HasSuggestion"] = any(m["Type"] == "SUGGESTION" for m in entry["Messages"])
entry["Tunable"] = any(m["Checked"] for m in entry["Messages"])
Output template
## Geofeed tuning report
**Status:** complete | blocked | needs input
**Input file:** <InputFile>
**Report data:** `./run/data/report-data.json`
**HTML report:** `./run/report/<report-name>.html`
### Summary
| Metric | Count |
| --- | --- |
| Total entries | <TotalEntries> |
| IPv4 entries | <IpV4Entries> |
| IPv6 entries | <IpV6Entries> |
| Invalid entries | <InvalidEntries> |
| Errors | <Errors> |
| Warnings | <Warnings> |
| Suggestions | <Suggestions> |
| Do-not-geolocate | <DoNotGeolocate> |
### Findings
| Line | Prefix | Status | Messages | Tunable |
| --- | --- | --- | --- | --- |
| <Line> | <IPPrefix> | <Status> | <ID: Text> | <true/false> |
### Validation
- Phase checklists completed: yes/no
- RFC 8805 structure checked: yes/no
- JSON schema written unchanged in Phase 3: yes/no
- Phase 4 `TunedEntry` extension only: yes/no/not run
Quality gate
1---2name: geofeed-tuner3description: Create, tune, validate, and publish RFC 8805 self-published IP geolocation CSV feeds for public IP space. Use when asked about "IP geolocation feeds", "RFC 8805", "geofeed CSV", "tune geofeed accuracy", "validate a geofeed", or "publish a self-published IP geolocation feed".4license: Apache-2.05---67<!-- Generated from harness/github-copilot/skills/geofeed-tuner/SKILL.md by harness/claude-code/scripts/convert_from_copilot.py. Edit the source, not this file. -->89# Geofeed tuner1011Create and improve public IP geolocation feed CSV files by validating RFC 8805 structure, making them well-formed and real-world useful, applying opinionated tuning checks to operator-supplied data, using bundled ISO and territory data, and generating an HTML tuning report.1213## When to invoke1415- "Validate this RFC 8805 geofeed CSV."16- "Tune our IP geolocation feed for better accuracy."17- "Create a self-published geofeed for these public prefixes."18- "Check geofeed country, region, city, and postal code fields."19- "Publish a geofeed for an ISP, carrier, cloud provider, IXP, hosting provider, or satellite provider."2021## Prerequisites and context2223- Python 3 is required.24- Use this skill only for publicly routable IP addresses. Do not use it for private or internal IP address management.25- Accepted input: pasted subnet rows, a local CSV file, or a remote URL pointing to a CSV file.26- If the user has not supplied IP subnets or ranges (`inetnum` or `inet6num`), ask for them before processing.2728## Progressive disclosure and bundled resources2930Use progressive-disclosure: bundled resources are read-only distribution content, while `run/` contains agent-generated files.313233| Path | Purpose | Rule |34| --- | --- | --- |35| `assets/` | Static ISO code and example data. | Read-only; never create, modify, or delete files here. |36| `assets/iso3166-1.json` | ISO 3166-1 alpha-2 country codes. | Use for country validation. |37| `assets/iso3166-2.json` | ISO 3166-2 subdivision codes. | Use for region validation. |38| `assets/small-territories.json` | Small-territory tuning data. | Use for region/city granularity suggestions. |39| `references/rfc8805.txt` | Full RFC text. | Read only for edge cases or standards questions. |40| `references/snippets-python3.md` | Python snippets. | Read when writing phase scripts. |41| `references/extended-guide.md` | Extended guidance beyond the main file. | Read when deeper phase details are needed. |42| `scripts/` | Distribution scripts and HTML templates. | Read-only distribution files. |43| `scripts/templates` | HTML report templates. | Use for generated reports if needed. |44| `run/` | Generated scripts and working files. | Agent-created, may be cleared between sessions. |45| `run/data/` | Downloaded CSV files and `report-data.json`. | Store remote downloads and JSON output here. |46| `run/report/` | Generated HTML reports. | Store final HTML tuning report here. |4748Execute generated scripts from the skill root directory, the directory containing `SKILL.md`, so relative paths such as `assets/iso3166-1.json` and `./run/data/report-data.json` resolve. Do not `cd` into `./run/` before running scripts.4950## RFC 8805 feed rules5152| Column | Field | Required | Rule |53| --- | --- | --- | --- |54| 1 | `ip_prefix` | Yes | CIDR notation; IPv4 or IPv6; must be a network address. |55| 2 | `alpha2code` | No | ISO 3166-1 alpha-2 country code; empty or `ZZ` means do-not-geolocate. |56| 3 | `region` | No | ISO 3166-2 subdivision code such as `US-CA`. |57| 4 | `city` | No | Free-text city name; no authoritative validation set. |58| 5 | `postal_code` | No | Deprecated; leave empty or absent. |5960Structural rules:6162- Files may contain comment lines beginning with `#`, including the header when present.63- Header row is optional; if present, treat it as a comment when it starts with `#`.64- Files must be UTF-8.65- Subnet host bits must not be set; `192.168.1.1/24` is invalid and should be `192.168.1.0/24`.66- Feed rows apply only to globally routable unicast addresses, not private, loopback, link-local, or multicast space.67- An empty `alpha2code` or case-insensitive `ZZ`, regardless of region/city, is an explicit do-not-geolocate signal.68- Postal or ZIP codes are deprecated by RFC 8805 Section 2.1.1.5 because they are too fine-grained and create privacy concerns.6970## Procedure7172Before each phase, print a visible TODO checklist, update it as steps complete, and do not skip phases.7374| Phase | Name | Output |75| --- | --- | --- |76| 1 | Understand the Standard | Use the RFC 8805 summary above; read `references/rfc8805.txt` only for edge cases. |77| 2 | Gather Input | UTF-8 normalized local working data, with remote URLs downloaded to `./run/data/`. |78| 3 | Checks & Suggestions | A separate generated script writes `./run/data/report-data.json`. |79| 4 | Tuning Data Lookup | Use Fastah's MCP tool or available tuning data to add `TunedEntry` to each `Entries` object. |80| 5 | Generate Tuning Report | HTML report in `./run/report/`. |81| 6 | Final Review | Consistency and completeness verification. |8283Phase 2 input handling:8485- Remote URL: download to `./run/data/`. On 4xx, 5xx, timeout, or redirect loop, stop with `Feed URL is not reachable: HTTP {status_code}. Please verify the URL is publicly accessible.`86- Local file: process directly without downloading.87- Encoding: try UTF-8, then `utf-8-sig`, then `latin-1`; on `UnicodeDecodeError`, continue to the next encoding; re-encode and rewrite the working copy as UTF-8. If decoding fails, stop with `Unable to decode input file. Please save it as UTF-8 and try again.`8889Phase 3 execution rules:9091- Generate a script for Phase 3.92- Do not combine Phase 3 with other phases.93- Do not precompute future-phase data.94- Store output exactly at `./run/data/report-data.json`.95- Keep Phase 3 schema IMMUTABLE; Phase 4 may add only `TunedEntry` to each object in `Entries`.9697## Phase 3 JSON schema9899Phase 3 MUST preserve each per-entry, entry-level, human-readable field. `Messages.Checked` is auto-tunable when the reference table has `Tunable: true`; checked report controls use `checked`, and non-tunable controls may render `disabled`. The schema includes non-empty `Messages` when a rule fires and preserves examples such as `"1101"`, `"3301"`, `"ERROR"`, `"WARNING"`, `"SUGGESTION"`, and `"ZZ"`.100101102JSON keys map directly to template placeholders such as `{{.CountryCode}}` and `{{.HasError}}`.103104```json105{106 "InputFile": "",107 "Timestamp": 0,108 "TotalEntries": 0,109 "IpV4Entries": 0,110 "IpV6Entries": 0,111 "InvalidEntries": 0,112 "Errors": 0,113 "Warnings": 0,114 "OK": 0,115 "Suggestions": 0,116 "CityLevelAccuracy": 0,117 "RegionLevelAccuracy": 0,118 "CountryLevelAccuracy": 0,119 "DoNotGeolocate": 0,120 "Entries": [121 {122 "Line": 0,123 "IPPrefix": "",124 "CountryCode": "",125 "RegionCode": "",126 "City": "",127 "Status": "",128 "IPVersion": "",129 "Messages": [130 {"ID": "", "Type": "", "Text": "", "Checked": false}131 ],132 "HasError": false,133 "HasWarning": false,134 "HasSuggestion": false,135 "DoNotGeolocate": false,136 "GeocodingHint": "",137 "Tunable": false138 }139 ]140}141```142143Top-level fields: `InputFile`, `Timestamp`, `TotalEntries`, `IpV4Entries`, `IpV6Entries`, `InvalidEntries`, `Errors`, `Warnings`, `OK`, `Suggestions`, `CityLevelAccuracy`, `RegionLevelAccuracy`, `CountryLevelAccuracy`, and `DoNotGeolocate`. Entry fields: `Line`, `IPPrefix`, `CountryCode`, `RegionCode`, `City`, `Status`, `IPVersion`, `Messages`, `HasError`, `HasWarning`, `HasSuggestion`, `DoNotGeolocate`, `GeocodingHint`, and `Tunable`.144145Status severity is `ERROR` > `WARNING` > `SUGGESTION` > `OK`. `Line` is 1-based and counts comments and blanks. `IPVersion` is `"IPv4"` or `"IPv6"`. `GeocodingHint` is always `""` in Phase 3. `Tunable` is true when any message has `Checked: true`.146147## Validation rules reference148149| ID | Type | Text | Checked | Condition |150| --- | --- | --- | --- | --- |151| `1101` | `ERROR` | IP prefix is empty | `false` | Empty IP prefix. |152| `1102` | `ERROR` | Invalid IP prefix: unable to parse as IPv4 or IPv6 network | `false` | Invalid syntax. |153| `1103` | `ERROR` | Non-public IP range is not allowed in an RFC 8805 feed | `false` | Non-public and non-public range. |154| `3101` | `SUGGESTION` | IPv4 prefix is unusually large and may indicate a typo | `false` | IPv4 prefix smaller than `/22`. |155| `3102` | `SUGGESTION` | IPv6 prefix is unusually large and may indicate a typo | `false` | IPv6 prefix smaller than `/64`. |156| `1201` | `ERROR` | Invalid country code: not a valid ISO 3166-1 alpha-2 value | `true` | Invalid country. |157| `1301` | `ERROR` | Invalid region format; expected COUNTRY-SUBDIVISION (e.g., US-CA) | `true` | Bad region format. |158| `1302` | `ERROR` | Invalid region code: not a valid ISO 3166-2 subdivision | `true` | Unknown region. |159| `1303` | `ERROR` | Region code does not match the specified country code | `true` | Country/region mismatch. |160| `1401` | `ERROR` | Invalid city name: placeholder value is not allowed | `false` | Placeholder city. |161| `1402` | `ERROR` | Invalid city name: abbreviated or code-based value detected | `true` | City abbreviation. |162| `2401` | `WARNING` | City name formatting is inconsistent; consider normalizing the value | `true` | City formatting. |163| `1501` | `ERROR` | Postal codes are deprecated by RFC 8805 and must be removed for privacy reasons | `true` | Postal code present. |164| `3301` | `SUGGESTION` | Region is usually unnecessary for small territories; consider removing the region value | `true` | Small territory region. |165| `3402` | `SUGGESTION` | City-level granularity is usually unnecessary for small territories; consider removing the city value | `true` | Small territory city. |166| `3303` | `SUGGESTION` | Region code is recommended when a city is specified; choose a region from the dropdown | `true` | Missing region with city. |167| `3104` | `SUGGESTION` | Confirm whether this subnet is intentionally marked as do-not-geolocate or missing location data | `true` | Unspecified geolocation. |168169Populate messages exactly from the table:170171```python172entry["Messages"].append({173 "ID": "1201",174 "Type": "ERROR",175 "Text": "Invalid country code: not a valid ISO 3166-1 alpha-2 value",176 "Checked": True,177})178entry["HasError"] = any(m["Type"] == "ERROR" for m in entry["Messages"])179entry["HasWarning"] = any(m["Type"] == "WARNING" for m in entry["Messages"])180entry["HasSuggestion"] = any(m["Type"] == "SUGGESTION" for m in entry["Messages"])181entry["Tunable"] = any(m["Checked"] for m in entry["Messages"])182```183184## Output template185186```markdown187## Geofeed tuning report188189**Status:** complete | blocked | needs input190**Input file:** <InputFile>191**Report data:** `./run/data/report-data.json`192**HTML report:** `./run/report/<report-name>.html`193194### Summary195| Metric | Count |196| --- | --- |197| Total entries | <TotalEntries> |198| IPv4 entries | <IpV4Entries> |199| IPv6 entries | <IpV6Entries> |200| Invalid entries | <InvalidEntries> |201| Errors | <Errors> |202| Warnings | <Warnings> |203| Suggestions | <Suggestions> |204| Do-not-geolocate | <DoNotGeolocate> |205206### Findings207| Line | Prefix | Status | Messages | Tunable |208| --- | --- | --- | --- | --- |209| <Line> | <IPPrefix> | <Status> | <ID: Text> | <true/false> |210211### Validation212- Phase checklists completed: yes/no213- RFC 8805 structure checked: yes/no214- JSON schema written unchanged in Phase 3: yes/no215- Phase 4 `TunedEntry` extension only: yes/no/not run216```217218## Quality gate219220- [ ] The task is for public, globally routable IP space, not private IPAM.221- [ ] Each phase printed and updated a visible TODO checklist.222- [ ] Phases ran in order from 1 through 6 with no skipped dependency.223- [ ] Remote downloads went only to `./run/data/`; reports went only to `./run/report/`; generated scripts went only to `./run/`.224- [ ] `assets/`, `references/`, and `scripts/` were not modified.225- [ ] Phase 3 wrote `./run/data/report-data.json` and did not add future-phase data.226- [ ] Validation messages use the exact `ID`, `Type`, `Text`, and `Checked` values from the table.227- [ ] Postal codes are removed or flagged; `ZZ` and empty `alpha2code` are treated as do-not-geolocate.