Skip to content

Commit 97e1185

Browse files
authored
feat: SPEC-001 package identity — name is one segment, wire key is the literal (0.0.106) (#280)
* feat: SPEC-001 package identity — name is one segment, wire key is the literal (0.0.106) 0.0.105 fixed #278 by REQUIRING `package.name` to be the fully-qualified `<namespace>.<name>`. That rule was an encoding constraint dressed up as a design rule. Its only cause: mcpp threw away the literal `package.name` it had already read and re-rendered `<ns>.<short>` to address the package, so the two only matched when the descriptor repeated its namespace inside `name`. Use the literal instead and the constraint disappears. The canonical form goes back to what the identity model always said: `namespace` carries the hierarchy, `name` is a single atomic segment. Paired with xlings 0.4.69 (openxlings/xlings#381), which keys its index by (namespace, name) so two packages sharing a short name in ONE index are both addressable. mcpp's part is sending `<namespace>:<literal name>` rather than a reconstructed string. Three subtractions, one addition: - identity normalization no longer splits on the last dot. It used to infer a namespace nobody declared — `ns="a"` + `name="a.b.c"` silently became `(a.b, c)`. Identity is now exactly the two declared fields; a leftover dot is rejected rather than reinterpreted. - the install target is the descriptor's literal `package.name`, prefixed with the package NAMESPACE (xlings' effective namespace), not the index name. Namespace-less upstream packages keep their bare name. - the store dir formula `{ns}-x-{name}` is first-class for both spellings instead of buried among six guesses. - discovery no longer stops at a fixed candidate-filename list. Recommended names stay the fast path; when they all miss, mcpp scans by declared identity. This finally delivers the "filename is not a key" half that identity-first resolution only ever promised — verification was implemented, discovery was not. Compatibility: the legacy fully-qualified spelling is still accepted (the prefix is stripped before judging), so every published descriptor keeps working unchanged. A full-registry sweep — 245 descriptors, mcpp indices and xlings-native ones alike — reports zero form violations. Notably this includes all 30 descriptors that needed `--allow-split-name` under 0.0.105; that flag is now obsolete and kept accepted only so 0.0.105-era index CI keeps running. Adds docs/spec/ for normative documents, with SPEC-001 covering identity, the four [dependencies] spellings and their candidate ladders, the matching pipeline, derived-value formulas and worked examples — each rule tagged with its implementation status. Verified: 37/37 unit binaries; e2e 161 inverted; new e2e 163 covers short-name install, legacy FQN install, arbitrary filename discovery, and two same-short-name packages in one index resolving to alpha-x-widget / beta-x-widget. Spec: docs/spec/package-identity.md Plan: .agents/docs/2026-07-25-spec001-implementation-plan.md * fix(ci): pick the newest mcpp.exe on Windows; gate e2e 163 step 4 on xlings 0.4.69 Two CI failures from PR #280, both real and both worth fixing rather than working around. 1. Windows e2e ran the PREVIOUS release's binary. `MCPP_SELF=$(find target -name "mcpp.exe" -path "*/bin/*" | head -1)` `target/` is restored from cache and keeps one directory per build fingerprint. A version bump changes the fingerprint, so the freshly built 0.0.106 binary landed next to the cached 0.0.105 one and `head -1` returned whichever the directory walk hit first — hence `Version mismatch: mcpp.toml=0.0.106, --version='mcpp 0.0.105'`. Latent all along; the version bump is just what made two candidates exist. The Linux workflows already sort by mtime and take the newest — this brings the five Windows/release sites in line with them. 2. e2e 163 step 4 asserted a capability the sandbox cannot have yet. That step covers two same-short-name packages in ONE index, which needs xlings >= 0.4.69 (openxlings/xlings#381). The sandbox xlings comes from bootstrapping the PREVIOUS mcpp release, so CI runs 0.4.68 until 0.0.106 ships bundling 0.4.69 — a genuine chicken-and-egg, not a defect. Step 4 now detects the sandbox xlings version and skips with a stated reason when it is older. Properties 1-3 (short-name install, legacy FQN install, arbitrary-filename discovery) are what this change is responsible for and stay asserted unconditionally. Verified both branches locally by swapping the sandbox xlings: 0.4.69 runs step 4, 0.4.68 skips it with the reason printed.
1 parent cee8130 commit 97e1185

26 files changed

Lines changed: 1618 additions & 268 deletions
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# `package.name` / `package.namespace` 双向验证报告
2+
3+
> 触发:mcpp-index PR#117 的文档把「`name` 必须写成 FQN」写成了**设计规则**,评审指出这只是兼容写法,层级应当全在 `namespace``name` 只留短名。
4+
> 目的:用当前源码 + 实机双向验证,判定哪一方成立、代价在哪、以及 PR#117 该怎么写。
5+
> 基线:mcpp `main` @ `cee8130`(0.0.105),xlings 0.4.68 / libxpkg 当前 HEAD。
6+
7+
---
8+
9+
## 0. 结论先行
10+
11+
| 命题 | 判定 |
12+
|---|---|
13+
| 「层级属于 `namespace`,`name` 概念上是短名」 |**成立**,且与 mcpp §4.2 一致 |
14+
|`name` 必须写 FQN」是 mcpp 的**设计规则** |**不成立** —— 这是我写错了 |
15+
|`name` 必须写 FQN」是**当前实现的编码约束** | ✅ 成立,但根因是**一行可改的代码**,不是架构 |
16+
| 我此前「根因在 libxpkg 的 key 空间」的判断 |**错误**,已实机证伪(见 §3) |
17+
| 短名形式需要改 libxpkg / xlings |**不需要**,mcpp 侧一行即可 |
18+
19+
**修正后的根因**:`prepare.cppm:1861` 把已经读到手的字面 `name` **丢弃**,改用 `ns + "." + short` 重新渲染一遍去当 wire key。改成直接用字面 `name`,短名形式立即可用,且**现网 FQN 形式继续可用**
20+
21+
---
22+
23+
## 1. 方向一:从源码看 mcpp 如何处理 name/namespace
24+
25+
### 1.1 读取 —— mcpp 完全自理,不依赖 xlings
26+
27+
| 环节 | 位置 | 是否依赖 xlings |
28+
|---|---|---|
29+
| 定位描述符文件 | `package_fetcher.cppm` `read_xpkg_lua*` | ❌ 自己扫盘 |
30+
| 解析 `namespace` | `xpkg.cppm:670` `extract_xpkg_namespace` | ❌ 自己解析 |
31+
| 解析 `name` | `xpkg.cppm:676` `extract_xpkg_name` | ❌ 自己解析 |
32+
| 归一化为身份 | `xpkg.cppm` `canonical_xpkg_identity` | ❌ 自己归一 |
33+
| 身份校验 | `xpkg_lua_identity_matches` | ❌ 自己校验 |
34+
35+
**评审意见在这一点上完全正确**:name/namespace 的处理是 mcpp 的自有机制,xlings 只被用于**取值/装包**
36+
37+
### 1.2 身份归一化对两种写法**已经等价**
38+
39+
```cpp
40+
// xpkg.cppm — canonical_xpkg_identity
41+
std::string prefix = ns + ".";
42+
fqn = name.starts_with(prefix) ? name : ns + "." + name; // 两种写法在此合流
43+
auto pos = fqn.rfind('.');
44+
return XpkgIdentity{ fqn.substr(0, pos), fqn.substr(pos + 1) };
45+
```
46+
47+
`(acme, asio)` 与 `(acme, acme.asio)` 归一化到**同一身份** `(acme, asio)`。身份层对写法**无偏好**。
48+
49+
### 1.3 唯一的偏好点:目标构造丢弃了字面值
50+
51+
```cpp
52+
// prepare.cppm:1861 —— 唯一把写法变成"有对错"的一行
53+
auto fqname = ns.empty() ? shortName : std::format("{}.{}", ns, shortName);
54+
...
55+
// :1906 / :1911
56+
auto target = std::format("{}@{}", fqname, version);
57+
target = std::format("{}:{}@{}", idxSpec->name, fqname, version);
58+
```
59+
60+
`luaContent`(含字面 `name`)**就在同一作用域**——`:1781``:1865` 的 INV-NAME 检查用的正是它。**信息在手,只是没用。**
61+
62+
### 1.4 store 目录:mcpp 自己猜,且**两种形式都已覆盖**
63+
64+
```cpp
65+
// compat.cppm install_dir_candidates
66+
candidates.push_back(std::format("{}-x-{}", ns, fqname)); // <ns>-x-<ns>.<short>
67+
...
68+
candidates.push_back(std::format("{}-x-{}", ns, shortName)); // <ns>-x-<short> ← 短名形式
69+
```
70+
71+
短名形式的 store 目录**早已在候选表里**(现标注为 COMPAT 兜底)。
72+
73+
---
74+
75+
## 2. 方向二:实机验证(真实包 `asio` 1.38.1,path index,mcpp 0.0.104 避开 INV-NAME 干扰)
76+
77+
固定 `namespace = "acme"`,只改 `name` 字段:
78+
79+
| # | `name` | mcpp 发出的 target | 结果 | store 目录 |
80+
|---|---|---|---|---|
81+
| 1 | `acme.asio` | `acme:acme.asio@1.38.1` | ✅ 装上并编译 | `acme-x-acme.asio` |
82+
| 2 | `asio` | `acme:acme.asio@1.38.1` |`E_NOT_FOUND` ||
83+
84+
第 2 行即 **#278 事故的最小复现**:索引 key 是 `asio`,mcpp 却要 `acme.asio`
85+
86+
### 2.1 关键实验:改一行后重测
87+
88+
实验补丁(**未合入**,已复原):目标构造改用描述符字面 `name`
89+
90+
```cpp
91+
if (luaContent) {
92+
auto lit = mcpp::manifest::extract_xpkg_name(*luaContent);
93+
if (!lit.empty()) fqname = lit;
94+
}
95+
```
96+
97+
| # | `name` | 结果 | store 目录 |
98+
|---|---|---|---|
99+
| 3 | `asio`(短名) |**装上并编译** | `acme-x-asio` |
100+
| 4 | `acme.asio`(FQN) | ✅ 装上并编译 | `acme-x-acme.asio` |
101+
| 5 | mcpp 自身依赖(真实生态) | ✅ 无回归 ||
102+
103+
**两种写法同时可用**,因为字面 `name` **恒等于**索引 key。下游(payload 定位、编译)零改动即работ——`install_dir_candidates` 已含短名候选。
104+
105+
---
106+
107+
## 3. 证伪:我此前「根因在 libxpkg」的判断是错的
108+
109+
我曾主张「libxpkg 的 key 空间没有 namespace 维度,所以 `name` 必须自带区分度」。**实验 3 直接证伪**:key 空间没变、libxpkg 一行没动,短名形式照样装上。
110+
111+
正确表述:key 空间确实是扁平的、以 `pkg.name` 字面值为键 —— 但这**不构成对 `name` 写法的约束**,因为 mcpp 完全可以把字面值原样送过去。真正的约束是 mcpp 自己**重新渲染**了一个可能不同的字符串。
112+
113+
**撞键顾虑同样不成立**:`compat``zlib``mcpplibs``zlib` 若都写 `name = "zlib"`,确实会在**同一索引内**撞键 —— 但这是**索引内命名唯一性**问题,可由 lint 保证(同一索引内 `name` 唯一),不必让每个包都背 FQN。跨索引不撞,因为每个索引一张表。
114+
115+
---
116+
117+
## 4. 兼容矩阵
118+
119+
| | 现网描述符(FQN) | 新描述符(短名) |
120+
|---|---|---|
121+
| 现有 mcpp(≤0.0.105) ||`E_NOT_FOUND` |
122+
| 改后 mcpp(用字面 name) |||
123+
124+
**只有一个破损格**:旧 mcpp + 新描述符。含义:
125+
126+
- 迁移**不是**一次性切换。改后的 mcpp 对两种写法都工作,所以**先发 mcpp、再按节奏迁描述符**
127+
- 描述符一旦改短名,消费它的最低 mcpp 版本就抬高了 → 迁移时 `index.toml``min_mcpp` **必须**同步抬到含该修复的版本(这次是硬性的,不像 0.0.105 那次可选)。
128+
129+
---
130+
131+
## 5. 对 PR#117 的处置建议
132+
133+
PR#117 现有文档把**编码约束**写成了**设计规则**,方向与上述结论相反,**不应按现状合入**。三选一:
134+
135+
**A. 拆分(推荐)** —— PR#117 只保留无争议部分:去重(字节级重复的 `capi.lua.lua`)、规范路径(`tensorvia-cpu.lua``aimol.tensorvia-cpu.lua`)、规则 2(短名原子)、规则 3(规范路径)、CI 升 0.0.105。**删掉全部关于 `name` 形式的表述与规则 1 lint**,留给迁移 PR。
136+
137+
**B. 并入迁移** —— PR#117 改造成迁移的一部分:等 mcpp 侧修复发布后,49 个描述符全改短名、lint 规则 1 反转、`min_mcpp` 抬版。
138+
139+
**C. 维持现状** —— 只在文档里把 FQN 标注为「当前编码约束、非设计理想」。**不推荐**:等于把一条要回滚的约定写进 49 个包的规范。
140+
141+
---
142+
143+
## 6. 若走迁移,工作项清单
144+
145+
**mcpp 侧(一次发版,例如 0.0.106):**
146+
147+
- [ ] `prepare.cppm` 目标构造改用描述符字面 `name`(字面为空时回落到现有渲染)
148+
- [ ] `compat.cppm` `install_dir_candidates``{ns}-x-{short}` 提为**首选**,`{ns}-x-{fqname}` 降为兼容项
149+
- [ ] **INV-NAME 谓词的语义反转**:从「必须是 FQN」改为「`name` 不得含点(层级归 `namespace`)」;`xpkg_name_form_violation``mcpp xpkg parse` 同步
150+
- [ ] e2e 161 断言反转;新增「短名描述符可安装」正向 e2e
151+
- [ ] 文档 `docs/05-mcpp-toml.md` §2.5 的 xpkg 作者段改写
152+
153+
**mcpp-index 侧(mcpp 发布后):**
154+
155+
- [ ] 49 个描述符 `name` 去掉 namespace 前缀
156+
- [ ] `check_package_name.lua` 规则 1 反转
157+
- [ ] `check_package_filename.lua` 的规范路径公式随之调整(FQN 文件名 → ?需重新定义)
158+
- [ ] `index.toml` `min_mcpp` / `latest_mcpp` 抬到 0.0.106
159+
- [ ] 一次全量 `mcpp test --workspace` 三平台验证
160+
161+
**未决问题(需拍板):**
162+
163+
1. **规范文件名怎么定?** 现行公式对非默认命名空间用 `<FQN>.lua`。若 `name` 变短名,是继续用 `<ns>.<short>.lua`(与 `name` 不再字面相等)还是改 `<short>.lua`(跨命名空间会重名)?
164+
2. **同索引内 `name` 唯一性**由谁保证?建议新增 lint:同一索引内 `name` 字面值不得重复(替代 FQN 天然提供的唯一性)。
165+
3. **是否需要过渡期双读?** 即改后的 mcpp 已同时支持两种写法,故不需要;但若希望旧 mcpp 也能读新描述符,只能靠 `min_mcpp` 挡住,无技术手段。
166+
167+
---
168+
169+
## 附:我为什么把 PR#117 写成那样
170+
171+
按时间顺序,证据链是这样的,错在**没有做本报告 §2.1 这一步实验**:
172+
173+
1. #278 事故现场:`name` 从 FQN 改成短名 → 三平台 `E_NOT_FOUND`。→ 得出「短名不可用」。**正确但不完整**——只证明了"当前 mcpp 下不可用",没证明"必然不可用"。
174+
2. 读 libxpkg:`entries[pkg.name]` 精确匹配、`build_index` 不传 namespace。→ 得出「key 空间无 namespace 维度,`name` 必须自带区分度」。**这一步是错的**:key 空间的形状不约束 `name` 的写法,只要 mcpp 把字面值原样送过去即可。
175+
3. 全索引扫描:46/49 用 FQN,2 个短名的都坏了。→ 强化了「FQN 是约定」。**幸存者偏差**:它们坏是因为 mcpp 的渲染,不是因为写法本身。
176+
4. 于是把「FQN」当作规则写进 lint 与文档。
177+
178+
**根本失误**:我把「当前实现的行为」当成了「架构的约束」,而没有去问"这一行改掉会怎样"。评审的两次纠正——先是「a.b.c 应在 namespace」,再是「mcpp 不依赖 xlings 的 name 机制」——正是指向这个缺口。§2.1 的实验本应在写 lint **之前**做。
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# mcpp `name` / `namespace` 规范实现(定稿)
2+
3+
> 依据:`2026-07-25-name-namespace-bidirectional-verification-report.md` 的双向验证 + 评审给定的规格
4+
> 基线:mcpp `main` @ 0.0.105
5+
> 状态:**待实现**。本文定的是目标实现,不是现状描述。
6+
7+
---
8+
9+
## 0. 边界约束(先于一切)
10+
11+
**mcpp 不改动 xlings 的任何规范,只基于 xlings 已定的机制去实现。**
12+
13+
xlings 侧属于**既定事实**,mcpp 只能遵守,不能重新定义:
14+
15+
| xlings 既定机制 | 内容 |
16+
|---|---|
17+
| 索引键 | `entries[package.name]`,以**字面 `name`** 为键,精确匹配 |
18+
| xpkg 目录 | `{namespace}-x-{name}` —— `name` 是什么就拼什么 |
19+
20+
**由此推出 mcpp 唯一正确的做法:把描述符里读到的字面值原样使用,不要在 mcpp 侧重新推导一遍。** 本文全部改动都是这一句话的展开。
21+
22+
> **勘误**:先前曾把「FQN 形式产出 `acme-x-acme.asio`」当作"不符合 `{ns}-x-{name}` 规范"、并当成短名为正解的佐证 —— **该推论是错的**`{ns}-x-{name}` 对两种形式都成立(`name="acme.asio"``acme-x-acme.asio`;`name="asio"``acme-x-asio`),它是确定的 xlings 机制,不偏袒任何一种写法。短名为规范形态的依据是数据模型本身(层级归 `namespace`),不是目录名。
23+
24+
---
25+
26+
## 1. 数据模型
27+
28+
包的身份是二元组,**两个字段都直接取自描述符声明,不做任何重新渲染**:
29+
30+
| 字段 | 含义 | 形态 |
31+
|---|---|---|
32+
| `package.namespace` | 命名空间,**点分层级路径** | 可空;`compat` / `mcpplibs.capi` / `a.b.c` |
33+
| `package.name` | 包名,**单一原子段** | **不含点**;`zlib` / `asio` / `lua` |
34+
35+
> **层级一律放 `namespace`,`name` 只留最后一段。**
36+
> `(mcpplibs.capi, lua)` 是规范形态;`namespace="mcpplibs", name="capi.lua"` 不是。
37+
38+
### 1.1 三个派生量,全部源自**字面 `name`**
39+
40+
| 派生量 | 公式 | 实测依据 |
41+
|---|---|---|
42+
| xlings 索引 key | `<字面 name>` | libxpkg `entries[pkg.name]`,精确匹配 |
43+
| store 目录 | `{namespace}-x-{字面 name}` | 全量实测 48/48 相符(FQN 与短名两种形式) |
44+
| 安装 target | `<indexName>:<字面 name>@<version>` | 实验证实短名/FQN 均可解析 |
45+
46+
**关键性质:三者都只依赖字面 `name`,与「短名形式 / 遗留 FQN 形式」无关。** 因此实现里**不需要按形式分支** —— 这是本方案能同时兼容新旧描述符的根本原因。
47+
48+
### 1.2 文件名不参与任何解析
49+
50+
描述符文件名**自由**。推荐(非强制)`<name>.lua``<namespace>.<name>.lua`
51+
52+
这是 `2026-06-26 identity-first` 的原意("Filename Is Not a Key")的完整落实 —— 此前只兑现了「命中后按声明身份复核」,**发现仍受候选文件名约束**,本方案一并补齐(§2.5)。
53+
54+
---
55+
56+
## 2. mcpp 侧实现
57+
58+
### 2.1 身份归一化 —— 去掉 split-on-last-dot
59+
60+
```cpp
61+
// 现状(xpkg.cppm canonical_xpkg_identity):把 name 拼成 FQN 再按最后一个点切开
62+
fqn = name.starts_with(ns + ".") ? name : ns + "." + name;
63+
auto pos = fqn.rfind('.');
64+
return { fqn.substr(0, pos), fqn.substr(pos + 1) }; // ← 会把 ns 从 name 里"猜"出来
65+
66+
// 目标:namespace 就是 namespace,name 就是 name;只为遗留 FQN 形式剥前缀
67+
std::string short_ = name.starts_with(ns + ".") ? name.substr(ns.size() + 1) : name;
68+
return XpkgIdentity{ ns, short_ };
69+
```
70+
71+
差别在于**不再从 `name` 反推命名空间**。`namespace="a", name="a.b.c"` 这类自相矛盾的写法,现状会静默解析成 `(a.b, c)`(一个描述符从未声明的命名空间),新实现直接由 §2.4 的校验拒绝。
72+
73+
### 2.2 安装 target —— 用字面 `name`,不再重新渲染
74+
75+
```cpp
76+
// prepare.cppm:1861 现状 —— 丢弃已读到的字面值,重新渲染
77+
auto fqname = ns.empty() ? shortName : std::format("{}.{}", ns, shortName);
78+
79+
// 目标:字面 name 就是 wire key(luaContent 已在同一作用域)
80+
auto wireName = luaContent ? mcpp::manifest::extract_xpkg_name(*luaContent) : std::string{};
81+
if (wireName.empty()) wireName = ns.empty() ? shortName // 兜底:读不到描述符时
82+
: std::format("{}.{}", ns, shortName);
83+
```
84+
85+
**已实验验证**(报告 §2.1):改此一行后短名形式立即可装(`acme-x-asio`),且现网 FQN 形式继续可装(`acme-x-acme.asio`),mcpp 自身依赖无回归。
86+
87+
### 2.3 store 目录 —— 收敛为单一公式
88+
89+
```cpp
90+
// compat.cppm install_dir_candidates 现状:6 个猜测候选,{ns}-x-{short} 被标为 COMPAT 兜底
91+
// 目标:{namespace}-x-{字面 name} 单一公式(全量实测 48/48 命中),其余降为过渡期兼容项
92+
```
93+
94+
### 2.4 `name` 形态校验 —— INV-NAME 语义反转
95+
96+
| | 现状(0.0.105) | 目标 |
97+
|---|---|---|
98+
| 规则 | `namespace` 非空时 `name` **必须**`<ns>.<short>` | `name` **不得含点**;层级归 `namespace` |
99+
| 违规示例 | `ns="chriskohlhoff", name="asio"` | `ns="mcpplibs", name="capi.lua"` |
100+
| 落点 | `xpkg_name_form_violation` + `mcpp xpkg parse` + 安装路径 | 同左 |
101+
102+
过渡期:遗留 FQN 形式(`name` 恰以 `namespace + "."` 开头)**降级为 warning**,不阻断;其他含点写法直接报错。
103+
104+
### 2.5 描述符发现 —— IdentityIndex(文件名自由的必要前提)
105+
106+
文件名一旦自由,「候选文件名探测」就不再可靠:`read_xpkg_lua*` 三个入口目前只探测 `compat::xpkg_lua_candidates` 生成的固定名单,叫别的名字的描述符**根本看不见**
107+
108+
因此必须落地 `2026-06-26 §5` 一直推迟的 `IdentityIndex`:扫描 `pkgs/*/*.lua`,读每个描述符声明的 `(namespace, name)` 建表,文件名仅作可选加速提示。
109+
110+
> **与 INV-RESOLVE 不冲突。** 被否决的是「裸名跨命名空间发现」(解析结果取决于装了哪些索引);IdentityIndex 是「按**已知的精确身份**查表」,确定性不受影响。消费端解析规则(裸名只解析 `mcpplibs` / `compat` / 无 ns 上游)**保持不变**
111+
112+
---
113+
114+
## 3. 遗留约束:同索引内 `name` 必须唯一(索引侧承担)
115+
116+
xlings 的索引是**每索引一张扁平表、以字面 `name` 为键**(§0 的既定机制)。短名形式下,同一索引里 `compat`/`zlib``mcpplibs`/`zlib` 会落到同一个键 `zlib` 上而**互相覆盖** —— 现状靠 FQN 写法天然错开。
117+
118+
按 §0,这**不是 mcpp 能解决的问题**:改键空间等于改 xlings 规范,越界。因此约束由**索引侧承担**:
119+
120+
> **同一索引内,`package.name` 的字面值必须唯一。**
121+
122+
由 mcpp-index 的 lint 强制(遍历 `pkgs/*/*.lua`,`name` 字面值不得重复)。
123+
124+
**现网检查:mcpplibs 索引 48 个描述符,短名化后无冲突**(`compat.*``mcpplibs.*` 之间无同短名者),故该约束立即可满足,不需要为它改任何包名。
125+
126+
若将来某个索引确实需要容纳同短名的两个包,那是 xlings 键空间的能力缺口,应作为**上游需求**提给 xlings,而不是在 mcpp 侧绕开。
127+
128+
---
129+
130+
## 4. 兼容矩阵与迁移
131+
132+
| | 现网描述符(FQN) | 新描述符(短名) |
133+
|---|---|---|
134+
| 现有 mcpp ≤0.0.105 ||`E_NOT_FOUND` |
135+
| 本方案实现后 |||
136+
137+
**只有一个破损格**,含义:
138+
139+
1. **先发 mcpp**(对两种写法都工作),**再按节奏迁描述符** —— 不是一次性切换。
140+
2. 描述符改短名时,该索引的 `index.toml` `min_mcpp` **必须**同步抬到含本实现的版本(硬性,无技术手段绕过)。
141+
3. 迁移可**逐包**进行,索引内可长期混存两种形式。
142+
143+
---
144+
145+
## 5. 实施清单
146+
147+
**mcpp(一次发版):**
148+
149+
- [ ] `canonical_xpkg_identity` 去 split-on-last-dot(§2.1)
150+
- [ ] `prepare.cppm` target 用字面 `name`(§2.2)
151+
- [ ] `install_dir_candidates` 收敛为 `{ns}-x-{字面 name}`(§2.3)
152+
- [ ] `xpkg_name_form_violation` 语义反转 + `mcpp xpkg parse` 同步(§2.4)
153+
- [ ] `IdentityIndex` 落地,`read_xpkg_lua*` 改走它(§2.5)
154+
- [ ] e2e 161 断言反转;新增「短名描述符可安装」「任意文件名可发现」正向 e2e
155+
- [ ] `docs/05-mcpp-toml.md` §2.5 xpkg 作者段改写
156+
157+
**mcpp-index(mcpp 发布后,可逐包):**
158+
159+
- [ ] 新增 lint:同索引内 `name` 字面值唯一(§3)
160+
- [ ] 描述符逐步短名化;`min_mcpp` 抬版
161+
- [ ] 现有「文件名必须规范」的 lint **不要加**(文件名自由)
162+
163+
---
164+
165+
## 附:本方案相对现状的三处"减法"
166+
167+
值得强调的是,这不是加复杂度,而是**去掉三处多余的推导**:
168+
169+
1. 不再从 `name` 反推命名空间(split-on-last-dot)
170+
2. 不再重新渲染 wire key(直接用字面值)
171+
3. 不再猜 store 目录(6 个候选 → 1 个公式)
172+
173+
三处都是「mcpp 手里已有准确值、却选择重新推导一遍」—— 与 #278 的根因同型。

0 commit comments

Comments
 (0)