Add typed context compiler v1
This commit is contained in:
@@ -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 ""
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user