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
26 changes: 26 additions & 0 deletions internal/apply/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package apply

import (
"context"
"errors"

"github.com/LAA-Software-Engineering/agentic-control-plane/internal/state"
)

// Applier mutates deployment state from an apply operation (design doc §5.2).
type Applier struct {
Deploy state.DeploymentStore
}

// NewApplier returns an applier backed by dep.
func NewApplier(dep state.DeploymentStore) *Applier {
return &Applier{Deploy: dep}
}

// RecordAppliedResource upserts one applied resource row.
func (a *Applier) RecordAppliedResource(ctx context.Context, r state.AppliedResource) error {
if a == nil || a.Deploy == nil {
return errors.New("apply: nil deployment store")
}
return a.Deploy.UpsertAppliedResource(ctx, r)
}
26 changes: 26 additions & 0 deletions internal/plan/plan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package plan

import (
"context"
"errors"

"github.com/LAA-Software-Engineering/agentic-control-plane/internal/state"
)

// Planner reads deployment state to compare desired vs applied resources (design doc §5.2).
type Planner struct {
Deploy state.DeploymentStore
}

// NewPlanner returns a planner backed by dep. dep must not be nil when methods are called.
func NewPlanner(dep state.DeploymentStore) *Planner {
return &Planner{Deploy: dep}
}

// ListAppliedResources returns applied resources for env (MVP entry point for plan input).
func (p *Planner) ListAppliedResources(ctx context.Context, env string) ([]state.AppliedResource, error) {
if p == nil || p.Deploy == nil {
return nil, errors.New("plan: nil deployment store")
}
return p.Deploy.ListAppliedResourcesByEnv(ctx, env)
}
50 changes: 50 additions & 0 deletions internal/plan/plan_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package plan_test

import (
"context"
"errors"
"testing"

"github.com/LAA-Software-Engineering/agentic-control-plane/internal/plan"
"github.com/LAA-Software-Engineering/agentic-control-plane/internal/spec"
"github.com/LAA-Software-Engineering/agentic-control-plane/internal/state"
)

// stubDeploy is a minimal [state.DeploymentStore] so this package never imports sqlite.
type stubDeploy struct {
list []state.AppliedResource
}

func (s *stubDeploy) UpsertAppliedResource(context.Context, state.AppliedResource) error {
return errors.New("stub")
}

func (s *stubDeploy) GetAppliedResource(context.Context, string, spec.ResourceID) (*state.AppliedResource, error) {
return nil, errors.New("stub")
}

func (s *stubDeploy) ListAppliedResourcesByEnv(context.Context, string) ([]state.AppliedResource, error) {
return s.list, nil
}

func (s *stubDeploy) UpsertAppliedProject(context.Context, state.AppliedProject) error {
return errors.New("stub")
}

func (s *stubDeploy) GetAppliedProject(context.Context, string, string) (*state.AppliedProject, error) {
return nil, errors.New("stub")
}

func TestPlanner_listAppliedResources_usesDeploymentStoreOnly(t *testing.T) {
st := &stubDeploy{list: []state.AppliedResource{{Name: "agent-a", Env: "dev", Kind: "Agent"}}}
p := plan.NewPlanner(st)
got, err := p.ListAppliedResources(context.Background(), "dev")
if err != nil {
t.Fatal(err)
}
if len(got) != 1 || got[0].Name != "agent-a" {
t.Fatalf("got %+v", got)
}
}

var _ state.DeploymentStore = (*stubDeploy)(nil)
26 changes: 26 additions & 0 deletions internal/runtime/runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package runtime

import (
"context"
"errors"

"github.com/LAA-Software-Engineering/agentic-control-plane/internal/state"
)

// Runner drives workflow execution against persistent run and trace state (design doc §5.2).
type Runner struct {
Runs state.RuntimeStore
}

// NewRunner returns a runner backed by runs.
func NewRunner(runs state.RuntimeStore) *Runner {
return &Runner{Runs: runs}
}

// PersistRunStart inserts a new run row before execution proceeds.
func (r *Runner) PersistRunStart(ctx context.Context, run state.Run) error {
if r == nil || r.Runs == nil {
return errors.New("runtime: nil runtime store")
}
return r.Runs.StartRun(ctx, run)
}
9 changes: 7 additions & 2 deletions internal/state/doc.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// Package state stores deployment state and runtime metadata.
// Deployment rows for plan/apply are implemented in internal/state/sqlite (§14.1).
// Package state stores deployment state and runtime metadata (design doc §5.2, §14).
//
// [DeploymentStore] and [RuntimeStore] are the boundaries plan, apply, and runtime code should use
// so callers do not depend on a specific SQL backend. MVP implements them in internal/state/sqlite.
//
// Thread-safety: interfaces assume a single-process CLI unless a concrete backend documents
// stronger concurrency guarantees.
package state
9 changes: 9 additions & 0 deletions internal/state/sqlite/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package sqlite

import "github.com/LAA-Software-Engineering/agentic-control-plane/internal/state"

// Compile-time check: *Store implements state facades (issue #11).
var (
_ state.DeploymentStore = (*Store)(nil)
_ state.RuntimeStore = (*Store)(nil)
)
32 changes: 32 additions & 0 deletions internal/state/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package state

import (
"context"
"time"

"github.com/LAA-Software-Engineering/agentic-control-plane/internal/spec"
)

// DeploymentStore persists deployment rows from design doc §14.1 (applied_resources, applied_projects).
//
// Thread-safety: MVP targets a single-process CLI. Implementations are not required to support
// arbitrary concurrent callers; treat the store as non-thread-safe unless a backend documents otherwise.
type DeploymentStore interface {
UpsertAppliedResource(ctx context.Context, r AppliedResource) error
GetAppliedResource(ctx context.Context, env string, id spec.ResourceID) (*AppliedResource, error)
ListAppliedResourcesByEnv(ctx context.Context, env string) ([]AppliedResource, error)
UpsertAppliedProject(ctx context.Context, p AppliedProject) error
GetAppliedProject(ctx context.Context, env, projectName string) (*AppliedProject, error)
}

// RuntimeStore persists execution rows from design doc §14.2 (runs, run_steps, trace_events).
//
// Thread-safety: same expectations as [DeploymentStore].
type RuntimeStore interface {
StartRun(ctx context.Context, r Run) error
FinishRun(ctx context.Context, runID, status string, finishedAt time.Time, outputJSON, errorText string, totalCostUSD float64) error
UpsertRunStep(ctx context.Context, st RunStep) error
AppendTraceEvent(ctx context.Context, runID string, ts time.Time, eventType string, stepID string, dataJSON string) (seq int64, err error)
GetRun(ctx context.Context, runID string) (*Run, error)
ListTraceEventsByRunID(ctx context.Context, runID string) ([]TraceEvent, error)
}
Loading