Skip to content

chore: regenerate catalog from OpenAPI spec - #104

Merged
dangrondahl merged 1 commit into
mainfrom
chore/update-catalog
Oct 27, 2026
Merged

dangrondahl merged 1 commit into
mainfrom
chore/update-catalog

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated catalog update from the Kosli OpenAPI spec.

Please review the diff to verify the changes look correct before merging.

@claude

claude Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Claude finished @github-actions[bot]'s task in 2m 33s —— View job


Catalog diff review

  • Read the catalog diff
  • Enumerate actions added / removed / changed
  • Check for dangling references in src/, test/, README.md
  • Sanity checks (action count, surviving $ref)
  • Post summary

Action count: 134 → 139. Purely additive: 5 actions added, 0 removed. The 2 deleted lines in the diff are both halves of one reworded description (list_attestations's type parameter — the text is duplicated in description and schema.description). So the case that bit this repo in #16 and #18 does not apply here — there is nothing to confirm against the live spec.


1. Actions added (5)

id method path
get_attestation GET /attestations/{org}/{flow_name}/trail/{trail_name}/attestation/{attestation_id}
get_rego_policy_bundle GET /rego-policies/{org}/{identifier}/versions/{version_number}/bundle
get_rego_policy_file GET /rego-policies/{org}/{identifier}/versions/{version_number}/files/{file_path}
list_rego_policy_files GET /rego-policies/{org}/{identifier}/versions/{version_number}/files
list_trail_events GET /trails/{org}/{flow_name}/{trail_name}/events

All five are GET, all five are org-scoped, all five have requestBody: null. Two coherent groups: Rego policy source browsing (beta, 403 without the feature), and trail event pagination.

2. Actions removed

None. No confirmation against https://app.kosli.com/api/v2/openapi.json is needed for this PR.

3. Actions changed (4) — all parameter additions, no removals

  • get_trail — new optional query param exclude, an array of the literal "events". Pairs with the new list_trail_events: the trail payload can now omit its event list and you page events separately. This is the one change with a real token-efficiency upside worth surfacing to users.
  • list_evaluations — four new optional query filters: policy (string), version (array of int, needs policy), outcome (array of allow/deny/error, needs policy), decided (boolean, needs policy). Note the documented coupling — three of the four are rejected or meaningless without policy, and the catalog expresses that only in prose, so the model has to read the description to get it right.
  • list_rego_policies — new optional query param input_schema, enum trail | trails.
  • list_attestations — description only: 'junit', 'decision' → 'junit' as built-in, 'decision' as system type. No schema change.

No parameters removed, no schema narrowed, nothing newly marked deprecated.

4. Dangling references

None — nothing was removed or renamed, so nothing can have gone stale.

  • src/hints.json covers only create_or_update_policy and create_or_update_flow_with_template; neither is touched.
  • test/fixtures/catalog-subset.json contains get_trail, which gained exclude. The fixture is a hand-curated trim and no test asserts it mirrors the real catalog, so this is not a failure — but if you want coverage of exclude (array-valued query param serialised via URLSearchParams.set(k, String(v)), i.e. comma-joined, not repeated), the fixture is where to add it.
  • README.md names no individual action ids.

5. Sanity checks

  • ✅ No $ref anywhere in src/catalog.json (0 occurrences) — generator inlining worked.
  • ✅ Count went up, not down.
  • ✅ All 5 new actions declare org as a path param, so test/catalog.test.ts's "exactly four actions take no org" and "never declares org as a query or header parameter" invariants still hold.
  • ✅ All 5 have requestBody: null, so the "body may not name an action's own parameter" invariant is trivially satisfied.

Two things worth a look before merging

a) get_rego_policy_file's file_path is a path parameter that will contain /.

KosliClient.buildUrl (src/client/kosli-client.ts:145) substitutes every path param through encodeURIComponent. A realistic value like policies/main.rego becomes:

/rego-policies/{org}/{id}/versions/1/files/policies%2Fmain.rego

Whether Kosli's router accepts a percent-encoded slash there, or expects a greedy multi-segment match, is a server-side question the catalog can't answer. Worth one live call against a policy with a nested file before users hit it. This is the first catalog action whose path param is itself path-shaped.

Fix this →

b) get_attestation is a confusable name. The catalog now has three similar ids:

  • get_attestation — by attestation_id, within a trail (new)
  • get_trail_attestation — by attestation_name, within a trail (existing)
  • get_artifact_attestation — by attestation_name, within an artifact (existing)

The ids are unique so no test breaks, but search_actions scores against searchText, and the new action's summary ("Get trail attestation by ID") overlaps the existing one's ("Get trail attestation") almost entirely. A query like "get trail attestation" will now surface both with close scores. That's arguably the right outcome — the model picks on whether it holds an id or a name — but it's the kind of thing worth watching in practice, and src/hints.json is the lever if it misfires.


What I could not verify

npm ci and npm test were not permitted in this run, so I did not execute the suite. I checked the catalog invariants in test/catalog.test.ts by hand instead (no $ref, unique ids, org as a path param on every new action, no request bodies at all) and all of them hold. CI's own run of the suite is the authority.

Separately, and outside this PR's scope: CLAUDE.md describes an isPathSegment guard in src/client/kosli-client.ts rejecting empty / . / .. path segments, plus a buildUrl check that the parsed pathname still ends in the path it built. I could not find either in src/. That is a pre-existing doc/code gap, not something this diff introduces — but the new file_path parameter is exactly the kind of input those guards were described for, so it may be worth a separate issue.

Verdict: additive, internally coherent, no removals to confirm, no stale references. Safe to merge on the catalog's own terms; item (a) is the only thing I'd want answered first, and it can equally be a follow-up.
· branch chore/update-catalog

@dangrondahl
dangrondahl merged commit 1f96e9f into main Oct 27, 2026
8 checks passed
@dangrondahl
dangrondahl deleted the chore/update-catalog branch October 27, 2026 14:21
dangrondahl added a commit that referenced this pull request Oct 27, 2026
…ng (#105)

## What this fixes for users

Ask Claude "show me the flows tagged team:platform or team:security" and
the model calls `list_flows` with `tag: ["team:platform",
"team:security"]`. The server sent that as one query entry:

```
GET /api/v2/flows/acme?tag=team%3Aplatform%2Cteam%3Asecurity
```

The API reads that as a single tag literally named
`team:platform,team:security`. No flow has it, so the answer is an empty
list, and the user is told there are no such flows. There is no error,
because a comma is a legal tag character. For enum and integer lists,
such as `list_evaluations` `outcome` and `version` in #104, the API
instead rejects the comma-joined value with a validation error.

After this PR the key is sent once per element, which is what the API
expects:

```
GET /api/v2/flows/acme?tag=team%3Aplatform&tag=team%3Asecurity
```

A one-element list serialised the same either way, so every single-value
filter worked and the bug only showed when a question needed two or more
values. The second commit covers a related case: these filters are typed
array-or-null, and a model that passes `null` for "no filter" was
sending `?tag=null`, again returning nothing. `null` now means not
supplied.

## Summary

`KosliClient.execute` serialised a GET/DELETE query with
`URLSearchParams.set(k, String(v))`, so an array like `["allow",
"deny"]` was sent as one `?outcome=allow,deny` entry. The Kosli API
declares every list query parameter as FastAPI `List[...] = Query()`,
which reads the key repeated (`?outcome=allow&outcome=deny`) and refuses
or literally matches a comma-joined value. Single values worked, so this
only showed up when the model passed two or more.

Eighteen catalog actions on `main` already take such parameters
(`list_flows` `tag`, `list_environments` `type`, `list_audit_log`
`role`, ...), and #104 adds `get_trail` `exclude` and `list_evaluations`
`version`/`outcome`.

## Changes

- Each array element appends its own query entry; scalars are unchanged.
- A query that serialises to nothing (for example an empty array) sends
no trailing `?`.
- Tests for scalar, repeated and empty array query parameters.
- One line in `CLAUDE.md` recording the convention next to the existing
"change how params are serialized" note.

The multipart branch already repeated array keys, so the two branches
now agree.

## Test plan

- [x] `npm test` (138 passing)
- [x] `npm run typecheck`
- [x] `npm run build`
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