Skip to content

feat: add access-profiles commands (the API's request catalogs) - #109

Merged
leet-c1 merged 14 commits into
mainfrom
feat/catalogs
Sep 3, 2026
Merged

feat: add access-profiles commands (the API's request catalogs)#109
leet-c1 merged 14 commits into
mainfrom
feat/catalogs

Conversation

@leet-c1

@leet-c1 leet-c1 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

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 the
RequestCatalog schema x-speakeasy-entity: Access_Profile, and its
sub-resources Access_Profile_Visibility_Bindings and
Access_Profile_Requestable_Entries. The command uses the product's name, the
wire keeps the API's. There is no catalogs alias.

Not to be confused with an app catalog, the per-user list of what one user can
request. Both are documented.

  • list emits NDJSON and auto-paginates. memberCount is omitted from rows:
    the list endpoint reports 0 for every profile while get on the same id
    returns a real count, so emitting it would read as "no members".
  • get unwraps requestCatalogView.requestCatalog, so jq -r .id works.
  • create sends only the flags you pass, leaving the server's defaults for the
    rest. --published and --visible-to-everyone both apply at create time.

Ordering, verified against the server

Publishing is necessary but not sufficient for a visibility binding:

profile POST .../visibility_bindings
unpublished 400 catalog must be published to add an access entitlement
published, visible to everyone 400 catalog is visible to everyone, cannot add access entitlements
published but not visible to everyone 200

All 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/update are not wrapped yet; reach them via c1i api.

🤖 Generated with Claude Code

leet-c1 and others added 14 commits September 1, 2026 04:35
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 leet-c1 changed the title feat: add catalogs commands for request catalogs (access profiles) feat: add access-profiles commands (the API's request catalogs) Sep 3, 2026
@leet-c1
leet-c1 merged commit 01a7fcf into main Sep 3, 2026
2 checks passed
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