From 7f45f3eed9bb7bb90179224e92a2d4407b89401e Mon Sep 17 00:00:00 2001 From: botre Date: Mon, 27 Jul 2026 21:07:37 +0200 Subject: [PATCH 1/6] Pin Node 22.23.1 and raise the e2e suite to TypeScript 6.0.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The e2e package declared TypeScript but nothing consumed it: there was no tsconfig, and Playwright transpiles specs without type checking. Adding one surfaced that the specs reach for Buffer while @types/node was never a dependency, so the types it declared could never have resolved. CI reads the Node version from .tool-versions rather than a literal, matching how the other repositories pin theirs. Verified: go vet, go test, the release build, and tsc against the new config. The Playwright suite was not run locally — httphq binds port 8080 from a constant, and that port is held here by an unrelated service. Claude-Session: https://claude.ai/code/session_01VmYrjjGBPRGS4zfYZuoCmw --- .github/workflows/test.yml | 2 +- .tool-versions | 1 + e2e/package-lock.json | 26 ++++++++++++++++++++++---- e2e/package.json | 3 ++- e2e/tsconfig.json | 13 +++++++++++++ 5 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 .tool-versions create mode 100644 e2e/tsconfig.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d3e9349..e61f996 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,7 @@ jobs: cache: false - uses: actions/setup-node@v6 with: - node-version: "22" + node-version-file: ".tool-versions" - name: Build server run: CGO_ENABLED=0 go build -o ./bin/httphq ./src working-directory: . diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..5f543b4 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 22.23.1 diff --git a/e2e/package-lock.json b/e2e/package-lock.json index 436698d..77f62d0 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -7,7 +7,8 @@ "name": "httphq-e2e", "devDependencies": { "@playwright/test": "^1.50.0", - "typescript": "^5.6.0" + "@types/node": "^22.20.0", + "typescript": "^6.0.3" } }, "node_modules/@playwright/test": { @@ -26,6 +27,16 @@ "node": ">=18" } }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -74,9 +85,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -86,6 +97,13 @@ "engines": { "node": ">=14.17" } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" } } } diff --git a/e2e/package.json b/e2e/package.json index 89c9b8d..cb4b9b9 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -8,6 +8,7 @@ }, "devDependencies": { "@playwright/test": "^1.50.0", - "typescript": "^5.6.0" + "@types/node": "^22.20.0", + "typescript": "^6.0.3" } } diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json new file mode 100644 index 0000000..9fca29a --- /dev/null +++ b/e2e/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "lib": ["ES2022", "DOM"], + "module": "preserve", + "moduleResolution": "bundler", + "noEmit": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2022", + "types": ["node"] + }, + "include": ["tests/**/*.ts", "playwright.config.ts"] +} From 3fb4e84e98e7899aa186551f6abd8eb5a8d5595e Mon Sep 17 00:00:00 2001 From: botre Date: Mon, 27 Jul 2026 22:09:22 +0200 Subject: [PATCH 2/6] Bring the workflow actions current and read the Go version from go.mod checkout, setup-go, setup-node and build-push-action go to v7. Both workflows hardcoded go-version "1.25" alongside a go.mod that already declares it. setup-go reads the module file instead, so the toolchain has one source of truth. Claude-Session: https://claude.ai/code/session_01VmYrjjGBPRGS4zfYZuoCmw --- .github/workflows/release.yml | 16 ++++++++-------- .github/workflows/test.yml | 14 +++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8435483..2a6df0f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,20 +10,20 @@ jobs: vet: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: - go-version: "1.25" + go-version-file: "go.mod" cache: false - run: go vet ./... build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: - go-version: "1.25" + go-version-file: "go.mod" cache: false - run: CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /tmp/httphq ./src @@ -34,7 +34,7 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - id: meta run: | @@ -55,7 +55,7 @@ jobs: - uses: docker/setup-buildx-action@v4 - - uses: docker/build-push-action@v6 + - uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e61f996..3de0e07 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,10 +13,10 @@ jobs: unit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: - go-version: "1.25" + go-version-file: "go.mod" cache: false - run: go test ./... e2e: @@ -25,12 +25,12 @@ jobs: run: working-directory: e2e steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: - go-version: "1.25" + go-version-file: "go.mod" cache: false - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: ".tool-versions" - name: Build server From 5a3d40ddc5a4ea3fe4d28932cbc3c20cec275b7e Mon Sep 17 00:00:00 2001 From: botre Date: Mon, 27 Jul 2026 22:43:07 +0200 Subject: [PATCH 3/6] Add an AGENTS.md and a CLAUDE.md pointer This repository had neither. It carries the same comment policy as the other three, plus the constraints that are not obvious from the source: that setting PLATFORM is a trust decision about a spoofable header, that captured data is deliberately ephemeral, the logging contract, and that the listen port is a constant rather than configuration. Claude-Session: https://claude.ai/code/session_01VmYrjjGBPRGS4zfYZuoCmw --- AGENTS.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 1 + 2 files changed, 54 insertions(+) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..87cbd14 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,53 @@ +# AGENTS.md + +Guidance for agents and contributors working in this repository. + +## Comments + +Comments describe what the code does now and warn about non-obvious constraints +or regressions. They are not a changelog, bug tracker, or ticket index. Write +them so they still make sense in a year. + +- No ticket or PR references. +- No bug war stories: don't narrate a specific bug and how it was fixed ("this + fixes the flicker when..."). Once fixed, that history is noise. +- No hyper-specific framing: don't tie comments to one-time scenarios; describe + the generic, reusable purpose instead. +- Explain intent and non-obvious behavior: why this branch exists, what + invariant it protects. +- Flag regression risks the next dev must respect (e.g. "excluded by default so + new surfaces are safe"). +- Keep comments generic and reusable, especially in shared helpers and test + fixtures. + +## Client IP is a trust decision + +`PLATFORM` selects which header the real client IP is read from, and setting it +trusts that header unconditionally — httphq cannot tell a platform's header +from one a client forged. Inbound traffic must not be able to reach the process +bypassing that platform, or a client can spoof its IP and evade rate limiting. +Leaving it unset behind a proxy is the opposite failure: every request looks +like it came from the proxy and rate limiting becomes global. + +## Captured data is ephemeral + +SQLite writes to the container's writable layer. Capture history is lost on +restart, by design — nothing here is a durable store, and no migration path +exists for it. + +## Logging + +Structured JSON to stdout via `log/slog`, with OpenTelemetry field names +(`service.name`, `http.request.method`, `url.path`, ...). Every request carries +a `request_id` that is reused from a valid inbound `X-Request-Id` or minted, +echoed on the response, and stamped onto every line emitted while handling it. + +Headers and bodies are never logged and paths are logged without their query +string; a denylist masks sensitive keys as a backstop. Probe traffic to +`/api/health` logs at debug so it stays out of production logs. + +## The listen port is a constant + +`port` in `src/application.go` is not configurable. Anything that needs to run +two instances, or to run alongside something already holding 8080, has to +change the constant. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8b7cbf4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +See [AGENTS.md](AGENTS.md). From 971639307a5e8afd0f8e8a76b562c9ce20f37fd0 Mon Sep 17 00:00:00 2001 From: botre Date: Mon, 27 Jul 2026 22:43:29 +0200 Subject: [PATCH 4/6] Drop the README logging section It now lives in AGENTS.md, and two copies of the same contract drift apart. Claude-Session: https://claude.ai/code/session_01VmYrjjGBPRGS4zfYZuoCmw --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index a9b3cc4..9c3faff 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,6 @@ without the noise of whatever provider sits in front of httphq. > behind a proxy but leave `PLATFORM` unset, every request appears to come > from the proxy and rate limiting becomes global. -## Logging - -httphq logs to stdout as structured JSON via the standard library's `log/slog` — one JSON object per line, with no log files or shipping built in, so any collector can pick the logs up. Field names follow OpenTelemetry conventions (`service.name`, `http.request.method`, `url.path`, `http.response.status_code`, ...). Every request gets a correlation `request_id` (a valid inbound `X-Request-Id` is reused, otherwise one is minted) that is echoed back on the response header and stamped onto every log line emitted while handling that request. Each request produces one access-log line; headers and bodies are never logged, paths are logged without their query string, and a denylist masks sensitive keys as a backstop. The level defaults to `info` in production (`debug` elsewhere) and is overridable with `LOG_LEVEL`; Kubernetes probe traffic to `/api/health` logs at `debug` so it stays out of production logs. - ## License [MIT](https://opensource.org/licenses/MIT) From 310fbc7b530b604aedb82ae7e9fd07b638f056b4 Mon Sep 17 00:00:00 2001 From: botre Date: Tue, 28 Jul 2026 10:52:54 +0200 Subject: [PATCH 5/6] Add the shared Renovate config and align @types/node Same configuration the other repositories carry, covering the Go module, the workflow actions and the e2e npm package, with a one-day minimumReleaseAge cooldown on proposed updates. Claude-Session: https://claude.ai/code/session_01VmYrjjGBPRGS4zfYZuoCmw --- e2e/package-lock.json | 2 +- e2e/package.json | 2 +- renovate.json | 13 +++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 renovate.json diff --git a/e2e/package-lock.json b/e2e/package-lock.json index 77f62d0..0a51883 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -7,7 +7,7 @@ "name": "httphq-e2e", "devDependencies": { "@playwright/test": "^1.50.0", - "@types/node": "^22.20.0", + "@types/node": "^22.20.1", "typescript": "^6.0.3" } }, diff --git a/e2e/package.json b/e2e/package.json index cb4b9b9..8d781a5 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -8,7 +8,7 @@ }, "devDependencies": { "@playwright/test": "^1.50.0", - "@types/node": "^22.20.0", + "@types/node": "^22.20.1", "typescript": "^6.0.3" } } diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..590de26 --- /dev/null +++ b/renovate.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"], + "rangeStrategy": "bump", + "minimumReleaseAge": "1 day", + "packageRules": [ + { + "matchDepTypes": ["devDependencies"], + "matchUpdateTypes": ["minor", "patch"], + "groupName": "dev dependencies (non-major)" + } + ] +} From a25faf4f38ffd5337a50ab3161b8a0891e1e1010 Mon Sep 17 00:00:00 2001 From: botre Date: Tue, 28 Jul 2026 11:14:09 +0200 Subject: [PATCH 6/6] Drop the Renovate config The Renovate app is not installed on this repository, so the file describes behaviour that does not run. Claude-Session: https://claude.ai/code/session_01VmYrjjGBPRGS4zfYZuoCmw --- renovate.json | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 renovate.json diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 590de26..0000000 --- a/renovate.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:recommended"], - "rangeStrategy": "bump", - "minimumReleaseAge": "1 day", - "packageRules": [ - { - "matchDepTypes": ["devDependencies"], - "matchUpdateTypes": ["minor", "patch"], - "groupName": "dev dependencies (non-major)" - } - ] -}