refactor: upgrade core v0.8.0-alpha.1 and replace banned imports

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-26 15:21:24 +00:00
parent 87f87bfc0a
commit f46cd04e2f
30 changed files with 485 additions and 339 deletions

View file

@ -29,12 +29,10 @@ import (
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
"sync"
"time"
core "dappco.re/go/core"
"dappco.re/go/core/crypt/crypt"
"dappco.re/go/core/crypt/crypt/lthn"
"dappco.re/go/core/crypt/crypt/pgp"
@ -218,12 +216,13 @@ func (a *Authenticator) Register(username, password string) (*User, error) {
}
// Encrypt metadata with the user's public key and store
metaJSON, err := json.Marshal(user)
if err != nil {
metaJSONResult := core.JSONMarshal(user)
if !metaJSONResult.OK {
err, _ := metaJSONResult.Value.(error)
return nil, coreerr.E(op, "failed to marshal user metadata", err)
}
encMeta, err := pgp.Encrypt(metaJSON, kp.PublicKey)
encMeta, err := pgp.Encrypt(metaJSONResult.Value.([]byte), kp.PublicKey)
if err != nil {
return nil, coreerr.E(op, "failed to encrypt user metadata", err)
}
@ -419,7 +418,7 @@ func (a *Authenticator) Login(userID, password string) (*Session, error) {
return nil, coreerr.E(op, "failed to read password hash", err)
}
if strings.HasPrefix(storedHash, "$argon2id$") {
if core.HasPrefix(storedHash, "$argon2id$") {
valid, err := crypt.VerifyPassword(password, storedHash)
if err != nil {
return nil, coreerr.E(op, "failed to verify password", err)
@ -482,7 +481,9 @@ func (a *Authenticator) RotateKeyPair(userID, oldPassword, newPassword string) (
}
var user User
if err := json.Unmarshal(metaJSON, &user); err != nil {
metaResult := core.JSONUnmarshal(metaJSON, &user)
if !metaResult.OK {
err, _ := metaResult.Value.(error)
return nil, coreerr.E(op, "failed to unmarshal user metadata", err)
}
@ -504,12 +505,13 @@ func (a *Authenticator) RotateKeyPair(userID, oldPassword, newPassword string) (
user.PasswordHash = newHash
// Re-encrypt metadata with new public key
updatedMeta, err := json.Marshal(&user)
if err != nil {
updatedMetaResult := core.JSONMarshal(&user)
if !updatedMetaResult.OK {
err, _ := updatedMetaResult.Value.(error)
return nil, coreerr.E(op, "failed to marshal updated metadata", err)
}
encUpdatedMeta, err := pgp.Encrypt(updatedMeta, newKP.PublicKey)
encUpdatedMeta, err := pgp.Encrypt(updatedMetaResult.Value.([]byte), newKP.PublicKey)
if err != nil {
return nil, coreerr.E(op, "failed to encrypt metadata with new key", err)
}
@ -556,11 +558,12 @@ func (a *Authenticator) RevokeKey(userID, password, reason string) error {
Reason: reason,
RevokedAt: time.Now(),
}
revJSON, err := json.Marshal(&rev)
if err != nil {
revJSONResult := core.JSONMarshal(&rev)
if !revJSONResult.OK {
err, _ := revJSONResult.Value.(error)
return coreerr.E(op, "failed to marshal revocation record", err)
}
if err := a.medium.Write(userPath(userID, ".rev"), string(revJSON)); err != nil {
if err := a.medium.Write(userPath(userID, ".rev"), string(revJSONResult.Value.([]byte))); err != nil {
return coreerr.E(op, "failed to write revocation record", err)
}
@ -586,7 +589,8 @@ func (a *Authenticator) IsRevoked(userID string) bool {
// Attempt to parse as JSON revocation record
var rev Revocation
if err := json.Unmarshal([]byte(content), &rev); err != nil {
revResult := core.JSONUnmarshal([]byte(content), &rev)
if !revResult.OK {
return false
}
@ -605,12 +609,13 @@ func (a *Authenticator) WriteChallengeFile(userID, path string) error {
return coreerr.E(op, "failed to create challenge", err)
}
data, err := json.Marshal(challenge)
if err != nil {
challengeResult := core.JSONMarshal(challenge)
if !challengeResult.OK {
err, _ := challengeResult.Value.(error)
return coreerr.E(op, "failed to marshal challenge", err)
}
if err := a.medium.Write(path, string(data)); err != nil {
if err := a.medium.Write(path, string(challengeResult.Value.([]byte))); err != nil {
return coreerr.E(op, "failed to write challenge file", err)
}
@ -645,7 +650,7 @@ func (a *Authenticator) verifyPassword(userID, password string) error {
// Try Argon2id hash first (.hash file)
if a.medium.IsFile(userPath(userID, ".hash")) {
storedHash, err := a.medium.Read(userPath(userID, ".hash"))
if err == nil && strings.HasPrefix(storedHash, "$argon2id$") {
if err == nil && core.HasPrefix(storedHash, "$argon2id$") {
valid, verr := crypt.VerifyPassword(password, storedHash)
if verr != nil {
return coreerr.E(op, "failed to verify password", nil)
@ -705,11 +710,11 @@ func (a *Authenticator) StartCleanup(ctx context.Context, interval time.Duration
case <-ticker.C:
count, err := a.store.Cleanup()
if err != nil {
fmt.Printf("auth: session cleanup error: %v\n", err)
core.Print(nil, "auth: session cleanup error: %v", err)
continue
}
if count > 0 {
fmt.Printf("auth: cleaned up %d expired session(s)\n", count)
core.Print(nil, "auth: cleaned up %d expired session(s)", count)
}
}
}

View file

@ -1,13 +1,11 @@
package auth
import (
"encoding/json"
"fmt"
"strings"
"sync"
"testing"
"time"
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -46,7 +44,7 @@ func TestRegister_Good(t *testing.T) {
assert.NotEmpty(t, user.PublicKey)
assert.Equal(t, userID, user.KeyID)
assert.NotEmpty(t, user.Fingerprint)
assert.True(t, strings.HasPrefix(user.PasswordHash, "$argon2id$"), "password hash should be Argon2id format")
assert.True(t, core.HasPrefix(user.PasswordHash, "$argon2id$"), "password hash should be Argon2id format")
assert.False(t, user.Created.IsZero())
}
@ -414,8 +412,8 @@ func TestAirGappedFlow_Good(t *testing.T) {
require.NoError(t, err)
var challenge Challenge
err = json.Unmarshal([]byte(challengeData), &challenge)
require.NoError(t, err)
result := core.JSONUnmarshal([]byte(challengeData), &challenge)
require.Truef(t, result.OK, "failed to unmarshal challenge: %v", result.Value)
// Client-side: decrypt nonce and sign it
privKey, err := m.Read(userPath(userID, ".key"))
@ -590,7 +588,7 @@ func TestConcurrentSessionCreation_Good(t *testing.T) {
const n = 10
userIDs := make([]string, n)
for i := range n {
username := fmt.Sprintf("concurrent-user-%d", i)
username := core.Sprintf("concurrent-user-%d", i)
_, err := a.Register(username, "pass")
require.NoError(t, err)
userIDs[i] = lthn.Hash(username)
@ -736,7 +734,11 @@ func TestEmptyPasswordRegistration_Good(t *testing.T) {
func TestVeryLongUsername_Ugly(t *testing.T) {
a, _ := newTestAuth()
longUsername := strings.Repeat("a", 10000)
longName := core.NewBuilder()
for range 10000 {
longName.WriteString("a")
}
longUsername := longName.String()
user, err := a.Register(longUsername, "pass")
require.NoError(t, err)
require.NotNil(t, user)
@ -795,8 +797,8 @@ func TestAirGappedRoundTrip_Good(t *testing.T) {
require.NoError(t, err)
var challenge Challenge
err = json.Unmarshal([]byte(challengeData), &challenge)
require.NoError(t, err)
result := core.JSONUnmarshal([]byte(challengeData), &challenge)
require.Truef(t, result.OK, "failed to unmarshal challenge: %v", result.Value)
assert.NotEmpty(t, challenge.Encrypted)
assert.True(t, challenge.ExpiresAt.After(time.Now()))
@ -870,13 +872,13 @@ func TestRegisterArgon2id_Good(t *testing.T) {
assert.True(t, m.IsFile(userPath(userID, ".hash")))
hashContent, err := m.Read(userPath(userID, ".hash"))
require.NoError(t, err)
assert.True(t, strings.HasPrefix(hashContent, "$argon2id$"), "stored hash should be Argon2id")
assert.True(t, core.HasPrefix(hashContent, "$argon2id$"), "stored hash should be Argon2id")
// .lthn file should NOT exist for new registrations
assert.False(t, m.IsFile(userPath(userID, ".lthn")))
// User struct should have Argon2id hash
assert.True(t, strings.HasPrefix(user.PasswordHash, "$argon2id$"))
assert.True(t, core.HasPrefix(user.PasswordHash, "$argon2id$"))
}
// TestLoginArgon2id_Good verifies login works with Argon2id hashed password.
@ -940,7 +942,7 @@ func TestLegacyLTHNMigration_Good(t *testing.T) {
assert.True(t, m.IsFile(userPath(userID, ".hash")), "migration should create .hash file")
newHash, err := m.Read(userPath(userID, ".hash"))
require.NoError(t, err)
assert.True(t, strings.HasPrefix(newHash, "$argon2id$"), "migrated hash should be Argon2id")
assert.True(t, core.HasPrefix(newHash, "$argon2id$"), "migrated hash should be Argon2id")
// Subsequent login should use the new Argon2id hash (not LTHN)
session2, err := a.Login(userID, "legacy-pass")
@ -1024,10 +1026,10 @@ func TestRotateKeyPair_Good(t *testing.T) {
require.NoError(t, err)
var meta User
err = json.Unmarshal(decrypted, &meta)
require.NoError(t, err)
result := core.JSONUnmarshal(decrypted, &meta)
require.Truef(t, result.OK, "failed to unmarshal metadata: %v", result.Value)
assert.Equal(t, userID, meta.KeyID)
assert.True(t, strings.HasPrefix(meta.PasswordHash, "$argon2id$"))
assert.True(t, core.HasPrefix(meta.PasswordHash, "$argon2id$"))
}
// TestRotateKeyPair_Bad verifies that rotation fails with wrong old password.
@ -1108,8 +1110,8 @@ func TestRevokeKey_Good(t *testing.T) {
assert.NotEqual(t, "REVOCATION_PLACEHOLDER", revContent)
var rev Revocation
err = json.Unmarshal([]byte(revContent), &rev)
require.NoError(t, err)
result := core.JSONUnmarshal([]byte(revContent), &rev)
require.Truef(t, result.OK, "failed to unmarshal revocation: %v", result.Value)
assert.Equal(t, userID, rev.UserID)
assert.Equal(t, "compromised key material", rev.Reason)
assert.False(t, rev.RevokedAt.IsZero())

View file

@ -1,11 +1,10 @@
package auth
import (
"encoding/json"
"errors"
"sync"
"time"
core "dappco.re/go/core"
"dappco.re/go/core/store"
)
@ -35,14 +34,16 @@ func (s *SQLiteSessionStore) Get(token string) (*Session, error) {
val, err := s.store.Get(sessionGroup, token)
if err != nil {
if errors.Is(err, store.ErrNotFound) {
if core.Is(err, store.ErrNotFound) {
return nil, ErrSessionNotFound
}
return nil, err
}
var session Session
if err := json.Unmarshal([]byte(val), &session); err != nil {
result := core.JSONUnmarshal([]byte(val), &session)
if !result.OK {
err, _ := result.Value.(error)
return nil, err
}
return &session, nil
@ -53,11 +54,12 @@ func (s *SQLiteSessionStore) Set(session *Session) error {
s.mu.Lock()
defer s.mu.Unlock()
data, err := json.Marshal(session)
if err != nil {
result := core.JSONMarshal(session)
if !result.OK {
err, _ := result.Value.(error)
return err
}
return s.store.Set(sessionGroup, session.Token, string(data))
return s.store.Set(sessionGroup, session.Token, string(result.Value.([]byte)))
}
// Delete removes a session by token from SQLite.
@ -68,7 +70,7 @@ func (s *SQLiteSessionStore) Delete(token string) error {
// Check existence first to return ErrSessionNotFound
_, err := s.store.Get(sessionGroup, token)
if err != nil {
if errors.Is(err, store.ErrNotFound) {
if core.Is(err, store.ErrNotFound) {
return ErrSessionNotFound
}
return err
@ -88,7 +90,8 @@ func (s *SQLiteSessionStore) DeleteByUser(userID string) error {
for token, val := range all {
var session Session
if err := json.Unmarshal([]byte(val), &session); err != nil {
result := core.JSONUnmarshal([]byte(val), &session)
if !result.OK {
continue // Skip malformed entries
}
if session.UserID == userID {
@ -114,7 +117,8 @@ func (s *SQLiteSessionStore) Cleanup() (int, error) {
count := 0
for token, val := range all {
var session Session
if err := json.Unmarshal([]byte(val), &session); err != nil {
result := core.JSONUnmarshal([]byte(val), &session)
if !result.OK {
continue // Skip malformed entries
}
if now.After(session.ExpiresAt) {

View file

@ -2,13 +2,11 @@ package auth
import (
"context"
"fmt"
"os"
"path/filepath"
"sync"
"testing"
"time"
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -66,7 +64,7 @@ func TestMemorySessionStore_DeleteByUser_Good(t *testing.T) {
// Create sessions for two users
for i := range 3 {
err := store.Set(&Session{
Token: fmt.Sprintf("user-a-token-%d", i),
Token: core.Sprintf("user-a-token-%d", i),
UserID: "user-a",
ExpiresAt: time.Now().Add(1 * time.Hour),
})
@ -86,7 +84,7 @@ func TestMemorySessionStore_DeleteByUser_Good(t *testing.T) {
// user-a sessions should be gone
for i := range 3 {
_, err := store.Get(fmt.Sprintf("user-a-token-%d", i))
_, err := store.Get(core.Sprintf("user-a-token-%d", i))
assert.ErrorIs(t, err, ErrSessionNotFound)
}
@ -146,11 +144,11 @@ func TestMemorySessionStore_Concurrent_Good(t *testing.T) {
for i := range n {
go func(idx int) {
defer wg.Done()
token := fmt.Sprintf("concurrent-token-%d", idx)
token := core.Sprintf("concurrent-token-%d", idx)
err := store.Set(&Session{
Token: token,
UserID: fmt.Sprintf("user-%d", idx%5),
UserID: core.Sprintf("user-%d", idx%5),
ExpiresAt: time.Now().Add(1 * time.Hour),
})
assert.NoError(t, err)
@ -222,7 +220,7 @@ func TestSQLiteSessionStore_DeleteByUser_Good(t *testing.T) {
// Create sessions for two users
for i := range 3 {
err := store.Set(&Session{
Token: fmt.Sprintf("sqlite-user-a-%d", i),
Token: core.Sprintf("sqlite-user-a-%d", i),
UserID: "user-a",
ExpiresAt: time.Now().Add(1 * time.Hour),
})
@ -242,7 +240,7 @@ func TestSQLiteSessionStore_DeleteByUser_Good(t *testing.T) {
// user-a sessions should be gone
for i := range 3 {
_, err := store.Get(fmt.Sprintf("sqlite-user-a-%d", i))
_, err := store.Get(core.Sprintf("sqlite-user-a-%d", i))
assert.ErrorIs(t, err, ErrSessionNotFound)
}
@ -296,7 +294,7 @@ func TestSQLiteSessionStore_Cleanup_Good(t *testing.T) {
func TestSQLiteSessionStore_Persistence_Good(t *testing.T) {
dir := t.TempDir()
dbPath := filepath.Join(dir, "sessions.db")
dbPath := core.Path(dir, "sessions.db")
// Write a session
store1, err := NewSQLiteSessionStore(dbPath)
@ -327,7 +325,7 @@ func TestSQLiteSessionStore_Persistence_Good(t *testing.T) {
func TestSQLiteSessionStore_Concurrent_Good(t *testing.T) {
// Use a temp file — :memory: SQLite has concurrency limitations
dbPath := filepath.Join(t.TempDir(), "concurrent.db")
dbPath := core.Path(t.TempDir(), "concurrent.db")
store, err := NewSQLiteSessionStore(dbPath)
require.NoError(t, err)
defer store.Close()
@ -339,11 +337,11 @@ func TestSQLiteSessionStore_Concurrent_Good(t *testing.T) {
for i := range n {
go func(idx int) {
defer wg.Done()
token := fmt.Sprintf("sqlite-concurrent-%d", idx)
token := core.Sprintf("sqlite-concurrent-%d", idx)
err := store.Set(&Session{
Token: token,
UserID: fmt.Sprintf("user-%d", idx%5),
UserID: core.Sprintf("user-%d", idx%5),
ExpiresAt: time.Now().Add(1 * time.Hour),
})
assert.NoError(t, err)
@ -480,8 +478,7 @@ func TestSQLiteSessionStore_UpdateExisting_Good(t *testing.T) {
func TestSQLiteSessionStore_TempFile_Good(t *testing.T) {
// Verify we can use a real temp file (not :memory:)
tmpFile := filepath.Join(os.TempDir(), "go-crypt-test-session-store.db")
defer os.Remove(tmpFile)
tmpFile := core.Path(t.TempDir(), "go-crypt-test-session-store.db")
store, err := NewSQLiteSessionStore(tmpFile)
require.NoError(t, err)

View file

@ -1,9 +1,7 @@
package crypt
import (
"fmt"
"path/filepath"
core "dappco.re/go/core"
"dappco.re/go/core/crypt/crypt"
"forge.lthn.ai/core/cli/pkg/cli"
)
@ -42,12 +40,12 @@ func runChecksum(path string) error {
if checksumVerify != "" {
if hash == checksumVerify {
cli.Success(fmt.Sprintf("Checksum matches: %s", filepath.Base(path)))
cli.Success(core.Sprintf("Checksum matches: %s", core.PathBase(path)))
return nil
}
cli.Error(fmt.Sprintf("Checksum mismatch: %s", filepath.Base(path)))
cli.Dim(fmt.Sprintf(" expected: %s", checksumVerify))
cli.Dim(fmt.Sprintf(" got: %s", hash))
cli.Error(core.Sprintf("Checksum mismatch: %s", core.PathBase(path)))
cli.Dim(core.Sprintf(" expected: %s", checksumVerify))
cli.Dim(core.Sprintf(" got: %s", hash))
return cli.Err("checksum verification failed")
}
@ -56,6 +54,6 @@ func runChecksum(path string) error {
algo = "SHA-512"
}
fmt.Printf("%s %s (%s)\n", hash, path, algo)
core.Print(nil, "%s %s (%s)", hash, path, algo)
return nil
}

View file

@ -1,9 +1,7 @@
package crypt
import (
"fmt"
"strings"
core "dappco.re/go/core"
"dappco.re/go/core/crypt/crypt"
coreio "dappco.re/go/core/io"
"forge.lthn.ai/core/cli/pkg/cli"
@ -74,7 +72,7 @@ func runEncrypt(path string) error {
return cli.Wrap(err, "failed to write encrypted file")
}
cli.Success(fmt.Sprintf("Encrypted %s -> %s", path, outPath))
cli.Success(core.Sprintf("Encrypted %s -> %s", path, outPath))
return nil
}
@ -103,7 +101,7 @@ func runDecrypt(path string) error {
return cli.Wrap(err, "failed to decrypt")
}
outPath := strings.TrimSuffix(path, ".enc")
outPath := core.TrimSuffix(path, ".enc")
if outPath == path {
outPath = path + ".dec"
}
@ -112,6 +110,6 @@ func runDecrypt(path string) error {
return cli.Wrap(err, "failed to write decrypted file")
}
cli.Success(fmt.Sprintf("Decrypted %s -> %s", path, outPath))
cli.Success(core.Sprintf("Decrypted %s -> %s", path, outPath))
return nil
}

View file

@ -1,8 +1,7 @@
package crypt
import (
"fmt"
core "dappco.re/go/core"
"dappco.re/go/core/crypt/crypt"
"forge.lthn.ai/core/cli/pkg/cli"
@ -39,7 +38,7 @@ func runHash(input string) error {
if err != nil {
return cli.Wrap(err, "failed to hash password")
}
fmt.Println(hash)
core.Println(hash)
return nil
}
@ -47,7 +46,7 @@ func runHash(input string) error {
if err != nil {
return cli.Wrap(err, "failed to hash password")
}
fmt.Println(hash)
core.Println(hash)
return nil
}

View file

@ -4,8 +4,8 @@ import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
core "dappco.re/go/core"
"forge.lthn.ai/core/cli/pkg/cli"
)
@ -43,12 +43,12 @@ func runKeygen() error {
switch {
case keygenHex:
fmt.Println(hex.EncodeToString(key))
core.Println(hex.EncodeToString(key))
case keygenBase64:
fmt.Println(base64.StdEncoding.EncodeToString(key))
core.Println(base64.StdEncoding.EncodeToString(key))
default:
// Default to hex output
fmt.Println(hex.EncodeToString(key))
core.Println(hex.EncodeToString(key))
}
return nil

View file

@ -3,13 +3,11 @@ package testcmd
import (
"bufio"
"cmp"
"fmt"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
core "dappco.re/go/core"
"dappco.re/go/core/i18n"
)
@ -40,7 +38,7 @@ func parseTestOutput(output string) testResults {
skipPattern := regexp.MustCompile(`^\?\s+(\S+)\s+\[no test files\]`)
coverPattern := regexp.MustCompile(`coverage:\s+([\d.]+)%`)
scanner := bufio.NewScanner(strings.NewReader(output))
scanner := bufio.NewScanner(core.NewReader(output))
for scanner.Scan() {
line := scanner.Text()
@ -85,21 +83,32 @@ func printTestSummary(results testResults, showCoverage bool) {
// Print pass/fail summary
total := results.passed + results.failed
if total > 0 {
fmt.Printf(" %s %s", testPassStyle.Render("✓"), i18n.T("i18n.count.passed", results.passed))
line := core.NewBuilder()
line.WriteString(" ")
line.WriteString(testPassStyle.Render("✓"))
line.WriteString(" ")
line.WriteString(i18n.T("i18n.count.passed", results.passed))
if results.failed > 0 {
fmt.Printf(" %s %s", testFailStyle.Render("✗"), i18n.T("i18n.count.failed", results.failed))
line.WriteString(" ")
line.WriteString(testFailStyle.Render("✗"))
line.WriteString(" ")
line.WriteString(i18n.T("i18n.count.failed", results.failed))
}
if results.skipped > 0 {
fmt.Printf(" %s %s", testSkipStyle.Render("○"), i18n.T("i18n.count.skipped", results.skipped))
line.WriteString(" ")
line.WriteString(testSkipStyle.Render("○"))
line.WriteString(" ")
line.WriteString(i18n.T("i18n.count.skipped", results.skipped))
}
fmt.Println()
core.Println(line.String())
}
// Print failed packages
if len(results.failedPkgs) > 0 {
fmt.Printf("\n %s\n", i18n.T("cmd.test.failed_packages"))
core.Println()
core.Println(" " + i18n.T("cmd.test.failed_packages"))
for _, pkg := range results.failedPkgs {
fmt.Printf(" %s %s\n", testFailStyle.Render("✗"), pkg)
core.Println(core.Sprintf(" %s %s", testFailStyle.Render("✗"), pkg))
}
}
@ -108,7 +117,8 @@ func printTestSummary(results testResults, showCoverage bool) {
printCoverageSummary(results)
} else if results.covCount > 0 {
avgCov := results.totalCov / float64(results.covCount)
fmt.Printf("\n %s %s\n", i18n.Label("coverage"), formatCoverage(avgCov))
core.Println()
core.Println(core.Sprintf(" %s %s", i18n.Label("coverage"), formatCoverage(avgCov)))
}
}
@ -117,7 +127,8 @@ func printCoverageSummary(results testResults) {
return
}
fmt.Printf("\n %s\n", testHeaderStyle.Render(i18n.T("cmd.test.coverage_by_package")))
core.Println()
core.Println(" " + testHeaderStyle.Render(i18n.T("cmd.test.coverage_by_package")))
// Sort packages by name
slices.SortFunc(results.packages, func(a, b packageCoverage) int {
@ -143,8 +154,8 @@ func printCoverageSummary(results testResults) {
if padLen < 0 {
padLen = 2
}
padding := strings.Repeat(" ", padLen)
fmt.Printf(" %s%s%s\n", name, padding, formatCoverage(pkg.coverage))
padding := repeatString(" ", padLen)
core.Println(core.Sprintf(" %s%s%s", name, padding, formatCoverage(pkg.coverage)))
}
// Print average
@ -155,13 +166,14 @@ func printCoverageSummary(results testResults) {
if padLen < 0 {
padLen = 2
}
padding := strings.Repeat(" ", padLen)
fmt.Printf("\n %s%s%s\n", testHeaderStyle.Render(avgLabel), padding, formatCoverage(avgCov))
padding := repeatString(" ", padLen)
core.Println()
core.Println(core.Sprintf(" %s%s%s", testHeaderStyle.Render(avgLabel), padding, formatCoverage(avgCov)))
}
}
func formatCoverage(cov float64) string {
s := fmt.Sprintf("%.1f%%", cov)
s := core.Sprintf("%.1f%%", cov)
if cov >= 80 {
return testCovHighStyle.Render(s)
} else if cov >= 50 {
@ -172,41 +184,47 @@ func formatCoverage(cov float64) string {
func shortenPackageName(name string) string {
const modulePrefix = "dappco.re/go/"
if strings.HasPrefix(name, modulePrefix) {
remainder := strings.TrimPrefix(name, modulePrefix)
// If there's a sub-path (e.g. "go/pkg/foo"), strip the module name
if idx := strings.Index(remainder, "/"); idx >= 0 {
return remainder[idx+1:]
if core.HasPrefix(name, modulePrefix) {
remainder := core.TrimPrefix(name, modulePrefix)
parts := core.SplitN(remainder, "/", 2)
if len(parts) == 2 {
return parts[1]
}
// Module root (e.g. "cli-php") — return as-is
return remainder
}
return filepath.Base(name)
return core.PathBase(name)
}
func printJSONResults(results testResults, exitCode int) {
// Simple JSON output for agents
fmt.Printf("{\n")
fmt.Printf(" \"passed\": %d,\n", results.passed)
fmt.Printf(" \"failed\": %d,\n", results.failed)
fmt.Printf(" \"skipped\": %d,\n", results.skipped)
payload := struct {
Passed int `json:"passed"`
Failed int `json:"failed"`
Skipped int `json:"skipped"`
Coverage float64 `json:"coverage,omitempty"`
ExitCode int `json:"exit_code"`
FailedPackages []string `json:"failed_packages"`
}{
Passed: results.passed,
Failed: results.failed,
Skipped: results.skipped,
ExitCode: exitCode,
FailedPackages: results.failedPkgs,
}
if results.covCount > 0 {
avgCov := results.totalCov / float64(results.covCount)
fmt.Printf(" \"coverage\": %.1f,\n", avgCov)
payload.Coverage = results.totalCov / float64(results.covCount)
}
fmt.Printf(" \"exit_code\": %d,\n", exitCode)
if len(results.failedPkgs) > 0 {
fmt.Printf(" \"failed_packages\": [\n")
for i, pkg := range results.failedPkgs {
comma := ","
if i == len(results.failedPkgs)-1 {
comma = ""
}
fmt.Printf(" %q%s\n", pkg, comma)
}
fmt.Printf(" ]\n")
} else {
fmt.Printf(" \"failed_packages\": []\n")
}
fmt.Printf("}\n")
core.Println(core.JSONMarshalString(payload))
}
func repeatString(part string, count int) string {
if count <= 0 {
return ""
}
builder := core.NewBuilder()
for range count {
builder.WriteString(part)
}
return builder.String()
}

View file

@ -2,20 +2,31 @@ package testcmd
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"context"
"runtime"
"strings"
"sync"
core "dappco.re/go/core"
"dappco.re/go/core/i18n"
coreerr "dappco.re/go/core/log"
"dappco.re/go/core/process"
)
var (
processInitOnce sync.Once
processInitErr error
)
func runTest(verbose, coverage, short bool, pkg, run string, race, jsonOutput bool) error {
processInitOnce.Do(func() {
processInitErr = process.Init(core.New())
})
if processInitErr != nil {
return coreerr.E("cmd.test", i18n.T("i18n.fail.run", "tests"), processInitErr)
}
// Detect if we're in a Go project
if _, err := os.Stat("go.mod"); os.IsNotExist(err) {
if !(&core.Fs{}).New("/").Exists("go.mod") {
return coreerr.E("cmd.test", i18n.T("cmd.test.error.no_go_mod"), nil)
}
@ -47,45 +58,32 @@ func runTest(verbose, coverage, short bool, pkg, run string, race, jsonOutput bo
// Add package pattern
args = append(args, pkg)
// Create command
cmd := exec.Command("go", args...)
cmd.Dir, _ = os.Getwd()
// Set environment to suppress macOS linker warnings
cmd.Env = append(os.Environ(), getMacOSDeploymentTarget())
if !jsonOutput {
fmt.Printf("%s %s\n", testHeaderStyle.Render(i18n.Label("test")), i18n.ProgressSubject("run", "tests"))
fmt.Printf(" %s %s\n", i18n.Label("package"), testDimStyle.Render(pkg))
core.Println(core.Sprintf("%s %s", testHeaderStyle.Render(i18n.Label("test")), i18n.ProgressSubject("run", "tests")))
core.Println(core.Sprintf(" %s %s", i18n.Label("package"), testDimStyle.Render(pkg)))
if run != "" {
fmt.Printf(" %s %s\n", i18n.Label("filter"), testDimStyle.Render(run))
core.Println(core.Sprintf(" %s %s", i18n.Label("filter"), testDimStyle.Render(run)))
}
fmt.Println()
core.Println()
}
// Capture output for parsing
var stdout, stderr strings.Builder
if verbose && !jsonOutput {
// Stream output in verbose mode, but also capture for parsing
cmd.Stdout = io.MultiWriter(os.Stdout, &stdout)
cmd.Stderr = io.MultiWriter(os.Stderr, &stderr)
} else {
// Capture output for parsing
cmd.Stdout = &stdout
cmd.Stderr = &stderr
options := process.RunOptions{
Command: "go",
Args: args,
Dir: core.Env("DIR_CWD"),
}
if target := getMacOSDeploymentTarget(); target != "" {
options.Env = []string{target}
}
err := cmd.Run()
exitCode := 0
proc, err := process.StartWithOptions(context.Background(), options)
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
exitCode = exitErr.ExitCode()
}
return coreerr.E("cmd.test", i18n.T("i18n.fail.run", "tests"), err)
}
// Combine stdout and stderr for parsing, filtering linker warnings
combined := filterLinkerWarnings(stdout.String() + "\n" + stderr.String())
waitErr := proc.Wait()
exitCode := proc.ExitCode
combined := filterLinkerWarnings(proc.Output())
// Parse results
results := parseTestOutput(combined)
@ -104,16 +102,23 @@ func runTest(verbose, coverage, short bool, pkg, run string, race, jsonOutput bo
printTestSummary(results, coverage)
} else if coverage {
// In verbose mode, still show coverage summary at end
fmt.Println()
if combined != "" {
core.Println(combined)
}
core.Println()
printCoverageSummary(results)
} else if combined != "" {
core.Println(combined)
}
if exitCode != 0 {
fmt.Printf("\n%s %s\n", testFailStyle.Render(i18n.T("cli.fail")), i18n.T("cmd.test.tests_failed"))
return coreerr.E("cmd.test", i18n.T("i18n.fail.run", "tests"), nil)
core.Println()
core.Println(core.Sprintf("%s %s", testFailStyle.Render(i18n.T("cli.fail")), i18n.T("cmd.test.tests_failed")))
return coreerr.E("cmd.test", i18n.T("i18n.fail.run", "tests"), waitErr)
}
fmt.Printf("\n%s %s\n", testPassStyle.Render(i18n.T("cli.pass")), i18n.T("common.result.all_passed"))
core.Println()
core.Println(core.Sprintf("%s %s", testPassStyle.Render(i18n.T("cli.pass")), i18n.T("common.result.all_passed")))
return nil
}
@ -128,18 +133,18 @@ func getMacOSDeploymentTarget() string {
func filterLinkerWarnings(output string) string {
// Filter out ld: warning lines that pollute the output
var filtered []string
scanner := bufio.NewScanner(strings.NewReader(output))
scanner := bufio.NewScanner(core.NewReader(output))
for scanner.Scan() {
line := scanner.Text()
// Skip linker warnings
if strings.HasPrefix(line, "ld: warning:") {
if core.HasPrefix(line, "ld: warning:") {
continue
}
// Skip test binary build comments
if strings.HasPrefix(line, "# ") && strings.HasSuffix(line, ".test") {
if core.HasPrefix(line, "# ") && core.HasSuffix(line, ".test") {
continue
}
filtered = append(filtered, line)
}
return strings.Join(filtered, "\n")
return core.Join("\n", filtered...)
}

View file

@ -2,9 +2,9 @@ package chachapoly
import (
"crypto/rand"
"fmt"
"io"
core "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
"golang.org/x/crypto/chacha20poly1305"
@ -34,7 +34,7 @@ func Decrypt(ciphertext []byte, key []byte) ([]byte, error) {
minLen := aead.NonceSize() + aead.Overhead()
if len(ciphertext) < minLen {
return nil, coreerr.E("chachapoly.Decrypt", fmt.Sprintf("ciphertext too short: got %d bytes, need at least %d bytes", len(ciphertext), minLen), nil)
return nil, coreerr.E("chachapoly.Decrypt", core.Sprintf("ciphertext too short: got %d bytes, need at least %d bytes", len(ciphertext), minLen), nil)
}
nonce, ciphertext := ciphertext[:aead.NonceSize()], ciphertext[aead.NonceSize():]

View file

@ -2,9 +2,9 @@ package chachapoly
import (
"crypto/rand"
"errors"
"testing"
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
)
@ -12,7 +12,7 @@ import (
type mockReader struct{}
func (r *mockReader) Read(p []byte) (n int, err error) {
return 0, errors.New("read error")
return 0, core.NewError("read error")
}
func TestEncryptDecrypt_Good(t *testing.T) {

View file

@ -5,17 +5,19 @@ import (
"crypto/sha512"
"encoding/hex"
"io"
"os"
core "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
)
// SHA256File computes the SHA-256 checksum of a file and returns it as a hex string.
func SHA256File(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
openResult := (&core.Fs{}).New("/").Open(path)
if !openResult.OK {
err, _ := openResult.Value.(error)
return "", coreerr.E("crypt.SHA256File", "failed to open file", err)
}
f := openResult.Value.(io.ReadCloser)
defer func() { _ = f.Close() }()
h := sha256.New()
@ -28,10 +30,12 @@ func SHA256File(path string) (string, error) {
// SHA512File computes the SHA-512 checksum of a file and returns it as a hex string.
func SHA512File(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
openResult := (&core.Fs{}).New("/").Open(path)
if !openResult.OK {
err, _ := openResult.Value.(error)
return "", coreerr.E("crypt.SHA512File", "failed to open file", err)
}
f := openResult.Value.(io.ReadCloser)
defer func() { _ = f.Close() }()
h := sha512.New()

View file

@ -1,10 +1,9 @@
package crypt
import (
"os"
"path/filepath"
"testing"
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -30,9 +29,9 @@ func TestSHA512Sum_Good(t *testing.T) {
// TestSHA256FileEmpty_Good verifies checksum of an empty file.
func TestSHA256FileEmpty_Good(t *testing.T) {
tmpDir := t.TempDir()
emptyFile := filepath.Join(tmpDir, "empty.bin")
err := os.WriteFile(emptyFile, []byte{}, 0o644)
require.NoError(t, err)
emptyFile := core.Path(tmpDir, "empty.bin")
writeResult := (&core.Fs{}).New("/").WriteMode(emptyFile, "", 0o644)
require.Truef(t, writeResult.OK, "failed to write empty test file: %v", writeResult.Value)
hash, err := SHA256File(emptyFile)
require.NoError(t, err)
@ -43,9 +42,9 @@ func TestSHA256FileEmpty_Good(t *testing.T) {
// TestSHA512FileEmpty_Good verifies SHA-512 checksum of an empty file.
func TestSHA512FileEmpty_Good(t *testing.T) {
tmpDir := t.TempDir()
emptyFile := filepath.Join(tmpDir, "empty.bin")
err := os.WriteFile(emptyFile, []byte{}, 0o644)
require.NoError(t, err)
emptyFile := core.Path(tmpDir, "empty.bin")
writeResult := (&core.Fs{}).New("/").WriteMode(emptyFile, "", 0o644)
require.Truef(t, writeResult.OK, "failed to write empty test file: %v", writeResult.Value)
hash, err := SHA512File(emptyFile)
require.NoError(t, err)
@ -69,9 +68,9 @@ func TestSHA512FileNonExistent_Bad(t *testing.T) {
// TestSHA256FileWithContent_Good verifies checksum of a file with known content.
func TestSHA256FileWithContent_Good(t *testing.T) {
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "test.txt")
err := os.WriteFile(testFile, []byte("hello"), 0o644)
require.NoError(t, err)
testFile := core.Path(tmpDir, "test.txt")
writeResult := (&core.Fs{}).New("/").WriteMode(testFile, "hello", 0o644)
require.Truef(t, writeResult.OK, "failed to write checksum fixture: %v", writeResult.Value)
hash, err := SHA256File(testFile)
require.NoError(t, err)

View file

@ -3,9 +3,9 @@ package crypt
import (
"crypto/subtle"
"encoding/base64"
"fmt"
"strings"
"strconv"
core "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
"golang.org/x/crypto/argon2"
@ -25,7 +25,7 @@ func HashPassword(password string) (string, error) {
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
encoded := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
encoded := core.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
argon2.Version, argon2Memory, argon2Time, argon2Parallelism,
b64Salt, b64Hash)
@ -35,20 +35,21 @@ func HashPassword(password string) (string, error) {
// VerifyPassword verifies a password against an Argon2id hash string.
// The hash must be in the format produced by HashPassword.
func VerifyPassword(password, hash string) (bool, error) {
parts := strings.Split(hash, "$")
parts := core.Split(hash, "$")
if len(parts) != 6 {
return false, coreerr.E("crypt.VerifyPassword", "invalid hash format", nil)
}
var version int
if _, err := fmt.Sscanf(parts[2], "v=%d", &version); err != nil {
version, err := parsePrefixedInt(parts[2], "v=")
if err != nil {
return false, coreerr.E("crypt.VerifyPassword", "failed to parse version", err)
}
if version != argon2.Version {
return false, coreerr.E("crypt.VerifyPassword", core.Sprintf("unsupported argon2 version %d", version), nil)
}
var memory uint32
var time uint32
var parallelism uint8
if _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &time, &parallelism); err != nil {
memory, time, parallelism, err := parseArgonParams(parts[3])
if err != nil {
return false, coreerr.E("crypt.VerifyPassword", "failed to parse parameters", err)
}
@ -67,6 +68,52 @@ func VerifyPassword(password, hash string) (bool, error) {
return subtle.ConstantTimeCompare(computedHash, expectedHash) == 1, nil
}
func parseArgonParams(input string) (uint32, uint32, uint8, error) {
fields := core.Split(input, ",")
if len(fields) != 3 {
return 0, 0, 0, core.NewError("invalid argon2 parameters")
}
memory, err := parsePrefixedUint32(fields[0], "m=")
if err != nil {
return 0, 0, 0, err
}
time, err := parsePrefixedUint32(fields[1], "t=")
if err != nil {
return 0, 0, 0, err
}
parallelismValue, err := parsePrefixedUint32(fields[2], "p=")
if err != nil {
return 0, 0, 0, err
}
return memory, time, uint8(parallelismValue), nil
}
func parsePrefixedInt(input, prefix string) (int, error) {
if !core.HasPrefix(input, prefix) {
return 0, core.NewError(core.Sprintf("missing %q prefix", prefix))
}
value, err := strconv.Atoi(core.TrimPrefix(input, prefix))
if err != nil {
return 0, err
}
return value, nil
}
func parsePrefixedUint32(input, prefix string) (uint32, error) {
if !core.HasPrefix(input, prefix) {
return 0, core.NewError(core.Sprintf("missing %q prefix", prefix))
}
value, err := strconv.ParseUint(core.TrimPrefix(input, prefix), 10, 32)
if err != nil {
return 0, err
}
return uint32(value), nil
}
// HashBcrypt hashes a password using bcrypt with the given cost.
// Cost must be between bcrypt.MinCost and bcrypt.MaxCost.
func HashBcrypt(password string, cost int) (string, error) {

View file

@ -4,7 +4,6 @@ import (
"bytes"
"crypto"
goio "io"
"strings"
framework "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
@ -102,7 +101,7 @@ func serializeEntity(w goio.Writer, e *openpgp.Entity) error {
// EncryptPGP encrypts data for a recipient identified by their public key (armored string in recipientPath).
// The encrypted data is written to the provided writer and also returned as an armored string.
func (s *Service) EncryptPGP(writer goio.Writer, recipientPath, data string, opts ...any) (string, error) {
entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(recipientPath))
entityList, err := openpgp.ReadArmoredKeyRing(framework.NewReader(recipientPath))
if err != nil {
return "", coreerr.E("openpgp.EncryptPGP", "failed to read recipient key", err)
}
@ -137,7 +136,7 @@ func (s *Service) EncryptPGP(writer goio.Writer, recipientPath, data string, opt
// DecryptPGP decrypts a PGP message using the provided armored private key and passphrase.
func (s *Service) DecryptPGP(privateKey, message, passphrase string, opts ...any) (string, error) {
entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(privateKey))
entityList, err := openpgp.ReadArmoredKeyRing(framework.NewReader(privateKey))
if err != nil {
return "", coreerr.E("openpgp.DecryptPGP", "failed to read private key", err)
}
@ -154,7 +153,7 @@ func (s *Service) DecryptPGP(privateKey, message, passphrase string, opts ...any
}
// Decrypt armored message
block, err := armor.Decode(strings.NewReader(message))
block, err := armor.Decode(framework.NewReader(message))
if err != nil {
return "", coreerr.E("openpgp.DecryptPGP", "failed to decode armored message", err)
}

View file

@ -6,8 +6,8 @@ import (
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
core "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
)
@ -24,7 +24,7 @@ func (s *Service) GenerateKeyPair(bits int) (publicKey, privateKey []byte, err e
const op = "rsa.GenerateKeyPair"
if bits < 2048 {
return nil, nil, coreerr.E(op, fmt.Sprintf("key size too small: %d (minimum 2048)", bits), nil)
return nil, nil, coreerr.E(op, core.Sprintf("key size too small: %d (minimum 2048)", bits), nil)
}
privKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {

View file

@ -6,9 +6,9 @@ import (
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors"
"testing"
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
)
@ -16,7 +16,7 @@ import (
type mockReader struct{}
func (r *mockReader) Read(p []byte) (n int, err error) {
return 0, errors.New("read error")
return 0, core.NewError("read error")
}
func TestRSA_Good(t *testing.T) {

4
go.mod
View file

@ -3,10 +3,11 @@ module dappco.re/go/core/crypt
go 1.26.0
require (
dappco.re/go/core v0.5.0
dappco.re/go/core v0.8.0-alpha.1
dappco.re/go/core/i18n v0.2.0
dappco.re/go/core/io v0.2.0
dappco.re/go/core/log v0.1.0
dappco.re/go/core/process v0.3.0
dappco.re/go/core/store v0.2.0
forge.lthn.ai/core/cli v0.3.7
github.com/ProtonMail/go-crypto v1.4.0
@ -48,7 +49,6 @@ require (
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/mod v0.34.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/term v0.41.0 // indirect
golang.org/x/text v0.35.0 // indirect

6
go.sum
View file

@ -1,11 +1,13 @@
dappco.re/go/core v0.5.0 h1:P5DJoaCiK5Q+af5UiTdWqUIW4W4qYKzpgGK50thm21U=
dappco.re/go/core v0.5.0/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A=
dappco.re/go/core v0.8.0-alpha.1 h1:gj7+Scv+L63Z7wMxbJYHhaRFkHJo2u4MMPuUSv/Dhtk=
dappco.re/go/core v0.8.0-alpha.1/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A=
dappco.re/go/core/i18n v0.2.0 h1:NHzk6RCU93/qVRA3f2jvMr9P1R6FYheR/sHL+TnvKbI=
dappco.re/go/core/i18n v0.2.0/go.mod h1:9eSVJXr3OpIGWQvDynfhqcp27xnLMwlYLgsByU+p7ok=
dappco.re/go/core/io v0.2.0 h1:zuudgIiTsQQ5ipVt97saWdGLROovbEB/zdVyy9/l+I4=
dappco.re/go/core/io v0.2.0/go.mod h1:1QnQV6X9LNgFKfm8SkOtR9LLaj3bDcsOIeJOOyjbL5E=
dappco.re/go/core/log v0.1.0 h1:pa71Vq2TD2aoEUQWFKwNcaJ3GBY8HbaNGqtE688Unyc=
dappco.re/go/core/log v0.1.0/go.mod h1:Nkqb8gsXhZAO8VLpx7B8i1iAmohhzqA20b9Zr8VUcJs=
dappco.re/go/core/process v0.3.0 h1:BPF9R79+8ZWe34qCIy/sZy+P4HwbaO95js2oPJL7IqM=
dappco.re/go/core/process v0.3.0/go.mod h1:qwx8kt6x+J9gn7fu8lavuess72Ye9jPBODqDZQ9K0as=
dappco.re/go/core/store v0.2.0 h1:MH3R9m3mdr5T3lMWi37ryvTrXzF4xLBTYBGyNZF0p3I=
dappco.re/go/core/store v0.2.0/go.mod h1:QQGJiruayjna3nywbf0N2gcO502q/oEkPoSpBpSKbLM=
forge.lthn.ai/core/cli v0.3.7 h1:1GrbaGg0wDGHr6+klSbbGyN/9sSbHvFbdySJznymhwg=

View file

@ -1,11 +1,11 @@
package trust
import (
"fmt"
"iter"
"sync"
"time"
core "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
)
@ -31,7 +31,7 @@ func (s ApprovalStatus) String() string {
case ApprovalDenied:
return "denied"
default:
return fmt.Sprintf("unknown(%d)", int(s))
return core.Sprintf("unknown(%d)", int(s))
}
}
@ -85,7 +85,7 @@ func (q *ApprovalQueue) Submit(agent string, cap Capability, repo string) (strin
defer q.mu.Unlock()
q.nextID++
id := fmt.Sprintf("approval-%d", q.nextID)
id := core.Sprintf("approval-%d", q.nextID)
q.requests[id] = &ApprovalRequest{
ID: id,
@ -107,10 +107,10 @@ func (q *ApprovalQueue) Approve(id string, reviewedBy string, reason string) err
req, ok := q.requests[id]
if !ok {
return coreerr.E("trust.ApprovalQueue.Approve", fmt.Sprintf("request %q not found", id), nil)
return coreerr.E("trust.ApprovalQueue.Approve", core.Sprintf("request %q not found", id), nil)
}
if req.Status != ApprovalPending {
return coreerr.E("trust.ApprovalQueue.Approve", fmt.Sprintf("request %q is already %s", id, req.Status), nil)
return coreerr.E("trust.ApprovalQueue.Approve", core.Sprintf("request %q is already %s", id, req.Status), nil)
}
req.Status = ApprovalApproved
@ -128,10 +128,10 @@ func (q *ApprovalQueue) Deny(id string, reviewedBy string, reason string) error
req, ok := q.requests[id]
if !ok {
return coreerr.E("trust.ApprovalQueue.Deny", fmt.Sprintf("request %q not found", id), nil)
return coreerr.E("trust.ApprovalQueue.Deny", core.Sprintf("request %q not found", id), nil)
}
if req.Status != ApprovalPending {
return coreerr.E("trust.ApprovalQueue.Deny", fmt.Sprintf("request %q is already %s", id, req.Status), nil)
return coreerr.E("trust.ApprovalQueue.Deny", core.Sprintf("request %q is already %s", id, req.Status), nil)
}
req.Status = ApprovalDenied

View file

@ -1,10 +1,10 @@
package trust
import (
"fmt"
"sync"
"testing"
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -234,7 +234,7 @@ func TestApprovalConcurrent_Good(t *testing.T) {
go func(idx int) {
defer wg.Done()
id, err := q.Submit(
fmt.Sprintf("agent-%d", idx),
core.Sprintf("agent-%d", idx),
CapMergePR,
"host-uk/core",
)

View file

@ -1,12 +1,12 @@
package trust
import (
"encoding/json"
"io"
"iter"
"sync"
"time"
core "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
)
@ -28,13 +28,20 @@ type AuditEntry struct {
// MarshalJSON implements custom JSON encoding for Decision.
func (d Decision) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
result := core.JSONMarshal(d.String())
if !result.OK {
err, _ := result.Value.(error)
return nil, err
}
return result.Value.([]byte), nil
}
// UnmarshalJSON implements custom JSON decoding for Decision.
func (d *Decision) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
result := core.JSONUnmarshal(data, &s)
if !result.OK {
err, _ := result.Value.(error)
return err
}
switch s {
@ -82,11 +89,12 @@ func (l *AuditLog) Record(result EvalResult, repo string) error {
l.entries = append(l.entries, entry)
if l.writer != nil {
data, err := json.Marshal(entry)
if err != nil {
dataResult := core.JSONMarshal(entry)
if !dataResult.OK {
err, _ := dataResult.Value.(error)
return coreerr.E("trust.AuditLog.Record", "marshal failed", err)
}
data = append(data, '\n')
data := append(dataResult.Value.([]byte), '\n')
if _, err := l.writer.Write(data); err != nil {
return coreerr.E("trust.AuditLog.Record", "write failed", err)
}

View file

@ -1,14 +1,11 @@
package trust
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
"sync"
"testing"
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -91,7 +88,7 @@ func TestAuditEntries_Good_AppendOnly(t *testing.T) {
for i := range 5 {
log.Record(EvalResult{
Agent: fmt.Sprintf("agent-%d", i),
Agent: core.Sprintf("agent-%d", i),
Cap: CapPushRepo,
Decision: Allow,
Reason: "ok",
@ -146,8 +143,8 @@ func TestAuditEntriesFor_Bad_NotFound(t *testing.T) {
// --- Writer output ---
func TestAuditRecord_Good_WritesToWriter(t *testing.T) {
var buf bytes.Buffer
log := NewAuditLog(&buf)
buf := core.NewBuilder()
log := NewAuditLog(buf)
result := EvalResult{
Decision: Allow,
@ -160,11 +157,11 @@ func TestAuditRecord_Good_WritesToWriter(t *testing.T) {
// Should have written a JSON line.
output := buf.String()
assert.True(t, strings.HasSuffix(output, "\n"))
assert.True(t, core.HasSuffix(output, "\n"))
var entry AuditEntry
err = json.Unmarshal([]byte(output), &entry)
require.NoError(t, err)
decodeResult := core.JSONUnmarshal([]byte(output), &entry)
require.Truef(t, decodeResult.OK, "failed to unmarshal audit entry: %v", decodeResult.Value)
assert.Equal(t, "Athena", entry.Agent)
assert.Equal(t, CapPushRepo, entry.Cap)
assert.Equal(t, Allow, entry.Decision)
@ -172,26 +169,26 @@ func TestAuditRecord_Good_WritesToWriter(t *testing.T) {
}
func TestAuditRecord_Good_MultipleLines(t *testing.T) {
var buf bytes.Buffer
log := NewAuditLog(&buf)
buf := core.NewBuilder()
log := NewAuditLog(buf)
for i := range 3 {
log.Record(EvalResult{
Agent: fmt.Sprintf("agent-%d", i),
Agent: core.Sprintf("agent-%d", i),
Cap: CapPushRepo,
Decision: Allow,
Reason: "ok",
}, "")
}
lines := strings.Split(strings.TrimSpace(buf.String()), "\n")
lines := core.Split(core.Trim(buf.String()), "\n")
assert.Len(t, lines, 3)
// Each line should be valid JSON.
for _, line := range lines {
var entry AuditEntry
err := json.Unmarshal([]byte(line), &entry)
assert.NoError(t, err)
result := core.JSONUnmarshal([]byte(line), &entry)
assert.Truef(t, result.OK, "failed to unmarshal audit line: %v", result.Value)
}
}
@ -226,35 +223,37 @@ func TestDecisionJSON_Good_RoundTrip(t *testing.T) {
expected := []string{`"deny"`, `"allow"`, `"needs_approval"`}
for i, d := range decisions {
data, err := json.Marshal(d)
require.NoError(t, err)
assert.Equal(t, expected[i], string(data))
result := core.JSONMarshal(d)
require.Truef(t, result.OK, "failed to marshal decision: %v", result.Value)
assert.Equal(t, expected[i], string(result.Value.([]byte)))
var decoded Decision
err = json.Unmarshal(data, &decoded)
require.NoError(t, err)
decodeResult := core.JSONUnmarshal(result.Value.([]byte), &decoded)
require.Truef(t, decodeResult.OK, "failed to unmarshal decision: %v", decodeResult.Value)
assert.Equal(t, d, decoded)
}
}
func TestDecisionJSON_Bad_UnknownString(t *testing.T) {
var d Decision
err := json.Unmarshal([]byte(`"invalid"`), &d)
result := core.JSONUnmarshal([]byte(`"invalid"`), &d)
err, _ := result.Value.(error)
assert.Error(t, err)
assert.Contains(t, err.Error(), "unknown decision")
}
func TestDecisionJSON_Bad_NonString(t *testing.T) {
var d Decision
err := json.Unmarshal([]byte(`42`), &d)
result := core.JSONUnmarshal([]byte(`42`), &d)
err, _ := result.Value.(error)
assert.Error(t, err)
}
// --- Concurrent audit logging ---
func TestAuditConcurrent_Good(t *testing.T) {
var buf bytes.Buffer
log := NewAuditLog(&buf)
buf := core.NewBuilder()
log := NewAuditLog(buf)
const n = 10
var wg sync.WaitGroup
@ -264,7 +263,7 @@ func TestAuditConcurrent_Good(t *testing.T) {
go func(idx int) {
defer wg.Done()
log.Record(EvalResult{
Agent: fmt.Sprintf("agent-%d", idx),
Agent: core.Sprintf("agent-%d", idx),
Cap: CapPushRepo,
Decision: Allow,
Reason: "ok",
@ -279,8 +278,8 @@ func TestAuditConcurrent_Good(t *testing.T) {
// --- Integration: PolicyEngine + AuditLog ---
func TestAuditPolicyIntegration_Good(t *testing.T) {
var buf bytes.Buffer
log := NewAuditLog(&buf)
buf := core.NewBuilder()
log := NewAuditLog(buf)
pe := newTestEngine(t)
// Evaluate and record

View file

@ -1,8 +1,9 @@
package trust
import (
"fmt"
"testing"
core "dappco.re/go/core"
)
// BenchmarkPolicyEvaluate measures policy evaluation across 100 registered agents.
@ -17,7 +18,7 @@ func BenchmarkPolicyEvaluate(b *testing.B) {
tier = TierVerified
}
_ = r.Register(Agent{
Name: fmt.Sprintf("agent-%d", i),
Name: core.Sprintf("agent-%d", i),
Tier: tier,
ScopedRepos: []string{"host-uk/core", "host-uk/docs"},
})
@ -32,7 +33,7 @@ func BenchmarkPolicyEvaluate(b *testing.B) {
b.ResetTimer()
for i := range b.N {
agentName := fmt.Sprintf("agent-%d", i%100)
agentName := core.Sprintf("agent-%d", i%100)
cap := caps[i%len(caps)]
_ = pe.Evaluate(agentName, cap, "host-uk/core")
}
@ -43,14 +44,14 @@ func BenchmarkRegistryGet(b *testing.B) {
r := NewRegistry()
for i := range 100 {
_ = r.Register(Agent{
Name: fmt.Sprintf("agent-%d", i),
Name: core.Sprintf("agent-%d", i),
Tier: TierVerified,
})
}
b.ResetTimer()
for i := range b.N {
name := fmt.Sprintf("agent-%d", i%100)
name := core.Sprintf("agent-%d", i%100)
_ = r.Get(name)
}
}
@ -62,7 +63,7 @@ func BenchmarkRegistryRegister(b *testing.B) {
b.ResetTimer()
for i := range b.N {
_ = r.Register(Agent{
Name: fmt.Sprintf("bench-agent-%d", i),
Name: core.Sprintf("bench-agent-%d", i),
Tier: TierVerified,
})
}

View file

@ -1,11 +1,9 @@
package trust
import (
"encoding/json"
"fmt"
"io"
"os"
core "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
)
@ -24,20 +22,31 @@ type PoliciesConfig struct {
// LoadPoliciesFromFile reads a JSON file and returns parsed policies.
func LoadPoliciesFromFile(path string) ([]Policy, error) {
f, err := os.Open(path)
if err != nil {
openResult := (&core.Fs{}).New("/").Open(path)
if !openResult.OK {
err, _ := openResult.Value.(error)
return nil, coreerr.E("trust.LoadPoliciesFromFile", "failed to open file", err)
}
defer f.Close()
return LoadPolicies(f)
return LoadPolicies(openResult.Value.(io.Reader))
}
// LoadPolicies reads JSON from a reader and returns parsed policies.
func LoadPolicies(r io.Reader) ([]Policy, error) {
readResult := core.ReadAll(r)
if !readResult.OK {
err, _ := readResult.Value.(error)
return nil, coreerr.E("trust.LoadPolicies", "failed to decode JSON", err)
}
data := []byte(readResult.Value.(string))
if err := validatePoliciesJSON(data); err != nil {
return nil, coreerr.E("trust.LoadPolicies", "failed to decode JSON", err)
}
var cfg PoliciesConfig
dec := json.NewDecoder(r)
dec.DisallowUnknownFields()
if err := dec.Decode(&cfg); err != nil {
decodeResult := core.JSONUnmarshal(data, &cfg)
if !decodeResult.OK {
err, _ := decodeResult.Value.(error)
return nil, coreerr.E("trust.LoadPolicies", "failed to decode JSON", err)
}
return convertPolicies(cfg)
@ -50,7 +59,7 @@ func convertPolicies(cfg PoliciesConfig) ([]Policy, error) {
for i, pc := range cfg.Policies {
tier := Tier(pc.Tier)
if !tier.Valid() {
return nil, coreerr.E("trust.LoadPolicies", fmt.Sprintf("invalid tier %d at index %d", pc.Tier, i), nil)
return nil, coreerr.E("trust.LoadPolicies", core.Sprintf("invalid tier %d at index %d", pc.Tier, i), nil)
}
p := Policy{
@ -82,12 +91,12 @@ func (pe *PolicyEngine) ApplyPolicies(r io.Reader) error {
// ApplyPoliciesFromFile loads policies from a JSON file and sets them on the engine.
func (pe *PolicyEngine) ApplyPoliciesFromFile(path string) error {
f, err := os.Open(path)
if err != nil {
openResult := (&core.Fs{}).New("/").Open(path)
if !openResult.OK {
err, _ := openResult.Value.(error)
return coreerr.E("trust.ApplyPoliciesFromFile", "failed to open file", err)
}
defer f.Close()
return pe.ApplyPolicies(f)
return pe.ApplyPolicies(openResult.Value.(io.Reader))
}
// ExportPolicies serialises the current policies as JSON to the given writer.
@ -106,14 +115,66 @@ func (pe *PolicyEngine) ExportPolicies(w io.Writer) error {
})
}
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
if err := enc.Encode(cfg); err != nil {
dataResult := core.JSONMarshal(cfg)
if !dataResult.OK {
err, _ := dataResult.Value.(error)
return coreerr.E("trust.ExportPolicies", "failed to encode JSON", err)
}
if _, err := w.Write(dataResult.Value.([]byte)); err != nil {
return coreerr.E("trust.ExportPolicies", "failed to encode JSON", err)
}
return nil
}
func validatePoliciesJSON(data []byte) error {
var raw map[string]any
result := core.JSONUnmarshal(data, &raw)
if !result.OK {
err, _ := result.Value.(error)
return err
}
for key := range raw {
if key != "policies" {
return core.NewError(core.Sprintf("json: unknown field %q", key))
}
}
rawPolicies, ok := raw["policies"]
if !ok {
return nil
}
policies, ok := rawPolicies.([]any)
if !ok {
return nil
}
for _, rawPolicy := range policies {
fields, ok := rawPolicy.(map[string]any)
if !ok {
continue
}
for key := range fields {
if !isKnownPolicyConfigKey(key) {
return core.NewError(core.Sprintf("json: unknown field %q", key))
}
}
}
return nil
}
func isKnownPolicyConfigKey(key string) bool {
switch key {
case "tier", "allowed", "requires_approval", "denied":
return true
default:
return false
}
}
// toCapabilities converts string slices to Capability slices.
func toCapabilities(ss []string) []Capability {
if len(ss) == 0 {

View file

@ -1,13 +1,9 @@
package trust
import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -35,13 +31,13 @@ const validPolicyJSON = `{
// --- LoadPolicies ---
func TestLoadPolicies_Good(t *testing.T) {
policies, err := LoadPolicies(strings.NewReader(validPolicyJSON))
policies, err := LoadPolicies(core.NewReader(validPolicyJSON))
require.NoError(t, err)
assert.Len(t, policies, 3)
}
func TestLoadPolicies_Good_FieldMapping(t *testing.T) {
policies, err := LoadPolicies(strings.NewReader(validPolicyJSON))
policies, err := LoadPolicies(core.NewReader(validPolicyJSON))
require.NoError(t, err)
// Tier 3
@ -66,33 +62,33 @@ func TestLoadPolicies_Good_FieldMapping(t *testing.T) {
func TestLoadPolicies_Good_EmptyPolicies(t *testing.T) {
input := `{"policies": []}`
policies, err := LoadPolicies(strings.NewReader(input))
policies, err := LoadPolicies(core.NewReader(input))
require.NoError(t, err)
assert.Empty(t, policies)
}
func TestLoadPolicies_Bad_InvalidJSON(t *testing.T) {
_, err := LoadPolicies(strings.NewReader(`{invalid`))
_, err := LoadPolicies(core.NewReader(`{invalid`))
assert.Error(t, err)
}
func TestLoadPolicies_Bad_InvalidTier(t *testing.T) {
input := `{"policies": [{"tier": 0, "allowed": ["repo.push"]}]}`
_, err := LoadPolicies(strings.NewReader(input))
_, err := LoadPolicies(core.NewReader(input))
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid tier")
}
func TestLoadPolicies_Bad_TierTooHigh(t *testing.T) {
input := `{"policies": [{"tier": 99, "allowed": ["repo.push"]}]}`
_, err := LoadPolicies(strings.NewReader(input))
_, err := LoadPolicies(core.NewReader(input))
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid tier")
}
func TestLoadPolicies_Bad_UnknownField(t *testing.T) {
input := `{"policies": [{"tier": 1, "allowed": ["repo.push"], "bogus": true}]}`
_, err := LoadPolicies(strings.NewReader(input))
_, err := LoadPolicies(core.NewReader(input))
assert.Error(t, err, "DisallowUnknownFields should reject unknown fields")
}
@ -100,9 +96,8 @@ func TestLoadPolicies_Bad_UnknownField(t *testing.T) {
func TestLoadPoliciesFromFile_Good(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "policies.json")
err := os.WriteFile(path, []byte(validPolicyJSON), 0644)
require.NoError(t, err)
path := core.Path(dir, "policies.json")
writePolicyFile(t, path, validPolicyJSON)
policies, err := LoadPoliciesFromFile(path)
require.NoError(t, err)
@ -122,7 +117,7 @@ func TestApplyPolicies_Good(t *testing.T) {
pe := NewPolicyEngine(r)
// Apply custom policies from JSON
err := pe.ApplyPolicies(strings.NewReader(validPolicyJSON))
err := pe.ApplyPolicies(core.NewReader(validPolicyJSON))
require.NoError(t, err)
// Verify the Tier 2 policy was replaced
@ -144,7 +139,7 @@ func TestApplyPolicies_Bad_InvalidJSON(t *testing.T) {
r := NewRegistry()
pe := NewPolicyEngine(r)
err := pe.ApplyPolicies(strings.NewReader(`{invalid`))
err := pe.ApplyPolicies(core.NewReader(`{invalid`))
assert.Error(t, err)
}
@ -153,7 +148,7 @@ func TestApplyPolicies_Bad_InvalidTier(t *testing.T) {
pe := NewPolicyEngine(r)
input := `{"policies": [{"tier": 0, "allowed": ["repo.push"]}]}`
err := pe.ApplyPolicies(strings.NewReader(input))
err := pe.ApplyPolicies(core.NewReader(input))
assert.Error(t, err)
}
@ -161,15 +156,14 @@ func TestApplyPolicies_Bad_InvalidTier(t *testing.T) {
func TestApplyPoliciesFromFile_Good(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "policies.json")
err := os.WriteFile(path, []byte(validPolicyJSON), 0644)
require.NoError(t, err)
path := core.Path(dir, "policies.json")
writePolicyFile(t, path, validPolicyJSON)
r := NewRegistry()
require.NoError(t, r.Register(Agent{Name: "A", Tier: TierFull}))
pe := NewPolicyEngine(r)
err = pe.ApplyPoliciesFromFile(path)
err := pe.ApplyPoliciesFromFile(path)
require.NoError(t, err)
// Verify Tier 3 was replaced — only 3 allowed caps now
@ -191,14 +185,14 @@ func TestExportPolicies_Good(t *testing.T) {
r := NewRegistry()
pe := NewPolicyEngine(r) // loads defaults
var buf bytes.Buffer
err := pe.ExportPolicies(&buf)
buf := core.NewBuilder()
err := pe.ExportPolicies(buf)
require.NoError(t, err)
// Output should be valid JSON
var cfg PoliciesConfig
err = json.Unmarshal(buf.Bytes(), &cfg)
require.NoError(t, err)
result := core.JSONUnmarshalString(buf.String(), &cfg)
require.Truef(t, result.OK, "failed to unmarshal exported policies: %v", result.Value)
assert.Len(t, cfg.Policies, 3)
}
@ -208,15 +202,15 @@ func TestExportPolicies_Good_RoundTrip(t *testing.T) {
pe := NewPolicyEngine(r)
// Export
var buf bytes.Buffer
err := pe.ExportPolicies(&buf)
buf := core.NewBuilder()
err := pe.ExportPolicies(buf)
require.NoError(t, err)
// Create a new engine and apply the exported policies
r2 := NewRegistry()
require.NoError(t, r2.Register(Agent{Name: "A", Tier: TierFull}))
pe2 := NewPolicyEngine(r2)
err = pe2.ApplyPolicies(strings.NewReader(buf.String()))
err = pe2.ApplyPolicies(core.NewReader(buf.String()))
require.NoError(t, err)
// Evaluations should produce the same results
@ -229,6 +223,13 @@ func TestExportPolicies_Good_RoundTrip(t *testing.T) {
}
}
func writePolicyFile(t *testing.T, path, content string) {
t.Helper()
result := (&core.Fs{}).New("/").WriteMode(path, content, 0o644)
require.Truef(t, result.OK, "failed to write %s: %v", path, result.Value)
}
// --- Helper conversion ---
func TestToCapabilities_Good(t *testing.T) {

View file

@ -1,10 +1,9 @@
package trust
import (
"fmt"
"slices"
"strings"
core "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
)
@ -48,7 +47,7 @@ func (d Decision) String() string {
case NeedsApproval:
return "needs_approval"
default:
return fmt.Sprintf("unknown(%d)", int(d))
return core.Sprintf("unknown(%d)", int(d))
}
}
@ -90,7 +89,7 @@ func (pe *PolicyEngine) Evaluate(agentName string, cap Capability, repo string)
Decision: Deny,
Agent: agentName,
Cap: cap,
Reason: fmt.Sprintf("no policy for tier %s", agent.Tier),
Reason: core.Sprintf("no policy for tier %s", agent.Tier),
}
}
@ -100,7 +99,7 @@ func (pe *PolicyEngine) Evaluate(agentName string, cap Capability, repo string)
Decision: Deny,
Agent: agentName,
Cap: cap,
Reason: fmt.Sprintf("capability %s is denied for tier %s", cap, agent.Tier),
Reason: core.Sprintf("capability %s is denied for tier %s", cap, agent.Tier),
}
}
@ -110,7 +109,7 @@ func (pe *PolicyEngine) Evaluate(agentName string, cap Capability, repo string)
Decision: NeedsApproval,
Agent: agentName,
Cap: cap,
Reason: fmt.Sprintf("capability %s requires approval for tier %s", cap, agent.Tier),
Reason: core.Sprintf("capability %s requires approval for tier %s", cap, agent.Tier),
}
}
@ -124,7 +123,7 @@ func (pe *PolicyEngine) Evaluate(agentName string, cap Capability, repo string)
Decision: Deny,
Agent: agentName,
Cap: cap,
Reason: fmt.Sprintf("agent %q does not have access to repo %q", agentName, repo),
Reason: core.Sprintf("agent %q does not have access to repo %q", agentName, repo),
}
}
}
@ -132,7 +131,7 @@ func (pe *PolicyEngine) Evaluate(agentName string, cap Capability, repo string)
Decision: Allow,
Agent: agentName,
Cap: cap,
Reason: fmt.Sprintf("capability %s allowed for tier %s", cap, agent.Tier),
Reason: core.Sprintf("capability %s allowed for tier %s", cap, agent.Tier),
}
}
}
@ -141,14 +140,14 @@ func (pe *PolicyEngine) Evaluate(agentName string, cap Capability, repo string)
Decision: Deny,
Agent: agentName,
Cap: cap,
Reason: fmt.Sprintf("capability %s not granted for tier %s", cap, agent.Tier),
Reason: core.Sprintf("capability %s not granted for tier %s", cap, agent.Tier),
}
}
// SetPolicy replaces the policy for a given tier.
func (pe *PolicyEngine) SetPolicy(p Policy) error {
if !p.Tier.Valid() {
return coreerr.E("trust.SetPolicy", fmt.Sprintf("invalid tier %d", p.Tier), nil)
return coreerr.E("trust.SetPolicy", core.Sprintf("invalid tier %d", p.Tier), nil)
}
pe.policies[p.Tier] = &p
return nil
@ -218,8 +217,8 @@ func (pe *PolicyEngine) loadDefaults() {
// isRepoScoped returns true if the capability is constrained by repo scope.
func isRepoScoped(cap Capability) bool {
return strings.HasPrefix(string(cap), "repo.") ||
strings.HasPrefix(string(cap), "pr.") ||
return core.HasPrefix(string(cap), "repo.") ||
core.HasPrefix(string(cap), "pr.") ||
cap == CapReadSecrets
}
@ -248,14 +247,14 @@ func matchScope(pattern, repo string) bool {
}
// Check for wildcard patterns.
if !strings.Contains(pattern, "*") {
if !core.Contains(pattern, "*") {
return false
}
// "prefix/**" — recursive: matches anything under prefix/.
if strings.HasSuffix(pattern, "/**") {
if core.HasSuffix(pattern, "/**") {
prefix := pattern[:len(pattern)-3] // strip "/**"
if !strings.HasPrefix(repo, prefix+"/") {
if !core.HasPrefix(repo, prefix+"/") {
return false
}
// Must have something after the prefix/.
@ -263,14 +262,14 @@ func matchScope(pattern, repo string) bool {
}
// "prefix/*" — single level: matches prefix/X but not prefix/X/Y.
if strings.HasSuffix(pattern, "/*") {
if core.HasSuffix(pattern, "/*") {
prefix := pattern[:len(pattern)-2] // strip "/*"
if !strings.HasPrefix(repo, prefix+"/") {
if !core.HasPrefix(repo, prefix+"/") {
return false
}
remainder := repo[len(prefix)+1:]
// Must have a non-empty name, and no further slashes.
return remainder != "" && !strings.Contains(remainder, "/")
return remainder != "" && !core.Contains(remainder, "/")
}
// Unsupported wildcard position — fall back to no match.

View file

@ -11,11 +11,11 @@
package trust
import (
"fmt"
"iter"
"sync"
"time"
core "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
)
@ -41,7 +41,7 @@ func (t Tier) String() string {
case TierFull:
return "full"
default:
return fmt.Sprintf("unknown(%d)", int(t))
return core.Sprintf("unknown(%d)", int(t))
}
}
@ -102,7 +102,7 @@ func (r *Registry) Register(agent Agent) error {
return coreerr.E("trust.Register", "agent name is required", nil)
}
if !agent.Tier.Valid() {
return coreerr.E("trust.Register", fmt.Sprintf("invalid tier %d for agent %q", agent.Tier, agent.Name), nil)
return coreerr.E("trust.Register", core.Sprintf("invalid tier %d for agent %q", agent.Tier, agent.Name), nil)
}
if agent.CreatedAt.IsZero() {
agent.CreatedAt = time.Now()

View file

@ -1,11 +1,11 @@
package trust
import (
"fmt"
"sync"
"testing"
"time"
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -196,7 +196,7 @@ func TestConcurrentRegistryOperations_Good(t *testing.T) {
for i := range n {
go func(idx int) {
defer wg.Done()
name := fmt.Sprintf("agent-%d", idx)
name := core.Sprintf("agent-%d", idx)
err := r.Register(Agent{Name: name, Tier: TierVerified})
assert.NoError(t, err)
}(i)
@ -206,7 +206,7 @@ func TestConcurrentRegistryOperations_Good(t *testing.T) {
for i := range n {
go func(idx int) {
defer wg.Done()
name := fmt.Sprintf("agent-%d", idx)
name := core.Sprintf("agent-%d", idx)
_ = r.Get(name) // Just exercise the read path
}(i)
}
@ -215,7 +215,7 @@ func TestConcurrentRegistryOperations_Good(t *testing.T) {
for i := range n {
go func(idx int) {
defer wg.Done()
name := fmt.Sprintf("agent-%d", idx)
name := core.Sprintf("agent-%d", idx)
_ = r.Remove(name)
}(i)
}
@ -281,7 +281,7 @@ func TestConcurrentListDuringMutations_Good(t *testing.T) {
// Pre-populate
for i := range 5 {
require.NoError(t, r.Register(Agent{
Name: fmt.Sprintf("base-%d", i),
Name: core.Sprintf("base-%d", i),
Tier: TierFull,
}))
}
@ -302,7 +302,7 @@ func TestConcurrentListDuringMutations_Good(t *testing.T) {
for i := range 10 {
go func(idx int) {
defer wg.Done()
name := fmt.Sprintf("concurrent-%d", idx)
name := core.Sprintf("concurrent-%d", idx)
_ = r.Register(Agent{Name: name, Tier: TierUntrusted})
}(i)
}