Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,26 @@ repos:

- repo: local
hooks:
- id: go-fmt
Comment thread
jhutar marked this conversation as resolved.
name: gofmt
entry: gofmt -w
language: system
types: [go]

- id: go-vet
name: go vet
entry: go vet ./...
language: system
types: [go]
pass_filenames: false

- id: lint-mint-embed-sync
name: lint mint embed sync
entry: ./hack/lint-mint-embed-sync
Comment thread
jhutar marked this conversation as resolved.
language: script
files: ^(internal/mint/|internal/mintcore/|internal/dispatch/gcf/mintsrc/)
pass_filenames: false

- id: lint-adr-status
name: lint ADR statuses
entry: ./hack/lint-adr-status
Expand Down
57 changes: 57 additions & 0 deletions hack/lint-mint-embed-sync
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/bin/bash

# lint-mint-embed-sync - Verify that the mint Cloud Function source files
# (main.go, go.mod, go.sum) match their embedded copies used for deployment.
# See CLAUDE.md for details on the mint/embed sync requirement.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

rc=0

SRC="$REPO_ROOT/internal/mint"
DST="$REPO_ROOT/internal/dispatch/gcf/mintsrc"

for f in "$SRC"/*; do
Comment thread
jhutar marked this conversation as resolved.
name="$(basename "$f")"
if [[ "$name" == *_test.go ]]; then
continue
fi

# "go.mod" special case since commit eecac658c
if [[ "$name" == "go.mod" ]]; then
ff="$( mktemp )"
Comment thread
jhutar marked this conversation as resolved.
sed 's| => ../mintcore$| => ./mintcore|' "$f" >"$ff"
f="$ff"
fi

if ! cmp -s "$f" "$DST/$name.embed"; then
echo "DESYNC: internal/mint/$name != internal/dispatch/gcf/mintsrc/$name.embed" >&2
Comment thread
jhutar marked this conversation as resolved.
rc=1
fi

# "go.mod" special case cleanup
[[ "$name" == "go.mod" ]] && rm "$f"
done

SRC="$REPO_ROOT/internal/mintcore"
DST="$REPO_ROOT/internal/dispatch/gcf/mintsrc/mintcore"

for f in "$SRC"/*; do
name="$(basename "$f")"
if [[ "$name" == *_test.go ]]; then
continue
fi

if ! cmp -s "$f" "$DST/$name.embed"; then
echo "DESYNC: internal/mintcore/$name != internal/dispatch/gcf/mintsrc/mintcore/$name.embed" >&2
rc=1
fi
done

if [[ $rc -eq 0 ]]; then
echo "OK: mint embed files in sync"
fi
exit $rc
8 changes: 4 additions & 4 deletions internal/cli/bootstrap_input.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ type harnessBootstrapWithHooks struct {
hooks security.ClaudeSandboxHooks
}

func (b *harnessBootstrap) SandboxName() string { return b.sandboxName }
func (b *harnessBootstrap) AgentPath() string { return b.agentPath }
func (b *harnessBootstrap) SkillDirs() []string { return b.skillDirs }
func (b *harnessBootstrap) PluginDirs() []string { return b.pluginDirs }
func (b *harnessBootstrap) SandboxName() string { return b.sandboxName }
func (b *harnessBootstrap) AgentPath() string { return b.agentPath }
func (b *harnessBootstrap) SkillDirs() []string { return b.skillDirs }
func (b *harnessBootstrap) PluginDirs() []string { return b.pluginDirs }

func (b *harnessBootstrapWithHooks) ClaudeSandboxHooks() security.ClaudeSandboxHooks {
return b.hooks
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func TestRunLock_SkillDirectoryType(t *testing.T) {
skillMD := []byte("# Test skill\nA test skill.")
helperSh := []byte("#!/bin/bash\necho hello")
skillFiles := map[string][]byte{
"SKILL.md": skillMD,
"SKILL.md": skillMD,
"scripts/helper.sh": helperSh,
}
treeHash := fetch.ComputeTreeHash(skillFiles)
Expand Down
2 changes: 1 addition & 1 deletion internal/dispatch/gcf/gcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1977,7 +1977,7 @@ func TestLiveGCFClient_GetServiceRevisionInfo_ShortRevisionName(t *testing.T) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"template": map[string]interface{}{
"revision": "my-svc-00042-abc",
"revision": "my-svc-00042-abc",
"containers": []interface{}{map[string]interface{}{}},
},
"trafficStatuses": []interface{}{
Expand Down
4 changes: 2 additions & 2 deletions internal/dispatch/gcf/mintsrc/mintcore/github.go.embed
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ type installationResponse struct {

// installationTokenResponse is the response from POST /app/installations/{id}/access_tokens.
type installationTokenResponse struct {
Token string `json:"token"`
ExpiresAt string `json:"expires_at"`
Token string `json:"token"`
ExpiresAt string `json:"expires_at"`
Permissions map[string]string `json:"permissions,omitempty"`
Repositories []installationTokenRepository `json:"repositories,omitempty"`
RepositorySelection string `json:"repository_selection,omitempty"`
Expand Down
12 changes: 6 additions & 6 deletions internal/dispatch/gcf/mintsrc/mintcore/jwks_verifier.go.embed
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ type JWKSVerifier struct {
allowedWorkflowFiles []string
perRepoWIFRepos map[string]bool

mu sync.RWMutex
keys map[string]*rsa.PublicKey
cachedJWKSURI string
fetchedAt time.Time
lastKidMissAt time.Time
refreshGroup singleflight.Group
mu sync.RWMutex
keys map[string]*rsa.PublicKey
cachedJWKSURI string
fetchedAt time.Time
lastKidMissAt time.Time
refreshGroup singleflight.Group
}

// JWKSVerifierConfig configures a new JWKSVerifier.
Expand Down
14 changes: 7 additions & 7 deletions internal/dispatch/gcf/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,16 @@ var embeddedMintSource embed.FS
// triggering Go's module boundary detection) to their real names for the
// Cloud Function deployment zip.
var embeddedMintFiles = map[string]string{
"go.mod.embed": "go.mod",
"go.sum.embed": "go.sum",
"main.go.embed": "main.go",
"mintcore/go.mod.embed": "mintcore/go.mod",
"mintcore/go.sum.embed": "mintcore/go.sum",
"mintcore/gcp_pem.go.embed": "mintcore/gcp_pem.go",
"go.mod.embed": "go.mod",
"go.sum.embed": "go.sum",
"main.go.embed": "main.go",
"mintcore/go.mod.embed": "mintcore/go.mod",
"mintcore/go.sum.embed": "mintcore/go.sum",
"mintcore/gcp_pem.go.embed": "mintcore/gcp_pem.go",
"mintcore/github.go.embed": "mintcore/github.go",
"mintcore/handler.go.embed": "mintcore/handler.go",
"mintcore/interfaces.go.embed": "mintcore/interfaces.go",
"mintcore/jwks_verifier.go.embed": "mintcore/jwks_verifier.go",
"mintcore/jwks_verifier.go.embed": "mintcore/jwks_verifier.go",
"mintcore/claims.go.embed": "mintcore/claims.go",
"mintcore/patterns.go.embed": "mintcore/patterns.go",
"mintcore/sts_verifier.go.embed": "mintcore/sts_verifier.go",
Expand Down
10 changes: 5 additions & 5 deletions internal/fetch/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,8 @@ func TestCachePutDir_CacheGetDir_RoundTrip(t *testing.T) {
root := t.TempDir()
url := "https://github.com/example/repo/tree/main/skills/review"
files := map[string][]byte{
"SKILL.md": []byte("# Review Skill\nA skill for reviews."),
"scripts/helper.sh": []byte("#!/bin/bash\necho helper"),
"SKILL.md": []byte("# Review Skill\nA skill for reviews."),
"scripts/helper.sh": []byte("#!/bin/bash\necho helper"),
"sub-agents/triage.md": []byte("# Triage sub-agent"),
}

Expand Down Expand Up @@ -322,9 +322,9 @@ func TestCacheGetDir_IntegrityVerification(t *testing.T) {
func TestCachePutDir_NestedDirectories(t *testing.T) {
root := t.TempDir()
files := map[string][]byte{
"SKILL.md": []byte("# Skill"),
"scripts/helper.sh": []byte("#!/bin/bash\necho hi"),
"sub-agents/review.md": []byte("# Review"),
"SKILL.md": []byte("# Skill"),
"scripts/helper.sh": []byte("#!/bin/bash\necho hi"),
"sub-agents/review.md": []byte("# Review"),
"sub-agents/deep/nested/file.md": []byte("# Deep nested"),
}

Expand Down
2 changes: 1 addition & 1 deletion internal/forge/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ type FakeClient struct {
CreatedReviews []ReviewRecord
DismissedReviews []DismissedReviewRecord
CommittedFiles []CommitFilesRecord
DeletedComments []int // comment IDs
DeletedComments []int // comment IDs

// internal counters
proposalCounter int
Expand Down
2 changes: 1 addition & 1 deletion internal/forge/github/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func AgentAppConfig(org, role, appSet string) AppConfig {

base.Name = fmt.Sprintf("%s-%s", appSet, role)

switch role{
switch role {
case "fullsend":
base.Description = fmt.Sprintf("Fullsend orchestrator for %s", org)
base.Permissions = AppPermissions{
Expand Down
10 changes: 5 additions & 5 deletions internal/harness/compose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,10 +373,10 @@ forge:
require.NoError(t, err)

// GitHub forge merged, then resolved
assert.Equal(t, "base-pre.sh", h.PreScript) // from base forge
assert.Equal(t, "child-post.sh", h.PostScript) // from child forge
assert.Contains(t, h.Skills, "gh-skill-base") // base skills
assert.Contains(t, h.Skills, "gh-skill-child") // child skills
assert.Equal(t, "base-pre.sh", h.PreScript) // from base forge
assert.Equal(t, "child-post.sh", h.PostScript) // from child forge
assert.Contains(t, h.Skills, "gh-skill-base") // base skills
assert.Contains(t, h.Skills, "gh-skill-child") // child skills
assert.Equal(t, "base-value1", h.RunnerEnv["GH_KEY1"])
assert.Equal(t, "child-value2", h.RunnerEnv["GH_KEY2"])

Expand Down Expand Up @@ -805,7 +805,7 @@ func TestMergeForgeBlocks(t *testing.T) {
// GitHub merged
gh := result["github"]
require.NotNil(t, gh)
assert.Equal(t, "base-pre.sh", gh.PreScript) // inherited
assert.Equal(t, "base-pre.sh", gh.PreScript) // inherited
assert.Equal(t, "child-post.sh", gh.PostScript) // from child
assert.Equal(t, []string{"base-skill", "child-skill"}, gh.Skills)
assert.Equal(t, "base1", gh.RunnerEnv["KEY1"]) // inherited
Expand Down
4 changes: 2 additions & 2 deletions internal/harness/forge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ func TestResolveForge_RunnerEnvMerge(t *testing.T) {
Forge: map[string]*ForgeConfig{
"github": {
RunnerEnv: map[string]string{
"OVERRIDE": "forge_val",
"GH_TOKEN": "${GH_TOKEN}",
"OVERRIDE": "forge_val",
"GH_TOKEN": "${GH_TOKEN}",
},
},
},
Expand Down
54 changes: 27 additions & 27 deletions internal/harness/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ var (
validModelName = regexp.MustCompile(`^[a-zA-Z0-9_.@-]+$`)
validPluginName = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
// validRoleName mirrors mintcore.RolePattern — duplicated to avoid coupling harness→mintcore.
validRoleName = regexp.MustCompile(`^[a-z][a-z0-9_-]*$`)
validSlugName = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]*$`)
envVarRef = regexp.MustCompile(`\$\{([^}]+)\}`)
validRoleName = regexp.MustCompile(`^[a-z][a-z0-9_-]*$`)
validSlugName = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]*$`)
envVarRef = regexp.MustCompile(`\$\{([^}]+)\}`)
)

// HostFile describes a file on the host that must be copied into the sandbox
Expand Down Expand Up @@ -124,8 +124,8 @@ type SandboxHooks struct {
SecretRedactPostTool *bool `yaml:"secret_redact_posttool,omitempty"` // default: true
UnicodePostTool *bool `yaml:"unicode_posttool,omitempty"` // default: true
ContextSuppressPostTool *bool `yaml:"context_suppress_posttool,omitempty"` // default: true
CanaryPreTool *bool `yaml:"canary_pretool,omitempty"` // default: true
CanaryPostTool *bool `yaml:"canary_posttool,omitempty"` // default: true
CanaryPreTool *bool `yaml:"canary_pretool,omitempty"` // default: true
CanaryPostTool *bool `yaml:"canary_posttool,omitempty"` // default: true
ToolAllowlistPreTool *ToolAllowlistConfig `yaml:"tool_allowlist_pretool,omitempty"`
}

Expand Down Expand Up @@ -195,29 +195,29 @@ type ValidationLoop struct {
// Harness is the per-agent configuration that the runner reads to provision
// a sandbox and launch one agent. It follows the ADR-0017 schema.
type Harness struct {
Agent string `yaml:"agent"`
Doc string `yaml:"doc,omitempty"` // source-repo-only; not resolved at runtime, used by lint-agent-docs
Description string `yaml:"description,omitempty"`
Role string `yaml:"role,omitempty"`
Slug string `yaml:"slug,omitempty"`
Base string `yaml:"base,omitempty"`
Image string `yaml:"image,omitempty"`
Policy string `yaml:"policy,omitempty"`
Skills []string `yaml:"skills,omitempty"`
Plugins []string `yaml:"plugins,omitempty"`
Providers []string `yaml:"providers,omitempty"`
HostFiles []HostFile `yaml:"host_files,omitempty"`
APIServers []APIServer `yaml:"api_servers,omitempty"`
Model string `yaml:"model,omitempty"`
PreScript string `yaml:"pre_script,omitempty"`
PostScript string `yaml:"post_script,omitempty"`
AgentInput string `yaml:"agent_input,omitempty"`
ValidationLoop *ValidationLoop `yaml:"validation_loop,omitempty"`
RunnerEnv map[string]string `yaml:"runner_env,omitempty"`
TimeoutMinutes int `yaml:"timeout_minutes,omitempty"`
SandboxTimeoutSeconds int `yaml:"sandbox_timeout_seconds,omitempty"`
Agent string `yaml:"agent"`
Doc string `yaml:"doc,omitempty"` // source-repo-only; not resolved at runtime, used by lint-agent-docs
Description string `yaml:"description,omitempty"`
Role string `yaml:"role,omitempty"`
Slug string `yaml:"slug,omitempty"`
Base string `yaml:"base,omitempty"`
Image string `yaml:"image,omitempty"`
Policy string `yaml:"policy,omitempty"`
Skills []string `yaml:"skills,omitempty"`
Plugins []string `yaml:"plugins,omitempty"`
Providers []string `yaml:"providers,omitempty"`
HostFiles []HostFile `yaml:"host_files,omitempty"`
APIServers []APIServer `yaml:"api_servers,omitempty"`
Model string `yaml:"model,omitempty"`
PreScript string `yaml:"pre_script,omitempty"`
PostScript string `yaml:"post_script,omitempty"`
AgentInput string `yaml:"agent_input,omitempty"`
ValidationLoop *ValidationLoop `yaml:"validation_loop,omitempty"`
RunnerEnv map[string]string `yaml:"runner_env,omitempty"`
TimeoutMinutes int `yaml:"timeout_minutes,omitempty"`
SandboxTimeoutSeconds int `yaml:"sandbox_timeout_seconds,omitempty"`
Security *SecurityConfig `yaml:"security,omitempty"`
AllowedRemoteResources []string `yaml:"allowed_remote_resources,omitempty"`
AllowedRemoteResources []string `yaml:"allowed_remote_resources,omitempty"`
Forge map[string]*ForgeConfig `yaml:"forge,omitempty"`
}

Expand Down
12 changes: 6 additions & 6 deletions internal/harness/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ forge:
// Skills = base skills + child skills + forge.github skills (concatenated in order).
assert.Equal(t, []string{
"base-skill-1", "base-skill-2", // from base top-level
"child-skill-1", // from child top-level
"gh-forge-skill", // from forge.github
"child-skill-1", // from child top-level
"gh-forge-skill", // from forge.github
}, h.Skills)

// RunnerEnv = base env merged with forge env (forge keys win).
Expand Down Expand Up @@ -146,10 +146,10 @@ forge:
// then ResolveForge appends the merged forge skills to the already-concatenated
// top-level skills (base-top + child-top).
assert.Equal(t, []string{
"base-s1", // base top-level
"child-s1", // child top-level
"base-forge-s1", // base forge.github (merged into child forge)
"child-forge-s1", // child forge.github
"base-s1", // base top-level
"child-s1", // child top-level
"base-forge-s1", // base forge.github (merged into child forge)
"child-forge-s1", // child forge.github
}, h.Skills)
}

Expand Down
10 changes: 6 additions & 4 deletions internal/layers/inference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ type fakeProvider struct {
err error
}

func (f *fakeProvider) Name() string { return f.name }
func (f *fakeProvider) SecretNames() []string { return f.secretNames }
func (f *fakeProvider) Provision(_ context.Context) (map[string]string, error) { return f.secrets, f.err }
func (f *fakeProvider) Variables() map[string]string { return f.variables }
func (f *fakeProvider) Name() string { return f.name }
func (f *fakeProvider) SecretNames() []string { return f.secretNames }
func (f *fakeProvider) Provision(_ context.Context) (map[string]string, error) {
return f.secrets, f.err
}
func (f *fakeProvider) Variables() map[string]string { return f.variables }

func newInferenceLayer(t *testing.T, client *forge.FakeClient, provider inference.Provider) (*InferenceLayer, *bytes.Buffer) {
t.Helper()
Expand Down
4 changes: 2 additions & 2 deletions internal/layers/secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ func twoAgents() []AgentCredentials {
{
AgentEntry: config.AgentEntry{Role: "fullsend", Name: "FullsendBot", Slug: "fullsend-bot"},
PEM: "-----BEGIN RSA PRIVATE KEY-----\nfullsend-key\n-----END RSA PRIVATE KEY-----",
ClientID: "Iv1.abc111",
ClientID: "Iv1.abc111",
},
{
AgentEntry: config.AgentEntry{Role: "triage", Name: "TriageBot", Slug: "triage-bot"},
PEM: "-----BEGIN RSA PRIVATE KEY-----\ntriage-key\n-----END RSA PRIVATE KEY-----",
ClientID: "Iv1.abc222",
ClientID: "Iv1.abc222",
},
}
}
Expand Down
2 changes: 0 additions & 2 deletions internal/layers/workflows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ func TestWorkflowsLayer_Install_ManagedHeaders(t *testing.T) {
}
}


func TestWorkflowsLayer_Install_Error(t *testing.T) {
client := &forge.FakeClient{
Repos: []forge.Repository{{
Expand Down Expand Up @@ -165,7 +164,6 @@ func TestWorkflowsLayer_Install_ExecutableModes(t *testing.T) {
}
}


func TestWorkflowsLayer_Uninstall_Noop(t *testing.T) {
client := forge.NewFakeClient()
layer, _ := newWorkflowsLayer(t, client)
Expand Down
Loading
Loading