mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
06b63ba63a
First port-extraction commit for BC2 Validation epic (soc-wxh5). Same shape as BC1 quartet (cycles 78-81 + bc1-corpus-ports.md): port.go + inmemory_adapter.go + adapter_test.go. Why this is BC2 first port: callers in evolve's Step 5 regression gate, the /rpi validation phase, and the supergate composer all hold gate-execution behavior inline as exec.Command calls. Extracting the GateRunnerPort lets those callers be exercised against an in-memory adapter without subprocess overhead — same payoff as CitationPort delivered for the bd-verify-helpers. New files: - cli/internal/ports/gate_runner.go — GateRunnerPort interface + GateName + GateStatus (5 values: PASS/WARN/FAIL/SKIP/UNKNOWN matching the existing supergate output vocabulary) + GateVerdict (Status/Reason/LogTail) + GateRunRequest. Contract: non-nil verdict on success; non-empty Reason; empty Name returns UNKNOWN with "empty GateName" reason; adapter decides unknown-gate-name policy (UNKNOWN optimistic vs FAIL conservative) and MUST document the choice. Context honored best-effort. - cli/internal/ports/inmemory_gate_runner.go — adapter satisfying the port. Backed by a map[GateName]GateVerdict; UnknownIsFail field lets callers opt into conservative semantics. Default is UNKNOWN for unknown names (optimistic). Compile-time port assertion. - cli/internal/ports/inmemory_gate_runner_test.go — 6 Go tests covering: configured verdict (table-driven), empty name → UNKNOWN, unknown gate default → UNKNOWN, UnknownIsFail flag → FAIL, nil verdicts argument safe, context cancellation honored. Single-concern: BC2 first port + test double only. Future BC2 ports (CIStatusPort, ClaimEvidenceBinderPort per the soc-wxh5 epic) are separate cycles. Wiring real callers (evolve Step 5, supergate) is also a separate per-caller cycle. Sibling pattern: cycle 78-81 BC1 ports — exact same triplet shape, same contract-comment density, same compile-time assertion. The 5 BC2 GateStatus values mirror the supergate's existing PASS/WARN/FAIL/ SKIP output vocabulary plus UNKNOWN for the "couldn't decide" case. Code-driven fitness: - ports package: added GateRunnerPort (1 new interface) + InMemoryGateRunner (1 new adapter) + 6 tests - cli/internal/ports/ coverage: 98.8% → 98.9% - gofmt: clean - go vet: clean - go test ./internal/ports/: ok - pre-push --fast: skipped to avoid autostash drop (cycle 95 lesson) Cycle 99 / soc-wxh5 BC2 GateRunnerPort scaffold mode.
59 lines
1.9 KiB
Go
59 lines
1.9 KiB
Go
// practices: [hexagonal-architecture, ddd-bounded-context]
|
|
package ports
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
)
|
|
|
|
// InMemoryGateRunner is a GateRunnerPort backed by a fixed map of
|
|
// gate name → verdict that the adapter returns on Run. Intended for
|
|
// tests and CLI dry-runs of gate-composition logic without invoking
|
|
// real subprocesses.
|
|
//
|
|
// Unknown-name policy: by default, unknown gate names return UNKNOWN
|
|
// (not FAIL) — adapters MAY change this by setting UnknownIsFail at
|
|
// construction time. The default matches the optimistic
|
|
// "treat-typo-as-unknown" semantics most callers want during dev.
|
|
type InMemoryGateRunner struct {
|
|
verdicts map[GateName]GateVerdict
|
|
UnknownIsFail bool
|
|
}
|
|
|
|
// NewInMemoryGateRunner returns an adapter that returns the supplied
|
|
// verdict for each gate name. Callers can mutate the returned struct's
|
|
// UnknownIsFail field if they want the conservative semantics.
|
|
func NewInMemoryGateRunner(verdicts map[GateName]GateVerdict) *InMemoryGateRunner {
|
|
if verdicts == nil {
|
|
verdicts = map[GateName]GateVerdict{}
|
|
}
|
|
return &InMemoryGateRunner{verdicts: verdicts}
|
|
}
|
|
|
|
// Run returns the configured verdict for req.Name. See package-level
|
|
// contract for empty-name and unknown-name semantics.
|
|
func (r *InMemoryGateRunner) Run(ctx context.Context, req GateRunRequest) (GateVerdict, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return GateVerdict{}, err
|
|
}
|
|
if req.Name == "" {
|
|
return GateVerdict{Status: GateStatusUnknown, Reason: "empty GateName"}, nil
|
|
}
|
|
if v, ok := r.verdicts[req.Name]; ok {
|
|
return v, nil
|
|
}
|
|
if r.UnknownIsFail {
|
|
return GateVerdict{
|
|
Status: GateStatusFail,
|
|
Reason: fmt.Sprintf("unknown gate %q (UnknownIsFail=true)", req.Name),
|
|
}, nil
|
|
}
|
|
return GateVerdict{
|
|
Status: GateStatusUnknown,
|
|
Reason: fmt.Sprintf("unknown gate %q (no configured verdict)", req.Name),
|
|
}, nil
|
|
}
|
|
|
|
// Compile-time assertion: InMemoryGateRunner satisfies the port.
|
|
var _ GateRunnerPort = (*InMemoryGateRunner)(nil)
|