Commit Graph
2154 Commits
Author SHA1 Message Date
Juan DenisandClaude Opus 5 550dd47585 fix(ratchets): regenerate the boundary baseline and the migration inventory
Two more shards were red on generated artifacts nobody regenerated:

- api_controller_boundary_baseline.json: the attachment round rewrote
  _persist_user_message to build the row before adding it, so the fingerprint
  moved from `db.session.add(AiMessage(...))` to `db.session.add(message)`.
  Same crossing, same category, count unchanged at 509 -- one line.

- docs/MIGRATION_INVENTORY.md: three measured numbers had moved without a
  regeneration -- bare @jwt_required() routes 577 -> 576, and the two frontend
  rows whose totals come from maps inside check-frontend-boundaries.mjs
  (setInterval pollers 10 -> 9, hex colour literals 151 -> 150). All three are
  deterministic AST/constant counts, so these are CI's numbers, not this box's.

The reason nobody caught the inventory locally is the third fix here: the
generator hardcoded `node`, and under WSL the Windows nvm directory is on PATH
but exposes only `node.exe`, so test_migration_inventory.py skipped on the dev
box. Both the generator and the test now resolve $NODE / node / node.exe, and
the test runs instead of skipping.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 20:14:34 -04:00
Juan DenisandClaude Opus 5 1c659824c1 fix(tests): the create_app guard must read code, not prose
The plan-77 G3 guard grepped raw file text for `create_app(`, so it counted
mentions. test_disk_reclaim.py failed CI for a docstring reading "Stand-in for
create_app() -- the disk command only needs a context": that file monkeypatches
the factory away and never boots anything. The same false positive had already
put test_api_error_shape.py into the baseline, for a comment.

Detection is now AST-based -- a real call, direct or attributed. String
literals still count, because test_ai_lazy_import.py boots its probe app from an
embedded script; docstrings and comments never boot anything, and an
unparseable file falls back to the old blunt check rather than passing free.

Baseline drops test_api_error_shape.py (it uses the shared `app` fixture) and
gains nothing. test_boot_detection_reads_code_not_prose proves the scanner
against both false positives and all three true positives, so the next docstring
does not turn CI red. BASELINE_COUNT +1 for it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 20:14:34 -04:00
Juan DenisandClaude Opus 5 009d9c4d7f fix(ai): raise a typed error for a malformed attachment manifest
The attachment round added two hand-shaped `jsonify({'error': str(exc)}), 400`
returns in api/ai.py, which pushed the error-shape census to 1146 against a
ceiling of 1144 and turned shard 2 red. The census policy is not "raise the
ceiling" -- new endpoints raise, and these are new.

AttachmentValidationError now subclasses app.exceptions.ValidationError, so the
global handler owns the 400 body, the `code`, and the X-Request-ID correlation.
It is still a ValueError (ValidationError subclasses one), so every existing
`except ValueError` caller and the pytest.raises assertions in
test_ai_attachments.py are unchanged, and the two try/except blocks in the
routes disappear entirely.

Census back to 1144 = ceiling. Verified: test_ai_attachments, test_ai_assistant,
test_error_shape_ratchet all green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 20:14:34 -04:00
github-actions[bot] eb6be3d863 chore: bump version to 1.7.129 [skip ci] 2026-08-21 23:42:15 +00:00
Juan DenisandClaude Opus 5 2ad6720bd0 fix(ci): make the test-count ratchet count what a clean checkout collects
The floor was --update'd from a dev box again, so CI collected 4516 against
a floor of 4530 and the ratchet went red for a suite that lost nothing.
Same 14 cases as last time (4431 -> 4417): test_builtin_extension_drift.py
parametrises over the on-disk intersection of builtin-extensions/<ext>/backend
and app/plugins/<ext>, and four live copies here are gitignored --
cloud-provision (3), ftp (3), remote-access (5), status (3).

Fixing the number alone would queue up a third occurrence, so the
machine-dependence is closed instead: check_test_count.py now collects with
SERVERKIT_CLEAN_COLLECT=1, and under that flag the drift test pairs only
git-TRACKED live copies. Ordinary runs are untouched and still compare the
gitignored copies on the machine that has them.

Verified with the WSL venv: the ratchet now reports 4518 locally, exactly
CI's 4516 plus the two tests added here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 19:40:42 -04:00
github-actions[bot] 4c10601703 chore: bump version to 1.7.128 [skip ci] 2026-08-21 23:31:53 +00:00
Juan Denis 19a7b59bd1 Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-08-21 19:31:35 -04:00
Juan DenisandClaude Opus 5 507a467d98 fix(scss): use the amber token for the AI attachment warning state
check-frontend-boundaries failed on dev: _ai-assistant.scss carried 11
colour literals against a legacy baseline of 10.

The extra one came in with the attachment composer as
`var(--warning, <amber literal>)`. `--warning` is not a defined token
anywhere in the tree, so that declaration always fell through to the
literal -- a hardcoded amber that runtime skins could never recolour,
which is precisely what the boundary check exists to catch. (_doctor.scss
reaches for the same undefined token with a *different* fallback, so the
two rendered slightly different ambers.)

$warning resolves to var(--amber, …), the real token. The adjacent line
already uses $warning-raw, so the variables are in scope; $warning itself
must not be fed to fade() since it expands to a var() rather than a
compile-time colour.

File back to 10 literals, matching the baseline without moving it.
Verified: boundary check, theme-token check, npm run lint and npm run
build all pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 19:30:57 -04:00
Juan DenisandClaude Opus 5 406d7c3ff1 chore(tests): raise the test-count floor to 4530
The ratchet only fails on a DROP, so a floor left un-bumped never
complains -- it just quietly stops protecting anything. At 4417 against
4530 collected, 113 tests could have been deleted with CI still green.

113 new tests since the floor was last set: ~35 from the disk-space work
(test_disk_reclaim.py plus the disk --json case) and the rest from the
branch merged into dev.

Verified trustworthy before committing: `pytest tests --collect-only`
(what backend-ci.yml runs) and the bare `pytest` that check_test_count.py
uses both report 4530 -- backend/dev-data/ errors out of collection
rather than adding to it, so the two agree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 19:26:47 -04:00
github-actions[bot] 63c51a3158 chore: bump version to 1.7.127 [skip ci] 2026-08-21 23:25:49 +00:00
Juan Denis 633efc250b Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-08-21 19:25:25 -04:00
Juan DenisandClaude Opus 5 b006f2e4da refactor(disk): route the reclaim sweep through run_checked
The raw subprocess census caught disk_reclaim_service as a 25th call
site against a ceiling of 24. The ceiling only goes down, and this is a
case where the shared door is genuinely the better call rather than a
box to tick.

_run's own `except OSError` turned a missing binary into an empty
result, which is exactly the sbin-PATH failure mode: a unit whose $PATH
omits sbin cannot exec a bare `journalctl`, and this sweep would have
reported "nothing to reclaim" on a host that had plenty to reclaim --
a false fact about the operator's disk rather than a visible error.

run_checked resolves argv[0] the way privileged_cmd does, and separates
"never ran" (returncode None) from "answered no". _run keeps its
(ok, stdout, stderr) shape, so callers and tests are unchanged; a
missing binary still degrades to a zero-sized candidate instead of
aborting the scan, but now the reason reaches stderr.

Census back to 24/24; all six ratchets pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 19:23:29 -04:00
github-actions[bot] 9dd1550ff1 chore: bump version to 1.7.126 [skip ci] 2026-08-21 23:19:17 +00:00
Juan DenisandClaude Opus 5 9e84923ef8 Merge fix/consolidate-error-state into dev
Brings the disk-space work (serverkit disk, telemetry retention, updater
hardening) plus the AI attachment composer, operations run consolidation
and remote-access pickers already on the branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 19:15:42 -04:00
Juan DenisandClaude Opus 5 6d2f71d789 fix(update): stop the updater from being able to fill the disk
A backup retention cap already existed (SERVERKIT_BACKUP_RETENTION,
default 5), yet a box was found holding six snapshots and 0 bytes free.
Three separate defects, each sufficient on its own:

1. cleanup() is the LAST phase of the update, so retention never ran on
   a run that died earlier -- including the run that died BECAUSE the
   disk was full. Retention that only executes on success cannot bound a
   failure loop. backup_current now trims BEFORE writing, which also
   makes peak usage `keep` snapshots rather than `keep + 1`.

2. The preflight required a flat 2 GiB, but the update is about to write
   two full copies of the database. With an 800 MB database a box at
   2.1 GiB free passed the check and then filled mid-update. The
   requirement is now 2 GiB + 2x the database, it trims stale snapshots
   and re-checks before giving up, and the halt message points at
   `serverkit disk`.

3. A count cap is not a disk guarantee: five snapshots of an 800 MB
   database is 8 GB, a third of a 25 GB droplet. The default drops to 3
   and enforce_backup_budget caps backups at a share of the filesystem
   (SERVERKIT_BACKUP_MAX_PERCENT, default 15), dropping oldest-first and
   always keeping at least one restore point -- an update with no way
   back is worse than a full disk.

BACKUP_DIR also becomes SERVERKIT_BACKUP_DIR-overridable so it can agree
with the reclaim service, which scans the same directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 19:12:42 -04:00
Juan DenisandClaude Opus 5 46fdf5fc13 feat(jobs): bound telemetry growth with a retention handler
`jobs` has had retention since day one, but the three tables that grow
alongside it never did. `queue_messages` and `system_events` gain a row
per scheduler tick and `api_usage_logs` one per request; measured on a
single-app box that is ~25k rows/day, or ~11 MB/day of database forever.

That is what filled a 25 GB host from nothing but routine updates: the
database reached 809 MB in two months, and every update copies it twice
as its safety net (a pre-upgrade snapshot plus a tree backup containing
it), so each update cost ~1.6 GB.

Adds builtin.telemetry_retention on a 6h tick, keeping
`telemetry.retention_days` (default 30); 0 disables it. It reuses the
prune from disk_reclaim_service, so the same guards apply -- terminal
rows only, and a queue message still referenced by a surviving job is
kept.

It deliberately does NOT vacuum. VACUUM needs an exclusive lock and free
space equal to the whole database, which is never acceptable on a
background tick. Steady-state pruning leaves the freed pages on SQLite's
freelist for reuse, so the file stops growing; `serverkit disk` remains
what actually shrinks it after a backlog.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 19:12:27 -04:00
Juan DenisandClaude Opus 5 e10ecc87d0 feat(cli): add serverkit disk to report and reclaim disk space
A panel host filled to 100% (0 bytes free) purely from routine
`serverkit update` runs, and recovering it meant hand-running du and
sqlite. `serverkit disk` measures every reclaimable item and offers them
for selection: numbers, "safe", or "all".

Upgrade snapshots are grouped by timestamp and offered individually with
age and size, since one update writes both a tree backup and a database
copy under one stamp -- half a snapshot is not a restore point. They can
be picked by number, by age (--older-than N, or "older-than N" in the
prompt), or left to the "all-but-newest" default.

Two ordering rules are enforced regardless of selection order:

* filesystem reclaimers run BEFORE the database VACUUM. VACUUM rewrites
  the database into a temp file beside it, so it needs free space equal
  to the database -- on a full disk it is the one step that cannot go
  first. It refuses with a clear message rather than failing halfway.
* `jobs` rows are deleted before `queue_messages`, and any message still
  referenced by a surviving job is kept, so jobs.queue_message_id never
  dangles.

in_flight messages and pending/failed jobs are never touched. /tmp is
only swept for entries matching mktemp naming, owned by the invoking
user, over 24h old AND holding an unpacked ServerKit tree. Docker prunes
dangling images and build cache only -- never tagged images or volumes.

Sizes are measured rather than estimated wherever measurement is
possible: docker reports its own reclaimed tally (shared layers make
dangling-image sizes overlap), and the journal and package caches report
before/after deltas -- apt-get clean empties the cache before a naive
count could see it, which would otherwise report 0 freed for real work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 19:12:12 -04:00
Juan Denis 885df406b2 feat(ai): add resource attachment composer 2026-08-21 10:59:01 -04:00
Juan Denis 3313bd842f feat(ai): persist authorized attachment manifests 2026-08-21 10:53:56 -04:00
Juan Denis 17a480aba0 feat(ai): add attachment resolver registry 2026-08-21 10:48:28 -04:00
Juan Denis 0cb26d6739 feat(operations): adopt core run workflows 2026-08-21 10:40:42 -04:00
Juan Denis 3f85945cd2 feat(operations): expose authoritative run actions 2026-08-21 10:34:52 -04:00
Juan Denis bba2019720 feat(operations): absorb service log sessions 2026-08-21 10:31:40 -04:00
Juan Denis 5db3595fe3 feat(operations): replace deploy pill with dock 2026-08-21 10:24:29 -04:00
Juan Denis 5a29dd5a6d feat(operations): add shell state provider 2026-08-21 10:15:23 -04:00
Juan Denis ee58075fae feat(operations): add generalized run stream 2026-08-21 10:09:02 -04:00
Juan Denis 0062a1a9f5 feat(remote-access): add paired resource pickers 2026-08-21 10:02:21 -04:00
Juan Denis 86c2ce5530 feat(templates): add capability-aware server picker 2026-08-21 09:58:52 -04:00
Juan Denis 988cb659d0 feat(new-service): add scoped resource selection 2026-08-21 09:54:56 -04:00
Juan Denis 652d1ab409 feat(frontend): adopt resource picker for targets 2026-08-21 09:51:50 -04:00
Juan Denis 29a95236fd feat(dashboard): add transactional editing session 2026-08-21 09:46:06 -04:00
Juan Denis 08315c99d7 feat(frontend): add editing controls and shortcut scope 2026-08-21 09:36:02 -04:00
Juan Denis c823c8dc15 feat(frontend): add editing session history 2026-08-21 09:28:20 -04:00
Juan Denis cfcc49adc2 feat(frontend): add resource options adapter 2026-08-21 09:24:29 -04:00
Juan Denis ee152eb1bd feat(search): add resource picker contract 2026-08-21 09:20:54 -04:00
Juan Denis 4959354cfe refactor: consolidate error state component [skip version]
Preserve ErrorState SDK compatibility and accessibility while using one canonical implementation.
2026-08-21 01:35:51 -04:00
Juan Denis 5fb29f14ef fix: preserve ErrorState SDK compatibility [skip version] 2026-08-21 01:35:07 -04:00
Juan Denis f1c7d1f44e refactor: consolidate error state component [skip version] 2026-08-21 01:30:58 -04:00
Juan Denis 476b6bda00 Merge main into dev [skip version] 2026-08-21 01:27:05 -04:00
github-actions[bot] a019c12d7e chore: bump version to 1.7.125 [skip ci] 2026-08-21 05:24:10 +00:00
Juan Denis e5979d8bed Merge pull request #112 from jhd3197/fix/tab-overflow-route-crash
Prevent tab overflow crashes during navigation
2026-08-21 01:24:00 -04:00
Juan Denis 5377a1f3e6 fix: prevent tab overflow route crashes 2026-08-21 01:20:43 -04:00
Juan Denis 4fbf46e00c Merge pull request #111 from jhd3197/dev
Add five more locales and lock releases to dev promotions
v1.7.124
2026-08-21 00:34:23 -04:00
Juan Denis bf8633b239 fix: close alternate release entry points [skip version] 2026-08-21 00:28:54 -04:00
Juan Denis 4abb50efbb Merge main into dev and preserve locale parity 2026-08-21 00:22:07 -04:00
github-actions[bot] 291b44dd93 chore: bump version to 1.7.124 [skip ci] 2026-08-21 04:17:00 +00:00
Juan Denis f9103c8239 Merge pull request #110 from jhd3197/fix/release-promotion-guards
Make releases immutable and enforce dev-to-main promotions
2026-08-21 00:16:48 -04:00
Juan Denis 92e1862f32 fix: make releases immutable and gate main promotions 2026-08-21 00:15:52 -04:00
Juan Denis fd31f7f8ec Merge pull request #109 from jhd3197/feature/localization-bundles
Expand ServerKit with ten localized language bundles
2026-08-20 23:48:56 -04:00
Juan Denis 2d95e186aa fix: align backend locale order with manifest 2026-08-20 23:42:15 -04:00