fix(admission): pilotläge utan reservation #439

Merged
admin merged 1 commits from fix/admission-pilot-mode into main 2026-11-02 15:44:59 +00:00
3 changed files with 20 additions and 5 deletions
+1 -1
View File
@@ -306,7 +306,7 @@ export async function POST(request: NextRequest) {
dataClass,
maxInputTokens: policy.maxInputTokens,
maxOutputTokens: policy.maxOutputTokens,
maximumCredits: Math.max(1, maximumCredits),
maximumCredits,
},
policy: {
tenantId,
+11 -4
View File
@@ -47,8 +47,11 @@ export function admitModelExecution(
|| !Array.isArray(policy.allowedModelIds) || !Array.isArray(policy.allowedDataClasses)
|| !['none', 'low', 'medium', 'high', 'restricted'].includes(request.dataClass)
|| !['internal', 'external'].includes(policy.destination) || typeof policy.externalTransferAllowed !== 'boolean'
|| ![request.maxInputTokens, request.maxOutputTokens, request.maximumCredits, policy.maxInputTokens, policy.maxOutputTokens]
.every((n) => Number.isSafeInteger(n) && n > 0)) return { allowed: false, reason: 'invalid_request' };
|| ![request.maxInputTokens, request.maxOutputTokens, policy.maxInputTokens, policy.maxOutputTokens]
.every((n) => Number.isSafeInteger(n) && n > 0)
|| !Number.isSafeInteger(request.maximumCredits) || request.maximumCredits < 0) {
return { allowed: false, reason: 'invalid_request' };
}
if (request.tenantId !== policy.tenantId || request.projectId !== policy.projectId) return { allowed: false, reason: 'scope_mismatch' };
if (!policy.allowedModelIds.includes(request.modelId)) return { allowed: false, reason: 'model_not_allowed' };
if (!policy.allowedDataClasses.includes(request.dataClass)) return { allowed: false, reason: 'data_class_not_allowed' };
@@ -58,9 +61,13 @@ export function admitModelExecution(
if (request.maxInputTokens > policy.maxInputTokens || request.maxOutputTokens > policy.maxOutputTokens) {
return { allowed: false, reason: 'token_limit_exceeded' };
}
if (!reservation || reservation.tenantId !== request.tenantId || reservation.projectId !== request.projectId
// maximumCredits === 0 means the operator runs this route without a wallet
// (explicit pilot mode), so no reservation is required. Any positive amount
// still requires an active, matching reservation.
if (request.maximumCredits > 0 && (
!reservation || reservation.tenantId !== request.tenantId || reservation.projectId !== request.projectId
|| reservation.jobId !== request.jobId || reservation.status !== 'reserved'
|| !Number.isSafeInteger(reservation.credits) || reservation.credits < request.maximumCredits) {
|| !Number.isSafeInteger(reservation.credits) || reservation.credits < request.maximumCredits)) {
return { allowed: false, reason: 'reservation_required' };
}
return { allowed: true };
@@ -18,6 +18,14 @@ describe('model execution admission', () => {
expect(admitModelExecution(request, policy, { ...hold, status: 'settled' })).toEqual({ allowed: false, reason: 'reservation_required' });
expect(admitModelExecution(request, policy, { ...hold, jobId: 'other' })).toEqual({ allowed: false, reason: 'reservation_required' });
});
it('pilot mode (maximumCredits 0) requires no reservation but still enforces every other rule', () => {
const pilot = { ...request, maximumCredits: 0 };
expect(admitModelExecution(pilot, policy, undefined)).toEqual({ allowed: true });
expect(admitModelExecution(pilot, { ...policy, allowedModelIds: [] }, undefined)).toEqual({ allowed: false, reason: 'model_not_allowed' });
expect(admitModelExecution(pilot, { ...policy, projectId: 'other' }, undefined)).toEqual({ allowed: false, reason: 'scope_mismatch' });
expect(admitModelExecution({ ...pilot, maximumCredits: -1 }, policy, undefined)).toEqual({ allowed: false, reason: 'invalid_request' });
expect(admitModelExecution(request, policy, undefined)).toEqual({ allowed: false, reason: 'reservation_required' });
});
it('rejects invalid limits and never treats an unknown destination as local', () => {
expect(admitModelExecution({ ...request, maxInputTokens: NaN }, policy, hold).allowed).toBe(false);
expect(admitModelExecution(request, { ...policy, destination: 'unknown' as never }, hold).allowed).toBe(false);