Skip to content

docs: add capabilities query recipes - #104

Merged
Kiran01bm merged 3 commits into
mainfrom
kiran01bm/cm5-capabilities-recipes
Sep 11, 2026
Merged

docs: add capabilities query recipes#104
Kiran01bm merged 3 commits into
mainfrom
kiran01bm/cm5-capabilities-recipes

Conversation

@Kiran01bm

Copy link
Copy Markdown
Collaborator

Document how people and automation can query the embedded capabilities matrix and route its results with jq.

Why

The support page explains the matrix, but consumers cannot discover the matching CLI JSON contract or ready-to-use filters from that page or the README.

What

  • Add the three contract query recipes to the capabilities page.
  • Point README readers to the table and JSON forms of the capabilities command.
  • Mark the consumer documentation step complete in the contract.

Before / after

Before

+--------+     +----------------------+
| reader | --> | capabilities.md only |
+--------+     +----------------------+

After

+-------------+     +----------------+     +----------------------------------+     +------------+
| YAML source | --> | generated page | <-> | pg-sprite capabilities --json   | --> | jq recipes |
+-------------+     +----------------+     +----------------------------------+     +------------+
                            ^                            ^
                            |                            |
                            +-------- docs point at both+

Help people and automation query the same embedded support matrix that
generates the documentation, without scraping Markdown or connecting to a
database.
@Kiran01bm
Kiran01bm marked this pull request as ready for review September 11, 2026 01:15
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@aparajon

Copy link
Copy Markdown
Collaborator

🤖 1/2 — the recipes. Where they came from, the rollout marker, and the README claims are in 2/2.

Reviewed 2c2dfc6..dd67034 — three files, +24/−1. A docs PR that publishes executable commands can be tested rather than read, so I built the binary at this head, ran every recipe against the real embedded matrix, and then mutated the contract underneath them to find out what would catch one going stale.

All three work, and they are better defended than they look.

Recipe Returns Of 53 rows
select(.tier == "t2") 19 t1 18 / t2 19 / t3 16
select(.front_doors.diff == "refused") 35 the other 18 are supported; no row omits the key
select(.owning_tool_class != null) 14 39 rows omit it entirely

Recipe 3 leans on a jq subtlety that happens to be right: owning_tool_class is tagged omitempty (capabilities.go:78), so an absent field reads as null and != null selects exactly the rows naming an owner. If that tag ever loses omitempty the recipe silently inverts to all 53 rows — the one failure mode here that returns a plausible answer rather than an empty one.

Mutation testing — I went looking for a rename that breaks a recipe with the suite still green, and did not find one:

Mutation Result
json:"owning_tool_class"owner_tool_class caught — TestContractDocExampleRowMatchesRegistry
top-level json:"capabilities"rows caught — TestCLIOutputExamplesCapabilitiesHeadMatchesCommand
TierTwo value "t2""planned" caught — 8 tests, starting at the YAML validator (TestEmbeddedCapabilitiesValidate)

Every field path and literal these recipes hard-code is anchored somewhere: the contract's example row (capabilities-contract.md:124-141) happens to be a T3 row carrying owning_tool_class, front_doors.diff: "refused" and a tier string, and capabilities.yaml plus its validator pin the enum values. The recipes inherit real protection from #99's tests rather than needing their own — worth saying, because it is not visible in the diff and it is why I am not asking for a test on the recipes themselves.


1. Recipe 2 does not distinguish the declarative door, and reads as though it does

front_doors.migrate and front_doors.diff are equal on all 53 rows — checked pairwise, not just as distributions, and the text form shows it too: every row prints m:yes d:yes or m:ref d:ref. So select(.front_doors.diff == "refused") returns exactly the 35 non-T1 rows, the same set as select(.tier != "t1").

That makes the recipe correct and its comment — "Everything the declarative door refuses" — misleading in a specific way. A consumer reaching for a per-door query is reaching for the one thing the schema models and the data does not yet exercise, and running it will not tell them: the answer looks door-specific because the question was. Two of the three recipes also collapse to the same partition, so the set teaches less than it appears to.

A clause fixes it — note that the doors agree on every row today and the map exists so they can diverge. Better, swap in a query a tier filter cannot express: select(.engine_path == "copy_and_swap") answers "what is waiting on the copy engine", which is a real roadmap question that cuts across tiers.

2. Recipe 3's set has a sharper definition than "owned by another tool class"

The field is not conventionally tied to ownership — capabilities.go:136-141 enforces a biconditional: a row with online_safety_problem: false must name an owning_tool_class, and one with true cannot. Zero rows violate it. So recipe 3 is exactly select(.online_safety_problem == false), stated through a different field.

The consequence a reader hits: it returns 14 of the 16 T3 rows. The two it omits — exclude-constraints-and-unrecognized-constraint-forms and add-constraint-using-index-on-a-partitioned-parent — are the rows, out of scope because PostgreSQL offers no online mechanism, not because another tool owns the job. Anyone using this to enumerate "what pg-sprite will not do" gets a quietly incomplete answer, and the page's own three-mark legend is what explains why. One clause — that this is the ⚪ and 🔵 rows, and rows have no owner — makes the recipe teach the distinction the legend already draws.

3. No recipe answers the question a consumer actually arrives with

All three filter by attribute; none looks up a single change. Is this operation supported today? is the question the page exists for, and select(.id == "…") is the first thing I would reach for.

This is not just my preference — the source these recipes come from says so. capabilities-contract.md:171-172 instructs consumers to "select by fields or id, not array position," and that guidance did not travel with the block (see 2/2, finding 1). A lookup-by-id recipe is the one that makes id matter, and it is also the one that demonstrates what the JSON buys over the table, which a human can already eyeball for tiers.


This review was generated by Claude Code (claude-opus-5).

@aparajon

Copy link
Copy Markdown
Collaborator

🤖 2/2 — provenance, the rollout marker, and the surrounding claims. The recipes themselves are in 1/2.

1. The recipe block is a byte-identical copy of the contract's, and now nothing keeps the two in step

capabilities-contract.md:177-186 already carries these three recipes, under "The contract must support these queries:". The new block at capabilities.md:40-49 is the same ten lines — same recipes, same comments, same order — diff reports no difference at all.

That is a defensible thing to do, since the two blocks answer different questions: in the contract they are a requirement on the schema, written at design time to say what the JSON must be able to express; on the capabilities page they are consumer documentation. Same text, different force. But the copy creates a second hand-maintained statement of the same thing in the one repo whose entire thesis is that there must not be one — the sentence directly under the rollout list (capabilities-contract.md:273-274) justifies shipping the generator in step 1 precisely so "there is never an intermediate state in which two hand-maintained matrices are authoritative." Completing that rollout by duplicating its recipe block is worth a moment's thought rather than none.

Three ways out, in increasing cost:

  • Link instead of copy. The new section already links the contract two lines above (:37-38); it could say "the queries the contract guarantees" and point at the anchor. Cheapest, and loses nothing except the ability to read the page standalone.
  • Copy, but pin it. A docs test asserting the two fenced blocks are identical is about eight lines and is exactly the idiom this repo already uses for cli-output-examples.mdTestCLIOutputExamplesMatchPipelineOutput exists for the same reason. This is the option I would take: it keeps the page standalone and makes divergence a test failure rather than a discovery.
  • Diverge deliberately — richer consumer-facing recipes here, the minimal requirement set there — and say in both that they are not the same list.

Whichever, the copy dropped the two sentences around the block that consumers need most and the contract audience needs least: :171-173, that array order is stable for display but consumers should select by fields or id rather than position, and that the command reads only embedded data. The second survived as a paraphrase at capabilities.md:36; the first did not travel, and it is the one that prevents the obvious .capabilities[0] mistake. It also points at the missing lookup-by-id recipe from 1/2.

2. Step 4 is marked done while step 3 is still pending, under a sentence calling the list an order

capabilities-contract.md:264 reads "Implementation order is, with each step marked as it ships:", and this PR leaves:

3. add the regenerate-and-diff CI gate to the normal pipeline; and *(pending)*
4. add documentation and `jq` recipes for consumers. *(done)*

Step 3 is #100, still open. So the first time the per-step marker convention carries real information, it records the order being skipped — and the step skipped is the convention's own PR.

Small edit either way, but the two fixes mean different things:

  • The list is an order. Then hold the marker flip until ci: fail when capabilities markdown disagrees with yaml #100 lands; the docs themselves are useful now and need not wait.
  • The list is a set of steps completed as convenient. Then :264 should stop saying "order". The generator sentence right below (:273-274) already names the one real sequencing constraint, which is the argument that the rest is not load-bearing as a sequence.

I lean to the second: nothing about the recipes depends on the gate existing, and a reader seeing 4 done before 3 will otherwise assume the marker is stale rather than accurate.

3. The page survives regeneration, which is the check this PR needed most

docs/capabilities.md is generated, so the risk in hand-editing it is that make gen-capabilities eats the addition. It does not: the new section sits at :32-50 and the ToC entry at :15, both above the first marker (:164), and running the generator at this head leaves the tree clean byte for byte. #100's make check-capabilities will pass on this content, and no capabilities.yaml change is needed.

Placing the section above ## What pg-sprite is is right for the same reason: a reader who wants the page as data should not scroll past the philosophy to learn they can have it. The anchor resolves and the ToC order matches the body.

4. The README claims check out, including the one easiest to get wrong

  • README.md:184 puts capabilities in the connection-requirement table as none. Verified rather than assumed: with the environment scrubbed (env -i, no DSN, no PG variables) both forms print the matrix and exit 0. The claim at capabilities.md:36 holds under the same test.
  • Describing --json as "the versioned automation contract" rather than just JSON is the right framing, and it is backed — capabilities-contract.md:192-203 defines what versioning means here, including that adding a field is additive and consumers must ignore unknown ones.
  • "compact table" is accurate: 54 lines for 53 rows, with elided columns.

Approving. Everything here is accurate, and the regeneration and offline claims both survive testing. Finding 1 above is the one I would act on — a pinning test is the small version of it — with finding 1 in 1/2 next.

This review was generated by Claude Code (claude-opus-5).

@aparajon aparajon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Approving on dd67034e. I built the binary and ran all three recipes against the real embedded matrix — they work, and three mutations to the JSON contract underneath them were each caught by #99's existing tests, so the recipes inherit real protection rather than needing their own. The generator leaves the tree clean with the new section in place, and the none connection claim holds with the environment scrubbed.

Two things to act on: the block is a byte-identical copy of the contract's own recipe list (capabilities-contract.md:177-186) with no gate keeping them in step — a pinning test is the small version of the fix — and the copy dropped the "select by fields or id, not array position" guidance that consumers need most. Details in the two comments.

This stamp was left by Claude Code (claude-opus-5).

Clarify that only marker-delimited support tables are generated from the YAML source of truth.

Keep operator jq recipes in the capabilities guide and link to them from the contract doc.
The Query-the-matrix recipes selected by tier, by one door's disposition, and by
owning tool class, but each was either redundant with a tier filter or silently
incomplete. Add a lookup by id (the first thing a consumer asks) and a filter by
engine_path (a roadmap cut no tier filter can express). Note that both doors carry
the same disposition on every row today, so the door filter is a hook for future
divergence, not a way to find differences. Note that the owning-tool-class filter
returns only the white and blue rows: the red rows name no owner because PostgreSQL
has no online mechanism for them, so the filter is not the full out-of-scope set.

Carry the contract's "select by fields or id, never by array position" rule onto the
operator page next to the recipes, so the guidance travels with the commands it
governs. The contract's summary of the recipe kinds now lists id lookup and engine
path too.

The contract's step list said "implementation order", but the recipes never depended
on the CI gate: reword the lead-in so only the first step is a prerequisite and the
rest land independently, which is what the done/pending marks already show.

🤖 Generated with Amp (Claude Opus 4.6)
@Kiran01bm

Copy link
Copy Markdown
Collaborator Author

🤖 Adversarial review response — created by Kiran's code review agent (Amp, Claude Opus 4.6) — block/pg-sprite pull/104, follow-up commit

Verdict: all three recipe findings and both actionable provenance findings are fixed; each recipe comment now states what the binary actually returns, checked against pg-sprite capabilities --json on this branch.

# Concern Status Explanation
C1-F1 front_doors.diff == "refused" reads as door-specific, but both doors agree on all 53 rows, so it collapses to tier != "t1"; the set never shows a query a tier filter cannot express fixed Re-verified: 0 of 53 rows differ between the two doors. The recipe stays because the door map exists so the doors can diverge; its comment now says the doors agree on every row today and tells the consumer to query the door they use. Added select(.engine_path == "copy_and_swap") (8 rows across tiers), the "what is waiting on the copy engine" question a tier filter cannot answer.
C1-F2 owning_tool_class != null returns 14 of 16 T3 rows and omits the two ❌ rows, so it is a quietly incomplete "what pg-sprite will not do" fixed The recipe comment now says it returns the ⚪ and 🔵 rows, and that the ❌ rows name no owner because PostgreSQL has no online mechanism for them, so it is not the full out-of-scope set. Re-verified: the two owner-less T3 rows are exactly the two ❌ rows (EXCLUDE constraints, USING INDEX on a partitioned parent).
C1-F3 No recipe looks up a single change by id, which is the question the page exists for fixed Added an id lookup as the first recipe, using a real registry id (add-column-no-default-or-constant-default).
C2-F1 The recipe block was a byte-identical copy of the contract's, with nothing keeping the two in step; the "select by fields or id, not array position" sentence did not travel with it fixed The copy was removed in e72510e: the contract links to the operator page's section and states that section is the one copy, so there is nothing left to pin. This commit carries the array-position sentence onto the operator page directly above the recipes, and the contract's summary of recipe kinds now lists id lookup and engine path.
C2-F2 Step 4 is marked done while step 3 is pending, under a lead-in that calls the list an order fixed Took the second option: the recipes never depended on the CI gate, so the lead-in was the inaccuracy. Reworded to "Implementation proceeds in these steps, each marked as it ships. Only the first step is a prerequisite for the others; the rest land independently", which names the one real sequencing constraint the generator sentence already describes.
C2-F3 The page survives regeneration; the new section and ToC entry sit above the first marker no action Verified correct at this head too: the new recipe lines sit inside the existing section above the first marker, and make gen-capabilities leaves the tree clean apart from these edits.
C2-F4 README claims (capabilities needs no connection, --json is the versioned contract, "compact table") check out no action Verified correct; nothing in this commit touches the README.

Verification: make gen-capabilities leaves the tree clean apart from these edits; SKIP_INTEGRATION=1 go test ./internal/cli/... ./pkg/capabilities/... passes; every recipe was run against ./bin/pg-sprite capabilities --json on this branch and the counts above are from that run.

Source: block/pg-sprite#104, review comments 5628073553 and 5628074103 and review 5174116303 at head dd67034e

@Kiran01bm
Kiran01bm enabled auto-merge (squash) September 11, 2026 04:00
@Kiran01bm
Kiran01bm merged commit 3b109e3 into main Sep 11, 2026
11 checks passed
@Kiran01bm
Kiran01bm deleted the kiran01bm/cm5-capabilities-recipes branch September 11, 2026 04:01
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