Add parseFacetName, parseSlug, and validateFacetName to protocol and enforce facet identity grammar in FacetManifestSchema#331
Conversation
…, integrate scoped `@scope/name` into `FacetManifestSchema`, and add OpenSpec change for scoped facet name support
🦋 Changeset detectedLatest commit: b34c363 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Member
Author
This stack of pull requests is managed by Graphite. Learn more about stacking. |
Member
Author
Merge activity
|
eXamadeus
pushed a commit
that referenced
this pull request
Jun 15, 2026
This PR was auto-generated by the release workflow. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @agent-facets/protocol@0.20.0 ### Minor Changes - [#331](#331) [`644f53b`](644f53b) Thanks [@eXamadeus](https://github.com/eXamadeus)! - Add a facet identity grammar to the public API and enforce it in `FacetManifestSchema`. New exports: `parseFacetName`, `parseSlug`, and `validateFacetName`, along with the `FacetName`, `FacetNameResult`, and `SlugResult` types. A facet identity is either an unscoped slug (`cowsay`) or a scoped `@scope/name` (`@julian/cowsay`), where every segment is a lowercase kebab slug. The parsers return discriminated-union results instead of throwing, and `parseSlug` is exported on its own so other facet-spec implementations (e.g. a registry enforcing scope ownership) validate scopes with the exact same grammar. `FacetManifestSchema` now validates the manifest `name` field against this grammar. This tightens the previous `name: string` behavior: malformed facet identities (uppercase, leading/trailing hyphens, traversal segments, extra path depth, missing slash after `@scope`, etc.) now fail at manifest validation instead of deferring failure to build, publish, or install. Asset names remain governed separately by `validateAssetName` — asset names stay local path-safe identifiers while facet identities may carry a registry scope.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Why
The registry identifies ownership with scoped facet names such as
@julian/cowsay, but the protocol had no authoritative grammar for what a facet identity is allowed to look like. The manifestnamefield was typed as a plainstring, meaning malformed identities (uppercase letters, trailing hyphens, traversal segments, missing slash after@scope, extra path depth) passed manifest validation and only failed later at build, publish, or install time.Details
A new
facet-name.tsmodule in@agent-facets/protocoldefines the canonical facet identity grammar around a single atomic unit, the slug: a lowercase-letter-start, lowercase/digit/hyphen interior, alphanumeric-end segment. A facet name is either an unscoped slug (cowsay) or a scoped@scope/name(@julian/cowsay) where bothscopeandnameare slugs.Three exports are added to the protocol public surface:
parseSlug— validates a single slug segment. Exported independently so other implementations (e.g. a registry enforcing scope ownership) reuse the exact same grammar rather than duplicating it.parseFacetName— parses a full facet identity into a discriminated-unionFacetName(unscopedorscoped) and returns aFacetNameResultrather than throwing. Thecanonicalfield on a successful result means callers never re-assemble@scope/nameby hand.validateFacetName— a thin boolean-style wrapper overparseFacetNameshaped to compose into ArkType'sctx.mustBe(...), mirroringvalidateAssetNamefrom@agent-facets/common.FacetManifestSchemanow callsvalidateFacetNamein its.narrow()block before the existing asset-count and asset-name constraints. Asset names remain governed separately byvalidateAssetName— asset names stay local path-safe kebab identifiers while facet identities may carry a registry scope. Combining the two validators was explicitly rejected to avoid making illegal states easier to represent.The tightening is intentional and breaking for previously-loadable manifests with malformed names. Invalid identities that used to pass because
namewas typed asstringnow fail at manifest validation.Verification
New unit tests cover:
parseSlugvalid and invalid cases including empty, uppercase, underscore, leading/trailing hyphen, dot, slash, and non-ASCII inputs.parseFacetNamevalid unscoped and scoped identities, all malformed scoped forms from the spec (@julian,@/cowsay,@julian/,@julian/cow/say,@julian/cow_say, uppercase variants, trailing hyphens), and malformed unscoped forms including traversal segments and bare slashes.validateFacetNamepass/fail/reason shape.FacetManifestSchemaacceptance of@julian/cowsayand rejection of each malformed identity listed in the spec scenario, asserting the error message includes"valid facet name".validateFacetArchiveacceptance of a scoped embedded facet manifest (@julian/cowsay).