Skip to content

feat(registry-cli): emdash-plugin.jsonc manifest, validate command, publisher pinning#1040

Merged
ascorbic merged 6 commits into
mainfrom
feat/registry-cli-manifest
May 14, 2026
Merged

feat(registry-cli): emdash-plugin.jsonc manifest, validate command, publisher pinning#1040
ascorbic merged 6 commits into
mainfrom
feat/registry-cli-manifest

Conversation

@ascorbic
Copy link
Copy Markdown
Collaborator

@ascorbic ascorbic commented May 14, 2026

What does this PR do?

Adds the emdash-plugin.jsonc manifest format and wires it into the existing emdash-registry CLI. Plugin authors can now declare profile fields (license, author, security contact, name, description, keywords, repo) once in a hand-edited JSONC file instead of passing them as flags on every publish. Adds emdash-registry validate for offline schema checking. Pins the publisher identity on first publish via a new publisher field, written back by the CLI as a DID with a // <handle> line comment for readability.

Foundation for the rest of the registry roadmap (issues #1029#1036). The init command from #1028 will land in a follow-up PR to keep this one reviewable.

This PR went through three rounds of adversarial review by a different model family (GPT-5.5). Each round surfaced real bugs (notably: write-back TOCTOU, duplicate-key shadowing, OOM via stat/readFile race, annotation targeting wrong line). All findings are addressed; see commits 2 and 3 for the fixes.

Closes part of #1028. Tracking Discussion: #296

Type of change

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (171 tests in @emdash-cms/registry-cli)
  • pnpm format has been run
  • I have added/updated tests for my changes
  • User-visible strings in the admin UI are wrapped for translation — N/A (CLI-only)
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion — tracked in parent issue Registry roadmap: fill the lexicon-to-UI gap #1026

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 4.7 (implementation), GPT-5.5 (adversarial review across 3 rounds)

Highlights

  • Zod schema for emdash-plugin.jsonc with v1 profile fields. Strict mode rejects unknown keys to catch typos.
  • JSONC loader with tsc-style file:line:column diagnostics. Bounded 1 MiB read (MANIFEST_TOO_LARGE) so a mistyped path can't OOM the CLI. Duplicate keys rejected with a source location.
  • emdash-registry validate for offline schema checking, suitable for pre-commit / CI.
  • publish reads the manifest from ./emdash-plugin.jsonc by default; explicit flags still win for CI override. --no-manifest opts out and warns if a manifest exists at the default path so the publisher-pin safety story stays visible.
  • publisher field pins the publishing identity. On first publish, the CLI writes the active session's DID back as publisher, with a // <handle> line comment. Subsequent publishes verify the active session matches the pinned publisher; mismatch is an immediate error (no override flag — the right fix is to switch sessions or update the manifest).
  • JSON Schema generated from the Zod source via z.toJSONSchema; ships in the package at schemas/emdash-plugin.schema.json for IDE completion. Drift caught by a snapshot test. (Known limitation: Zod's .refine() cross-field rules don't translate to JSON Schema, so editor validation is looser than the CLI's. To be tightened in a follow-up.)

Test output

Test Files  12 passed (12)
     Tests  171 passed (171)

ascorbic added 3 commits May 14, 2026 11:24
…ublisher pinning

Foundation for the registry roadmap. Adds the file format, loader, and
schema; no change to discovery or publish behaviour beyond now reading
the manifest by default.

- Zod schema for `emdash-plugin.jsonc` with v1 profile fields (license,
  author/authors, security/securityContacts, name, description, keywords,
  repo). Strict mode rejects unknown keys to catch typos.
- JSONC loader with `tsc`-style file:line:column diagnostics.
- `emdash-registry validate` for offline schema checking, suitable for
  pre-commit and CI.
- `publish` reads the manifest from `./emdash-plugin.jsonc` by default;
  explicit flags still win for CI overrides. `--no-manifest` opts out.
- New `publisher` field pins the publishing identity. On first publish,
  the CLI writes the active session's DID back as `publisher`, with a
  `// <handle>` line comment for readability. Subsequent publishes
  verify the active session matches the pinned publisher.
- JSON Schema generated from the Zod source via z.toJSONSchema; ships
  in the package at `schemas/emdash-plugin.schema.json` for IDE
  completion. Drift caught by a snapshot test.

Part of #1028 (the init command lands separately in a follow-up).
…rnings

Adversarial review findings:

- Annotation in writePublisherBack now anchors to the publisher property
  via the parse tree, not a raw DID string search. Previously, a
  description (or any other field) that contained the DID would have
  taken the // <handle> comment.
- Loader rejects manifests over 1 MiB (MANIFEST_TOO_LARGE) so a
  mistyped --manifest at a huge file fails fast instead of OOMing.
- Loader rejects duplicate JSONC keys with a source location, so a
  reviewer can't be fooled by an honest "publisher": "did:plc:X" at the
  top of the file that's silently overridden by a hostile value below.
- writePublisherBack hashes the original source, re-reads the file
  immediately before rename, and aborts if the bytes changed. Narrows
  the TOCTOU window between concurrent publishes / editor saves.
- publish accepts --manifest=false and --manifest= as opt-out
  shorthands, matching the help text.
- publish warns when --no-manifest is set and a manifest exists at the
  default path, so the publisher pin's safety story stays visible.

Cleanup:

- Removed the findUnwiredManifestFields helper and the in-product
  warnings about fields the schema accepts but publish doesn't yet
  consume. The release ships when the work is done; leaking
  intermediate state into the CLI is noise.

Tests: 170 passing.
…failure

Adversarial review round 3 findings:

- Replace stat+readFile with a bounded read primitive in both the
  loader and writePublisherBack. open() + a pre-allocated
  MANIFEST_MAX_BYTES+1 buffer means the file can't grow between
  syscalls to OOM the CLI. Returns a discriminated union so the type
  system enforces handling oversize/missing/ok cases.
- writePublisherBack's drift-check read now routes unexpected fs
  errors (EISDIR after file replacement, EACCES, etc.) through the
  same tmpfile-cleanup path as legitimate drift. Previously an
  unexpected throw would leak the tmpfile next to the manifest.
- Fix stale doc comments in load.ts (failure-modes list missing
  MANIFEST_TOO_LARGE and duplicate-key) and publish.ts (no longer
  warns about unwired fields).
- Replace lazy `await import("node:fs/promises")` with a top-of-file
  import; the dynamic form was carry-over from an earlier iteration.
- New test asserts MANIFEST_TOO_LARGE fires on an oversized file.
Copilot AI review requested due to automatic review settings May 14, 2026 10:55
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 14, 2026

🦋 Changeset detected

Latest commit: a763150

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

This PR includes changesets to release 1 package
Name Type
@emdash-cms/registry-cli Minor

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

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 14, 2026

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-perf-coordinator a763150 May 14 2026, 01:25 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 14, 2026

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-i18n a763150 May 14 2026, 01:25 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 14, 2026

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
docs a763150 May 14 2026, 01:26 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 14, 2026

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 a763150 May 14 2026, 01:27 PM

@github-actions
Copy link
Copy Markdown
Contributor

Scope check

This PR changes 2,806 lines across 19 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.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 14, 2026

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 a763150 May 14 2026, 01:27 PM

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 14, 2026

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@1040

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@1040

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@1040

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@1040

emdash

npm i https://pkg.pr.new/emdash@1040

create-emdash

npm i https://pkg.pr.new/create-emdash@1040

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@1040

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@1040

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@1040

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@1040

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@1040

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@1040

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@1040

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@1040

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@1040

commit: a763150

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an emdash-plugin.jsonc manifest format to @emdash-cms/registry-cli so plugin authors can declare profile fields once instead of repeating them as flags on every publish. Introduces a Zod-driven schema, JSONC loader with source-mapped diagnostics, an offline validate command, publisher-DID pinning with safe write-back, and a generated JSON Schema for IDE completion. This is the foundation PR (#1028) for the broader registry roadmap (#1026); subsequent PRs will wire the richer manifest fields through the publish payload (#1029) and beyond.

Changes:

  • New manifest/{schema,load,translate,publisher}.ts modules: strict-mode Zod schema, JSONC loader with tsc-style file:line:column diagnostics, bounded 1 MiB read, duplicate-key rejection, and atomic write-back of the session DID with TOCTOU narrowing.
  • New emdash-registry validate command and updated publish command that auto-loads ./emdash-plugin.jsonc, layers it under explicit flags, verifies pinned publisher matches active session, and warns when --no-manifest opts out while a manifest exists.
  • JSON Schema generation script (scripts/gen-schema.ts) emits schemas/emdash-plugin.schema.json, guarded by a snapshot drift test; jsonc-parser and zod added as runtime deps.

Reviewed changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pnpm-workspace.yaml Add infra/plugins-site workspace + jsonc-parser catalog entry; bump zod to ^4.4.1 (and remove its rationale comment).
pnpm-lock.yaml Lockfile updates for new catalog/workspace entries.
packages/registry-cli/src/manifest/schema.ts Zod schema + field-level exports for the manifest, with strict-mode and refine-based mutual-exclusivity rules.
packages/registry-cli/src/manifest/load.ts Bounded JSONC loader with parse-error and Zod-issue source mapping; duplicate-key detection.
packages/registry-cli/src/manifest/translate.ts Normalises single/multi author+contact forms and bridges to ProfileBootstrap.
packages/registry-cli/src/manifest/publisher.ts checkPublisher (DID/handle compare) + writePublisherBack with atomic rename, hash-based TOCTOU narrowing, and JSONC-aware annotation.
packages/registry-cli/src/commands/publish.ts Loads manifest, runs publisher check, layers manifest under flags, and triggers post-publish write-back.
packages/registry-cli/src/commands/validate.ts New offline validate command with human + --json output paths.
packages/registry-cli/src/index.ts Registers the validate subcommand.
packages/registry-cli/scripts/gen-schema.ts Generates the committed JSON Schema from the Zod source of truth.
packages/registry-cli/schemas/emdash-plugin.schema.json Committed JSON Schema, shipped for $schema-driven IDE completion.
packages/registry-cli/package.json Adds runtime deps (jsonc-parser, zod), gen-schema script, schemas/templates to files.
packages/registry-cli/README.md Documents the manifest format, publisher pinning behavior, and validate command.
packages/registry-cli/tests/* Coverage for schema, loader, translate, publisher check/write-back, and JSON Schema drift.
.changeset/busy-rivers-drive.md Minor changeset for @emdash-cms/registry-cli.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pnpm-workspace.yaml
typescript: ^5.9.3
vite: ^8.0.11
vitest: ^4.1.5
wrangler: ^4.83.0
Comment on lines +65 to +76
export function manifestToProfileBootstrap(manifest: NormalisedManifest): ProfileBootstrap {
const author = manifest.authors[0];
const security = manifest.securityContacts[0];

const profile: ProfileBootstrap = { license: manifest.license };
if (author?.name !== undefined) profile.authorName = author.name;
if (author?.url !== undefined) profile.authorUrl = author.url;
if (author?.email !== undefined) profile.authorEmail = author.email;
if (security?.email !== undefined) profile.securityEmail = security.email;
if (security?.url !== undefined) profile.securityUrl = security.url;
return profile;
}
Comment thread packages/registry-cli/README.md
Comment thread packages/registry-cli/package.json Outdated
- Restore the zod catalog-pin rationale comment that got dropped when
  pnpm add rewrote pnpm-workspace.yaml. The comment is still relevant
  after the 4.4.1 bump.
- Drop "templates" from the package files array. The directory lands
  with the init command in a follow-up PR; including a non-existent
  path causes pnpm pack warnings.
- Document in translate.ts that name/description/keywords/repo are
  intentionally deferred to a follow-up issue, not silently lost.
@ascorbic ascorbic enabled auto-merge (squash) May 14, 2026 11:56
@ascorbic ascorbic disabled auto-merge May 14, 2026 11:56
@ascorbic
Copy link
Copy Markdown
Collaborator Author

/review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

Adversarial review. One CI-blocking bug, plus a few smaller items.

The CRITICAL one: the schema-drift test is failing on this PR right now. The style: format commit (f565fa3) collapsed short JSON arrays in schemas/emdash-plugin.schema.json from multi-line to single-line, but the drift test compares byte-for-byte against JSON.stringify(..., "\t") which always emits multi-line arrays. pnpm test reports 1 failed | 82 passed in this package. The PR description's "171 passed" line is stale.

Reproduced locally: cd packages/registry-cli && pnpm test tests/schema-drift.test.ts fails with the exact diff shown in the comment on the schema file. This is a recurring flap: pnpm build regenerates the schema, then pnpm format collapses it again, then the drift test fails. Either the generator needs to emit oxfmt-canonical JSON, or the schema needs to live in .prettierignore/oxfmt's ignore list, or the drift test needs to compare structurally rather than byte-for-byte.

"$ref": "#/$defs/__schema18"
}
},
"required": ["license"],
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.

CRITICAL — CI-blocking, the PR's own drift test fails on this file.

This file was reformatted by pnpm format (commit f565fa3 style: format from emdashbot) after gen-schema produced it. Oxfmt collapsed every short array to a single line:

  • "required": ["license"] (here)
  • "examples": ["MIT", "Apache-2.0", "MIT OR Apache-2.0"]
  • "examples": ["did:plc:abc123def456", "example.com"]
  • "required": ["name"]
  • "examples": ["https://github.com/emdash-cms/plugin-gallery"]

But scripts/gen-schema.ts writes the file with JSON.stringify(document, null, "\t"), which always emits multi-line arrays. The drift test in tests/schema-drift.test.ts:51 compares byte-for-byte, so it now fails:

FAIL  tests/schema-drift.test.ts > JSON Schema drift > matches the output of z.toJSONSchema(ManifestSchema)
Error: schemas/emdash-plugin.schema.json is out of date with the Zod schema.
Run: pnpm --filter @emdash-cms/registry-cli gen-schema

This is a flap loop: pnpm build regenerates → pnpm format collapses → drift test fails → run gen-schema → format → repeat. The PR description claims 171 passed (171) but the package currently reports 1 failed | 82 passed.

Three options to fix:

  1. Add this path to oxfmt's ignore list (and document why).
  2. Make gen-schema.ts post-process the output to match oxfmt's array-collapsing rule.
  3. Change the drift test to parse + structurally compare instead of byte-for-byte.

Option 1 is the cleanest — the file is a generated artifact, formatting it serves no review purpose.

// of the object if `license` isn't present, which shouldn't
// happen for a schema-valid manifest but is handled defensively).
const edits = modify(source, ["publisher"], sessionDid, {
formattingOptions: { insertSpaces: false, tabSize: 1 },
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.

MEDIUM — modify will reformat the entire file when the user's source isn't tab-indented.

With formattingOptions: { insertSpaces: false, tabSize: 1 }, jsonc-parser.modify doesn't just insert the new property — when the surrounding source disagrees with the formatting options, it rewrites the affected region using the configured options. For a single-line compact manifest:

{ "license": "MIT", "author": { "name": "J" }, "security": { "email": "a@b.com" } }

the edit replaces bytes 0..83 with the entire tab-indented multi-line form. A user who deliberately authored a 2-space-indented or compact manifest will see their whole file silently restructured after first publish. The TOCTOU hash check still works, but the user-visible result is unexpected.

Not critical for v1, but worth either: documenting in the README that write-back forces tab indentation, OR detecting the existing indentation from the source (jsonc-parser doesn't expose this directly; you'd sniff the first indented line) and matching it.

// before we open any network connections to the PDS. The check runs
// here (rather than at manifest-load time) because it depends on
// session.did, which we only know now.
if (manifestLoad?.manifest.publisher !== undefined) {
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.

LOW (UX) — publisher mismatch check runs after a (potentially slow) tarball download.

The publisher check at lines 224–244 only needs manifestLoad.manifest.publisher and session.did, both available at line 218. But the tarball fetch at line 185 has already completed, plus tar decompression and manifest extraction.

For a publisher who's accidentally got the wrong session active, this means waiting for an entire MAX_TARBALL_BYTES download (up to 384 KB over slow links) before getting the immediate MANIFEST_PUBLISHER_MISMATCH they could have gotten in ~10ms. The check is offline (DID compare is verbatim; handle resolution only runs if the manifest pins a handle, which the README discourages).

Move the check to right after credentials.current() returns a session and before the fetchTarball call. Same logic, no extra round-trips, just runs earlier.

* are not silently lost — the manifest is the source of truth and we'll
* read them again when the publish API accepts them.
*/
export function manifestToProfileBootstrap(manifest: NormalisedManifest): ProfileBootstrap {
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.

LOW — schema accepts fields that this function silently discards.

manifestToProfileBootstrap only consumes license, the first author, and the first security contact. The manifest schema validates name, description, keywords, repo, and authors[2..N]/securityContacts[2..N], but they're not surfaced to publishRelease. The README example at packages/registry-cli/README.md:60-65 advertises name/description/keywords/repo as functional optional fields.

The JSDoc on line 66-70 calls this out ("land in publish in a follow-up issue"), but a user reading the README + writing a populated manifest gets no feedback that their keywords array was parsed-and-thrown-away. At minimum, emit a one-time info log from runPublish when the manifest carries fields v1 doesn't wire through, so a publisher who fills out the full example doesn't silently lose data on every publish until #1029 lands.

Same for multi-author manifests: authors[1..N] are silently dropped. The schema accepts up to 32; the bootstrap takes the first. Surface a warning, or limit the schema to length 1 until the bootstrap can carry the array.

* common for "required" issues: the field doesn't exist in the source,
* so we point at the parent object instead).
*/
function findNodeAtPath(root: Node, path: ReadonlyArray<string | number>): Node | undefined {
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.

LOW — findNodeAtPath returns the parent node when traversing into a non-object/non-array, but returns undefined when the child key doesn't exist.

For an unrecognized-key validation issue (e.g., user types "licens": "MIT"), Zod's path is ["licens"]. This function walks the root object, looks for property licens, doesn't find it, and current becomes undefined. On the next loop iteration if (!current) return undefined. So the issue gets no source location, and the user sees:

/path/manifest.jsonc: licens: unrecognized key

instead of /path/manifest.jsonc:3:2: licens: unrecognized key. Typos in field names are exactly the case the :line:column reporting was designed for, and they're the ones that lose the location.

Fix: when the final segment resolves to nothing, fall back to the parent object's offset. Or for unrecognized-keys specifically, the issue's keys array contains the offending key names — find them in the parent object's children directly.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 14, 2026

Review posted successfully (ID 4289727949). Summary of what I found:

CRITICAL: The schema-drift test is failing on the current PR HEAD. The style: format commit collapsed JSON arrays in schemas/emdash-plugin.schema.json to single lines, but the drift test compares byte-for-byte against JSON.stringify(..., "\t") output. This is a recurring flap loop — pnpm build regenerates → pnpm format collapses → test fails. The PR description's "171 tests passed" claim is stale; the package currently reports 1 failed | 82 passed.

MEDIUM: jsonc-parser.modify with forced tab indentation will silently reformat a user's entire manifest file on first publish if they authored it with 2-space indents or as a compact one-liner.

LOW: Publisher mismatch check runs after the tarball download instead of immediately after session resume — wastes bandwidth on the unhappy path.

LOW: manifestToProfileBootstrap silently drops name/description/keywords/repo and authors[1..N], even though the README advertises them and the schema validates them. No warning fires.

LOW: findNodeAtPath loses source-location info for unrecognized-key issues (typos like "licens"), exactly the case the file:line:column reporting was designed for.

github run

Five findings from the second-opinion review on PR #1040:

CRITICAL: schema-drift CI flap
- pnpm format collapsed short arrays in the generated JSON Schema
  (`required: ["license"]`, `examples: [...]`, etc.) into single-line
  form, but `JSON.stringify(_, null, "\t")` from gen-schema.ts always
  emits multi-line. The drift test failed on every PR after format ran,
  driving a regen-format-fail loop. Add the schemas dir to oxfmt's
  ignorePatterns — generated artifacts shouldn't be reformatted.

LOW: typo'd field names lose source location
- `findNodeAtPath` returned undefined when a path traversed into a
  missing key, so an unrecognized-key issue (the most common error)
  reported with no line:col. Now falls back to the parent node, and
  for `unrecognized_keys` issues specifically resolves the first
  offending key inside the parent object so the pointer lands on
  the actual typo's line+column instead of the parent's opening brace.

LOW: publisher mismatch only fires after tarball fetch
- Move the publisher-mismatch check ahead of fetchTarball so a
  wrong-account publish fails in ms rather than after a 384 KB
  download + decompress + manifest extract. The check is offline;
  it only needs the session DID and the manifest's publisher value,
  both available before any network access.

MEDIUM: writePublisherBack reformats non-tab manifests
- Hard-coding `insertSpaces: false, tabSize: 1` made jsonc-parser's
  modify() rewrite a 2-space-indented manifest with tabs on first
  publish. Sniff the source's indentation and match it.

LOW: README advertises fields publish silently discards
- Per maintainer direction (drop in-development warnings), this stays
  as a JSDoc note in translate.ts. README documents the target shape,
  not the current implementation. Already addressed in the previous
  commit.
@ascorbic ascorbic merged commit e6f7311 into main May 14, 2026
38 checks passed
@ascorbic ascorbic deleted the feat/registry-cli-manifest branch May 14, 2026 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants