feat: add access-profiles commands (the API's request catalogs) - #109
Merged
Conversation
Three commands over the catalog routes, built from the existing apps
list/get/create shapes rather than anything new:
- `catalogs list` — GET /api/v1/catalogs, NDJSON, auto-paginating via the
shared pagination registrar, with a case in listPaginationCases.
- `catalogs get <catalog-id>` — GET /api/v1/catalogs/{id}. The envelope is
requestCatalogView.requestCatalog, two levels deep; writeResource already
handles that shape, so it hoists the catalog's keys and keeps memberCount,
the two JSONPATH siblings and `expanded` beside them. Added to
getUnwrapCases, which TestEveryTypedGetIsCovered had already failed on.
- `catalogs create --display-name <name>` — POST /api/v1/catalogs. Optional
flags are sent only when passed, so the server's defaults apply to the rest
and `--published=false` stays distinguishable from silence.
Three server behaviors settled live on the test tenant rather than assumed.
`catalogs list` rows carry no member count. The list endpoint reports
memberCount as "0" for every catalog while `catalogs get` on those same ids
answers a non-zero count, and the endpoint accepts no parameter that could
populate it. A row with an always-zero count reads as "no members", so the key
is omitted and the count is left to the get.
Visibility bindings require a published catalog. POST
/api/v1/catalogs/{id}/visibility_bindings against an unpublished catalog is a
400, "catalog must be published to add an access entitlement"; the identical
call after publishing returns 200. That ordering is now stated in `create`'s
help, README and cmd/agents.md, quoting the server's own string.
Delete is a soft delete: the catalog leaves `catalogs list` while
`catalogs get` still returns it at exit 0 with deletedAt set. `deleted_at`
therefore reads null in every list row; the key is kept for consistency with
the sibling row builders, and the docs say not to filter on it.
`--published` and `--visible-to-everyone` were confirmed to take effect at
create time — a catalog can be created already published.
Deliberately out of scope: the sub-resource routes (requestable entitlements,
visibility bindings, bundle automation) and delete/update. `c1i api` reaches
them meanwhile, and both docs say so.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The product, its docs and the OpenAPI spec all call this object an access profile; only the REST route says catalog. Command, flags and help now use the product name, with one line in the group help mapping it to /api/v1/catalogs. No alias: the old name is gone. Go symbols and request paths keep "catalog" — that is the wire. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- README promised a 200 for a visibility binding on a published catalog, in a sentence whose subject was the visible-to-everyone catalog that 400s. The accurate statement was already two lines above it. - --description "" was dropped despite the help promising every flag you pass is sent; it now tests Changed() like the booleans beside it. - Fix an article left behind by the rename in cmd/agents.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The same unqualified claim the previous commit removed from README survived here: a profile created with both --published and --visible-to-everyone is "created with --published" and still 400s, which the preceding clause of the same bullet says. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The fact lives in four sources and the unqualified form came back twice in one branch: it reads as true until you remember --visible-to-everyone is also a create-time flag. Each source is now held to the qualifier, and to the server strings it quotes. Whitespace is flattened before matching, since a wrapped quote in the source otherwise reads as absent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The branch's first commit had hoisted 0.6.0's released Added entries into Unreleased and dropped the tasks close/reassign entry entirely. Rebuilt the file from main's structure with only the access-profiles entry added. The drift guard skipped any source that had lost the quoted server string, so losing the quote was itself a way to pass. Every source is now held to both 400s and the qualifier. A get always returns accessEntitlements, empty when there are none; three docs said "when it has any". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
All three were in the guard itself, not the docs it checks: - "whether or not visible to everyone" contains "not visible to everyone", so a phrasing asserting the opposite satisfied the substring check. - The non-empty assertion had been dropped, so an emptied source list passed. - The qualifier was matched anywhere in the file, so an append-only changelog could satisfy a new unqualified entry with an old one's wording. The qualifier is now required near the quote that states the ordering; the other quote is the counter-example and only has to be present. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
expanded is a top-level sibling of requestCatalogView, not one of the view's own siblings; the branch's fixture and the live response both show this. Measured across every profile on the tenant: accessEntitlements is present on all of them, so "always present, empty when there are none" holds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two rounds of this guard tried to detect any English wording that meant "publishing is necessary but not sufficient", and each was defeated by a rewording: "whether or not visible to everyone" contains "not visible to everyone", so a sentence asserting the opposite satisfied the qualifier and supplied it. A hand-maintained list of negations had to stay in sync with the list of qualifiers, which is the drift this guard exists to prevent. All four sources now use one clause, "published but not visible to everyone", required verbatim in every block that states the ordering. No negation can contain it, so rewording fails rather than passing. Blocks are blank-line separated, so a new changelog entry cannot borrow a neighbouring entry's qualifier — the byte window could not tell those apart. Every occurrence is checked, not the first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Blank lines alone did not bound a claim: a markdown list has none between its items, so the agent doc's gotcha list was a single 5.5KB block and a new bullet could satisfy the guard with a clause 4,900 characters away — a wider borrow than the byte window this replaced. Splitting on list items too brings that block to 418 characters. A fenced transcript of the server's error is an example, not a claim, and cannot carry prose, so requiring the clause inside one failed on correct docs. Fences are stripped before the claim scan; the quote-presence check still sees them. Failures now name the block they came from. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The automatic merge kept both sides of [Unreleased], leaving two Added and two Changed sections and the tasks close/reassign entry twice. Rebuilt from main so the only difference is the access-profiles entry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A table has no blank lines and no list markers, so a whole table was one block — the same shape as the list bug, in a file that has tables. Rows now break a block, as do the CommonMark marker forms the previous pattern missed: + bullets and 1) numbering. Tilde fences are exempted alongside backtick ones, since the reason a transcript cannot carry the clause does not depend on which fence it uses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
leet-c1
force-pushed
the
feat/catalogs
branch
from
September 3, 2026 00:09
341ca21 to
ad51903
Compare
catalogs commands for request catalogs (access profiles)access-profiles commands (the API's request catalogs)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
c1i access-profiles list|get|create.An access profile controls which entitlements are requestable and who can
request them. The API calls the object a request catalog and routes it under
/api/v1/catalogs, so its JSON keys and ids say "catalog"; the spec tags theRequestCatalogschemax-speakeasy-entity: Access_Profile, and itssub-resources
Access_Profile_Visibility_BindingsandAccess_Profile_Requestable_Entries. The command uses the product's name, thewire keeps the API's. There is no
catalogsalias.Not to be confused with an app catalog, the per-user list of what one user can
request. Both are documented.
listemits NDJSON and auto-paginates.memberCountis omitted from rows:the list endpoint reports
0for every profile whilegeton the same idreturns a real count, so emitting it would read as "no members".
getunwrapsrequestCatalogView.requestCatalog, sojq -r .idworks.createsends only the flags you pass, leaving the server's defaults for therest.
--publishedand--visible-to-everyoneboth apply at create time.Ordering, verified against the server
Publishing is necessary but not sufficient for a visibility binding:
POST .../visibility_bindings400catalog must be published to add an access entitlement400catalog is visible to everyone, cannot add access entitlements200All three re-observed live. A test holds the four places that state this to one
clause, so the unqualified restatement ("publish and it works") cannot come
back — it is bounded to the block that makes the claim, across paragraphs,
lists, tables and every CommonMark marker form.
Not covered
The sub-resource routes (requestable entitlements, visibility bindings, bundle
automation) and
delete/updateare not wrapped yet; reach them viac1i api.🤖 Generated with Claude Code