Skip to content

Use successful deployment tags for DAR versions#290

Merged
HardlyDifficult merged 1 commit into
mainfrom
codex/tag-anchored-dar-versions
Jul 13, 2026
Merged

Use successful deployment tags for DAR versions#290
HardlyDifficult merged 1 commit into
mainfrom
codex/tag-anchored-dar-versions

Conversation

@HardlyDifficult

@HardlyDifficult HardlyDifficult commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • replace the live-network policy from Anchor mutable DAR candidates to DevNet #289 with annotated success tags: dar-deploy/<network>/<package>/v<version>
  • derive the shared mutable candidate from the latest DevNet success tag, with committed legacy markers only as migration fallback
  • require the fresh build to match the committed backup and stop release-time DAR mutation
  • tag DevNet and Mainnet only after every provider succeeds; Mainnet requires the exact DevNet-tagged hash
  • serialize package releases and make exact-tag reruns idempotent, including post-publish finalization
  • preserve every historical backup while allowing only the expected current candidate slot to change

Scope

CI reads Git history, tags, and committed files only. It has no Canton query, deployment secret, pull_request_target, or PR-writing path. This PR intentionally does not normalize OpenCapTable-v34 or touch feature/test code. The audit correctly reports that legacy DevNet 0.0.1 makes 0.0.2 the candidate while current main still contains noisy 0.0.3; stack normalization is separate.

Validation

  • 11/11 focused policy tests
  • TypeScript build
  • ESLint and Prettier
  • full DAML build
  • upgrade compatibility check
  • 3/3 DAR integrity checks
  • base-relative policy check and expected failing all-package migration audit
  • workflow YAML parsing
  • git diff --check

Durable backup and release guidance is updated in the repository wiki.


Note

High Risk
Changes production release and Canton DAR deployment ordering (DevNet tags before Mainnet, immutable deployed artifacts); mistakes could block releases or upload wrong bytes, though policy tests and CI gates mitigate this.

Overview
Replaces live-network / dars.lock network markers as the source of truth with annotated git tags dar-deploy/<network>/<package>/v<version>, whose dars.lock at the tag defines deployed bytes. A new DAR version policy (scripts + tests) picks the mutable candidate as one patch above the latest DevNet success tag (legacy lock networks only as migration fallback), enforces immutable deployed versions, history retention on PRs, and Mainnet upload only after matching DevNet.

CI fetches tags and runs test:dar-version-policy plus check:dar-version-policy (base origin/main on PR builds); auto-fix pushes are limited to branch refs.

Release serializes on package-release-ocp, gates DevNet/Mainnet upload with check:dar-deployment, records success tags after full provider upload, drops release-time backup-dar, blocks mutating dars/ during artifact commit, and allows idempotent reruns when npm is already published but both deployment tags exist. backup-dar and upload-dar no longer auto-backup or write networks to the lock; upload requires backup bytes to match a fresh build.

Reviewed by Cursor Bugbot for commit e6b8979. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 16 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ffaf6f42-ae81-4213-9de7-913376749c3b

📥 Commits

Reviewing files that changed from the base of the PR and between 4b57e0e and e6b8979.

📒 Files selected for processing (9)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • package.json
  • scripts/backup-dar.ts
  • scripts/check-dar-version-policy.ts
  • scripts/dar-utils.ts
  • scripts/dar-version-policy.test.ts
  • scripts/dar-version-policy.ts
  • scripts/upload-dar.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/tag-anchored-dar-versions

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

@HardlyDifficult HardlyDifficult marked this pull request as ready for review July 13, 2026 21:26

Copy link
Copy Markdown
Collaborator Author

@codex review

@cursor review

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e6b8979. Configure here.

Comment thread scripts/backup-dar.ts
console.log(` SHA256: ${hash}`);
console.log(` Size: ${stats.size} bytes`);
console.log(`${plan.replace ? '✅ Backed up' : '✅ Verified'}: ${key}`);
console.log(` SHA256: ${sha256}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Backup skips missing file check

Medium Severity

When planCandidateBackup returns replace: false, backup-dar prints Verified and saves dars.lock without confirming the backup file exists on disk or matches the lock hash. A lock entry with a missing or corrupt file is no longer detected or restored.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e6b8979. Configure here.

Copilot AI 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.

Pull request overview

This PR updates the DAR release/deployment pipeline to treat annotated Git success tags (dar-deploy/<network>/<package>/v<version>) as the source of truth for deployed DAR bytes, and adds a policy layer that enforces immutable deployed artifacts while allowing only the expected mutable candidate slot to change.

Changes:

  • Add a DAR version policy implementation + tests to derive the mutable candidate from the latest DevNet success tag (with legacy dars.lock network markers only as fallback).
  • Require uploads to use the committed backup and prove the fresh build is byte-identical; stop mutating dars.lock at upload time.
  • Update CI and release workflows to fetch tags, run the new policy checks, gate deployments, and record success tags only after full provider success.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
scripts/upload-dar.ts Enforces “fresh build must match committed backup” before uploading; removes lock/network mutation on upload.
scripts/dar-version-policy.ts Implements tag-based deployment evidence, candidate selection, history retention, and deployment gating logic.
scripts/dar-version-policy.test.ts Adds focused unit tests for the new policy logic (candidate selection, immutability, gating, retention).
scripts/dar-utils.ts Removes auto-backup detection and network upload recording helpers (shifts responsibility to tag-based flow).
scripts/check-dar-version-policy.ts Adds a CI/PR check tool to validate backups, fresh builds, history retention, and tag-derived policy.
scripts/backup-dar.ts Refactors backup behavior to replace only the expected candidate while preserving deployed DAR history.
package.json Adds scripts for policy tests and policy checks used by CI/release.
.github/workflows/release.yml Serializes releases, fetches tags, gates DevNet/Mainnet uploads, records annotated success tags, prevents DAR mutation during release finalization, supports idempotent reruns.
.github/workflows/ci.yml Fetches full history + tags, runs policy tests, and enforces base-relative policy checks against origin/main.

Comment thread scripts/backup-dar.ts
Comment on lines +49 to +53
if (plan.replace) {
fs.mkdirSync(path.dirname(destination), { recursive: true });
const temporary = `${destination}.tmp-${process.pid}`;
try {
fs.copyFileSync(source, temporary);

@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: e6b89790c6

ℹ️ 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".

run: |
tag="dar-deploy/mainnet/${PACKAGE_NAME}/v${PACKAGE_VERSION}"
sha=$(node -e 'const l=require("./dars/dars.lock"); console.log(l.packages[`${process.env.PACKAGE_NAME}/${process.env.PACKAGE_VERSION}/${process.env.PACKAGE_NAME}.dar`].sha256)')
git tag -a "$tag" -m "Deployed ${PACKAGE_NAME} v${PACKAGE_VERSION} to Mainnet (${sha})"

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 Configure Git before mainnet-only tag creation

When rerunning after DevNet succeeded and pushed dar-deploy/devnet/... but Mainnet did not get tagged, steps.devnet_gate.outputs.tag_exists is true, so the DevNet record step (the only place that runs git config user.name/user.email) is skipped. The Mainnet record step still creates an annotated tag with git tag -a (confirmed via git tag -h: -a creates an annotated tag), and in a clean runner without a configured identity this fails with Git's unable to auto-detect email address, blocking recovery from a partial release. Configure Git in this step too, or before both tag-creation steps.

Useful? React with 👍 / 👎.

PACKAGE_VERSION: ${{ steps.release_tag.outputs.version }}
run: |
tag="dar-deploy/devnet/${PACKAGE_NAME}/v${PACKAGE_VERSION}"
sha=$(node -e 'const l=require("./dars/dars.lock"); console.log(l.packages[`${process.env.PACKAGE_NAME}/${process.env.PACKAGE_VERSION}/${process.env.PACKAGE_NAME}.dar`].sha256)')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Read dars.lock as JSON before tagging deployments

When the deployment tag is missing, this command runs after the upload succeeds, but require("./dars/dars.lock") is not parsed as JSON because .lock is not a registered Node JSON extension; locally, node -e 'require("./dars/dars.lock")' fails with Unexpected token ':'. That means both fresh DevNet and Mainnet releases upload the DAR and then fail before recording the success tag, so reruns cannot become idempotent until this reads the file with fs.readFileSync/JSON.parse or a .json path.

Useful? React with 👍 / 👎.

Comment thread scripts/backup-dar.ts
const key = getDarLockKey(pkg.name, version, pkg.darName);
const destination = path.join(getDarsDir(), key);

if (plan.replace) {

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 Restore the backup file when the lock entry matches

If a current candidate already has a matching dars.lock entry but the DAR file was deleted or corrupted, plan.replace is false and this branch skips both copying the fresh build and verifying destination, then reports ✅ Verified. That breaks the documented recovery path for the current backup is missing / dars.lock lists ... but the file is missing failures, because rerunning npm run backup-dar will not recreate the file until the lock hash or size changes.

Useful? React with 👍 / 👎.

@HardlyDifficult HardlyDifficult marked this pull request as ready for review July 13, 2026 21:35

Copilot AI 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.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread scripts/backup-dar.ts
Comment on lines +45 to +73
const plan = planCandidateBackup(pkg, lock, readDeploymentTags(pkg, root), sha256, size);
const key = getDarLockKey(pkg.name, version, pkg.darName);
const destination = path.join(getDarsDir(), key);

if (plan.replace) {
fs.mkdirSync(path.dirname(destination), { recursive: true });
const temporary = `${destination}.tmp-${process.pid}`;
try {
fs.copyFileSync(source, temporary);
if (computeSha256(temporary) !== sha256) throw new Error('Temporary DAR copy failed integrity verification');
fs.renameSync(temporary, destination);
} finally {
if (fs.existsSync(temporary)) fs.unlinkSync(temporary);
}

// Verify hash
const hash = computeSha256(destPath);
if (hash !== lock.packages[lockKey].sha256) {
console.error(`❌ Hash mismatch! File may have been modified.`);
process.exit(1);
}

// Add network if specified
if (network && !lock.packages[lockKey].networks.includes(network)) {
lock.packages[lockKey].networks.push(network);
lock.packages[lockKey].networks.sort();
saveDarsLock(lock);
console.log(`✅ Added network: ${network}`);
}
return;
lock.packages[key] = {
sha256,
size,
sdkVersion: sdkVersion(pkg.sourceDir),
uploadedAt: new Date().toISOString(),
networks: [],
};
}

// Create backup
fs.mkdirSync(destDir, { recursive: true });
fs.copyFileSync(sourcePath, destPath);

const hash = computeSha256(destPath);
const stats = fs.statSync(destPath);

lock.packages[lockKey] = {
sha256: hash,
size: stats.size,
sdkVersion: getSdkVersion(pkg.sourceDir),
uploadedAt: new Date().toISOString(),
networks: network ? [network] : [],
};

// Sort for consistent ordering
const sorted: Record<string, DarsLockEntry> = {};
Object.keys(lock.packages)
.sort()
.forEach((k) => {
sorted[k] = lock.packages[k];
});
lock.packages = sorted;

lock.packages = Object.fromEntries(
Object.entries(lock.packages).sort(([left], [right]) => left.localeCompare(right))
);
saveDarsLock(lock);

console.log(`✅ Backed up: ${lockKey}`);
console.log(` SHA256: ${hash}`);
console.log(` Size: ${stats.size} bytes`);
console.log(`${plan.replace ? '✅ Backed up' : '✅ Verified'}: ${key}`);
console.log(` SHA256: ${sha256}`);
@HardlyDifficult HardlyDifficult marked this pull request as draft July 13, 2026 21:40

@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: e6b89790c6

ℹ️ 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 thread .github/workflows/ci.yml
run: npm run build

- name: Check DAR version policy
run: npm run check:dar-version-policy -- --base origin/main

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 Compare main pushes against the previous commit

When this workflow runs for a push to main, the preceding fetch step updates refs/remotes/origin/main to the commit that is currently being built (git fetch accepts the explicit main:refs/remotes/origin/main refspec). Passing that same ref as --base makes changedPackages() use HEAD as the merge base and see an empty diff, so direct main pushes can skip the DAR version policy entirely. Use github.event.before for main pushes or run the policy with --all in that context.

Useful? React with 👍 / 👎.

const mode = parseMode();
const lock = loadDarsLock();
const packages = mode.all ? getAllPackages() : changedPackages(mode.base!);
const baseLock = mode.base ? readLockAt(mode.base) : undefined;

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 Use the merge base for retention checks

changedPackages() diffs from the merge base, but this loads the retention baseline from the tip of --base. On a feature branch that was cut before main added a DAR backup, CI compares the branch's lock file against the newer origin/main lock and assertHistoryRetention() reports that main's new historical row was deleted even though the branch never touched it. Use the same merge-base commit for baseLock that is used for the path diff.

Useful? React with 👍 / 👎.

npm run build
npm run backup-dar -- --package "${{ steps.release_tag.outputs.package_key }}" --version "${{ steps.release_tag.outputs.version }}"
npm run test:dar-version-policy
npm run check:dar-version-policy -- --all

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 Avoid running the full migration audit during release

In the committed migration state, OpenCapTable-v34/daml.yaml is 0.0.3 while dars/dars.lock only marks 0.0.1 as deployed unless a dar-deploy/devnet/.../v0.0.2 tag already exists. Running --all here invokes the policy before any upload and requires the candidate to be 0.0.2, so a normal 0.0.3 release is blocked in that state. Gate only the release package against the release tag/tags, or require the migration tag before enabling this release step.

Useful? React with 👍 / 👎.

const entry = getLockEntry(lock, key);
const backup = path.join(getDarsDir(), key);
const built = path.join(ROOT, pkg.sourceDir, '.daml', 'dist', `${pkg.darName}-${pkg.version}.dar`);
if (!entry || !fs.existsSync(backup)) throw new Error(`${pkg.name}: current backup is missing (${key})`);

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 Verify historical DAR files, not just the current backup

When a branch deletes a non-current historical DAR file but leaves its dars.lock entry intact, changedPackages() selects the package, yet this check only verifies the current version's backup path and assertHistoryRetention() compares lock entries rather than files. That lets CI pass while removing a deployed artifact needed for audit or re-upload; check every retained lock entry's file/hash here or run verify-dars in the CI path.

Useful? React with 👍 / 👎.

@HardlyDifficult HardlyDifficult marked this pull request as ready for review July 13, 2026 22:29
@HardlyDifficult HardlyDifficult merged commit 2076b55 into main Jul 13, 2026
19 checks passed
@HardlyDifficult HardlyDifficult deleted the codex/tag-anchored-dar-versions branch July 13, 2026 22:30
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.

2 participants