feat(auth): add first-party acceptance harness
CI / test (push) Failing after 1m4s

This commit is contained in:
m
2026-09-01 23:13:15 +03:00
parent 750c87c926
commit 81ce8dfdb2
17 changed files with 356 additions and 10 deletions
+108
View File
@@ -0,0 +1,108 @@
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"git.cin.su/m/Assistant/contracts/identity"
"git.cin.su/m/Assistant/contracts/interaction"
)
const tokenPath = "/run/secrets/acceptance_harness_service_token"
type client struct {
base string
token string
http *http.Client
}
func main() {
base := strings.TrimRight(os.Getenv("CORE_RUNTIME_URL"), "/")
if base == "" {
base = "http://core-runtime-candidate:8080"
}
token, err := os.ReadFile(tokenPath)
if err != nil {
fail("acceptance credential unavailable")
}
c := client{base: base, token: strings.TrimSpace(string(token)), http: &http.Client{Timeout: 90 * time.Second}}
if strings.TrimSpace(c.token) == "" {
fail("acceptance credential unavailable")
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
var issued struct {
Token string
Session identity.CreatorSession
}
if err := c.post(ctx, "/v1/acceptance/sessions", nil, &issued); err != nil {
fail("acceptance authentication failed")
}
if issued.Token == "" || issued.Session.PrincipalKind != identity.PrincipalKindService {
fail("acceptance principal was not authenticated as service")
}
c.token = issued.Token
var first interaction.ChatMessageResponse
if err := c.post(ctx, "/v1/creator/chat/messages", interaction.ChatMessageRequest{Message: "synthetic acceptance statement", CorrelationID: "acceptance-harness", SyntheticAcceptance: true, SyntheticScopeID: "synthetic-acceptance-scope"}, &first); err != nil {
fail("synthetic interaction failed")
}
var corrected interaction.ChatMessageResponse
if err := c.post(ctx, "/v1/creator/chat/messages", interaction.ChatMessageRequest{ConversationID: first.ConversationID, Message: "synthetic acceptance correction", CorrelationID: "acceptance-harness-correction", CorrectsUtteranceID: first.UtteranceID, SyntheticAcceptance: true, SyntheticScopeID: "synthetic-acceptance-scope"}, &corrected); err != nil {
fail("synthetic correction failed")
}
var summary struct {
LearningSignals int `json:"learning_signals"`
ImprovementNeeds int `json:"improvement_needs"`
LearningLedger int `json:"learning_ledger"`
}
if err := c.get(ctx, "/v1/acceptance/self/summary", &summary); err != nil {
fail("acceptance SELF summary unavailable")
}
fmt.Printf("acceptance_pass learning_signals=%d improvement_needs=%d learning_ledger=%d\n", summary.LearningSignals, summary.ImprovementNeeds, summary.LearningLedger)
}
func (c client) post(ctx context.Context, path string, body any, out any) error {
return c.request(ctx, http.MethodPost, path, body, out)
}
func (c client) get(ctx context.Context, path string, out any) error {
return c.request(ctx, http.MethodGet, path, nil, out)
}
func (c client) request(ctx context.Context, method, path string, body any, out any) error {
var reader io.Reader
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return err
}
reader = bytes.NewReader(data)
}
req, err := http.NewRequestWithContext(ctx, method, c.base+path, reader)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+c.token)
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := c.http.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return errors.New("acceptance request denied")
}
if out != nil && json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(out) != nil {
return errors.New("invalid acceptance response")
}
return nil
}
func fail(message string) { fmt.Fprintln(os.Stderr, message); os.Exit(1) }
+74 -6
View File
@@ -47,6 +47,7 @@ const (
epistemicsDBPasswordPath = "/run/secrets/epistemics_db_password"
interactionDBPasswordPath = "/run/secrets/interaction_db_password"
selfDBPasswordPath = "/run/secrets/self_db_password"
acceptanceHarnessTokenPath = "/run/secrets/acceptance_harness_service_token"
controlPlaneServiceTokenFile = "control_plane_service_token"
)
@@ -162,6 +163,18 @@ func main() {
if err := bootstrapCreatorAccount(context.Background(), identityRepository, creatorUsername, creatorPasswordPath); err != nil {
log.Fatal(err)
}
acceptanceServiceToken := ""
var acceptancePrincipal identity.ServicePrincipal
if os.Getenv("ACCEPTANCE_HARNESS_SERVICE_CREDENTIAL_REF") != "" {
acceptanceServiceToken, err = readCredentialRefSecretPurpose("ACCEPTANCE_HARNESS_SERVICE_TOKEN", "ACCEPTANCE_HARNESS_SERVICE_CREDENTIAL_REF", acceptanceHarnessTokenPath, secretcontracts.SecretPurposeServiceAuthentication)
if err != nil {
log.Fatal(err)
}
acceptancePrincipal, err = identityRepository.LoadServicePrincipal(context.Background(), identity.PrincipalID("domovoy-acceptance-harness-principal"))
if err != nil || !acceptancePrincipal.Active {
log.Fatal("acceptance service principal is unavailable")
}
}
interactionPassword, err := readCredentialRefSecret("INTERACTION_POSTGRES_PASSWORD", "INTERACTION_POSTGRES_CREDENTIAL_REF", interactionDBPasswordPath)
if err != nil {
log.Fatal(err)
@@ -230,12 +243,32 @@ func main() {
mux := http.NewServeMux()
authenticateCreator := func(w http.ResponseWriter, r *http.Request) (identity.CreatorSession, bool) {
session, err := identitydomain.ValidateCreatorSession(sessionKey, bearerToken(r), time.Now())
if err != nil {
if err != nil || (session.PrincipalKind != "" && session.PrincipalKind != identity.PrincipalKindHuman) {
http.Error(w, "creator session denied", http.StatusUnauthorized)
return identity.CreatorSession{}, false
}
return session, true
}
authenticateAcceptance := func(w http.ResponseWriter, r *http.Request) (identity.CreatorSession, bool) {
if acceptanceServiceToken == "" || acceptancePrincipal.Kind != identity.PrincipalKindService || !acceptancePrincipal.Active || !identitydomain.BootstrapMatches(acceptanceServiceToken, bearerToken(r)) {
http.Error(w, "acceptance authentication denied", http.StatusUnauthorized)
return identity.CreatorSession{}, false
}
_, session, err := identitydomain.IssueServiceSession(sessionKey, acceptancePrincipal, time.Now(), 30*time.Minute)
if err != nil {
http.Error(w, "acceptance session unavailable", http.StatusServiceUnavailable)
return identity.CreatorSession{}, false
}
return session, true
}
authenticateAcceptanceSession := func(w http.ResponseWriter, r *http.Request) (identity.CreatorSession, bool) {
session, err := identitydomain.ValidateCreatorSession(sessionKey, bearerToken(r), time.Now())
if err != nil || session.PrincipalKind != identity.PrincipalKindService || session.PrincipalID != acceptancePrincipal.PrincipalID || !acceptancePrincipal.Active {
http.Error(w, "acceptance session denied", http.StatusUnauthorized)
return identity.CreatorSession{}, false
}
return session, true
}
resolveFocus := func(r *http.Request) (situation.FocusBinding, error) {
runtime := situation.RuntimeBinding{SessionID: situation.SessionID(r.URL.Query().Get("session_id")), Workspace: situation.WorkspaceID(r.URL.Query().Get("workspace_id"))}
bindings, err := repository.ByRuntime(r.Context(), runtime)
@@ -286,6 +319,35 @@ func main() {
w.WriteHeader(http.StatusNoContent)
})
mux.HandleFunc("POST /v1/creator/sessions", creatorDatabaseSessionHandler(bootstrapToken, creatorUsername, identityRepository, sessionKey, time.Now))
mux.HandleFunc("POST /v1/acceptance/sessions", func(w http.ResponseWriter, r *http.Request) {
session, ok := authenticateAcceptance(w, r)
if !ok {
return
}
token, issued, err := identitydomain.IssueServiceSession(sessionKey, acceptancePrincipal, time.Now(), 30*time.Minute)
if err != nil {
http.Error(w, "acceptance session unavailable", http.StatusServiceUnavailable)
return
}
_ = session
writeCreatorSession(w, token, issued)
})
mux.HandleFunc("GET /v1/acceptance/self/summary", func(w http.ResponseWriter, r *http.Request) {
if _, ok := authenticateAcceptanceSession(w, r); !ok {
return
}
if selfRepository == nil {
http.Error(w, "SELF unavailable", http.StatusServiceUnavailable)
return
}
summary, err := selfRepository.AcceptanceSummary(r.Context(), string(acceptancePrincipal.PrincipalID))
if err != nil {
http.Error(w, "SELF summary unavailable", http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(summary)
})
mux.HandleFunc("POST /v1/creator/account/password", func(w http.ResponseWriter, r *http.Request) {
session, ok := authenticateCreator(w, r)
if !ok {
@@ -596,15 +658,21 @@ func main() {
}{Experiences: experiences, Reflections: reflections, Skills: skills})
})
mux.HandleFunc("POST /v1/creator/chat/messages", func(w http.ResponseWriter, r *http.Request) {
var request interaction.ChatMessageRequest
body, readErr := io.ReadAll(http.MaxBytesReader(w, r.Body, interactiondomain.MaxHumanUtteranceBytes+1024))
if readErr != nil || json.Unmarshal(body, &request) != nil {
http.Error(w, "invalid chat message", http.StatusBadRequest)
return
}
session, ok := authenticateCreator(w, r)
if !ok && request.SyntheticAcceptance {
session, ok = authenticateAcceptanceSession(w, r)
}
if !ok {
return
}
var request interaction.ChatMessageRequest
decoder := json.NewDecoder(io.LimitReader(r.Body, interactiondomain.MaxHumanUtteranceBytes+1024))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil || decoder.Decode(&struct{}{}) != io.EOF {
http.Error(w, "invalid chat message", http.StatusBadRequest)
if session.PrincipalKind == identity.PrincipalKindService && (!request.SyntheticAcceptance || request.SyntheticScopeID != acceptancePrincipal.SyntheticScope) {
http.Error(w, "synthetic acceptance scope denied", http.StatusForbidden)
return
}
now := time.Now()
+4
View File
@@ -17,6 +17,7 @@ const (
func main() {
reason := flag.String("reason", "", "rotation reason: owner-requested-rotation or exposure-detected")
credential := flag.String("credential", "control-plane", "credential purpose: control-plane or acceptance-harness")
flag.Parse()
if flag.NArg() != 1 {
fail("usage: secret-control-plane [ensure|rotate]")
@@ -26,6 +27,9 @@ func main() {
fail("control-plane credential store unavailable")
}
spec := platformsecrets.ControlPlaneServiceTokenSpec()
if strings.TrimSpace(*credential) == "acceptance-harness" {
spec = platformsecrets.AcceptanceHarnessServiceTokenSpec()
}
switch strings.TrimSpace(flag.Arg(0)) {
case "ensure":
result, err := store.Ensure(spec)