Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions internal/jobs/payment_grace_terminator.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ package jobs
// payment_grace_periods table looking for teams whose grace expired
// (expires_at < now()) and calls the api repo's
// POST /internal/teams/:id/terminate endpoint to (a) pause all resources,
// (b) downgrade tier to anonymous, (c) mark the dunning row terminated.
// (b) downgrade tier to FREE — NOT anonymous; a terminated team keeps its
// team_id, so "free" (claimed-but-unpaid) is the correct floor (see
// api internal_terminate.go), (c) mark the dunning row terminated.
//
// Cadence: every 1h (per the brief). The 1h floor is fine because the
// grace clock is 7 days; a customer who recovers in the final hour
Expand All @@ -27,14 +29,18 @@ package jobs
// without action — better than booting the worker with no secret and
// later writing audit_log rows for non-terminations.
//
// TODO(ops): WORKER_INTERNAL_JWT_SECRET is a new env var. Operator must
// OPS: WORKER_INTERNAL_JWT_SECRET is a shared env var. Operator must
// (1) generate a 32-byte random value, (2) write it into the worker's
// secrets, (3) write the same value into the api's secrets under
// WORKER_INTERNAL_JWT_SECRET so the api can verify the bearer token,
// (4) the api must expose POST /internal/teams/:id/terminate that
// accepts the bearer + does pause/downgrade/mark-terminated. The
// endpoint is referenced by this dispatcher but NOT yet implemented in
// the api repo — that side ships in a separate PR.
// WORKER_INTERNAL_JWT_SECRET so the api can verify the bearer token.
// The api side IS shipped: POST /internal/teams/:id/terminate is
// registered (api router.go → internal_terminate.go::Terminate) and
// does the pause-resources + downgrade-to-free + mark-grace-terminated
// + audit-emit work. This dispatcher only fires the call and mirrors the
// lifecycle event into audit_log for the email forwarder. (Historical
// note: when this file was first written the api endpoint was a separate
// follow-up PR — that PR has since merged, so the prior "NOT yet
// implemented" TODO is removed to avoid misleading an agent reading it.)

import (
"bytes"
Expand Down
10 changes: 7 additions & 3 deletions internal/jobs/quota_wall_nudge.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ package jobs
// inside the last 24h, regardless of axis. This keeps the dashboard banner
// stable (it won't flicker between axes once it's earned a slot).
//
// Tier gating: teams on the "team" tier are skipped entirely — team tier
// is unlimited on every axis, so a wall nudge is incoherent. Teams on
// tier "free" (claimed-but-unpaid) and "anonymous" (unclaimed) are also
// Tier gating: teams on the "team" tier are skipped entirely — Team is the
// top self-serve tier, so an "upgrade for headroom" nudge has no target
// to point at. (The wall nudge is an UPGRADE banner; post the
// strict-≥80%-margin redesign of 2026-06-05 Team is no longer "unlimited"
// — it carries finite high-capacity limits — but there is still nothing
// above it to upsell, so skipping it remains correct.) Teams on tier
// "free" (claimed-but-unpaid) and "anonymous" (unclaimed) are also
// skipped: they are pre-conversion and the conversion CTA is the claim
// banner, not an upgrade banner.
//
Expand Down
Loading