Adversarial review found a display-vs-run mismatch: the seat picker collapses the library to the
first row per key, but ListSkills ordered by version only — so for a key the org authored alongside
a higher-versioned builtin, the picker showed/flagged the builtin while the run injected the org's
own skill. ListSkills now orders the same way the run-time catalog resolves (Published-first,
org-owned-over-builtin, then latest version with the same Ordinal comparison), computed in-memory so
the version tiebreak can't diverge from SkillCatalog. The run itself was already correct; this aligns
what the operator sees with what executes. No client change needed.
SkillRunScopingTests now also asserts the library's first row for a key the org authored is the
org-owned Published row, not the builtin.
Verified: skills test subset 4/4 (full suite green pre-merge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Orgs can now share skills across the tenant boundary — the next step after the per-org library.
Endpoints (all ManageSkills-gated + audited):
- POST /{key}/publish — list one of your published versions on the marketplace (Visibility→Public;
only a Published/golden-tested skill may be listed). POST /{key}/unpublish reverses it.
- POST /install — copy a publicly-listed skill (by row id) into your org as a private Installed
copy; rejects installing your own skill and duplicate (org+key+version) installs.
- GET /marketplace?organizationId= — other orgs' Authored+Public+Published skills (yours excluded),
each flagged whether that exact (key, version) is already in your library.
- SkillSummary now carries Id (install targets a specific source row). Authored skills default to
private — listing is an explicit publish step, never a side effect of authoring.
UI (Skills page): a Marketplace tab with Install / "In your library"; Publish / Unlist on your own
published skills; a "Listed" badge.
Fixes from the adversarial review (4 confirmed findings, all addressed):
- HIGH — Public⟹Published is now a domain invariant (Skill.Index forces PrivateToOrg whenever the
re-derived status isn't Published), so re-authoring a listed version without golden tests can no
longer leave it Public+Draft or decouple the marketplace gate from the eval gate.
- MEDIUM — install now uses an insert-only indexer path so the (org,key,version) unique index is the
source of truth: a race with a concurrent install/author becomes a clean 409, never an in-place
clobber of an existing row's content/ownership.
- MEDIUM/LOW — AlreadyInLibrary is computed per (key, version) to match the install conflict rule, so
a newer, not-yet-owned version of a key you already hold still shows as installable.
Verified: ArchitectureTests 8/8, IntegrationTests 47/47 (SkillMarketplaceTests: publish gate, own-org
exclusion, cross-org list→install→private copy, duplicate 409, per-version flag, Public⟹Published
invariant, Member 403), client build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Skills move from a global Git-only registry to a per-company library that orgs author and
version in-app — Git stays as the shared *starter* library.
Domain & persistence:
- Skill gains OrganizationId (null = shared builtin, visible to every org), Origin
(Builtin | Authored | Installed), AuthoredByMemberId. Identity is now
(OrganizationId, SkillKey, Version); the unique index uses NULLS NOT DISTINCT so builtins
stay unique by key+version while each org gets its own namespace (and can fork a builtin).
AddSkillOwnership migration backfills existing rows as Builtin.
- Owned GoldenExample rows are cloned in Skill.Index so a fork can't re-parent the source's
tracked entities.
Authoring (tenant, dynamic):
- POST /api/skills/authored — structured fields → same indexer pipeline (embedding +
publish gate apply identically), tagged org + author. POST /api/skills/{key}/fork copies a
builtin/global skill into your org as an editable Authored draft. List/Get are org-scoped
(your org + shared builtins). New Capability.ManageSkills (Owner + TeamOwner), audited.
- GET /api/skills/marketplace: read-only seam listing public skills across orgs (install is
the next step).
Security (from adversarial review — two confirmed criticals):
- Managing shared builtins is an operator action, not a tenant one. /index (posts arbitrary
content as a global builtin) and /sync (re-indexes the shared library) now require a
platform admin key (X-Skills-Admin-Key, fixed-time compare, fail-closed when unset) via
SkillAdminOptions — previously any authenticated user of any org could inject/poison global
skills. New test asserts an authenticated Owner without the key gets 403 on both.
UI: new /skills library page — browse shared + org skills grouped by key with their versions,
create / new-version / fork, golden-test editor + body, Draft/Published badge and the
publish-gate hint (needs roles + ≥1 golden test).
Verified: ArchitectureTests 8/8, IntegrationTests 46/46 (new SkillLibraryTests: org
isolation, version coexistence, fork, publish gate, Member 403, admin-gate 403), client build
green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Author the four V1 skill atoms in skills/ (Git is the source of truth): spec-writing &
story-breakdown (product-owner), test-plan-generation & diff-review (qa) — each with
risk-tagged actions, golden tests, and a body.
- SharedKernel: IGitProvider seam (read-only, provider-agnostic) + GitFile.
- Integrations module (its first real code): FileSystemGitProvider (dogfood/local) and a
GiteaGitProvider (Gitea REST: recursive tree → SKILL.md blobs → base64 contents); the
provider is chosen by GitSource:Provider config.
- Skills: SkillSyncService consumes IGitProvider (never Integrations) and indexes each file;
POST /api/skills/sync and a POST /api/skills/webhook/gitea (re-sync on push; signature
verification + changed-file-only + queue offload come later).
Verified: build green; ArchitectureTests 8/8 (Skills & Integrations reference only
SharedKernel; the Git seam lives in SharedKernel); IntegrationTests 22/22 incl. a sync that
indexes the four real atoms from skills/, published and queryable by role.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Skills module (references SharedKernel only):
- Skill entity + SkillsDbContext (schema "skills") + InitialSkills migration: roles/tools/
context as text[], risk-tagged actions and golden tests as jsonb, a nullable vector(384)
embedding, unique (SkillKey, Version).
- SkillMarkdownParser: YAML frontmatter (YamlDotNet) + markdown body → SkillManifest.
- HashingSkillEmbedder: placeholder deterministic embedder so the pgvector path is real now;
swapped for ONNX/BYOK embeddings at M3-M4 (384-dim to match MiniLM/bge).
- SkillIndexer: parse → hash → embed → upsert; structural publish gate (roles + >=1 golden
test). Executing golden tests against a model + gating on edit distance lands at M4.
- Endpoints: GET /api/skills (filter by role/visibility), GET /api/skills/{key},
POST /api/skills/index (manual/admin) — all authenticated.
Verified: build green; ArchitectureTests 8/8 (Skills references only SharedKernel);
IntegrationTests 21/21 incl. a new skill-registry flow — index a SKILL.md, it publishes,
is queryable by role (and not under others), re-index dedups, malformed is 400, catalogue
needs auth.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>