mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 21:39:54 +02:00
A skill_file row whose path is the skill's own SKILL.md (persisted by older builds or direct create/update API calls) collides with the primary content the daemon writes itself, failing task prep with errPathPreExists on every non-codex local runtime (#3489). #3526 guarded this with strings.EqualFold(path, "SKILL.md") at the daemon write site and the three API ingress points, but the stored path is not canonicalized: "./SKILL.md" or "sub/../SKILL.md" slip past the exact-match guard while filepath.Join still resolves them onto the same SKILL.md, so prep can still break. Extract one canonical helper, skill.IsReservedContentPath, that cleans the path before the case-insensitive compare, and use it at all four sites (execenv writeSkillFiles, skill create, update, single-file upsert). Add a daemon-side regression test for writeSkillFiles ignoring a bundled SKILL.md (exact + "./" spellings) — the load-bearing fix previously had only API-layer coverage — plus a unit test for the helper. Existing poisoned rows are intentionally left in place (skipped at prep) per the decision on MUL-2928. MUL-2928 Follow-up to #3526; supersedes #3560. Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
28 lines
1.1 KiB
Go
28 lines
1.1 KiB
Go
package skill
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// ContentFilename is the canonical filename of a skill's primary content
|
|
// (the Content field). The daemon writes Content to this path itself when
|
|
// preparing the execution environment, so it is reserved: a supporting file
|
|
// may not also claim it.
|
|
const ContentFilename = "SKILL.md"
|
|
|
|
// IsReservedContentPath reports whether p targets the reserved primary
|
|
// content file (SKILL.md).
|
|
//
|
|
// The path is cleaned before comparison so non-canonical spellings like
|
|
// "./SKILL.md" or "sub/../SKILL.md" — which filepath.Join still resolves onto
|
|
// the very SKILL.md the daemon writes itself — are caught too. An exact
|
|
// string match would let them slip through both the API guards and the daemon
|
|
// guard, and the duplicate write would then fail task prep with
|
|
// errPathPreExists (or, on the nil-manifest path, clobber the primary
|
|
// content). Comparison is case-insensitive to match the rest of the SKILL.md
|
|
// handling in this package and the daemon.
|
|
func IsReservedContentPath(p string) bool {
|
|
return strings.EqualFold(filepath.Clean(p), ContentFilename)
|
|
}
|