Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
951e1b8
feat(g01): observe bounded idle drain ordering
jjangg96 Sep 9, 2026
f44f5f9
fix(g01): close drain lifecycle gaps
jjangg96 Sep 9, 2026
945371e
fix(g01): admit drain in paired verification
jjangg96 Sep 9, 2026
6e954cf
fix(g01): quarantine runner identity changes
jjangg96 Sep 9, 2026
82d0d7d
fix(g01): fence drain observations and lifecycle
jjangg96 Sep 9, 2026
c677598
docs(g01): record independent drain corrections
jjangg96 Sep 9, 2026
f482d24
fix(g01): retain ambiguous drain sessions
jjangg96 Sep 9, 2026
02ba764
chore(g01): format drain SDK adapter
jjangg96 Sep 9, 2026
21fe1f8
fix(g01): fence unknown drain samples and cancellation
jjangg96 Sep 9, 2026
6e1b214
fix(g01): reject ambiguous drain evidence
jjangg96 Sep 9, 2026
5d715c4
fix(g01): bind drain evidence and reject physical poll retries
jjangg96 Sep 9, 2026
f5020e8
fix(g01): scope replay prerequisite to before snapshot
jjangg96 Sep 9, 2026
1769da6
fix(g01): bind replay to ordered drain snapshots
jjangg96 Sep 9, 2026
3a18028
fix(g01): enforce drain capacity ordinals
jjangg96 Sep 9, 2026
668c361
fix(g01): bind drain wire identity and poll cursor
jjangg96 Sep 9, 2026
4d9043c
docs(g01): record exact-head drain corrections
jjangg96 Sep 9, 2026
9b1f0c2
fix(g01): enforce strict drain wire evidence
jjangg96 Sep 9, 2026
5ffaa60
fix(g01): bind acquire capture to actions endpoint
jjangg96 Sep 9, 2026
9904048
docs(g01): record acquire target correction
jjangg96 Sep 9, 2026
19f53d4
fix(g01): close drain SDK evidence boundaries
jjangg96 Sep 9, 2026
88d37ab
docs(g01): record SDK drain boundary evidence
jjangg96 Sep 9, 2026
64c6fce
fix(g01): gate drain release on completed withdrawal
jjangg96 Sep 9, 2026
cb2c1ba
docs(g01): record withdrawal race correction evidence
jjangg96 Sep 9, 2026
b3d45cb
fix(g01): bind drain queue to approved actions host
jjangg96 Sep 9, 2026
d85213a
docs(g01): record queue host boundary evidence
jjangg96 Sep 9, 2026
05b5fff
fix(g01): bind drain acquisition and close wire requests
jjangg96 Sep 10, 2026
116beda
docs(g01): record acquisition and close wire evidence
jjangg96 Sep 10, 2026
1d25d31
fix(g01): bind acquisition and session-close wire effects
jjangg96 Sep 10, 2026
f08e9f3
docs(g01): record exact wire follow-up evidence
jjangg96 Sep 10, 2026
c0e8a2e
fix(g01): fence marked session-open target mismatches
jjangg96 Sep 10, 2026
6357865
fix(g01): bind session origin across drain and cleanup
jjangg96 Sep 10, 2026
8439c12
Harden G01 drain marked request origins
jjangg96 Sep 12, 2026
cbf711c
fix(g01): bind drain poll approval and session origin
jjangg96 Sep 12, 2026
a646e4c
docs(g01): record origin and poll boundary evidence
jjangg96 Sep 12, 2026
1f12e11
fix G01 runtime tenant binding and session cardinality
jjangg96 Sep 12, 2026
1bb0c87
docs: record G01 exact-head follow-up evidence
jjangg96 Sep 12, 2026
a1f2077
fix G01 marked ACK and close request boundaries
jjangg96 Sep 12, 2026
a070263
test G01 DELETE boundaries report inner calls
jjangg96 Sep 12, 2026
1e19ec5
docs G01 marked DELETE boundary evidence
jjangg96 Sep 12, 2026
44524b9
fix G01 marked request Host boundary
jjangg96 Sep 12, 2026
d58224f
docs G01 Host boundary evidence
jjangg96 Sep 12, 2026
bf45b49
fix G01 marked opaque and snapshot boundaries
jjangg96 Sep 12, 2026
91aa852
docs G01 opaque and snapshot boundary evidence
jjangg96 Sep 12, 2026
3be170f
fix(g01): bind marked session origin before forwarding
jjangg96 Sep 12, 2026
2c32277
docs(g01): record session origin finding evidence
jjangg96 Sep 12, 2026
e4e2a28
fix(g01): reject case-folded duplicate poll capacity
jjangg96 Sep 12, 2026
dff2e03
docs(g01): record duplicate poll capacity evidence
jjangg96 Sep 12, 2026
c68e8c2
fix(g01): bind terminal set origin
jjangg96 Sep 12, 2026
e68d51f
docs(g01): record terminal origin evidence
jjangg96 Sep 12, 2026
cbab4d8
fix(g01): bind drain wire close and session auth
jjangg96 Sep 12, 2026
a8bfc27
docs(g01): record drain wire P1 evidence
jjangg96 Sep 12, 2026
3c85677
fix(g01): fence marked wire requests across wrappers
jjangg96 Sep 12, 2026
c2ebdaf
docs(g01): record wire boundary P1 evidence
jjangg96 Sep 12, 2026
6b025bc
fix(g01): reject evidence delete close failures
jjangg96 Sep 12, 2026
6e727bf
docs(g01): record expected-204 close failure evidence
jjangg96 Sep 13, 2026
d2a2be8
fix(g01): bind JIT wire and terminal absence evidence
jjangg96 Sep 13, 2026
fe666a9
docs(g01): record terminal and JIT wire findings
jjangg96 Sep 13, 2026
0aabf2b
fix(g01): reject workflow verification close failures
jjangg96 Sep 13, 2026
5b24636
docs(g01): record verification close failure evidence
jjangg96 Sep 13, 2026
0f36583
fix(g01): bind folded baseline wire markers
jjangg96 Sep 13, 2026
f5560ba
docs(g01): record folded marker evidence
jjangg96 Sep 13, 2026
cd480d8
docs(g01): record exact-head P1 audit
jjangg96 Sep 16, 2026
4d98f68
fix(g01): reject preflight response close failures
jjangg96 Sep 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,827 changes: 2,827 additions & 0 deletions docs/evidence/g01-idle-drain.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion experiments/g01-scaleset/cmd/g01-live/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func runWithPreparation(args []string, in io.Reader, out io.Writer, revisionForB
if *approvalPath != "" || *statePath != "" || *phase != "" || workerInputs || *pairedBinding != "" {
return reject()
}
fmt.Fprintln(out, "Controller-only phases: create, before-ack, after-ack, before-acquire, acquire-loss, jit-loss, inspect, cleanup. Paired terminal mode uses one same-process executable with explicit worker approval/state inputs and fixed terminal sequencing; no worker launch or workflow dispatch. Live execution requires an immutable reviewed build, exact private approval and controller-side broker input.")
fmt.Fprintln(out, "Controller-only phases: create, before-ack, after-ack, before-acquire, acquire-loss, jit-loss, drain, inspect, cleanup. Paired terminal mode uses one same-process executable with explicit worker approval/state inputs and fixed terminal sequencing; no worker launch or workflow dispatch. Live execution requires an immutable reviewed build, exact private approval and controller-side broker input.")
return 0
}
modeCount := 0
Expand Down
4 changes: 2 additions & 2 deletions experiments/g01-scaleset/livecanary/approval.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var nonce = regexp.MustCompile(`^[a-f0-9]{32}$`)
var sha = regexp.MustCompile(`^[a-f0-9]{40}$`)
var actionsHost = regexp.MustCompile(`^[a-z0-9-]+(?:\.[a-z0-9-]+)*\.actions\.githubusercontent\.com$`)
var workflowPath = regexp.MustCompile(`^\.github/workflows/[a-zA-Z0-9_-]+\.ya?ml$`)
var phases = []string{"create", "before-ack", "after-ack", "before-acquire", "acquire-loss", "jit-loss", "inspect", "cleanup"}
var phases = []string{"create", "before-ack", "after-ack", "before-acquire", "acquire-loss", "jit-loss", "drain", "inspect", "cleanup"}

func (a Approval) setName() string { return "g01-" + a.OwnerNonce }
func (a Approval) workerName() string { return a.setName() + "-worker-1" }
Expand All @@ -24,7 +24,7 @@ func (a Approval) Validate(now time.Time) error {
if a.Organization == ".." {
return ErrApproval
}
if !component.MatchString(a.Organization) || !component.MatchString(a.Repository) || !component.MatchString(a.Controller) || a.Organization == "." || a.Repository == "." || a.Repository == ".." || a.RepositoryID <= 0 || a.RunnerGroupID <= 0 || !nonce.MatchString(a.OwnerNonce) || !sha.MatchString(a.HarnessSHA) || !sha.MatchString(a.WorkflowSHA) || !workflowPath.MatchString(a.WorkflowPath) || !a.ExpiresAt.After(now) || a.ExpiresAt.After(now.Add(24*time.Hour)) || len(a.ActionsHosts) == 0 || len(a.ActionsHosts) > 8 || len(a.Phases) == 0 {
if !component.MatchString(a.Organization) || !component.MatchString(a.Repository) || !component.MatchString(a.Controller) || a.Organization == "." || a.Repository == "." || a.Repository == ".." || a.RepositoryID <= 0 || a.RunnerGroupID <= 0 || !nonce.MatchString(a.OwnerNonce) || !sha.MatchString(a.HarnessSHA) || !sha.MatchString(a.WorkflowSHA) || !workflowPath.MatchString(a.WorkflowPath) || !a.ExpiresAt.After(now) || a.ExpiresAt.After(now.Add(24*time.Hour)) || len(a.ActionsHosts) == 0 || len(a.ActionsHosts) > 8 || len(a.Phases) == 0 || len(a.Phases) > 9 {
return ErrApproval
}
seen := map[string]bool{}
Expand Down
12 changes: 11 additions & 1 deletion experiments/g01-scaleset/livecanary/baseline_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,17 @@ func (s *pairedBaselineScope) afterAcquire(ctx context.Context, acquisition base
if err != nil {
return err
}
capture := &baselineWireCapture{stage: "jit", setID: s.setID}
if s.listener == nil {
return ErrQuarantine
}
// AcquireJobs invokes this continuation while the listener mutex is held.
// wire reads the already-captured session tuple directly for this handoff;
// taking the listener accessors here would recursively lock that mutex.
capture := s.listener.wire("jit")
capture.runnerName = s.approval.workerName()
if capture.origin == "" || !capture.runtimePathPrefixSet || capture.runtimePathPrefix == "" || capture.runnerName == "" {
return ErrQuarantine
}
request, cancel := context.WithTimeout(ctx, operationTimeout)
got, callErr := s.captured.GenerateJIT(capture.context(request), s.setID, s.approval.workerName())
cancel()
Expand Down
17 changes: 17 additions & 0 deletions experiments/g01-scaleset/livecanary/baseline_journal.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,23 @@ func cloneEvent(e Event) Event {
e.Authority = &x
}
e.RequestIDs = slices.Clone(e.RequestIDs)
if e.Drain != nil {
data, _ := json.Marshal(e.Drain)
var x drainObservation
_ = json.Unmarshal(data, &x)
x.Ordering = slices.Clone(e.Drain.Ordering)
e.Drain = &x
}
if e.DrainSnapshot != nil {
data, _ := json.Marshal(e.DrainSnapshot)
var x drainSnapshot
_ = json.Unmarshal(data, &x)
if e.DrainSnapshot.Runner != nil {
runner := *e.DrainSnapshot.Runner
x.Runner = &runner
}
e.DrainSnapshot = &x
}
if e.Baseline != nil {
data, _ := json.Marshal(e.Baseline)
var x baselineRecord
Expand Down
80 changes: 59 additions & 21 deletions experiments/g01-scaleset/livecanary/baseline_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,25 @@ var errBaselineCollected = errors.New("baseline callback collection complete")
// No current phase/CLI calls this. The future pair orchestrator must prove
// completed pairing and host preflight before invoking this experiment slice.
type baselineListener struct {
finalizer *pairedBaselineScope
finalizing bool
pairGuard func() error
mu sync.Mutex
ctx context.Context
cancel context.CancelFunc
approval Approval
journal *FileJournal
api SDKAPI
identity controllerJournalIdentity
creation controllerRecordRef
setID int
session Session
initial scaleset.RunnerScaleSetSession
sessionID, queue string
running, used, invalid bool
after func(context.Context, baselineAcquisition) error
finalizer *pairedBaselineScope
finalizing bool
pairGuard func() error
mu sync.Mutex
ctx context.Context
cancel context.CancelFunc
approval Approval
journal *FileJournal
api SDKAPI
identity controllerJournalIdentity
creation controllerRecordRef
setID int
session Session
initial scaleset.RunnerScaleSetSession
sessionID, queue, origin string
runtimePathPrefix string
runtimePathPrefixSet bool
running, used, invalid bool
after func(context.Context, baselineAcquisition) error
}

func newBaselineListenerHeld(ctx context.Context, a Approval, j *FileJournal, api *SDKAPI, setID int) (*baselineListener, error) {
Expand Down Expand Up @@ -163,7 +165,25 @@ func (b *baselineListener) finish(r baselineRecord, known bool) (controllerRecor
return ref, nil
}
func (b *baselineListener) wire(stage string) *baselineWireCapture {
return &baselineWireCapture{stage: stage, setID: b.setID, queue: b.queue}
return &baselineWireCapture{stage: stage, setID: b.setID, organization: b.approval.Organization, owner: b.approval.setName(), sessionID: b.sessionID, queue: b.queue, origin: b.origin, runtimePathPrefix: b.runtimePathPrefix, runtimePathPrefixSet: b.runtimePathPrefixSet, allowedHosts: baselineWireAllowedHosts(b.approval, b.api.drainEndpointHost())}
}

func (b *baselineListener) capturedOrigin() string {
if b == nil {
return ""
}
b.mu.Lock()
defer b.mu.Unlock()
return b.origin
}

func (b *baselineListener) capturedRuntimePathPrefix() (string, bool) {
if b == nil {
return "", false
}
b.mu.Lock()
defer b.mu.Unlock()
return b.runtimePathPrefix, b.runtimePathPrefixSet && !b.invalid
}
func (b *baselineListener) run(after func(context.Context, baselineAcquisition) error) error {
if b == nil || b.ctx == nil || b.cancel == nil || !b.mu.TryLock() {
Expand Down Expand Up @@ -225,7 +245,16 @@ func (b *baselineListener) initialize() error {
cancel()
r.Set = c.set
r.HTTPStatus = c.status
known := c.observed() && r.Set.eligible(b.approval, b.setID) && ((callErr != nil && b.ctx.Err() != nil) || (callErr == nil && set != nil && set.ID == r.Set.ID && set.Name == r.Set.Name && set.RunnerGroupID == r.Set.GroupID && set.RunnerSetting.DisableUpdate && r.Set.Statistics.matches(set.Statistics)))
beforeOrigin := c.requestOrigin()
prefix, prefixKnown := c.requestRuntimePathPrefix()
known := c.observed() && prefixKnown && r.Set.eligible(b.approval, b.setID) && ((callErr != nil && b.ctx.Err() != nil) || (callErr == nil && set != nil && set.ID == r.Set.ID && set.Name == r.Set.Name && set.RunnerGroupID == r.Set.GroupID && set.RunnerSetting.DisableUpdate && r.Set.Statistics.matches(set.Statistics)))
if prefixKnown {
b.runtimePathPrefix = prefix
b.runtimePathPrefixSet = true
}
if c.observed() && beforeOrigin != "" {
b.origin = beforeOrigin
}
if _, err = b.finish(r, known); err != nil {
return err
}
Expand All @@ -238,16 +267,24 @@ func (b *baselineListener) initialize() error {
session, callErr := b.api.OpenSession(c.context(ctx), b.setID, b.approval.setName())
cancel()
sf, _, _, status := c.facts()
origin := c.requestOrigin()
sessionPrefix, sessionPrefixKnown := c.requestRuntimePathPrefix()
r.Session = sf
r.HTTPStatus = status
known = c.observed() && sf.eligible(b.approval, b.setID) && ((callErr != nil && b.ctx.Err() != nil) || (callErr == nil && session != nil))
known = c.observed() && sessionPrefixKnown && sf.eligible(b.approval, b.setID) && ((callErr != nil && b.ctx.Err() != nil) || (callErr == nil && session != nil))
var initial scaleset.RunnerScaleSetSession
if callErr == nil && session != nil {
initial = session.Session()
}
if known && callErr == nil {
known = initial.SessionID.String() == sf.SessionID && initial.OwnerName == sf.Owner && sf.Statistics.matches(initial.Statistics) && initial.MessageQueueURL != ""
}
if known && (origin == "" || !b.runtimePathPrefixSet || sessionPrefix != b.runtimePathPrefix) {
known = false
}
if known && origin != "" {
b.origin = origin
}
if callErr == nil && session != nil && sf != nil && initial.SessionID.String() == sf.SessionID && initial.OwnerName == b.approval.setName() {
b.session = session
b.sessionID = sf.SessionID
Expand Down Expand Up @@ -388,13 +425,14 @@ func (b *baselineListener) AcquireJobs(_ context.Context, ids []int64) ([]int64,
return nil, err
}
c := b.wire("acquire")
c.requestIDs = slices.Clone(ids)
ctx, cancel := context.WithTimeout(b.ctx, operationTimeout)
got, callErr := b.session.AcquireJobs(c.context(ctx), slices.Clone(ids))
cancel()
_, _, accepted, status := c.facts()
r.Accepted = accepted
r.HTTPStatus = status
known := c.observed() && status == 200 && accepted != nil && accepted.Count != nil && *accepted.Count == 1 && len(accepted.IDs) == 1 && accepted.IDs[0] == ids[0] && ((callErr == nil && slices.Equal(got, ids)) || (callErr != nil && b.ctx.Err() != nil)) && b.session.Session().SessionID.String() == b.sessionID
known := c.observed() && status == 200 && accepted.matches(ids) && ((callErr == nil && slices.Equal(got, ids)) || (callErr != nil && b.ctx.Err() != nil)) && b.session.Session().SessionID.String() == b.sessionID
resultRef, err := b.finish(r, known)
if err != nil {
return nil, err
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func newBaselineFixtureWithApproval(t *testing.T, change func(string, any) any,
t.Cleanup(transport.CloseIdleConnections)
outer := transport.Clone()
outer.RegisterProtocol("https", baselineRoundTrip(func(r *http.Request) (*http.Response, error) {
response, err := (responseBudgetTransport{inner: transport}).RoundTrip(r)
response, err := (responseBudgetTransport{inner: baselineRequestCaptureTransport{inner: transport}}).RoundTrip(r)
if err == nil && f.afterResponse != nil {
f.afterResponse(r, response)
}
Expand Down
116 changes: 112 additions & 4 deletions experiments/g01-scaleset/livecanary/baseline_message.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import (
"github.com/actions/scaleset"
)

// Pointer facts retain missing/null separately from explicit zero/false. No
// queue/acquisition URL, token, arbitrary display text or raw response is kept.
// Pointer facts retain missing/null separately from explicit zero/false.
// Persistent facts omit queue/acquisition URLs, tokens, arbitrary display text
// and raw responses.
type baselineStatistics struct {
Available *int `json:"total_available_jobs"`
Acquired *int `json:"total_acquired_jobs"`
Expand Down Expand Up @@ -55,11 +56,66 @@ type baselineSessionFacts struct {
SetID int `json:"set_id"`
SetName string `json:"set_name"`
GroupID int `json:"group_id"`
queueURL string `json:"-"` // ephemeral drain validation only; never serialized
authorization string `json:"-"` // ephemeral drain validation only; never serialized
}

// baselineRunnerFacts retains only the bounded fields needed to compare the
// pinned SDK's runner lookup result with the exact wire response. The list
// count is kept separately because the SDK collapses count==0 to nil and
// otherwise returns only the first value.
type baselineRunnerFacts struct {
Count int
ID int
Name string
ScaleSetID int
}

func decodeBaselineRunner(data []byte) (*baselineRunnerFacts, error) {
var w struct {
Count *int `json:"count"`
Value *[]struct {
ID *int `json:"id"`
Name *string `json:"name"`
ScaleSetID *int `json:"runnerScaleSetId"`
} `json:"value"`
}
if !uniqueKeys(json.NewDecoder(strings.NewReader(string(data)))) || json.Unmarshal(data, &w) != nil || w.Count == nil || w.Value == nil || *w.Count < 0 || *w.Count > 1 || *w.Count != len(*w.Value) {
return nil, ErrRemote
}
facts := &baselineRunnerFacts{Count: *w.Count}
if facts.Count == 0 {
return facts, nil
}
candidate := (*w.Value)[0]
if candidate.ID == nil || *candidate.ID <= 0 || candidate.Name == nil || !baselineText(*candidate.Name, 256) || candidate.ScaleSetID == nil || *candidate.ScaleSetID <= 0 {
return nil, ErrRemote
}
facts.ID = *candidate.ID
facts.Name = *candidate.Name
facts.ScaleSetID = *candidate.ScaleSetID
return facts, nil
}

func (r *baselineRunnerFacts) matches(v *scaleset.RunnerReference) bool {
if r == nil {
return false
}
if r.Count == 0 {
return v == nil
}
return r.Count == 1 && v != nil && r.ID == v.ID && r.Name == v.Name && r.ScaleSetID == v.RunnerScaleSetID
}

type baselineAccepted struct {
Count *int `json:"count"`
IDs []int64 `json:"ids"`
}

func (a *baselineAccepted) matches(ids []int64) bool {
return a != nil && a.Count != nil && *a.Count == len(ids) && slices.Equal(a.IDs, ids)
}

type baselineSetFacts struct {
ID int `json:"id"`
Name string `json:"name"`
Expand Down Expand Up @@ -95,7 +151,27 @@ func decodeBaselineSet(data []byte) (*baselineSetFacts, error) {
return &baselineSetFacts{w.ID, w.Name, w.Group, w.Labels, w.Setting.Disabled, s}, nil
}
func (s *baselineSetFacts) eligible(a Approval, id int) bool {
if s == nil || s.ID != id || s.Name != a.setName() || s.GroupID != a.RunnerGroupID || s.DisableUpdate == nil || !*s.DisableUpdate || !s.Statistics.eligible(false) {
return s.eligibleWithStats(a, id, false)
}

func (s *baselineSetFacts) eligibleForDrain(a Approval, id int) bool {
if !s.matchesOwner(a, id) || s.Statistics == nil {
return false
}
for _, p := range []*int{s.Statistics.Available, s.Statistics.Acquired, s.Statistics.Assigned, s.Statistics.Running, s.Statistics.Registered, s.Statistics.Busy, s.Statistics.Idle} {
if p == nil || *p < 0 {
return false
}
}
return true
}

func (s *baselineSetFacts) eligibleWithStats(a Approval, id int, acquired bool) bool {
return s.matchesOwner(a, id) && s.Statistics.eligible(acquired)
}

func (s *baselineSetFacts) matchesOwner(a Approval, id int) bool {
if s == nil || s.ID != id || s.Name != a.setName() || s.GroupID != a.RunnerGroupID || s.DisableUpdate == nil || !*s.DisableUpdate {
return false
}
for _, l := range s.Labels {
Expand All @@ -105,6 +181,17 @@ func (s *baselineSetFacts) eligible(a Approval, id int) bool {
}
return false
}

// matches compares the bounded facts captured by the strict wire reader with
// the SDK value returned from the same request. A caller may use eligible to
// establish approved identity, but must also prove that the lossy SDK object
// did not disagree with those wire facts.
func (s *baselineSetFacts) matches(v *scaleset.RunnerScaleSet) bool {
if s == nil || v == nil || s.ID != v.ID || s.Name != v.Name || s.GroupID != v.RunnerGroupID || s.DisableUpdate == nil || *s.DisableUpdate != v.RunnerSetting.DisableUpdate || !slices.Equal(s.Labels, v.Labels) {
return false
}
return s.Statistics.matches(v.Statistics)
}
func baselineText(s string, limit int) bool {
if len(s) > limit || !utf8.ValidString(s) {
return false
Expand All @@ -116,6 +203,14 @@ func baselineText(s string, limit int) bool {
}
return true
}

// validDrainAuthorizationToken accepts only a bounded, header-safe opaque
// token. The value is retained solely in memory to corroborate the pinned SDK
// session and bind later marked runtime requests.
func validDrainAuthorizationToken(value string) bool {
return value != "" && baselineText(value, 4096) && strings.TrimSpace(value) == value && !strings.ContainsAny(value, "\r\n\x00")
}

func baselineStats(data json.RawMessage) (*baselineStatistics, error) {
if len(data) == 0 || string(data) == "null" {
return nil, nil
Expand Down Expand Up @@ -157,6 +252,19 @@ func (s *baselineStatistics) matches(sdk *scaleset.RunnerScaleSetStatistic) bool
}
return true
}

func (s *baselineStatistics) completeDrain() bool {
if s == nil {
return false
}
values := []*int{s.Available, s.Acquired, s.Assigned, s.Running, s.Registered, s.Busy, s.Idle}
for _, value := range values {
if value == nil || *value < 0 {
return false
}
}
return validKnownDrainStatistics(drainStatistics{Available: *s.Available, Acquired: *s.Acquired, Assigned: *s.Assigned, Running: *s.Running, Registered: *s.Registered, Busy: *s.Busy, Idle: *s.Idle})
}
func decodeBaselineItem(data []byte, index int) (baselineItem, error) {
var w struct {
Kind string `json:"messageType"`
Expand Down Expand Up @@ -264,7 +372,7 @@ func decodeBaselineSession(data []byte) (*baselineSessionFacts, error) {
if err != nil {
return nil, err
}
x := &baselineSessionFacts{SessionID: w.ID, Owner: w.Owner, Statistics: s}
x := &baselineSessionFacts{SessionID: w.ID, Owner: w.Owner, Statistics: s, queueURL: w.URL, authorization: w.Token}
if len(w.Set) > 0 && string(w.Set) != "null" {
// Set contains SDK-defined fields not used here; ambiguity is rejected,
// but unrelated forward-compatible set metadata is not copied to history.
Expand Down
Loading
Loading