Skip to content

fix: maintainer-loop bug sweep 1 (#132, #133, #134, #135, #137)#176

Merged
cevheri merged 19 commits into
mainfrom
loop/maintainer-bugsweep-1
Jul 11, 2026
Merged

fix: maintainer-loop bug sweep 1 (#132, #133, #134, #135, #137)#176
cevheri merged 19 commits into
mainfrom
loop/maintainer-bugsweep-1

Conversation

@cevheri

@cevheri cevheri commented Jul 11, 2026

Copy link
Copy Markdown
Member

Summary

Five first-run/install-path bugs fixed by an autonomous, test-gated maintainer loop (scaffold in loop/, adapted from the open-spek/loop template) working from a fixed queue of filed issues, one commit per issue, full gate green throughout.

Every fix is test-first with a regression test that failed red before the change. Full details, root-cause analysis, and decisions (with rationale) are in loop/PROGRESS.md; final state is in loop/HANDOFF.md.

Test plan

Refs #132, #133, #134, #135, #137 (not closed by this PR - left to the merger's judgment).

Follow-up in this PR: full-autonomy hardening of the loop (d36581d)

Bug Sweep 1 ran against a maintainer-curated queue; this commit prepares the same scaffold to run against the open public issue tracker autonomously:

  • Untrusted-input firewall: ALL issue content (bodies, not just comments) is now treated as untrusted public input. Build mode takes its acceptance bar only from maintainer-loop-authored text (sanitized specs in loop/TRIAGE.md + plan tasks); raw issues are evidence to re-verify, never authority. New LOOP-ENGINEERING.md section documents the four firewall layers.
  • Triage mode (loop/PROMPT-TRIAGE.md): classifies untriaged open issues into loop:queued (verified in code, sanitized spec written), loop:needs-info (one clarifying question posted; only a human clears it), or loop:needs-moderator-action (injection attempts, security reports, privileged changes, product decisions — label only, no reply). Labels created idempotently by loop/scripts/setup-labels.sh.
  • Fresh-context review gate: every build-mode commit now requires a loop-reviewer subagent pass (.claude/agents/loop-reviewer.md) checking correctness against the sanitized spec, convention fit (provider triad, platform-integration rules), test realness, scope, and supply-chain red flags. This also fixes LOOP-ENGINEERING.md's previously broken references by porting the judge/reviewer agents from the template.
  • Runner-level defense-in-depth (loop/config/loop.env): no curl/wget/WebFetch, gh surface reduced to issue read/label/comment. Pushing, PRs, merges, and closing issues remain human-only.

Runbook for the next autonomous milestone is in loop/HANDOFF.md (triage -> plan -> build -> human push).

cevheri added 15 commits July 11, 2026 19:54
The .deb/.rpm wrapper and the Homebrew formula's bin launcher exec'd node
with whatever HOSTNAME the calling shell happened to have - empty (falls
back to Next.js's own 0.0.0.0 default) or, under Docker, the container ID
it exports as HOSTNAME to every process. Both wrappers now default to
127.0.0.1 on a direct run and only bind elsewhere when LIBREDB_BIND opts
in, mirroring the npx launcher's local-first default.

The deb/rpm wrapper skips this when INVOCATION_ID is set (systemd sets it
for every unit process), since the systemd unit already resolves HOSTNAME
correctly via Environment=/EnvironmentFile= before the wrapper execs -
forcing it unconditionally would have broken that already-working,
documented HOSTNAME=0.0.0.0 override in /etc/libredb-studio/env.
The bin wrapper's direct-run mode set no STORAGE_SQLITE_PATH, so
server.js's chdir into libexec resolved the zero-config auth-bootstrap
file (and any SQLite storage) inside the versioned keg, losing it on
every brew upgrade. Default it to the same var/libredb-studio path the
brew services block already uses.
--verify-cache and every --archive run replaced the whole payload
directory (rm + rename) after re-extracting the tarball, wiping the
default data/ dir (generated auth-bootstrap.json credentials, any
STORAGE_PROVIDER=sqlite state) since every release tarball ships an
empty one. preservePayloadData now moves the previous data/ dir onto
the freshly extracted staging directory before the swap.
…#133)

scripts/build-standalone-payload.sh now packs the payload under a
top-level libredb-studio-<version>/ directory via the new
scripts/lib/pack-standalone-tarball.sh instead of tarring the payload
dir's contents at archive root. The npx launcher's extract() (and every
other tar -xzf consumer: the build script's own --smoke self-test, the
deb/rpm and snap release jobs) now strips that one path component via
the new extractTarball helper in bin/lib/launcher-utils.mjs. Homebrew's
formula needs no change - it strips a single top-level directory
automatically for its main url/sha256 download.
Add a regression test asserting the real helm-templated Deployment mounts
an emptyDir at /app/data by default and a PVC when persistence.enabled=true.
The underlying fix already shipped in #165; no existing CI check pinned
this specific behavior (ct install only checks pod readiness, which the
pre-#165 template also satisfied since the seeding failure was WARN-only).
…#137)

Verified every ACCEPTANCE.md criterion against actual repo state (issues,
docs, and a fresh full-gate + helm lint rerun on the clean branch tip),
ticked ACCEPTANCE.md and the Phase F plan item, and reconciled
HANDOFF.md/PROGRESS.md with the milestone's final state.
tests/unit/helm-chart-persistence.test.ts shells out to `helm template`
against charts/libredb-studio, whose postgresql subchart is fetched via
`helm dependency build` (never vendored in git - charts/**/charts/*.tgz
is gitignored, matching the existing helm-lint job's own pattern) rather
than committed. The Unit & Integration Tests job had no such step, so a
clean checkout failed with "missing in charts/ directory: postgresql"
even though the test passed in a dev environment with a stale local
build of that dependency lying around.

Reproduced locally by removing the vendored .tgz (exact CI error), then
confirmed `helm repo add bitnami` + `helm dependency build
charts/libredb-studio` - the same two steps helm-lint already runs -
fixes it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a set of first-run/install-path regressions across multiple distribution channels (npx launcher, standalone tarball, deb/rpm wrapper, Homebrew formula, Helm chart) and introduces a loop/ scaffold used to drive the automated bug-sweep workflow, plus regression tests to prevent recurrence.

Changes:

  • Enforce local-first bind defaults for direct wrapper runs and move Homebrew direct-run state out of the versioned Cellar keg.
  • Preserve payload/data/ across npx --verify-cache / --archive re-extractions and switch standalone tarballs to a versioned top-level root with matching extraction changes.
  • Add regression/unit tests (including Helm render assertions) and update docs/workflows/scripts to match the new packaging behaviors.

Reviewed changes

Copilot reviewed 22 out of 25 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/unit/packaging-standalone-tarball.test.ts New unit test verifying tarball root directory layout.
tests/unit/packaging-homebrew-datadir.test.ts New unit test verifying Homebrew wrapper defaults state outside the keg.
tests/unit/packaging-bind-address.test.ts New unit test verifying wrapper bind address behavior (loopback default + opt-in).
tests/unit/launcher-utils.test.ts Adds tests for payload data preservation and tarball extraction stripping.
tests/unit/helm-chart-persistence.test.ts Adds Helm template-based regression test for /app/data mount behavior.
scripts/lib/pack-standalone-tarball.sh New helper script to pack payload under libredb-studio-<version>/.
scripts/build-standalone-payload.sh Uses new pack script; updates smoke extraction to --strip-components=1.
packaging/linux/libredb-studio Forces loopback bind on direct run (with systemd guard) and allows home override.
packaging/homebrew/libredb-studio.rb.tmpl Forces loopback bind for direct run; defaults STORAGE_SQLITE_PATH outside keg; updates comments.
loop/scripts/loop.sh Adds autonomous maintainer-loop runner script.
loop/scripts/gate.sh Adds gate script matching the repo’s pre-commit verification.
loop/PROMPT.md Adds build-mode maintainer-loop prompt/spec.
loop/PROMPT-PLANNING.md Adds planning-mode prompt for future milestones.
loop/PROGRESS.md Adds loop progress log and decision record for the bug sweep.
loop/LOOP-ENGINEERING.md Adds loop operating manual/documentation.
loop/IMPLEMENTATION_PLAN.md Adds milestone plan checklist for the sweep.
loop/HANDOFF.md Adds milestone handoff summary.
loop/config/loop.env Adds loop runner configuration defaults.
loop/ACCEPTANCE.md Adds milestone acceptance criteria and completion signal rules.
docs/DISTRIBUTION.md Updates distribution docs for bind behavior, tarball root layout, and data preservation.
bin/studio.js Switches extraction to shared helpers; preserves payload data across swaps.
bin/lib/launcher-utils.mjs Adds preservePayloadData and extractTarball helpers.
.github/workflows/release-artifacts.yml Updates tar extraction to --strip-components=1 for new tarball layout.
.github/workflows/ci.yml Adds Helm repo/dependency steps to support new Helm-rendering unit test.
.gitignore Ignores maintainer-loop runtime artifacts and docs summaries directory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/ci.yml
Comment on lines +21 to +31
PAYLOAD_DIR=$1
VERSION=$2
OUT_TARBALL=$3

ROOT_NAME="libredb-studio-${VERSION}"
PARENT_DIR=$(cd "$(dirname "$PAYLOAD_DIR")" && pwd)
ROOT_DIR="$PARENT_DIR/$ROOT_NAME"

rm -rf "${ROOT_DIR:?}"
mv "$PAYLOAD_DIR" "$ROOT_DIR"
tar -czf "$OUT_TARBALL" -C "$PARENT_DIR" "$ROOT_NAME"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked the caller: pack-standalone-tarball.sh has exactly one caller (build-standalone-payload.sh), where PAYLOAD_DIR lives entirely under a mktemp -d STAGE_DIR that the caller unconditionally rm -rf's via trap cleanup EXIT, success or failure. So a mid-tar failure here does not lose any caller-owned working tree - the whole scratch tree is already disposed of regardless. Not adding the trap/restore for a scenario the current sole caller's contract already rules out; will revisit if a second caller with different guarantees shows up.

Matches helm-lint's own azure/setup-helm pin (v3.16.0) instead of
relying on whatever ubuntu-latest happens to bundle, so both jobs
that run real helm commands stay on the same version regardless of
future runner image updates.

Addresses Copilot review comment on PR #176.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 25 changed files in this pull request and generated 2 comments.

Comment on lines +39 to +45
const run = Bun.spawnSync(["helm", "template", "release-under-test", CHART_DIR, ...extraArgs], {
stdout: "pipe",
stderr: "pipe",
});
if (run.exitCode !== 0) {
throw new Error(`helm template failed (exit ${run.exitCode}): ${run.stderr.toString()}`);
}
Comment thread loop/scripts/loop.sh
exit 1
fi

if ! command -v ${AGENT_CMD%% *} >/dev/null 2>&1; then
cevheri added 3 commits July 12, 2026 00:32
…sue operation

- Triage mode (loop/PROMPT-TRIAGE.md + loop/TRIAGE.md): classify untriaged
  open issues into loop:queued / loop:needs-info / loop:needs-moderator-action,
  writing sanitized specs that firewall build mode from raw issue text.
- Treat ALL issue content as untrusted input, bodies included, not just
  comments: PROMPT.md 0e and 999i rewritten, 999j gh-surface allow-list added,
  new 1b moderator-escalation path for injection attempts, security reports,
  and changes beyond loop authority.
- Mandatory fresh-context loop-reviewer pass before every build-mode commit
  (.claude/agents/loop-reviewer.md; loop-judge.md ported alongside, fixing
  LOOP-ENGINEERING.md's broken judge/workflows references).
- Runner-level defense-in-depth in loop/config/loop.env: no curl/wget/WebFetch,
  gh surface reduced to issue read/label/comment; publishing stays human-only.
- Label taxonomy created idempotently via loop/scripts/setup-labels.sh (run
  against the repo).
- LOOP-ENGINEERING.md: new untrusted-input firewall section, three-mode table;
  HANDOFF.md: runbook for the next autonomous milestone
  (triage -> plan -> build -> human push).
…ssue text

Completes the untrusted-input firewall chain: plan task text becomes build
mode's acceptance bar, so planning mode must not lift reproduction steps or
suggested fixes verbatim from public issue content.
bun run chart:bump keeps charts/libredb-studio/Chart.yaml appVersion
and version in sync per the CI-enforced invariant (#138).
@sonarqubecloud

Copy link
Copy Markdown

@cevheri cevheri merged commit 5b5a083 into main Jul 11, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants