Files
stack/apps/gateway/src/enrollment/enrollment.dto.ts
T
fredandmarcie 431ead3a18
ci/woodpecker/push/publish Pipeline was successful
feat(gateway,cli): agent enrollment command family (M4-4b) (#1483)
Co-authored-by: fred <[email protected]>
2026-08-30 04:30:05 +00:00

108 lines
3.0 KiB
TypeScript

import { Type } from 'class-transformer';
import {
IsIn,
IsOptional,
IsString,
IsUUID,
MaxLength,
MinLength,
ValidateIf,
ValidateNested,
} from 'class-validator';
/**
* Agent enrollment command DTOs (design
* docs/plans/2026-08-29-agent-enrollment-command-design.md §3.1/§3.2,
* contract 5 §4.1 typed boundary).
*
* The global ValidationPipe runs with whitelist + forbidNonWhitelisted, so
* closure is contract surface here exactly as in the hierarchy DTOs:
* - EnrollAgentDto declares NO isSystem field — `is_system` is never
* settable through this command (design §3.1 rule 4); the pipe refuses it.
* - replayMode admits ONLY 'actor-bound': `shared` is seed-only (contract 3
* §4.3), so a shared declaration is refused `validation_failed` at the
* boundary, executes nothing, and records no fence row (design §3.1).
* Every class here must be registered in PIPE_GUARDED_DTOS so the boot-time
* assertion proves the pipe sees the decorators.
*/
/**
* Credential input, discriminated on `mode` (design §3.1):
* - `{ mode: 'reference' }` — a stored credential for (actor, provider)
* must already exist; `type`/`value` must be ABSENT (the repository
* refuses a reference that smuggles a value).
* - `{ mode: 'intake', type: 'api_key', value }` — the value is sealed
* into the credential store inside the enrollment transaction and is
* never echoed anywhere (§3.1 rule 1).
*/
export class EnrollCredentialDto {
@IsIn(['reference', 'intake'])
mode!: 'reference' | 'intake';
@ValidateIf((o: EnrollCredentialDto) => o.mode === 'intake')
@IsIn(['api_key'])
type?: 'api_key';
@ValidateIf((o: EnrollCredentialDto) => o.mode === 'intake')
@IsString()
@MinLength(1)
@MaxLength(4096)
value?: string;
}
export class EnrollAgentDto {
/** Registered harness name; a well-formed name missing from the registry is `precondition_failed`. */
@IsString()
@MinLength(1)
@MaxLength(200)
harness!: string;
@IsString()
@MinLength(1)
@MaxLength(200)
name!: string;
/** Stored as the agent's system prompt; null/absent leaves it unset. */
@IsOptional()
@IsString()
@MaxLength(20000)
persona?: string | null;
/** Provider-qualified model id. */
@IsString()
@MinLength(1)
@MaxLength(200)
model!: string;
/** Names the credential's provider. */
@IsString()
@MinLength(1)
@MaxLength(200)
provider!: string;
@ValidateNested()
@Type(() => EnrollCredentialDto)
credential!: EnrollCredentialDto;
/** REQUIRED — contract 3 §4.3, ratified into contract 5 §4 via §7 item 4. */
@IsUUID()
idempotencyKey!: string;
/** Optional; generated when absent (contract 5 §4.3). */
@IsOptional()
@IsUUID()
correlationId?: string;
/** Only 'actor-bound' is admissible on this family — see module doc. */
@IsOptional()
@IsIn(['actor-bound'])
replayMode?: 'actor-bound';
}
/** Query envelope for agent.enrollment.get (design §3.2): correlation only, no idempotency key. */
export class GetEnrollmentQueryDto {
@IsOptional()
@IsUUID()
correlationId?: string;
}