Add typed context compiler v1

This commit is contained in:
m
2026-08-22 11:41:52 +03:00
parent 3e0be6245e
commit 879de3bd95
4 changed files with 175 additions and 2 deletions
+91
View File
@@ -0,0 +1,91 @@
package cognitive
import (
"errors"
"sort"
"strings"
"git.cin.su/m/Assistant/contracts/capability"
)
var ErrInvalidContextBudget = errors.New("context budget must be positive")
type ContextCandidate struct {
Excerpt ContextExcerpt
Relevance int
}
type ContextCompileInput struct {
Candidates []ContextCandidate
DataPolicy capability.DataPolicy
MaxBytes int
}
type ContextOmission struct {
ID ContextExcerptID
Reason string
}
type ContextBundle struct {
Excerpts []ContextExcerpt
Omissions []ContextOmission
Bytes int
}
// CompileContext deterministically selects permitted, relevant excerpts for a
// gateway request. It carries no provider identity and performs no I/O.
func CompileContext(input ContextCompileInput) (ContextBundle, error) {
if input.MaxBytes <= 0 {
return ContextBundle{}, ErrInvalidContextBudget
}
candidates := append([]ContextCandidate(nil), input.Candidates...)
sort.SliceStable(candidates, func(i, j int) bool {
if candidates[i].Relevance != candidates[j].Relevance {
return candidates[i].Relevance > candidates[j].Relevance
}
return string(candidates[i].Excerpt.ID) < string(candidates[j].Excerpt.ID)
})
bundle := ContextBundle{Excerpts: []ContextExcerpt{}, Omissions: []ContextOmission{}}
seen := make(map[ContextExcerptID]struct{}, len(candidates))
for _, candidate := range candidates {
excerpt := candidate.Excerpt
if _, ok := seen[excerpt.ID]; ok {
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: excerpt.ID, Reason: "duplicate"})
continue
}
seen[excerpt.ID] = struct{}{}
reason := permittedContextReason(excerpt, input.DataPolicy)
if reason != "" {
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: excerpt.ID, Reason: reason})
continue
}
if strings.TrimSpace(excerpt.Text) == "" {
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: excerpt.ID, Reason: "empty"})
continue
}
bytes := len([]byte(excerpt.Text))
if bundle.Bytes+bytes > input.MaxBytes {
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: excerpt.ID, Reason: "budget"})
continue
}
bundle.Excerpts = append(bundle.Excerpts, excerpt)
bundle.Bytes += bytes
}
return bundle, nil
}
func permittedContextReason(excerpt ContextExcerpt, policy capability.DataPolicy) string {
if excerpt.Sensitivity == SensitivitySecret {
return "secret"
}
if excerpt.Sensitivity == SensitivityUnknown {
return "unknown_sensitivity"
}
if policy == capability.DataPolicyExternalMessageOnly {
return "policy_message_only"
}
if policy == capability.DataPolicyExternalMinimizedContext && excerpt.Sensitivity != SensitivityPublic {
return "policy_public_only"
}
return ""
}
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000106-governed-self-development-proposal.json",
"task_id": "DEV-000106",
"task_file": "dev/tasks/DEV-000107-context-compiler-v1.json",
"task_id": "DEV-000107",
"task_revision": 1
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000107",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "3e0be62",
"risk_tier": "E",
"intent": "Implement a typed context compiler for relevance, sensitivity, provenance and byte budgets at the cognitive contract boundary.",
"requirement_ids": ["WP-101", "Typed context bundle", "Sensitivity/egress filtering and budget"],
"allowed_change_scope": {"modules": ["cognitive-contracts"], "paths": ["contracts/cognitive/**", "tests/context_compiler/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000107-context-compiler-v1.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "modules/**", "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 ContextExcerpt and Gateway request boundary", "Capability DataPolicy values"], "may_change": ["Cognitive context bundle/compiler contract"]},
"semantic_owner": "cognitive-context-contract",
"canonical_state": [],
"state_transitions": ["candidate excerpts -> selected typed context bundle", "budget/sensitivity decision -> included or omitted with reason"],
"side_effects_and_privileges": ["Pure deterministic compilation; no provider call, persistence, external mutation or credential access"],
"persistence_and_migrations": ["No database schema or migration changes"],
"failure_retry_restart": ["Compilation is deterministic and repeatable; empty permitted context remains a valid bundle"],
"assumptions": ["Gateway remains responsible for provider selection", "Secret and unknown excerpts are never emitted", "Provider identity remains invocation provenance"],
"acceptance_scenarios": ["Relevant excerpts are selected deterministically", "Duplicate and low-value excerpts do not consume budget", "Secret/unknown data is omitted", "External minimized policy excludes private data", "UTF-8 byte budget is respected", "Provider identity is not represented as persona"],
"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", "context compiler tests"],
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
}
@@ -0,0 +1,59 @@
package context_compiler_test
import (
"testing"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/cognitive"
)
func TestCompileContextOrdersDeduplicatesAndBudgets(t *testing.T) {
bundle, err := cognitive.CompileContext(cognitive.ContextCompileInput{
MaxBytes: 4,
Candidates: []cognitive.ContextCandidate{
{Excerpt: cognitive.ContextExcerpt{ID: "low", Sensitivity: cognitive.SensitivityPublic, Text: "low"}, Relevance: 1},
{Excerpt: cognitive.ContextExcerpt{ID: "high", Sensitivity: cognitive.SensitivityPublic, Text: "éé"}, Relevance: 2},
{Excerpt: cognitive.ContextExcerpt{ID: "high", Sensitivity: cognitive.SensitivityPublic, Text: "hi"}, Relevance: 3},
},
})
if err != nil {
t.Fatal(err)
}
if len(bundle.Excerpts) != 1 || bundle.Excerpts[0].ID != "high" || bundle.Bytes != 2 {
t.Fatalf("unexpected bundle: %#v", bundle)
}
if len(bundle.Omissions) != 2 || bundle.Omissions[0].Reason != "duplicate" || bundle.Omissions[1].Reason != "budget" {
t.Fatalf("unexpected omissions: %#v", bundle.Omissions)
}
}
func TestCompileContextFiltersSensitivityByPolicy(t *testing.T) {
bundle, err := cognitive.CompileContext(cognitive.ContextCompileInput{
MaxBytes: 100,
DataPolicy: capability.DataPolicyExternalMinimizedContext,
Candidates: []cognitive.ContextCandidate{
{Excerpt: cognitive.ContextExcerpt{ID: "public", Sensitivity: cognitive.SensitivityPublic, Text: "ok"}},
{Excerpt: cognitive.ContextExcerpt{ID: "private", Sensitivity: cognitive.SensitivityPrivateAllowed, Text: "private"}},
{Excerpt: cognitive.ContextExcerpt{ID: "secret", Sensitivity: cognitive.SensitivitySecret, Text: "secret"}},
},
})
if err != nil {
t.Fatal(err)
}
if len(bundle.Excerpts) != 1 || bundle.Excerpts[0].ID != "public" {
t.Fatalf("unexpected excerpts: %#v", bundle.Excerpts)
}
if len(bundle.Omissions) != 2 {
t.Fatalf("unexpected omissions: %#v", bundle.Omissions)
}
}
func TestCompileContextAllowsEmptyResultAndRejectsInvalidBudget(t *testing.T) {
if _, err := cognitive.CompileContext(cognitive.ContextCompileInput{}); err == nil {
t.Fatal("expected invalid budget")
}
bundle, err := cognitive.CompileContext(cognitive.ContextCompileInput{MaxBytes: 10, Candidates: []cognitive.ContextCandidate{{Excerpt: cognitive.ContextExcerpt{ID: "secret", Sensitivity: cognitive.SensitivitySecret, Text: "x"}}}})
if err != nil || len(bundle.Excerpts) != 0 || len(bundle.Omissions) != 1 {
t.Fatalf("unexpected empty result: %#v %v", bundle, err)
}
}