feat: Module Package v1 carries the module's own license (ADR 0051)
CI / verify (pull_request) Successful in 58s
CI / infrastructure-smoke (pull_request) Successful in 35s
CI / migration-smoke (pull_request) Successful in 1m53s
CI / image-build (pull_request) Successful in 30s
CI / deploy-smoke (same-origin) (pull_request) Successful in 50s
CI / deploy-smoke (split) (pull_request) Successful in 1m32s
CI / verify (pull_request) Successful in 58s
CI / infrastructure-smoke (pull_request) Successful in 35s
CI / migration-smoke (pull_request) Successful in 1m53s
CI / image-build (pull_request) Successful in 30s
CI / deploy-smoke (same-origin) (pull_request) Successful in 50s
CI / deploy-smoke (split) (pull_request) Successful in 1m32s
Every Module Package v1 now declares the module's license and carries its text. packageFormat stays 1; pre-ADR-0051 metadata fails closed. - Contracts: ekklesia.package.json requires an SPDX `license` expression, validated with spdx-expression-parse, with UNLICENSED and non-expressions refused. The `files` list must hold exactly one non-empty root LICENSE. The package contracts move to the @ekklesia/contracts/module-package subpath, so the SPDX parser and its data (spdx-exceptions and spdx-license-ids ship no licence files) never reach a module bundle through the SDK's re-export of the contracts root. - Packager: reads the source package.json's string SPDX declaration and never invents one. It requires an explicit --license-file (a non-empty regular file, no symlink, no parent search, no download) and copies its bytes unchanged to LICENSE, listed with size and SHA-256 and covered by the archive digest. THIRD_PARTY_NOTICES stays third-party only, and bundled Ekklesia code stays in `incorporates`. - Verifier: rejects missing or invalid license metadata, and a missing, unlisted, duplicated, or corrupted LICENSE, through the schema and the existing strict inventory. - Template: packaged as AGPL-3.0-or-later with the repository's canonical LICENSE, passed explicitly (Dockerfile, smoke, docs). Packing it twice gives byte-identical archives. Started by Codex; reviewed and completed in Claude Code (empty license-text refusal, schema non-empty LICENSE, symlink and empty-file tests). ADR 0051 is Accepted. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
30f61094e6
commit
85fae9e07e
@@ -9,7 +9,6 @@ export * from "./module-http.js";
|
||||
export * from "./module-lifecycle.js";
|
||||
export * from "./module-manifest.js";
|
||||
export * from "./module-migration.js";
|
||||
export * from "./module-package.js";
|
||||
export * from "./module-realtime.js";
|
||||
export * from "./module-runtime.js";
|
||||
export * from "./module-schema.js";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import parseSpdxExpression from "spdx-expression-parse";
|
||||
import { moduleIdSchema } from "./identifiers.js";
|
||||
import { semanticVersionRangeSchema, semanticVersionSchema } from "./versions.js";
|
||||
|
||||
@@ -11,11 +12,30 @@ import { semanticVersionRangeSchema, semanticVersionSchema } from "./versions.js
|
||||
*/
|
||||
|
||||
export const MODULE_PACKAGE_FORMAT = 1;
|
||||
export const MODULE_PACKAGE_LICENSE_FILENAME = "LICENSE";
|
||||
export const MODULE_PACKAGE_METADATA_FILENAME = "ekklesia.package.json";
|
||||
export const MODULE_PACKAGE_NOTICES_FILENAME = "THIRD_PARTY_NOTICES";
|
||||
/** The portable target: a package with no native or platform-specific file. */
|
||||
export const MODULE_PACKAGE_ANY_TARGET = "any";
|
||||
|
||||
/** The module publisher's package-level license declaration (ADR 0051). This validates SPDX syntax,
|
||||
* not whether Ekklesia approves, recommends, or considers the license legally compatible. */
|
||||
export const modulePackageLicenseExpressionSchema = z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(1024)
|
||||
.superRefine((value, context) => {
|
||||
if (value === "UNLICENSED") {
|
||||
context.addIssue({ code: "custom", message: "Must be an SPDX license expression." });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
parseSpdxExpression(value);
|
||||
} catch {
|
||||
context.addIssue({ code: "custom", message: "Must be an SPDX license expression." });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* `any`, or `<platform>-<arch>[-<libc>]` in Node.js's own `process.platform` and `process.arch`
|
||||
* vocabulary. On Linux the C library is required, because a native addon built against one does
|
||||
@@ -104,6 +124,8 @@ export const modulePackageMetadataSchema = z
|
||||
.strictObject({
|
||||
packageFormat: z.literal(MODULE_PACKAGE_FORMAT),
|
||||
module: z.strictObject({ id: moduleIdSchema, version: semanticVersionSchema }),
|
||||
/** The module's authoritative package-release license declaration (ADR 0051). */
|
||||
license: modulePackageLicenseExpressionSchema,
|
||||
target: modulePackageTargetSchema,
|
||||
runtime: modulePackageRuntimeSchema,
|
||||
provenance: z.strictObject({
|
||||
@@ -124,6 +146,16 @@ export const modulePackageMetadataSchema = z
|
||||
files: z.array(modulePackageFileSchema),
|
||||
})
|
||||
.superRefine((metadata, context) => {
|
||||
const licenseFiles = metadata.files.filter(
|
||||
(file) => file.path === MODULE_PACKAGE_LICENSE_FILENAME,
|
||||
);
|
||||
if (licenseFiles.length !== 1 || licenseFiles[0]?.size === 0) {
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message: "The payload must contain exactly one non-empty root LICENSE file.",
|
||||
path: ["files"],
|
||||
});
|
||||
}
|
||||
if (
|
||||
metadata.target === MODULE_PACKAGE_ANY_TARGET &&
|
||||
(metadata.runtime.nodeApi !== undefined ||
|
||||
@@ -157,6 +189,10 @@ export function safeParseModulePackageMetadata(input: unknown) {
|
||||
return modulePackageMetadataSchema.safeParse(input);
|
||||
}
|
||||
|
||||
export function safeParseModulePackageLicenseExpression(input: unknown) {
|
||||
return modulePackageLicenseExpressionSchema.safeParse(input);
|
||||
}
|
||||
|
||||
export function safeParseModulePackageTarget(input: unknown) {
|
||||
return modulePackageTargetSchema.safeParse(input);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user