feat: add artifact pass/fail/needs-fix lifecycle for SDLC execution phases

- Add pass/fail/needs-fix CLI commands to cmd/sdlc/cmd_artifact.go
- Add 3 new methods to SDLCExecutor interface in internal/port
- Implement methods in kubernetes adapter
- Add service methods to SDLCService
- Add HTTP handlers for POST .../artifacts/{type}/pass|fail|needs-fix
- Update 6 skeleton commands to evaluate and set artifact status
- Update test mocks

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jordan 2026-02-02 22:14:53 -07:00
parent 64ccf0b85d
commit 6e8f5821af
15 changed files with 430 additions and 11 deletions

View File

@ -35,6 +35,7 @@ Run Claude Code instances in isolated Kubernetes pods with REST API control. Ena
| **DNS / Cloudflare** | [services/dns-cloudflare.md](.claude/guides/services/dns-cloudflare.md) | | **DNS / Cloudflare** | [services/dns-cloudflare.md](.claude/guides/services/dns-cloudflare.md) |
| **Network policies / internal routing** | [ops/networking.md](.claude/guides/ops/networking.md) | | **Network policies / internal routing** | [ops/networking.md](.claude/guides/ops/networking.md) |
| **SDLC orchestration** | [services/sdlc.md](.claude/guides/services/sdlc.md) | | **SDLC orchestration** | [services/sdlc.md](.claude/guides/services/sdlc.md) |
| **Visual verification (Playwright)** | [services/visual-verification.md](.claude/guides/services/visual-verification.md) |
## Critical Rules ## Critical Rules
@ -171,6 +172,7 @@ cookbooks/ # End-to-end workflow guides
| Build Orchestration | Planned | Structured build specs via API | | Build Orchestration | Planned | Structured build specs via API |
| SDLC Orchestration | **Done** | Deterministic feature lifecycle with classifier engine, API, orchestrator, and 15 skeleton commands | | SDLC Orchestration | **Done** | Deterministic feature lifecycle with classifier engine, API, orchestrator, and 15 skeleton commands |
| Composable Monorepo Templates | **Done** | Monorepo skeleton + component templates (service, worker, app-astro, app-react, cli) | | Composable Monorepo Templates | **Done** | Monorepo skeleton + component templates (service, worker, app-astro, app-react, cli) |
| Visual Verification | Planned | Playwright screenshots/video + AI evaluation for feature completeness |
**Current Version:** v0.10.25 **Current Version:** v0.10.25

View File

@ -151,6 +151,147 @@ var artifactRejectCmd = &cobra.Command{
}, },
} }
var artifactPassCmd = &cobra.Command{
Use: "pass <feature> <type>",
Short: "Mark an artifact as passed (for execution artifacts: review, audit, qa_results)",
Args: cobra.ExactArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
root := mustResolveRoot()
slug, artTypeStr := args[0], args[1]
artType := sdlc.ArtifactType(artTypeStr)
f, err := sdlc.LoadFeature(root, slug)
if err != nil {
return err
}
art := f.GetArtifact(artType)
if art == nil {
return sdlc.ErrArtifactNotFound
}
art.MarkPassed()
if err := f.Save(root); err != nil {
return err
}
// Record in state
state, err := sdlc.LoadState(root)
if err != nil {
return err
}
state.RecordAction("PASS_ARTIFACT", slug, "user", "success")
if err := state.Save(root); err != nil {
return err
}
if jsonOutput {
return printJSON(map[string]string{
"feature": slug,
"artifact": string(artType),
"status": "passed",
})
}
fmt.Printf("Passed: %s/%s\n", slug, artType)
return nil
},
}
var artifactFailCmd = &cobra.Command{
Use: "fail <feature> <type>",
Short: "Mark an artifact as failed (for execution artifacts: review, audit, qa_results)",
Args: cobra.ExactArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
root := mustResolveRoot()
slug, artTypeStr := args[0], args[1]
artType := sdlc.ArtifactType(artTypeStr)
f, err := sdlc.LoadFeature(root, slug)
if err != nil {
return err
}
art := f.GetArtifact(artType)
if art == nil {
return sdlc.ErrArtifactNotFound
}
art.MarkFailed()
if err := f.Save(root); err != nil {
return err
}
// Record in state
state, err := sdlc.LoadState(root)
if err != nil {
return err
}
state.RecordAction("FAIL_ARTIFACT", slug, "user", "success")
if err := state.Save(root); err != nil {
return err
}
if jsonOutput {
return printJSON(map[string]string{
"feature": slug,
"artifact": string(artType),
"status": "failed",
})
}
fmt.Printf("Failed: %s/%s\n", slug, artType)
return nil
},
}
var artifactNeedsFixCmd = &cobra.Command{
Use: "needs-fix <feature> <type>",
Short: "Mark an artifact as needing fixes (for execution artifacts: review, audit)",
Args: cobra.ExactArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
root := mustResolveRoot()
slug, artTypeStr := args[0], args[1]
artType := sdlc.ArtifactType(artTypeStr)
f, err := sdlc.LoadFeature(root, slug)
if err != nil {
return err
}
art := f.GetArtifact(artType)
if art == nil {
return sdlc.ErrArtifactNotFound
}
art.MarkNeedsFix()
if err := f.Save(root); err != nil {
return err
}
// Record in state
state, err := sdlc.LoadState(root)
if err != nil {
return err
}
state.RecordAction("NEEDS_FIX_ARTIFACT", slug, "user", "success")
if err := state.Save(root); err != nil {
return err
}
if jsonOutput {
return printJSON(map[string]string{
"feature": slug,
"artifact": string(artType),
"status": "needs_fix",
})
}
fmt.Printf("Needs fix: %s/%s\n", slug, artType)
return nil
},
}
var artifactStatusCmd = &cobra.Command{ var artifactStatusCmd = &cobra.Command{
Use: "status <feature>", Use: "status <feature>",
Short: "Show all artifact statuses for a feature", Short: "Show all artifact statuses for a feature",
@ -185,6 +326,9 @@ func init() {
artifactCreateCmd, artifactCreateCmd,
artifactApproveCmd, artifactApproveCmd,
artifactRejectCmd, artifactRejectCmd,
artifactPassCmd,
artifactFailCmd,
artifactNeedsFixCmd,
artifactStatusCmd, artifactStatusCmd,
) )
rootCmd.AddCommand(artifactCmd) rootCmd.AddCommand(artifactCmd)

View File

@ -210,6 +210,21 @@ func (e *SDLCExecutor) RejectArtifact(ctx context.Context, podName, slug string,
return e.execSDLCNoOutput(ctx, podName, "artifact", "reject", slug, string(artType)) return e.execSDLCNoOutput(ctx, podName, "artifact", "reject", slug, string(artType))
} }
// PassArtifact marks a feature artifact as passed.
func (e *SDLCExecutor) PassArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
return e.execSDLCNoOutput(ctx, podName, "artifact", "pass", slug, string(artType))
}
// FailArtifact marks a feature artifact as failed.
func (e *SDLCExecutor) FailArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
return e.execSDLCNoOutput(ctx, podName, "artifact", "fail", slug, string(artType))
}
// NeedsFixArtifact marks a feature artifact as needing fixes.
func (e *SDLCExecutor) NeedsFixArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
return e.execSDLCNoOutput(ctx, podName, "artifact", "needs-fix", slug, string(artType))
}
// ListTasks returns all tasks for a feature. // ListTasks returns all tasks for a feature.
func (e *SDLCExecutor) ListTasks(ctx context.Context, podName, slug string) ([]sdlc.Task, error) { func (e *SDLCExecutor) ListTasks(ctx context.Context, podName, slug string) ([]sdlc.Task, error) {
out, err := e.execSDLC(ctx, podName, "task", "list", slug) out, err := e.execSDLC(ctx, podName, "task", "list", slug)

View File

@ -88,16 +88,32 @@ Write to `.sdlc/features/$ARGUMENTS/audit.md`:
[Ordered list of actions to take] [Ordered list of actions to take]
``` ```
### 7. Register the Artifact ### 7. Register and Evaluate the Artifact
Create the artifact:
```bash ```bash
sdlc artifact create $ARGUMENTS audit sdlc artifact create $ARGUMENTS audit
``` ```
Then evaluate the audit results and set the appropriate status:
- If the audit has **no critical or high findings**: mark as passed
```bash
sdlc artifact pass $ARGUMENTS audit
```
- If the audit has **critical or high findings**: mark as needs-fix
```bash
sdlc artifact needs-fix $ARGUMENTS audit
```
This status drives the SDLC classifier to either advance to QA or trigger remediate-audit.
## Critical Rules ## Critical Rules
- NEVER skip OWASP checks -- even if the feature seems low-risk - NEVER skip OWASP checks -- even if the feature seems low-risk
- ALWAYS check for hardcoded secrets, tokens, and credentials - ALWAYS check for hardcoded secrets, tokens, and credentials
- ALWAYS verify authentication and authorization boundaries - ALWAYS verify authentication and authorization boundaries
- NEVER pass an audit with critical or high severity findings unresolved - NEVER mark an audit as passed if it has unresolved critical or high findings
- ALWAYS run static analysis tools before manual review - ALWAYS run static analysis tools before manual review
- ALWAYS set the artifact status (pass or needs-fix) after writing the audit

View File

@ -55,9 +55,22 @@ Update `.sdlc/features/$ARGUMENTS/qa-results.md`:
- Add a remediation note explaining what was fixed - Add a remediation note explaining what was fixed
- Verify the overall status (PASS only if all scenarios pass) - Verify the overall status (PASS only if all scenarios pass)
### 8. Report ### 8. Update Artifact Status
Summarize: failures fixed, root causes, regression status (all previously passing tests still pass). After all fixes are applied and tests re-run:
- If **all scenarios now pass**: mark as passed
```bash
sdlc artifact pass $ARGUMENTS qa_results
```
- If **failures remain**: keep as failed
```bash
sdlc artifact fail $ARGUMENTS qa_results
```
### 9. Report
Summarize: failures fixed, root causes, regression status (all previously passing tests still pass), and artifact status.
## Critical Rules ## Critical Rules
@ -66,3 +79,4 @@ Summarize: failures fixed, root causes, regression status (all previously passin
- NEVER fix tests to match broken behavior -- fix the implementation - NEVER fix tests to match broken behavior -- fix the implementation
- ALWAYS keep passing tests passing -- no regressions - ALWAYS keep passing tests passing -- no regressions
- ALWAYS update the QA results document with resolution details - ALWAYS update the QA results document with resolution details
- ALWAYS set the artifact status after fixing (pass if all scenarios now pass)

View File

@ -55,9 +55,22 @@ go test ./... 2>/dev/null || true
All tests must pass after all fixes are applied. All tests must pass after all fixes are applied.
### 8. Report ### 8. Update Artifact Status
Summarize: findings fixed by severity, files modified, test results. After all fixes are applied and tests pass, re-evaluate the review:
- If **all blockers are resolved** and tests pass: mark as passed
```bash
sdlc artifact pass $ARGUMENTS review
```
- If **blockers remain unresolved**: keep as needs-fix
```bash
sdlc artifact needs-fix $ARGUMENTS review
```
### 9. Report
Summarize: findings fixed by severity, files modified, test results, and artifact status.
## Critical Rules ## Critical Rules
@ -66,3 +79,4 @@ Summarize: findings fixed by severity, files modified, test results.
- NEVER close a finding without actually fixing it - NEVER close a finding without actually fixing it
- NEVER introduce new issues while fixing existing ones - NEVER introduce new issues while fixing existing ones
- ALWAYS update the review report with resolution notes - ALWAYS update the review report with resolution notes
- ALWAYS set the artifact status after fixing (pass if all blockers resolved)

View File

@ -56,9 +56,22 @@ Update `.sdlc/features/$ARGUMENTS/audit.md` with remediation notes:
| [description] | CRITICAL | REMEDIATED | [what was done] | | [description] | CRITICAL | REMEDIATED | [what was done] |
``` ```
### 8. Report ### 8. Update Artifact Status
Summarize: findings remediated by severity, remaining items, verification results. After all remediations are applied and security checks pass, re-evaluate the audit:
- If **all critical and high findings are remediated**: mark as passed
```bash
sdlc artifact pass $ARGUMENTS audit
```
- If **critical or high findings remain**: keep as needs-fix
```bash
sdlc artifact needs-fix $ARGUMENTS audit
```
### 9. Report
Summarize: findings remediated by severity, remaining items, verification results, and artifact status.
## Critical Rules ## Critical Rules
@ -68,3 +81,4 @@ Summarize: findings remediated by severity, remaining items, verification result
- NEVER fix security issues with workarounds -- address root causes - NEVER fix security issues with workarounds -- address root causes
- ALWAYS update the audit report with remediation details - ALWAYS update the audit report with remediation details
- NEVER remove security findings from the report -- mark them as remediated - NEVER remove security findings from the report -- mark them as remediated
- ALWAYS set the artifact status after remediation (pass if all critical/high resolved)

View File

@ -70,16 +70,32 @@ Write to `.sdlc/features/$ARGUMENTS/review.md`:
[Which acceptance criteria have tests? Which are missing?] [Which acceptance criteria have tests? Which are missing?]
``` ```
### 6. Register the Artifact ### 6. Register and Evaluate the Artifact
Create the artifact:
```bash ```bash
sdlc artifact create $ARGUMENTS review sdlc artifact create $ARGUMENTS review
``` ```
Then evaluate the review results and set the appropriate status:
- If the review has **NO blockers**: mark as passed
```bash
sdlc artifact pass $ARGUMENTS review
```
- If the review has **blockers**: mark as needs-fix
```bash
sdlc artifact needs-fix $ARGUMENTS review
```
This status drives the SDLC classifier to either advance to audit or trigger fix-review-issues.
## Critical Rules ## Critical Rules
- ALWAYS read spec and design before reviewing code - ALWAYS read spec and design before reviewing code
- NEVER skip the security review dimension - NEVER skip the security review dimension
- ALWAYS check test coverage against acceptance criteria - ALWAYS check test coverage against acceptance criteria
- ALWAYS provide actionable findings with file locations - ALWAYS provide actionable findings with file locations
- NEVER approve a review with unresolved blockers - NEVER mark a review as passed if it has unresolved blockers
- ALWAYS set the artifact status (pass or needs-fix) after writing the review

View File

@ -77,12 +77,27 @@ Write to `.sdlc/features/$ARGUMENTS/qa-results.md`:
[Detailed description of each failure with reproduction steps] [Detailed description of each failure with reproduction steps]
``` ```
### 6. Register the Artifact ### 6. Register and Evaluate the Artifact
Create the artifact:
```bash ```bash
sdlc artifact create $ARGUMENTS qa_results sdlc artifact create $ARGUMENTS qa_results
``` ```
Then evaluate the QA results and set the appropriate status:
- If **all scenarios pass** and all acceptance criteria are covered: mark as passed
```bash
sdlc artifact pass $ARGUMENTS qa_results
```
- If **any scenario fails** or acceptance criteria have gaps: mark as failed
```bash
sdlc artifact fail $ARGUMENTS qa_results
```
This status drives the SDLC classifier to either advance to merge or trigger fix-qa-failures.
## Critical Rules ## Critical Rules
- ALWAYS execute every scenario from the QA plan -- no skipping - ALWAYS execute every scenario from the QA plan -- no skipping
@ -90,3 +105,4 @@ sdlc artifact create $ARGUMENTS qa_results
- ALWAYS document ALL results, including passing scenarios - ALWAYS document ALL results, including passing scenarios
- ALWAYS verify acceptance criteria coverage explicitly - ALWAYS verify acceptance criteria coverage explicitly
- NEVER fabricate test evidence -- run the actual tests - NEVER fabricate test evidence -- run the actual tests
- ALWAYS set the artifact status (pass or fail) after writing QA results

View File

@ -55,6 +55,9 @@ func (h *SDLCHandler) Mount(r api.Router) {
// Artifacts - write // Artifacts - write
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/features/{slug}/artifacts/{type}/approve", h.ApproveArtifact) r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/features/{slug}/artifacts/{type}/approve", h.ApproveArtifact)
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/features/{slug}/artifacts/{type}/reject", h.RejectArtifact) r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/features/{slug}/artifacts/{type}/reject", h.RejectArtifact)
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/features/{slug}/artifacts/{type}/pass", h.PassArtifact)
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/features/{slug}/artifacts/{type}/fail", h.FailArtifact)
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/features/{slug}/artifacts/{type}/needs-fix", h.NeedsFixArtifact)
// Tasks - read // Tasks - read
r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).Get("/features/{slug}/tasks", h.ListTasks) r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).Get("/features/{slug}/tasks", h.ListTasks)

View File

@ -82,3 +82,87 @@ func (h *SDLCHandler) RejectArtifact(w http.ResponseWriter, r *http.Request) {
"status": "rejected", "status": "rejected",
}) })
} }
// PassArtifact marks a feature artifact as passed (for execution artifacts).
// POST /projects/{id}/sdlc/features/{slug}/artifacts/{type}/pass
func (h *SDLCHandler) PassArtifact(w http.ResponseWriter, r *http.Request) {
projectID := chi.URLParam(r, "id")
slug := chi.URLParam(r, "slug")
artTypeStr := chi.URLParam(r, "type")
artType := sdlc.ArtifactType(artTypeStr)
if !sdlc.IsValidArtifactType(artType) {
api.WriteBadRequest(w, r, "invalid artifact type: "+artTypeStr)
return
}
ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite)
defer cancel()
if err := h.sdlcService.PassArtifact(ctx, projectID, slug, artType); err != nil {
writeSDLCError(w, r, err)
return
}
api.WriteSuccess(w, r, map[string]any{
"feature": slug,
"artifact": artTypeStr,
"status": "passed",
})
}
// FailArtifact marks a feature artifact as failed (for execution artifacts).
// POST /projects/{id}/sdlc/features/{slug}/artifacts/{type}/fail
func (h *SDLCHandler) FailArtifact(w http.ResponseWriter, r *http.Request) {
projectID := chi.URLParam(r, "id")
slug := chi.URLParam(r, "slug")
artTypeStr := chi.URLParam(r, "type")
artType := sdlc.ArtifactType(artTypeStr)
if !sdlc.IsValidArtifactType(artType) {
api.WriteBadRequest(w, r, "invalid artifact type: "+artTypeStr)
return
}
ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite)
defer cancel()
if err := h.sdlcService.FailArtifact(ctx, projectID, slug, artType); err != nil {
writeSDLCError(w, r, err)
return
}
api.WriteSuccess(w, r, map[string]any{
"feature": slug,
"artifact": artTypeStr,
"status": "failed",
})
}
// NeedsFixArtifact marks a feature artifact as needing fixes.
// POST /projects/{id}/sdlc/features/{slug}/artifacts/{type}/needs-fix
func (h *SDLCHandler) NeedsFixArtifact(w http.ResponseWriter, r *http.Request) {
projectID := chi.URLParam(r, "id")
slug := chi.URLParam(r, "slug")
artTypeStr := chi.URLParam(r, "type")
artType := sdlc.ArtifactType(artTypeStr)
if !sdlc.IsValidArtifactType(artType) {
api.WriteBadRequest(w, r, "invalid artifact type: "+artTypeStr)
return
}
ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite)
defer cancel()
if err := h.sdlcService.NeedsFixArtifact(ctx, projectID, slug, artType); err != nil {
writeSDLCError(w, r, err)
return
}
api.WriteSuccess(w, r, map[string]any{
"feature": slug,
"artifact": artTypeStr,
"status": "needs_fix",
})
}

View File

@ -64,6 +64,15 @@ func (m *testSDLCExecutor) ApproveArtifact(_ context.Context, _, _ string, _ sdl
func (m *testSDLCExecutor) RejectArtifact(_ context.Context, _, _ string, _ sdlc.ArtifactType) error { func (m *testSDLCExecutor) RejectArtifact(_ context.Context, _, _ string, _ sdlc.ArtifactType) error {
return m.err return m.err
} }
func (m *testSDLCExecutor) PassArtifact(_ context.Context, _, _ string, _ sdlc.ArtifactType) error {
return m.err
}
func (m *testSDLCExecutor) FailArtifact(_ context.Context, _, _ string, _ sdlc.ArtifactType) error {
return m.err
}
func (m *testSDLCExecutor) NeedsFixArtifact(_ context.Context, _, _ string, _ sdlc.ArtifactType) error {
return m.err
}
func (m *testSDLCExecutor) ListTasks(_ context.Context, _, _ string) ([]sdlc.Task, error) { func (m *testSDLCExecutor) ListTasks(_ context.Context, _, _ string) ([]sdlc.Task, error) {
return m.tasks, m.err return m.tasks, m.err
} }

View File

@ -46,6 +46,15 @@ type SDLCExecutor interface {
// RejectArtifact rejects a feature artifact. // RejectArtifact rejects a feature artifact.
RejectArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error RejectArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error
// PassArtifact marks a feature artifact as passed (for execution artifacts).
PassArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error
// FailArtifact marks a feature artifact as failed (for execution artifacts).
FailArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error
// NeedsFixArtifact marks a feature artifact as needing fixes.
NeedsFixArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error
// ListTasks returns all tasks for a feature. // ListTasks returns all tasks for a feature.
ListTasks(ctx context.Context, podName, slug string) ([]sdlc.Task, error) ListTasks(ctx context.Context, podName, slug string) ([]sdlc.Task, error)

View File

@ -186,6 +186,45 @@ func (s *SDLCService) RejectArtifact(ctx context.Context, projectID, slug string
return nil return nil
} }
// PassArtifact marks a feature artifact as passed.
func (s *SDLCService) PassArtifact(ctx context.Context, projectID, slug string, artType sdlc.ArtifactType) error {
podName, err := s.resolveProjectPod(ctx, projectID)
if err != nil {
return err
}
if err := s.sdlcExec.PassArtifact(ctx, podName, slug, artType); err != nil {
return err
}
s.logger.Info("artifact passed", "project", projectID, "feature", slug, "artifact", string(artType))
return nil
}
// FailArtifact marks a feature artifact as failed.
func (s *SDLCService) FailArtifact(ctx context.Context, projectID, slug string, artType sdlc.ArtifactType) error {
podName, err := s.resolveProjectPod(ctx, projectID)
if err != nil {
return err
}
if err := s.sdlcExec.FailArtifact(ctx, podName, slug, artType); err != nil {
return err
}
s.logger.Info("artifact failed", "project", projectID, "feature", slug, "artifact", string(artType))
return nil
}
// NeedsFixArtifact marks a feature artifact as needing fixes.
func (s *SDLCService) NeedsFixArtifact(ctx context.Context, projectID, slug string, artType sdlc.ArtifactType) error {
podName, err := s.resolveProjectPod(ctx, projectID)
if err != nil {
return err
}
if err := s.sdlcExec.NeedsFixArtifact(ctx, podName, slug, artType); err != nil {
return err
}
s.logger.Info("artifact needs fix", "project", projectID, "feature", slug, "artifact", string(artType))
return nil
}
// ListTasks returns all tasks for a feature. // ListTasks returns all tasks for a feature.
func (s *SDLCService) ListTasks(ctx context.Context, projectID, slug string) ([]sdlc.Task, error) { func (s *SDLCService) ListTasks(ctx context.Context, projectID, slug string) ([]sdlc.Task, error) {
podName, err := s.resolveProjectPod(ctx, projectID) podName, err := s.resolveProjectPod(ctx, projectID)

View File

@ -24,6 +24,9 @@ type mockSDLCExecutor struct {
getArtifactStatusFn func(ctx context.Context, podName, slug string) (map[sdlc.ArtifactType]*sdlc.Artifact, error) getArtifactStatusFn func(ctx context.Context, podName, slug string) (map[sdlc.ArtifactType]*sdlc.Artifact, error)
approveArtifactFn func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error approveArtifactFn func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error
rejectArtifactFn func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error rejectArtifactFn func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error
passArtifactFn func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error
failArtifactFn func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error
needsFixArtifactFn func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error
listTasksFn func(ctx context.Context, podName, slug string) ([]sdlc.Task, error) listTasksFn func(ctx context.Context, podName, slug string) ([]sdlc.Task, error)
addTaskFn func(ctx context.Context, podName, slug, title string) (*sdlc.Task, error) addTaskFn func(ctx context.Context, podName, slug, title string) (*sdlc.Task, error)
startTaskFn func(ctx context.Context, podName, slug, taskID string) error startTaskFn func(ctx context.Context, podName, slug, taskID string) error
@ -123,6 +126,27 @@ func (m *mockSDLCExecutor) RejectArtifact(ctx context.Context, podName, slug str
return nil return nil
} }
func (m *mockSDLCExecutor) PassArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
if m.passArtifactFn != nil {
return m.passArtifactFn(ctx, podName, slug, artType)
}
return nil
}
func (m *mockSDLCExecutor) FailArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
if m.failArtifactFn != nil {
return m.failArtifactFn(ctx, podName, slug, artType)
}
return nil
}
func (m *mockSDLCExecutor) NeedsFixArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
if m.needsFixArtifactFn != nil {
return m.needsFixArtifactFn(ctx, podName, slug, artType)
}
return nil
}
func (m *mockSDLCExecutor) ListTasks(ctx context.Context, podName, slug string) ([]sdlc.Task, error) { func (m *mockSDLCExecutor) ListTasks(ctx context.Context, podName, slug string) ([]sdlc.Task, error) {
if m.listTasksFn != nil { if m.listTasksFn != nil {
return m.listTasksFn(ctx, podName, slug) return m.listTasksFn(ctx, podName, slug)