Skip to content

feat(plugin-cli): add profile policy command#1925

Merged
ascorbic merged 2 commits into
feat/delegated-release-servicefrom
feat/delegated-release-service-05-profile-policy
Jul 10, 2026
Merged

feat(plugin-cli): add profile policy command#1925
ascorbic merged 2 commits into
feat/delegated-release-servicefrom
feat/delegated-release-service-05-profile-policy

Conversation

@ascorbic

@ascorbic ascorbic commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds emdash-plugin policy set for interactively editing an existing package's signed release policy using the publisher's local OAuth session.

It supports provenance policy, confirmation mode, approver replacement/clearing, canonical HTTPS repository anchors, dry-run, JSON, and CID CAS while preserving unrelated profile and extension data.

Related to #1908 and #1918.

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: RFC: Attested Automated Publishing #1590

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: OpenCode with GPT-5.6-terra

Screenshots / test output

  • pnpm build passes with existing warnings
  • Plugin CLI suite: 424 tests passed
  • Plugin CLI typecheck and full lint pass
  • Formatting and git diff --check pass

Try this PR

Open a fresh playground →

A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.

Tracks feat/delegated-release-service-05-profile-policy. Updated automatically when the playground redeploys.

@changeset-bot

changeset-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5c3d9d2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@emdash-cms/plugin-cli Minor
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 922 lines across 6 files. Large PRs are harder to review and more likely to be closed without review.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@github-actions github-actions Bot added size/XL review/needs-review No maintainer or bot review yet cla: signed labels Jul 10, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 10, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 5c3d9d2 Jul 10 2026, 05:14 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 10, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 5c3d9d2 Jul 10 2026, 05:12 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 10, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-do 5c3d9d2 Jul 10 2026, 05:12 PM

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This PR adds a sensible, narrowly-scoped piece of the delegated-release work: a policy set command that edits the release-policy fields inside an existing package profile's com.emdashcms.experimental.package.profileExtension extension. It mirrors the architecture of the existing update-package command (API split from CLI wrapper, dry-run by default, atproto CID-based CAS, mock-PDS tests) and uses the right lexicon layer.

I checked the diff, the new command/API files, the sibling update-package and publish commands, the registry lexicons, the publishing client, and the new tests. The implementation is mostly careful about validation, preserving unknown profile/extension fields, and stale-record handling. The test suite covers the main success, idempotency, validation, and CAS paths.

I found two real issues in the JSON-mode contract that should be fixed before this ships, plus one style/consistency note:

  1. Unstable JSON diff envelope: formatPolicyJsonResult returns result.diffs with undefined before/after values, so JSON.stringify omits those keys. Diff objects therefore have an unpredictable shape. The sibling update-package command normalises undefined to null so every diff has both keys.
  2. JSON errors drop ProfilePolicyError.detail: expectedCid for STALE_RECORD, validation issues for LEXICON_VALIDATION_FAILED, etc. are lost because the catch block only extracts code and message. update-package preserves detail in its JSON error envelope.
  3. CliError shape diverges from publish.ts/update-package.ts: it lacks the exitCode parameter, so every failure exits 1 and the constructor signature is inconsistent across the package's commands.

Otherwise this looks good to merge once #1 and #2 are addressed.

profile: result.profileUri,
written: result.written,
applied,
diffs: result.diffs,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[needs fixing] The JSON success envelope returns diffs: result.diffs unchanged. When a field was previously unset, before is undefined, which JSON.stringify omits, so diff objects have an unstable shape (some entries have before, others do not). The PR advertises stable JSON output; the sibling update-package command avoids this by normalising undefined to null for both sides.

Suggested change
diffs: result.diffs,
diffs: result.diffs.map((d) => ({
field: d.field,
before: d.before ?? null,
after: d.after ?? null,
})),

: "INTERNAL_ERROR";
const message = error instanceof Error ? error.message : String(error);
consola.error(message);
if (json) process.stdout.write(`${JSON.stringify(formatPolicyJsonError(code, message))}\n`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[needs fixing] The top-level catch block calls formatPolicyJsonError(code, message) and discards the detail object carried by ProfilePolicyError — e.g., expectedCid for STALE_RECORD and validation results for LEXICON_VALIDATION_FAILED. update-package preserves detail in its JSON error envelope, and callers scripting this command will want that context.

Capture the detail at the call site:

Suggested change
if (json) process.stdout.write(`${JSON.stringify(formatPolicyJsonError(code, message))}\n`);
const detail = error instanceof ProfilePolicyError ? error.detail : undefined;
const message = error instanceof Error ? error.message : String(error);
consola.error(message);
if (json) process.stdout.write(`${JSON.stringify(formatPolicyJsonError(code, message, detail))}\n`);

And update the formatter at line 172 to accept it:

Suggested change
if (json) process.stdout.write(`${JSON.stringify(formatPolicyJsonError(code, message))}\n`);
export function formatPolicyJsonError(code: string, message: string, detail?: Record<string, unknown>) {
const body: Record<string, unknown> = { code, message };
if (detail !== undefined) body.detail = detail;
return { error: body };
}

return typeof value === "string" ? [value] : undefined;
}

class CliError extends Error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[suggestion] CliError here is new CliError(message, code), but the equivalent classes in publish.ts and update-package.ts are new CliError(message, exitCode, code). That lets those commands return 2 for usage/flag errors and 1 for runtime failures. Aligning the constructor (and passing 2 for INVALID_POLICY_FLAGS, 1 for auth/publisher/manifest errors) would keep the CLI's error handling consistent and make scripts more reliable.

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Jul 10, 2026
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Jul 10, 2026
@ascorbic ascorbic merged commit e819a5f into feat/delegated-release-service Jul 10, 2026
8 checks passed
@ascorbic ascorbic deleted the feat/delegated-release-service-05-profile-policy branch July 10, 2026 18:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: signed review/needs-rereview Author pushed changes since the last review size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant