Skip to content
Open
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
9 changes: 6 additions & 3 deletions internal/cli/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ func lockOneAgent(ctx context.Context, agentName, absFullsendDir, forgeFlag stri
WorkspaceRoot: absFullsendDir,
FetchPolicy: policy,
AuditLogPath: filepath.Join(absFullsendDir, ".fullsend-cache", "fetch-audit.jsonl"),
OrgAllowlist: orgAllowlist,
MaxDepth: rFlags.maxDepth,
MaxResources: rFlags.maxResources,
TreeFetcher: rFlags.treeFetcher,
Expand Down Expand Up @@ -713,7 +714,7 @@ func lockForgePlatforms(harnessPath, forgePlatform string) ([]string, error) {
// Mutations are collected first and applied only after all dependencies are
// confirmed present in cache, so a partial failure leaves the harness unchanged
// and the caller can safely fall back to network-based resolution.
func resolveFromLock(h *harness.Harness, entry *lock.HarnessLock, workspaceRoot string, printer *ui.Printer) (resolve.ResolveResult, error) {
func resolveFromLock(h *harness.Harness, entry *lock.HarnessLock, workspaceRoot string, orgAllowlist []string, printer *ui.Printer) (resolve.ResolveResult, error) {
type mutation struct {
field string
localPath string
Expand All @@ -727,8 +728,10 @@ func resolveFromLock(h *harness.Harness, entry *lock.HarnessLock, workspaceRoot
for _, lockDep := range entry.Dependencies {
// Agent source URLs are validated against the org-level allowlist
// during lock creation, not the harness's own AllowedRemoteResources.
// Skip the harness-level allowlist check for these entries.
if lockDep.Field != "agent_source" && h.MatchingAllowedPrefix(lockDep.URL) == "" {
// Skip the allowlist check for these entries.
if lockDep.Field != "agent_source" &&
h.MatchingAllowedPrefix(lockDep.URL) == "" &&
harness.MatchingAllowedPrefixInList(lockDep.URL, orgAllowlist) == "" {
return resolve.ResolveResult{}, fmt.Errorf(
"locked dependency %s (%s) is no longer in allowed_remote_resources — run 'fullsend lock' to update",
lockDep.Field, lockDep.URL)
Expand Down
76 changes: 38 additions & 38 deletions internal/cli/lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ allowed_remote_resources:
require.NoError(t, err)
require.NoError(t, h2.ResolveRelativeTo(dir))

lockResult, err := resolveFromLock(h2, entry, dir, printer)
lockResult, err := resolveFromLock(h2, entry, dir, nil, printer)
require.NoError(t, err)

// Verify the round-trip: agent resolved as file, skill resolved as directory.
Expand Down Expand Up @@ -574,7 +574,7 @@ func TestResolveFromLock_Success(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)

Expand All @@ -601,7 +601,7 @@ func TestResolveFromLock_MissingCache(t *testing.T) {
}

printer := ui.New(os.Stdout)
_, err := resolveFromLock(h, entry, t.TempDir(), printer)
_, err := resolveFromLock(h, entry, t.TempDir(), nil, printer)
require.Error(t, err)
assert.Contains(t, err.Error(), "not in cache")
}
Expand Down Expand Up @@ -630,7 +630,7 @@ func TestResolveFromLock_SkillSlots(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 2)

Expand Down Expand Up @@ -658,7 +658,7 @@ func TestResolveFromLock_TransitiveDeps(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)

Expand Down Expand Up @@ -692,7 +692,7 @@ func TestResolveFromLock_DiamondDependency(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)

Expand Down Expand Up @@ -726,7 +726,7 @@ func TestResolveFromLock_OverrideSlots(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)

Expand Down Expand Up @@ -765,7 +765,7 @@ func TestResolveFromLock_OverrideSlotsPreservesExisting(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 2)

Expand Down Expand Up @@ -795,7 +795,7 @@ func TestResolveFromLock_OverrideDoesNotCorruptSource(t *testing.T) {
}

printer := ui.New(os.Stdout)
_, err := resolveFromLock(h, entry, root, printer)
_, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)

assert.Equal(t, "skills/pr-review", h.Skills[0].Source)
Expand Down Expand Up @@ -836,7 +836,7 @@ func TestResolveFromLock_DirectoryType(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)

Expand Down Expand Up @@ -879,7 +879,7 @@ func TestResolveFromLock_DirectoryTypeScript(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)

Expand Down Expand Up @@ -922,7 +922,7 @@ func TestResolveFromLock_EmptyTypeDefaultsToFile(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)
assert.Equal(t, "file", lockResult.Deps[0].Type, "empty Type should default to file for backward compatibility")
Expand All @@ -948,7 +948,7 @@ func TestResolveFromLock_TransitivePolicySkipped(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)

Expand Down Expand Up @@ -981,7 +981,7 @@ func TestResolveFromLock_NoPartialMutation(t *testing.T) {
}

printer := ui.New(os.Stdout)
_, err := resolveFromLock(h, entry, root, printer)
_, err := resolveFromLock(h, entry, root, nil, printer)
require.Error(t, err)

// Harness should be unchanged — no partial mutations.
Expand Down Expand Up @@ -1055,7 +1055,7 @@ func TestResolveFromLock_BaseFieldNoOp(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)

// All three deps should be returned (base, agent, skill).
Expand Down Expand Up @@ -1119,7 +1119,7 @@ func TestResolveFromLock_AgentSourceNoOp(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)

// All three deps should be returned.
Expand Down Expand Up @@ -1158,7 +1158,7 @@ func TestResolveFromLock_ValidationLoopSchema(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 2)

Expand Down Expand Up @@ -1502,7 +1502,7 @@ func TestResolveFromLock_ProfileReconstruction(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)
require.Len(t, lockResult.Profiles, 1)
Expand Down Expand Up @@ -1563,7 +1563,7 @@ func TestResolveFromLock_ProfileSymlinkError(t *testing.T) {
}

printer := ui.New(os.Stdout)
_, err = resolveFromLock(h, entry, root, printer)
_, err = resolveFromLock(h, entry, root, nil, printer)
require.Error(t, err)
assert.Contains(t, err.Error(), "naming cached profile")
}
Expand Down Expand Up @@ -1594,7 +1594,7 @@ func TestResolveFromLock_ProfileEmptyID(t *testing.T) {
}

printer := ui.New(os.Stdout)
_, err := resolveFromLock(h, entry, root, printer)
_, err := resolveFromLock(h, entry, root, nil, printer)
require.Error(t, err)
assert.Contains(t, err.Error(), "has no id")
}
Expand Down Expand Up @@ -1623,7 +1623,7 @@ func TestResolveFromLock_ProviderReconstruction(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Providers, 1)
assert.Equal(t, "my-provider", lockResult.Providers[0].Def.Name)
Expand Down Expand Up @@ -1655,7 +1655,7 @@ func TestResolveFromLock_ProviderMissingName(t *testing.T) {
}

printer := ui.New(os.Stdout)
_, err := resolveFromLock(h, entry, root, printer)
_, err := resolveFromLock(h, entry, root, nil, printer)
require.Error(t, err)
assert.Contains(t, err.Error(), "has no name")
}
Expand Down Expand Up @@ -1684,7 +1684,7 @@ func TestResolveFromLock_ProviderMissingType(t *testing.T) {
}

printer := ui.New(os.Stdout)
_, err := resolveFromLock(h, entry, root, printer)
_, err := resolveFromLock(h, entry, root, nil, printer)
require.Error(t, err)
assert.Contains(t, err.Error(), "has no type")
}
Expand Down Expand Up @@ -1713,7 +1713,7 @@ func TestResolveFromLock_ProviderLiteralCredentialWarning(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)
assert.NotEmpty(t, lockResult.Deps[0].Warning)
Expand Down Expand Up @@ -1745,7 +1745,7 @@ func TestResolveFromLock_RejectsDisallowedURL(t *testing.T) {
}

printer := ui.New(os.Stdout)
_, err := resolveFromLock(h, entry, root, printer)
_, err := resolveFromLock(h, entry, root, nil, printer)
require.Error(t, err)
assert.Contains(t, err.Error(), "no longer in allowed_remote_resources")
assert.Contains(t, err.Error(), "example.com")
Expand Down Expand Up @@ -1776,7 +1776,7 @@ func TestResolveFromLock_EmptyAllowlistDeniesURLs(t *testing.T) {
}

printer := ui.New(os.Stdout)
_, err := resolveFromLock(h, entry, root, printer)
_, err := resolveFromLock(h, entry, root, nil, printer)
require.Error(t, err)
assert.Contains(t, err.Error(), "no longer in allowed_remote_resources")
}
Expand Down Expand Up @@ -1814,7 +1814,7 @@ func TestResolveFromLock_PluginMalformedFieldError(t *testing.T) {
}

printer := ui.New(os.Stdout)
_, err = resolveFromLock(h, entry, root, printer)
_, err = resolveFromLock(h, entry, root, nil, printer)
require.Error(t, err)
assert.Contains(t, err.Error(), "cannot parse plugin index")
}
Expand Down Expand Up @@ -1851,7 +1851,7 @@ func TestResolveFromLock_PluginOutOfRangeError(t *testing.T) {
}

printer := ui.New(os.Stdout)
_, err = resolveFromLock(h, entry, root, printer)
_, err = resolveFromLock(h, entry, root, nil, printer)
require.Error(t, err)
assert.Contains(t, err.Error(), "out of range")
}
Expand Down Expand Up @@ -1893,7 +1893,7 @@ func TestResolveFromLock_PluginExecutablePermissions(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)

Expand Down Expand Up @@ -1940,7 +1940,7 @@ func TestResolveFromLock_PluginSlots(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)

Expand Down Expand Up @@ -1984,7 +1984,7 @@ func TestResolveFromLock_PluginSharedURLWithSkill(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)

Expand Down Expand Up @@ -2031,7 +2031,7 @@ func TestResolveFromLock_PluginRawContentURL(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)

Expand Down Expand Up @@ -2082,7 +2082,7 @@ func TestResolveFromLock_SkillRawContentURL(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 2)
require.Len(t, h.Skills, 2)
Expand Down Expand Up @@ -2139,7 +2139,7 @@ func TestResolveFromLock_ForgeScopedSkillNoMutation(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Deps, 1)

Expand Down Expand Up @@ -2180,7 +2180,7 @@ func TestResolveFromLock_SkillRepoRootURLRejected(t *testing.T) {
}

printer := ui.New(os.Stdout)
_, err = resolveFromLock(h, entry, root, printer)
_, err = resolveFromLock(h, entry, root, nil, printer)
require.Error(t, err)
assert.Contains(t, err.Error(), "skills[0]: URL must point to a directory inside the repo, not the repo root")
}
Expand Down Expand Up @@ -2218,7 +2218,7 @@ func TestResolveFromLock_PluginInvalidBasenameRejected(t *testing.T) {
}

printer := ui.New(os.Stdout)
_, err = resolveFromLock(h, entry, root, printer)
_, err = resolveFromLock(h, entry, root, nil, printer)
require.Error(t, err)
assert.Contains(t, err.Error(), "contains invalid characters")
}
Expand Down Expand Up @@ -2319,7 +2319,7 @@ func TestResolveFromLock_LocalPathsSurviveStrip(t *testing.T) {
}

printer := ui.New(os.Stdout)
_, err := resolveFromLock(h, entry, root, printer)
_, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)

// Local-path profile must survive the strip.
Expand Down Expand Up @@ -2368,7 +2368,7 @@ func TestResolveFromLock_ProfileAndProviderReconstruction(t *testing.T) {
}

printer := ui.New(os.Stdout)
lockResult, err := resolveFromLock(h, entry, root, printer)
lockResult, err := resolveFromLock(h, entry, root, nil, printer)
require.NoError(t, err)
require.Len(t, lockResult.Profiles, 1)
assert.Equal(t, "anthropic", lockResult.Profiles[0].ID)
Expand Down
3 changes: 2 additions & 1 deletion internal/cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ func runAgent(ctx context.Context, agentName, fullsendDir, outputBase, targetRep
printer.StepWarn(fmt.Sprintf("Harness has changed since lock file was generated. Run 'fullsend lock %s --fullsend-dir %s' to update.", agentName, fullsendDir))
} else {
printer.StepStart("Using pinned dependencies from lock file")
lockResult, lockResolveErr := resolveFromLock(h, entry, absFullsendDir, printer)
lockResult, lockResolveErr := resolveFromLock(h, entry, absFullsendDir, orgAllowlist, printer)
if lockResolveErr != nil {
printer.StepFail("Lock file resolution failed: " + lockResolveErr.Error())
printer.StepWarn("Falling back to normal resolution")
Expand Down Expand Up @@ -474,6 +474,7 @@ func runAgent(ctx context.Context, agentName, fullsendDir, outputBase, targetRep
WorkspaceRoot: absFullsendDir,
FetchPolicy: policy,
AuditLogPath: filepath.Join(absFullsendDir, ".fullsend-cache", "fetch-audit.jsonl"),
OrgAllowlist: orgAllowlist,
MaxDepth: rFlags.maxDepth,
MaxResources: rFlags.maxResources,
TreeFetcher: rFlags.treeFetcher,
Expand Down
Loading
Loading