Skip to content

Add parseFacetName, parseSlug, and validateFacetName to protocol and enforce facet identity grammar in FacetManifestSchema#331

Merged
eXamadeus merged 1 commit into
mainfrom
julian/06-14-add_parsefacetname___parseslug___validatefacetname_protocol_grammar_integrate_scoped_scope_name_into_facetmanifestschema_and_add_openspec_change_for_scoped_facet_name_support
Jun 15, 2026
Merged

Add parseFacetName, parseSlug, and validateFacetName to protocol and enforce facet identity grammar in FacetManifestSchema#331
eXamadeus merged 1 commit into
mainfrom
julian/06-14-add_parsefacetname___parseslug___validatefacetname_protocol_grammar_integrate_scoped_scope_name_into_facetmanifestschema_and_add_openspec_change_for_scoped_facet_name_support

Conversation

@eXamadeus

Copy link
Copy Markdown
Member

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 manifest name field was typed as a plain string, 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.ts module in @agent-facets/protocol defines 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 both scope and name are 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-union FacetName (unscoped or scoped) and returns a FacetNameResult rather than throwing. The canonical field on a successful result means callers never re-assemble @scope/name by hand.
  • validateFacetName — a thin boolean-style wrapper over parseFacetName shaped to compose into ArkType's ctx.mustBe(...), mirroring validateAssetName from @agent-facets/common.

FacetManifestSchema now calls validateFacetName in its .narrow() block before the existing asset-count and asset-name constraints. Asset names remain governed separately by validateAssetName — 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 name was typed as string now fail at manifest validation.

Verification

New unit tests cover:

  • parseSlug valid and invalid cases including empty, uppercase, underscore, leading/trailing hyphen, dot, slash, and non-ASCII inputs.
  • parseFacetName valid 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.
  • validateFacetName pass/fail/reason shape.
  • FacetManifestSchema acceptance of @julian/cowsay and rejection of each malformed identity listed in the spec scenario, asserting the error message includes "valid facet name".
  • validateFacetArchive acceptance of a scoped embedded facet manifest (@julian/cowsay).

…, integrate scoped `@scope/name` into `FacetManifestSchema`, and add OpenSpec change for scoped facet name support
Copilot AI review requested due to automatic review settings June 15, 2026 01:32
@changeset-bot

changeset-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b34c363

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@agent-facets/protocol Minor

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

Copy link
Copy Markdown
Member Author

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copy link
Copy Markdown
Member Author

Merge activity

  • Jun 15, 2:10 AM UTC: A user started a stack merge that includes this pull request via Graphite.

@eXamadeus eXamadeus merged commit 644f53b into main Jun 15, 2026
5 of 6 checks passed
@eXamadeus eXamadeus deleted the julian/06-14-add_parsefacetname___parseslug___validatefacetname_protocol_grammar_integrate_scoped_scope_name_into_facetmanifestschema_and_add_openspec_change_for_scoped_facet_name_support branch June 15, 2026 02:24
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants