Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions go/internal/store/db/accounts.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions go/internal/store/db/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions go/internal/store/queries/accounts.sql
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,8 @@ SELECT EXISTS (
)
AND a.id = $2
);

-- The tenant an account belongs to. A system-role caller (the delivery loop) reads
-- it to act for that account under its own tenant.
-- name: AccountTenant :one
SELECT tenant_id FROM accounts WHERE id = $1;
13 changes: 13 additions & 0 deletions go/internal/store/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,16 @@ func (s *Store) resolveTenant(ctx context.Context) TenantID {
func (s *Store) EffectiveTenant(ctx context.Context) TenantID {
return s.resolveTenant(ctx)
}

// AccountTenant returns the tenant an account belongs to. ErrNotFound when the
// account is not visible to ctx (call it under the system role to see all).
func (s *Store) AccountTenant(ctx context.Context, account AccountID) (TenantID, error) {
t, err := s.q.AccountTenant(ctx, string(account))
if err != nil {
if noRows(err) {
return "", fmt.Errorf("%w: account %q", ErrNotFound, account)
}
return "", fmt.Errorf("store: account tenant: %w", err)
}
return TenantID(t), nil
}
6 changes: 6 additions & 0 deletions go/internal/store/tenant_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ func WithSystemRole(ctx context.Context) context.Context {
return context.WithValue(ctx, systemRoleKey{}, true)
}

// WithoutSystemRole clears the system-role mark, so store calls on the returned
// ctx are tenant-scoped again. Pair it with WithTenant.
func WithoutSystemRole(ctx context.Context) context.Context {
return context.WithValue(ctx, systemRoleKey{}, false)
}

// isSystemRole reports whether ctx is the cross-tenant system path.
func isSystemRole(ctx context.Context) bool {
v, _ := ctx.Value(systemRoleKey{}).(bool)
Expand Down
19 changes: 19 additions & 0 deletions go/server/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ var _ runnerhub.LifecycleCaller = (*lifecycleService)(nil)
// caller and no RequireAgentSessionSubscriber); no prior session → a fresh
// hub.Start; no placement → a logged no-op.
func (l *lifecycleService) WakeAgent(ctx context.Context, agent store.AccountID) {
ctx, ok := l.wakeCtx(ctx, agent)
if !ok {
return
}
// 1. Not-live pre-check (cost control): a live agent is already awake, so
// there is nothing to resume. No-op, no log line — a wake is only an attempt
// against an OFFLINE agent.
Expand Down Expand Up @@ -628,3 +632,18 @@ func (l *lifecycleService) freshStart(ctx context.Context, agent store.AccountID
}
return wakeOutcomeFreshStarted
}

// wakeCtx scopes a wake to the agent's tenant. The delivery loop wakes under the
// system role, which sets no tenant, so rows the wake writes (agent_sessions,
// bindings) would land with an empty tenant_id, invisible to every tenant.
func (l *lifecycleService) wakeCtx(ctx context.Context, agent store.AccountID) (context.Context, bool) {
if !store.IsSystemRole(ctx) {
return ctx, true
}
tenant, err := l.store.AccountTenant(ctx, agent)
if err != nil {
slog.ErrorContext(ctx, "agent wake: resolving tenant failed", "agent_account_id", agent, "error", err)
return ctx, false
}
return store.WithTenant(store.WithoutSystemRole(ctx), tenant), true
}
4 changes: 1 addition & 3 deletions go/server/offline_mention_e2e_pgtest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -498,12 +498,10 @@ func TestMentionBeforeRunnerEnrollsWakesOnAttach(t *testing.T) {

// The wake fires during attach, before the harness clears its probe bookkeeping,
// so assert the durable session the fresh start records, not the command log.
// Read under the system role, as the wake path does: a wake from the delivery
// loop records its session without a tenant.
w.runner = attachFakeRunner(t, w.store, w.hub, false)
deadline = timeAfter()
for {
if _, ok, err := w.store.LatestSessionForAccount(store.WithSystemRole(w.ctx), agent.ID); err != nil {
if _, ok, err := w.store.LatestSessionForAccount(w.ctx, agent.ID); err != nil {
t.Fatalf("LatestSessionForAccount: %v", err)
} else if ok {
return
Expand Down
Loading