Commit Graph

26 Commits

Author SHA1 Message Date
dependabot[bot] 86772b5e9e chore(ci)(deps): bump the actions-minor-and-patch group across 1 directory with 6 updates (#774)
Bumps the actions-minor-and-patch group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/setup-go](https://github.com/actions/setup-go) | `6.4.0` | `6.5.0` |
| [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) | `9.2.0` | `9.3.0` |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `4.0.0` | `4.2.0` |
| [docker/login-action](https://github.com/docker/login-action) | `4.1.0` | `4.4.0` |
| [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) | `7.2.1` | `7.2.3` |
| [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) | `4.1.0` | `4.1.1` |



Updates `actions/setup-go` from 6.4.0 to 6.5.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/4a3601121dd01d1626a1e23e37211e3254c1c06c...924ae3a1cded613372ab5595356fb5720e22ba16)

Updates `golangci/golangci-lint-action` from 9.2.0 to 9.3.0
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/1e7e51e771db61008b38414a730f564565cf7c20...ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a)

Updates `docker/setup-buildx-action` from 4.0.0 to 4.2.0
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd...bb05f3f5519dd87d3ba754cc423b652a5edd6d2c)

Updates `docker/login-action` from 4.1.0 to 4.4.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/4907a6ddec9925e35a0a9e82d7399ccc52663121...af1e73f918a031802d376d3c8bbc3fe56130a9b0)

Updates `goreleaser/goreleaser-action` from 7.2.1 to 7.2.3
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8...f06c13b6b1a9625abc9e6e439d9c05a8f2190e94)

Updates `actions/attest-build-provenance` from 4.1.0 to 4.1.1
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32...0f67c3f4856b2e3261c31976d6725780e5e4c373)

---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
  dependency-version: 4.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-and-patch
- dependency-name: actions/setup-go
  dependency-version: 6.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-and-patch
- dependency-name: docker/login-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-and-patch
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-and-patch
- dependency-name: golangci/golangci-lint-action
  dependency-version: 9.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-and-patch
- dependency-name: goreleaser/goreleaser-action
  dependency-version: 7.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 01:38:34 -04:00
raj kumar 694497948b ci: add Windows and macOS smoke tests (#911) 2026-07-11 01:22:41 -04:00
xarmian 2ff8ac9c15 fix(ci): let Go fetch the 1.26.5 toolchain go.mod pins (unbreak CI) (#901)
#896 raised the go.mod floor to `go 1.26.5`, but actions/setup-go's
`go-version: "1.26"` resolves to the newest patch in its manifest
(1.26.4) and unconditionally exports GOTOOLCHAIN=local, so every Go
command fails with "go.mod requires go >= 1.26.5 (running go 1.26.4)".
This broke `go vet`, the PostgreSQL test job, and the e2e "Build pad
binary" step on main and every PR since #896.

Override GOTOOLCHAIN=auto via a step that writes $GITHUB_ENV *after*
setup-go (last-write wins, since setup-go's export is unconditional),
so Go downloads the required toolchain on demand. Applied to all three
Go jobs in ci.yml plus release.yml.

Claude-Session: https://claude.ai/code/session_015yuBJQYfDj95cgX3DaD8SF
2026-07-10 19:40:27 -04:00
xarmian d6c6dfe682 build(deps): govulncheck binary mode + Go 1.26.5 / x-crypto / gRPC security bumps (#896)
BUG-2084. Two parts.

RAM fix: `make vuln` and CI's Go job now run govulncheck in BINARY mode
(`-mode binary` against a freshly-built pad binary) instead of source mode
(`govulncheck ./...`). Source mode builds an SSA call-graph over the whole
dependency tree (BigQuery/OTel/gRPC/Cloud) and balloons to multiple GB of
RAM, which was locking up a memory-constrained host. Binary mode reads the
binary's symbol table — ~99 MB peak here — while staying call-graph-precise
and still detecting stdlib vulns from the Go version stamped in the binary.
The scan binary is written to the repo root (real disk, gitignored), never
/tmp, since some hosts mount /tmp as a small RAM-backed tmpfs where a large
embedded binary can hit "no space left" and consume the RAM we're sparing.

Vuln fix (govulncheck binary mode: 0 vulnerabilities after):
- go 1.26.4 -> 1.26.5: clears the only CALLED vuln GO-2026-5856 (crypto/tls)
  plus not-called os GO-2026-4970.
- golang.org/x/crypto v0.51.0 -> v0.52.0: clears 13 not-called advisories.
- google.golang.org/grpc v1.59.0 -> v1.79.3: clears GO-2026-4762 (gRPC
  authorization bypass). pad runs no gRPC server, but grpc.Server.Serve ships
  transitively (OTel/ory/grpc-gateway) so binary mode flags the symbol.
  Contained 12-line go.mod bump (genproto/protobuf/oauth2 family), no cascade.

Remaining not-called advisories deferred to a follow-up dependency sweep:
GO-2026-4985 (otel otlptracehttp) and GO-2026-5932 (x/crypto, Fixed in: N/A).
2026-07-10 12:02:20 -04:00
xarmian 982f534b4a fix(web): exact-pin coordinated Tiptap packages + CI drift guard (BUG-2009) (#838)
CLAUDE.md mandates exact-pinning the coordinated Tiptap packages so npm
cannot silently slide the shared Y.Doc/ProseMirror schema out of sync.
@tiptap/core (^3.22.5), @tiptap/y-tiptap (^3.0.3) and @tiptap/pm (^3.20.4)
were still floating on caret ranges; only @tiptap/extension-collaboration
was exact.

- Exact-pin @tiptap/core, @tiptap/y-tiptap and @tiptap/pm to their
  currently-resolved versions (3.22.5 / 3.0.3 / 3.22.5); refresh lockfile.
- Add web/scripts/check-tiptap-pins.mjs + check:tiptap-pins npm script:
  fails if any coordinated package loses its exact pin in package.json or
  resolves to more than one version in the lockfile.
- Wire the guard into the CI web job.
2026-07-07 16:11:44 -04:00
xarmian 88c5771aed ci(web): run vitest unit tests in CI + Makefile web-test target (#835)
The 128-test vitest suite (7 files, incl. the WebMCP dispatch/descriptor
tests backing PLAN-1888) ran nowhere in CI. Add a "Run web unit tests"
step to the Web job after the build/check steps, a `web-test` Makefile
target wired into the `check` chain, and a CLAUDE.md Testing note.

Fixes TASK-1999.

Claude-Session: https://claude.ai/code/session_01BoPkYhKqMiWPYmxQigeWsA
2026-07-07 15:12:19 -04:00
xarmian 4e4d4edc86 fix(ci): raise race-step go test timeout to 45m (BUG-1913) (#787)
The internal/server suite's aggregate runtime under -race crossed the
30m budget (~30.3m on a fast local machine, 734 tests, none slower than
14s — growth, not a hang), making the Go jobs fail most main runs with
'panic: test timed out after 30m0s' on whichever test happened to be
running. 45m restores headroom; genuine deadlocks still produce the
goroutine-dump panic, up to 15m later. Real fix (cheaper suite) stays
tracked on BUG-1913.

Claude-Session: https://claude.ai/code/session_01CL1pBjNpPUX6SWkuAuYXHS
2026-07-03 02:45:12 -04:00
xarmian a88f7755c9 chore(ci): bump Node 20 actions to Node 24 ahead of June 2026 deadline (TASK-1165) (#635)
* chore(ci): bump Node 20 actions to Node 24 ahead of June 2026 deadline (TASK-1165)

GitHub deprecated Node 20 in Actions runners; the hard cutoff is
June 2nd, 2026. Pre-emptively bumps the three remaining Node 20
holdouts to their latest Node 24 versions, SHA-pinned per the
existing convention:

- actions/setup-node v4.4.0 → v6.4.0 (using: node24)
- actions/upload-artifact v4.6.2 → v7.0.1 (using: node24)
- anchore/sbom-action/download-syft v0.18.0 → v0.24.0 (using: node24)

Breaking-change review (all clear for our usage):

- setup-node v5/v6: only behavioral change is "limit automatic caching
  to npm" — we already pass cache: "npm" explicitly. node-version: "24"
  + cache-dependency-path: web/package-lock.json continue to work.
- upload-artifact v5/v6: v5 treats the Node 24 bump as breaking; v6
  requires Actions Runner ≥ 2.327.1 (GitHub-hosted runners are
  auto-updated, so no concern). Our single-fixed-name failure-only
  upload is unaffected.
- upload-artifact v7: adds optional archive: false single-file unzipped
  uploads + ESM internals. Our usage (name/path/retention-days) is
  unchanged.
- sbom-action 0.18→0.24: minor 0.x bumps; v0.24 release notes
  explicitly cite "update to node 24 + deps".

Post-audit: every uses: spec in .github/workflows/ now reports
node24 or composite. No Node 20 actions remain.

Per TASK-1165 verification: this PR touches .github/workflows/release.yml,
so the playbook's RC decision rule (PLAYB-1160 step 1) triggers — the
next release will warrant a vX.Y.Z-rc.1 to confirm the deprecation
annotation is gone before shipping stable.

* chore(ci): cap golangci-lint cache to 1 day to avoid poisoning recurrences (BUG-1624)

PR #635's first CI run failed on 30+ SA5011/SA4023 false positives
against unchanged code; local cold-cache lint reported 0 issues.
Diagnosis: golangci-lint-action's cache stores the prior pass's
resolved issue list, and once a pass writes degenerate results
(analyzer upgrade, plugin reset, sub-package drift), every downstream
restore replays that list verbatim until the cache key rotates.

The cache key hashes go.mod/go.sum/.golangci.yml plus an action-internal
prefix, so in steady state the key is stable for days and the
poisoned content propagates across PRs. Default invalidation is 7 days.

This change cuts it to 1 day. Most runs still hit warm cache (lint
runs back-to-back within hours of each other are common); we
guarantee a daily fresh full pass that overwrites any bad cached
state. Estimated cost: ~30-60s extra on one CI run per day.

Hand-mitigated the immediate occurrence by deleting the two poisoned
cache entries via the GH cache API; rerun then went green. BUG-1624
captures the full diagnosis + alternatives considered.

* chore(ci): cache Playwright browsers to dodge CDN slow-paths (BUG-1625)

PR #635 hit two consecutive 10-minute timeouts on the E2E job, both
dying inside `npx playwright install --with-deps chromium` while
downloading Chrome from cdn.playwright.dev. The apt portion completed
in ~10s; the CDN download hung for ~7 minutes before the
`timeout-minutes: 10` ceiling killed the job.

Same code earlier in the day ran E2E green in 1m07s — it's a CDN
slowness event, not a behavioral regression. But two-runs-in-a-row
timeouts mean the steady state is fragile.

Fix: cache `~/.cache/ms-playwright` per resolved @playwright/test
version. Splits the install step in two:

- Cache miss: `npx playwright install --with-deps chromium` — full
  apt + browser download (current behavior).
- Cache hit:  `npx playwright install-deps chromium` — apt system
  libraries only (~10s); browser binary is already on disk.

Cache key reads the resolved version from package-lock.json so a
Playwright bump auto-invalidates. Pinned to actions/cache v5.0.5
(node24) per the workflow's SHA-pinning convention.

After the first warm run on each Playwright version, the CDN is
out of the critical path; an outage there can only burn one CI run
before steady state recovers. BUG-1625 has the full diagnosis.
2026-05-27 17:03:34 -04:00
xarmian c2351d861d fix(ci): lower test-only bcrypt cost + re-enable -race on PRs (BUG-1371) (#513)
The full `internal/server` test suite under `-race` had grown past the
30m CI timeout, failing every push to main since ~TASK-1354. Diagnosis:
bcrypt at the production cost (12) takes ~3s per call under the race
detector, and dozens of tests now bootstrap a user via the loopback
HTTP path (`bootstrapFirstUser` → `store.CreateUser` →
`bcrypt.GenerateFromPassword`). Cumulative cost dominated the budget.

Two coordinated changes:

1. Lower bcrypt cost in test binaries. `bcryptCost` becomes a package
   var (still package-private), and a new `SetBcryptCostForTesting`
   helper lets each test binary's `TestMain` drop it to
   `bcrypt.MinCost`. Production stays at 12 — only the test process
   ever mutates the value.

2. Re-enable `-race` on pull requests. The `if: github.ref ==
   'refs/heads/main'` gate was originally a GitHub Actions minutes
   cost-control; the repo is public now, so PR minutes are free, and
   we'd rather catch race regressions on the contributing branch than
   after merge.

Measured impact:
- `go test -race ./internal/server`: 1800s timeout → 830s (13m51s).
- `go test ./internal/store`: 808s → 35s.
- `go test ./internal/server`: 192s → 60s.

The 30m timeout stays — it's headroom for genuine deadlocks, which
would still hit the goroutine-dump panic the way BUG-851 did.

Prior art: BUG-851 (10m → 30m bump, ipRateLimiter goroutine drain).
This is a different cause (bcrypt cumulative time) so the fix is
different.
2026-05-12 10:01:14 -04:00
xarmian e380b4e660 chore(deps): bump Node 22 → 24 (Dockerfile + CI workflows) (TASK-1235) (#443)
Bumps all four Node version pins from "22" to "24" together so CI ↔
production stay aligned:
  • Dockerfile (production image): node:22-alpine → node:24-alpine
  • .github/workflows/ci.yml — Web job + E2E job
  • .github/workflows/release.yml — release pipeline

Going to LTS-bound 24 instead of dependabot's proposed 25-alpine,
which hits EOL on 2026-06-01 (~3 weeks from this commit). Going to
24 instead of waiting for 26-LTS (Oct 2026) because 5 months is too
long to sit on the deferred-bumps backlog and 24 is already a year
into LTS-tested production use. Worst case follow-up is one more
trivial Dockerfile bump in October.

Verified:
  • Local `docker build --target web-builder` on node:24-alpine
    (npm ci + npm run build) — clean, 24s end-to-end
  • `make check` — golangci-lint + go test + npm run build +
    svelte-check, 0 errors

Closes dependabot/docker/node-25-alpine (PR #209) — closing rather
than rebasing because we're going to 24, not 25.
2026-05-08 07:32:43 -04:00
dependabot[bot] 90dcc3d4ce chore(ci)(deps): bump actions/checkout from 4.3.1 to 6.0.2 (#204)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.3.1 to 6.0.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/34e114876b0b11c390a56381ad16ebd13914f8d5...de0fac2e4500dabe0009e67214ff5f5447ce83dd)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-07 23:51:15 -04:00
dependabot[bot] 8b9a4d63d5 chore(ci)(deps): bump actions/setup-go from 5.6.0 to 6.4.0 (#203)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5.6.0 to 6.4.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/40f1582b2485089dde7abd97c1529aa768e1baff...4a3601121dd01d1626a1e23e37211e3254c1c06c)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-07 23:50:08 -04:00
xarmian 756d91acad fix(ci): gofmt + bump race-detector timeout to 30m (#299)
CI on main has been failing since the PLAN-866 attachment work
landed. Two independent issues:

1. gofmt failures (golangci-lint) — seven files in the attachments
   path had trailing-comment alignment that gofmt wanted nudged a
   column. Pure whitespace; ran `gofmt -w` across the affected
   files. golangci-lint's gofmt linter caught it on every PR /
   push since TASK-870 but we hadn't been watching those signals.

   Files cleaned: internal/attachments/{fs_store_test,mime,
   mime_test,processor_test}.go, internal/server/{
   handlers_attachments_download_test,handlers_attachments_transform,
   render/attachments_test}.go.

   Local guard: `gofmt -l ./...` now exits clean.

2. Race-detector tests timed out at 20m on the GitHub-hosted runner.
   Two contributors:
     - PostgreSQL adds latency on every CREATE/DROP plus on the
       bcrypt hash inside auth/bootstrap (~3s per call under -race
       on the runner). Tests that bootstrap a fresh user (e.g.
       TestSessionIPChange_*) pay the full cost each time.
     - The PLAN-866 image-processing tests (thumbnail derivation,
       rotate / crop transform) added ~2-3 minutes of decode/encode
       work on top of the existing suite.

   The previous "20m gives margin without papering over a hang"
   comment was right at the time it was written; we now genuinely
   need more headroom. Bumped to 30m on both the SQLite and
   PostgreSQL race steps. Genuine deadlocks would still trip this
   and produce the goroutine-dump panic — we just stop confusing
   "slow but progressing" with "permanently hung".

   Reference points before / after:
     - TASK-875 main run #294: Go (PostgreSQL) finished in 17m48s ✓
     - TASK-880 main run #298: Go (PostgreSQL) hit 20m timeout ✗
     - Local: my new tests under -race add ~63s on a developer laptop
       (TestThumbnails + TestTransform + TestProcessor combined).

Verification:
  go test ./...             — pass
  go vet ./...              — clean
  gofmt -l (recursively)    — clean
2026-04-29 15:41:12 -04:00
xarmian 715ec70e94 fix(server): drain ipRateLimiter cleanup goroutines on Stop() (BUG-851) (#276)
NewRateLimiters spawned 9 ipRateLimiter cleanup goroutines per Server,
each in an unbounded `for { time.Sleep(5*time.Minute); ... }` loop with
no exit signal (middleware_ratelimit.go:78-89). Every testServer(t)
call leaked all 9, accumulating across the 210-test internal/server
suite. Under -race the goroutine count + sync overhead pushed the run
past the default 10m timeout, which is why the `Run tests with race
detector` step (gated to main pushes) has been failing on every main
run since the step was added on 2026-04-13.

This is the same flavor as BUG-842 part 2 (request-handler
fire-and-forget goroutines drained via Server.bg WaitGroup). The
rate-limiter case wasn't in BUG-842's scope: those goroutines are
spawned at construction time, not at request time, so they need a
different drain primitive.

Changes:

  - ipRateLimiter gains stopCh + stopOnce + stopWg. cleanup() rewrites
    its loop as a select over stopCh and a 5-minute ticker, deferring
    stopWg.Done(). New Stop() closes stopCh once and waits for the
    cleanup goroutine to return.
  - RateLimiters gains a Stop() that walks all 9 limiters (nil-safe
    via the (*ipRateLimiter).Stop receiver guard).
  - Server.Stop() now also calls s.rateLimiters.Stop() after
    s.bg.Wait(). Test cleanups already call Server.Stop() (added in
    BUG-842), so no test-helper changes needed.
  - New TestServer_Stop_DrainsRateLimiterCleanup pins the contract:
    construct + Stop N servers, assert runtime.NumGoroutine() returns
    to baseline ±3.
  - .github/workflows/ci.yml: bump the -race timeout from the default
    10m to 20m. The full server suite under -race takes ~13m on a dev
    laptop after the leak fix; 20m gives margin without papering over
    an actual hang. Both `Run tests with race detector` (SQLite) and
    `Run tests with race detector against PostgreSQL` are bumped.

Verified locally: go test -race -timeout=1500s ./internal/server/
finishes ok in 776s (12m57s). Without the leak fix, the same command
times out at 600s (10m) with a goroutine dump showing hundreds of
ipRateLimiter.cleanup frames.
2026-04-28 17:20:03 -04:00
xarmian e5e2bd7b86 chore: flip CI only-new-issues=false + scope lint policy (TASK-771) (#253)
* chore: gate CI on full lint, scoped to checks we enforce (TASK-771)

Flip golangci-lint-action's only-new-issues from true to false so CI
fails on ANY linter finding, not just findings on PR-changed lines.
This catches lint regressions on the next push instead of letting them
drift into main.

The gate flip is paired with a deliberate scope-down of .golangci.yml:

1. errcheck is disabled. The codebase has 325 pre-existing unchecked-
   error sites where the error is intentionally discarded (best-effort
   logging writes, defensive parses with zero-valued fallbacks, etc.).
   Auditing every site is its own project — bigger than IDEA-732 by
   an order of magnitude. Tracked as a follow-up if/when we want the
   safety net back.

2. staticcheck is restricted to the SA* check family (real-bug
   detectors). The ST*/QF*/S* families are stylistic/quick-fix
   suggestions we don't gate CI on yet — they would have re-flooded
   the lint output with capitalized error strings, De Morgan's law
   simplification suggestions, etc., that aren't bug-finding signals.
   Re-enable selectively if the team wants them.

After scoping, the live linters are: govet, ineffassign, staticcheck
(SA*), unused, gofmt — exactly the set that IDEA-732 cleaned up.

Other changes in this PR:

- Drop pull-requests:read permission. It was only required by the
  golangci-lint-action when only-new-issues=true (the action used it
  to fetch PR diff metadata). Not needed any more.
- Update the Run-golangci-lint comment block to explain the new
  policy and reference the IDEA-732 cleanup PRs (#247/#249/#251/#252).
- Replace the SA4017 //lint:ignore directive in cmd/pad/main.go:4631
  with an inline //nolint:staticcheck — the multi-line //lint:ignore
  block was too far from the if statement for staticcheck's
  proximity rule, so the directive wasn't taking effect.
- Apply gofmt -w on three files where post-deletion blank-line
  artifacts had drifted (cmd/pad/main.go imports, two trailing newline
  fix-ups in handlers_items.go and middleware_ratelimit.go).

Verified:
- `golangci-lint run ./...` reports 0 issues.
- `go build ./...` clean.
- `go vet ./...` clean.
- `go test ./...` all pass.

Parent: PLAN-644.

* chore: address Codex round 1 on PR #253 (TASK-771)

Two LOWs from Codex on the gate-flip PR:

1. //nolint:staticcheck was broader than necessary (suppressed any
   future staticcheck diagnostic on the line) and didn't self-report
   when the underlying false positive gets fixed upstream. Codex
   suggested swapping back to a tightly-placed //lint:ignore SA4017.

   I tried that, but golangci-lint v2's staticcheck integration does
   not honour //lint:ignore the way direct staticcheck does — the
   directive was silently no-op'd via golangci-lint while the same
   directive worked when staticcheck was invoked directly.

   So instead of fighting the linter wrapper, sidestep the false
   positive entirely: rewrite the keepalive check from
   `strings.HasPrefix(line, ":")` to `len(line) > 0 && line[0] == ':'`.
   Same observable behaviour for a single-byte ASCII prefix, no
   suppression directive needed at all, no exposure when staticcheck
   eventually fixes the false positive.

2. The new lint-step comment in ci.yml said main is "clean of
   staticcheck SA*/U1000" — but U1000 is reported by the standalone
   `unused` linter in .golangci.yml, not by staticcheck.checks.
   Tighten the comment to attribute each enforced check correctly.

Verified:
- `golangci-lint run ./...` reports 0 issues
- `go test ./cmd/pad/...` passes (the SSE watch loop is exercised by
  reconcile_test.go and the broader integration tests).
2026-04-25 12:36:05 -04:00
xarmian 157ca4e88f chore: bump Go toolchain to 1.26 (TASK-763) (#247)
* chore: bump Go toolchain to 1.26 (TASK-763)

Bump Go from 1.25 to 1.26 across all toolchain pins:

- go.mod — go 1.25.0 → go 1.26.0
- Dockerfile — golang:1.25-alpine → golang:1.26-alpine
- .github/workflows/ci.yml — three setup-go steps (Go, Go-Postgres, E2E jobs)
- .github/workflows/release.yml — release pipeline

No `toolchain` directive: the repo is pre-launch with no external
contributors yet, so we set the floor where we want it (hard requirement).

Verified locally before commit:
- golangci-lint v2.11.4 builds and runs under Go 1.26.2 (pinned in CI)
- golang:1.26-alpine and 1.26.2-alpine images present on Docker Hub
- go build ./... clean
- go vet ./... clean
- go test ./... all pass

Parent: PLAN-644 (OSS Repo Hygiene and Launch Polish).

* chore: gofmt -w under Go 1.26 (TASK-763)

Apply Go 1.26's gofmt to the codebase. ~41 files reformatted, all
struct-tag whitespace realignment — no semantic changes. Verified:

- gofmt -l ./cmd ./internal returns empty after
- go build ./... still clean
- go test ./... still passes (run before commit)

Bundling the gofmt diff with the toolchain bump in the same PR because
the formatting drift is a direct consequence of moving from 1.25 to
1.26; splitting them creates a mandatory two-PR ordering for no value.

Parent: PLAN-644.

* docs: bump documented Go floor to 1.26 (TASK-763)

Match go.mod's hard 1.26.0 requirement in the source-build instructions.
Caught by Codex review round 1 on PR #247.

- README.md:158 — "Go 1.25+" → "Go 1.26+"
- CONTRIBUTING.md:9 — "Go 1.25+" → "Go 1.26+"
2026-04-25 11:35:19 -04:00
xarmian c8601a2031 test(e2e): Playwright smoke test infrastructure + 2 dashboard tests (TASK-689) (#225)
* test(e2e): Playwright smoke test infrastructure + 2 dashboard tests (TASK-689)

Option A of TASK-689: land the test infrastructure and a minimal smoke
test on both mobile and desktop viewports. Broader flow coverage (board
view drag, item detail, comments, mobile hamburger, BottomSheet
regression guard) is tracked as TASK-733.

Infrastructure
--------------
- web/playwright.config.ts: two projects (desktop-chromium, mobile-
  chromium via Pixel 7), reporter list+html, trace/video/screenshot
  retained on failure, webServer that wipes + recreates the data dir
  then runs the pad binary. Paths anchored to the config file's
  directory so runs are cwd-invariant.
- web/e2e/global-setup.ts: bootstraps admin via POST /auth/bootstrap,
  logs in, creates the e2e workspace, mints a user-scoped API token,
  and persists the token + resolved admin username to fixture.json.
- web/e2e/fixtures.ts: extends base test so every BrowserContext
  automatically gets Authorization: Bearer <token>. Uses a token
  rather than a session cookie because sessions are User-Agent bound
  in middleware_auth.go and a node-minted session would be rejected
  by a Chromium UA.

Tests
-----
- web/e2e/dashboard.spec.ts: a logged-in user lands on the seeded
  workspace, no login form is rendered, and the workspace name
  appears on the page. Runs in both project viewports.

CI
--
- New `e2e` job in .github/workflows/ci.yml: builds web UI + binary,
  installs Playwright chromium with OS deps, runs the suite, and
  uploads the HTML report as an artifact on failure. Timeout capped
  at 10 minutes (suite itself runs in ~4s today).

Local run (in mcr.microsoft.com/playwright:v1.59.1-noble): 2 passed
in 4.1s.

Parent: PLAN-644.
Follow-up: TASK-733 for broader flow coverage (Option B in the
original ship plan).

* fix(e2e): persist server-returned workspace slug instead of the constant (TASK-689)

Addresses Codex P2 on PR #225. When Playwright's `reuseExistingServer:
true` (local dev), a re-run of globalSetup hits `POST /api/v1/workspaces`
against a DB that already has `e2e`. The server uniquifies the slug
(`e2e` → `e2e-2` → …) and returns the uniquified value, but the old
code wrote `WORKSPACE_SLUG` (the constant) to fixture.json. Tests
then navigated to /e2e-admin/e2e — which might still exist from a
previous run with stale state — instead of /e2e-admin/e2e-2, missing
regressions in freshly-seeded content.

Fix: read `slug` back from the workspace-create response and use that
when writing fixture.json. Local re-runs now always point at the
workspace this run actually created.

Parent: PLAN-644.

* fix(e2e): cross-platform webServer bootstrap via Node wrapper (TASK-689)

Addresses Codex P2 on PR #225: `rm -rf && mkdir -p && pad server start`
in webServer.command is POSIX-only. Windows contributors on cmd.exe or
PowerShell can't run `npm run test:e2e` at all — the e2e suite becomes
Linux/macOS-only, defeating the "CI parity" goal.

Fix: extract the wipe-and-exec logic into web/e2e/run-pad.mjs. Node's
fs.rmSync / mkdirSync / child_process.spawn are uniform across
platforms, and the wrapper forwards SIGTERM/SIGINT so Playwright's
teardown still cleanly kills the child on suite exit.

Local re-run in mcr.microsoft.com/playwright:v1.59.1-noble: 2 passed.

Parent: PLAN-644.
2026-04-22 22:35:08 -04:00
xarmian 04db0c1a67 chore: add .editorconfig, golangci-lint, pre-commit (TASK-682) (#220)
* chore: add .editorconfig, golangci-lint, pre-commit (TASK-682)

Prevents style churn from first-time external contributors by
codifying the project's formatting and lint rules into shared config.

Changes:
- .editorconfig: tabs for code, 2-space for YAML/JSON/Markdown, LF
  everywhere, UTF-8; Makefile overrides enforce tab (syntactic).
- .golangci.yml: enables gofmt, govet, errcheck, ineffassign,
  staticcheck, unused. Scoped to Go sources; excludes web/, docs/,
  deploy/, skills/.
- .pre-commit-config.yaml: repo-local hygiene (trailing whitespace,
  EOF, YAML/JSON checks, merge-conflict markers, 500KB file cap,
  LF line endings), Go formatting (go-fmt, go-imports), and
  prettier for YAML/JSON/Markdown only (Svelte intentionally
  excluded — no Svelte prettier config yet).
- CI: wires golangci-lint into .github/workflows/ci.yml via the
  official pinned action (v6.5.2 → SHA 55c2c144...). Uses
  only-new-issues: true so this PR is not blocked by the 17
  pre-existing findings on main, which are tracked as IDEA-732
  and will flip to strict enforcement after they're resolved.

Verified:
- golangci-lint run with this config locally; config parses and
  --new-from-rev=HEAD is clean
- All YAML parses via yq
- go build/vet/test + web build all green

Parent: PLAN-644.
Follow-up: IDEA-732 (fix legacy lint findings, flip to strict mode).

* fix(ci): upgrade golangci-lint to v2 for Go 1.25 support (TASK-682)

Per Codex review on PR #220: golangci-lint v1.x (including v1.64.8
which I originally pinned) is capped at Go 1.24 support. Running v1
binaries against Go 1.25 source can silently drop/misreport findings,
defeating the purpose of a lint gate.

Switch to:
- Action: golangci/golangci-lint-action@v9.2.0 (pinned SHA 1e7e51e7...)
- Lint version: v2.11.4 (latest stable v2)
- Config rewritten to v2 YAML format (version: "2", linters.default,
  formatters section for gofmt, exclusions.paths)

Verified:
- `golangci-lint config verify` clean
- `golangci-lint run --new-from-rev=HEAD` reports 0 issues on the
  current diff (still well under the safety cap since we're using
  only-new-issues: true)
- Full run against main surfaces the expected legacy findings, which
  remain tracked in IDEA-732

Parent: PLAN-644.

* fix(ci): anchor golangci-lint exclusion paths + add PR read perm (TASK-682)

Addresses two follow-up comments from Codex on PR #220:

P1 — golangci-lint v2 uses regex path matching for exclusions, so the
bare patterns "web" and "skills" would also match unrelated Go files
whose path contains those substrings (e.g. "internal/websocket").
Anchor with a leading "^" and trailing "/" so only the intended
directory trees are skipped.

P2 — The workflow already grants "contents: read" at top level, but
golangci-lint-action with only-new-issues: true also fetches PR diff
metadata from the GitHub API; the action docs list "pull-requests:
read" as required for that path. Add it explicitly so the action
doesn't fall back to scanning all code and defeating the purpose of
scoped issue reporting.

Parent: PLAN-644.
2026-04-22 20:44:52 -04:00
xarmian 1052be7282 security(ci): pin all GitHub Actions to commit SHAs (TASK-677) (#201)
Every third-party Action in .github/workflows/ was using a floating
tag (@v4, @v5, @v6). A compromised maintainer — or a tag that gets
re-pointed at a malicious commit — could execute attacker code in CI
with contents:write, packages:write, and the GHCR token in scope.
Release.yml is especially exposed: a compromised step there could
publish tampered binaries to GitHub Releases and GHCR.

All 12 'uses:' references now pin to a 40-char commit SHA with a
trailing '# vX.Y.Z' comment (the comment is what humans read during
review; the SHA is what GitHub actually resolves):

  actions/checkout@34e114876b  # v4.3.1
  actions/setup-go@40f1582b24  # v5.6.0
  actions/setup-node@49933ea528 # v4.4.0
  docker/setup-buildx-action@8d2750c68a # v3.12.0
  docker/login-action@c94ce9fb46       # v3.7.0
  goreleaser/goreleaser-action@e435ccd777 # v6.4.0

Version bumps: pin to the newest release within the same major that
was previously in use (so behavior stays the same — no major version
jumps hidden inside a security PR). Dependabot (incoming in TASK-678)
will track the commit-pinned refs and open PRs that update both the
SHA and the version comment together.

Parent: PLAN-644.
2026-04-22 15:27:37 -04:00
xarmian 4f298db4d0 docs(readme): add 'Hardening for public deployments' section + govulncheck CI gate (PLAN-643 exit) (#195)
* docs(readme): add 'Hardening for public deployments' + govulncheck CI gate (PLAN-643 exit criteria)

Closes the last two exit criteria of PLAN-643 (OSS Security Hardening):

- README.md gains a full "Hardening for public deployments" section
  walking operators through the network boundary (bind addr, TLS,
  trusted proxies), secrets (PAD_ENCRYPTION_KEY, token scopes,
  bootstrap window), auth hardening (PAD_IP_CHANGE_ENFORCE, password
  strength UI messaging, PAD_CORS_ORIGINS), observability (PAD_METRICS_
  TOKEN, audit-log shipping), and a deploy-day checklist. Cross-
  references every relevant env var documented elsewhere.
- CI workflow gains a govulncheck step on the Go job, mirroring the
  existing `npm audit --audit-level=high --omit=dev` gate on the web
  job. Locally `govulncheck ./...` reports "No vulnerabilities found",
  so the first run on main should pass.

Exit criteria for PLAN-643:
  [x] All CRITICAL + HIGH + MEDIUM findings closed and verified
  [x] `npm audit --audit-level=high --production` clean in web/
  [x] CSP denies inline event handlers (script-src-attr 'none')
  [x] Docker default compose publishes to 127.0.0.1 only
  [x] README has a "Hardening for public deployments" section
  [x] `govulncheck ./...` clean

* fix(ci): pin govulncheck to v1.2.0 instead of @latest (PLAN-643)

Addresses Codex P2 on PR #195: tracking @latest on every CI run makes
the gate non-deterministic — a future upstream release could change
behavior or require a newer Go toolchain than the workflow's pinned
`go-version: 1.25` and break unrelated PRs. Pin to the currently-
released v1.2.0 (Go 1.26.2 toolchain) and update intentionally.

* docs(readme): recommend pinned govulncheck install in hardening section

Follows Codex P2 on PR #195: the earlier commit pinned the CI workflow
to v1.2.0 but the README's hardening-checklist bullet still told
operators to install @latest. Teams copying that into their own CI
would re-introduce the non-determinism the pin was meant to fix.
Update the docs to recommend a pinned tag (matching the workflow's
v1.2.0) and note that the pin should be bumped intentionally.
2026-04-22 13:46:32 -04:00
xarmian 3544e42de1 chore(web): npm audit fix + CI audit gate (TASK-654) (#181)
web/package-lock.json had 11 advisories (1 low, 1 moderate, 9 high)
before this PR: @sveltejs/kit (redirect/body-size), cookie<0.7.0,
dompurify<=3.3.3, vite 7.0.0-7.3.1, lodash-es, picomatch, chevrotain,
etc. All required a mix of `npm audit fix` and targeted upgrades.

Changes:
- web/package.json: upgrade @sveltejs/kit to ^2.57.1. Add `overrides`
  map pinning cookie to ^0.7.2 (upstream @sveltejs/kit@2.57.1 still
  ships cookie@0.6.0 which is LOW severity but trivially fixable).
- web/package-lock.json: regenerated via `npm install` + `npm audit fix`.
- .github/workflows/ci.yml: add `npm audit --audit-level=high --omit=dev`
  step after `npm ci`. Fails the build on any HIGH+ advisory in
  production deps; dev-only issues stay informational so CI isn't held
  hostage by unfixable upstream chevrotain/vite dev-server advisories.

`npm audit --audit-level=high --omit=dev` now reports 0 vulnerabilities
locally. `npm run build` and `go test ./...` remain green.

Parent: PLAN-643 (OSS Security Hardening).
2026-04-21 21:37:16 -04:00
xarmian b2b4feecb9 feat: console navigation, PostgreSQL CI, and operational improvements
- Route root (/) to /console for centralized workspace management
- Update TopBar user dropdown with console nav links (workspaces, settings, billing, admin)
- Move account settings (profile, password, tokens) from workspace settings to /console/settings
- Enhance admin page with email configuration UI and CSRF-protected writes
- Add PostgreSQL CI job to GitHub Actions with race detector on main
- Add `make test-pg` for local PostgreSQL testing via docker-compose
- Expand health/ready endpoint with DB connection pool stats
- Increase item number retry limit for high-concurrency environments
- Add concurrent store benchmarks and FTS search quality tests
- Add AGENTS.md for multi-agent development guidance
2026-04-13 01:29:15 +00:00
xarmian 239ce19d1f ci: reduce CI usage by ~60-70% with optimized workflow
- Add concurrency groups to cancel superseded in-progress runs
- Move race detector to main-only (saves ~9.5 min per PR run)
- Merge go-vet into go job (eliminates separate VM)
- Remove redundant full-build job (release.yml handles real builds)
- Add binary build+verify as steps in go job for smoke testing
2026-04-09 00:27:55 +00:00
xarmian 14fe7fd631 Fix CI: create web build placeholder for Go embed in test and vet jobs
The go:embed directive requires web/build/* to exist. Go test and vet jobs
run without building the web UI, so create a minimal placeholder directory
to satisfy the embed pattern.
2026-03-27 03:35:55 +00:00
xarmian 7ba69abb88 Misc improvements: CLI field summaries, editor enhancements, CI and UI polish
Show field summary after create/update CLI commands. Make svelte-check
blocking in CI. Improve editor block handling, field editor layout,
conventions page, and minor UI consistency fixes across pages.
2026-03-27 01:13:07 +00:00
xarmian 81579847c6 Initial release
Pad — project management for developers and AI agents.
Single Go binary with embedded SvelteKit web UI, SQLite storage,
CLI, and Claude Code /pad skill integration.

https://getpad.dev
2026-03-26 01:52:36 +00:00