You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by "M4n5ter (via GitHub)" <gi...@apache.org> on 2026/08/20 14:22:59 UTC

[PR] feat(release): prepare ASF npm candidates [maka]

M4n5ter opened a new pull request, #3335:
URL: https://github.com/apache/maka/pull/3335

   <details open>
   <summary>English</summary>
   
   ## Summary
   
   - add a credential-free workflow that prepares one unsigned npm convenience candidate from the exact annotated ASF source RC tag;
   - reuse the immutable CLI package validation matrix and carry the same tarball through Linux, macOS, Windows, and Eval checks;
   - add SHA-512 and a closed, independently verifiable record binding the tarball bytes to the source tag, commit, repository, workflow, run, and attempt;
   - document the Release Manager handoff and the boundary around signing, voting, credentials, and publication.
   
   ## Scope and dependency
   
   This is the first, non-publishing slice of G8. It depends on #3222 for the unified product identity, npm package construction, and reusable validation workflow.
   
   The workflow deliberately does not sign artifacts, call npm staging or publication, modify dist-tags, or claim that the source release has passed either required vote. Those requirements remain tracked in #3275.
   
   Refs #3275.
   
   ## Verification
   
   - `node --test scripts/asf-npm-candidate.test.mjs scripts/asf-npm-workflow-policy.test.mjs`
   - `mise exec go -- go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.7 .github/workflows/asf-npm-candidate.yml .github/workflows/cli-package-validation.yml`
   - `npm run build`
   - `npm run format:check`
   - `npm run lint`
   - `npm run check:release`
   - real npm tarball creation and `npm run release:cli:smoke`
   
   </details>
   
   <details>
   <summary>简体中文</summary>
   
   ## 概述
   
   - 新增一个无凭据工作流,从精确的 ASF 源码 RC annotated tag 准备唯一的未签名 npm convenience candidate;
   - 复用不可变 CLI 包验证矩阵,让同一份 tarball 通过 Linux、macOS、Windows 和 Eval 检查;
   - 增加 SHA-512 以及闭合、可独立验证的记录,将 tarball 字节绑定到源码 tag、commit、仓库、工作流、run 和 attempt;
   - 记录 Release Manager 交接流程,并明确签名、投票、凭据和发布的职责边界。
   
   ## 范围与依赖
   
   这是 G8 的第一个非发布阶段。它依赖 #3222 提供统一产品身份、npm 包构建和可复用验证工作流。
   
   该工作流刻意不签名、不调用 npm staging 或发布、不修改 dist-tag,也不声称源码 release 已通过任一必要投票。其余要求继续由 #3275 跟踪。
   
   Refs #3275。
   
   ## 验证
   
   - `node --test scripts/asf-npm-candidate.test.mjs scripts/asf-npm-workflow-policy.test.mjs`
   - `mise exec go -- go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.7 .github/workflows/asf-npm-candidate.yml .github/workflows/cli-package-validation.yml`
   - `npm run build`
   - `npm run format:check`
   - `npm run lint`
   - `npm run check:release`
   - 实际 npm tarball 构建及 `npm run release:cli:smoke`
   
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org


Re: [PR] feat(release): prepare ASF npm candidates [maka]

Posted by "M4n5ter (via GitHub)" <gi...@apache.org>.
M4n5ter closed pull request #3335: feat(release): prepare ASF npm candidates
URL: https://github.com/apache/maka/pull/3335


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org


Re: [PR] feat(release): prepare ASF npm candidates [maka]

Posted by "M4n5ter (via GitHub)" <gi...@apache.org>.
M4n5ter merged PR #3335:
URL: https://github.com/apache/maka/pull/3335


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org


Re: [PR] feat(release): prepare ASF npm candidates [maka]

Posted by "Astro-Han (via GitHub)" <gi...@apache.org>.
Astro-Han commented on code in PR #3335:
URL: https://github.com/apache/maka/pull/3335#discussion_r3835650544


##########
scripts/asf-npm-candidate.mjs:
##########
@@ -0,0 +1,285 @@
+import { createHash } from 'node:crypto';
+import { readFileSync, realpathSync, writeFileSync } from 'node:fs';
+import { basename, dirname, join, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { parseAsfSourceReferenceTag } from './product-release-identity.mjs';
+import { parseProductReleaseVersion } from './release-version.mjs';
+
+const PACKAGE_NAME = 'maka-agent';
+const ASF_REPOSITORY = 'apache/maka';
+const WORKFLOW_PATH = '.github/workflows/asf-npm-candidate.yml';
+const RECORD_SCHEMA_VERSION = 1;
+const defaultRepoRoot = resolve(import.meta.dirname, '..');
+
+export function asfNpmCandidateIdentity(version) {
+  parseProductReleaseVersion(version);
+  const tarball = `${PACKAGE_NAME}-${version}.tgz`;
+  return {
+    packageName: PACKAGE_NAME,
+    version,
+    tarball,
+    sha256: `${tarball}.sha256`,
+    sha512: `${tarball}.sha512`,
+    inventory: `${tarball}.files.json`,
+    record: `${tarball}.asf-candidate.json`,
+  };
+}
+
+export function createAsfNpmCandidateRecord({
+  repoRoot = defaultRepoRoot,
+  releaseDirectory = join(repoRoot, 'packages/cli/release'),
+  sourceReferenceTag,
+  sourceCommit,
+  repository,
+  runId,
+  runAttempt,
+}) {
+  const { version, ...sourceReference } = resolveSourceReference({
+    sourceReferenceTag,
+    sourceCommit,
+  });
+  const identity = asfNpmCandidateIdentity(version);
+  validateRepositoryVersion(repoRoot, version);
+  const workflow = validateWorkflowIdentity({
+    repository,
+    path: WORKFLOW_PATH,
+    runId,
+    runAttempt,
+  });
+  const candidate = validateCandidateFiles(releaseDirectory, identity);
+  writeFileSync(
+    join(releaseDirectory, identity.sha512),
+    `${candidate.sha512}  ${identity.tarball}\n`,
+    { flag: 'wx', mode: 0o644 },
+  );
+  const record = {
+    schemaVersion: RECORD_SCHEMA_VERSION,
+    artifactType: 'npm-convenience-candidate',
+    packageName: PACKAGE_NAME,
+    version,
+    tarball: identity.tarball,
+    digests: {
+      sha256: candidate.sha256,
+      sha512: candidate.sha512,
+    },
+    sourceReference,
+    workflow,
+  };
+  const recordPath = join(releaseDirectory, identity.record);
+  writeFileSync(recordPath, `${JSON.stringify(record, null, 2)}\n`, {
+    flag: 'wx',
+    mode: 0o644,
+  });
+  return { record, recordPath };
+}
+
+export function verifyAsfNpmCandidateRecord({ recordPath }) {
+  const record = readJson(recordPath, 'ASF npm candidate record');
+  exactKeys(
+    record,
+    [
+      'schemaVersion',
+      'artifactType',
+      'packageName',
+      'version',
+      'tarball',
+      'digests',
+      'sourceReference',
+      'workflow',
+    ],
+    'ASF npm candidate record',
+  );
+  if (
+    record.schemaVersion !== RECORD_SCHEMA_VERSION ||
+    record.artifactType !== 'npm-convenience-candidate' ||
+    record.packageName !== PACKAGE_NAME
+  ) {
+    throw new Error('Unsupported ASF npm candidate record');
+  }
+  const identity = asfNpmCandidateIdentity(record.version);
+  if (record.tarball !== identity.tarball) {
+    throw new Error('ASF npm candidate record tarball is inconsistent');
+  }
+  exactKeys(record.digests, ['sha256', 'sha512'], 'ASF npm candidate digests');
+  exactKeys(record.sourceReference, ['candidateTag', 'commit'], 'ASF source reference');
+  const { version: sourceVersion } = resolveSourceReference({
+    sourceReferenceTag: record.sourceReference.candidateTag,
+    sourceCommit: record.sourceReference.commit,
+  });
+  if (sourceVersion !== record.version) {
+    throw new Error(
+      `Source candidate tag version ${sourceVersion} does not match ${record.version}`,
+    );
+  }
+  exactKeys(record.workflow, ['repository', 'path', 'runId', 'runAttempt'], 'workflow identity');
+  validateWorkflowIdentity(record.workflow);
+  const releaseDirectory = dirname(resolve(recordPath));
+  const candidate = validateCandidateFiles(releaseDirectory, identity);
+  validateChecksumFile(
+    join(releaseDirectory, identity.sha512),
+    identity.tarball,
+    candidate.sha512,
+    128,
+  );
+  if (record.digests.sha256 !== candidate.sha256 || record.digests.sha512 !== candidate.sha512) {
+    throw new Error('ASF npm candidate record digest does not match the tarball');
+  }
+  return { record, tarballPath: candidate.tarballPath };
+}
+
+function validateRepositoryVersion(repoRoot, version) {

Review Comment:
   [P2] Could this candidate reuse the existing `product-release-identity` authority instead of keeping a second manifest check? This function validates only root/CLI versions, while the generated npm manifest copies the CLI `bin`; an exact-head reproduction with `{ "bin": { "notMaka": "./dist/not-maka.js" } }` still produced a candidate record even though the canonical product contract requires the sole command to remain `maka`. Calling `resolveProductManifestIdentity()` here and adding an extra/missing `bin` regression would leave one release identity seam.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org


Re: [PR] feat(release): prepare ASF npm candidates [maka]

Posted by "M4n5ter (via GitHub)" <gi...@apache.org>.
M4n5ter commented on code in PR #3335:
URL: https://github.com/apache/maka/pull/3335#discussion_r3835697570


##########
scripts/asf-npm-candidate.mjs:
##########
@@ -0,0 +1,285 @@
+import { createHash } from 'node:crypto';
+import { readFileSync, realpathSync, writeFileSync } from 'node:fs';
+import { basename, dirname, join, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { parseAsfSourceReferenceTag } from './product-release-identity.mjs';
+import { parseProductReleaseVersion } from './release-version.mjs';
+
+const PACKAGE_NAME = 'maka-agent';
+const ASF_REPOSITORY = 'apache/maka';
+const WORKFLOW_PATH = '.github/workflows/asf-npm-candidate.yml';
+const RECORD_SCHEMA_VERSION = 1;
+const defaultRepoRoot = resolve(import.meta.dirname, '..');
+
+export function asfNpmCandidateIdentity(version) {
+  parseProductReleaseVersion(version);
+  const tarball = `${PACKAGE_NAME}-${version}.tgz`;
+  return {
+    packageName: PACKAGE_NAME,
+    version,
+    tarball,
+    sha256: `${tarball}.sha256`,
+    sha512: `${tarball}.sha512`,
+    inventory: `${tarball}.files.json`,
+    record: `${tarball}.asf-candidate.json`,
+  };
+}
+
+export function createAsfNpmCandidateRecord({
+  repoRoot = defaultRepoRoot,
+  releaseDirectory = join(repoRoot, 'packages/cli/release'),
+  sourceReferenceTag,
+  sourceCommit,
+  repository,
+  runId,
+  runAttempt,
+}) {
+  const { version, ...sourceReference } = resolveSourceReference({
+    sourceReferenceTag,
+    sourceCommit,
+  });
+  const identity = asfNpmCandidateIdentity(version);
+  validateRepositoryVersion(repoRoot, version);
+  const workflow = validateWorkflowIdentity({
+    repository,
+    path: WORKFLOW_PATH,
+    runId,
+    runAttempt,
+  });
+  const candidate = validateCandidateFiles(releaseDirectory, identity);
+  writeFileSync(
+    join(releaseDirectory, identity.sha512),
+    `${candidate.sha512}  ${identity.tarball}\n`,
+    { flag: 'wx', mode: 0o644 },
+  );
+  const record = {
+    schemaVersion: RECORD_SCHEMA_VERSION,
+    artifactType: 'npm-convenience-candidate',
+    packageName: PACKAGE_NAME,
+    version,
+    tarball: identity.tarball,
+    digests: {
+      sha256: candidate.sha256,
+      sha512: candidate.sha512,
+    },
+    sourceReference,
+    workflow,
+  };
+  const recordPath = join(releaseDirectory, identity.record);
+  writeFileSync(recordPath, `${JSON.stringify(record, null, 2)}\n`, {
+    flag: 'wx',
+    mode: 0o644,
+  });
+  return { record, recordPath };
+}
+
+export function verifyAsfNpmCandidateRecord({ recordPath }) {
+  const record = readJson(recordPath, 'ASF npm candidate record');
+  exactKeys(
+    record,
+    [
+      'schemaVersion',
+      'artifactType',
+      'packageName',
+      'version',
+      'tarball',
+      'digests',
+      'sourceReference',
+      'workflow',
+    ],
+    'ASF npm candidate record',
+  );
+  if (
+    record.schemaVersion !== RECORD_SCHEMA_VERSION ||
+    record.artifactType !== 'npm-convenience-candidate' ||
+    record.packageName !== PACKAGE_NAME
+  ) {
+    throw new Error('Unsupported ASF npm candidate record');
+  }
+  const identity = asfNpmCandidateIdentity(record.version);
+  if (record.tarball !== identity.tarball) {
+    throw new Error('ASF npm candidate record tarball is inconsistent');
+  }
+  exactKeys(record.digests, ['sha256', 'sha512'], 'ASF npm candidate digests');
+  exactKeys(record.sourceReference, ['candidateTag', 'commit'], 'ASF source reference');
+  const { version: sourceVersion } = resolveSourceReference({
+    sourceReferenceTag: record.sourceReference.candidateTag,
+    sourceCommit: record.sourceReference.commit,
+  });
+  if (sourceVersion !== record.version) {
+    throw new Error(
+      `Source candidate tag version ${sourceVersion} does not match ${record.version}`,
+    );
+  }
+  exactKeys(record.workflow, ['repository', 'path', 'runId', 'runAttempt'], 'workflow identity');
+  validateWorkflowIdentity(record.workflow);
+  const releaseDirectory = dirname(resolve(recordPath));
+  const candidate = validateCandidateFiles(releaseDirectory, identity);
+  validateChecksumFile(
+    join(releaseDirectory, identity.sha512),
+    identity.tarball,
+    candidate.sha512,
+    128,
+  );
+  if (record.digests.sha256 !== candidate.sha256 || record.digests.sha512 !== candidate.sha512) {
+    throw new Error('ASF npm candidate record digest does not match the tarball');
+  }
+  return { record, tarballPath: candidate.tarballPath };
+}
+
+function validateRepositoryVersion(repoRoot, version) {

Review Comment:
   Fixed in efe48ad65. Candidate record creation now delegates root/Desktop/CLI manifest validation to `resolveProductManifestIdentity()` and only performs the source-RC-to-canonical-version comparison locally, removing the partial version authority. I also added a regression where `maka` remains present but an extra public `legacyMaka` bin is declared; record creation now rejects it through the canonical product contract. `check:asf-npm` passes 7/7, the shared product identity suite passes 23/23, and Biome is clean.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org