Skip to content

Commit 57ca241

Browse files
authored
Merge pull request #43 from LAA-Software-Engineering/issue/11-state-facade
feat(state): DeploymentStore and RuntimeStore facades (#11)
2 parents dfd75d4 + 9606a5b commit 57ca241

7 files changed

Lines changed: 176 additions & 2 deletions

File tree

internal/apply/apply.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package apply
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
"github.com/LAA-Software-Engineering/agentic-control-plane/internal/state"
8+
)
9+
10+
// Applier mutates deployment state from an apply operation (design doc §5.2).
11+
type Applier struct {
12+
Deploy state.DeploymentStore
13+
}
14+
15+
// NewApplier returns an applier backed by dep.
16+
func NewApplier(dep state.DeploymentStore) *Applier {
17+
return &Applier{Deploy: dep}
18+
}
19+
20+
// RecordAppliedResource upserts one applied resource row.
21+
func (a *Applier) RecordAppliedResource(ctx context.Context, r state.AppliedResource) error {
22+
if a == nil || a.Deploy == nil {
23+
return errors.New("apply: nil deployment store")
24+
}
25+
return a.Deploy.UpsertAppliedResource(ctx, r)
26+
}

internal/plan/plan.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package plan
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
"github.com/LAA-Software-Engineering/agentic-control-plane/internal/state"
8+
)
9+
10+
// Planner reads deployment state to compare desired vs applied resources (design doc §5.2).
11+
type Planner struct {
12+
Deploy state.DeploymentStore
13+
}
14+
15+
// NewPlanner returns a planner backed by dep. dep must not be nil when methods are called.
16+
func NewPlanner(dep state.DeploymentStore) *Planner {
17+
return &Planner{Deploy: dep}
18+
}
19+
20+
// ListAppliedResources returns applied resources for env (MVP entry point for plan input).
21+
func (p *Planner) ListAppliedResources(ctx context.Context, env string) ([]state.AppliedResource, error) {
22+
if p == nil || p.Deploy == nil {
23+
return nil, errors.New("plan: nil deployment store")
24+
}
25+
return p.Deploy.ListAppliedResourcesByEnv(ctx, env)
26+
}

internal/plan/plan_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package plan_test
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
8+
"github.com/LAA-Software-Engineering/agentic-control-plane/internal/plan"
9+
"github.com/LAA-Software-Engineering/agentic-control-plane/internal/spec"
10+
"github.com/LAA-Software-Engineering/agentic-control-plane/internal/state"
11+
)
12+
13+
// stubDeploy is a minimal [state.DeploymentStore] so this package never imports sqlite.
14+
type stubDeploy struct {
15+
list []state.AppliedResource
16+
}
17+
18+
func (s *stubDeploy) UpsertAppliedResource(context.Context, state.AppliedResource) error {
19+
return errors.New("stub")
20+
}
21+
22+
func (s *stubDeploy) GetAppliedResource(context.Context, string, spec.ResourceID) (*state.AppliedResource, error) {
23+
return nil, errors.New("stub")
24+
}
25+
26+
func (s *stubDeploy) ListAppliedResourcesByEnv(context.Context, string) ([]state.AppliedResource, error) {
27+
return s.list, nil
28+
}
29+
30+
func (s *stubDeploy) UpsertAppliedProject(context.Context, state.AppliedProject) error {
31+
return errors.New("stub")
32+
}
33+
34+
func (s *stubDeploy) GetAppliedProject(context.Context, string, string) (*state.AppliedProject, error) {
35+
return nil, errors.New("stub")
36+
}
37+
38+
func TestPlanner_listAppliedResources_usesDeploymentStoreOnly(t *testing.T) {
39+
st := &stubDeploy{list: []state.AppliedResource{{Name: "agent-a", Env: "dev", Kind: "Agent"}}}
40+
p := plan.NewPlanner(st)
41+
got, err := p.ListAppliedResources(context.Background(), "dev")
42+
if err != nil {
43+
t.Fatal(err)
44+
}
45+
if len(got) != 1 || got[0].Name != "agent-a" {
46+
t.Fatalf("got %+v", got)
47+
}
48+
}
49+
50+
var _ state.DeploymentStore = (*stubDeploy)(nil)

internal/runtime/runner.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package runtime
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
"github.com/LAA-Software-Engineering/agentic-control-plane/internal/state"
8+
)
9+
10+
// Runner drives workflow execution against persistent run and trace state (design doc §5.2).
11+
type Runner struct {
12+
Runs state.RuntimeStore
13+
}
14+
15+
// NewRunner returns a runner backed by runs.
16+
func NewRunner(runs state.RuntimeStore) *Runner {
17+
return &Runner{Runs: runs}
18+
}
19+
20+
// PersistRunStart inserts a new run row before execution proceeds.
21+
func (r *Runner) PersistRunStart(ctx context.Context, run state.Run) error {
22+
if r == nil || r.Runs == nil {
23+
return errors.New("runtime: nil runtime store")
24+
}
25+
return r.Runs.StartRun(ctx, run)
26+
}

internal/state/doc.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1-
// Package state stores deployment state and runtime metadata.
2-
// Deployment rows for plan/apply are implemented in internal/state/sqlite (§14.1).
1+
// Package state stores deployment state and runtime metadata (design doc §5.2, §14).
2+
//
3+
// [DeploymentStore] and [RuntimeStore] are the boundaries plan, apply, and runtime code should use
4+
// so callers do not depend on a specific SQL backend. MVP implements them in internal/state/sqlite.
5+
//
6+
// Thread-safety: interfaces assume a single-process CLI unless a concrete backend documents
7+
// stronger concurrency guarantees.
38
package state
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package sqlite
2+
3+
import "github.com/LAA-Software-Engineering/agentic-control-plane/internal/state"
4+
5+
// Compile-time check: *Store implements state facades (issue #11).
6+
var (
7+
_ state.DeploymentStore = (*Store)(nil)
8+
_ state.RuntimeStore = (*Store)(nil)
9+
)

internal/state/store.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package state
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/LAA-Software-Engineering/agentic-control-plane/internal/spec"
8+
)
9+
10+
// DeploymentStore persists deployment rows from design doc §14.1 (applied_resources, applied_projects).
11+
//
12+
// Thread-safety: MVP targets a single-process CLI. Implementations are not required to support
13+
// arbitrary concurrent callers; treat the store as non-thread-safe unless a backend documents otherwise.
14+
type DeploymentStore interface {
15+
UpsertAppliedResource(ctx context.Context, r AppliedResource) error
16+
GetAppliedResource(ctx context.Context, env string, id spec.ResourceID) (*AppliedResource, error)
17+
ListAppliedResourcesByEnv(ctx context.Context, env string) ([]AppliedResource, error)
18+
UpsertAppliedProject(ctx context.Context, p AppliedProject) error
19+
GetAppliedProject(ctx context.Context, env, projectName string) (*AppliedProject, error)
20+
}
21+
22+
// RuntimeStore persists execution rows from design doc §14.2 (runs, run_steps, trace_events).
23+
//
24+
// Thread-safety: same expectations as [DeploymentStore].
25+
type RuntimeStore interface {
26+
StartRun(ctx context.Context, r Run) error
27+
FinishRun(ctx context.Context, runID, status string, finishedAt time.Time, outputJSON, errorText string, totalCostUSD float64) error
28+
UpsertRunStep(ctx context.Context, st RunStep) error
29+
AppendTraceEvent(ctx context.Context, runID string, ts time.Time, eventType string, stepID string, dataJSON string) (seq int64, err error)
30+
GetRun(ctx context.Context, runID string) (*Run, error)
31+
ListTraceEventsByRunID(ctx context.Context, runID string) ([]TraceEvent, error)
32+
}

0 commit comments

Comments
 (0)