Skip to content

v10.0.0 - #811

Merged
wayneeseguin merged 50 commits into
developfrom
v10.0.0
Jul 28, 2026
Merged

v10.0.0#811
wayneeseguin merged 50 commits into
developfrom
v10.0.0

Conversation

@wayneeseguin

@wayneeseguin wayneeseguin commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Modernizes SHIELD for a v10.0.0 release: current Go and dependencies, a
CLI rebuilt on Cobra, AWS SDK v2 in the object-storage plugins, and
support for running the daemon against PostgreSQL and MySQL instead of
only SQLite.

Rebased onto current develop.

Reviewing this

The diff touches 2,604 files, but 2,404 of those are vendor/. There are
148 files of actual change. Reviewing with vendored dependencies
hidden is strongly recommended:

git diff develop...v10.0.0 -- . ':(exclude)vendor'

On the GitHub UI, append ?w=1 to ignore whitespace and use the file
filter to exclude vendor/.

What changed

Toolchain and dependencies

Go 1.25.12 to 1.26.1. Dropped the stale go-restful replace directive.
Retired jhunt/go-log, go-cli, go-querytron, go-s3, and
go-snapshot; go-querytron is inlined into the client package rather
than replaced. Also dropped sqlx, lib/pq, blazer, mapstructure,
yaml.v2, and yaml.v3.

Logging

jhunt/go-log replaced with a log/slog shim across 27 files.

CLI

The shield CLI is decomposed into per-topic Cobra command files, and
the five binaries plus the plugin framework move to Cobra. One follow-up
commit restores environment-variable support that the migration dropped.

Storage plugins

The S3 and Backblaze plugins are rewritten on AWS SDK v2, sharing a new
plugin/s3util package.

Database layer

sqlx is replaced with database/sql, and a dialect adapter lets the
daemon run against PostgreSQL and MySQL as well as SQLite.
cmd/shieldd and cmd/shield-schema register all three drivers.

Tests

Ginkgo v1 to v2, with suites bootstrapped for previously untested
packages.

Multi-database support

This is the most substantial functional addition, and the part most
worth close review. The schema and queries had only ever run against
SQLite, whose dynamic typing and permissive SQL hid a fair amount that
stricter servers reject outright:

  • Columns were declared UUID but hold arbitrary strings, including
    "", which PostgreSQL rejects on insert.

  • Several columns were declared BOOLEAN with an integer default, or
    INTEGER while carrying a Go bool. stores.last_test_task_uuid was
    declared BOOLEAN but holds a task UUID.

  • TEXT columns carried bare literal defaults, and were indexed without
    a prefix length; MySQL rejects both.

  • The recent_tasks CTE selected bare columns alongside GROUP BY,
    which only SQLite permits. It now ranks rows with ROW_NUMBER().
    This was also a latent correctness bug on SQLite, which returned an
    arbitrary row per job rather than the most recent one.

  • Filter builders emitted WHERE 1, which PostgreSQL rejects as
    non-boolean.

  • Task-log appends used ||, which MySQL reads as logical OR.

Because IsNoSuchTable compared driver error strings, it never matched
either server in practice. It tested for exact equality with
pq: relation "x" does not exist, but the registered driver is
pgx/v5/stdlib, which formats the same error as
ERROR: relation "x" does not exist (SQLSTATE 42P01); and it tested for
the prefix Error 1146: Table, while go-sql-driver emits
Error 1146 (42S02): Table. Empty-database detection therefore failed
on both servers. It now matches structured
driver errors via errors.As on *pgconn.PgError (SQLSTATE 42P01) and
*mysql.MySQLError (1146), falling back to a message match only for
go-sqlite3, whose generic error code carries no other signal.

Bugs fixed along the way

Three defects that predate this branch and are unrelated to portability:

  • MarkTasksIrrelevant had an unclosed parenthesis and referenced a
    jobs.keepdays column that does not exist. It fails on SQLite too;
    the failure went unnoticed because its only caller discards the error,
    so retention for clear = 'normal' tasks has never worked.

  • AnnotateTargetTask concatenated its updates directly onto WHERE
    with no separating space.

  • Session cookies were set SameSite=Strict. The OAuth providers
    (github, okta, uaa) return the browser to /auth/:provider/redir as a
    top-level cross-site navigation, on which Strict cookies are withheld
    — so the via cookie set moments earlier was never sent back, and
    shield login could not complete against any OAuth provider. Now
    Lax, which still withholds the cookie from cross-site POSTs and
    subresource requests.

The Secure cookie attribute is set only when the request arrived over
TLS, or when a terminating proxy reports an https scheme. shieldd
only ever calls ListenAndServe, so setting it unconditionally would
mean browsers silently discard the session cookie on any deployment not
fronted by a TLS terminator.

Testing

make test is unchanged and still requires nothing but SQLite; the
PostgreSQL and MySQL specs skip themselves unless a DSN is exported.

To exercise them against real servers:

make test-databases        # brings both up, runs ./db/
make test-databases-down

These run the real migrations and the real queries against a live
PostgreSQL 16 and MySQL 8. Everything in the multi-database section
above was found by running them, not by inspection — the unit specs
that existed asserted hand-written strings copied from the
implementation, so they stayed green while the feature was broken.

Known issues

  • TestAgent fails with exit status 127. This failure is present on
    develop and is not introduced here.

  • go vet reports unreachable code in plugin/fs/plugin.go and two
    self-assignments in db/tenant_test.go. Both predate this branch.

  • The vendored dependency churn is large and unavoidable given the SDK
    and toolchain upgrades.

@wayneeseguin

wayneeseguin commented Mar 30, 2026

Copy link
Copy Markdown
Contributor Author

Progress Update — 2026-03-30

YAML Library Migration: gopkg.in/yaml.v3goccy/go-yaml

Completed migration from gopkg.in/yaml.v3 to github.com/goccy/go-yaml across all SHIELD source files.

Commits:

  • a56814de — Fix format string vet errors in core
  • 482d75a7 — Add YAML serialization tests for core and CLI
  • ae1a9dfe — Migrate from yaml.v3 to goccy/go-yaml

What changed:

  • Swapped imports in 5 source files (core/core.go, core/duration.go, agent/config.go, cmd/shield/config.go, cmd/shield/cmd_misc.go)
  • Rewrote duration.UnmarshalYAML from *yaml.Node signature to goccy's callback signature
  • gopkg.in/yaml.v3 fully removed from go.mod and vendor
  • Added 15 new tests (12 Ginkgo specs for duration unmarshaling, 3 stdlib tests for config round-trips and import manifest parsing)
  • Fixed 7 pre-existing go vet format-string errors in core/main.go, core/auth.go, core/api_v2.go

Test results: All new tests pass. Existing agent config tests pass. 5 pre-existing agent SSH test failures (missing test binary) are unrelated.


Cumulative v10.0.0 Progress

Area Status
Go 1.26 upgrade Done
Dependency updates (x/, consul, oauth2, docker) Done
Cobra CLI decomposition Done
yaml.v2 → yaml.v3 migration Done
yaml.v3 → goccy/go-yaml migration Done
Ginkgo v1 → v2 upgrade Done
Multi-database dialect adapter (PostgreSQL + MySQL) Done
go vet clean build Done

@wayneeseguin
wayneeseguin marked this pull request as draft March 30, 2026 14:39
tristanpoland and others added 27 commits July 27, 2026 12:13
…ments to SHIELD auth flow (#794)

* Add upfront permission check to Postgres restore

Introduces a checkRestorePermissions function to verify that the user has sufficient privileges before starting a restore operation. This helps prevent restore failures due to inadequate permissions by checking for superuser status or database creation rights.

* Add option to skip Postgres permission check

Introduces a new 'pg_skip_permission_check' boolean field to allow skipping upfront permission validation during restore. This is useful for advanced users who understand the risks and want to bypass permission checks, with appropriate warnings in the help text. The PostgresConnectionInfo struct and related logic are updated to support this option.

* Fix permission check and add postgres.exe binary

Corrected the method call to BooleanValueDefault for 'pg_skip_permission_check' in plugin.go and made minor formatting improvements. Added the postgres.exe binary to the repository.

* Authenticate before establishing WebSocket connection

Added a preliminary authentication check via bearings API before attempting to connect to the WebSocket. Refactored code to fetch and process bearings data only once, improving efficiency by rejecting subscription if authentication fails.

* Improve WebSocket reconnection logic

Refactored WebSocket handling to add a dedicated _reconnect method that validates authentication before reconnecting. Bearings data is now only processed on initial connection, not on reconnection, preventing redundant state updates.

* Refactor bearings data handling on WebSocket connect

Improves logic for processing bearings data on initial connection versus reconnection. On initial connection, all bearings data is cleared and reloaded; on reconnection, only core authentication data and grants are updated. Also fixes passing of bearings data during reconnection.

* Delete postgres.exe

* Update data.js

* Update data.js

* Update data.js

* Create .env

* Add local compose, update build and revamp CSS

Add a local docker-compose stack and developer config, improve build flow, and modernize the UI styles. Files added: docker-compose.local.yml (local stack: vault/core/agent/webdav/demo) and .claude/settings.local.json; .gitignore updated to ignore /.vscode. Dockerfile: bump Go to 1.23 and run go mod tidy + go mod vendor before building. Makefile: add JOBS, more robust plugin build (fallback to module mode), safer demo target, and new demo-local / dev-local targets to run the local stack. Major rewrite of web/htdocs/shield.css to a modern dark/light theme using CSS variables, layout improvements, responsive tweaks and refreshed components.

* Add collapsible sidebar with icons

Introduce a collapsible story-sidebar: include FontAwesome solid icons, restructure sidebar nav items to show icons + labels, and add a toggle button in index.html. Add JS handlers in events.js to toggle collapse state and persist it to localStorage, and restore persisted state on startup in shield.js. Add CSS in shield.css for collapsed/expanded styles, transitions, toggle button, and adjustments to footer and pane widths to support the compact sidebar.

* Update top-bar UI and rename M1 to Apple Silicon

Change index.html labels from "MacOS (M1)" to "MacOS (Apple Silicon)". Major CSS updates to top-bar and account UX in shield.css: set title widths and truncation, add styled account button and dropdown/flyout (menu, header, divider, item states, current-tenant styling), adjust top-bar spacing, move sidebar down (top/height calc) and tweak sidebar-toggle position, and remove an unused selector. These changes improve account menu usability and layout spacing.

* Fix top-bar dropdown and toggle styles

Allow the top-bar dropdown to escape the bar and improve the sidebar toggle positioning and visuals. Removed overflow:hidden on table so dropdowns can escape, converted .top-bar .flyout from absolute to fixed (top:48px, min-width:260px) and adjusted its right positioning. Made the sidebar toggle fixed to the viewport and centered on the sidebar right edge / navbar bottom (computed left/top values), changed background to --bg-secondary, increased z-index to sit between sidebar and top-bar, refined transitions, added box-shadow, and consolidated collapsed-state selectors to use body.sidebar-collapsed. Also added a smooth icon rotation transition. These changes fix clipping issues and provide more stable, consistent positioning and styling for the toggle and flyout.

* Add theme toggle, top-nav and lock styling

Add a light/dark theme toggle and apply persisted theme before first paint to avoid a flash. Introduce a top-bar inline navigation rendered for authenticated users (remove duplicated nav markup), and add a theme-toggle button with JS to toggle data-theme and store the choice in a cookie. Update CSS to style the new top-nav, compact the LOCKED banner into a compact, responsive inline warning, adjust sidebar-toggle positioning, and refine related layout/spacing for header/account link. Changes touch index.html, js/events.js (theme toggle handler + init script), and shield.css (navigation, theme toggle, locked state and layout tweaks).

* Add HUD background image and overlay

Add bg.jpg asset and update shield.css to use it as the .hud background (cover, centered, no-repeat). Add a semi-opaque overlay (::before) to preserve card readability with a light-theme variant, and ensure .hud children are positioned above the overlay via relative positioning and z-index.
New internal/log/ package provides a thin slog-based
shim preserving go-log's function signatures (Infof,
Debugf, Errorf, Warnf, Warningf, Alertf, IsDebug)
so 363 call sites only require import path changes.

New plugin/s3util/ package extracts shared S3 client
construction (NewClient), backup path generation
(GenBackupPath), and byte-counting reader
(CountingReader) for use by both S3 and Backblaze
storage plugins.
Remove jmoiron/sqlx dependency from the database
layer. SQLite uses ? placeholders natively so the
sqlx.Rebind() wrapper was a no-op. Change connection
field type from *sqlx.DB to *sql.DB and Open call
from sqlx.Open to sql.Open.
Replace external go-querytron dependency with a local
generateQueryString() helper in client/v2/shield/qs.go.
Uses reflection over qs struct tags to build url.Values,
handling string, int, bool (with true/false value
mapping), *bool, and *int field types. Update 10
consumer files to call the local function.
Replace github.com/jhunt/go-log import with
github.com/shieldproject/shield/internal/log in all
agent, core, db, and route packages. The slog shim
preserves identical function signatures so no call
site changes are needed.
Replace jhunt/go-s3 with AWS SDK v2 via the shared
s3util package. Store uses PutObject with a
CountingReader for accurate size tracking. Retrieve
uses GetObject with io.Copy. Purge uses DeleteObject.

Fix path-style detection to check for
PermanentRedirect instead of substring "301" match.
Fix default endpoint typo (amazonawd → amazonaws).
Replace kurin/blazer with AWS SDK v2 S3-compatible
API via the shared s3util package. Auto-detect bucket
region using B2 native API (b2_authorize_account +
s3ApiUrl hostname parsing) to avoid config changes.

Fix three bugs: hardcoded Store() size of 1024 bytes
now uses CountingReader for actual size, unchecked
io.Copy error in Retrieve() now propagated, and
response status checked before JSON decode in region
detection.
Replace go-github v0 with v66 using WithAuthToken
pattern instead of oauth2 HTTP client wrapper. Fix
silently broken team membership listing by using
Teams.ListUserTeams (moved from Organizations service
in v28+). Add nil guards for org/team pointer fields.

Modernize OAuth2 token exchange in GitHub auth
provider to use oauth2.Config.Exchange() instead of
manual HTTP POST. Replace deprecated io/ioutil.
Replace go-cli struct-tag parsing in plugin.Run()
with Cobra root command and 7 subcommands (info,
validate, backup, restore, store, retrieve, purge).
Persistent flags handle --debug, --endpoint, --key,
and --text. Cobra manages --help automatically.

Preserve Plugin interface, PluginInfo, Field,
ShieldEndpoint, dispatch(), codeForError(), and all
error types unchanged so 24 plugin binaries require
no modifications.
Replace go-cli/go-envirotron with Cobra commands in
shieldd, shield-agent, shield-schema, shield-crypt,
and shield-report. Environment variable overrides
handled via os.Getenv in PersistentPreRunE. Log setup
calls updated to use new slog shim. All flags and
behavior preserved.
Split 3508-line main.go into root.go and 14 command
group files (cmd_auth, cmd_admin, cmd_tenants,
cmd_targets, cmd_stores, cmd_jobs, cmd_archives,
cmd_tasks, cmd_users, cmd_sessions, cmd_agents,
cmd_fixups, cmd_misc, cmd_op). Each command registers
via init() on the root Cobra command.

Delete help.go (2062-line help dispatcher) and
help.pl (code generator) — Cobra handles help text
via Long field on each command. Remove help.go
generation target from Makefile.
Add: spf13/cobra, aws-sdk-go-v2 (config, credentials,
s3, feature/s3/manager), google/go-github/v66.

Remove: jhunt/go-cli, jhunt/go-log, jhunt/go-s3,
jhunt/go-querytron, jhunt/go-snapshot, jmoiron/sqlx,
kurin/blazer, google/go-github (v0).

Bump Go version from 1.23.0 to 1.24 as required by
aws-sdk-go-v2.
Advance go directive from 1.24 to 1.26 and toolchain
from go1.24.2 to go1.26.1 to enable latest dependency
versions for the v10.0.0 release.
x/crypto v0.36.0 → v0.49.0
x/oauth2 v0.30.0 → v0.36.0
x/net    v0.38.0 → v0.52.0
x/sys    v0.31.0 → v0.42.0 (indirect)
x/term   v0.30.0 → v0.41.0 (indirect)
x/text   v0.23.0 → v0.35.0 (indirect)
google.golang.org/api v0.126.0 → v0.273.0

This eliminates the archived go.opencensus.io dependency
which has been replaced internally by OpenTelemetry.
Also removes google.golang.org/appengine and
golang/groupcache as transitive dependencies.
Also drops archived google/btree and mitchellh/mapstructure
as transitive dependencies since newer consul/api uses
go-viper/mapstructure/v2 (the maintained fork).

Only mitchellh/go-homedir remains as an archived indirect
dep, still required by hashicorp/go-rootcerts.
go-dockerclient  v1.12.1  → v1.13.0
go-sql-driver    v1.5.0   → v1.9.3
gorilla/websocket v1.4.2  → v1.5.3
go-sqlite3       v1.14.15 → v1.14.37
prometheus       v1.19.1  → v1.23.2
etcd/client/v3   v3.5.9   → v3.6.9
gomega           v1.24.2  → v1.39.1
pborman/uuid     v1.2.0   → v1.2.1

Also updates numerous transitive dependencies including
docker, klauspost/compress, miekg/dns, and others.
The emicklei/go-restful/v3 replace directive was a
leftover with no corresponding require entry. Removed
during final dependency cleanup.
yaml.v3 returns map[string]interface{} for YAML maps
instead of map[interface{}]interface{}, eliminating the
need for the StringifyKeys conversion utility. The
UnmarshalYAML interface also changed from a callback
to a *yaml.Node receiver.

Changes:
- Swap gopkg.in/yaml.v2 imports to gopkg.in/yaml.v3
- Rewrite duration.UnmarshalYAML for yaml.Node API
- Update AuthProvider.Configure param type
- Remove StringifyKeys from util and all callers
- Update go.mod and vendor
Add suite bootstrap files for route, core/scheduler,
and core/bus packages to enable BDD testing with
Ginkgo v1 + Gomega.
SetCookie and ClearCookie were missing security flags,
leaving sessions vulnerable to XSS and CSRF attacks.
The retry condition used || causing success (rc=0) to
loop forever. Changed to && with rc!=0 so retries stop
on success. Also made rc atomic for goroutine safety.
CreateRestoreTask now rejects cross-tenant and cross-
plugin restores. Also fix API handler tenant check to
use target.TenantUUID instead of archive.TenantUUID.
Worker.available was a plain bool read and written from
multiple goroutines without synchronization. Changed to
atomic.Bool with sync.Mutex guarding the task field.
Verify client connection drop, metrics tracking, and
slot recovery when the message bus backlog is full.
Test target deletion with orphaned archives and agent
re-registration with address change impact on lookups.
Migrate test framework from Ginkgo v1.16.5 to v2.28.1
and Gomega from v1.24.2 to v1.39.1 across all 24 test
files in 7 packages.

- Update import paths to github.com/onsi/ginkgo/v2
- Convert 17 async Done patterns in db/bus_test.go
  to Eventually/Receive with preserved timeouts
- Replace deprecated ioutil with os/io equivalents
- Update Makefile race target for v2 CLI syntax
- Rebuild vendor with pruned v1-only dependencies
Introduce db/dialect.go with Dialect type,
DetectDialect(), Rebind(), and IsNoSuchTable()
to handle placeholder translation across
PostgreSQL ($1), MySQL (?), and SQLite3 (?)
without the sqlx dependency.
Wire Rebind() into statement() so queries
using ? placeholders translate to $1/$2 for
PostgreSQL. Change Connect(file) to
Connect(driver, dsn) with eager Ping().
Fix BEGIN TRANSACTION to BEGIN for MySQL,
IFNULL to COALESCE for ANSI SQL, and
consolidate table-missing checks via
IsNoSuchTable().
Add Database.Driver/DSN config fields with
sqlite3 defaults for backward compatibility.
Import pgx/v5/stdlib and go-sql-driver/mysql
in shieldd and shield-schema binaries. Add
--driver flag to shield-schema CLI.
Correct mismatched format verbs and missing arguments
in log.Errorf/Infof/Warnf calls across core package
to pass go vet cleanly.
Add Ginkgo test suite for core package with 12 tests
covering duration unmarshaling (string formats, bare
integers, fractional values, nested structs). Add 3
stdlib tests for CLI config round-trips, legacy config
parsing, and import manifest with mixed-type maps.
Replace gopkg.in/yaml.v3 with github.com/goccy/go-yaml
across all 5 source files. Rewrite duration.UnmarshalYAML
from *yaml.Node signature to callback signature. Update
go.mod and vendor; yaml.v3 fully removed.
SHIELD_CORE_MASTER, SHIELD_CORE_USERNAME, SHIELD_CORE_PASSWORD,
and SHIELD_CORE_TOKEN env vars stopped working after the cobra
migration because only global flags got os.Getenv fallbacks in
PersistentPreRunE. Add inline env var checks for command-local
flags in init, unlock, rekey, and login commands, matching the
precedence order: CLI flag > env var > interactive prompt.
Update Dockerfile to Go 1.26.1, Ubuntu noble, and
embed Vault 1.21.4 in the core container matching
phalanx's proven architecture. Update nginx images
from bullseye to stable-bookworm. Add healthchecks,
proper depends_on conditions, and init scripts that
start Vault, wait for readiness, run schema
migrations, then launch shieldd with config file.
Add missing event.preventDefault() on the decide
template form handlers so clicking Set Up navigates
to the password form instead of reloading the page.
Add $form.reset() before validation and CSS rule to
hide error spans by default in the init form, which
uses .ctl containers not covered by the .field rule.
Remove erroneous .Elem() call when reading string
field values in the query string generator. String
kinds are not pointers and do not need dereferencing.
Delete all existing keys before restoring from backup
so that keys created after the backup was taken do
not persist through a restore operation.
Buffer stdin to a seekable temp file before upload
since PutObject requires a seekable body for payload
hash computation. Disable automatic checksum
calculation for non-TLS endpoints to avoid trailing
checksum failures when streaming.
Develop advanced past this branch with newer versions of
several shared dependencies. Restore the newer versions
and regenerate the vendor tree:

  go-github     v66.0.0  -> v76.0.0
  x/crypto      v0.49.0  -> v0.52.0
  x/net         v0.52.0  -> v0.54.0
  x/sys         v0.42.0  -> v0.45.0
  x/text        v0.35.0  -> v0.37.0
  go-sqlite3    v1.14.37 -> v1.14.42
  edwards25519  v1.1.0   -> v1.2.0
Three Errorf calls had %s verbs with no matching
argument, emitting %!s(MISSING) instead of the request
or task identifier.

These predate this branch, but the slog shim is a
recognized printf wrapper, so vet now sees them and
fails the build of the db and route test packages.
Strict withholds a cookie on top-level cross-site navigation, which
is exactly how an oauth2 provider returns the browser to
/auth/:provider/redir. Both the "via" cookie and any existing
session cookie were therefore absent by the time the redirect
handler ran, so `shield login` against github, okta, or uaa could
never take the cli branch.

Lax still withholds the cookie from cross-site POSTs and subresource
requests, which is where the CSRF risk lives.
The string comparisons never matched. pgx renders errors as
"ERROR: ... (SQLSTATE 42P01)", nothing like the lib/pq form that was
being compared against, and go-sql-driver emits "Error 1146 (42S02):"
whenever the server supplies a SQLSTATE, which every supported MySQL
does. So a fresh PostgreSQL or MySQL database was never recognized as
empty, and SchemaVersion() returned the error instead of 0, leaving
the core unable to deploy its schema.

Match the structured values the drivers return instead, and compare
the table name by identifier token so that a message about "jobs" is
not read as one about "job".

The specs asserted against hand-written strings copied from the
implementation, so they passed throughout; they now use the drivers'
own error types.
The migrations only ever ran against SQLite, which ignores declared
column types, so several of them were wrong in ways nothing surfaced:

  - UUID columns hold arbitrary strings, including "", which
    PostgreSQL's uuid type rejects and MySQL has no equivalent for.
    They are VARCHAR(36) now.

  - stores.last_test_task_uuid holds a task uuid but was declared
    BOOLEAN; jobs.fixed_key, tasks.fixed_key, tasks.ok, and
    tasks.relevant hold Go bools but were declared INTEGER.

  - BOOLEAN columns defaulted to 0 and 1, which PostgreSQL will not
    coerce, as did the INSERT ... SELECT that rebuilds the jobs and
    targets tables.

  - MySQL takes a default on a TEXT column only as an expression, and
    cannot index one at all without a prefix length, so users.account
    and agents.address are VARCHAR(255).

SQLite stores these values exactly as before.
  - The recent_tasks CTE selected bare columns alongside a GROUP BY,
    which only SQLite permits, and even there it yields an arbitrary
    row rather than the newest one. Rank the rows so every backend
    agrees which task is recent.

  - An empty filter produced "WHERE 1"; PostgreSQL wants a boolean.

  - MySQL reads || as logical OR, so appending to a task log needs
    CONCAT, but PostgreSQL cannot infer the type of a placeholder
    passed to it. Spell the concatenation per dialect, and COALESCE
    the NULL log a task starts life with, which both forms otherwise
    propagate.

  - updateTaskStatus passed 0 and 1 for a column the rest of the code
    reads as a bool.

Two statements were also plainly broken and had gone unnoticed
because their only caller discards the error: the subquery in
MarkTasksIrrelevant was missing its closing paren and named a
jobs.keepdays column that does not exist, and AnnotateTargetTask ran
its updates together with the WHERE clause. The subquery reads from
jobs via EXISTS now, since MySQL will not read the table an UPDATE
targets from a subquery's FROM clause.
Unit tests over hand-written error strings and DDL cannot tell us
whether a server accepts any of it, which is how the multi-database
support came to be broken on both new backends while its specs
passed.

These run the real migrations and the real queries against live
servers. They skip themselves unless a DSN is exported, so `make
test` still needs nothing but SQLite; `make test-databases` brings
both servers up and runs them.
These files drifted out of gofmt while the logging calls
were migrated to slog.  No functional change; `gofmt -l`
now reports nothing outside of vendor/.
.vscode was already listed in .gitignore but its contents
had been committed anyway; .claude, .env, and .DS_Store
were never listed.  Nothing reads .env -- the compose file
hardcodes its VERSION and make takes it from the calling
environment -- and it still claimed 9.0.0.
shieldd itself only ever calls ListenAndServe, so a browser
talking to it directly gets its session cookie over plain
HTTP -- and discards it on the spot when it is marked
Secure, locking the user out of the web UI entirely.

Set the flag when the request arrived over TLS, or when a
terminating proxy in front of the daemon reports an https
scheme.  Trusting that header cannot weaken anything: it
only ever adds the flag, so a client forging it merely
throws away its own cookie.
@wayneeseguin
wayneeseguin marked this pull request as ready for review July 27, 2026 17:47
@wayneeseguin

Copy link
Copy Markdown
Contributor Author

This is complete and ready to merge.

Worth saying plainly: despite the branch name, none of this is gated on a
v10.0.0 release. It is debt cleanup and quality work — current toolchain
and dependencies, retired abandoned libraries, a CLI on a maintained
framework, AWS SDK v2, and a database layer that works on more than one
database. It stands on its own merits and can land whenever it suits the
release plan, under whatever version number that turns out to be.

Verification

  • go build ./... clean.

  • gofmt -l reports nothing outside vendor/.

  • go vet clean apart from two findings that predate this branch
    (unreachable code in plugin/fs/plugin.go, two self-assignments in
    db/tenant_test.go).

  • 16 of 17 test-bearing packages pass. The one failure, TestAgent
    (exit status 127), fails identically on develop and is not
    introduced here.

  • The db package passes against SQLite, PostgreSQL 16, and MySQL 8 —
    real servers running the real migrations and the real queries, via
    make test-databases.

  • Every commit was checked out individually and confirmed to build and
    test green, so the history is bisectable rather than only green at the
    tip.

A note on the multi-database work

The portability defects listed in the description were found by running
against live servers, not by reading the code. The unit specs that
existed asserted hand-written strings copied from the implementation, so
they passed while the feature did not work at all — missing-table
detection never matched either driver, and the migrations did not survive
the first CREATE TABLE on PostgreSQL. That is the reason the new specs
exercise real servers, and the reason make test deliberately still
requires nothing but SQLite: contributors keep a fast default, while the
claim of multi-database support has something behind it.

Three unrelated pre-existing bugs surfaced along the way and are fixed
here — see the description. The MarkTasksIrrelevant one is worth a
reviewer's attention: it means task retention for clear = 'normal' has
never actually run.

Suggested review approach

Exclude vendor/ — 2,404 of the 2,604 changed files are vendored, and
the remaining 148 are the real diff:

git diff develop...v10.0.0 -- . ':(exclude)vendor'

@wayneeseguin
wayneeseguin requested review from Copilot and krutten July 27, 2026 17:49

This comment was marked as low quality.

@wayneeseguin
wayneeseguin merged commit 3274249 into develop Jul 28, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants