Commit Graph

14 Commits

Author SHA1 Message Date
Jordan Ritter 6e90e7bb38 feat(showcase): fleet contracts + PB run-metadata schema for run-visibility
Adds run-id/family/worker-id columns to probe_jobs and resource_snapshots,
plus the EnqueueJobInput.family + FleetQueueClient.pruneAged contracts and
the hoisted deriveHealth primitive that downstream projections share. The
fleet-claim PB hook stamps run-id/family at claim time so every later
projection has a stable join key. probes/run-history is updated to read
the new columns.
2026-06-11 21:04:59 -07:00
Jordan Ritter 1a87c279a7 fix(harness): fleet-claim handlers preserve {ok:false} shape on transaction throw 2026-06-11 20:42:50 -07:00
github-actions[bot] a97bf39ccb style: auto-fix formatting 2026-06-11 20:42:49 -07:00
Jordan Ritter 34e67f7b4a fix(harness): G1g batch — report retry guard, at-least-once sweep split, 401 race, single-attempt decode write, hook jobId guard, doc corrections
- report() retry: a null getOne resolution is a FAILED read (throw, no blind
  write) and a "" result is PB's unset-JSON shape (absent → write proceeds).
- sweepExpired: thrown-release conservative maybes now counted on a separate
  reclaimedIndeterminate (SweepResultWithIndeterminate); reclaimed counts only
  CAS-confirmed re-queues. Producer one-liner documented for when the
  sibling-owned TickResult gains the field.
- job-claim 401 retry: snapshot the token the failed request used; only null
  authToken if unchanged (no clobbering a concurrently refreshed token).
- decode-failure synthetic result write: single attempt, no 250ms retry pacing
  inside the claim race (consumer crash-synthesis is the documented backstop).
- fleet-claim.pb.js: typeof jobId !== "string" → 400 in all three handlers.
- docs: recent-lease bound is expiryPeriods × period (not one window); claim
  5xx→won:false bounded false-overlay source; report retryability deploy-skew
  note; drainStalePending page-advance indeterminacy note.
2026-06-11 20:38:40 -07:00
Jordan Ritter 9ce9b2d947 fix(showcase): fleet-claim hook hardening — explicit status, lease floor, workerId type, claim idempotency
- release: status is REQUIRED (the old '|| done' fallback silently
  finished a job whose caller omitted/emptied status) — 400 like
  jobId/workerId
- claim+renew: leaseSeconds floored at 1s (0.001 previously yielded a
  1ms lease — instantly stealable, renew thrash)
- all handlers: reject non-string workerId (a JSON number coerces into
  the text claimed_by column and the holder can never renew/release)
- claim: same-holder live-lease re-claim answers claimed:true with an
  alreadyHeld:true marker (timeout-after-commit retry no longer abandons
  a row the worker actually holds); client treats it as a plain win
  (ClaimEndpointBody documents the no-op marker)
- hook-parity pins updated/added for every change
2026-06-11 20:38:29 -07:00
Jordan Ritter dc3acb8427 fix(showcase): queue-client robustness batch (CR G1f)
(i) per-candidate try/catch in claimNext's CAS race — a thrown transport
claim no longer aborts the whole rotation (warn + next candidate);
(ii) discoverPendingFamilies' duplicate-family defensive break now warns
before breaking; (iii) renew re-read triage split — a decodePayload
failure logs as a protocol violation, not a read blip; (iv) the
decode-failure synthetic result uses an injected clock (new config.now)
instead of new Date(); (v) backslash charset guard for family clause
building (the equality/LIKE escape contracts contradict for backslash —
skip such families with a warn) and the over-claiming VERIFIED comment
scoped to %/_ only; (vi) single-sweeper assumption documented as
load-bearing at the grace-set declaration; (vii) hook comment misname
fixed (worker-crashed-mid-job -> worker-reclaimed-pending); (viii)
claimJob maps a 5xx claim response to a lost CAS (won:false, warn) —
a WAL serialization error escaping runInTransaction surfaces as 500 —
while 4xx and renew/release 5xx still throw loud.
2026-06-11 20:38:19 -07:00
Jordan Ritter 5730b86f94 fix(showcase): make report() retryable via release refusal reasons (CR G1e)
After a release-CAS success + result-write exhaustion, report() throws;
a natural retry got REFUSED (row already terminal) and emitted an error
claiming the result is discarded and the job re-runs — both false (the
result is still writable by this holder; terminal rows never re-run).
The hook's release response now carries a refusal reason
(refused_terminal_same_holder / refused_not_holder /
refused_lease_live), threaded through job-claim's ReleaseResult.
report() treats refused-terminal-under-my-workerId as the second leg of
a timeout-after-commit retry and proceeds to writeResult; the
not-holder error is reworded to 're-runs only if reclaimed to pending'.
A reason-less refusal still fails closed.
2026-06-11 20:38:19 -07:00
Jordan Ritter 937ec924de fix(showcase): stop the next sweep from claim-deleting a re-queued long-runner (CR G1d)
Stale-pending age is anchored on PB created and never re-anchored on
re-queue: a job running longer than its family expiry window got
lease-reclaimed with a 'back in flight' comm error, then claim-deleted
by the NEXT sweep before any plausible re-run — the dashboard
permanently showed 're-queued' for silently-discarded work. Schema-free
fix: the release hook now RETAINS the expired lease_expires_at on a
pending re-queue (claim admits pending rows regardless of lease), and
the stale phase skips rows whose retained lease is recent (parseable
and within now - familyExpiryWindow) — recently in flight means the
created-based age is stale evidence. Comments cover the heuristic, the
requeued_at-column alternative, and the sweeper-garbage lingering
tradeoff; hook parity test pins the retention.
2026-06-11 20:38:19 -07:00
Jordan Ritter 547e6f2841 fix(showcase): enforce superuser auth + leaseSeconds clamp on fleet claim endpoints (CR G1a)
The three /api/fleet/* routerAdd handlers carried no auth middleware —
a middleware-less PB 0.22 routerAdd handler is PUBLIC, so any
unauthenticated caller could claim/renew/release arbitrary jobs despite
the header claiming superuser auth was required. Append
$apis.requireAdminAuth() (verified against PB 0.22.21 JSVM types) to
all three routes; the client already authenticates as superuser with a
401-reauth retry, so enforcement is compat-safe. Also clamp
leaseSeconds in claim+renew (numeric only, 3600s ceiling, 30s default
on garbage) and pin both contracts in the hook-source parity tests.
2026-06-11 20:38:17 -07:00
Jordan Ritter 5362f93993 fix(showcase): re-check lease expiry on pending-target releases — close the sweeper TOCTOU
sweepExpired (and fleet-health's reclaim) decide 'expired' from a listed
SNAPSHOT, then releaseJob(jobId, holder, 'pending') authorizes on
claimed_by alone — a worker renewing between the list and the release
still matches claimed_by, so a live just-renewed job was yanked back to
pending (duplicate execution + a false worker-reclaimed-pending comm
error). The /api/fleet/release hook now refuses a pending-target release
while the row's CURRENT lease is still live, re-checked inside the same
transaction with the leaseExpired helper that stays byte-equivalent to
the client's anchored parse. The client needs no change: released:false
already maps to the sweep's skip path. Pinned by a hook-source parity
test plus a renewed-after-list race test against a hook-faithful fake.
2026-06-11 20:37:27 -07:00
Jordan Ritter 57122abe83 feat(showcase): add PocketBase pull-queue collections + CAS claim hook for harness fleet 2026-06-05 10:38:40 -07:00
github-actions[bot] 5b2852c5b5 style: auto-fix formatting 2026-06-04 12:23:05 -07:00
Jordan Ritter 928793c07b fix(showcase/pocketbase): make image bootable on an existing staging volume
A PB image freshly built from main crash-loops staging PocketBase (502s)
because of two latent defects, both verified by booting the built image
against a real PB 0.22.21 binary on a volume that already has the
collections but has NOT recorded their migrations in `_migrations`.

1. Hook API. `pb_hooks/main.pb.js` registered its CORS middleware via the
   bare global `onBeforeServe(...)`, which is undefined in PB 0.22.x JSVM
   (only the `$app.onBeforeServe()` Go method exists) — it throws
   `ReferenceError: onBeforeServe is not defined` at hook load and crashes
   the server. Switch to the documented global `routerUse((next) => (c) =>
   …)` entry point. Separately, the per-request closure runs in PB's pooled
   goja runtime where top-level helpers/consts are out of scope, so calling
   them throws per request and the router returns HTTP 400 on EVERY route;
   inline the entire allowlist/env/match logic into the closure to fix that
   second regression. Verified: health 200, collection reads 200, the
   allowlisted origin is echoed on `Access-Control-Allow-Origin`, a
   non-allowlisted origin is not, and OPTIONS preflight returns 204.

2. Migration idempotency. `1777700000_create_baseline.js` and the three
   original `1745193*` creators (status, status_history, alert_state) called
   `saveCollection(new Collection(...))` unconditionally, so on a volume
   where the collection already exists they throw
   `UNIQUE constraint failed: _collections.name`, aborting the ENTIRE
   migration chain before later migrations (resource_snapshots, future fleet
   collections) can run. Guard each with the proven find-or-skip pattern
   already used by probe_runs / resource_snapshots, and harden their down
   arms to tolerate an already-absent collection. Verified end to end:
   deleting those migrations' `_migrations` rows while leaving the
   collections in place (the exact staging state), then rebooting the built
   image — boots healthy, re-records the migrations cleanly with no UNIQUE
   abort and no duplicate collections, and a brand-new collection migration
   still applies through the now-clean chain (the pool-fleet path).
2026-06-04 12:23:05 -07:00
Jordan Ritter 6faf41b8ab feat(showcase/pocketbase): service (migrations + hooks + Docker)
PocketBase backend for showcase-ops: Dockerfile + entrypoint for the
Railway-hosted instance, JSVM main.pb.js hook for CORS + request
shaping, migration sequence creating status / status_history /
alert_state collections, CORS config, and the recreate_collections
v1/v2 + drop_history_fail_count schema drift corrections.
2026-04-22 11:00:47 -07:00