Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
377 changes: 375 additions & 2 deletions .github/workflows/agentplugins-npm-publish.yml

Large diffs are not rendered by default.

328 changes: 324 additions & 4 deletions .github/workflows/agentplugins-release.yml

Large diffs are not rendered by default.

432 changes: 420 additions & 12 deletions cli/plugin-kit-ai/cmd/agentplugins/release_workflow_test.go

Large diffs are not rendered by default.

242 changes: 238 additions & 4 deletions npm/agentplugins/scripts/authoring-native-inputs.js

Large diffs are not rendered by default.

217 changes: 207 additions & 10 deletions npm/agentplugins/scripts/authoring-promotion.js

Large diffs are not rendered by default.

30 changes: 25 additions & 5 deletions npm/agentplugins/scripts/npm-public-contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,28 +41,47 @@ function validateExpected(version, integrity, shasum) {
if (!SHASUM.test(shasum)) fail("shasum must be an exact lowercase SHA-1 value");
}

function validatePackJSON(value, version) {
// Fixed packing identities only; registry/provenance policy remains agent-only.
function validateProductPackJSON(value, product, version) {
const name = product === "agentplugins" ? PACKAGE_NAME :
product === "plugin-kit-ai" ? "plugin-kit-ai" : null;
if (!name) fail("unknown fixed npm product");
if (typeof version !== "string" || version.match(VERSION)?.[0] !== version) {
fail("version must be an exact stable semantic version");
}
let record;
if (Array.isArray(value)) {
if (value.length !== 1) fail("npm pack JSON must contain exactly one record");
[record] = value;
} else if (value && typeof value === "object") {
const keys = Object.keys(value);
if (keys.length !== 1 || keys[0] !== PACKAGE_NAME) {
if (keys.length !== 1 || keys[0] !== name) {
fail("npm pack JSON must contain exactly one package-named record");
}
record = value[PACKAGE_NAME];
record = value[name];
} else {
fail("npm pack JSON must contain exactly one record");
}
if (!record || record.name !== PACKAGE_NAME || record.version !== version ||
record.filename !== `${PACKAGE_NAME}-${version}.tgz`) {
if (!record || typeof record !== "object" || Array.isArray(record) || record.name !== name ||
record.version !== version ||
record.filename !== `${name}-${version}.tgz`) {
fail("npm pack JSON package identity does not match the release");
}
if (typeof record.integrity !== "string" || typeof record.shasum !== "string") {
fail("npm pack JSON integrity and shasum must be strings");
}
if (record.shasum.length !== 40) fail("shasum must be an exact lowercase SHA-1 value");
validateExpected(version, record.integrity, record.shasum);
if ("sha512-" + Buffer.from(record.integrity.slice(7), "base64").toString("base64") !== record.integrity) {
fail("npm pack JSON integrity must be canonical SHA-512 SRI");
}
return record;
}

function validatePackJSON(value, version) {
return validateProductPackJSON(value, "agentplugins", version);
}

function validatePublicMetadata(metadata, version, integrity, shasum) {
validateExpected(version, integrity, shasum);
if (Array.isArray(metadata)) {
Expand Down Expand Up @@ -261,6 +280,7 @@ module.exports = {
validateDownloadedTarball,
validateAuditSignatures,
validatePackJSON,
validateProductPackJSON,
validatePublicMetadata,
validateSLSAAttestation
};
572 changes: 565 additions & 7 deletions npm/agentplugins/scripts/stage-authoring-npm.js

Large diffs are not rendered by default.

35 changes: 27 additions & 8 deletions npm/agentplugins/scripts/stage-dual-authoring-npm.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const cp = require("node:child_process");
const crypto = require("node:crypto");
const c = require("./dual-authoring-candidate");
const producer = require("./stage-dual-authoring-candidate");
const { validateProductPackJSON } = require("./npm-public-contract");
const MODE = "release-cli-contract-v1";
const PACKAGES = { agentplugins: "universal-agent-plugins", "plugin-kit-ai": "plugin-kit-ai" };
const COMMON = ["lib/verifier.js", "scripts/dual-authoring-candidate.js",
Expand Down Expand Up @@ -39,14 +40,17 @@ function npmContext(workParent) {
}

function blobs(repo, commit, env, closure = "private") {
if (!["private", "public"].includes(closure)) throw new Error("unknown fixed npm closure");
const allowlist = closure === "private" ? ALLOWLIST : require("./stage-authoring-npm").ALLOWLIST;
if (!["private", "public", "stage"].includes(closure)) throw new Error("unknown fixed npm closure");
const allowlist = closure === "private" ? ALLOWLIST : require("./stage-authoring-npm")[closure === "stage" ? "STAGE_ALLOWLIST" : "ALLOWLIST"];
c.safeDirectory(repo);
if (run("/usr/bin/git", ["rev-parse", "HEAD"], env, repo).toString().trim() !== commit) {
throw new Error("expected source must equal checkout HEAD");
}
const result = {};
for (const name of allowlist) {
// Check the newly executing helper at the same commit without extending the
// historical private/public preparation wrapper_blobs receipt inventories.
const packHelper = PREFIX + "scripts/npm-public-contract.js";
for (const name of [...new Set([...allowlist, packHelper])]) {
const entry = run("/usr/bin/git", ["ls-tree", "-z", commit, "--", name], env, repo).toString();
const match = /^(100644|100755) blob ([0-9a-f]{40})\t([^\0]+)\0$/.exec(entry);
if (!match || match[3] !== name) throw new Error(`required regular Git blob missing: ${name}`);
Expand All @@ -55,14 +59,26 @@ function blobs(repo, commit, env, closure = "private") {
}
// The code doing verification/generation must itself be this committed code.
// A dirty caller may not manufacture an exact-source claim using old blobs.
for (const name of ["scripts/stage-dual-authoring-npm.js", "scripts/stage-dual-authoring-candidate.js",
for (const name of ["scripts/npm-public-contract.js", "scripts/stage-dual-authoring-npm.js", "scripts/stage-dual-authoring-candidate.js",
"scripts/dual-authoring-candidate.js", ...(closure === "public" ?
["scripts/stage-authoring-npm.js", "scripts/authoring-release.js", "lib/public-authoring.js"] : [])]) {
if (!c.readFile(path.resolve(__dirname, "..", name)).equals(result[PREFIX + name].bytes)) {
throw new Error(`executing stager differs from committed source: ${name}`);
}
}
return result;
if (closure === "stage") {
// Every listed checkout byte AND every executing-tree byte must be F. Keep
// legacy preparation inventories/checks unchanged; stage has its own set.
const executing = path.resolve(__dirname, "../../..");
for (const root of new Set([repo, executing])) for (const name of allowlist) {
const file = path.join(root, name), pin = result[name];
if (!c.readFile(file).equals(pin.bytes) ||
(fs.lstatSync(file).mode & 0o777) !== (pin.mode === "100755" ? 0o755 : 0o644)) {
throw new Error(`stage source differs from committed F: ${name}`);
}
}
}
return Object.fromEntries(allowlist.map(name => [name, result[name]]));
}

function packageFiles(product, source, manifestBytes, options) {
Expand Down Expand Up @@ -108,14 +124,17 @@ function verifyPack(tarball, files, destination, env) {

// One exact pack algorithm for both fixed closures; private defaults are intact.
function packPackage(product, files, root, options, context) {
if (!c.PRODUCTS.includes(product)) throw new Error("unknown fixed npm product");
const result = JSON.parse(run(options.node, [options.npm, "pack", "--ignore-scripts", "--offline", "--json",
"--pack-destination", options.output], context.env, root));
const filename = `${PACKAGES[product]}-${options.identity.versions[product]}.tgz`;
if (result.length !== 1 || result[0].filename !== filename) throw new Error("unexpected npm pack result");
const record = validateProductPackJSON(result, product, options.identity.versions[product]);
const tarball = path.join(options.output, filename), bytes = c.readFile(tarball);
verifyPack(tarball, files, path.join(context.root, product), context.env);
const integrity = "sha512-" + crypto.createHash("sha512").update(bytes).digest("base64");
if (result[0].integrity !== integrity) throw new Error("npm integrity differs from actual pack");
const shasum = crypto.createHash("sha1").update(bytes).digest("hex");
if (record.integrity !== integrity) throw new Error("npm integrity differs from actual pack");
if (record.shasum !== shasum) throw new Error("npm shasum differs from actual pack");
verifyPack(tarball, files, path.join(context.root, product), context.env);
return { file: filename, ...c.metadata(bytes), integrity };
}

Expand Down
Loading
Loading