SDKs · 6. Known issues
Every defect below was found by source read and, where the wording says "verified", reproduced against the published artifact. Six are blocking: a consumer who does the obvious thing hits them. The rest are staleness and documentation contradictions that will mislead you if you trust the affected file.
None of these affects @cuecrux/client or corecrux-client, the two SDKs chapter 0 recommends. Five of the six blocking defects are in SDKCrux packages; the sixth is a documentation contradiction in the Crux repository.
This page is reference, and it is standing rather than provisional: it is dated, it will be re-dated when it is re-verified, and defects are removed only when they are fixed, not when they become inconvenient. Nothing here is an apology. A page like this exists so you can decide with the same information we have.
6.0 The register
| # | Where | Defect | Verified how |
|---|---|---|---|
| B1 | @cuecrux/engine-client 1.1.1 | Declares types: dist/index.d.ts; the build emits dist/src/index.d.ts. The published package has no resolvable typings | Published tarball listing and three tsc resolution modes |
| B2 | @cuecrux/engine-client 1.1.1 | search() and trustReport() call routes the Engine does not serve | Absent from the Engine's OpenAPI description; absent from its route registration |
| B3 | @cuecrux/engine-client 1.1.1 | No way to send a credential, though the Engine declares X-API-Key and bearer as global security | Source read of all nine methods |
| B4 | @cuecrux/memory 0.1.0 | getFactsByEntity() types the daemon's {"facts": […]} wrapper as Fact[] | Daemon handler source against the SDK's declared type |
| B5 | cuecrux-receipt 0.1.1 | Public-scoped but depends transitively on two restricted packages. It would install broken | Manifest dependency chain |
| B6 | @cuecrux/policy-cli 0.14.1 | Public @cuecrux scope, not private, no publishConfig, invisible to the publish guard | The guard's scan roots |
| S1 | @cuecrux/memory, 6.7 | Recorded daemon spec is 0.3.1; the daemon is 0.5.52 | Both version strings |
| S2 | @cuecrux/engine-client, 6.8 | Recorded Engine spec has 157 paths; the Engine has 164. pnpm test:api-parity fails today | Path counts from both documents |
| S7 | Crux/docs/sdk-release-lifecycle.md, 6.9 | Describes as "gated future work" two changes both workflows already implement; its adoption gates are unchecked though done | The workflow files |
| S8 | Crux/docs/developer-portal.md, 6.10 | Prescribes OpenAPI regeneration for two hand-written clients | The SDK sources |
| S9 | Both repositories, 6.11 | Neither mentions the other, though they share an npm scope | Full-text search of both |
| S3–S6 | SDKCrux, 6.12 | bom.json 2026-01-20; docs/context/ 2025-11-04; a committed packages/watch/dist 2025-11-06; three dead README links | File timestamps and git log |
6.1 B1: engine-client ships no resolvable typings
The defect. @cuecrux/engine-client@1.1.1 declares "types": "dist/index.d.ts" and exports["."].types = "dist/index.d.ts" (package.json:13). Its declaration build is tsc -b tsconfig.dts.json, which inherits composite: true from the repository's base config without setting rootDir. Under composite with no explicit rootDir, TypeScript roots the output at the tsconfig directory, so declarations land in dist/src/. tsup is configured without a dts option, so nothing fills the gap.
Evidence, from the published artifact. npm pack @cuecrux/engine-client@1.1.1 on 2026-07-27 yields fifteen files. The relevant ones:
package/dist/index.cjs
package/dist/index.mjs
package/dist/src/index.d.ts <- the declarations are here
package/dist/src/fetch-guard.d.ts
package/dist/src/types.gen.d.ts
package/package.json <- which points at dist/index.d.ts
There is no package/dist/index.d.ts. Importing the package under TypeScript 5.7.3 fails in every resolution mode:
moduleResolution | Result |
|---|---|
nodenext | error TS2307: Cannot find module '@cuecrux/engine-client' or its corresponding type declarations. |
bundler | error TS2307, identical |
node10 | error TS7016: Could not find a declaration file for module '@cuecrux/engine-client'. '…/dist/index.cjs' implicitly has an 'any' type. |
tsc --traceResolution shows exactly where it gives up:
Entering conditional exports.
Matched 'exports' condition 'types'.
package.json scope '…/node_modules/@cuecrux/engine-client' has invalid type for target of specifier '.'
Failed to resolve under condition 'types'.
Consequence. The package installs and runs, dist/index.mjs exists and the import condition resolves, but a TypeScript consumer gets no types at all, and under strict the import is a hard compile error rather than a silent any. The exports map also blocks the obvious deep-import workaround: import … from "@cuecrux/engine-client/dist/src/index.js" fails with TS2307 too, because no subpath is exported.
Workaround, verified. A paths mapping in your tsconfig.json compiles clean:
{
"compilerOptions": {
"paths": {
"@cuecrux/engine-client": [
"./node_modules/@cuecrux/engine-client/dist/src/index.d.ts"
]
}
}
}
Verified 2026-07-27: with that mapping added, a file importing createEngineClient compiles with zero errors under strict and moduleResolution: nodenext. Runtime resolution is unaffected - paths is a type-only redirect.
The same latent bug sits in six sibling packages, each declaring a types path its build does not emit: @cuecrux-internal/web, @cuecrux-internal/engine, @cuecrux-internal/engine-sdk, @cuecrux-internal/mocks, @cuecrux-internal/watch, and cuecrux-receipt. None of them is published, so none has bitten yet. @cuecrux/memory gets it right; it declares types: dist/src/index.d.ts (package.json:13) - as does @cuecrux/factory, which builds declarations through tsup instead.
6.2 B2: search() and trustReport() call routes the Engine does not serve
The defect. Two of @cuecrux/engine-client's nine methods address routes that do not exist.
search() issues GET /v1/search (index.ts:130). The source acknowledges that the operation was dropped from the Engine's OpenAPI description and asserts "the endpoint is still served" (index.ts:4). That assertion no longer holds.
trustReport() issues GET /v1/answers/{id}/trust-report (index.ts:194).
Evidence. The Engine's OpenAPI description, read 2026-07-27, contains 164 paths. /v1/search is not among them; the only paths containing "search" are /v1/embeddings/search and /v1/community/search. No path contains "trust-report"; the only paths containing "trust" are three under /v1/intel/. A read of the Engine's route registration on the same date confirms that neither route is mounted, trust-report appears nowhere in the Engine source at all.
Consequence. Both methods 404 at runtime and throw a bare Error reading search failed: 404 or trust-report failed: 404, with no status property to branch on. The release note for 1.1.0 still headlines a trust-report fix for the second of these.
Workaround. There is none within the package. Use answers() for retrieval-backed answers, and call whichever Engine route you actually need directly. Note that @cuecrux-internal/mocks registers a handler for GET */search, not GET */v1/search, so it will not intercept search() either, a test suite mocking this package will not catch the 404.
6.3 B3: engine-client cannot send a credential
The defect. createEngineClient(baseUrl, fetchImpl?) takes no options object, sets no Authorization and no X-API-Key on any of its nine methods, and exposes no hook to add one (index.ts:96). Every request it issues is anonymous.
Evidence. The Engine's OpenAPI description declares two global security schemes, ApiKeyAuth, an apiKey in the header X-API-Key, described as "Obtain a key from the CueCrux console"; and BearerAuth, HTTP bearer with bearerFormat: JWT, applied at the document's security level, so they cover every route. The client sets neither. Its README's advice, "No internal headers are injected. Pass whatever authentication header your account requires" (README.md:20), names no mechanism because none exists.
Consequence. Silent. There is no error, no warning and no type-level hint; you get whatever the Engine returns to an anonymous caller, which may be a 401, may be a degraded result, and will not be what you expected.
Workaround. Wrap the fetch you pass in. The full pattern is in 3.5.
Related, same file. The @cuecrux-internal/core CueCruxClient has no auth surface either - CueCruxClientOptions carries no token or key field.
6.4 B4: getFactsByEntity() mistypes the daemon's wrapper
The defect. @cuecrux/memory's getFactsByEntity(entity) is declared Promise<Fact[]> and implemented as requestJson<Fact[]>(...) (client.ts:52, client.ts:133). The daemon returns a wrapped object.
Evidence. The handler for GET /v1/facts/entity/{entity} ends (facts.rs:625):
(StatusCode::OK, axum::Json(serde_json::json!({"facts": facts}))).into_response()
The two other clients handle this correctly. @cuecrux/client returns Promise<{facts: Fact[]}> (index.ts:122). corecrux-client does data.get("facts", []) (client.py:235). The source of the error is an inline comment asserting that the handler "returns the registered Fact schema as an array" (client.ts:131) - a reasonable inference from a spec gap, and wrong.
Consequence. No exception is thrown. The method returns the wrapper object typed as an array: .length is undefined, iteration yields nothing, and .map() throws a TypeError one frame later. Code that treats "no facts" as a valid empty result will silently do the wrong thing.
Workaround, if you are running a vendored copy:
const raw = (await client.getFactsByEntity(entity)) as unknown as { facts: Fact[] };
const facts = raw.facts ?? [];
This is the only functional bug in @cuecrux/memory. deleteFact is safe despite the same class of spec gap, because it discards the response body.
6.5 B5: cuecrux-receipt is public-scoped but depends on restricted packages
The defect. cuecrux-receipt declares publishConfig.access: 'public' with no registry override, so it targets public npm (package.json:32). Its runtime dependency is @cuecrux-internal/engine-sdk at workspace:* (package.json:24), which is published to GitHub Packages with access: restricted, and which itself depends on @cuecrux-internal/core, also restricted.
Evidence. The .npmrc routes @cuecrux-internal:registry=https://npm.pkg.github.com (.npmrc:2). A consumer installing from public npm has no such mapping and no GitHub Packages token, so the dependency resolves against registry.npmjs.org, where it does not exist.
Consequence. Publishing this package as configured would produce an uninstallable package: npm install cuecrux-receipt would fail at dependency resolution with a 404 on @cuecrux-internal/engine-sdk. It has not bitten anyone, because there is no release workflow for it and it is in no publish allowlist, the package is not on npm today. This is a loaded gun, not a fired one.
Workaround. Build it from the SDKCrux checkout, or reimplement the verification: it is canonicalJson plus BLAKE3 plus Ed25519 verify, described in 5.8.
6.6 B6: @cuecrux/policy-cli is invisible to the publish guard
The defect. apps/policy-cli is named @cuecrux/policy-cli, the public scope, is not marked private, and has no publishConfig (package.json:2). It depends on @cuecrux-internal/core, which is restricted, so publishing it would produce the same broken install as B5.
Evidence. The publish guard loads workspace packages from exactly two directories (verify-lib.ts:22):
const areas = [path.join(root, 'packages'), path.join(root, 'packages', 'internal')];
apps/ is never scanned, so no mode of verify-publish-targets can see this package, warn about it, or reject it. The workspace globs do include apps/*, so it is a full workspace member - pnpm -r publish would consider it.
Consequence. Today: none. @cuecrux/policy-cli is not on npm. The exposure is that the guard designed to stop an accidental public publish has a hole precisely where the one accidentally public-scoped package lives.
Workaround. If you maintain this repository, add "private": true to that manifest, or add apps to the guard's scan roots. If you consume it, you are building from source and nothing changes.
6.7 S1: the memory client's recorded spec is nine daemon minors old
@cuecrux/memory generates its types from a recorded copy of the daemon's OpenAPI document. That copy reports info.version 0.3.1 and contains 18 paths. The daemon's workspace version is 0.5.52 (Cargo.toml:45).
The spec was dumped at Crux commit 5b3c008, recorded in a source comment (client.ts:6). That commit is dated 2026-06-12.
Consequence. The generated types cannot describe routes added since, session archive and unarchive, the witness smoke route, and the receipts list route among them. Existing types remain valid where the wire format did not change, which is why the package still works. The refresh procedure is prose only: "fetch GET /v1/openapi.json from a daemon built from Crux main and replace openapi/openapi.json, then regenerate" (README.md:42). There is no sync script and no parity check for this package, unlike @cuecrux/engine-client, which has both.
6.8 S2: the recorded Engine spec is seven paths behind
pnpm test:api-parity byte-compares the recorded Engine spec against a sibling ../Engine checkout. It fails today.
| Document | Paths |
|---|---|
| The Engine's own OpenAPI description | 164 |
The copy recorded in packages/engine-client/openapi/openapi.json | 157 |
Both counts verified 2026-07-27. The seven paths present in the Engine and absent from the SDK's copy: /v1/receipts, /v1/receipts/{snapshotId}/reconstruct, /operator/reconstruction-inputs/{tenantId}, /internal/artifacts/{artifactId}/living-status, /v1/artifacts/{artifactId}/living-status, /v1/ops/admin/tenant-data, /v1/ops/admin/tenant-data-export.
Consequence. Three, in increasing order of annoyance. The parity test is red, so the signal it was built to give is lost. The generated types.gen.ts cannot type the seven missing routes. And PUBLIC_API.md, which is regenerated from this recorded copy and CI-enforced for freshness against it, is transitively stale; it is faithful to the recording, and the recording is behind.
A note on that file: PUBLIC_API.md does not document the SDK. It is a generated inventory of the Engine's HTTP endpoints, including roughly ninety internal/, ops/, crux/ and intel/ routes that no SDK method exposes. There is no generated reference for the TypeScript API of any package in the repository.
6.9 S7: the release-lifecycle doc describes shipped work as gated
Crux/docs/sdk-release-lifecycle.md is accurate on versioning, tag decoupling, the n−1 support policy, the release procedure and the fail-closed immutability rule. Two of its sections are not.
| Claim | Line | Contradicting evidence |
|---|---|---|
| "Target state: both items are workflow changes, gated until the supply-chain release pipeline merges" | :44 | Both changes are already live |
npm provenance, id-token: write and Trusted Publishing described as future work | :48 | sdk-typescript.yml:63 grants id-token: write; :79 publishes with --provenance; :61 states there is no NPM_TOKEN |
| PyPI Trusted Publishing described as future work | :53 | sdk-python.yml:70 states there is no PYPI_TOKEN; :81 uses the OIDC action |
| "Until cutover, the existing token-based publishes remain" | :59 | No token-based publish exists in either workflow |
| Adoption-gate checklist, all boxes unchecked | :82 | At least the two workflow-edit gates are complete |
| "Each SDK declares the API version it was generated/tested against… Generated clients are regenerated per daemon release" | :22 | Neither SDK declares such a version, in its README or its metadata. Both are hand-written, not generated |
| "Pre-1.0… Say so in the README until 1.0" | :27 | Neither README carries a pre-1.0 stability notice |
| Policy extended to packages published from other repositories | :5 | SDKCrux references this policy nowhere; its own release doc describes a different, non-tag-gated procedure |
Consequence. A reader assessing supply-chain posture from this document concludes that Trusted Publishing and provenance are pending. They are live. The document undersells the estate it governs, which is an unusual failure mode and still a failure mode: it is the document a procurement reviewer would read.
6.10 S8: the developer portal prescribes regeneration for hand-written clients
Crux/docs/developer-portal.md is otherwise accurate; its endpoints, ports and OpenAPI location are correct, and line 61 carries the clearest canonicality statement in either repository. Two gaps.
It prescribes a workflow with no applicable target. "Generated clients should be pinned to the daemon release they target and regenerated from /v1/openapi.json when the HTTP API moves" (:67). Neither in-repo SDK is generated. Both are hand-written: sdks/typescript/src/index.ts and sdks/python/src/corecrux_client/client.py are handwritten method-per-route clients with no generator step in their build. The only genuinely generated client in the portfolio is @cuecrux/memory, which lives in a different repository this document does not mention.
It cites RFC 7807 where the SDK cites RFC 9457. The portal says "Error format: RFC 7807 Problem Details" (:13); the TypeScript SDK says 9457 (index.ts:34). 9457 obsoletes 7807 and the wire format is identical, so this is a citation inconsistency, not a behavioural one.
6.11 S9: neither repository acknowledges the other
This is the finding with the widest blast radius, and it is a documentation defect rather than a code one.
| Document | Claim | Problem |
|---|---|---|
Crux/docs/developer-portal.md :61 | "The in-repo SDKs are the supported public SDK surface" | Never mentions SDKCrux, @cuecrux/memory or @cuecrux/engine-client, so a reader cannot tell whether they are out of scope deliberately or by omission |
SDKCrux/README.md :7 | The repository "contains the entire CueCrux SDK surface" | It contains neither Crux SDK. The claim is false as written |
Both statements cannot be true. Neither document mentions that the @cuecrux/ npm scope is shared across two repositories with two independent release pipelines. That is the trap chapter 0 exists to close.
6.12 Stale artefacts, in one table
None of these breaks a consumer directly. Each will mislead you if you read it as current.
| Artefact | Dated | Problem |
|---|---|---|
SDKCrux/bom.json | 2026-01-20 | A CycloneDX 1.6 SBOM with 1,252 components, generated before @cuecrux/memory existed. CI writes a fresh SBOM to an artifact directory, never back to the repository root, so the committed copy is never refreshed |
SDKCrux/docs/context/ | 2025-11-04 | A generated repository index, 379 per-file records. No record for packages/memory, for connector tests added since, or for five of the current workflows |
SDKCrux/packages/watch/dist/index.js and index.cjs | 2025-11-06 | Force-added build output in a repository whose .gitignore excludes dist/. Roughly nine months stale |
SDKCrux/packages/engine-client/dist/index.mjs | 2026-06-12 | Also force-added. Carries no .d.ts and no .cjs, so it does not satisfy the main or types fields it appears to back |
SDKCrux/packages/factory/dist/index.mjs | 2026-02-21 | As above |
SDKCrux/coverage-summary/coverage-summary.json | 2026-06-16 | A local developer artefact with absolute filesystem paths, accidentally committed. Nothing reads it |
SDKCrux/docs/releases/engine-client-1.1.0.md | - | Documents 1.1.0 while the package is 1.1.1, and headlines a trust-report fix for a route that does not exist (B2) |
Three README links in SDKCrux resolve to nothing. Verified 2026-07-27: dev_guides/sdk-assurance.md (README.md:58, :129), docs/architecture/sdk-autonomy.md (:130), and ../ApiCrux/README.md (:131). No docs/architecture/ directory exists and there is no ApiCrux repository in the workspace. A conformance script in the same repository also checks ApiCrux/, so it cannot pass.
6.13 What this page does not claim
Stated as plainly as the defects, because a disclosure with an unstated scope is worth less than one with a stated one.
- This is not an exhaustive audit of correctness. It records what a source read and a set of reproductions found on 2026-07-27. Absence from this page is not evidence of absence.
- No claim here is a security finding. None of these defects is a vulnerability. B5 and B6 are supply-chain hygiene: they describe how a bad publish could happen, not one that did.
- **The runtime behaviour of
@cuecrux/clientandcorecrux-clientwas verified by type-check and signature inspection, not by an end-to-end run against a live daemon.** The examples in chapters 1 and 2 compile and their signatures match the published artifacts; they were not executed against a running daemon as part of this work, and we say so rather than implying otherwise. - Version numbers move. Every version, path count and date here carries its verification date. Re-run the checks below rather than trusting this page indefinitely.
Reproducing the two most important checks takes under a minute:
# B1, do the published typings resolve?
npm pack @cuecrux/engine-client@1.1.1
tar tzf cuecrux-engine-client-1.1.1.tgz | grep 'd\.ts'
# 0.2, what does each registry actually serve?
npm view @cuecrux/client version
npm view @cuecrux/engine-client version
curl -s https://pypi.org/pypi/corecrux-client/json | jq -r .info.version
Sources
- packages/engine-client/package.json:13, the
typesdeclaration behind B1 - packages/engine-client/src/index.ts:4, the
/v1/searchassertion behind B2 - packages/engine-client/src/index.ts:96, the signature behind B3
- packages/memory/src/client.ts:133, the declaration behind B4
- crates/corecruxd/src/http/facts.rs:625, the daemon's wrapped response
- packages/cli/package.json:24, the restricted dependency behind B5
- scripts/verify-lib.ts:22, the guard's scan roots behind B6
- Cargo.toml:45, daemon version 0.5.52
- docs/sdk-release-lifecycle.md:42, the stale "Publish integrity" section
- docs/developer-portal.md:67, the regeneration instruction
- Published-tarball listing, three
tscresolution modes, thepathsworkaround, npm and PyPI registry state, and both OpenAPI path counts: all reproduced 2026-07-27 with TypeScript 5.7.3 and npm 11 - The CueCrux Engine is closed-source. Claims about its route registration were verified by source read on 2026-07-27; no line-level link is published for it, in line with this documentation set's rule that closed systems publish contracts and behaviour, never mechanism

