mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-10 15:05:40 +00:00
main
20 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
38d8803603 |
fix(nix): gate the vendorHash heal on state, not on the push range (BUG-2974) (#1303)
* fix(nix): gate the vendorHash heal on state, not on the push range (BUG-2974) The heal job asked whether THIS push touched go.mod/go.sum. That is the right question for loop prevention and the wrong one for recovery: after a lost push race the tree needing the fix was written by an EARLIER push, so every later merge that did not itself move the module set refused to heal, exited GREEN, and left main carrying a hash a clean `nix build` rejects. It happened on |
||
|
|
49d1bfcd85 |
ci(nix): a Go bump's Nix check is green when the build passes, and main heals its own vendorHash (TASK-2954) (#1292)
`vendorHash` pins the Go module set by content hash. Dependabot updates go.mod and go.sum and has no idea `nix/package.nix` exists, so every Go-dependency bump PR failed `Nix build & check` on a fixed-output hash mismatch — structurally, and forever. Both open Go bumps (#1274, #1275) are red that way today; the npm-side bumps (#1276, #1277) are green, which is the control that isolates the cause. A permanently-red check is not a check: a bump that genuinely breaks the build looks identical, at a glance, to one that only moved the hash. Two halves, and they are deliberately in different places. THE CHECK IS MADE HONEST WITHOUT A TOKEN. Every Nix run recomputes the hash in its working tree before the build steps judge it, so green means the build passed with that ref's actual module set. This runs for every author, not just Dependabot: a human's own go.mod change moves the hash the same way, and a check that is honest for one author only is the shape this removes. MAIN HEALS ITSELF ONE COMMIT AFTER A MERGE. The corrected file cannot be pushed from a Dependabot PR run: such a run gets a read-only GITHUB_TOKEN — it runs as if from a fork — and the `permissions` key does NOT lift that. Only a repository-wide setting does, and that setting would hand fork PRs write tokens on a public repo, which is the surface CONVE-2438 exists to keep closed. The merge, however, is authored by a human, so the `push: main` run that follows is ordinary. A separate job with the only `contents: write` in the file commits the recomputed value there, gated on the JOB (a step-level `if` is not a boundary — the job would still hold the token and checkout persists it), on the build having passed, and on this push having touched go.mod or go.sum. The loop guard is the commit's own contents: the bot commit touches nix/package.nix and nothing else, so the run it triggers finds go.mod and go.sum unchanged and stops at the first gate. Not a heuristic about who pushed — the fix cannot invalidate the hash it just wrote. Three ways that gate could have lost a heal, all closed. A cancelled main run's heal is never retried — the next push's run recomputes correctly but its gate sees only its OWN commits — so `cancel-in-progress` is now `pull_request`-only; and that alone is not enough, because GitHub holds only ONE pending run per concurrency group and a third push evicts the queued second, which looks exactly like a run that found nothing to do — so push runs get a per-commit group that nothing can evict. The gate also compares `before..after` on a full clone rather than `HEAD~1..HEAD` on a two-commit one, because a direct push of several commits can carry the go.sum change anywhere in the range. A concurrent merge makes the push a non-fast-forward: the job goes red rather than overwriting, and that merge's own run heals. WHAT THE PARSER REFUSES is the whole correctness argument. This build has many fixed-output derivations: every npm tarball `importNpmLock` fetches is one, and a mismatch in any of them prints the same block with a `got:` line. Taking "the got: hash" writes a tarball's hash into `vendorHash` and looks like it worked — which is what package.nix's old comment told a human to do by eye. So each line is stripped of its runner timestamp and indentation SEPARATELY and matched whole, and a header counts only if it says `error:`, names a single store path segment that STARTS with a 32-character store hash then `-pad-` and ends `-go-modules.drv':` with nothing after it — identity, not resemblance, since `-pad-` anywhere in the name also matches `…-other-pad-tool-…-go-modules.drv`; the hash is then taken only from a canonical-length `got:` on the line IMMEDIATELY after a canonical-length `specified:`. Anything else exits 1 having written nothing, and the build stays red. 40 assertions in nix/bump-vendor-hash_test.sh, wired into the CI Go job and `make test-nix-hash` — in ci.yml rather than nix.yml because otherwise nothing on an ordinary PR would run it, and a break would surface on the next bump. 14 mutants, all killed — but five of them survived the first suite that claimed to cover them, each because the case written for the rule was ALSO refused for a second reason and so discriminated nothing about it: short hashes on both lines never exercised either length rule on its own, and a nested path that also had a malformed store hash never exercised the single-segment rule. A sixth, dropping the canonical length from the `got:` condition, survived because the extractor re-stated the rule; the fix was to state it once. Portability is checked, not assumed: `awk` is gawk here and mawk on the runners, so the suite re-runs itself under mawk, gawk and busybox and is green only if all agree. The parser also had a real defect — it worked only on timestamped CI logs, not the local log package.nix tells a human to produce — found by asserting an exit code rather than file contents, because for a no-op input "did not write" and "could not parse" leave identical files. Claude-Session: https://claude.ai/code/session_01Xk9M5UVPdc84xL5E1mZkm8 |
||
|
|
110045578c |
fix(build): make install proves what it installed and what it restarted (BUG-2897, TASK-2787) (#1272)
`make install` made three claims it did not check.
1. It brought the server back BY SIDE EFFECT -- `pad auth whoami` triggers
an auto-start, which does not know the killed process's argv. A server
running `--host 0.0.0.0` came back bound to the default host alone:
curl 127.0.0.1:7777 -> 000 while the LAN address -> 200, with the
process count and the version both reading correct (BUG-2897).
2. `cp` copied whatever was at the repo path, not what the invocation
built. Two sessions sharing the checkout interleave and the loser's
build is installed by the winner, every exit code green (TASK-2787).
3. "Server restarted." was printed after a command ending in `|| true`,
with no probe of any kind. Not "the wrong address went unverified" --
nothing was verified. Found reading the recipe; neither filing names it,
and it is what made the other two invisible.
The logic moves to scripts/install-refresh.sh for one reason above
readability: a script can be TESTED. internal/buildtools drives it against
a compiled stub `pad`, including the branches where a check must FAIL.
Recipe-inline logic is only exercisable by running `make install`, which
stops the developer's server -- a test nobody runs twice, which is how this
target accumulated three unverified claims.
The script checks OUTCOMES rather than steps: what got installed, and what
is answering afterwards. Two commit checks, deliberately, answering
different questions -- the ARTIFACT before the kill, so a wrong build costs
an error instead of an outage, and the DESTINATION after the copy, which is
the shared-path race TASK-2787 names. The restart uses the argv read from
/proc before the kill, and nothing is printed about a restart until the
server answers on BOTH 127.0.0.1 and the configured host (`--host 0.0.0.0`
resolving to loopback plus the primary LAN address, since 0.0.0.0 is a bind
spec, not something to curl).
Three defects were found in the fix itself, by its own tests and by the
first real run:
- The restart redirected to $HOME/.pad/server.log with nothing creating
that directory. On a fresh HOME the redirect fails, the server never
starts, and the probe reports "did not answer" -- true, and three steps
downstream. Invisible to every hand-run this script could have had,
because a developer's box has ~/.pad by luck of history.
- The post-copy check SURVIVED its mutant: both fixtures reported the same
version from source and destination, so the guard and its absence were
indistinguishable. Fixed by making the stub's version depend on its path.
- Comparing commits by equality rejected a healthy build. `git rev-parse
--short` returns the shortest UNAMBIGUOUS prefix, so its width grows with
the object database: the binary embedded `a3a1d58` and the Makefile
produced `a3a1d586` minutes later. Now a prefix comparison in either
direction, with a negative leg pinning that a genuinely different commit
of the same width is still refused.
Five mutants, each verified to compile, each detected by its own leg.
Verified end to end on the real box: captured `--host 0.0.0.0`, installed
|
||
|
|
5aa4bbe319 |
fix(build): give each worktree its own Postgres test port, and refuse to run when it is unreachable (TASK-2708) (#1253)
* fix(build): give each worktree its own Postgres test port, and refuse to run when it is unreachable (TASK-2708) docker-compose.test.yml bound the host port to 5445, so exactly one worktree could run make test-pg at a time. With concurrent worktrees the normal operating mode that produced three incidents in an afternoon: a port-already-allocated collision, a container dying mid-run under concurrent suites, and a stack orphaned by a removed worktree blocking the port for everyone. The worst of the three forged a gate leg: go test exited 2 having executed NO TESTS because the database was unreachable, and exit 2 with zero FAIL lines reads like a pass at a glance. Docker now assigns the host port and the Makefile reads it back with docker compose port. Before running anything the target probes the HOST path the tests will use, from a throwaway container, and refuses with a banner saying no tests executed rather than letting an unreachable database look like a result. If the suite fails and the database is gone afterwards, it says the failures are infrastructure. The compose project name was already per-directory, so teardown never could reach a sibling; the orphan recovery command is now documented where someone looking for it will be. Claude-Session: https://claude.ai/code/session_01HeChkgZVYb3NTgTcckF5KR * docs: record that make test-pg is now safe from concurrent worktrees (TASK-2708) The worktree section is where a reader learns what is safe to run alongside a sibling, so it is where this belongs — including that a privately-started container is no longer needed, and the recovery command for a stack orphaned by a deleted worktree. Claude-Session: https://claude.ai/code/session_01HeChkgZVYb3NTgTcckF5KR * docs(store): the mutation-harness recipe reads the port back instead of hardcoding it (TASK-2708) A paste-ready recipe in a comment is a consumed artifact: it said 5445, and after the ephemeral-port change pasting it would connect to whatever else is on that port, or to nothing. Found by re-running the prose sweep with a path-scoped exclusion — the first pass piped through 'grep -v node_modules', which filters by LINE CONTENT and had silently eaten the hits in files whose matching line mentions node_modules. Claude-Session: https://claude.ai/code/session_01HeChkgZVYb3NTgTcckF5KR * docs(build): say that the banner discriminates, not the exit code (TASK-2708) Measured while building the counterfactual matrix: make collapses every failed recipe to exit 2, so the infrastructure refusals and an ordinary test failure are indistinguishable by status. The banners are the only discriminator, and a reader who assumed otherwise would build automation on a difference that does not exist. Claude-Session: https://claude.ai/code/session_01HeChkgZVYb3NTgTcckF5KR * fix(build): unique compose project, guarded startup, loud teardown failure (TASK-2708) All four from codex round 1, each verified in the recipe before accepting. Compose defaults the project name to the directory BASENAME, so two checkouts sharing a basename share a stack and one down -v tears down the other's database mid-run — the cross-worktree teardown this task exists to prevent, reached through a second door. The project name is now explicit and keyed to the absolute path. My compose comment had claimed the default was already sufficient, in the place the next reader would believe it. up --wait now runs inside the guarded block: a health-check timeout used to abort the recipe before teardown, leaving the stack behind and creating exactly the orphan this task was filed about. A failed teardown is announced with the command to reap the stack instead of being swallowed. It does NOT fail the build: the tests genuinely ran and their status is honest; the leak is a separate fact and is now a loud one. make test-pg-project prints the name so an orphan can be reaped without re-deriving it. Claude-Session: https://claude.ai/code/session_01HeChkgZVYb3NTgTcckF5KR * fix(build): portable readiness probe, safe project derivation, honest recovery docs (TASK-2708) Five from codex round 2, each verified before accepting. The readiness probe used `docker run --network host`, which is Linux-only by default. On Docker Desktop a perfectly healthy database would have been reported unreachable and the target would have REFUSED TO RUN where it used to work — a guard against false greens turned into a false red. It now uses the host's pg_isready when present and falls back to an in-container check, which is weaker but never lies about the platform. The project name interpolated CURDIR into shell command text, so a checkout path containing a quote would have broken the quoting. The shell now reads its own working directory instead. Teardown failures on the three guard exits were silenced by >/dev/null, contradicting the loud-teardown promise those same guards make. Two docs were falsified by my own earlier commit in this branch: CLAUDE.md still told the reader to reap a stack by directory name, and the mutation recipe in the store test omitted -p entirely, which is exactly the same-basename collision the change exists to prevent. Claude-Session: https://claude.ai/code/session_01HeChkgZVYb3NTgTcckF5KR * fix(build): tear the stack down on interrupt; record why a post-run outage is not reported (TASK-2708) Round 3, one finding accepted and one refused. ACCEPTED: Ctrl-C during go test killed the recipe shell before down -v, leaving an orphaned stack — the exact failure this task was filed about. An INT/TERM trap set before up covers startup as well. REFUSED, with the premise checked rather than argued: the reviewer asked for the post-run banner's EXIT_CODE gate to be dropped so a database dying after a passing run is reported. storetest.NewPostgres skips only when the env var is EMPTY; a database that is gone produces t.Fatalf, not a skip. So exit 0 means every Postgres-backed test completed against a live database, and failing the leg because the container stopped afterwards would convert honest greens into reds. Written into the Makefile so the next reviewer does not re-raise it. Claude-Session: https://claude.ai/code/session_01HeChkgZVYb3NTgTcckF5KR * fix(build): probe the host-published port on every platform; interrupt reports teardown honestly (TASK-2708) Round 4, both findings real. The readiness fallback ran 'compose exec pg_isready', which answers whether the server is alive INSIDE the container — a broken host port mapping passes it and the guard is bypassed. Not a rarely-exercised path either: this box has no host pg_isready, so the fallback is the branch that has been running all along. It now reaches back through host.docker.internal from a throwaway container, which is native on Docker Desktop and resolves on Linux via --add-host=...:host-gateway. Verified against a live stack, with a negative control on a port nothing listens on. That is the third version of this probe. --network host was Linux-only and would have falsely refused on Desktop; compose exec was portable but asked a narrower question than the claim it carried. The interrupt trap announced 'stack torn down' unconditionally, so an interrupted run whose teardown failed reported successful cleanup. It now reports what happened and names the command to reap the stack. Claude-Session: https://claude.ai/code/session_01HeChkgZVYb3NTgTcckF5KR |
||
|
|
a1716d8170 |
ci(web): decide the npm audit gate from the report, not the exit code, and run it last (BUG-2881) (#1247)
* ci(web): decide the npm audit gate from the report, not the exit code, and run it last (BUG-2881) `npm audit` exits non-zero identically for "a HIGH/CRITICAL advisory exists" and "the advisory service was unreachable". The Web job ran it before Build / Type check / vitest under `bash -e`, so a registry timeout (main, 03:50Z) and a 503 (#1246, 04:33Z) on 2026-09-04 each produced a red row with every frontend verification step SKIPPED — a lane that read like a failure and had asked nothing. scripts/ci-audit.mjs runs the audit in --json mode and decides from the report: metadata.vulnerabilities present → fail iff high+critical > 0, naming the advisories; an error envelope or unparseable output → a GitHub warning annotation saying the gate did not run, exit 0. The step moves to the end of the job so the frontend's own verdict always exists whatever the audit does. Verified locally against five report shapes (transport timeout envelope, E503 envelope, one high advisory, clean, garbage) and two live runs (the real registry: clean; a dead registry: warning, exit 0). `--input <file>` is the seam those checks use. Fixes BUG-2881 * ci(web): the audit gate fails closed — retry an unreachable advisory service, then fail under its own title Codex round 1 on #1247: the first draft warned and exited 0 when the advisory service could not be asked, which made the only supply-chain gate pass exactly when it had not run. A gate that passes when it cannot run is not a gate. Now: up to three attempts with backoff (registry blips are usually seconds long), then `::error title=npm audit did not run` and exit 1. The title is distinct from `::error title=npm audit` (a real advisory) so the checks tab tells the two apart without opening the log; re-running is the remedy for the first and never for the second. Because the step runs last, Build / Type check / vitest have already produced their result either way — the original blindness is gone regardless of which way this step fails. Verified against the same five saved shapes (transport and E503 envelopes and garbage now exit 1 under the did-not-run title; a high advisory exits 1 under the advisory title; clean exits 0) and two live runs (real registry: clean; dead registry: three attempts logged, exit 1). Refs BUG-2881 * ci(web): the audit gate refuses counts it cannot read, and refuses bad tuning without crashing Codex round 2 on #1247. (1) metadata.vulnerabilities was checked for presence, not for shape: Number("x") + Number(null) > 0 is false, so a malformed count read as a clean audit — a second fail-open, one layer deeper than round 1's. high/critical must now be non-negative integers or the report is unreadable, which is the fail-closed path. (2) The two env knobs are operator-set, but CI_AUDIT_ATTEMPTS=NaN left the retry loop unexecuted and threw a TypeError, and CI_AUDIT_BACKOFF_MS=Infinity parked Atomics.wait forever; both now fall back to the default with a line saying so. Refs BUG-2881 * build: the local preflight runs the same audit gate CI does, and runs it last Codex round 3 on #1247 (blast radius): `make web-check` still chained bare `npm audit && npm run check`, so a registry blip stopped svelte-check locally exactly as it had in CI, and CONTRIBUTING documented the bare command as the way to reproduce the gate. New `web-audit` target runs `npm run audit:ci`; `check` runs it after web-check and web-test, mirroring the Web job's order. CONTRIBUTING and docs/architecture.md say so. Refs BUG-2881 * build: web-audit stands alone — no `web` prerequisite, so `check` runs npm ci once and no new target reaches it Codex round 4 on #1247: `web-audit: web` made `check` run `npm ci` twice (`web` is .PHONY) and added a target CLAUDE.md's worktree rule did not list as reaching `npm ci`. `npm audit` reads the lockfile and needs neither node_modules nor a build — verified by running it with node_modules removed — so the prerequisite goes; CLAUDE.md's safe list gains `web-audit`. Refs BUG-2881 |
||
|
|
f9195c5b09 |
ci: make the go test timeout explicit everywhere (TASK-2545) (#1089)
* ci: make the go test timeout explicit everywhere (TASK-2545) The v0.13.0 release pre-flight died on `panic: test timed out after 10m0s` in internal/store, on a commit whose Go tree was identical to a green run an hour earlier. Nothing hung — the package's runtime simply crossed a budget nobody had chosen. `go test` without -timeout uses a 10m per-test-binary default. This repo raised the two RACE steps to 45m twice as the suite grew (BUG-1371 30m, BUG-1913 30m→45m), each time with a careful comment — and each time left their non-race siblings on the silent default. Three steps were still running on it, including the release gate: ci.yml "Run tests" (SQLite) ci.yml "Run tests against PostgreSQL" (the one that panicked) release.yml "Run tests" (the release gate itself) All three now carry -timeout=45m, matching the race legs so the file has one number, with comments saying it is a hang-catcher rather than a performance budget and that job wall-clock is the signal for "the suite got slow". Measured at |
||
|
|
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). |
||
|
|
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 |
||
|
|
bfce069f28 |
fix(web,build): search palette hang on numeric query + graceful SSE shutdown (BUG-1531) (#588)
* fix(web,build): search palette hang on numeric query + graceful SSE shutdown (BUG-1531) CommandPalette's reactive `$effect` subscribed to every workspace's `localSearch.epoch` + `localIndex.bootstrapStateFor`. Bare-digit queries short-circuit to `exactItemNumberLookup` (synchronous, very fast) and stacked re-fires of the effect inside one microtask tick whenever an SSE delta arrived — Svelte tripped `effect_update_depth_exceeded` and the palette froze. Treat bare-digit queries the same as `body:` queries (skip the subscription reads) and wrap `doSearch()` in `untrack()` so its internal reactive reads can't smuggle hidden dependencies into the effect. The SSE churn that fanned the loop was rooted in `make install` using `killall -9` — SIGKILL drops every open SSE stream mid-chunk so every browser tab logs `ERR_INCOMPLETE_CHUNKED_ENCODING` and reconnects. Switch to SIGTERM + 5s wait + SIGKILL fallback so the server's existing graceful-shutdown path (cmd/pad/main.go:811-857) actually runs and the http.Server writes a final 0-chunk on each open stream. Follow-up tidy-ups (unchecked write errors in writeSSEEvent, link the 30s keepalive to the 120s IdleTimeout in code) tracked in BUG-1532. * fix(web): track workspace slug in palette $effect per Codex review (round 1) After wrapping doSearch() in untrack(), the workspaceStore.current?.slug read that doSearch performs at line 209 no longer registered as a tracked dep of the search effect. The non-body / non-bare-digit branch still reads the slug via localIndex.bootstrapStateFor(...), so workspace switches re-fire the effect for that branch — but body: and bare-digit queries skip that block entirely. Without an explicit slug subscription they wouldn't re-dispatch on workspace switch; an in-flight server response would land stale, get discarded by isSameDispatch(), and loading could stick true. Hoist `void workspaceStore.current?.slug` into the unconditional void block so all four query shapes re-fire on workspace switch. Refs BUG-1531. * chore: gofmt handlers_claim_code_test.go Drive-by formatting fix to unblock CI on this PR. The file landed slightly unaligned in #586 (TASK-1525) — gofmt straightens the struct tag column on claimCodeResponse. |
||
|
|
21e8ca6a20 |
chore(make): add make check mirroring CI lint + test + web build (IDEA-921) (#322)
* chore(make): add `make check` mirroring CI lint + test + web build (IDEA-921) Closes the local-vs-CI gap that let PR #321 ship a trivial gofmt violation past every step of CONVE-190's pre-flight (`go build && go test && cd web && npm run build`). - `make lint` now runs the same golangci-lint v2.11.4 suite CI runs (govet, ineffassign, staticcheck SA*, unused, plus the gofmt formatter with simplify: true). The bootstrap rule auto-installs the pinned binary into $(go env GOPATH)/bin on first run, so contributors don't need a separate setup step. - `make check` is a new umbrella target that runs lint, the Go test suite, and the web build — the exact set of jobs CI's "Go" and "Web" jobs run. Run it before pushing. - `make install` is unchanged (build + restart) so the inner dev loop stays fast. `check` is the opt-in pre-push gate. CONVE-190 updated separately via the Pad CLI to point contributors at `make check` instead of the old three-command list. Verified locally: `make check` passes on a clean tree (after a one- time `golangci-lint cache clean` to clear stale entries from a prior run — that's a known golangci-lint quirk, not a workflow bug). * fix(make): enforce lint version pin + cover full CI surface (round 1) Codex review on PR #322 round 1 surfaced two real gaps in the make check / make lint plumbing. 1. Makefile:83 — `lint` did not actually enforce GOLANGCI_LINT_VERSION. The previous file-target dependency only fired the install rule when the binary was missing, so an older or newer locally-installed golangci-lint was silently reused, defeating the pin. The recipe now compares the installed version against the pin and reinstalls on mismatch. 2. Makefile:97 — `check` claimed to mirror CI's Go and Web jobs but omitted Web's `npm run check` (svelte-check type checking) and the Go job's `govulncheck` step. CI could fail on either while local `make check` passed. Added new `vuln` (pinned to GOVULNCHECK_VERSION = v1.2.0, matches CI) and `web-check` targets, both wired into `make check`. `make check` now runs: lint + go test + govulncheck + npm ci + npm audit + npm run build + svelte-check — exactly mirroring the gates the CI Go and Web jobs use to fail a PR. The race-detector and PostgreSQL jobs only run on push to main and are intentionally not part of `make check` (run `make test-pg` separately if needed). Verified locally: `make check` exits 0; `make lint` correctly no-ops when the pinned version is already installed. |
||
|
|
062eef41b2 |
docs: architecture guide + full .env.example + gitattributes + Makefile note (TASK-687) (#222)
Grouped nice-to-haves called out in the pre-launch audit. 1. docs/architecture.md — new contributor-focused architecture doc. CLAUDE.md covers the same ground but is agent-oriented; this is the human companion. Covers backend layout, request flow, frontend / data model / CLI↔daemon model / agent integration / testing. 2. .env.example — extended to document every PAD_* variable in docs/deployment.md (core, database, real-time events, security, email). Existing Postgres/Redis + encryption secrets kept at the top; new variables grouped by concern with inline comments and safe defaults commented out. 3. .gitattributes — normalize LF line endings repo-wide, mark binary assets, and flag web/build + web/.svelte-kit as generated so they don't pollute GitHub linguist stats or PR diffs. 4. Makefile — CAUTION comment on `make install` noting that the `killall -9 pad` step is system-wide; anyone else's pad daemon on the same machine gets killed too. Designed for single-developer local setups; not for shared hosts. Parent: PLAN-644. |
||
|
|
b027046605 |
fix: address review findings for PR #91 (iteration 1)
Ensure make test-pg cleans up Docker containers even when tests fail by capturing the exit code and running cleanup unconditionally. Remove dead CSS rules from root page after welcome template simplification. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
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 |
||
|
|
f5649b912e | refactor(cli): group first-release commands for TASK-127 (#45) | ||
|
|
eb4e41ad0b | build(embed): stop rewriting embed.go for TASK-112 (#32) | ||
|
|
056beba12b |
fix: run npm ci before web build to ensure dependencies are installed
Prevents "vite: not found" errors on fresh clones or after node_modules is removed, by installing dependencies before building. |
||
|
|
0972347cf0 |
Fix svelte warnings and add build version info (#26)
* Fix all 17 svelte-check warnings across 7 components - Add tabindex to toolbar role elements (BoardView, Editor) - Replace nested buttons with div[role=button] in ListView group headers - Add role="none" to click-to-close backdrop overlays (Editor, slug page) - Fix label→span for non-input field labels (CreateWorkspaceModal) - Add keyboard handlers to interactive divs (CreateWorkspaceModal drop zone, slug page modal) - Remove unused .slash-backdrop CSS (Editor) and input[type=text] selector (CreateWorkspaceModal) - Fix state_referenced_locally in RawMarkdownEditor ($state init) - Add svelte-ignore for conditional tabindex false positive (ToastContainer) * feat: add build version, commit hash, and timestamp to CLI and web UI Inject version info via ldflags during build (Makefile, GoReleaser, Dockerfile). Expose version/commit/build_time in the health API endpoint and display it in the sidebar footer. Dev builds show "dev (abc1234 ...)", releases show "v1.2.3 (abc1234 ...)". |
||
|
|
a6befde8bf |
feat: email-based password reset flow (#24)
* feat: email-based password reset flow (IDEA-81) Add full password reset flow: forgot password request, time-limited reset tokens (1hr, single-use, SHA-256 hashed), new password form, and automatic session creation after reset. * fix: use all: prefix in go:embed to include _-prefixed files Go's embed package excludes files starting with _ or . when recursing directories. SvelteKit/Vite occasionally generates chunk filenames with _ prefixes (e.g. _VLZtjCJ.js), causing them to be silently dropped from the embedded filesystem and served as HTML by the SPA fallback. The all: prefix includes everything regardless of filename prefix. Fixed in both embed.go and the Makefile which regenerates it. * fix: address PR review — atomic token consumption, error handling, log reset URL - Replace ValidatePasswordReset + MarkPasswordResetUsed with atomic ConsumePasswordReset using UPDATE ... WHERE ... RETURNING to prevent race conditions where two concurrent requests consume the same token - Handle DeleteUserSessions errors (log instead of silently ignoring) - Log the full reset URL when email is not configured so the admin CLI fallback is actually usable |
||
|
|
f7ac533ec1 |
Fix critical security issues found in pre-public audit
- Fix SQL injection in sort parameter: validate field names against alphanumeric regex before interpolating into json_extract queries - Restrict CORS to localhost origins only (remove http://* wildcard) - Change Makefile HOST default from 0.0.0.0 to 127.0.0.1 - Add Apache-2.0 license field to web/package.json |
||
|
|
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 |