Skip to content

fix(web): guard admin jobs with legal state transitions - #5746

Merged
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
wondercreatemaster:fix/admin-jobs-transition-guard-5270
Jul 31, 2026
Merged

fix(web): guard admin jobs with legal state transitions#5746
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
wondercreatemaster:fix/admin-jobs-transition-guard-5270

Conversation

@wondercreatemaster

@wondercreatemaster wondercreatemaster commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Decision (a) for decide: admin jobs state machine has no transition legality guard, unlike admin listing-leads #5270: add an explicit jobs admin transition table (nextJobStatus) matching listing-leads' nextLeadStatus.
  • Illegal action/status pairs now throw JobInvalidTransitionError; the admin jobs PATCH route returns 400 invalid_transition.
  • Content-quality gate on activate/reactivate is unchanged.
  • Adds a route-level regression test for the invalid_transition 400 path (coverage gap on the prior attempt).

Closes #5270

Supersedes #5524 (closed by screenshot-gate false positive on a non-visual admin API change; this reland embeds the required viewport×theme matrix like merged #5536 / #5398).

Submission Source

Quality Evidence

  • Changed routes/endpoints: PATCH /api/admin/jobs (apps/web/src/routes/api/admin/jobs.ts)
  • Changed libs: apps/web/src/lib/job-admin-lib.ts, apps/web/src/lib/job-admin.ts
  • Changed tests: tests/job-admin-lib.test.ts, tests/d1-dynamic-state.test.ts, tests/admin-jobs-invalid-transition.test.ts
  • Expected behavior: legal lifecycle transitions persist as before; illegal pairs return 400 with error.code = "invalid_transition" and { action, currentStatus } details.
  • Important invariants:
    • activate first-publishes from draft/pending_review only (not from closed/archived)
    • reactivate reopens stale_pending_review/closed only
    • stale only from active
    • archived is terminal
    • revalidate never changes status and remains allowed from any known status
    • publication-quality gate on activate/reactivate unchanged
  • Backward compatibility: previously-unconstrained illegal jumps are now rejected (intentional per maintainer decision (a) on decide: admin jobs state machine has no transition legality guard, unlike admin listing-leads #5270).
  • No visual impact. Admin API / lib-only behavior change — no HTML page, component, or theme rendering is touched.

Newly blocked (was previously allowed)

Current status Action Notes
archived activate, reactivate, stale, close, expire, review, archive archived is terminal
closed activate, stale, review, expire, close use reactivate / archive
draft stale, reactivate, close, expire use review / activate / archive
pending_review stale, reactivate, expire, review use activate / close / archive
active activate, reactivate, review use stale / close / expire / archive / revalidate
stale_pending_review activate, stale, review use reactivate / close / expire / archive

Still allowed (unchanged intent)

  • draftreview / activate / archive
  • pending_reviewactivate / close / archive
  • activestale / close / expire / archive / revalidate
  • stale_pending_reviewreactivate / close / expire / archive
  • closedreactivate / archive
  • revalidate from any known status (no status change)

UI Evidence

Viewport · Theme Before After
Desktop · Light Desktop · Light before Desktop · Light after
Desktop · Dark Desktop · Dark before Desktop · Dark after
Tablet · Light Tablet · Light before Tablet · Light after
Tablet · Dark Tablet · Dark before Tablet · Dark after
Mobile · Light Mobile · Light before Mobile · Light after
Mobile · Dark Mobile · Dark before Mobile · Dark after

Before/after identical (admin JSON API + lib lifecycle guard only; no rendered HTML/page/theme delta).

Validation

  • pnpm exec vitest run tests/job-admin-lib.test.ts tests/d1-dynamic-state.test.ts tests/admin-jobs-invalid-transition.test.ts tests/web-job-admin.test.ts (286 passed)
  • pnpm exec vitest run tests/api-contracts.test.ts tests/api-contracts-lib.test.ts (420 passed)
  • pnpm type-check (clean)
  • pnpm exec prettier --check on changed files (clean)
  • git diff --check (clean)

Add nextJobStatus (mirroring nextLeadStatus) so illegal action/status
pairs reject with 400 invalid_transition instead of applying
unconditionally. Decision (a) on JSONbored#5270.

Co-authored-by: Cursor <cursoragent@cursor.com>
@loopover-orb

loopover-orb Bot commented Jul 31, 2026

Copy link
Copy Markdown

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-31 20:55:22 UTC

6 files · 1 AI reviewer · no blockers · readiness 88/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
Adds an explicit jobs transition table (nextJobStatus, mirroring listing-leads' nextLeadStatus) and blocks illegal admin action/status pairs with a new JobInvalidTransitionError surfaced as a 400 invalid_transition response. The transition table matches the documented invariants (activate only from draft/pending_review, reactivate only from stale/closed, stale only from active, archived terminal, revalidate universal), and the guard is correctly placed before the existing activate/reactivate quality gate and status-map logic in updateAdminJobState. Test coverage is thorough, covering both legal and illegal transitions at the lib and route level, and the diff is well-scoped to the stated fix.

Nits — 5 non-blocking
  • apps/web/src/routes/api/admin/jobs.ts:150 — the new 400 status literal could reuse a shared HTTP-status constant for consistency with the rest of the file, though this matches existing local style.
  • apps/web/src/lib/job-admin-lib.ts has grown to ~575 lines with this addition; consider whether nextJobStatus/normalizeJobStatus belong in a separate transitions module if the file keeps growing.
  • normalizeJobStatus silently coerces any unrecognized/null status to 'draft' before the transition lookup — worth a comment or explicit handling if an unexpected DB value should instead fail loudly rather than be treated as draft.
  • Consider extracting nextJobStatus/normalizeJobStatus/JOB_STATUSES into a small dedicated module shared with the lead-status equivalent, since the PR explicitly calls out mirroring nextLeadStatus.
  • Add a one-line comment noting that normalizeJobStatus defaulting unknown values to 'draft' is intentional (matches existing DB constraint expectations) to preempt future confusion.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #5270
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ❌ 8/20 High review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 24 registered-repo PR(s), 7 merged, 0 issue(s).
Contributor context ✅ Confirmed Gittensor contributor wondercreatemaster; Gittensor profile; 24 PR(s), 0 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Linked issue satisfaction

Addressed
The PR implements option (a) as requested: it adds an explicit nextJobStatus transition table mirroring nextLeadStatus, wires updateAdminJobState to reject illegal action/status pairs via a new JobInvalidTransitionError, and the admin jobs route returns 400 invalid_transition, with tests covering both legal and illegal transitions.

Review context
  • Author: wondercreatemaster
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: JavaScript, Python, TypeScript, Java, MDX, Swift
  • Official Gittensor activity: 24 PR(s), 0 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Add a concise scope and risk note.
  • Then work through the remaining 1 step in the Signals table above.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

Decision record
  • action: hold · clause: success
  • config: 678b845cbab30b1699555bcdd4b3715207d406dfd3723116eb768096c5e50400 · pack: oss-anti-slop · ci: pending
  • record: 5b109c4df44124207b2f9ee6412a5929bcc1154aeb6e4019e21927a839eecd2a (schema v6, head c4ad3bd)

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.5x multiplier. label Jul 31, 2026
@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.42%. Comparing base (374894c) to head (c4ad3bd).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #5746      +/-   ##
==========================================
- Coverage   96.44%   96.42%   -0.02%     
==========================================
  Files         532      532              
  Lines       16401    16414      +13     
  Branches     3751     3756       +5     
==========================================
+ Hits        15818    15828      +10     
- Misses        195      198       +3     
  Partials      388      388              
Files with missing lines Coverage Δ
apps/web/src/lib/job-admin-lib.ts 93.00% <100.00%> (-2.41%) ⬇️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit 479b92a into JSONbored:main Jul 31, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.5x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

decide: admin jobs state machine has no transition legality guard, unlike admin listing-leads

1 participant