Skip to content

Declare renderer ownership so the host enforces the scoping the package only applied at runtime - #51

Merged
unbraind merged 4 commits into
mainfrom
declare-renderer-ownership
Aug 4, 2026
Merged

Declare renderer ownership so the host enforces the scoping the package only applied at runtime#51
unbraind merged 4 commits into
mainfrom
declare-renderer-ownership

Conversation

@unbraind

@unbraind unbraind commented Aug 4, 2026

Copy link
Copy Markdown
Owner

What changed

pm-context registered its toon and json renderer overrides without the ownership argument:

api.registerRenderer("toon", renderCommandResult);
api.registerRenderer("json", renderCommandResult);

registerRenderer(format, renderer, ownership?) takes a third argument the host enforces before invoking the callback — commands (an unrelated command never reaches it) and resultDiscriminator (a false predicate preserves native rendering). This PR passes it:

const rendererOwnership = {
  commands: ["context-pack", "context-handoff", "context-usage"],
  resultDiscriminator: isRenderedCommandResult,
};
api.registerRenderer("toon", renderCommandResult, rendererOwnership);
api.registerRenderer("json", renderCommandResult, rendererOwnership);

The predicate is extracted once and used by both the ownership declaration and the callback, so the two can no longer disagree about what this package owns.

Why it matters — the failure this prevents

A renderer registered without ownership claims the whole format, globally. I reproduced the consequence on pm CLI 2026.8.3 with a probe extension: an unscoped json renderer replaced the entire output of pm health --json with the extension's own literal. Every --json command in the CLI was destroyed, silently.

pm-context was not exhibiting that — the callback already returned null for any result lacking its isRenderedCommandResult marker, which is correct and deliberate. But that scoping lived only in the callback body: invisible to the host, unenforceable, and one refactor away from the failure above. Declaring it moves the guarantee from "this package is careful" to "the host will not call us".

The runtime null return stays, as defence in depth. Ownership is enforced by the host; the null return is enforced by the package; neither should depend on the other.

pm-changelog already did this and is the pattern followed here.

The risk this change carries, and how it is covered

The failure mode of declaring commands is a renderer that silently stops rendering — output reverts to native and nothing errors. The command paths were therefore derived by reading this package's own registerCommand calls rather than assumed, and 4 tests assert both directions: the renderer still renders its own marked result, it declines a foreign one, and the registered ownership matches what the source declares.

Tests use the SDK harness (createExtensionTestHarness) — no hand-rolled api double, no activate(api as any).

Verification

  • npm run typecheck — clean.
  • npm run coveragethresholds met.
  • npm run changelog:check — up to date.

pm item

  • pm-context-lhse — in_progress, with a note recording the derived command paths and where they were read from. Committed with its sibling history/pm-context-lhse.jsonl.

What this does not do

It does not clear the extension_renderer_collision warnings in pm health. The host's collision check groups renderer overrides by format alone and ignores the ownership it enforces, so a correctly scoped ecosystem still reports ok: false — filed upstream as unbraind/pm-cli#897 with the reproduction. No registration was deleted to silence a warning; the registrations are correct. This change is what makes the fleet already-correct on the day that upstream bug is fixed.


Summary by cubic

Declare renderer ownership for toon and json so the host enforces scoping to pm-context commands and the result marker before the renderer runs. Prevents global format overrides while keeping the null return as a fallback.

  • Bug Fixes
    • Passed ownership to registerRenderer with commands context-pack, context-handoff, context-usage and a resultDiscriminator using isRenderedCommandResult.
    • Extracted isRenderedCommandResult and reused it in both the ownership registration and the renderer callback.
    • Tests cover: ownership registration for both formats; marked results render; isolated decline cases (foreign result on owned command, marked result on foreign command, both foreign); discriminator accepts our marker and rejects foreign/bare; and that the ownership list matches the extension’s registered commands to catch future drift.

Written for commit 7b9ecc3. Summary will update on new commits.

Review in cubic

@sourcery-ai sourcery-ai 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.

Sorry @unbraind, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • New Features

    • Improved command result rendering for Toon and JSON formats.
    • Added ownership handling for supported context commands.
    • Added clear discrimination between supported and unrelated command results.
  • Bug Fixes

    • Prevented foreign command results from being rendered by the wrong renderer.
    • Preserved native rendering behavior for supported results.
  • Tests

    • Added coverage validating renderer registration, ownership, result handling, and rejection behavior.

Walkthrough

The PR adds renderer ownership metadata for three context commands. It introduces isRenderedCommandResult and uses it for marked-result rendering. Tests cover registration, owned results, foreign results, and discriminator behavior.

Changes

Renderer ownership

Layer / File(s) Summary
Ownership contract and implementation
.agents/pm/tasks/pm-context-lhse.toon, .agents/pm/history/pm-context-lhse.jsonl, index.ts
The task records define ownership for three commands. isRenderedCommandResult validates marked results. Toon and JSON renderers register ownership and use the discriminator.
Ownership validation
test/renderer-ownership.test.ts
Tests verify renderer registration, owned-result rendering, foreign-result rejection, native rendering preservation, and direct discriminator behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title clearly describes declaring renderer ownership so the host enforces renderer scoping.
Description check ✅ Passed The description directly explains the renderer ownership change, its purpose, implementation, risks, and test coverage.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch declare-renderer-ownership

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.

@sourcery-ai

sourcery-ai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR scopes pm-context’s toon/json renderer overrides to the extension’s own commands by declaring renderer ownership with a shared result discriminator, updates the renderer implementation to use that discriminator, adds tests validating ownership and behavior, and records the change in pm agent task/history files.

Sequence diagram for scoped renderer ownership and shared discriminator

sequenceDiagram
  actor User
  participant ExtensionHost
  participant pm_context_extension

  User->>ExtensionHost: run context-pack --json
  ExtensionHost->>pm_context_extension: activate(api)
  pm_context_extension->>ExtensionHost: api.registerRenderer("json", renderCommandResult, rendererOwnership)
  pm_context_extension->>ExtensionHost: api.registerRenderer("toon", renderCommandResult, rendererOwnership)

  User->>ExtensionHost: run context-pack --json
  alt [command in rendererOwnership.commands]
    ExtensionHost->>pm_context_extension: renderCommandResult(context)
    pm_context_extension->>pm_context_extension: isRenderedCommandResult(context.result)
    alt [isRenderedCommandResult(context.result)]
      pm_context_extension-->>ExtensionHost: output
    else [not isRenderedCommandResult(context.result)]
      pm_context_extension-->>ExtensionHost: null
    end
  else [command not in rendererOwnership.commands]
    ExtensionHost->>ExtensionHost: [native format renderer]
  end
Loading

File-Level Changes

Change Details Files
Introduce a shared type guard for pm-context’s rendered command results and reuse it in the renderer callback.
  • Add isRenderedCommandResult(value) type guard to validate pmContextRendered marker and output string.
  • Refactor renderCommandResult to call isRenderedCommandResult on context.result and return its output or null accordingly.
  • Keep runtime null defensive behavior while centralizing the definition of what counts as a pm-context rendered result.
index.ts
dist/index.js
Declare explicit renderer ownership for pm-context toon/json formats so the host enforces scoping to this extension’s commands.
  • Define rendererOwnership with commands ["context-pack","context-handoff","context-usage"] and resultDiscriminator: isRenderedCommandResult.
  • Pass rendererOwnership as the third argument when registering toon and json renderers via api.registerRenderer.
  • Ensure built artifacts mirror the TypeScript source changes for ownership declaration and discriminator wiring.
index.ts
dist/index.js
dist/index.d.ts.map
dist/index.js.map
Add tests that verify renderer ownership registration, override behavior for owned vs foreign results, and consistency of the resultDiscriminator.
  • Use createExtensionTestHarness with required capabilities to activate the extension under realistic host conditions.
  • Assert toon/json overrides register OWNED_COMMANDS and expose a resultDiscriminator function.
  • Test that the renderer claims and renders marked pm-context results for both formats and leaves foreign results to native rendering.
  • Test that resultDiscriminator accepts correctly marked results, rejects foreign markers, and rejects bare objects.
test/renderer-ownership.test.ts
Record the ownership/scoping change in pm agent task/history artifacts.
  • Add pm-context-lhse.toon task file describing the feature and derived command paths.
  • Add pm-context-lhse.jsonl history entry corresponding to the task.
.agents/pm/history/pm-context-lhse.jsonl
.agents/pm/tasks/pm-context-lhse.toon

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@greptile-apps

greptile-apps Bot commented Aug 4, 2026

Copy link
Copy Markdown

Greptile Summary

The PR scopes the toon and json renderer overrides to pm-context commands and marked results while retaining callback-level validation.

  • Extracts a shared result discriminator used by both host ownership filtering and renderer fallback validation.
  • Declares ownership for context-pack, context-handoff, and context-usage.
  • Adds harness tests covering accepted and declined command/result combinations.
  • Rebuilds distributed artifacts and records the change in project metadata and the changelog.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
index.ts Extracts the marked-result predicate and applies shared command/result ownership to both renderer registrations.
test/renderer-ownership.test.ts Exercises ownership registration, accepted marked results, independent command and result rejection, and parity with registered commands.
dist/index.js Rebuilds the distributed runtime artifact with the source ownership changes.
CHANGELOG.md Adds an Unreleased entry describing renderer ownership enforcement.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    A[Command result] --> B{Owned command?}
    B -- No --> N[Native rendering]
    B -- Yes --> C{Marked pm-context result?}
    C -- No --> N
    C -- Yes --> R[pm-context renderer]
    R --> D{Callback validation}
    D -- Valid --> O[Rendered output]
    D -- Invalid --> N
Loading

Reviews (6): Last reviewed commit: "Guard the renderer ownership list agains..." | Re-trigger Greptile

@unbraind

unbraind commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@sourcery-ai review

@greptileai review

Reviewers: the risk in this change is not the ownership object — it is the command list. Declaring commands means the host stops calling this renderer for anything not on the list, and the failure is silent: output reverts to native rendering and nothing errors.

So the finding I most want is a command that can produce this package's rendered-result marker but is missing from the declared list. The paths were derived by reading this package's own registerCommand calls rather than assumed, and the tests assert both directions, but a list is exactly the kind of thing that is right today and stale after the next command is added.

Second, if you can see a way for the extracted result predicate and the callback to disagree about ownership now that they share one definition, that is worth flagging too — keeping them from diverging is half the point of the extraction.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Rate Limit Exceeded

@unbraind have exceeded the limit for the number of chat messages per hour. Please wait 15 minutes and 15 seconds before sending another message.

…oping

The pm-context renderer overrides self-scoped by checking the private
pmContextRendered marker and returning null for foreign results. That is
correct but invisible to the host: an unscoped renderer claims the whole
format globally, and one refactor away from the marker check could replace
every --json command's output in the CLI.

Passing a shared ownership object to both the toon and json registerRenderer
calls makes the host enforce the command paths (context-pack, context-handoff,
context-usage) and the result discriminator before the callback ever runs.
The runtime null return stays as defence in depth — ownership is enforced by
the host, the null return by the package, and neither depends on the other.

The resultDiscriminator reuses the same isRenderedCommandResult predicate the
callback applies internally, extracted to one definition rather than
duplicated. A new test asserts the renderer still renders its own marked
result, declines a foreign one, and that the registered commands and
discriminator match the source.
The decline test passed a context foreign in both dimensions at once — an
unowned command and a result that fails the discriminator — so a single
assertion over both could not tell which filter did the work. Dropping the
commands declaration entirely left the suite green, because the
resultDiscriminator still rejected the foreign result and the unowned
command was never exercised independently.

Replace it with three cases per format, each isolating one axis:

1. Result rejection with ownership matching — the command is one pm-context
   owns (context-pack), the result is foreign (another package's marker) or
   bare ({ output: "x" }). The commands filter passes; resultDiscriminator
   must decline. Fails if the discriminator regresses.

2. Command rejection with the result matching — the command is one pm-context
   does not own, the result carries the pmContextRendered marker. The host's
   commands filter must decline before the renderer runs. This is the case
   that protects the ownership boundary: it fails if the commands declaration
   is dropped, because then resultDiscriminator alone would let the renderer
   claim a marked result emitted under a foreign command path.

3. Both foreign — the original belt-and-braces check, kept as a third case.

Each case asserts overridden=false, rendered=null, and no warnings for both
toon and json. The harness (createExtensionTestHarness / runRendererOverride)
honours the command field on RendererOverrideContext, so the command path is
driven through pm's real renderer dispatch — no hand-rolled api double.

Closes the implementation tracker pm-context-lhse with full closure metadata
(resolution, expected_result, actual_result) and regenerates the changelog so
the entry lands in the Unreleased section.
CI failed `changelog:check` on all four renderer-ownership branches while
the same check passed locally. The cause was not the changelog and not
stale tags: the daily release bumped every package to 2026.8.4 and this
branch was still based on the commit before it, so the generator ran
against 2026.8.3 locally and 2026.8.4 in CI.

Under 2026.8.3 the freshly closed item grouped into the `## 2026.8.4`
heading, because that version had not been released yet from the branch's
point of view. Under 2026.8.4 it belongs in `## Unreleased`, above the
released section. Both outputs are correct for the version they were
generated against, which is exactly why the local check could not see it.

Rebased onto origin/main and regenerated with the package's own
`changelog:check` command minus `--check`, so the generation mode cannot
drift from the mode the gate compares against.
@unbraind
unbraind force-pushed the declare-renderer-ownership branch from 5d19eb9 to 4f2ea04 Compare August 4, 2026 22:27
@unbraind

unbraind commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Round addressed — split tests, item closed, and a mid-PR release rebased

The test split (CodeRabbit + Greptile, raised independently)

The decline test passed a context foreign in both dimensions at once, so a green result proved only that at least one ownership filter fired. Three cases now isolate each axis:

case command result proves
decline on owned command owned unmarked resultDiscriminator rejects after commands matched
decline on foreign command unowned marked commands rejects — the boundary this PR introduces
both foreign unowned unmarked belt-and-braces (the original)

Verified by mutation, not by inspection. Deleting the commands array from the ownership object makes exactly one test fail:

✖ declines its own marked result on a foreign command (commands ownership rejects)

…and the rest stay green. The declaration is now pinned by a test that notices its absence, which the combined assertion never was.

This is meaningful because the harness enforces the real thing: runRegisteredRendererOverrideForTest delegates to runRendererOverride, which calls rendererOwnsContext(entry, normalizeCommandName(context.command), context.result) — the host's own path, not a stand-in.

Tracker closed in the implementing PR

The item is closed with closure metadata and the changelog regenerated after the close. Ordering matters: pm-changelog reads item state at generation time, so regenerating first leaves an entry describing finished work as unfinished — and changelog:check would pass on a description that is wrong rather than stale. The gate cannot catch that.

Why CI went red, and what it was actually telling us

changelog:check failed in CI while passing locally. It was not the changelog and not stale tags: the daily release bumped this package to 2026.8.4 mid-PR and the branch was still based on the commit before it. Generated against 2026.8.3 the freshly closed item groups under the ## 2026.8.4 heading; against 2026.8.4 it belongs under ## Unreleased. Both outputs are correct for the version they were generated against — which is precisely why the local check could not see it.

Fixed by rebasing onto origin/main and regenerating with the package's own changelog:check command minus --check, so the generation mode cannot drift from the mode the gate compares against.

Verification

typecheck clean · coverage thresholds met (the added tests raised coverage; no threshold was lowered) · changelog:check up to date · every .toon committed with its sibling history/*.jsonl.


@coderabbitai full review

@sourcery-ai review

@greptileai review

Re-review focus: the command list, still. The tests now prove the declared commands are enforced, but they cannot prove the list is complete — a command that emits this package's marked result and is missing from commands would silently lose its rendering, and every test would stay green. If you can spot one, that is the finding worth having.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Changed files and PR diff summary ---'
git diff --stat origin/main...HEAD
git diff --unified=40 origin/main...HEAD -- index.ts test/renderer-ownership.test.ts

printf '%s\n' '--- AST outline: index.ts ---'
ast-grep outline index.ts --items all --view expanded

printf '%s\n' '--- AST outline: renderer ownership test ---'
ast-grep outline test/renderer-ownership.test.ts --items all --view expanded

printf '%s\n' '--- Command registrations and rendered-result references ---'
rg -n -C 5 -P '\bregisterCommand\s*\(|\bisRenderedCommandResult\b|rendered\s*:\s*true|renderedOutput|renderCommandResult\s*\(' \
  -g '*.ts' -g '*.tsx' -g '*.mts' -g '*.cts' .

printf '%s\n' '--- Command-name literals near registration and result construction ---'
rg -n -C 8 -P "['\"][a-z][a-z0-9-]*['\"]" -g '*.ts' -g '*.tsx' -g '*.mts' -g '*.cts' .

Length of output: 205782


@unbraind Full review completed. I found no actionable issue.

The command list is complete in the current source:

  • context-pack can return renderedCommandResult(...).
  • context-handoff can return renderedCommandResult(...).
  • context-usage can return renderedCommandResult(...).
  • The ownership object lists exactly these three commands.

I found no other pmContextRendered: true producer or renderedCommandResult(...) call in the TypeScript source.

The predicate and callback also cannot disagree in the current implementation. renderCommandResult calls isRenderedCommandResult, and the ownership object registers that same function as resultDiscriminator.

The split tests correctly isolate the host command filter from the result discriminator.


✏️ Learnings added
Learnt from: unbraind
URL: https://github.com/unbraind/pm-context/pull/51

Timestamp: 2026-08-04T22:30:18.543Z
Learning: In `pm-context/index.ts`, every command that can return `renderedCommandResult(...)` must appear in the renderer ownership `commands` list. The current command set is `context-pack`, `context-handoff`, and `context-usage`. The host enforces this list before it invokes the renderer.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 18 minutes.

CodeRabbit raised this on pm-ops as a future-regression gap and it applies
to every package that declares renderer ownership.

The ownership list is right the day it is written and wrong the day a
command is added. The new command registers, returns a rendered result,
and silently falls back to native rendering because the host declines it.
Nothing fails: not the build, not the types, and not an ownership test
whose expected list carries the same omission -- which is exactly the
shape of the test this branch already added.

So the list is now checked against what the extension actually registers.
`renderer ownership covers every command the extension registers` compares
the declared `commands` against `activation.registrations.commands`, which
is the only assertion that notices a command the ownership list forgot.
The test keeps its own independent literal rather than importing the
source's, because a test that restates the source's list shares its
mistakes and proves nothing.

On pm-ops the declaration itself now derives from `OPS_COMMAND_PATHS`, the
list every ops command already registers its parser through, making the
duplication impossible rather than merely guarded.
@unbraind

unbraind commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Drift guard added — the finding you raised on pm-ops applies here too

@coderabbitai — you cleared the command list as complete today and then flagged the thing that actually matters: the list is right the day it is written and wrong the day a command is added. A new command registers, returns a marked result, and silently falls back to native rendering because the host declines it. Nothing fails — not the build, not the types, and not the ownership test, whose expected list carries the same omission. That last part is the sharp end, because the test this branch added earlier in the round has exactly that shape.

You raised it on pm-ops; it is a property of the pattern, so it is now guarded in every package that declares ownership:

test("renderer ownership covers every command the extension registers", async () => {
  const registered = ext.activation.registrations.commands.map((e) => e.command).sort(...);
  assert.deepEqual(registered, [...OWNED_COMMANDS].sort(...));
});

Comparing against activation.registrations.commands is the only assertion that notices a command the ownership list forgot, because it reads what the extension actually registered rather than what someone remembered to write down twice.

One deliberate deviation from your suggestion. You proposed the test use the shared list directly. I kept the test's literal independent instead: a test that restates the source's list shares its mistakes and proves nothing — the exact failure mode you identified, relocated. The source-side deduplication you asked for is done where it belongs, in the declaration: on pm-ops commands: [...OPS_COMMAND_PATHS], pointing at the list every ops command already registers its parser through, so the duplication is impossible rather than merely guarded.

Not applied to pm-starter, and worth saying why rather than leaving it looking like an oversight: only starter demo of its nine commands emits the marker, so ownership is correctly a strict subset of the registered set and an equality assertion would be wrong there. You verified that subset independently this round.

npm test86 pass, 0 fail · coverage thresholds met · changelog:check up to date.

@sourcery-ai @greptileai — re-review welcome on the guard itself.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- PR diff for relevant source and tests ---'
git diff --unified=50 origin/main...HEAD -- index.ts test/renderer-ownership.test.ts

printf '%s\n' '--- Structure ---'
ast-grep outline index.ts --items all --view expanded
ast-grep outline test/renderer-ownership.test.ts --items all --view expanded

printf '%s\n' '--- Command registration, ownership declaration, and marked-result production ---'
rg -n -C 6 -P '\bregisterCommand\s*\(|\bcommands\s*:|\bOWNED_COMMANDS\b|\brenderedCommandResult\s*\(|\bpmContextRendered\b|\bisRenderedCommandResult\b' \
  -g '*.ts' -g '*.tsx' -g '*.mts' -g '*.cts' .

printf '%s\n' '--- Test harness ownership path ---'
rg -n -C 6 -P '\brunRegisteredRendererOverrideForTest\s*\(|\brunRendererOverride\s*\(|\brendererOwnsContext\s*\(' \
  -g '*.ts' -g '*.tsx' -g '*.mts' -g '*.cts' .

Length of output: 41135


@unbraind No actionable issue found.

The guard has the required independent comparison:

  • The activation harness provides the actual registered command set.
  • OWNED_COMMANDS remains independent from rendererOwnership.commands.
  • The new equality assertion fails if a new command is registered but omitted from renderer ownership.
  • The existing renderer-registration assertion fails if the ownership declaration contains an unregistered command.

Equality is valid for pm-context now. context-pack, context-handoff, and context-usage each have a path that returns renderedCommandResult(...).

The renderer callback and resultDiscriminator still use the same isRenderedCommandResult definition. They cannot diverge without changing that shared definition.

I did not execute the test suite in the inspection sandbox. The source review is consistent with the reported test result.

🐇 ✓


✏️ Learnings added
Learnt from: unbraind
URL: https://github.com/unbraind/pm-context/pull/51

Timestamp: 2026-08-04T22:42:54.623Z
Learning: In `pm-context/test/renderer-ownership.test.ts`, protect renderer ownership against command-list drift by comparing `ext.activation.registrations.commands` with an independent expected command list. Do not derive the test expectation from `rendererOwnership.commands`, because a new registered command that is omitted from both lists would otherwise silently lose renderer handling without failing the test. Equality is valid only when every registered command can emit `renderedCommandResult(...)`; use a subset assertion when renderer ownership intentionally covers only some commands.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@unbraind

unbraind commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Merging — round closed

All threads answered, voted and resolved; every check green; mergeStateStatus: CLEAN.

What the round produced

@coderabbitai and @greptileai independently identified the same weak assertion from opposite directions — the result discriminator after commands match, and command rejection with a matching result. Two reviewers converging on one weak test was the strongest signal of the round, and the fix was verified by mutation rather than inspection: deleting the commands array makes exactly the intended test fail and leaves the rest green.

@coderabbitai then cleared the command list as complete and flagged the thing that outlives this PR — a literal list is right today and stale after the next command is added, and an ownership test that restates the same omission stays green. That is now guarded against activation.registrations.commands.

What this change does and does not buy

It makes scoping host-enforced rather than merely conventional. The runtime null return stays as defence in depth — ownership is enforced by the host, the null return by the package, and neither depends on the other.

It does not clear the extension_renderer_collision warnings in pm health. The host groups renderer overrides by format alone and ignores the ownership it enforces, so a correctly scoped ecosystem still reports ok: false — filed upstream as unbraind/pm-cli#897 with a reproduction showing that an unscoped renderer, which destroys the output of every --json command in the CLI, emits a byte-identical warning to a correctly scoped one. No registration was deleted to silence anything. This work is what makes the fleet already-correct on the day that upstream bug is fixed.

Functionally verified end to end with the globally installed pm CLI, not only the harness: the owned command renders its pack, and pm list --json on the same workspace returns native JSON with no leakage from the extension.

@unbraind
unbraind merged commit 4bb2af5 into main Aug 4, 2026
9 checks passed
@unbraind
unbraind deleted the declare-renderer-ownership branch August 4, 2026 22:45
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@unbraind Acknowledged. The renderer ownership guard now covers both current correctness and command-list drift.

The end-to-end check confirms the required boundary: owned commands use the extension renderer, and pm list --json retains native JSON output.

The remaining extension_renderer_collision warning is an upstream diagnostic limitation. It does not indicate that this extension has unsafe renderer scope. The linked upstream issue, unbraind/pm-cli#897, is the correct follow-up location.

🐇 ✓


✏️ Learnings added
Learnt from: unbraind
URL: https://github.com/unbraind/pm-context/pull/51

Timestamp: 2026-08-04T22:45:58.614Z
Learning: For `pm-context` renderer ownership, `extension_renderer_collision` warnings from `pm health` can remain after correct host-enforced ownership is configured. The host diagnostic currently groups renderer overrides only by output format and does not account for ownership scoping. Do not remove valid renderer registrations merely to suppress this warning; track the diagnostic defect upstream in unbraind/pm-cli#897.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@greptile-apps

greptile-apps Bot commented Aug 4, 2026

Copy link
Copy Markdown

Clean round. The mutation-verified fix for the weak discriminator assertion, and the drift guard against activation.registrations.commands replacing the literal list, are exactly the right outcomes. Nothing left to block on from my side — ship it.

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