尧图精选

pnpm 共享产物协议(Shared Artifact Protocol)的判别式 Subject 模型:candidates 与签名 payload 的版本化演进

🕒 发布时间:2026/9/20 0:06:18 📁 来源:尧图网络
pnpm 共享产物协议Shared Artifact Protocol的判别式 Subject 模型candidates 与签名 payload 的版本化演进【免费下载链接】pnpmFast, disk space efficient package manager项目地址: https://gitcode.com/gh_mirrors/pn/pnpm导读pnpm 的共享产物协议Shared Artifact Protocol是一个实验性协议构建方把依赖副作用或工作区任务的构建产物上传到 pnpr 服务端其他机器可以按兼容性约束复用这些产物从而跳过重复的脚本执行与构建。本文基于 .changeset/general-artifact-subjects.md 的变更说明结合仓库内 Rust 协议实现与 TypeScript 客户端源码深入讲解协议中**判别式 Subjectdiscriminated subject**的设计candidates 与签名 payload 如何通过dependency-side-effects与workspace-task两类 subject 明确这份产物属于谁以及该演进对协议请求体、签名载荷和版本兼容性带来的影响。读完本文你将理解共享产物协议的线格式、subject 校验规则与升级注意事项。一、变更背景为什么需要 Subject 概念在共享产物协议中一次复用的核心是回答一个问题某一构建输入input的产物是否已经由可信构建方产出并存放在 pnpr 服务端在 subject 模型落地之前candidates 与签名 payload 对产物归属的刻画不够结构化。本次变更general-artifact-subjects将这一刻画统一抽象为判别式 subjectGeneralized the experimental shared-artifact protocol so candidates and signed payloads identify a discriminated subject. Dependency side effects use package and source-integrity subjects, while workspace tasks use project and task subjects.依赖副作用dependency side effects的产物用package包名 版本与 source-integrity源码完整性作为 subject工作区任务workspace tasks的产物用project项目标识与 task任务名作为 subject。这一改动同时触及协议的两类关键数据resolve 请求中的 candidate客户端询问我要什么和发布请求中的签名 payload服务端存储我信什么因此属于破坏性变更客户端包pnpm/pnpr.client为此打上了major版本号。二、Subject 的线格式一个带判别标签的枚举2.1 Rust 侧定义Subject 在协议 crate 中被建模为带 Serde 标签的枚举线格式上通过kind字段判别。核心定义位于 pnpm/crates/shared-artifact-protocol/src/lib.rs#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(tag kind)] pub enum ArtifactSubject { #[serde(rename dependency-side-effects)] DependencySideEffects { package: PackageIdentity, #[serde(rename sourceIntegrity)] source_integrity: String, }, #[serde(rename workspace-task)] WorkspaceTask { project: String, task: String }, }其中PackageIdentity由包名与版本构成lib.rspub struct PackageIdentity { pub name: String, pub version: String, }对应的 JSON 线格式示例// 依赖副作用 subject { kind: dependency-side-effects, package: { name: esbuild, version: 0.21.5 }, sourceIntegrity: sha512-xxxx } // 工作区任务 subject { kind: workspace-task, project: apps/web, task: build }注意sourceIntegrity字段在 Rust 侧通过#[serde(rename)]映射为 camelCase与协议线格式保持一致而kind字段的标签值使用 kebab-casedependency-side-effects/workspace-task。2.2 TypeScript 客户端侧的镜像定义pnpr 的 Node.js 客户端pnpr/client/src/sharedSideEffects.ts维护了完全同构的类型定义并通过subjectArtifactIdentity函数把 subject 映射回对应的 artifact kind 与 input key 前缀export interface DependencySideEffectsSubject { kind: dependency-side-effects package: PackageIdentity sourceIntegrity: string } export interface WorkspaceTaskSubject { kind: workspace-task project: string task: string } export type ArtifactSubject DependencySideEffectsSubject | WorkspaceTaskSubjectsubject 与产物类型artifact kind之间存在一一对应的派生关系由 sharedSideEffects.ts 集中维护Subject kindArtifact kindInput key 前缀dependency-side-effectsdependency-side-effects:v1dependency-side-effects:v1:workspace-taskworkspace-task:v1workspace-task:v1:这些常量在 Rust 侧同样定义lib.rspub const DEPENDENCY_SIDE_EFFECTS_ARTIFACT_KIND: str dependency-side-effects:v1; pub const DEPENDENCY_SIDE_EFFECTS_INPUT_KEY_PREFIX: str dependency-side-effects:v1:; pub const WORKSPACE_TASK_ARTIFACT_KIND: str workspace-task:v1; pub const WORKSPACE_TASK_INPUT_KEY_PREFIX: str workspace-task:v1:;也就是说只要拿到一个 subject协议就能推导出它属于哪一类产物、其 input key 必须以哪个前缀开头——这正是判别式discriminated的含义。三、candidates客户端如何声明我要这份产物resolve 请求中客户端发送一个 candidate 列表服务端据此返回已存储的产物变体。candidate 由三要素组成lib.rspub struct ArtifactCandidate { pub key: String, pub subject: ArtifactSubject, pub owner: OwnerScope, }keyinput key必须以 subject 对应的前缀开头subject判别式 subject声明这份产物属于哪个包/源码或哪个项目/任务owner所有权范围Organization或Publisher见 lib.rs。在客户端侧resolveSharedSideEffectssharedSideEffects.ts会先按策略过滤候选再对每个 candidate 执行validateCandidatesharedSideEffects.ts校验key前缀、owner 与 subject 本身。发送的请求体形如POST /-/pnpr/v0/artifacts/resolve { candidates: [ { key: dependency-side-effects:v1:..., subject: { kind: dependency-side-effects, package: { name: ..., version: ... }, sourceIntegrity: ... }, owner: { type: publisher, package: ... } } ] }服务端返回每个 key 对应的ResolvedArtifact其中variants是若干已签名信封ArtifactVariantlib.rs。四、签名 payloadsubject 如何进入受信任的载荷4.1 Payload 结构签名 payloadArtifactPayload中同样携带 subjectlib.rspub struct ArtifactPayload { pub kind: String, pub subject: ArtifactSubject, pub input_key: String, pub owner: OwnerScope, pub builder_id: String, pub builder_profile: BuilderProfile, pub compatibility: CompatibilityConstraints, pub manifest: ArtifactManifest, }也就是说subject 是签名内容的一部分。当服务端/客户端验证信封签名后得到的 payload 同时包含了产物归属subject这一承诺任何篡改 subject例如把 A 包的产物伪称为 B 包的都会导致签名验证失败或 subject 校验失败。4.2 签名信封信封结构定义于 lib.rspub struct SignedArtifactEnvelope { pub algorithm: String, // ecdsa-p256-sha256 pub key_id: String, pub payload: String, // base64 编码的 JSON 字节 pub signature: String, // base64 编码的 DER P-256 签名 }签名与验证的完整实现位于 pnpm/crates/shared-artifact-protocol/src/signatures.rs签名前先对 payload 执行payload.validate()再对serde_json::to_vec(payload)得到的精确字节做 ECDSA P-256 签名验证时先解码 payload 字节、校验 base64 与 DER 的 canonical 形式再用公钥验签最后再次validate()。由于签名覆盖的是 payload 的原始字节而非某种规范化 JSON不同实现之间无需约定 JSON 规范化算法即可互操作。客户端侧对应实现为createSignedArtifactEnvelope与verifySignedArtifactEnvelopesharedSideEffects.ts 与 sharedSideEffects.ts并在 sharedSideEffects.ts 强制要求密钥必须是 P-256 EC 私钥。4.3 Payload 与 candidate 的交叉校验无论是服务端发布校验还是客户端 resolve 选择都会把 payload 与 candidate 做一致性比对。Rust 侧的artifact_matches_candidatepnpr/crates/shared-artifacts/src/artifact_identity.rs要求三者全部相等pub(super) fn artifact_matches_candidate(payload: ArtifactPayload, candidate: ArtifactCandidate) - bool { let ArtifactCandidate { key: input_key, subject, owner } candidate; payload.input_key *input_key payload.subject *subject payload.owner *owner }客户端侧在 sharedSideEffects.ts 用subjectsEqual对 payload.subject 与 candidate.subject 做逐字段比对sharedSideEffects.ts。这保证了请求的产物与签名承诺的产物必须指向同一个 subject。五、Subject 的校验规则两个变体的不同约束subject 校验位于 Rust 侧 pnpm/crates/shared-artifact-protocol/src/validation.rs客户端侧镜像位于 sharedSideEffects.ts。两类 subject 的约束差异如下dependency-side-effectspackage必须是合法的包标识包名、版本长度均不超过 256 字节且不含控制字符PackageIdentity::validatevalidation.rssourceIntegrity长度不超过 1024 字节若 owner 为Publisher则owner 中的包名必须与 subject 中的包名一致validate_publisher_packagevalidation.rs——防止一个发布者替别的包署名产物。workspace-taskproject长度不超过 4096 字节task长度不超过 256 字节owner 必须是Organizationworkspace-task产物不允许Publisher所有权validation.rs错误信息为workspace task artifacts require an organization owner。此外payload 校验还会检查kind与 subject 派生出的 artifact kind 一致、input_key以对应前缀开头validation.rs从而把 kind、input key、subject 三者绑定为一个自洽的整体。六、Subject 如何参与存储身份entry digest在 pnpr 服务端存储条目entry的身份由input key subject共同哈希而来pnpr/crates/shared-artifacts/src/artifact_identity.rspub(super) fn entry_digest(key: str, subject: ArtifactSubject) - String { let mut hasher Sha256::new(); hasher.update(bpnpm-shared-artifact-entry-v1\0); hasher.update(key.as_bytes()); hasher.update([0]); hasher.update(serde_json::to_vec(subject).expect(artifact subjects serialize)); hex(hasher.finalize()) }这意味着subject 不同产物就存到不同的 entry 下。例如同一 input key 下dependency-side-effects与workspace-task两个 subject 不会互相干扰同一包不同sourceIntegrity源码被修改也会得到不同的 entry。这种key subject的复合寻址正是本次变更把 subject 引入 candidates 与签名 payload 后存储层得以区分产物归属的关键。七、版本兼容性为什么必须服务端与客户端版本匹配变更说明最后一条明确强调This changes shared-artifact request bodies and signed payloads. A pnpr server and its clients have to be on matching versions.由于本次变更同时修改了resolve 请求体candidates 携带 subject与签名 payloadsubject 字段旧版服务端无法理解新版客户端的请求结构新版客户端也无法接受旧版服务端返回/存储的载荷结构。因此pnpm/pnpr.client打major版本请求体与签名载荷的线格式破坏性变更pnpm/pnpr、pacquet、pnpm打minor版本作为消费方跟随协议演进。升级实践上应先升级 pnpr 服务端再升级使用共享产物协议的客户端pnpm / pacquet / pnpr.client并确保两端处于同一协议代际。客户端还提供了能力探测入口pnprSupportsSharedSideEffectssharedSideEffects.ts通过GET /-/pnpr检查服务端是否声明支持 artifacts 协议版本可用于发布/解析前的兼容性判断。八、进一步阅读与验证想深入验证 subject 模型的完整行为可以从以下仓库位置入手协议线格式与常量pnpm/crates/shared-artifact-protocol/src/lib.rs签名与验签pnpm/crates/shared-artifact-protocol/src/signatures.rssubject/payload/candidate 校验规则pnpm/crates/shared-artifact-protocol/src/validation.rs服务端存储身份entry digestpnpr/crates/shared-artifacts/src/artifact_identity.rs服务端并发与作用域scope管理pnpr/crates/shared-artifacts/src/scopes.rsTypeScript 客户端实现与类型定义pnpr/client/src/sharedSideEffects.ts客户端行为测试pnpr/client/test/sharedSideEffects.test.ts 与 pnpr/crates/shared-artifacts/src/tests/behavior.rs小结general-artifact-subjects是共享产物协议在身份建模上的一次关键演进通过dependency-side-effectspackage sourceIntegrity与workspace-taskproject task两个判别式 subjectcandidates 与签名 payload 得以精确、可校验地声明产物归属kind、input key 前缀与 subject 三者互相绑定entry 存储身份也由 key subject 共同决定。由于线格式与签名载荷同步变更升级时务必保证 pnpr 服务端与客户端版本匹配。【免费下载链接】pnpmFast, disk space efficient package manager项目地址: https://gitcode.com/gh_mirrors/pn/pnpm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →