Skip to content

feat(target-config): add release identity contract - #124

Open
imjlk wants to merge 1 commit into
mainfrom
codex/release-identity
Open

feat(target-config): add release identity contract#124
imjlk wants to merge 1 commit into
mainfrom
codex/release-identity

Conversation

@imjlk

@imjlk imjlk commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • add a shared immutable release identity contract for multi-target artifacts
  • keep a common human-facing release label separate from native package/build counters
  • document versioning rules and verify legacy manifest merging

Validation

  • pnpm check
  • pnpm smoke:release-manifest-merge
  • final OCR re-review: no findings

Summary by CodeRabbit

  • New Features

    • Added shared release identity information, including game versions, release revisions, labels, and release IDs.
    • Release manifests now include optional release identity details while remaining compatible with legacy manifests.
    • Added validation to prevent mismatched versions, labels, revisions, and release identifiers.
    • Target release versions now support consistent, non-resetting release labels across platforms.
  • Documentation

    • Updated release versioning guidance to clarify revision allocation, label validation, and platform-specific build version rules.
  • Bug Fixes

    • Improved final semantic-version validation and release-manifest merging behavior.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds shared release identity utilities, extends release manifests with optional identity data, integrates identity creation and validation into manifest generation and merging, updates final SemVer checks, and documents the release versioning model.

Changes

Release identity and manifest integration

Layer / File(s) Summary
Release identity utilities
packages/target-config/src/*, packages/target-config/test/*, packages/release-manifest/package.json
The target-config package adds release identity types and helpers for SemVer validation, revision parsing, label creation, and release ID formatting. Smoke tests cover valid and invalid inputs.
Manifest identity contract
packages/release-manifest/src/index.ts, release.manifest.schema.json
Release manifests optionally include releaseIdentity. Validation checks canonical labels, matching game versions, and formatted release IDs.
Manifest generation and merge flow
tools/target/*, tools/smoke/release-manifest-merge.ts
Manifest generation reads release identity inputs, emits identity-derived values, compares identity fields during merges, and uses shared final-SemVer validation. Smoke tests cover legacy manifests and revision changes.
Versioning contract documentation
docs/MULTI_TARGET_RELEASE_VERSIONING.md, .sampo/changesets/jovial-warden-akka.md
The documentation defines release revisions, common labels, target build counters, validation, manifest handling, and the package patch release.
Estimated code review effort: 3 (Moderate) ~25 minutes

Possibly related PRs

Poem

A rabbit labels each release,
With revisions that never cease.
SemVer stays neat, IDs align,
Manifests merge by one design.
Hop, hop—the targets now agree!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding a release identity contract to target-config.
Description check ✅ Passed The description includes a relevant summary and validation details, although it omits the Notes section and some template validation items.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/release-identity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 74839cd0c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +30 to +32
const semVerPattern = new RegExp(
`^${semVerCore}(?:-${preReleaseIdentifier}(?:\\.${preReleaseIdentifier})*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$`,
'u',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve SemVer build metadata in release IDs

When releaseRevision is omitted, this pattern accepts a valid version such as 1.2.3+sha, and createMpgdReleaseIdentity uses it verbatim as the label. generate-release-manifest.ts then passes that label to formatMpgdReleaseId, whose component validation rejects every +, so manifest generation now fails for APP_VERSION or package.json.version values containing SemVer build metadata that worked before this commit. The accepted version grammar and release-ID encoding need to compose, or metadata must be rejected explicitly at the identity boundary.

Useful? React with 👍 / 👎.

Comment on lines +79 to +80
if (manifest.gameVersion !== releaseIdentity.gameVersion) {
throw new TypeError('Release manifest gameVersion must match releaseIdentity.gameVersion.');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject noncanonical nested game versions

If a schema-valid or hand-edited manifest contains releaseIdentity.gameVersion: " 1.0.0 " while the top-level version, label, and release ID are canonical, createMpgdReleaseIdentity trims the nested value and this comparison succeeds because it checks only the top-level value against the normalized result. The validator therefore returns a noncanonical identity; when another target is generated with "1.0.0", hasMatchingReleaseIdentity raw-compares the nested strings and resets the manifest instead of preserving the earlier targets. Check the original nested value against the normalized value, analogous to the label check above.

Useful? React with 👍 / 👎.

Comment on lines +329 to +330
if (previous === undefined || next === undefined) {
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Separate legacy prereleases from revision labels

When the previous file is a legacy manifest for game version 1.0.0-v42, its old release ID is identical to the new release ID for { gameVersion: "1.0.0", releaseRevision: 42 } when the build ID is reused. If the source and other contract fields also match—for example, APP_VERSION is changed between builds from the same checkout while the default BUILD_ID=local is retained—this unconditional match preserves targets built for the prerelease inside the final release manifest. Compare the legacy top-level game version as well, or otherwise disambiguate legacy prerelease labels before merging.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/target-config/src/releaseIdentity.ts`:
- Around line 90-136: Update normalizeMpgdSemVer to reject SemVer values
containing build metadata (+ suffix), so gameVersion cannot produce a label with
the reserved release-ID separator. Ensure createMpgdReleaseIdentity fails at
normalization time with a clear TypeError, while preserving acceptance of valid
versions without build metadata.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 985fbbe8-86d1-485f-a9b9-a56cc79d7e89

📥 Commits

Reviewing files that changed from the base of the PR and between 0e23de1 and 74839cd.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • .sampo/changesets/jovial-warden-akka.md
  • docs/MULTI_TARGET_RELEASE_VERSIONING.md
  • packages/release-manifest/package.json
  • packages/release-manifest/src/index.ts
  • packages/target-config/src/index.ts
  • packages/target-config/src/releaseIdentity.ts
  • packages/target-config/test/target-config-smoke.ts
  • release.manifest.schema.json
  • tools/smoke/release-manifest-merge.ts
  • tools/target/generate-release-manifest.ts
  • tools/target/native-release-identity.ts

Comment on lines +90 to +136
export function formatMpgdReleaseId(label: string, buildId: string): string {
assertReleaseIdComponent(label, 'label');
assertReleaseIdComponent(buildId, 'buildId');

return `mpgd-${label}+${buildId}`;
}

/** Parse an optional release revision without making empty values meaningful. */
export function parseMpgdReleaseRevision(value: string | undefined): number | undefined {
if (value === undefined || value.trim().length === 0) {
return undefined;
}

const normalized = value.trim();

if (!/^[1-9]\d*$/u.test(normalized)) {
throw new TypeError('Release revision must be a positive safe integer.');
}

const releaseRevision = Number.parseInt(normalized, 10);

if (!Number.isSafeInteger(releaseRevision)) {
throw new TypeError('Release revision must be a positive safe integer.');
}

return releaseRevision;
}

function normalizeMpgdSemVer(value: string): string {
const normalized = value.trim();

if (!semVerPattern.test(normalized)) {
throw new TypeError('Release identity gameVersion must be a SemVer value.');
}

return normalized;
}

function assertReleaseIdComponent(value: string, label: string): void {
if (value.length === 0 || /\s/u.test(value)) {
throw new TypeError(`Release identifier ${label} must be a non-empty canonical token.`);
}

if (value.includes('+')) {
throw new TypeError(`Release identifier ${label} must not contain "+".`);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Build-metadata gameVersion silently breaks formatMpgdReleaseId.

normalizeMpgdSemVer (Lines 118-126) accepts SemVer build metadata (the +... suffix) as part of a valid gameVersion. When releaseRevision is not set, formatNormalizedMpgdReleaseLabel returns that gameVersion unchanged as the label (Lines 64-66), so the label can contain +. formatMpgdReleaseId and assertReleaseIdComponent then reject any label containing +, because + is the reserved separator between label and buildId.

A gameVersion such as 0.3.30+001 with no release revision passes createMpgdReleaseIdentity, but then throws later when formatMpgdReleaseId(releaseIdentity.label, buildId) runs in tools/target/generate-release-manifest.ts (Line 103) or packages/release-manifest/src/index.ts (Line 83). The failure surfaces far from its cause and with a confusing message.

Reject build metadata in normalizeMpgdSemVer, or strip it before it becomes part of the label, so invalid input is caught where it originates.

🐛 Proposed fix: reject build metadata in gameVersion at the source
-const semVerPattern = new RegExp(
-  `^${semVerCore}(?:-${preReleaseIdentifier}(?:\\.${preReleaseIdentifier})*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$`,
-  'u',
-);
+// Build metadata is intentionally unsupported: `+` is reserved as the
+// releaseId separator between the label and the build identifier.
+const semVerPattern = new RegExp(
+  `^${semVerCore}(?:-${preReleaseIdentifier}(?:\\.${preReleaseIdentifier})*)?$`,
+  'u',
+);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/target-config/src/releaseIdentity.ts` around lines 90 - 136, Update
normalizeMpgdSemVer to reject SemVer values containing build metadata (+
suffix), so gameVersion cannot produce a label with the reserved release-ID
separator. Ensure createMpgdReleaseIdentity fails at normalization time with a
clear TypeError, while preserving acceptance of valid versions without build
metadata.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant