mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-25 14:00:40 +00:00
4532eecffdeac4eeff1cd159ff1353df1ccac07b
78 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1923c5a04d |
ci: split E2E into two Playwright shards behind one E2E (Playwright) verdict (IDEA-3189) (#1813)
* ci: split E2E into two Playwright shards behind one E2E (Playwright) verdict (IDEA-3189) IDEA-3189's tripwire fired: the median of the last 10 green E2E jobs is 16.5m (13.7-19.3m) against its 14m threshold, and #1811's E2E was cancelled at the 20m cap with no failing test. The suite now runs as --shard=1/2 and 2/2, each job keeping the 20m cap and the TASK-3415 binary guards. A project cut was measured and rejected: desktop-chromium carries 1222s of 1359s. The check named "E2E (Playwright)" is now an aggregate that needs both shards, runs if: always(), and passes only when every shard succeeded. Report and flaky-evidence artifacts carry the shard number. Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS * ci: shard desktop E2E in halves and run mobile whole (IDEA-3189) A bare --shard=N/2 split exactly on the project boundary: Playwright cuts the ordered test list by count, the projects run in sequence and have the same test count, so the first run's shard 2/2 was mobile-chromium alone (63 passed, 553 skipped, 1.9m) and shard 1/2 all of desktop. The matrix is now desktop 1/2, desktop 2/2 (308 tests each by --list) and mobile whole. Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS |
||
|
|
77085b7f3c |
chore(ci)(deps): bump anchore/sbom-action/download-syft (#1799)
Bumps the actions-minor-and-patch group with 1 update: [anchore/sbom-action/download-syft](https://github.com/anchore/sbom-action). Updates `anchore/sbom-action/download-syft` from 0.24.2 to 0.24.3 - [Release notes](https://github.com/anchore/sbom-action/releases) - [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md) - [Commits](https://github.com/anchore/sbom-action/compare/3ad7283483fc7af8ff2b4ea19663c2d5ca935e26...66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c) --- updated-dependencies: - dependency-name: anchore/sbom-action/download-syft dependency-version: 0.24.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> |
||
|
|
78f3c08240 |
test(e2e): a real installed app, end to end over https (TASK-3415) (#1804)
* feat(e2e): a test-build-only door for the app origin's CA (TASK-3415) Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS * test(e2e): a real install end to end over an https front and app origin (TASK-3415) Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS * test(e2e): the install review waits for a real manifest fetch (TASK-3415) Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS * chore: ignore the e2etest pad binary (TASK-3415) Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS * test(e2e): discovered https endpoints, awaited teardown, OS-assigned ports, an explicit timeout (codex r1 on TASK-3415) Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS * test(e2e): teardown survives a spawn failure, pad's port retries, the front drops hop-by-hop headers, cert setup cleans up (codex r2 on TASK-3415) Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS |
||
|
|
745f576ebc |
ci: run the native PostgreSQL backup/restore round trip (TASK-2962) (#1761)
* ci: run the native PostgreSQL backup/restore round trip (TASK-2962) TestPostgresBackupRestoreConnection had never run in CI. It skips unless PAD_TEST_POSTGRES_TOOLS_URL is set, and CI could not have set it: the Go (PostgreSQL) job's server is postgres:17, ubuntu-latest ships the 16 clients, and pg_dump refuses to dump a newer server. The restore rewrite (#1703) shipped in rc.3 with its round trip unexercised. The job now: - installs postgresql-client-17 from PGDG, after checking the signing key against its published fingerprint, and puts its bin first on PATH - checks that pg_dump and psql are both version 17 - runs the round trip alone and verbosely with PAD_TEST_POSTGRES_TOOLS_URL set, and fails unless the output shows it PASSED. In the quiet suite a skip and a pass look the same, which is how it went unrun. Measured locally against a postgres:17 server, with 17 clients (via docker wrappers): the test passes, and it fails for each of four restore and backup defects: - no --single-transaction: a failed restore left its change committed - psqlrc honoured: a restore reported success after a SQL error - no ON_ERROR_STOP: the same - BUG-2952's PGDATABASE=<uri>: pg_dump failed Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * ci: the PGDG key file must hold exactly one primary key, PGDG's (TASK-2962) Codex round 1: signed-by trusts every key in the file, and the check read only the first fingerprint, so a response carrying PGDG's key followed by another would have passed. The file now has to contain exactly one primary key, and its fingerprint must be PGDG's. Checked locally: the real key is accepted, and the real key with a second key appended is refused. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC |
||
|
|
d78ff9bdc5 |
feat(materialize): worker supervisor with per-OS memory cap (TASK-2198 U3) (#1682)
* feat(materialize): parent-side worker supervisor with per-OS memory cap (TASK-2198 U3) The Supervisor runs materializer jobs in ONE long-lived child process (`pad __materialize-worker`), started lazily on the first job and fed jobs serially. A job past PAD_MATERIALIZE_TIMEOUT (default 2s) kills the child (goja's Interrupt, sent as timeout_ms a tenth earlier, is only the soft layer); a dead child is respawned on the next job after an exponential backoff (1s doubling, capped at 5 min, reset by a child that lived a minute). Every stop is logged with its reason (deadline, memory, exit, protocol, start, canceled, closed); every start logs the effective timeout, memory limit and cap mechanism. The child's stderr goes to the log, pid-tagged and bounded, never near the protocol. A job whose child died is never retried here. Memory cap (PAD_MATERIALIZE_MEM_LIMIT, default 2GiB, bytes or KiB/MiB/GiB, clamped to 1536MiB-16GiB): - Linux: RLIMIT_AS set on the child by prlimit before its first byte of input; the child only runs its own startup uncapped. - Windows: Job Object with JOB_OBJECT_LIMIT_PROCESS_MEMORY; the child is created suspended, assigned, then resumed: no uncapped window. - macOS: parent-side RSS watchdog every 100ms (proc_info syscall, ps fallback). - Other: deadline kill only. Not wired into the server yet (U4). Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * ci(materialize): run the supervisor on the real macOS and Windows caps (TASK-2198 U3) native-smoke now builds the real materializer bundle (as the go job does) and runs the supervisor, memory-cap and deadline tests on macos-latest (proc_info RSS watchdog) and windows-latest (suspended start + Job Object). PAD_MATERIALIZE_TEST_NO_SKIP turns the cap tests' skips into failures there, so a cap that stops running cannot pass as skipped. Tests: a real Windows processGone (OpenProcess + GetExitCodeProcess), RSS samplers for darwin (the production proc_info path) and Windows (K32GetProcessMemoryInfo), and an allocation helper that writes noise into every byte so macOS memory compression cannot hide it from the watchdog. The worker-loop tests share one loaded Runner through serveWorker (RunWorker's loop, split out); TestWorkerRoundTrip still goes through RunWorker end to end. go test -race -count=3 on the package: 714s -> 413s. docs: the materializer section says the worker and its knobs are inert until U4 wires it in. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * fix(materialize): a worker never outlives its supervising process (TASK-2198 U3, codex r1) If the server died mid-job (SIGKILL, OOM kill, crash) its worker ran on with no deadline kill and, on macOS, no memory watchdog. Two layers: - Linux: Pdeathsig = SIGKILL. The fork runs on a fresh goroutine (startUnlocked), never locked to its thread, so it cannot land on a thread the runtime later retires (golang/go#27505); Go's forkExec re-checks getppid if the parent is already gone. - Every Unix (the only layer on macOS): the worker command calls ExitWhenOrphaned before the bundle loads, polling its parent pid every 200ms against PAD_MATERIALIZE_PARENT_PID and exiting (status 70) when it changes. It writes nothing. - Windows was already covered by the Job Object's KILL_ON_JOB_CLOSE. TestSupervisorOrphanedWorkerExits kills a helper process that supervises a worker hung in a job and requires the worker gone within 1s: 0s on Linux, 105ms with the ppid watch alone; with both layers removed the worker outlives its parent by 5s. It matches native-smoke's TestSupervisor.* selection, so it runs on macOS and Windows too. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * fix(materialize): relative memory cap, refuse when uncappable, GOMAXPROCS=2; nix-safe ps test (TASK-2198 U3) The absolute Linux RLIMIT_AS failed on CI: a 64 MiB allocation died under a 1600 MiB cap, because the runtime there reserved more address space than on the machine the floor was measured on. The cap is now RELATIVE on every OS: once the worker has loaded and before it is sent any job, its usage is read as a baseline and the cap is set to baseline + PAD_MATERIALIZE_MEM_LIMIT — address space on Linux (/proc/<pid>/stat vsize, prlimit, read back), committed memory on Windows (the Job Object's per-process limit, set after the suspended start and load), resident memory on macOS (the watchdog's threshold). Every start logs baseline, limit and effective cap. Lead ruling, REFUSE: if the baseline probe fails or is implausible (0, or above MaxPlausibleBaseline = 8 GiB, ~4.8x the largest measured 1704 MiB), onBaselineProbeFailure kills the worker without sending it the job and fails the job with ErrNoMemoryCap; one WARN until a probe succeeds again. An OS with no cap mechanism never starts a worker and refuses every job with ErrNoMemoryCap, one WARN naming runtime.GOOS. The worker runs with GOMAXPROCS=2: measured loaded-worker baseline 1357 MiB vs 1597 MiB at this host's default (8) and 1704 MiB at 64. MinMemLimit drops from 1536 MiB (an artefact of the absolute cap) to 256 MiB: the corpus grows the worker by at most +10 MiB of address space and +18 MiB resident, and a 4,000-deep nested document by +129..194 MiB. A cgo worker's "pthread_create failed" at the cap is now classified as a memory death. Nix: TestParsePSRSS parses fixed strings only; the live `ps` leg is TestPSRSSLive and skips when there is no `ps` (the Nix sandbox has none; ps is only macOS's fallback), unless PAD_MATERIALIZE_TEST_NO_SKIP is set. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * fix(materialize): Windows kills on the job's memory notification; steady thread set before the baseline (TASK-2198 U3) Windows (CI on c1866957): the Job Object refused the 1 GiB commit (VirtualAlloc errno=1455), but the worker did not die and the job ran to its deadline. The cap no longer depends on the runtime choosing to crash: the job is associated with an I/O completion port, and JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT for the worker's pid kills it at once (ErrMemoryLimit). errno=1455 on stderr is a second signal. On every OS a cap mechanism that sees the limit hit calls onLimit, and the kill is reported as "reached its <mechanism> cap". Linux (CI's cgo test binary at GOMAXPROCS=64): threads created after the baseline ate the growth budget. The worker now brings its thread set to steady state before readiness (warmThreads: an allocating goroutine per P, then a GC). Measured on a cgo build: threads added after the baseline are 0 at GOMAXPROCS=2 with or without it; at 64, +3 threads and +272 MiB of address space during the corpus without it, +0 and +0 MiB with it. 256 MiB stays the floor (largest growth at GOMAXPROCS=2: +193 MiB, a 4,000-deep nested document; the corpus adds +0 MiB). The cap test's second leg no longer uses GOMAXPROCS=64 (which also changed thread creation) to stand in for a bigger host: the helper inflates its own baseline before readiness, with never-touched address space on Linux, committed memory on Windows and resident memory on macOS. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * feat(materialize): the worker stops after an idle timeout; the next job restarts it (TASK-2198 U3) Dave's ruling on the worker process: GO, with an idle exit. A loaded worker holds ~200 MB resident, which an install that rarely materializes should not pay for all day. PAD_MATERIALIZE_IDLE_TIMEOUT: default 5m, clamped to 30s-24h, 0 = never. After each job the supervisor (re)arms a timer. When it fires, the stop runs only while holding the job slot, so it never takes a worker with a job in flight. It re-checks the idle time, forgets the worker WITHOUT recording a death (backoff is untouched), and closes the worker's stdin so its read loop ends with status 0, with a kill after a grace if it does not go. The stop is logged at INFO, reason=idle. A job that arrives meanwhile waits for the slot, finds no worker, and starts a new one with no backoff wait. Legs: the idle exit after the timeout (INFO, status 0, no WARN); the next job respawns and succeeds with no wait; an idle stop leaves the backoff streak alone (crash, idle, crash waits 2s, not 4s); a job arriving during the stop runs on a new worker; a timer that fires mid-job does not stop that worker; NeverIdle arms no timer; env parsing and clamps. Mutations, each red on its own: recording a death (3 legs), logging as a kill (1), not holding the slot (1), never arming (3). Docs: the knob beside the other two, and `idle` in the stop reasons. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * fix(materialize): Close does not return while an idle-stopping worker still runs (TASK-2198 U3, codex r1) An idle stop forgets the worker before it has exited, so a Close in that window found no worker and returned with it still alive. The supervisor now tracks the worker an idle stop is ending, and Close kills and reaps it. A leg pins it, and goes red without the fix. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC * fix(materialize): an idle stop that Close ended says so (TASK-2198 U3, codex r2) The idle stop stored its kill reason unconditionally, so it overwrote the 'closed' of a Close that had already killed the worker, and its stop line said idle. It now claims the reason only if none is set, and the line reports reason=closed. The Close-during-idle-stop leg asserts it. Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC |
||
|
|
fa2755e183 |
feat(materialize): headless op-log materializer bundle, goja runner and worker (TASK-2198 U2) (#1678)
* feat(materialize): headless op-log materializer bundle, goja runner and worker (TASK-2198 U2)
Builds the editor's own JS (Yjs -> y-tiptap -> Tiptap schema -> tiptap-markdown
-> the tab's flush pipeline) into one IIFE, embeds it, and runs it in goja. Not
wired into the server yet (U3 supervisor, U4 write path).
- web/src/lib/collab/materializer/: bundle entry (extension list mirrors
Editor.svelte's collab branch via the U1 shared modules), op-log replay
mirroring wsProvider's MESSAGE_SYNC arm, linkedom DOM shim and polyfills
from the spike, and a vendored patch of prosemirror-markdown
MarkdownSerializerState.atBlank (quadratic under goja; equivalent).
- web/scripts/build-materializer.mjs (esbuild) -> web/build-materializer/,
run by `npm run build`; embedded as pad.MaterializerJS.
- internal/materialize: Runner (Go-native atob/btoa/TextEncoder/TextDecoder/
getRandomValues, schema-version check, ctx interrupt with a clean VM after),
and RunWorker's length-prefixed JSON protocol behind the hidden
`pad __materialize-worker` command.
- Parity: vitest drives two real Editor.svelte mounts and compares
materialize() with the tab's flush on 50 seeded cases; the same cases are
committed as testdata/corpus.json and replayed in goja (TestCorpusParity).
- CI Go jobs, release tests, Docker and Nix produce the bundle; linkedom and
esbuild are new devDependencies.
Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC
* test(materialize): pin the atBlank patch's equivalence under V8 too (TASK-2198 U2, codex r1)
Codex r1 claimed upstream's /(^|\n)$/ is true for a trailing CR in JS. It is
not (checked in V8: false for "\r" and "a\u2028", as the patch returns),
but the only equivalence test ran under goja and the patch header cited a
V8-side test that did not exist. This adds it, with the line-terminator edge
cases, and it fails when the patch checks CR instead of LF.
Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC
* build(nix): vendorHash for goja (TASK-2198 U2)
Taken from the Nix job's recompute on
|
||
|
|
e776a64fcd |
chore(deps): bump libopenapi to 0.40.1; hold mcp-go at 1.1.0 (TASK-3298) (#1656)
* chore(deps): bump libopenapi to 0.40.1; hold mcp-go at 1.1.0 (TASK-3298) Replaces dependabot #1635, which also moved mcp-go 1.1.0 -> 1.1.1. That release (upstream #977) makes StdioServer.Listen cancel every in-flight request's context when it reads stdin EOF. For `pad mcp serve` fed a piped batch, each dispatcher-backed tool call and resource read then answers `context canceled` instead of its result (measured: 3 of 4 ids failed on a build of #1635, 0 of 4 on main). - go.mod/go.sum: pb33f/libopenapi 0.38.7 -> 0.40.1 (pulls pb33f/jsonpath 0.8.2 -> 0.8.3). Identical to #1635's change minus mcp-go. - .github/dependabot.yml: ignore mcp-go 1.1.1 only, not the dependency. - internal/mcp/stdio_eof_inflight_test.go: pins the behaviour. Three tool calls and one resource read are held inside a blocking dispatcher/fetcher, stdin is closed while all four are in flight, then they are released and every id must answer successfully. Passes 3/3 on mcp-go 1.1.0 and fails 3/3 on 1.1.1 (all four ids "context canceled"), run via a scratch -modfile. Claude-Session: https://claude.ai/code/session_016PXRzXhokr61CLbbu3Txj9 * test(mcp): release held calls on observed EOF, not a sleep (TASK-3298) Codex round 1 on |
||
|
|
e78e06bd56 |
BUG-3278 diagnostics: self-describing move-menu steps + flaky-run evidence upload (#1648)
* test(e2e): the move walk's card-menu steps fail in seconds naming what the menu rendered (BUG-3278 diagnostics) A card's action menu HIDES a Move entry its position rules out, so a bare getByRole click on an absent entry waited out the whole 30 s test budget and reported only the name it waited for. That is BUG-3278's CI failure, which cannot say whether the menu never opened or opened without the entry. clickMenuEntry asserts the menu opened, then that the entry rendered, and otherwise throws naming the rendered entries and the lane the entry was chosen from, both then and now. Applied at all four trigger-then-menuitem sites in the spec. Diagnostics, not a fix: the flake is not reproduced locally, and two candidate mechanisms (a lane re-sort after placement, a card remount) were probed and not observed. Claude-Session: https://claude.ai/code/session_01NidgPePY7UvHcEJCMkD2Qz * ci: upload Playwright evidence for flaky runs (BUG-3278) A test that fails and then passes on retry leaves the job green, so the failure()-gated report upload never runs and the flake ships no screenshot, trace or video, even though retain-on-failure kept them in web/test-results. Upload that directory as playwright-flaky-evidence on a green run, only when a failed attempt left a trace.zip. Retention is 7 days. Claude-Session: https://claude.ai/code/session_01NidgPePY7UvHcEJCMkD2Qz |
||
|
|
2d8aedc9a4 |
fix(release): the changelog starts at the previous stable release by version, and its exclude filters match GitHub's hash-prefixed entries (TASK-3268) (#1626)
* fix(release): the changelog starts at the previous stable release by version, and its exclude filters match GitHub's hash-prefixed entries (TASK-3268) Two release-notes defects, measured with the pinned GoReleaser (v2.15.4) in a changelog-only dry run whose output for v0.16.0 is byte-identical to the published notes. Base: GoReleaser took the previous tag from git describe on the new tag's parent, which sees only tags on that ancestry. Releases are cut on release-X.Y.x branches, so v0.16.0 was built from v0.15.0-rc.1, and a tag cut from main now would describe back past v0.16.0: GitHub's compare reports 547 commits from there against 301 from v0.16.0, re-listing all of v0.16.0. release.yml now pins GORELEASER_PREVIOUS_TAG through scripts/release-previous-tag.sh (the highest stable tag below the current one by version; an RC gets the previous stable) and fails the run if none exists. Filters: with `use: github` every entry is "<sha>: <message>", so the message-anchored excludes never matched and v0.16.0 shipped 30 docs, test, ci and chore lines. The rule now allows the prefix, a scope, and a type list made only of excluded types (`docs,test(store):`). Dry run of v0.16.0 with both fixes: 230 entries become 200. Every one of the 30 removed is an excluded type, and nothing is added. Claude-Session: https://claude.ai/code/session_016PXRzXhokr61CLbbu3Txj9 * fix(release): a breaking change stays in the notes whatever its type (TASK-3268 codex r1) The exclude accepted `!` before the colon, so `chore!: ...` would have been dropped. Codex round 1, P2. The v0.16.0 dry run is unchanged at 200 entries; an RE2 matrix of 15 messages, each with and without the hash prefix, answers as intended. Claude-Session: https://claude.ai/code/session_016PXRzXhokr61CLbbu3Txj9 |
||
|
|
9059862431 |
fix(session): a headless disarm holds for as long as its session lives: an unverifiable owner fails closed and is kept, and the file names the session, not the exiting command (BUG-2771, BUG-3227) (#1574)
* ci(smoke): a headless disarm on Windows must survive later reads (BUG-2771) Added before the fix, so the first CI run shows it red against the pre-fix binary. * fix(session): an arm-state file whose session cannot be verified is kept and fails closed, so a disarm is never forgotten (BUG-2771) The unknown owner verdict (every headless file on Windows; on unix an unexaminable /proc entry or socket) was treated as dead: the next read reaped the file and auto_arm re-armed over an explicit disarm. It now resolves to LocalArmUnverifiable, which is not armed and not reaped. `pad session arm` reports the real state, `status` explains it, and a new `pad session reset` removes the kept file so auto_arm decides again. * ci(smoke): the headless-disarm step covers unix too, owned by the step's shell via PAD_SESSION_PID (BUG-3227) Pushed on top of the BUG-2771 fix but before BUG-3227's, so this run shows the unix path red pre-fix while Windows turns green. * fix(session): a headless arm or disarm is owned by the session process, not the exiting command, and a write no session can own is refused (BUG-3227) A headless arm-state file recorded os.Getpid(), the pid of the short-lived `pad session arm`/`disarm` itself, so on unix it was dead on the next read and reaped: an explicit disarm never held. It now records CaptureSessionOwner() (PAD_SESSION_PID, then CLAUDE_PID), as the session registry does. With no session process to name, the owner is the command, and arm/disarm refuse rather than report a consent change that did not happen. * docs(session): arm and disarm --help say how a headless session is identified, and that with no owner they exit non-zero (BUG-3227) |
||
|
|
16e60cf79f |
ci(BUG-3188): the E2E job cap goes 15 -> 20 minutes; the green median is 10.9m and rising with the suite (#1496)
Measured 2026-09-24 on 5 green CI runs: E2E wall time 9.3-11.7 min (median 10.9) against a 15-min cap, versus 7m54s when #1158 set it. One flaky retry (~+2m) still fits, two may not, and a cap-end cancels a job whose tests passed. The growth is new specs: tests present in both the baseline (#1158 merge run) and today's run went 606s -> 566s in total, while 83 added tests account for 257 test-seconds (test phase 5.5m -> 7.3m). Raising the cap treats the symptom. The tripwire is IDEA-3189: shard or trim when the median of the last 10 green E2E jobs passes 14m. Claude-Session: https://claude.ai/code/session_01Vf8uZmYdsrQqJtbAmjUFSX |
||
|
|
c85910039e |
chore(ci)(deps): bump docker/setup-buildx-action (#1409)
Bumps the actions-minor-and-patch group with 1 update: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action). Updates `docker/setup-buildx-action` from 4.3.0 to 4.4.1 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/37fe631027851001ddb9b187196cc803df7f5f0e...f87e5991a6d7451dcb8d9637bfbc97413f497069) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: 4.4.1 dependency-type: direct:production update-type: version-update:semver-minor 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> |
||
|
|
b7d19e68c8 |
chore(ci)(deps): bump DeterminateSystems/nix-installer-action (#1367)
Bumps [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) from 22 to 23. - [Release notes](https://github.com/determinatesystems/nix-installer-action/releases) - [Commits](https://github.com/determinatesystems/nix-installer-action/compare/ef8a148080ab6020fd15196c2084a2eea5ff2d25...3138316df39ed29be04236d7ffc686fa525866aa) --- updated-dependencies: - dependency-name: DeterminateSystems/nix-installer-action dependency-version: '23' 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> |
||
|
|
d83632e258 |
feat(attachments): extension trust — the audio/video split, the text family, the CFB office trio and RTF (TASK-2976 / BUG-2963 PR B) (#1309)
* test(attachments): record the Void-beyond-window limit, and correct three comments that claimed more than their evidence (TASK-2976) The first commit of BUG-2963 PR B, before any extension-trust work: round 6's three items, deliberately kept out of PR A so its verified tip would not move, plus the nix terminator-comment correction folded in from BUG-2974's trail. 1. TestMatroskaDocTypeBeyondTheWindowIsWebM records a LIMITATION. A Void element is legal anywhere in an EBML header and may be any size; make one larger than the 512 bytes this door reads and the DocType behind it is not in the input at all, so the parse finds nothing and the stdlib's video/webm stands. Nothing got worse — the same file was video/webm before the DocType read existed, and video/webm's own allowlist entry permits inline serving — and no larger window fixes it, since Void may be larger still. The fixture is the ordinary FFmpeg Matroska with a 560-byte Void spliced into its header and the header size widened to match, the same construction as matroska-void-padded.head512; the complete file reads as matroska,webm under ffprobe, and what is committed is its first 512 bytes, so the DocType is absent by construction rather than by truncation accident. 2. TestTarWinsAPrefixCollision no longer calls the collision "asymmetric". That was the round-4 premise round 5 refuted with flac-ustar-in-comment.head512 — a FLAC's Vorbis COMMENT tags are arbitrary UTF-8, so real audio carries "ustar" at offset 257 as readily as a real tar carries an audio marker at offset zero. The implementation and the fixtures already said so; the stale word survived in the place a reader looks first. The comment now gives the real reason tar leads the default order, which is weaker: its magic sits at a fixed offset rather than at a prefix. 3. The seven-byte textual-AAC input's comment says which of the two things it is. It is a valid ADTS sync and layer signature that the stdlib reads as text — not a decodable AAC, and nothing in the test establishes that it is. What review established is the case it stands in for, and the comment now separates the two. 4. The nix loop-terminator comments named the wrong mechanism (BUG-2974, observed day 62). The heal push cannot loop because a push made with the default GITHUB_TOKEN creates no workflow runs at all — `gh run list --branch main` spans |
||
|
|
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 |
||
|
|
4618876e3e |
fix(cli): pad server stop signals only a process it can prove is ours (BUG-2969) (#1299)
fix(cli): `pad server stop` signals only a process it can prove is ours (BUG-2969) Measured on the merged binary before this change: a `sleep 600` whose pid had been written into the PID file was SIGTERMed, and stop printed "Server stopped." No pad server was running anywhere near that config. Three things had to be true at once for that. os.FindProcess succeeds for ANY pid on Unix. Nothing asked whether the pid belonged to a pad server. And the confirmation loop polled the PORT — which is unhealthy from the first poll when nothing was ever serving, so the success check was satisfied by the failure case. Liveness is the wrong question, and this is the trap the obvious fix falls into: the stranger WAS alive. The question is whether the pid is OUR server. ## The discriminator Unix takes an advisory flock on the PID file, held for the server's lifetime. `stop` probes it non-blockingly: acquiring it proves nobody holds the file, so the record is stale whatever the pid now names; failing to acquire proves a live pad server holds THIS file. One implementation for Linux and macOS, no new dependency, and the same primitive session_lock_unix.go has used since TASK-2767. Windows has no flock in that pattern, so it compares the process creation time from GetProcessTimes against the one recorded at start — the attribute that survives pid reuse, since a reused pid belongs to a process that started later. The lead first ruled start-time comparison on every platform; I objected with the cost (three implementations — /proc, a macOS sysctl promoting x/sys to a direct dependency, and GetProcessTimes) and the ruling changed to this hybrid. The cost table is on the item so the next reader sees why the shape moved. The PID file gains a fingerprint on both platforms — pid, start time, executable path — as JSON, with the legacy bare-integer form still parsed. A legacy record carries no proof, which reads as UNPROVABLE, and unprovable means nothing is signalled. ## Three races, each found by codex and each the same shape 1. Reading the record and checking ownership were separate steps, so a successor could claim the file between them: the lock then reported "held" — truthfully, about the successor — while the pid handed back was the predecessor's. pidFileOwner now returns the record it read from the descriptor it probed. 2. Removing the PID file after a successful stop could delete a fast successor's live record. It no longer removes at all there: the server removes its own on the way down, and a file left by a crash is handled by the next stop. 3. Removing a STALE file after the probe released the lock had the same window. The removal now happens inside the ownership check, while the lock is held — the only moment at which no replacement can have claimed the path. A claim arriving during that instant retries for half a second rather than losing its claim for the life of the process. Windows deliberately does NOT delete a stale file: with no atomic primitive, a check-then-remove would race a successor, and a stale file that the next start overwrites is recoverable where a wrongly deleted record is not. ## Verified Negative control, and it is the literal one: with the ownership check bypassed, `go test` reports `signal: terminated` — the test binary is SIGTERMed by the code under test, because the stale record names the test process itself. Live, in throwaway HOMEs: a stale record naming a live `sleep` is refused and the sleep survives (it was killed before this change); a stale record with a HEALTHY port answering is still refused, nothing signalled, and both the stranger and the real server survive; a server stopped through its own held record stops, and its file is gone. The CI smoke on windows-latest now stops the server with `pad server stop` instead of Stop-Process, because that is the only place the Windows ownership check runs — a smoke that killed the process directly would leave the GetProcessTimes path unexercised on every platform. make lint, make test green; codex CLEAN in round 4. Claude-Session: https://claude.ai/code/session_01HeChkgZVYb3NTgTcckF5KR |
||
|
|
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 |
||
|
|
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 |
||
|
|
99ec711813 |
chore(ci)(deps): bump anchore/sbom-action/download-syft (#1222)
Bumps the actions-minor-and-patch group with 1 update: [anchore/sbom-action/download-syft](https://github.com/anchore/sbom-action). Updates `anchore/sbom-action/download-syft` from 0.24.0 to 0.24.2 - [Release notes](https://github.com/anchore/sbom-action/releases) - [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md) - [Commits](https://github.com/anchore/sbom-action/compare/e22c389904149dbc22b58101806040fa8d37a610...3ad7283483fc7af8ff2b4ea19663c2d5ca935e26) --- updated-dependencies: - dependency-name: anchore/sbom-action/download-syft dependency-version: 0.24.2 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> |
||
|
|
55fce493f1 |
chore(ci)(deps): bump docker/setup-buildx-action (#1189)
Bumps the actions-minor-and-patch group with 1 update: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action). Updates `docker/setup-buildx-action` from 4.2.0 to 4.3.0 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/bb05f3f5519dd87d3ba754cc423b652a5edd6d2c...37fe631027851001ddb9b187196cc803df7f5f0e) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: 4.3.0 dependency-type: direct:production update-type: version-update:semver-minor 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> |
||
|
|
b62c6692c8 |
ci: stabilize two required gates that go red with no code involvement (BUG-2645 opt 1, BUG-2568 opt 1) (#1158)
Two self-inflicting required gates, one reviewed workflow unit (CONVE-2438: its own PR, never a rider; minimal diff, action pins untouched). - E2E (Playwright) timeout-minutes 10 -> 15 (BUG-2645 mechanism b). The 10m cap covers build-web + build-binary + install-playwright + ~190 tests; a clean pass is ~8-10m, so a single flaky-test retry tips the total past the cap and the job is CANCELLED mid-suite while every test passed. 15m absorbs one retry and still kills a genuine hang. Right-sizing evidence: an 8m10s clean rerun vs a 10m18s cancel at the SAME SHA (PLAN-2636 unit 2, #1157). - golangci-lint-action verify: false (BUG-2568). The action's pre-lint `config verify` fetches its JSON schema over the network from golangci-lint.run and fails the required Go gate when that host is slow/unreachable — no lint finding, no code involvement (once skipped govulncheck entirely). A malformed .golangci.yml is still caught by the lint run itself, with a worse message. Scope: this closes BUG-2645 mechanism (b) only. The other two 2645 mechanisms remain, documented as known and out of scope for this unit: (a) the CI/Nix twin-workflow concurrency race that cancels an already-passed run, and (c) a job-rerun expiring inside a parent run already terminal-cancelled. Both are concurrency/rerun-config issues, not a timeout bump. Claude-Session: https://claude.ai/code/session_017jD6t1zjxGSq47SQpZfp1V |
||
|
|
a8989a0ad3 |
chore(ci)(deps): bump actions/attest-build-provenance (#1071)
Bumps the actions-minor-and-patch group with 1 update in the / directory: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance). Updates `actions/attest-build-provenance` from 4.1.1 to 4.2.2 - [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/0f67c3f4856b2e3261c31976d6725780e5e4c373...4d101475d8b20a2381f78447822ac1eab6504dd8) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-version: 4.2.2 dependency-type: direct:production update-type: version-update:semver-minor 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> |
||
|
|
f1bd144b02 |
chore(ci)(deps): bump actions/checkout from 6.0.2 to 7.0.1 (#1073)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 7.0.1. - [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/v6.0.2...3d3c42e5aac5ba805825da76410c181273ba90b1) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.1 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> |
||
|
|
cfad8d989e |
ci(nix): gate the Nix-built binary with govulncheck (BUG-2567) (#1095)
* ci(nix): gate the Nix-built binary with govulncheck (BUG-2567) The main CI govulncheck job scans a go-built binary, which honours go.mod's toolchain line — so the Nix artifact (GOTOOLCHAIN=local in nixpkgs, go 1.26.5 until nixos-26.05 backports 1.26.6) shipped with no vulnerability gate over it at all. Add nix/vulnscan.sh: binary-mode govulncheck against result/bin/pad, compared to nix/accepted-advisories.txt. Known advisories stay green and recorded in-repo; any NEW advisory fails the Nix job; a cleared advisory emits a warning annotation so the list gets pruned and BUG-2567 closed when the backport lands. The accepted list carries 11 entries, measured against a build-faithful proxy (GOTOOLCHAIN=go1.26.5, CGO_ENABLED=0, ldflags "-s -w"): the 8 reachable stdlib advisories from BUG-2565, plus 3 module-level entries that only appear because -s -w strips the symbols govulncheck needs for call-graph precision — a symbol-precise scan of the same source shows all three uncalled. Claude-Session: https://claude.ai/code/session_01BhQoeaWXxJbvw86ezzK8dt * ci(nix): guard vulnscan against empty or non-binary govulncheck output Codex round 2: an exit-0 govulncheck run that produced empty, truncated, or garbled JSON — or silently ran in a mode other than binary — was indistinguishable from a clean scan. Assert the stream's config message reports scan_mode=binary and make both jq extractions fail closed (exit 2, operational error). Also sharpen the accepted-list comment on the three module-level entries: on the stripped artifact govulncheck reports them as affected with symbol frames (it cannot prune the call graph, so every vulnerable symbol of an imported package counts as potentially called); the round-2 reading of "degrades to module-level reporting" as functionless findings was wrong, verified against the actual JSON stream. Claude-Session: https://claude.ai/code/session_01BhQoeaWXxJbvw86ezzK8dt |
||
|
|
8af62d1c4e |
fix(ci): build with go1.26.6 to clear the govulncheck gate (BUG-2565) (#1093)
* fix(ci): build with go1.26.6 to clear the govulncheck gate (BUG-2565)
main has been red since
|
||
|
|
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 |
||
|
|
5320eebe9b |
Merge pull request #927 from PerpetualSoftware/dependabot/github_actions/actions/cache-6.1.0
chore(ci)(deps): bump actions/cache from 5.0.5 to 6.1.0 |
||
|
|
0cdd4620fa |
Merge pull request #982 from PerpetualSoftware/dependabot/github_actions/actions/setup-go-7.0.0
chore(ci)(deps): bump actions/setup-go from 6.5.0 to 7.0.0 |
||
|
|
08732f2653 |
Merge pull request #983 from PerpetualSoftware/dependabot/github_actions/actions/setup-node-7.0.0
chore(ci)(deps): bump actions/setup-node from 6.4.0 to 7.0.0 |
||
|
|
6be16e33db |
Merge pull request #1040 from PerpetualSoftware/dependabot/github_actions/actions/checkout-7.0.1
chore(ci)(deps): bump actions/checkout from 6.0.2 to 7.0.1 |
||
|
|
10c7b9a8b0 |
Merge pull request #1039 from PerpetualSoftware/dependabot/github_actions/actions-minor-and-patch-dee7dd5463
chore(ci)(deps): bump docker/login-action from 4.4.0 to 4.6.0 in the actions-minor-and-patch group across 1 directory |
||
|
|
2ab5b1033f |
chore(ci)(deps): bump actions/setup-go from 6.5.0 to 7.0.0
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.5.0 to 7.0.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/924ae3a1cded613372ab5595356fb5720e22ba16...b7ad1dad31e06c5925ef5d2fc7ad053ef454303e) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
71c18200c5 |
ci(nix): pin nix-installer-action to the actual v22 commit
The pinned SHA was a genuine ancestor commit from the upstream repo but ~68 commits behind the v22 tag it claimed; the workflow's own convention requires SHA and version comment to move together. Claude-Session: https://claude.ai/code/session_01RNcrc3CtXwJwreubtHTgN6 |
||
|
|
38e1042fee |
chore(ci)(deps): bump docker/login-action
Bumps the actions-minor-and-patch group with 1 update in the / directory: [docker/login-action](https://github.com/docker/login-action). Updates `docker/login-action` from 4.4.0 to 4.6.0 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/af1e73f918a031802d376d3c8bbc3fe56130a9b0...dbcb813823bdd20940b903addbd779551569679f) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 4.5.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions-minor-and-patch ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
1dafa37c1d |
chore(ci)(deps): bump actions/checkout from 6.0.2 to 7.0.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 7.0.1. - [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/de0fac2e4500dabe0009e67214ff5f5447ce83dd...3d3c42e5aac5ba805825da76410c181273ba90b1) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
02b302519e |
feat(nix): add flake packaging for pad with CI build
Adds a Nix flake exposing the pad binary as packages.default (buildGoModule + importNpmLock for the embedded SvelteKit UI), a devShell, and flake checks (package build with `go test ./...`, plus a `pad --version` smoke test). nix/package.nix is written nixpkgs-submission-ready (no flake-specific inputs) so it can later be adapted for pkgs/by-name. Also adds a GitHub Actions workflow that runs `nix flake check` and `nix build` on push/PR, and documents `nix run` / `nix profile install` / `nix develop` in the README. |
||
|
|
b5d0d5a028 |
chore(ci)(deps): bump actions/setup-node from 6.4.0 to 7.0.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.4.0 to 7.0.0. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e...820762786026740c76f36085b0efc47a31fe5020) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
0f69e7bbb8 |
chore(ci)(deps): bump actions/cache from 5.0.5 to 6.1.0
Bumps [actions/cache](https://github.com/actions/cache) from 5.0.5 to 6.1.0. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/27d5ce7f107fe9357f9df03efb73ab90386fccae...55cc8345863c7cc4c66a329aec7e433d2d1c52a9) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
f528190de4 |
chore(ci)(deps): bump sigstore/cosign-installer from 3.10.1 to 4.1.2 (#498)
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.10.1 to 4.1.2. - [Release notes](https://github.com/sigstore/cosign-installer/releases) - [Commits](https://github.com/sigstore/cosign-installer/compare/7e8b541eb2e61bf99390e1afd4be13a184e9ebc5...6f9f17788090df1f26f669e9d70d6ae9567deba6) --- updated-dependencies: - dependency-name: sigstore/cosign-installer dependency-version: 4.1.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> |
||
|
|
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> |
||
|
|
694497948b | ci: add Windows and macOS smoke tests (#911) | ||
|
|
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 |
||
|
|
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). |
||
|
|
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. |
||
|
|
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 |
||
|
|
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 |
||
|
|
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. |
||
|
|
5673522608 |
fix(release): force GORELEASER_CURRENT_TAG from github.ref_name
When two lightweight tags point at the same commit (v0.4.0 cut on top
of v0.4.0-rc.1 with no intervening commits, per PLAYB-1160), goreleaser's
git-describe-based auto-detection picked the wrong one on the CI runner
and stamped v0.4.0 artifacts with version 0.4.0-rc.1 — they then collided
with the existing RC release-page assets and the workflow aborted with
422 already_exists. Setting GORELEASER_CURRENT_TAG to ${{ github.ref_name }}
bypasses the auto-detection: it's exactly the tag that triggered the run.
Root-cause-fixes the v0.4.0 ship failure and prevents recurrence for any
future RC → stable sequence where the stable tag sits on top of the RC
without an intervening commit.
|
||
|
|
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. |
||
|
|
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.
|