Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ba41ac2
feat(db): add support organization access grants
pandemicsyn Nov 2, 2026
6e334bd
feat(admin): define support access policy and mode
pandemicsyn Nov 2, 2026
bdc0855
feat(admin): manage support org access requester permission
pandemicsyn Nov 2, 2026
e88baa1
feat(admin): add support access grant lifecycle
pandemicsyn Nov 2, 2026
90ea4c1
Merge remote-tracking branch 'origin/feat/admin-assurance-inventory' …
pandemicsyn Nov 2, 2026
eeb4f75
feat(org): resolve organization access by support mode
pandemicsyn Nov 2, 2026
c1e421e
feat(org): plumb support access through allowlisted reads
pandemicsyn Nov 2, 2026
39cbe46
feat(admin): gate noncentral admin bypasses by support mode
pandemicsyn Nov 2, 2026
74819aa
feat(admin): add support access console and org banner
pandemicsyn Nov 2, 2026
e4f9ff3
fix(org): keep membership error semantics under support access
pandemicsyn Nov 2, 2026
0afa97d
chore(admin): remove unused support access helpers
pandemicsyn Nov 2, 2026
b2c161e
refactor(admin): use one support grant resolver
pandemicsyn Nov 2, 2026
91b8e0c
fix(db): restrict support grant event deletion
pandemicsyn Nov 2, 2026
c2b4841
fix(admin): address support access review round 1
pandemicsyn Nov 2, 2026
1b392bf
fix(admin): restore off-mode parity and round 2 review fixes
pandemicsyn Nov 2, 2026
1c9cba6
fix(admin): finalize support access parity and telemetry
pandemicsyn Nov 2, 2026
9393ea3
fix(admin): tolerate observe grant lookup failures
pandemicsyn Nov 2, 2026
67e5946
fix(admin): address Kilobot feedback round
pandemicsyn Nov 3, 2026
28ac13e
feat(admin): make support access a binary on/off switch
pandemicsyn Nov 3, 2026
1f2f0ee
fix(admin): address binary on/off review round
pandemicsyn Nov 3, 2026
e522c6b
Merge remote-tracking branch 'origin/feat/admin-worker-capability' in…
pandemicsyn Nov 3, 2026
2af43cb
fix(admin): use kilo user id for self-action guards
pandemicsyn Nov 3, 2026
45b5a17
fix(admin): gate grant lifecycle on the support switch
pandemicsyn Nov 3, 2026
4065534
Merge remote-tracking branch 'origin/feat/admin-worker-capability' in…
pandemicsyn Nov 4, 2026
ec9345e
Merge remote-tracking branch 'origin/feat/admin-worker-capability' in…
pandemicsyn Nov 4, 2026
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
1 change: 1 addition & 0 deletions ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ ordinary local startup does not require this credential.
- `STYTCH_PUBLIC_TOKEN` - Stytch legacy public token alias used in some test fixtures. [PUBLIC]
- `INTERNAL_API_SECRET` - Shared secret for internal API calls between services; used in `apps/web/src/lib/kiloclaw/cli-runs.test.ts`, `kiloclaw-router.test.ts`, dev seed scripts, and other service routers. `[SECRET]`
- `BOT_TOKEN_MINT_SECRET` - Dedicated bearer secret for `POST /api/internal/bot-token`, which mints one-hour tokens for `bot-internal-*` users. It authenticates only the mint and is never a Kilo credential; unset or empty disables the endpoint. Separate from `INTERNAL_API_SECRET`. `[SECRET]`
- `SUPPORT_ACCESS_MODE` - Selects explicit support-organization access behavior: `off` (default), `observe`, or `enforce`. Unset, empty, or any other value is `off`. `enforce` is clamped to `observe` in this release; the test seam is the only way to exercise enforcement. In `observe`, admins keep today's `owner` shortcut and the resolved support decision is recorded in `admin_access` telemetry. Set it for the `kilocode-app` project with `pnpm web:env set SUPPORT_ACCESS_MODE`; the value takes effect on redeploy. [SERVER]
- `USAGE_QUEUE_ONLY_ENABLED` - Master queue-only accounting launch switch; default off, requires exactly `true`. Launch and regional exposure are independent: the master alone selects no production traffic. These server environment controls are read at startup; launch, exposure and allowlist changes require a gateway redeployment. There are no per-event remote feature-flag requests. Staging retains the existing experiment in both function regions when `VERCEL_TARGET_ENV=staging` and `VERCEL_ENV` is not `production`. Production requires both environment labels exactly `production`, trusted function `VERCEL_REGION=sfo1` or `fra1`, and that region's cohort controls below. Unselected FRA keeps direct accounting; unselected SFO keeps the existing primary-region handoff. Selected events await one finalized event publication within the accounting lifetime. HTTP 202 skips the direct usage write and returns a queued identity, not a committed row; spend-watched Bouncer events are delivered by the writer after its outbox commits, with cron retry on failure. Missing or unparseable publisher configuration publishes nothing, so each selected event is written through the direct accounting path (including the SFO primary-region handoff), logs `publisher_not_configured` with its usage ID, and raises at most one Sentry error per instance per minute. A lifetime registration failure (no request scope or `waitUntil`) also publishes nothing and uses the same direct fallback. Any other unavailable publication or unexpected rejection logs the finalized usage ID and a failure category and returns a null accounting result without a direct write, because the queue may already hold the event. Apart from those two fallbacks, no gateway billing writer is called on acceptance or failure. Failed accounting retains best-effort Bouncer reporting, which does not persist billing. Events not accepted by the queue have no durable recovery in this path and may be lost; a timeout may also hide an accepted event that the consumer later records. Cloudflare HTTP publication opts into up to four attempts for transient failures, each with up to five seconds for confirmation, within a 25-second overall elapsed budget. Retry waits use exponential full jitter from zero up to (excluding) 500 ms, 1,000 ms, then 2,000 ms; sleeps and attempt timeouts are clipped to remaining budget, with no new attempt after exhaustion. Runtime scheduling can delay deadline observation. Non-retry calls remain one two-second attempt; Vercel queue transport is unchanged. Publication occurs only for selected queue-only accounting. The obsolete background shadow publisher has been removed; historical shadow events already accepted by the queue must still be reconciled and drained before enabling a processing consumer. This code does not remove queued messages. Ambiguous retries may enqueue duplicates with the same usage ID; the writer deduplicates billing. Publisher logs carry `duplicate_possible: true` once any attempt timed out, lost its response or received a 5xx. Requires an independently verified processing consumer and authenticated writer; this flag does not establish readiness or a durable production publication guarantee. [SERVER]
- `USAGE_QUEUE_SFO_EXPOSURE` - Production SFO payer exposure from `0` to `1`, default `0` (off): `0.01` selects 1%, `0.1` selects 10%, and `1` selects 100%. Missing, malformed, nonfinite or out-of-range values select nobody. The retired percentage variable is ignored; existing settings do not activate this control. Stable versioned SHA-256 buckets use the organization ID for organization usage, otherwise the user ID (including arbitrary non-UUID strings). The cohort grows monotonically as exposure increases. This is a percentage of payers, not an exact fraction of requests; active payers can dominate volume. Selection controls accounting transport only: Bouncer availability/watch state, account classification, free/BYOK usage, and classifier rows do not cause selected events to fall back to direct accounting. Existing Bouncer reporting/outbox behavior is retained, including attached usage events travelling with queued accounting. At `1` with the master enabled and no allowlist restriction, SFO usage uses the queue; FRA exposure is controlled independently below. [SERVER]
- `USAGE_QUEUE_FRA_EXPOSURE` - Production Frankfurt payer exposure from `0` to `1`, default `0` (off): `0.01` selects 1%, `0.1` selects 10%, and `1` selects 100%. Unset/empty, malformed (including hex/binary/octal), nonfinite or out-of-range values select nobody; decimal/exponent syntax matches SFO. Requires `USAGE_QUEUE_ONLY_ENABLED=true`, both Vercel environment labels exactly `production`, and function `VERCEL_REGION=fra1`. The FRA cohort uses stable versioned SHA-256 payer buckets (`usage-fra-v1`), lowercase organization UUIDs or exact arbitrary-string personal user IDs, and grows monotonically. SFO exposure/hash/allowlist and staging behavior are independent and unchanged; the SFO allowlist does not restrict FRA. This selects accounting transport for existing FRA requests, not inference routing or compute location. Selected personal, organization, free/BYOK and classifier usage follows the existing queue accounting, retries and failure behavior; unselected FRA uses the existing direct path. The internal writer never re-enqueues. Configure on `kilocode-ai-gateway` Production only, and redeploy the gateway after changes. Master off or FRA exposure zero stops new FRA selection after redeploy; already accepted work must continue draining. Merge alone changes no production exposure, and these controls do not establish processing-consumer/writer readiness. [SERVER]
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/app/(app)/organizations/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default async function OrganizationByIdPage({
return (
<OrganizationByPageLayout
params={params}
supportRead
render={({ role, organization }) => (
<OrganizationDashboard
organizationId={organization.id}
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/app/admin/components/AppSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ const userManagementItems: MenuItem[] = [
url: '/admin/organizations',
icon: () => <Building2 />,
},
{
title: () => 'Support access',
url: '/admin/support-access',
icon: () => <KeyRound />,
},
{
title: () => 'Trial Organizations',
url: '/admin/organizations/trials',
Expand Down
35 changes: 35 additions & 0 deletions apps/web/src/app/admin/components/PlatformAdminsContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type PermissionValues = {
canViewSessions: boolean;
canManageCredits: boolean;
canExportApiRequestLogs: boolean;
canRequestSupportOrgAccess: boolean;
};

function AccountStatusBadge({ user }: { user: PlatformAdminUser }) {
Expand Down Expand Up @@ -192,6 +193,7 @@ export function PlatformAdminsContent() {
canViewSessions: user.can_view_sessions,
canManageCredits: user.can_manage_credits,
canExportApiRequestLogs: user.can_export_api_request_logs,
canRequestSupportOrgAccess: user.can_request_support_org_access,
});
}

Expand All @@ -212,6 +214,10 @@ export function PlatformAdminsContent() {
permissionTarget.can_export_api_request_logs && {
canExportApiRequestLogs: permissionValues.canExportApiRequestLogs,
}),
...(permissionValues.canRequestSupportOrgAccess !==
permissionTarget.can_request_support_org_access && {
canRequestSupportOrgAccess: permissionValues.canRequestSupportOrgAccess,
}),
};

if (Object.keys(permissions).length === 0) {
Expand Down Expand Up @@ -612,6 +618,35 @@ export function PlatformAdminsContent() {
</Label>
</div>

<div className="flex min-h-11 items-center justify-between gap-4">
<div className="flex flex-col gap-1">
<Label htmlFor="permission-support-org-access">Support org access</Label>
<p
id="permission-support-org-access-help"
className="text-muted-foreground text-sm"
>
Can request time-bounded read or approved-write access to a customer
organization.
</p>
</div>
<Label
htmlFor="permission-support-org-access"
className="flex size-11 shrink-0 cursor-pointer items-center justify-center"
>
<Switch
id="permission-support-org-access"
aria-describedby="permission-support-org-access-help"
checked={permissionValues.canRequestSupportOrgAccess}
disabled={setPermissionsMutation.isPending}
onCheckedChange={canRequestSupportOrgAccess =>
setPermissionValues(
values => values && { ...values, canRequestSupportOrgAccess }
)
}
/>
</Label>
</div>

<div className="flex min-h-11 items-center justify-between gap-4">
<div className="flex flex-col gap-1">
<Label htmlFor="permission-api-request-log-exporter">
Expand Down
Loading