planning, and a file named planning appears. It becomes visible the moment two facets publish the same name, or a project decides it wants a published asset under a different name — or not at all.
Materialization decides names, never content. Every archive path, every integrity hash, and every byte of an asset is fixed by its publisher and unaffected by anything on this page.
Two identities
Every asset has two names, and conflating them is the mistake this model exists to prevent.fixed by the publisher
The name declared in the facet’s
facet.json. It fixes the asset’s canonical paths inside the archive — skills/<authored>/SKILL.md, agents/<authored>.md, commands/<authored>.md — and therefore every integrity value recorded for it. It is what the lockfile stores. Aliasing and omission never change it.chosen by the project
The name the asset is materialized under: the file an adapter reads, writes, and deletes. It equals the authored name unless the consuming project aliased it.
name field written into a materialized asset is the effective name, while description stays as the publisher wrote it. Renaming an asset does not rewrite it.
Dispositions
A project resolves each authored asset to exactly one of three outcomes.the default
Materialize under the publisher’s name. Expressed by recording nothing — the absence of an override is the authored disposition. An explicit
authored override in facets.json MUST be rejected, so that one state has exactly one spelling.{ kind: 'aliased', as }
Materialize under a different effective name.
as is required and MUST satisfy the single-segment asset-name grammar. An invalid alias MUST be rejected, never normalized or sanitized — silently rewriting a chosen name would make the materialized identity unpredictable.{ kind: 'omitted' }
Do not materialize this asset, or any file it owns. The asset is still resolved, still verified, and still recorded in the lockfile — it simply never reaches an adapter.
- A project override admits only
aliasedandomitted, becauseauthoredis the absence of an override. - A materialized disposition — what the install receipt records for an asset present on disk — admits only
authoredandaliased, because an omitted asset is not on disk. “Omitted but present” is unrepresentable rather than merely invalid.
An omitted asset stays listed in the lockfile with its complete authored file records. Dropping it would make an omission indistinguishable from a facet that never published the asset at all.
Recording intent
Dispositions live infacets.json as durable project intent, keyed by asset type and then by authored name:
facets.json
Namespaces
Two assets may share a name freely across namespaces, but never within one.skill-command— skills and commands compete for the same names, because they materialize into a single flat command surface in the tools facets target.agent— agents occupy their own namespace.
deploy and a command deploy collide, while an agent deploy coexists with both. This is the same rule a single facet’s facet.json is validated against; materialization applies it across every facet in the project at once.
Collisions
Two assets collide when their collision keys are equal:- It uses the effective name, so aliasing can both cause and cure a collision.
- It folds asset type into its namespace, giving the rule above.
- It folds the name portably — Unicode NFC, then case — so two assets differing only by case or normalization collide rather than silently overwriting each other on a case-insensitive volume.
(scope, type, effective name), keyed by asset type rather than namespace and by the verbatim effective name rather than a folded one: a skill deploy and a command deploy are two different files even though they may not legally coexist.
When collisions are detected
Detection is global and pre-write. Every facet in the desired set is resolved and verified first; only then is the complete effective set checked, before any adapter is asked to write anything. This ordering is the reason the whole desired set must exist before the first write: a per-facet loop cannot detect that facet A and facet Z want the same name until it has already materialized A. When the effective set does collide, the result carries every group with every claimant — never a truncated report, and never a generated winner. A tool that picked a winner would silently discard someone’s asset.Planning rules
Materialization planning is a single pass, not a fixed-point resolver. Overrides are applied once against authored identity, then the resulting effective set is checked once. Four consequences follow, and all four are normative:- Alias swaps are legal. If
Aaliases toBandBaliases toA, both land. Neither observes the other’s result, because there is no second pass. - Duplicate alias targets fail. Two assets aliased to the same name collide like any other pair.
- A name freed by an omission is available. Omitting one claimant removes it from the effective set entirely.
- Order does not matter. The result never depends on the order facets were declared in.
facets.json, or the assets a facet declares, never changes the disposition an asset receives, whether a collision is detected, or which effective set is materialized. The same inputs plan to the same answer however they were enumerated — which is why no claimant can win a collision by being listed first.
Collision reports are additionally stable for presentation: groups, and the claimants within a group, come back in a fixed order, so the same unresolved conflict reads identically on every machine and on every rerun. That ordering is deliberately not locale-aware; locale-sensitive collation would make output depend on the machine’s environment.
Stale overrides
An override naming an asset the resolved facet version no longer contains is stale — typically because you upgraded a facet and the publisher renamed or dropped that asset. A stale override is reported, not fatal. Because intent is durable, it survives a failed operation and is pruned only as part of a successful commit — at which point the CLI tells you what it dropped. Under--frozen-lockfile the same report is blocking drift instead: frozen mode has no transaction to prune in, and silently ignoring recorded intent would defeat the flag’s purpose.
What aliasing does not change
Aliasing renames; it does not rewrite. Everything in this list is anchored to the authored name and is byte-identical whether or not an asset is aliased:- Canonical inner-archive paths, and therefore the archive’s per-entry hashes.
- The facet’s canonical fingerprint (
content_integrity). - The lockfile’s
nameandfilesrecords for the asset. - The asset’s content and its
description.