mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
Problem: CodeQL reported new path and allocation alerts, and Windows repeatedly failed the retained-hook restoration test. Root cause: Checked selectors were still passed onward as raw request values; some generated paths and capacity hints did not expose their bounds at the IO owner. The HTTP DTO also carried an internal inherited binding. The hook fixture fed Windows backslashes through Bash resource parsing. Fix: Resolve import/export selections to registered values, forward checked relative names for resume/migration, and guard the generated session slug before joining it to state storage. Use rooted declaration/resource reads and copies, bound resource reads, and avoid unchecked capacity addition. Pass inherited bindings outside the wire DTO. Normalize and quote the hook fixture path, including spaces, while retaining source-deletion and exact-binding assertions. Verification: Hosted alert locations reproduced locally with the path-injection and allocation queries; before/after query results and the base scan are retained separately. Boundary regressions fail on the original head and pass with the fix. Owning HTTP, store, config, hook, sidecar, plugin, specification and encoding suites; full builtin/boot tests; gofmt, vet, lint and changed-file checks. Hosted Windows and CodeQL results remain required for exact-head acceptance.
229 lines
7.7 KiB
Go
229 lines
7.7 KiB
Go
package boot
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"reasonix/internal/contract/agentcomposition"
|
|
"reasonix/internal/contract/config"
|
|
"reasonix/internal/ext/extension"
|
|
"reasonix/internal/ext/extension/dispatch"
|
|
"reasonix/internal/ext/extension/sidecar"
|
|
"reasonix/internal/ext/extensioncontract"
|
|
)
|
|
|
|
// RuntimeReload is previous-generation state for incremental sidecar adoption
|
|
// and subgraph-classified rebuild.
|
|
type RuntimeReload struct {
|
|
// ForceFullRebuild bypasses extension subgraph reuse when config-owned
|
|
// skills/providers may have changed without changing the native graph.
|
|
ForceFullRebuild bool
|
|
Extensions *sidecar.Manager
|
|
Graph *extension.DependencyGraph
|
|
Generation uint64
|
|
// Owner is reused across one controller/session rebuild lineage. Cold builds
|
|
// leave it nil and receive a fresh isolated owner.
|
|
Owner *extension.RuntimeOwner
|
|
// PreviousSnapshot/Dispatcher enable CacheHash-stable no-op rebuilds and
|
|
// interceptor-only rewire without rediscovering the whole assembly.
|
|
PreviousSnapshot *extension.RuntimeSnapshot
|
|
PreviousDispatcher *dispatch.Dispatcher
|
|
PreviousPlan *extension.RuntimePlan
|
|
// ReuseAssembly, when set with a compatible plan, skips skill/command/hook
|
|
// rediscovery inside BuildRuntime.
|
|
ReuseAssembly *ReusedAssembly
|
|
}
|
|
|
|
// buildRuntimeGraph constructs the dependency graph for installed native
|
|
// runtime packages. Host-provided capabilities (if any) are included as a
|
|
// synthetic "host" component so plugin requirements can resolve.
|
|
func buildRuntimeGraph(home string, hostProvides []extensioncontract.Capability, bindings ...*agentcomposition.Binding) (*extension.DependencyGraph, error) {
|
|
packages, _ := sidecar.LoadRuntimePackages(home)
|
|
if len(bindings) > 0 {
|
|
var err error
|
|
packages, err = compositionPackages(home, packages, bindings[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
comps := make([]extension.ComponentDescriptor, 0, len(packages))
|
|
if len(hostProvides) > 0 {
|
|
comps = append(comps, extension.ComponentDescriptor{
|
|
ID: "host",
|
|
Source: extension.ContributionSource{Scope: extension.ScopeBuiltin, Origin: "host"},
|
|
Provides: hostProvides,
|
|
})
|
|
}
|
|
for _, item := range packages {
|
|
pkg := item.Package
|
|
id := sidecar.PluginComponentID(pkg.Manifest.Name)
|
|
var intercepts []extension.InterceptorPoint
|
|
var replaces []extension.Slot
|
|
priority := 0
|
|
optional := true
|
|
if rt := pkg.Manifest.Runtime; rt != nil {
|
|
priority = rt.Priority
|
|
optional = !rt.Required
|
|
for _, p := range rt.Intercepts {
|
|
intercepts = append(intercepts, extension.InterceptorPoint(p))
|
|
}
|
|
for _, s := range rt.Replaces {
|
|
if slot, err := extension.ParseSlot(s); err == nil {
|
|
replaces = append(replaces, slot)
|
|
}
|
|
}
|
|
}
|
|
comps = append(comps, extension.ComponentDescriptor{
|
|
ID: id,
|
|
Source: extension.ContributionSource{Scope: extension.ScopePlugin, PluginID: pkg.Manifest.Name, Origin: "plugin", Version: pkg.Manifest.Version},
|
|
Requires: pkg.Requires(),
|
|
Provides: pkg.ProvidesCapabilities(),
|
|
Intercepts: intercepts,
|
|
Replaces: replaces,
|
|
Priority: priority,
|
|
Optional: optional,
|
|
})
|
|
}
|
|
if len(comps) == 0 {
|
|
return &extension.DependencyGraph{
|
|
Components: map[extension.ComponentID]extension.ComponentDescriptor{},
|
|
Edges: map[extension.ComponentID][]extension.ComponentID{},
|
|
Providers: map[string][]extension.ComponentID{},
|
|
}, nil
|
|
}
|
|
return extension.BuildDependencyGraph(comps)
|
|
}
|
|
|
|
// attachPlanAndStatus fills BuildResult.Plan and Status from graphs using the
|
|
// lifecycle registry so doctor can explain Inactive/Failed components.
|
|
func attachPlanAndStatus(res *BuildResult, from *extension.DependencyGraph, to *extension.DependencyGraph, fromGen uint64, previousSnapshot *extension.RuntimeSnapshot) {
|
|
if res == nil {
|
|
return
|
|
}
|
|
toGen := uint64(0)
|
|
if res.Snapshot != nil {
|
|
toGen = res.Snapshot.Generation()
|
|
}
|
|
plan := extension.DiffRuntimePlan(from, to, fromGen, toGen)
|
|
if previousSnapshot != nil && res.Snapshot != nil {
|
|
plan.PrefixChanged = previousSnapshot.CacheHash() != res.Snapshot.CacheHash()
|
|
}
|
|
res.Plan = plan
|
|
life := extension.NewLifecycleRegistry(toGen)
|
|
status := &extension.RuntimeStatus{
|
|
PublishedGeneration: toGen,
|
|
Plan: extension.PlanView(plan),
|
|
}
|
|
if res.Runtime != nil {
|
|
status.Receipts = res.Runtime.Receipts()
|
|
}
|
|
if to != nil {
|
|
for _, id := range to.ActivateOrder() {
|
|
life.Ensure(id)
|
|
_ = life.Transition(id, extension.ComponentPreparing, "")
|
|
// Structured inactive reasons: missing requirements + graph diagnostics.
|
|
inactive := false
|
|
var diags []string
|
|
for _, d := range to.Diagnostics {
|
|
if strings.Contains(d, string(id)) {
|
|
inactive = true
|
|
diags = append(diags, d)
|
|
}
|
|
}
|
|
if desc, ok := to.Components[id]; ok {
|
|
for _, req := range desc.Requires {
|
|
if req.Optional {
|
|
continue
|
|
}
|
|
if _, found := to.EpochFor(id, req); !found {
|
|
inactive = true
|
|
diags = append(diags, "missing required capability "+req.Key.String()+" (versionRange="+req.VersionRange+")")
|
|
}
|
|
}
|
|
// Declared provides with no live client → Unavailable diagnostic.
|
|
if res.Extensions != nil && strings.HasPrefix(string(id), "plugin/") {
|
|
name := sidecar.PluginNameFromComponentID(id)
|
|
if name != "" && res.Extensions.Client(name) == nil && len(desc.Provides) > 0 {
|
|
inactive = true
|
|
for _, cap := range desc.Provides {
|
|
diags = append(diags, "capability Unavailable: "+cap.Key.String()+" (runtime client not started)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if inactive {
|
|
_ = life.Transition(id, extension.ComponentInactive, strings.Join(diags, "; "))
|
|
} else if res.Extensions != nil && strings.HasPrefix(string(id), "plugin/") {
|
|
name := sidecar.PluginNameFromComponentID(id)
|
|
if name != "" && res.Extensions.Client(name) == nil {
|
|
_ = life.Transition(id, extension.ComponentInactive, "runtime client not started")
|
|
} else {
|
|
_ = life.Transition(id, extension.ComponentActive, "")
|
|
}
|
|
} else {
|
|
_ = life.Transition(id, extension.ComponentActive, "")
|
|
}
|
|
}
|
|
status.Components = life.All()
|
|
}
|
|
res.Status = status
|
|
res.Lifecycle = life
|
|
}
|
|
|
|
// planForPreflight builds the RuntimePlan used to adopt Unchanged sidecars.
|
|
func planForPreflight(opts Options, toGen uint64) *extension.RuntimePlan {
|
|
if opts.Extensions == nil && opts.Graph == nil {
|
|
return nil
|
|
}
|
|
toGraph, err := buildRuntimeGraph(opts.roots().Home(), nil, opts.Composition)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return extension.DiffRuntimePlan(opts.Graph, toGraph, opts.Generation, toGen)
|
|
}
|
|
|
|
// finalizeBuildResult attaches the cold-start RuntimePlan and status, then
|
|
// publishes the generation so stale traffic from prior runtimes is dropped.
|
|
func finalizeBuildResult(roots config.Roots, res *BuildResult, publish bool) *BuildResult {
|
|
if res == nil {
|
|
return nil
|
|
}
|
|
if graph, err := buildRuntimeGraph(roots.Home(), nil, res.Controller.CompositionBinding()); err == nil {
|
|
attachPlanAndStatus(res, nil, graph, 0, nil)
|
|
}
|
|
if publish {
|
|
publishBuildResult(res)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func publishBuildResult(res *BuildResult) {
|
|
if res == nil || res.Snapshot == nil {
|
|
return
|
|
}
|
|
gen := res.Snapshot.Generation()
|
|
if gen == 0 {
|
|
return
|
|
}
|
|
owner := res.Owner
|
|
if owner == nil {
|
|
owner = extension.RuntimeOwnerOrDefault(nil)
|
|
res.Owner = owner
|
|
}
|
|
gate := owner.Gate
|
|
// Expire any previous drain TTLs before publishing the new generation.
|
|
_ = gate.SweepAndForceExpire()
|
|
gate.Publish(gen)
|
|
// Product path: force-expire old generations after drainTTL so in-flight
|
|
// work cannot linger forever after rebuild.
|
|
gate.ScheduleDrainWatch()
|
|
if res.Controller != nil {
|
|
res.Controller.SetRuntimeGeneration(gen)
|
|
}
|
|
if res.Runtime != nil {
|
|
owner.Receipts.IngestScope(res.Runtime.Scope())
|
|
}
|
|
if res.Status != nil {
|
|
res.Status.PublishedGeneration = gen
|
|
}
|
|
}
|