docs(api-reference): sync Platform API docs for platform-client v0.4.0 - #11668
Conversation
Regenerates api.yaml, docs.json and introduction.mdx from the cubejs-enterprise public OpenAPI spec, and changelog.mdx from the platform-client CHANGELOG.md, via scripts/extract-api.mjs and extract-changelog.mjs. Adds the new Region Private Links group to TAG_ORDER so it's placed after Regions instead of appended A-Z. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XdLcEJjqSB78Zu4zdbo5yn
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
Claude finished @vasilev-alex's task in 5m 29s —— View job Review: looks good to merge — 0 blocking, 2 medium (both upstream/policy, not defects in this PR), 3 nitsFull review — verification performed, findings, and checklist
What I verifiedI could not run
FindingsMedium — cursor-pagination parameter descriptions lost (inline) · Medium — 13 new pages are Cube-staff-only (inline) · Nits
Notes on the standard review dimensionsSecurity / performance / error handling / test coverage are all n/a here: the diff is generated OpenAPI + nav JSON + a changelog entry, with one declarative array element of hand-written change. Nothing executes. The one genuinely security-adjacent observation is the staff-only endpoint exposure above, and that's a documentation-surface question, not an authorization one — auth is enforced server-side. The changelog entry is unusually good — the breaking removal explains the why (CUB-3898), the |
| - in: query | ||
| name: after | ||
| schema: | ||
| oneOf: | ||
| - type: string | ||
| - type: 'null' | ||
| - in: query | ||
| name: first | ||
| schema: | ||
| oneOf: | ||
| - minimum: 1 | ||
| type: integer | ||
| - type: 'null' |
There was a problem hiding this comment.
Upstream regression: cursor-pagination parameter docs were dropped.
The previous spec documented first/after on every cursor-paginated list, e.g.:
- in: query
name: first
required: false
schema: { type: integer, minimum: 1, maximum: 200 }
description: Page size for cursor pagination (default 100, max 200).
- in: query
name: after
required: false
schema: { type: string }
description: Opaque cursor for the next page; pass the previous response's `pageInfo.endCursor`.After the sync, Page size for cursor and Opaque cursor have zero occurrences in api.yaml — every occurrence is now the bare oneOf: [<type>, 'null'] form with no description and no maximum. So the rendered pages lose both the explanation of what after takes and the documented default/max page size, across ~10 endpoint groups (env-vars, environments, GitHub, user attributes, groups, users, regions, report folders…).
This isn't a bug in this PR — extract-api.mjs deliberately leaves parameter descriptions alone (see the comment at scripts/extract-api.mjs:211) — but it's a real docs quality loss worth fixing at the source (.describe() / .max(200) on the zod query schemas in cubejs-enterprise) and re-syncing. Also note oneOf: [{type: string}, {type: 'null'}] renders as a nullable union for a query param, which reads oddly compared to the old plain type: string.
There was a problem hiding this comment.
Agreed this is real and confirmed it's upstream, not this PR's diff — leaving unfixed here as you suggested, and noted it in the PR description so it isn't lost. The fix belongs on the CursorPaginationQuery zod/class-validator schema in cubejs-enterprise's console-server (.describe() + .max(200) on first/after), then a re-sync; that's a separate cross-repo change outside a docs-only PR here.
Generated by Claude Code
…gelog Re-syncs changelog.mdx after cubedevinc/cubejs-enterprise#14466 fixed three inaccuracies in the release notes flagged by review: users/groups already had cursor pagination in 0.3.0 (search is what's new), the DeploymentEnvironment(Tokens)ListResponse.pagination narrowing is a TS break, and the report-placement field additions were unlisted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XdLcEJjqSB78Zu4zdbo5yn
Re-reviewing after
|
…from public docs
Per review: every new Regions/PrivateLinks operation (create/get/update/
destroy/apply/provisioning-status/cloud-provider-catalog on regions, and
all 5 PrivateLink ops) is gated "Cube super admin only" — no reader of
these docs can call any of them. EXCLUDE_OPERATIONS already exists for
exactly this class of route ("Account-level / internal admin APIs kept
out of the public docs"), and no prior operation in this spec carried
that banner, so this adds all 12 to it rather than leaving the sidebar's
first customer-uncallable pages in by default.
GET /api/v1/regions (listRegions) is untouched — any tenant can call it.
The Region Private Links tag now has zero operations left, so it drops
out of the nav automatically; reverts the TAG_ORDER entry added for it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdLcEJjqSB78Zu4zdbo5yn
Re-reviewing after
|
…og correction Re-syncs after cubedevinc/cubejs-enterprise#14466's follow-up fix: the report-input field list is now split per schema (Connect/Create/Update gained different subsets), and ResourceGroupPolicyDto joins its sibling InheritedGroupPolicyDto for the name/userCount addition. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XdLcEJjqSB78Zu4zdbo5yn
Re-reviewing after
|
… note Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XdLcEJjqSB78Zu4zdbo5yn
|
Claude finished @vasilev-alex's task in 4m 8s —— View job Review of
|
| Check | Result |
|---|---|
Cube super admin only occurrences in api.yaml |
0 (was 13) |
Region paths surviving in api.yaml |
exactly one — /api/v1/regions (GET listRegions), at api.yaml:3609 |
docs.json Regions group |
one page, GET /api/v1/regions (docs.json:799-803); no Region Private Links group |
| Orphaned schemas | none — RegionCreateRequest, ProvisionedRegionResponse, RegionPrivateLink*, RegionProvisioningRun*, RegionCloudProviderCatalog* all have zero occurrences, so the $ref closure pruned them with the operations |
TAG_ORDER |
reverted to master's shape — 'Regions' stays inline on line 209, no Region Private Links entry, and the formatting nit from the last review is gone with it |
| Excluded set vs. reality | all 12 entries (extract-api.mjs:178-189) correspond to paths/methods now absent; GET /api/v1/regions correctly left in |
| Nav ↔ spec for the rest of the sync | every new op present in both — GET/POST/GET {id}/DELETE {id}//logs//result under dbt Sync, DELETE .../branches, PUT .../branches/staging-environment, GET .../data-model/validate, DELETE .../connect-workbook. No orphans either direction. |
introduction.mdx |
Regions row still resolves (/api-reference/regions/list-regions ← summary "List regions"); dbt Sync row points at list-dbt-syncs-for-a-deployment, matching summary: List dbt syncs for a deployment at api.yaml:306 |
The changelog corrections check out
e2c3462—BranchResponse.isStagingEnvironmentEnabledis real:api.yaml:4806,oneOf: [boolean, 'null']. ✅0551b22— the per-schema split of the report-input fields, andResourceGroupPolicyDtojoiningInheritedGroupPolicyDto, are both changes to schemas that don't surface in this repo's public spec (the resource-policies ops are inEXCLUDE_OPERATIONS, so the DTO is pruned), so they can't be cross-checked here — but the correction direction matches the upstream commit message and the entry is now more precise, not less. Splitting "each gained X, Y, Z" into three per-schema lists is the right call: the previous wording would have had an SDK consumer looking forplacementIdonCreateReportInput, where it doesn't exist.
New findings (both low, both follow-ups)
- An
EXCLUDE_OPERATIONSentry that stops matching fails open, silently (inline) ·scripts/extract-api.mjs:189. Exact-string lookup with no assertion that each entry matched. If upstream renames a path, the exclusion becomes a no-op and the staff-only pages quietly return — and--checkwon't catch it, since it compares generated output to committed output and both would contain the leak. ~5 lines to make it abort instead. - The changelog still announces the 12 endpoints the exclusion removed (inline) ·
changelog.mdx:13-16. Two bullets walk through operations and 13 schema names that no longer exist anywhere inapi.yaml. Correct for SDK consumers, confusing for reference readers — the seam the exclusion opened. Fix is upstream (annotate the entry as staff-only, or teachextract-changelog.mjsabout the exclusion set).
Prior threads
- Cursor-pagination parameter docs (thread) — re-affirmed, left unresolved. Still zero occurrences of
Page size for cursor/Opaque cursorinapi.yaml. Agreed the fix is.describe()/.max(200)on the cursor query schema incubejs-enterprise, out of scope here; leaving the thread open as a tracking marker rather than resolving it. (Small observation while checking:GET .../dbt-syncatapi.yaml:290-298declaresfirst/afteras plainstring/integerwhile every other endpoint usesoneOf: [<type>, 'null']— same upstream inconsistency, same fix.) - Staff-only endpoints in public docs — resolved upstream by
05cfc05; the thread is already marked resolved. - Nits 1 and 3 from the first review are addressed (PR description now distinguishes "new to the client changelog" from "new to these docs";
TAG_ORDERreverted). Nit 2 (drift check not wired into CI) still stands and is still understandable given the source spec lives in a private repo.
Standard dimensions
Security / performance / error handling / test coverage remain n/a — generated OpenAPI, nav JSON, and a changelog entry, plus 15 declarative lines in the extractor. The one security-adjacent item, staff-only endpoint exposure, is now closed; finding 1 above is about keeping it closed.
…tches nothing Per review: the exclusion lookup silently no-ops if an entry's method+path stops matching (upstream rename/move), and --check can't catch that either — both the committed and freshly generated output would contain the leak. That's low-stakes for "stray/incomplete admin routes" but more load-bearing now that 12 entries carry a privacy intent (keeping Cube-staff-only Regions/PrivateLinks operations out of the public docs). Track which entries matched during the path loop and abort with the unmatched ones listed if any didn't. This immediately caught a real stale entry: `GET /api/v1/ai-engineer/ active-region` was removed from the source spec as a breaking change in platform-client 0.3.0, so its exclusion had already gone dead — removed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XdLcEJjqSB78Zu4zdbo5yn
Re-reviewing after
|
Re-syncs after cubedevinc/cubejs-enterprise#14466 added a note that these SDK-typed operations are excluded from this reference (EXCLUDE_OPERATIONS). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XdLcEJjqSB78Zu4zdbo5yn
|
Claude finished @vasilev-alex's task in 1m 54s —— View job Review of
|
| Check | Result |
|---|---|
matchedExcludes populated at the exclusion site |
✅ extract-api.mjs:292-295 — same key string used for lookup and recording, so they can't drift |
| Assertion fires in both modes | ✅ it sits at line 330, before the --check drift comparison at line 516 — so a CI-style --check catches a stale entry too, not just a write run. That was the important half: --check was exactly the path that couldn't see the leak before. |
| No partial output on abort | ✅ every writeOut call is downstream of line 330; a failed run writes nothing |
| Ordering vs. the "no paths matched" guard | ✅ after it, so a totally-wrong SRC_SPEC still gives the more specific error rather than a 27-line unmatched dump |
Interaction with INCLUDE_PREFIXES (line 286 continue) |
✅ an excluded op that moves outside /api/v1/ or /build/api/v1/ also fails the assertion. Technically a "false positive" — the op isn't leaking, it just moved — but aborting and demanding a human look is the right default for a set whose entries carry privacy intent. |
| Error message | lists every unmatched entry, one per line, and names the likely cause. Actionable without reading the script. |
The matchedExcludes set is the minimal correct implementation — no reliance on ordering, no second pass over the spec, and it can't false-negative (the only way an entry gets recorded is the same branch that deletes the operation).
The removed GET /api/v1/ai-engineer/active-region entry is genuinely stale
Independently corroborated from inside this repo: changelog.mdx:163 (the v0.3.0 entry, already on master) records it as a breaking removal —
BREAKING: removed the
GET /api/v1/ai-engineer/active-regionoperation (AIEngineerPublicController.getMyActiveAgentRegion) and theActiveAgentRegionschema. UseGET /api/v1/regions/to discover regions.
So the operation left the source spec one release before this sync, the exclusion had been a no-op since, and dropping it is right rather than a workaround for the new assertion. Nice validation of the assertion's premise on its first run.
4e4c819 — the changelog note lands where it should
The added line at changelog.mdx:15 sits under the Regions/PrivateLinks heading, ahead of both bullets, and closes the seam the exclusion opened: a reader who follows the changelog to a reference page that doesn't exist now learns why in one sentence, and why the SDK still has the methods. Option 1 from the thread, and it's the cheaper of the two — no new coupling between extract-changelog.mjs and EXCLUDE_OPERATIONS.
Verified the note is consistent with the current state: Cube super admin only still has 0 occurrences in api.yaml, and /api/v1/regions is still the only surviving region path (api.yaml:3609). The GET /api/v1/regions/ mention in the cursor-pagination bullet (line 46) refers to the list endpoint that is still documented, so it's correctly not covered by the note.
The two 0551b22/e2c3462 changelog corrections were verified last round and are unchanged here.
Nits (neither worth an inline comment)
EXCLUDE_OPERATIONScan no longer hold a defensive entry. The assertion makes the set strictly descriptive of the current spec — you can't pre-emptively list an operation you want kept out if it ever appears, because the build fails while it's absent. That's the correct trade for the privacy guarantee, and the two-line fix if it ever bites (anOPTIONAL_EXCLUDESsub-set exempt from the assertion) is obvious enough to leave until then. Practical consequence today:active-regionreturning upstream would now be published rather than silently dropped — very unlikely given it was removed as breaking.- The upstream
CHANGELOG.mdnow names this docs site. The new note is authored inplatform-client's changelog and shipped to npm, where it tells SDK consumers what docs.cube.dev does or doesn't publish. Immutable once released, so if the exclusion is ever reverted here the v0.4.0 release notes become quietly wrong. Fine given the exclusion is a deliberate standing policy; just noting the coupling.
Prior threads
- Cursor-pagination parameter docs (thread) — re-affirmed, still unresolved as a tracking marker. Unchanged by these commits; fix is
.describe()/.max(200)on the cursor query schema incubejs-enterprise, out of scope here. - Both low findings from last round are resolved by the author; their threads are already marked resolved. No stale
claudethreads left to close. - Nit 2 from the first review (drift check not wired into CI) still stands — though
d24cdd8makes it more worth doing, since--checknow enforces something--checkpreviously couldn't.
Standard dimensions
Security / performance / testing / error handling: the only executable change is 15 lines of post-loop validation in a build-time script. Error handling is the change — it converts a silent fail-open into a hard exit with a specific message. No new inputs, no runtime surface. Test coverage remains n/a (no test harness exists for these extractors); the assertion is itself a self-check, and it caught a real stale entry on first run, which is about the best evidence you get without one.
Good to merge.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XdLcEJjqSB78Zu4zdbo5yn
|
Claude finished @vasilev-alex's task in 1m 5s —— View job Review of
|
| Claim | Check |
|---|---|
GET /api/v1/regions stays in the public reference |
✅ /api/v1/regions is still the only region path in api.yaml (api.yaml:3609), and docs.json:799-803 has the Regions group with exactly that one page |
| It isn't staff-only | ✅ Cube super admin only still has 0 occurrences in api.yaml; the list op's x-mint.content is the plain "Lists the regions available to the account…" prose, no lock banner |
| "see the cursor-pagination note below" resolves | ✅ changelog.mdx:48 (the Changed section) does credit GET /api/v1/regions/ in the first/after list, so the forward reference lands on a real bullet on the same page |
| Exclusion set unchanged | ✅ all 12 entries still correspond to absent operations; the d24cdd8 assertion would abort otherwise |
This closes the last residue of the changelog-vs-reference seam: a reader who hits the staff-only note now knows it doesn't sweep up the one region endpoint they can call, which was the ambiguity the previous wording left — the note sat directly above two bullets, both of which mention /api/v1/regions paths, so "these operations" could plausibly have read as covering the list too.
Nit (not worth an inline comment)
The forward reference points at a bullet that says nothing about staff-only-ness — the cursor-pagination line just names GET /api/v1/regions/ among ten paginated endpoints. It works as "here's proof the list endpoint is still a normal documented endpoint," but a reader following the pointer expecting an explanation will find an unrelated pagination change. Naming the reference page directly would be more direct. Immaterial, and it's upstream text in platform-client's CHANGELOG.md, so not worth a round-trip.
Also carried over unchanged: the trailing slash in GET /api/v1/regions/ doesn't match the actual path (/api/v1/regions). Pre-existing across several entries including v0.3.0 on master, so consistent with itself; cosmetic.
Prior threads
- Cursor-pagination parameter docs (thread) — re-affirmed, left unresolved as a tracking marker. Still 0 occurrences of
Page size for cursor/Opaque cursorinapi.yaml;first/afterstill render as bareoneOf: [<type>, 'null']with no description and nomaximum: 200. Fix is.describe()/.max(200)on the cursor query schema incubejs-enterprise, out of scope for a docs-only PR here. - All three other
claudethreads are already resolved by the author and their fixes verified in earlier rounds (05cfc05exclusion,d24cdd8fail-closed assertion,4e4c819changelog note). No stale threads left to close. - Nit 2 from the first review (drift check not wired into CI) still stands, still understandable given the source spec lives in a private repo.
Standard dimensions
Security / performance / error handling / test coverage: unchanged from last round and n/a for this commit — one line of generated prose, nothing executable. The staff-only exposure question is closed and the d24cdd8 assertion keeps it closed.
Good to merge.
…elog section Re-syncs after cubedevinc/cubejs-enterprise#14466 dropped the section per direction not to surface or mention Cube-staff-only endpoints anywhere, including the SDK changelog. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XdLcEJjqSB78Zu4zdbo5yn
|
Claude finished @vasilev-alex's task in 1m 23s —— View job Review of
|
| Check | Result |
|---|---|
| Any surviving mention of the excluded ops | ✅ none — private-link/PrivateLink has 0 occurrences in api-reference/ (the one docs.json hit is the unrelated pre-existing /admin/deployment/byoc/aws/privatelink redirect) |
| Orphaned schema names in the changelog | ✅ all 13 (ProvisionedRegionResponse, RegionPrivateLink*, RegionProvisioningRun*, RegionCloudProviderCatalog*) are gone with the bullets — no name left in the changelog that's absent from api.yaml |
Remaining region mentions |
✅ 4, all legitimate: UpdateDeploymentInput.region (line 30), the cursor-pagination list crediting GET /api/v1/regions/ (line 41), the v0.3.0 listRegions addition (line 90), the v0.3.0 active-region breaking removal (line 156). All refer to the list endpoint that is published. |
| Dangling cross-reference | ✅ the "see the cursor-pagination note below" pointer went out with the note it lived in — no forward reference left hanging (this also closes the nit from the a56ad11 review) |
| Entry structure intact | ✅ ### Added still opens on a #### subsection (dbt Sync), and the tags={["Added","Changed","Removed"]} header still matches the three sections present |
EXCLUDE_OPERATIONS still coherent |
✅ Cube super admin only still 0 occurrences in api.yaml; /api/v1/regions still the sole region path; the d24cdd8 assertion would abort the sync otherwise |
One open question (not a blocker)
The SDK ships typed createRegion/applyRegion/listPrivateLinks/… methods, and as of this commit platform-client's v0.4.0 release notes say nothing about them — a consumer who sees the methods in autocomplete has no release note explaining what they are or that they're staff-gated. The previous state (note + bullets) covered that audience; this state covers the docs reader instead. That's a defensible call given the stated direction ("don't surface or mention staff-only endpoints anywhere"), and the omission is invisible to everyone except a caller who'd get a 403 anyway. Worth being deliberate about, since npm release notes are immutable: if the exclusion is ever revisited, v0.4.0 simply has a silent gap rather than a wrong statement — strictly better than the previous coupling, which I'd flagged as a nit last round. Nothing to change here.
Practical consequence, for the record: the changelog is now the client's release notes filtered by this repo's docs policy. That coupling is upstream-authored, so it stays consistent as long as both sides keep making the same call.
Prior threads
- Cursor-pagination parameter docs (thread) — re-affirmed, left unresolved as a tracking marker. Re-checked: still 0 occurrences of
Page size for cursor/Opaque cursorinapi.yaml;first/afterstill render as bareoneOf: [<type>, 'null']with no description and nomaximum: 200. Unchanged by this commit, and the fix is still.describe()/.max(200)on the cursor query schema incubejs-enterprise. No new inline comment — the existing thread covers it on the same path and line. - The other three
claudethreads are resolved and their fixes verified in earlier rounds (05cfc05exclusion,d24cdd8fail-closed assertion,4e4c819changelog note — the last of which this commit supersedes). Nothing stale left to close. - Nit 2 from the first review (drift check not wired into CI) still stands, still understandable given the source spec lives in a private repo.
Standard dimensions
Security / performance / error handling / test coverage: n/a for this commit — a 7-line deletion of generated prose, nothing executable. The staff-only exposure question is closed on both surfaces now (reference by 05cfc05, changelog by this commit), and the d24cdd8 assertion keeps the reference side closed across future syncs.
Good to merge.
…s from the routing Per direction: instead of hand-maintaining a list of super-admin-gated paths in EXCLUDE_OPERATIONS (which only protects endpoints someone remembered to add, and only re-validates the ones already listed), detect them structurally at generation time. console-server prefixes every super-admin-gated operation's OpenAPI description with SUPER_ADMIN_ONLY_DOC_MARKER (see cubedevinc/ cubejs-enterprise#14467 for the cross-reference comment on that constant). Matching on that exact text in the path loop — before applyDescription() moves the description into x-mint.content — catches any current or future staff-only operation automatically, with no per-endpoint bookkeeping in this repo. Removes the 12 manually-listed Regions/PrivateLinks entries from EXCLUDE_OPERATIONS (now redundant and would trip the fail-closed assertion from d24cdd8, since the auto-detection deletes them first). --check confirms byte-identical output to the hand-maintained list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XdLcEJjqSB78Zu4zdbo5yn
|
Per direction: upgraded the exclusion mechanism from a hand-maintained list to structural auto-detection (ab7fc3a). console-server prefixes every super-admin-gated operation's OpenAPI description with a stable marker ( Cross-referenced from the console-server side too: cubedevinc/cubejs-enterprise#14467 documents the dependency next to the marker constant. The SDK changelog no longer mentions these operations at all either (cubedevinc/cubejs-enterprise#14466, latest commit) — not even the "staff-only, excluded from public docs" note from earlier, per further direction not to surface them anywhere. Generated by Claude Code |
|
Claude finished @vasilev-alex's task in 1m 59s —— View job Review of
|
| Check | Result |
|---|---|
| Generated output unchanged | ✅ git show ab7fc3a --stat — script only; api.yaml, docs.json, introduction.mdx, changelog.mdx untouched |
Staff-only prose in api.yaml |
✅ still 0 occurrences of super admin |
| Region surface | ✅ /api/v1/regions (GET listRegions) is still the only region path, api.yaml:3609 |
| Placement in the loop | ✅ line 302 runs before kept++ and before applyDescription() (line 313), so the match sees the raw description and a fully-excluded path still drops out via if (!kept) continue |
Ordering vs. EXCLUDE_OPERATIONS |
✅ manual check first (line 294), auto second — a path in both records a match, so no interaction with the d24cdd8 assertion |
EXCLUDE_OPERATIONS after the removal |
✅ 15 entries, all still matching (the assertion at line 330 would abort otherwise) |
| Schema pruning | ✅ same deletion point as before, so the transitive-$ref closure prunes the 13 region schemas identically — confirmed by output being byte-identical |
The design is right. A hand-maintained list only protects endpoints someone remembered to add; matching a marker the server emits covers staff-only operations that don't exist yet. And doing it at the source-description level rather than post-x-mint is the correct seam.
Finding — medium
The marker match fails open on drift (inline) · extract-api.mjs:302. This trades a fail-closed check for a fail-open one: the old shape aborted when an entry stopped matching (d24cdd8), the new shape has no assertion at all. One exact .includes() on a 100-char string that must stay byte-for-byte in sync across two repos — reword the sentence upstream, drop the 🔒, swap the em dash, and the match silently returns false for every operation, all 12 pages return, and --check can't see it (both sides of the comparison contain the leak). The only signal is a console.log at line 446 that stops printing.
Two guards restore the guarantee: abort when autoExcludedCount === 0, plus a loosely-worded residual scan (/super admin only|🔒/i) over kept operations that fires precisely when the exact match doesn't. Details and code in the inline comment. Not blocking — the current output is correct and verified — but the whole point of this mechanism is that it must not miss, and right now missing is silent.
Nits
- The commit message has the ordering backwards. It says the 12 entries "would trip the fail-closed assertion from
d24cdd8, since the auto-detection deletes them first." TheEXCLUDE_OPERATIONScheck is at line 294, the auto-detection at line 302 — manual wins,matchedExcludesrecords the hit, no assertion trip. Removing them is still right (they're genuinely redundant), just not for the stated reason. Code is fine; only the rationale is off. - The cross-repo constant has no automated tie. The comment says "keep this byte-for-byte in sync with
SUPER_ADMIN_ONLY_DOC_MARKER" and points atvalidate-admin-access.ts, which is the best available given the repo split — but it's a comment, not a check. The residual scan above is what makes the drift detectable from this side.
Prior threads
- Cursor-pagination parameter docs (thread) — re-affirmed, left unresolved as a tracking marker. Unchanged by this commit; still 0 occurrences of
Page size for cursor/Opaque cursor, fix is still.describe()/.max(200)upstream. - The other three
claudethreads are resolved and outdated; nothing stale left to close. Note the thread that05cfc05closed is now closed by a different mechanism than the one it was resolved with — the inline finding above is about keeping that new mechanism honest, not about reopening the question.
Standard dimensions
Security / performance / test coverage: the only executable change is ~10 lines in a build-time script. The security-adjacent dimension is exactly the finding above — not an authorization issue (enforced server-side) but a docs-surface guarantee that's now unverified. Performance n/a (one String.includes per operation). Test coverage n/a — no harness exists for these extractors, which is what makes the self-checks in the script the de facto tests, and why the missing one matters.
Per review: the marker-based auto-exclusion added in ab7fc3a is an exact-substring match against text authored in a different repo, so it fails OPEN (not closed) if that text drifts upstream — the opposite of what d24cdd8 just bought for the old hand-maintained list. Two guards restore fail-closed without depending on the marker staying exact: abort if the marker matched zero operations (today's spec always has staff-only ones), and a residual scan over every kept operation's rendered content for "super admin" (looser than the marker itself, specifically excluding the unrelated-but-similar 🔒 "Admin only" marker, which is legitimately public). Verified both actually fire: a marker that matches nothing aborts, and the real output is unaffected (byte-identical api.yaml/docs.json/introduction.mdx). Also narrowed the marker itself to a short, stable anchor ("Cube super admin only") rather than the full two-sentence constant, which is more exposed to upstream rewrapping — safe now that the residual scan is the actual backstop either way. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XdLcEJjqSB78Zu4zdbo5yn
|
Claude finished @vasilev-alex's task in 2m 3s —— View job Review of
|
| Check | Result |
|---|---|
| Generated artifacts unchanged | ✅ git diff ab7fc3a a05c720 --stat — script only; api.yaml, docs.json, introduction.mdx, changelog.mdx byte-identical |
| Guards run before any write | ✅ both at lines 361/384, well ahead of the CHECK branch at line 564 — so --check enforces them too, which was the whole point (the previous shape's leak was invisible to --check) |
Guards run after paths is fully built |
✅ after the path loop and after the "no paths matched" and unmatchedExcludes guards, so a broken SRC_SPEC still yields the more specific error first |
Residual scan sees post-applyDescription text |
✅ x-mint.content ?? description — matches wherever the prose landed |
| Excluded ops can't be re-scanned | ✅ delete val[m] happens in the loop, so the scan only sees kept operations — no self-satisfying match |
| False-positive risk today | ✅ super admin has 0 occurrences in api-reference/; the 21 Admin only hits are the unrelated public marker and correctly don't match |
| Floor guard is live | ✅ staff-only ops do exist in the source spec every sync today, so zero is a real signal, and the comment says exactly what to do if that ever changes deliberately |
The /super admin/i choice over the prior review's /super admin only|🔒/i is a better call than what I suggested: the bare 🔒 would have matched ADMIN_ONLY_DOC_MARKER ("🔒 Admin only.") on 21 legitimately-public operations and hard-aborted every sync. Catching that before shipping it is the right kind of not-taking-the-suggestion-literally.
Finding — low
One drift shape still slips past both guards (inline) · extract-api.mjs:377. The scan has a literal space between the words. A total reword to "super-admin" is caught by the floor guard (count hits zero), but a partial drift — existing ops keep today's wording so the count stays non-zero, while a newly-added op uses the hyphenated variant — leaks silently. /super[-\s]?admins?\b/i closes it at no cost.
Nits
if (autoExcludedCount)at line 481 is now dead. The floor guard at 361 already exits when it's zero, so the conditional around the summaryconsole.logcan only be true. Harmless; an unconditional log would read more honestly.- The residual scan has no escape hatch. It aborts the whole sync if any kept operation's prose merely mentions a super admin — e.g. a public endpoint documented as "only a super admin can change this in the console". Zero occurrences today, and failing closed on an ambiguous match is the right default; just noting the fix at that point is a one-line allowlist rather than something the current code anticipates.
Prior threads
- Cursor-pagination parameter docs (thread) — re-affirmed, left unresolved as a tracking marker. Re-checked: still 0 occurrences of
Page size for cursor/Opaque cursorinapi.yaml. Unchanged by this commit; fix is still.describe()/.max(200)on the cursor query schema incubejs-enterprise. - The medium from last round is resolved by the author and verified above. All other
claudethreads were already resolved in earlier rounds — nothing stale left to close, and no duplicate inline posted (the new finding is a different line and a different root cause than the resolved thread at:301). - Nit 2 from the first review (drift check not wired into CI) still stands, and each of these guards makes it a bit more worth doing — they only fire when someone runs the script.
Standard dimensions
Security / performance / testing: the only executable change is ~35 lines of post-loop validation in a build-time script. Security-adjacent dimension — the docs-surface guarantee that staff-only pages stay unpublished — is the change, and it's now enforced rather than assumed, in both write and --check modes. Performance: one extra String.includes and one regex per kept operation, negligible. Test coverage: still no harness for these extractors, which is why the in-script self-checks are the de facto tests; the commit message reports both guards were manually verified to fire, which is the right level of diligence here.
Good to merge.
…al scan Widen the residual-scan regex from /super admin/i to /super[-\s]?admins?\b/i so a hyphenated "super-admin" rewording on a newly-added operation still trips the guard even when autoExcludedCount stays non-zero (the floor guard alone can't catch a partial drift, only a total reword).
|
Claude finished @vasilev-alex's task in 1m 2s —— View job Review of
|
| Check | Result |
|---|---|
| Generated artifacts unchanged | ✅ git diff a05c720 81adfd2 --name-only → extract-api.mjs only; api.yaml, docs.json, introduction.mdx, changelog.mdx byte-identical |
| Closes the gap it claims to | ✅ the uncovered shape was a partial drift — a newly-added op worded super-admin while existing ops keep today's wording, so autoExcludedCount > 0 and the floor guard stays silent. [-\s]? catches super-admin, superadmin, and (via s?) the plural forms |
| False positives against today's spec | ✅ 0 matches for super[-\s]?admins? anywhere in api.yaml. The 21 Admin only occurrences (the unrelated public ADMIN_ONLY_DOC_MARKER) still don't match — the widening didn't erode the distinction the last commit was careful to preserve |
\b anchor |
✅ prevents matching inside a longer word, so e.g. superadministrator in future prose won't trip it spuriously; super admins does |
| Guard ordering / placement | ✅ unchanged — both guards still sit at lines 361/384, ahead of the --check branch, so --check enforces them too |
| Marker anchor vs. scan | ✅ intentional asymmetry preserved: SUPER_ADMIN_ONLY_MARKER (:190) stays the exact 'Cube super admin only', the scan is looser. Total reword → floor guard; partial reword → scan. Both shapes now covered |
The comment rewrite is worth calling out separately: it now states why the scan exists in terms of the specific failure the floor guard can't catch, rather than just "looser than the marker." Someone tightening this regex later will see what they'd be giving up.
Findings
None. No new inline comment posted.
Residual trade, unchanged from last round and already acknowledged in the prior thread: the scan aborts on any kept operation whose prose merely mentions a super admin, with no allowlist. Slightly wider surface now (super-admin/superadmins also count), still zero occurrences, still the right fail-closed default.
Prior threads
- Cursor-pagination parameter docs (thread) — re-affirmed, left unresolved as a tracking marker. Re-checked against the current tree: still 0 occurrences of
Page size for cursor/Opaque cursorinapi.yaml;first/afterstill render as bareoneOf: [<type>, 'null']with no description and nomaximum: 200. Untouched by this commit; fix is still.describe()/.max(200)on the cursor query schema incubejs-enterprise, out of scope for a docs-only PR here. - All five other
claudethreads are resolved by the author, their fixes verified in earlier rounds (05cfc05,d24cdd8,4e4c819/922d2bc,a05c720, and now81adfd2). Nothing stale left to close. - Nit 2 from the first review (drift check not wired into CI) still stands — and is now the last remaining gap in this mechanism's story: three self-checks (
unmatchedExcludes, floor, residual scan) all guard--checkas well as write mode, but nothing runs--check. Understandable while the source spec lives in a private repo.
Standard dimensions
Security / performance / error handling / test coverage: the only executable change is one regex literal in a build-time script. Security-adjacent dimension — keeping staff-only pages unpublished across future syncs — is the change, and after this commit both drift shapes (total and partial) fail closed. Performance: same one regex per kept operation. Test coverage: still no harness for these extractors; the commit message reports the widened pattern was verified against the real spec, which matches what I see (0 matches, 21 Admin only untouched).
Good to merge.


Check List
node scripts/extract-api.mjs --checkpasses after regeneration)Description of Changes Made
Regenerates the Platform API docs (
docs-mintlify/api-reference/api.yaml,docs.json,introduction.mdx) from thecubejs-enterprisepublic OpenAPI spec, andapi-reference/changelog.mdxfrom theplatform-clientpackage'sCHANGELOG.md, via the repo's ownscripts/extract-api.mjs/extract-changelog.mjsextractors — companion PR: cubedevinc/cubejs-enterprise#14466 (@cube-dev/platform-client@0.4.0).New in this sync's changelog: Regions/PrivateLinks infrastructure-provisioning endpoints, dbt Sync status/result/log/cancel/list endpoints, data-model branch cleanup + staging-environment + validation endpoints, deployment settings/versions endpoints,
GET /api/v1/spec, a report-disconnect endpoint plus report-placement fields, and theplatform-client@0.4.0changelog entry (a breaking removal already curated upstream, plus two TS-level breaking narrowings). Note the spec-doc pages for deployment settings/versions/GET /api/v1/specwere already live from an earlier sync (this repo'sapi.yamltracks console-server's live public spec, notplatform-client's tagged releases) — they're new to the client changelog, not to these docs.Excluded from the public docs (per review): every new Regions/PrivateLinks operation —
POST/GET/PUT/DELETE /api/v1/regions/{regionId},/apply,/provisioning-status,/cloud-provider-catalog, and all 5/private-linksops — is gated "🔒 Cube super admin only," uncallable by any docs reader. Added all 12 toEXCLUDE_OPERATIONS(existing precedent: "Account-level / internal admin APIs kept out of the public docs"), which also drops the now-emptyRegion Private Linksnav group and makes theTAG_ORDERaddition for it unnecessary (reverted).GET /api/v1/regions(list) is untouched — any tenant can call it.Also flagged but not fixed here (upstream, not a bug in this PR): the cursor-pagination
first/afterparameter descriptions/max are missing from the synced spec across ~10 endpoint groups.extract-api.mjsdeliberately leaves parameter descriptions alone (see the comment atscripts/extract-api.mjs:211); the real fix is.describe()/.max(200)on the zod query schemas incubejs-enterprise, out of scope for a docs-only PR in this repo.