298 Commits

Author SHA1 Message Date
8a928d0e9c fix gsheet reader skip empty records 2026-09-09 03:20:28 -06:00
07d4a2652c
feat!(ippolicy): share refreshed IP policy behavior
Rename net/ipgate to net/ippolicy and compose independently refreshed
whitelist and blacklist sources into one IP decision policy.

- rename slog group and error prefixes from ipgate to ippolicy
- delete unused ParseDomainSet and RecordReader from domainsetcsv.go
- rewrite README to match the actual Config API
- document nil-whitelist behavior on New()
- bump iplist dependency to v0.8.5
- go mod tidy: remove stale ipgate and iplist entries
net/ippolicy/v0.8.5
2026-09-09 02:55:09 -06:00
869086ce0c
feat(iplist): add refreshable Source net/iplist/v0.8.5 2026-09-09 02:54:34 -06:00
1cb0235d9d
feat(ipgate): configure matcher refresh intervals net/ipgate/v0.6.2 2026-09-09 02:29:23 -06:00
03722a2934
feat(iplist): allow custom HTTP clients net/iplist/v0.7.0 2026-09-09 01:14:05 -06:00
c41ac3732d
feat(iplist): load and validate IP policy entries from TSV/CSV sources
Add net/iplist for shared IP-policy source loading.

- Accept local TSV/CSV file paths or HTTP(S) URLs (including Google Sheets).
- Auto-detect TSV vs CSV format from file content.
- Send Accept: text/tab-separated-values header for content negotiation.
- Parse the first column and ignore comments (#) and optional "network" header.
- Expand nested list URLs up to MaxNestedSources (2) levels.
- Support Basic Auth from URL userinfo without sending credentials in the URL.
- Persist cached URL responses across restarts via httpcache.
- Refresh periodically and preserve the known-good cache on fetch or parse failure.
- Validate entries as IP address, CIDR range, or domain name (Validate()).
- Use golib/https.NewInternalClient() through httpcache.NewWith.
- Add README with usage examples and entry format documentation.
2026-09-08 22:46:17 -06:00
a16ec976bb
feat(gsheet2csv): add context and custom-client APIs
Add non-breaking context-aware and custom-HTTP-client variants of the
existing reader and fetch functions:

- NewReaderFromContext / NewReaderFromContextWith
- NewReaderFromURLContext / NewReaderFromURLWith
- NewReaderFromIDsContext
- GetSheetContext

All existing function signatures are unchanged. The shared response
handling is extracted into newReaderFromResponse, and the legacy GetSheet
path is refactored through getSheetWithGet for clarity.
io/transform/gsheet2csv/v1.1.1
2026-09-08 22:46:11 -06:00
bb488fe230 fix(net/ipgate): prevent nil cohort by construction, extract cachedCohortValid
Ensure cohort is never nil:
- NewDomainSet stores an empty Cohort before rebuildCohort, so a parse
  failure leaves an empty (non-nil) cohort instead of nil
- Contains uses CompareAndSwap to lazily self-initialise a zero-value
  struct, making nil impossible after first access — no explicit nil
  check needed

Extract the cache-validity check into a testable cachedCohortValid()
helper and add filesPresent() to detect when data files exist on disk.

Use strings.Builder in commaify() for efficiency.

Tests:
- TestPrefixSetContainsZeroValue: zero-value self-heals via CAS,
  cohort is non-nil after first Contains call
- TestCachedCohortValidRequiresFiles: cache invalidation logic
net/ipgate/v0.5.3
2026-09-08 20:36:43 -06:00
70d95be546 test(net/gitshallow): support modern git orphan branches net/gitshallow/v0.9.3 2026-09-08 20:16:37 -06:00
4260287467 chore(net/gitshallow): add .goreleaser.yaml 2026-09-08 20:16:37 -06:00
f7ca1d22a5
feat(geoip-update): align CLI flags with conventions 2026-09-08 15:19:38 -06:00
e82e3979d2
fix(geoip): update httpcache dependency net/geoip/v0.5.1 2026-09-08 14:47:28 -06:00
05505c3554
fix(geoip-update): back off failed downloads 2026-09-08 14:44:48 -06:00
71f7752015
fix(httpcache): back off after failed fetches net/httpcache/v0.5.1 2026-09-08 14:44:48 -06:00
70aa1cbaa4
chore: remove comment io/transform/gsheet2csv/v1.1.0 2026-08-19 15:02:50 -06:00
1d16cabf87
doc(auth/jwt): add README.md (condensed from doc.go) auth/jwt/v0.10.1 2026-08-17 23:38:42 -06:00
392b8bc0f9
feat(auth/jwt): add HMACSigner/HMACVerifier 2026-08-17 23:38:42 -06:00
ab3013609a
chore(auth/jwt): enforce Signer/Verifier interfaces internally 2026-08-17 23:38:42 -06:00
20c7887bcd
test(auth): add AuthorizationSchemes tests (any-scheme, unaccepted scheme)
Add tests for AuthorizationSchemes ["*"] accepting any scheme and
for unaccepted schemes returning ErrNoCredentials without falling
through to cookies. These fill pre-existing test gaps unrelated to
the TokenCookies feature.
auth/v1.2.0
2026-08-17 23:15:24 -06:00
4b4a83b220
test(auth): add tests for BasicRequestAuthenticator including TokenCookies
Cover cookie extraction, priority ordering, empty value skipping,
error propagation, and existing gaps (Bearer any-scheme, unaccepted
scheme rejection, Basic Auth priority).
2026-08-17 23:15:18 -06:00
a87529dc5f
feat(auth): add TokenCookies to BasicRequestAuthenticator
Add a TokenCookies []string field checked after query params (step 5).
Each named cookie's value is passed directly to Authenticator.Authenticate
as a token, enabling cookie-based token flows alongside Bearer and query params.
2026-08-17 21:34:59 -06:00
484b6bc11a
fix(io/transform/gsheet2csv): use safe defaults for http client io/transform/gsheet2csv/v1.0.6 2026-08-17 17:25:45 -06:00
c66c4a8de4
ref!(https): rewrite with latest best practices, for publish https/v0.9.0 2026-08-17 15:15:09 -06:00
71671b7852
fix(sql-migrate): add missing period 'basename+".up.sql"' 2026-07-07 16:40:37 -06:00
bc3fb945af
chore(sql-migrate): lint 2026-07-07 16:30:13 -06:00
3e2b055c88
fix(sql-migrate): strip id\t prefix when parsing migrations.log
parseAndFixupBatches treated every line as a migration name, but the
new log format uses id\tname (tab-separated). This caused fixupMigration
to look for non-existent files like '05aba66a\t2026-06-03-003000_team-create.up.sql'.

Align with shmigrate.Applied() which already handles both formats.
cmd/sql-migrate/v2.2.5
2026-06-04 22:08:22 -06:00
a408dc890b
feat(sync/dataset): generic hot-swap refresh with atomic.Pointer
Coordinates a Set of Fetchers (each producing a typed dataset) so a
running server can swap to fresh data without locks on the read path.
Readers go through atomic.Pointer; Load/Close are mutex-serialized so
concurrent reloads can't race. Add and AddInitial register fetchers
before the first Load.
sync/dataset/v0.5.0
2026-05-26 22:46:04 -06:00
584061744b
feat(geoip-update): CLI for downloading GeoLite2 databases via conditional HTTP GET
- Downloads all editions listed in a GeoIP.conf
  - Flags: -config, -dir, -fresh-days, -base-url (override
    DownloadBase for custom mirror/proxy), -V/--version, -help
  - Version output shows name, version, commit, date, copyright and
    license (populated via ldflags at build time)
  - Uses Basic auth with split credential encoding for readability
  - ETag/Last-Modified caching via httpcache
net/geoip/v0.5.0 ‎net/geoip/v0.5.0
2026-05-26 22:04:22 -06:00
2f33b5e430
feat(net/geoip): MaxMind GeoLite2 reader with auto-update
Add a Go package for reading MaxMind GeoLite2 City and ASN databases.

  - ParseConf: parses geoipupdate-style config files (AccountID,
    LicenseKey, EditionIDs, DatabaseDirectory)
  - Databases: holds open City + ASN readers; Open() loads both
    editions from a directory, extracting .mmdb from tar.gz in memory
    without writing to disk
  - FindTarGz: resolves cache paths, preferring *_LATEST.tar.gz
  - Lookup: returns city, region, country, ASN, and AS org for an IP
  - DownloadBase constant, TarGzName helper, DefaultConfPaths and
    DefaultCacheDir conveniences

Integration tests verify download and conditional-get freshness.
2026-05-26 22:04:17 -06:00
ecba24973b
chore: add LOCAL.md and agents/ to .gitignore 2026-05-26 21:59:57 -06:00
04b8bb406d
feat(net/httpcache): ETag/Last-Modified HTTP cache
Conditional GET cache for periodic data sources. Persists ETag and
Last-Modified to a .meta sidecar so 304s survive process restarts.
Two independent rate-limit gates (MaxAge file-mtime, MinInterval
in-memory). Body cap via MaxBytes defends against fill-disk from a
hostile upstream or redirect target. Authorization (and any
caller-supplied) headers stripped on every redirect hop. Userinfo
in URL is redacted from error messages.
net/httpcache/v0.5.0
2026-05-26 01:27:20 -06:00
dddfdd392c
feat(net/ipgate): IP allowlist/blocklist via PrefixSet and DomainSet
PrefixSet: git-backed CIDR files via gitshallow, refreshed every 47 min.
DomainSet: takes pre-parsed (staticPrefixes, domains []string); resolves
hostnames every 5 min, retaining stale IPs on failure. Both use
atomic-swapped ipcohort.Cohort for lock-free reads.

ParseDomainSet(RecordReader) parses rows into prefixes and hostnames;
caller constructs the record reader (e.g. csv.Reader with delimiter,
comment char). RecordReader interface is satisfied by *csv.Reader.

commaify uses plain string concat (integers max ~7 groups).
net/ipgate/v0.5.2
2026-05-22 12:23:35 -04:00
d472c00ef1
feat(net/dnsresolver): parallel DNS resolver with fallback servers
Resolves A, CNAME chain, and SRV records via miekg/dns. Queries all
configured servers in parallel and returns the first success. Falls
back to OpenDNS/Cloudflare/Quad9 when /etc/resolv.conf is absent or empty.
net/dnsresolver/v0.5.0
2026-05-22 11:16:22 -04:00
1db21be047
fix(gitshallow): depth-3 windowed fetch with low-memory GC
- Default depth changed from 1 to 3: enough overlap for delta
  compression even if the remote pushes 2 commits between fetches
- Keep --depth on fetch (not just clone) so shallow stays windowed
- GCInterval default changed: 0 now means every fetch (was disabled)
- GC sequence: reflog expire → prune → gc --prune=now
  Expire makes old shallow entries' objects unreachable, prune removes
  them cheaply (5MB RSS), then gc delta-compresses what remains (~10MB)
- Only GC when HEAD actually changed (skip no-op fetches)
- Dropped GCAggressive (no benefit in shallow clones)
net/gitshallow/v0.9.1
2026-05-11 23:45:10 -06:00
1dacbdd8f7
feat(net/gitshallow): incremental shallow git-repo mirroring
Periodically-updated shallow clone for repos used as a data source.
One Fetch verb (clone-or-pull-or-skip) drives both the Fetcher
interface and per-File handles. MaxAge gates on FETCH_HEAD mtime so
short-lived CLI invocations don't hammer the remote across process
restarts; an in-memory 1s debounce coalesces concurrent Fetches.
Hardens git invocations against argument injection: validates
branch/ref shape, rejects URLs starting with '-', and uses '--'
separators before positional args. Userinfo in URL is redacted from
error output (both args echo and git's own messages).
net/gitshallow/cmd/git-shallow-sync/v0.9.0 net/gitshallow/v0.9.0
2026-05-02 16:15:54 -06:00
bea4c3de0a
feat(net/ipcohort): IP block/allow cohort filter
IPv4 host + CIDR membership testing backed by sorted /32 binary search
and a linear scan of CIDR ranges. CSV/text loader with bounded reads
(maxBytes is required so callers pick a limit appropriate to their
input source — 1 KB allowlists through 100+ MB threat feeds). Returns
errors for unparseable IPs so callers can decide fail-open vs
fail-closed; partial cohorts are returned alongside parse errors.
net/ipcohort/cmd/ipcohort-contains/v0.9.0 net/ipcohort/v0.9.0
2026-05-02 15:41:38 -06:00
c2f5dbeeca
doc(skills): pgmigrate clarificaton 2026-04-19 19:25:52 -06:00
65432d7c29
database/sqlmigrate/pgmigrate: add Schema field for qualified _migrations table
Add Schema string field to Migrator. When set, Applied() constructs a
schema-qualified table name via pgx.Identifier.Sanitize() rather than
the bare "_migrations". New() signature is unchanged.

Usage:
    runner := pgmigrate.New(conn)
    runner.Schema = "authz"
database/sqlmigrate/pgmigrate/v1.0.5
2026-04-17 03:53:32 -06:00
17bbc881a9
database/sqlmigrate: allow optional schema prefix in INSERT INTO _migrations
Update idFromInsert regex to match schema-qualified table references
such as INSERT INTO authz._migrations, in addition to the existing
unqualified INSERT INTO _migrations form.
database/sqlmigrate/v1.0.3
2026-04-17 02:30:41 -06:00
02fef67e53
fix(auth/csvauth): ID() returns Name only, not Name~hashID for tokens
Principal identity is the subject (who), not the credential instance
(which token). The hashID suffix was an internal cache fingerprint that
leaked into the public ID. Callers that need to distinguish individual
token instances must use a separate mechanism.

TSV serialization in ToRecord() still writes Name~hashID when hashID is
set so the credential file round-trips correctly.
auth/csvauth/v1.2.9
2026-04-13 22:57:21 -06:00
fbb4a14620
chore(git): ignore worktrees and vim swap files 2026-04-13 17:14:08 -06:00
4abac2a0df
feat(auth/xhubsig): X-Hub-Signature HMAC webhook verification + HTTP middleware
Verify X-Hub-Signature-256 (and SHA-1) webhook signatures. Middleware
buffers and re-exposes the body for downstream handlers. Errors honor
Accept header: TSV default (text/plain for browsers), JSON, CSV, or
Markdown — three fields (error, description, hint) with pseudocode hints.
auth/xhubsig/v0.9.0
2026-04-13 17:04:45 -06:00
aebef71a95
test(sqlmigrate): add ordering, end-to-end, rollback, and dialect-specific tests
Across all four backends:

- TestAppliedOrdering: insert rows out of order, verify Applied()
  returns them sorted by name. Guards against the ORDER BY clause
  being dropped or the query returning rows in arbitrary order.
- TestEndToEndCycle: Collect → Up → Applied → Down → Applied via
  the sqlmigrate orchestrator with real migration files. Catches
  wiring bugs between Migrator and orchestrator that the in-package
  mockMigrator tests cannot.
- TestDMLRollback: multi-statement DML migration where the last
  statement fails, verifies earlier INSERTs are rolled back. MySQL
  note: DML-only because MySQL implicitly commits DDL.

Dialect-specific:

- mymigrate TestMultiStatementsRequired: strip multiStatements=true
  from the DSN, verify ExecUp fails with a clear error mentioning
  multiStatements (rather than silently running only the first
  statement of a multi-statement migration).
- litemigrate TestForeignKeyEnforcement: verifies FK constraints
  are enforced when the DSN includes _pragma=foreign_keys(1).

Test fixture fix: cleanup closures now use context.Background()
instead of the test context. t.Context() is canceled before
t.Cleanup runs, so DB cleanup silently failed. Previously the
_migrations cleanup appeared to work because the next test's
connect() re-ran DROP TABLE at setup, but domain tables (test_*)
leaked across runs. New tests also pre-clean at setup for
self-healing after interrupted runs.
database/sqlmigrate/pgmigrate/v1.0.4 database/sqlmigrate/litemigrate/v1.0.4 database/sqlmigrate/msmigrate/v1.0.4 database/sqlmigrate/mymigrate/v1.0.4
2026-04-10 01:07:58 -06:00
28af8f49b8
test(mymigrate): use single-schema test fixture for hosted MariaDB
Hosted MariaDB users (e.g. todo_test_*) typically have access to a single
database/schema and cannot CREATE/DROP DATABASE. Drop the per-test database
isolation pattern in favour of dropping _migrations directly on entry and
exit, matching msmigrate's approach. Tests must not run concurrently
against the same DSN.
2026-04-10 00:25:29 -06:00
3402b60bc6
fix(sqlmigrate): defensive table-missing check at rows.Err() across backends
Apply the same lazy-error pattern fix to all backends, plus regression
tests that catch the bug.

pgmigrate is the confirmed-broken case (pgx/v5's Conn.Query is lazy and
surfaces 42P01 at rows.Err() once the prepared statement cache is primed).
The defensive check at rows.Err() is also added to mymigrate and msmigrate
in case their drivers exhibit similar behavior in some configurations.

litemigrate is refactored to probe sqlite_master with errors.Is(sql.ErrNoRows)
instead of string-matching the error message — SQLite returns the generic
SQLITE_ERROR code for "no such table" so a typed-error approach isn't
possible at the driver layer; the probe lets us use idiomatic errors.Is.

Tests:
- litemigrate: in-memory SQLite, runs on every go test (no infra)
- pgmigrate:   PG_TEST_URL env-gated; verified against real Postgres,
               TestAppliedAfterDropTable reproduces the agent's exact error
               message ("reading rows: ... 42P01") without the fix
- mymigrate:   MYSQL_TEST_DSN env-gated
- msmigrate:   MSSQL_TEST_URL env-gated; verified against real SQL Server

Each backend has four cases: missing table, populated table, empty table,
and table-dropped-after-cache-primed (the lazy-error scenario).
2026-04-10 00:15:06 -06:00
e11b228765
fix(pgmigrate): handle 42P01 surfaced lazily at rows.Err()
pgx/v5's Conn.Query is lazy — when the queried table doesn't exist,
the 42P01 error doesn't surface at Query() time, it surfaces at
rows.Err() after the iteration loop. The original code only checked
for 42P01 at the Query() site, so first-run migrations against an
empty database failed with:

    reading rows: ERROR: relation "_migrations" does not exist (SQLSTATE 42P01)

Apply the typed-error check at both sites via a shared helper.
2026-04-10 00:01:55 -06:00
0c1eb1f125
feat(skills): add sqlmigrate skill index and per-database skills
Index skill (use-sqlmigrate) plus focused skills for CLI usage, Go
library integration, and per-database conventions (PostgreSQL,
MySQL/MariaDB, SQLite, SQL Server).
2026-04-09 17:08:32 -06:00
5b3a4be3c2
fix(sql-migrate): remove local replace directives from go.mod
Replace directives pointing to local paths prevent `go install` from
working. Use published module versions (sqlmigrate v1.0.2, shmigrate
v1.0.2) so users can install with:

    go install github.com/therootcompany/golib/cmd/sql-migrate/v2@latest
cmd/sql-migrate/v2.2.4
2026-04-09 16:51:56 -06:00
dda4491c94
chore: golint fixes across all modules
Run scripts/golint: goimports, go fix, go vet, go mod tidy.
2026-04-09 14:23:33 -06:00
2256b12223
feat(sql-migrate): add sqlite and sqlcmd --sql-command aliases
- Use full flag names in command defaults (--no-align, --tuples-only, etc.)
- Add sqlite/sqlite3/lite alias for SQLite
- Add sqlcmd/mssql/sqlserver alias for SQL Server (go-sqlcmd, TDS 8.0)
- Add SQL Server help text with SQLCMD env vars and TLS docs
- Add sqlite and sqlcmd SELECT expressions for id+name migration log
2026-04-09 13:26:40 -06:00