Add cognitive admission policy

This commit is contained in:
m
2026-08-22 11:57:43 +03:00
parent e83cf3582b
commit cb92f95550
4 changed files with 130 additions and 2 deletions
+68
View File
@@ -0,0 +1,68 @@
package capability
type AdmissionComplexity string
type AdmissionDecisionState string
type AdmissionReason string
const (
AdmissionSimple AdmissionComplexity = "SIMPLE"
AdmissionNormal AdmissionComplexity = "NORMAL"
AdmissionHeavy AdmissionComplexity = "HEAVY"
AdmissionAdmitted AdmissionDecisionState = "ADMITTED"
AdmissionRejected AdmissionDecisionState = "REJECTED"
AdmissionReasonNone AdmissionReason = "NONE"
AdmissionReasonConcurrency AdmissionReason = "CONCURRENCY_LIMIT"
AdmissionReasonAcceptedHour AdmissionReason = "ACCEPTED_PER_HOUR_LIMIT"
AdmissionReasonDeadline AdmissionReason = "DEADLINE"
AdmissionReasonInputBudget AdmissionReason = "INPUT_BUDGET"
AdmissionReasonOutputBudget AdmissionReason = "OUTPUT_BUDGET"
)
type AdmissionPolicy struct {
ID string
MaxConcurrent int
MaxAcceptedPerHour int
MaxInputBytes int
MaxOutputBytes int
}
type AdmissionRequest struct {
Complexity AdmissionComplexity
InputBytes int
MaxOutputBytes int
DeadlineMillis int64
}
type AdmissionCounters struct {
Concurrent int
AcceptedLastHour int
}
type AdmissionDecision struct {
State AdmissionDecisionState
Reason AdmissionReason
PolicyID string
}
func Admit(request AdmissionRequest, policy AdmissionPolicy, counters AdmissionCounters, nowMillis int64) AdmissionDecision {
decision := AdmissionDecision{State: AdmissionAdmitted, Reason: AdmissionReasonNone, PolicyID: policy.ID}
if policy.ID == "" || policy.MaxConcurrent < 1 || policy.MaxAcceptedPerHour < 1 || policy.MaxInputBytes < 1 || policy.MaxOutputBytes < 1 {
return AdmissionDecision{State: AdmissionRejected, Reason: AdmissionReasonInputBudget, PolicyID: policy.ID}
}
if counters.Concurrent >= policy.MaxConcurrent {
return AdmissionDecision{State: AdmissionRejected, Reason: AdmissionReasonConcurrency, PolicyID: policy.ID}
}
if counters.AcceptedLastHour >= policy.MaxAcceptedPerHour {
return AdmissionDecision{State: AdmissionRejected, Reason: AdmissionReasonAcceptedHour, PolicyID: policy.ID}
}
if request.InputBytes < 0 || request.InputBytes > policy.MaxInputBytes {
return AdmissionDecision{State: AdmissionRejected, Reason: AdmissionReasonInputBudget, PolicyID: policy.ID}
}
if request.MaxOutputBytes < 1 || request.MaxOutputBytes > policy.MaxOutputBytes {
return AdmissionDecision{State: AdmissionRejected, Reason: AdmissionReasonOutputBudget, PolicyID: policy.ID}
}
if request.DeadlineMillis > 0 && request.DeadlineMillis <= nowMillis {
return AdmissionDecision{State: AdmissionRejected, Reason: AdmissionReasonDeadline, PolicyID: policy.ID}
}
return decision
}
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000114-run-liveness-assessment.json",
"task_id": "DEV-000114",
"task_file": "dev/tasks/DEV-000115-cognitive-admission-control.json",
"task_id": "DEV-000115",
"task_revision": 1
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000115",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "e83cf35",
"risk_tier": "E",
"intent": "Add a deterministic cognitive admission policy for concurrency, accepted/hour, deadline and input/output budgets without changing provider routing or bindings.",
"requirement_ids": ["WP-203", "Bounded concurrency/rate limits", "Explainable admission decision"],
"allowed_change_scope": {"modules": ["cognitive-admission"], "paths": ["contracts/capability/**", "modules/capability/domain/**", "tests/cognitive_admission/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000115-cognitive-admission-control.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "cmd/**", "runtime/**", "adapters/**", ".gitea/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["Existing cognitive capability registry and LimitPolicyRef", "ADR-0006 bounded advisory boundary"], "may_change": ["Typed admission request/policy/decision contract"]},
"semantic_owner": "capability-cognitive-admission",
"canonical_state": [],
"state_transitions": ["request + policy + observed counters -> admitted or typed rejection"],
"side_effects_and_privileges": ["Pure policy evaluation; no provider call, queue, persistence, binding mutation or external mutation"],
"persistence_and_migrations": ["No schema or migration changes"],
"failure_retry_restart": ["Rejected requests carry a reason; retry requires a fresh policy evaluation"],
"assumptions": ["Counters are supplied by a later durable admission implementation", "Policy does not choose or activate providers", "Deadline and budgets are request requirements"],
"acceptance_scenarios": ["Concurrency limit rejects excess request", "Accepted/hour limit rejects excess request", "Deadline and byte budget reject invalid request", "Admitted decision exposes reason and policy reference", "Simple/normal/heavy complexity remains a routing input, not authority"],
"required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "admission policy tests"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
}
@@ -0,0 +1,37 @@
package cognitive_admission_test
import (
"testing"
"git.cin.su/m/Assistant/contracts/capability"
)
func TestAdmitReturnsExplainableDecision(t *testing.T) {
policy := capability.AdmissionPolicy{ID: "local-v1", MaxConcurrent: 2, MaxAcceptedPerHour: 10, MaxInputBytes: 1000, MaxOutputBytes: 500}
request := capability.AdmissionRequest{Complexity: capability.AdmissionNormal, InputBytes: 100, MaxOutputBytes: 100, DeadlineMillis: 2000}
if decision := capability.Admit(request, policy, capability.AdmissionCounters{}, 1000); decision.State != capability.AdmissionAdmitted || decision.PolicyID != policy.ID {
t.Fatalf("admitted = %#v", decision)
}
checks := []struct {
counters capability.AdmissionCounters
reason capability.AdmissionReason
}{
{capability.AdmissionCounters{Concurrent: 2}, capability.AdmissionReasonConcurrency},
{capability.AdmissionCounters{AcceptedLastHour: 10}, capability.AdmissionReasonAcceptedHour},
}
for _, check := range checks {
if decision := capability.Admit(request, policy, check.counters, 1000); decision.State != capability.AdmissionRejected || decision.Reason != check.reason {
t.Fatalf("rejected = %#v want %s", decision, check.reason)
}
}
}
func TestAdmitRejectsBudgetAndDeadline(t *testing.T) {
policy := capability.AdmissionPolicy{ID: "local-v1", MaxConcurrent: 1, MaxAcceptedPerHour: 1, MaxInputBytes: 10, MaxOutputBytes: 10}
if decision := capability.Admit(capability.AdmissionRequest{InputBytes: 11, MaxOutputBytes: 1}, policy, capability.AdmissionCounters{}, 1); decision.Reason != capability.AdmissionReasonInputBudget {
t.Fatalf("input decision = %#v", decision)
}
if decision := capability.Admit(capability.AdmissionRequest{InputBytes: 1, MaxOutputBytes: 1, DeadlineMillis: 1}, policy, capability.AdmissionCounters{}, 1); decision.Reason != capability.AdmissionReasonDeadline {
t.Fatalf("deadline decision = %#v", decision)
}
}