Files
Claude (gsc-ops-api init) 3847eb2036 Initial import — snapshot from admin host /srv/gosec/gsc-ops-api
This repo had no version control prior to this commit. The import is a
straight snapshot of the working tree at 2026-05-03; the deployed
binary on fihelvop01 was being rebuilt from this source via `make
build` + scp into place, with no upstream review path.

The snapshot already includes one in-flight fix made on 2026-05-03 to
internal/service/persona.go:GetSelfModel — the handler queried
`source` and `strength` columns plus an `is_active = true` filter on
persona.persona_commitments, none of which exist on that table (its
shape is session-bound commitments with `status`, `commitment_meta`,
etc.). The query returned a 500 every time SynapseHub bootstrapped a
persona's self-model, dropping the IdentityConstraints / Commitments /
ConscienceStandards layer from the assembled prompt. The patched
query reads existing columns only (commitment_text, commitment_type),
filters on `status='active'`, and synthesises Source="learned" /
Strength=1.0 to keep the SelfModel response shape stable for callers.

Verified live: `GET /api/v1/personas/70f7cfd9-.../self-model` now
returns 200 with `{identityConstraints:[],commitments:[],
conscienceStandards:[]}` instead of 500.

Future changes go through PRs against this repo — no more bin-only
deploys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:06:02 +02:00

111 lines
2.3 KiB
Go

package service
import (
"bufio"
"strings"
"github.com/rs/zerolog"
"github.com/gosec/gsc-ops-api/internal/client"
"github.com/gosec/gsc-ops-api/pkg/types"
)
// PGPService handles Hockeypuck PGP key operations
type PGPService struct {
client *client.HockeypuckClient
logger zerolog.Logger
}
// NewPGPService creates a new PGP service
func NewPGPService(hkpClient *client.HockeypuckClient, logger zerolog.Logger) *PGPService {
return &PGPService{
client: hkpClient,
logger: logger.With().Str("service", "pgp").Logger(),
}
}
// SearchKeys searches for PGP keys
func (s *PGPService) SearchKeys(query string) ([]types.PGPKey, error) {
result, err := s.client.SearchKeys(query)
if err != nil {
return nil, err
}
if result == "" {
return []types.PGPKey{}, nil
}
return parseMachineReadableIndex(result), nil
}
// GetKey retrieves a PGP key by key ID
func (s *PGPService) GetKey(keyID string) (*types.PGPKey, error) {
armoredKey, err := s.client.GetKey(keyID)
if err != nil {
return nil, err
}
if armoredKey == "" {
return nil, nil
}
return &types.PGPKey{
KeyID: keyID,
ArmoredKey: armoredKey,
}, nil
}
// UploadKey uploads a PGP public key
func (s *PGPService) UploadKey(keyText string) error {
return s.client.UploadKey(keyText)
}
// DeleteKey deletes a PGP key
func (s *PGPService) DeleteKey(keyID string) error {
return s.client.DeleteKey(keyID)
}
// parseMachineReadableIndex parses the HKP machine-readable index format
func parseMachineReadableIndex(data string) []types.PGPKey {
keys := []types.PGPKey{}
var current *types.PGPKey
scanner := bufio.NewScanner(strings.NewReader(data))
for scanner.Scan() {
line := scanner.Text()
fields := strings.Split(line, ":")
if len(fields) < 2 {
continue
}
switch fields[0] {
case "pub":
if current != nil {
keys = append(keys, *current)
}
current = &types.PGPKey{}
if len(fields) > 1 {
current.KeyID = fields[1]
}
if len(fields) > 2 {
current.Algorithm = fields[2]
}
if len(fields) > 4 {
current.Created = fields[4]
}
if len(fields) > 5 {
current.Expires = fields[5]
}
case "uid":
if current != nil && len(fields) > 1 {
current.UIDs = append(current.UIDs, fields[1])
}
}
}
if current != nil {
keys = append(keys, *current)
}
return keys
}