All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- 30s-timeout / 202-receipt race, narrowed (T-702).
_DEFAULT_TIMEOUT_S(python/src/lucairn/client.py) was30.0, with a comment citing "TS DEFAULT_TIMEOUT_MS = 30_000" — a constant that no longer exists.ts/src/client.ts:40moved toDEFAULT_TIMEOUT_MS = 60_000in the CON-07 fix (f874f0a, 2026-05-28 hardening audit). 30s is the gateway's default sync-wait timeout (after which it returns a 202 processing receipt with ajob_id); it is configurable (GATEWAY_WAIT_TIMEOUT), and both hosted compose files already pin it to 120s, with the live pilot box running an even higher override. The Python SDK's stale 30s default meant a caller against an unconfigured gateway on a ~30s turn timed out exactly at the receipt handoff and never saw thejob_idto poll. Bumped_DEFAULT_TIMEOUT_Sto60.0and the comment now cites the live TS constant — this restores cross-SDK parity with TS and narrows the race, but does not structurally close it against a gateway configured above 60s; callers with long-running turns (e.g. L3) should pass an explicit timeout at or above the gateway's configured wait. No API surface change — behavior- only default change; callers who don't override the timeout now wait up to 60s (was 30s) before a timeout error on a dead/unreachable gateway.
- 30s-timeout / 202-receipt race, narrowed (T-702). Same defect and
fix as
Python 1.4.1above, ingo/lucairn.go'sDefaultTimeoutconstant (was30 * time.Second, citing the same dead TS constant). Bumped to60 * time.Second, comment now cites the livets/src/client.ts:40value and the gateway's default (configurable) 30s sync-wait boundary — see thePython 1.4.1entry above for the full boundary detail; this bump restores cross-SDK parity with TS and narrows the race, it does not structurally close it against a gateway configured above 60s. No API surface change; callers who don't override the timeout now wait up to 60s (was 30s) before a timeout error on a dead/unreachable gateway.
- Broken 1.2.8 publish (missing
dist/).1.2.8shipped to npm with onlyLICENSE,README.md, andpackage.json— no compiled code — becausedist/is gitignored and there was no build hook to run it before packing. Publishing from a clean checkout (no prior localnpm run build) packed zero code;1.2.7only worked becausedist/happened to already exist in the publisher's working directory at the time. - Added
"prepack": "npm run build"tomcp-server/package.jsonscripts.prepackruns automatically before bothnpm packandnpm publish, so publishing now always compilesdist/first regardless of the publisher's local build state. - Version bumped 1.2.8 → 1.2.9 (1.2.8's version number is burned on the registry with broken content; publish 1.2.9 instead of re-publishing 1.2.8).
- Canonical-JSON aligned to the witness signer (M3). The TS canonical-JSON
verifier (
ts/src/verify-certificate/canonical-json.ts) now reconstructs the witness signer's bytes exactly: every UTF-16 code unit>= U+0080is escaped to a lowercase\uXXXX(a supplementary-plane rune renders as its surrogate pair\uHHHH\uLLLL), and<,>,&are emitted literally (the witness does NOT HTML-escape them — the previousescapeHtmlSafe(JSON.stringify(...))path both HTML-escaped<>&and emitted raw UTF-8 for non-ASCII, diverging on both axes).U+2028/U+2029are>= U+0080and so escape through the same path. The UTF-8 byte-wise key sort (from the prior release) is unchanged. Backward-compatible, NOT a protocol bump: the signable shape is unchanged and existing ASCII certificates produce byte-identical output (the ASCII signable-freeze fixtures are untouched). The only behavioural change: a certificate whose signable carries a non-ASCII byte or<>&now verifies (it previously failedinvalid_signature). Cross-language golden test added —canonicalJsonis asserted byte-identical to the witness'spkg/veil/canonical.goCanonicalJSONon a non-ASCII vector (é, à, em-dash, unicorn emoji surrogate pair,<>&,U+2028/2029, control chars, non-ASCII keys, nested + array-of-maps), via a fixture shared with the Go and Python golden tests. Closes the latent witness↔SDK divergence (M3). Publish before any non-ASCIIorg_id/client_idis introduced upstream.
- Canonical-JSON aligned to the witness signer (M3).
canonical_json(python/src/lucairn/verify_certificate/canonical_json.py) now serializes leaves withjson.dumps(..., ensure_ascii=True)— byte-identical to the witness signer — and the explicit<>&HTML-escaping (and the redundantU+2028/U+2029post-processing) has been removed:ensure_ascii=Trueescapes every codepoint>= U+0080to a lowercase\uXXXX(supplementary plane → surrogate pair) and emits<,>,&literally, matching the witness. Lone / mismatched UTF-16 surrogates are still rejected as a typedTypeError(now via an explicit pre-serialization guard, sinceensure_ascii=Truewould otherwise emit them). Backward-compatible, NOT a protocol bump — see thesdk 1.4.0entry. Shared cross-language golden test added.
- Canonical-JSON aligned to the witness signer (M3).
verify.CanonicalJSON(go/internal/verify/canonical.go) replaces the previousjson.Marshal(defaultSetEscapeHTML(true)) leaf encoding — which HTML-escaped<>&and emitted raw UTF-8 for non-ASCII — with an explicit port of the witness'sencodePythonAsciiString: every rune>= U+0080→ lowercase\uXXXX(supplementary plane → UTF-16 surrogate pair), control chars → short escapes or\u00XX,U+007F→�, and<,>,&emitted literally. Map keys are sorted bytewise over their UTF-8 bytes at every nesting level via explicit recursion (no longer relying onjson.Marshal's key ordering). Backward-compatible, NOT a protocol bump — the v2/v3 signable-freeze fixtures are byte-identical. Shared cross-language golden test added.
- v3 dual-protocol certificate verification (
verifyCertificate). Whencert.signable_protocol_version_emitted >= 3andcert.signable_v3_signatureis present, the SDK verifies the 13-key v3 signable map (7 v2 keys + 6 promoted carry-forwards:client_id,api_key_id,byok_exempt,redaction_manifest_hash,sanitized_fields_body_hash,tms_manifest_hash) and returnssignableVersion: 'v3'on the result. Legacy certs and dual- protocol certs served to v0.5.x callers continue verifying via the 7-key v2 path and returnsignableVersion: 'v2'. Both paths use the same Ed25519 witness key — no caller change required. (PRD criterion #7/#8.) signableVersion: 'v2' | 'v3'field onVerifyCertificateResult.api_key_id,signable_v2_signature,signable_v3_signature,signable_protocol_version_emittedoptional fields onVeilCertificatetype.deriveWitnessSignedBytesV3exported fromverify-certificate/signable.ts.normalizeIssuedAtexported fromverify-certificate/signable.ts(regression- test surface; not a primary SDK API).
issued_atRFC3339Nano normalization (H6): the witness signsissuedAt.Format(time.RFC3339Nano)which strips trailing fractional-second zeros (e.g.,.1Z), but the gateway serves the cert via protojson which zero-pads to 9 digits (e.g.,.100000000Z). The SDK now normalizes the protojson form before placingissued_atin the signable bytes, fixing ~10% of certs that would otherwise fail verify. Applied to both v2 and v3 signable reconstruction paths.
- v3 dual-protocol certificate verification —
VerifyCertificatenow dispatches onsignable_protocol_version_emitted: certs with value>= 3are verified againstsignable_v3_signature(13-key signable map); legacy certs without that field use the unchanged v2 path (7-key map, byte-identical). PRD criterion #7. Source:go/internal/verify/pipeline.go,go/internal/verify/v3_signable.go. VerifyCertificateResult.SignableVersion—"v2"or"v3"identifying which signable map was verified. PRD criterion #7.DeriveV3SignedBytes— internal v3 13-key signable reconstruction. Mirrorsdual-sandbox-architecture/services/veil-witness/internal/assembler/assembler.go:380-413field-for-field.go/internal/verify/v3_signable.go.ExtractSanitizerPayloadHash— readsredaction_manifest_hash,sanitized_fields_hash,tms_manifest_hashfrom the dsa-sanitizer claim'scanonical_payload["payload"], surviving the gateway's server-side strip pipeline. Strip-surviving discipline from BLOCKER-1 (PR #247).VeilCertificate.APIKeyID(*string, proto field 15) — surfaces the gateway API-key metadata field added in Phase B.VeilCertificate.SignableV2Signature,SignableV3Signature,SignableProtocolVersionEmitted— surfaces the dual-protocol fields from v3 certs.issued_atRFC3339Nano normalization (H6 fix, ~10% verify-failure root cause) —normalizeIssuedAtstrips trailing fractional-second zeros before placingissued_atin signable bytes. Applies to BOTH v2 and v3 paths. Protojson zero-padded form (...878143387000000000Z) now normalizes to the witness-signed form (...878143387Z).go/internal/verify/signable.go.- Real production cert round-trip test —
TestDeriveV3SignedBytes_RealProductionCert_RoundTripverifies the full real production v3 cert against the production witness public key. Passes only if v3 signable reconstruction is byte-exact (Ed25519 verify). Located atgo/internal/verify/v3_signable_test.go; fixture atgo/internal/verify/testdata/real-v3-cert.fixture.json. - v2 backward-compat test (
TestDeriveV3SignedBytes_V2BackwardCompat_SameRealCert), H6 round-trip test (TestDeriveV3SignedBytes_TrailingZeroIssuedAt_RoundTrip), 13-key freeze test (TestDeriveV3SignedBytes_SignableContainsExactlyThirteenKeys), andTestNormalizeIssuedAt_TrailingZerosStripped.
RawCert(internalparse.go) gainsSignableProtocolVersionEmitted,SignableV3Signature,RawClaims,ClientID,APIKeyID,ByokExemptfor v3 dispatch.
- v3 dual-protocol certificate verification (SDK signable-versioning v3
chain, criterion #7/#8).
verify_certificatenow dispatches onsignable_protocol_version_emitted: certs with value>= 3are verified againstsignable_v3_signatureusing the new 13-key signable map (v2's 7 keys +client_id,api_key_id,byok_exempt,redaction_manifest_hash,sanitized_fields_body_hash,tms_manifest_hash). Certs without the field fall back to the legacy 7-key v2 path (backward compat, criterion #8). VerifyCertificateResult.signable_versionfield:'v3'for new dual-protocol certs,'v2'for legacy certs (criterion #7).- New
derive_v3_signed_bytes()function inverify_certificate/v3_signable.pyimplementing the 13-key v3 reconstruction. Hash fields sourced fromdsa-sanitizerclaim'scanonical_payload(strip-surviving; body bytes are gateway-stripped but the hashes survive in the sanitizer-signed inner JSON).tms_manifest_hashisNone/nulluntil TMS rewrite Slice 5 — accepted without error. normalize_issued_at()utility exported fromverify_certificate/signable.py.
- issued_at RFC3339Nano trailing-zero mismatch (H6, ~10% verify-failure class).
The witness signs
issued_atusing Gotime.RFC3339Nanowhich strips trailing zeros (e.g..1Z). The gateway serves via protojson which zero-pads to 9 digits (e.g..100000000Z). The SDK now normalizes the served timestamp before placing it into the signable bytes. Applied to both v2 and v3 reconstruction paths. Round-trip validated against a real production v3 cert (Ed25519 signature verifies against the live production witness key).
VeilCertificatePydantic model gains optional fields:api_key_id(str|None),signable_v2_signature(str|None),signable_v3_signature(str|None),signable_protocol_version_emitted(int, default 0). All default-safe; older certs without these fields parse cleanly.
- MCP tool result now normalizes Lucairn certificate URLs to the
auth-less
/public-summaryendpoint in BOTH the human-readable trailer (already normalized in v1.2.5) AND thestructuredContent.compliancepayload (newly closed here). v1.2.5 introducedpublicCertificateUrl()but applied it only to the trailer string; themetadata.dsa_complianceobject that crosses the MCPstructuredContentboundary still carried the auth-gated/summaryURLs and returned 401 to MCP-client end-users that followed them. v1.2.6 introducespublicComplianceMetadata()which re-mapsveil_summary_urlANDveil_certificate_urlon thedsa_complianceblock before it leavesformatToolResult(), and the trailer now reads the pre-normalizedcompliance.veil_summary_urldirectly. Source:mcp-server/src/server.ts:270-308. Trailer-side bug latent from v1.0.0 through v1.2.4 (5 versions); structuredContent-side bug latent from v1.2.2 (whenstructuredContentwas first emitted) through v1.2.5 (4 versions).
publicCertificateUrl()helper inmcp-server/src/server.ts:293. Rewrites a/summarypath on a Lucairn certificate URL to the auth-less/public-summaryroute. Used by the human-readable_Lucairn certificate: …_trailer emitted byformatToolResult()so MCP-client end-users following the trailer link land on the public, auth-less endpoint rather than the auth-gated one that returns 401. Seemcp-server/src/server.ts:270-302.
- v1.2.5 still emits auth-gated
/summaryURLs insidestructuredContent.compliance; use v1.2.6. v1.2.5 is a partial fix: the newpublicCertificateUrl()helper rewrites only the trailer string and does NOT touch themetadata.dsa_complianceobject that crosses the MCPstructuredContentboundary. MCP clients that followstructuredContent.compliance.veil_summary_urlorveil_certificate_urlcontinue to hit the auth-gated route and receive 401. v1.2.6 closes the gap viapublicComplianceMetadata(). This version will be marked deprecated on npm vianpm deprecate @lucairn/mcp-server@1.2.5 "use v1.2.6 — partial fix; leaks auth-gated cert URLs in structuredContent".
- README + version table copy refreshed across the public SDK
registry surfaces (
README.md,mcp-server/README.md,python/README.md,ts/README.md,go/README.md,python/pyproject.toml,python/src/lucairn/types.py) to document the v1.2.x mcp-server lineage in lockstep with the TS/Python/Go SDK READMEs. Docs-only release; no code change. Cert-URL normalization did NOT ship in this version — that landed in v1.2.5 (trailer) and v1.2.6 (full).
mcpName: "io.github.Declade/lucairn-mcp-server"field onmcp-server/package.json. Required by the Official MCP Registry's npm-package verification step (without it,mcp-publisher publishreturnsRegistry validation failed for package.).- New
mcp-server/server.jsonat the package root with the canonical Lucairn-shaped registry metadata: stdio transport, five environment variables (LUCAIRN_API_KEYrequired + optional BYOKANTHROPIC_API_KEY/OPENAI_API_KEY+ optionalLUCAIRN_BASE_URL/LUCAIRN_TRANSPORToverrides), repository URL, andwebsiteUrlpointing at the developer documentation page. Uses the2025-12-11server-schema revision.
- Version bumped 1.2.2 → 1.2.3 so the
publish-mcp-server.ymlGitHub Action republishes with the newmcpNamefield (npm would otherwise 409 on a duplicate publish).
CHAT_TOOL_DESCRIPTOR.annotations— declares the canonical MCP hint set (readOnlyHint:false,destructiveHint:false,idempotentHint:false,openWorldHint:true, plus atitle), mirroring the gateway streamable-HTTP descriptor atservices/gateway/internal/api/mcp_streamable.go:402-424byte-for-byte.CHAT_TOOL_DESCRIPTOR.outputSchema— declares the response shape (text,model,stop_reason,usage, optionalcomplianceblock surfacingmetadata.dsa_compliance).formatToolResult()now returnsstructuredContentalongsidecontent[]. Required by the@modelcontextprotocol/sdk@~1.29.0type contract when a tool declaresoutputSchema; the canonicalcontent[]text payload is unchanged.
- Closes the Capability Quality gap on the Smithery listing (84 → 100). All annotation values match the gateway streamable-HTTP descriptor, keeping the npm package's direct-http path and the gateway's stdio-bridge path in lockstep.
- Repository metadata in
mcp-server/package.jsonnow points athttps://github.com/Declade/lucairn-sdks(the actual public repo the package is published from). The 1.2.0 manifest still carried the pre-Stage-2Declade/theveil-sdksslug, so the Repository link on the npm package page rendered a 404 to visitors and Smithery-style listing crawlers. Source code unchanged from 1.2.0.
- Opt-in
LUCAIRN_TRANSPORT=stdio-bridgemode that turns@lucairn/mcp-serverinto a thin stdio↔HTTP bridge against the gateway's streamable-HTTP MCP endpoint atPOST /mcp(live since 2026-05-06 via dual-sandbox-architecture PR #135). Reads JSON-RPC frames from stdio via the SDK'sStdioServerTransport, forwards each frame asPOST {baseUrl}/mcpwithAuthorization: Bearer lcr_live_*, and writes the gateway's reply back to stdout. Per-requestX-Upstream-Keyselection mirrors the direct-http path'spickUpstreamKeyfortools/callframes. Source:mcp-server/src/bridge.ts. - Smithery card surface (Smithery web-UI publishing path).
LUCAIRN_TRANSPORTvalidation: any value other thandirect-http(default) orstdio-bridgeexits non-zero with a clear error message naming the supported set (mcp-server/src/server.ts:64-72).
- Default transport remains
direct-http, byte-identical to v1.1.x. The bridge backend is opt-in only; no change in behaviour for callers who do not setLUCAIRN_TRANSPORT.
redaction_count: int | None = Nonefield onProxySyncResponse. Mirrors the Anthropic-compatible/v1/messages(metadata.dsa_compliance.redaction_count,dual-sandbox-architecture/services/gateway/internal/api/anthropic_types.go:331) and OpenAI-compatible/v1/chat/completions(metadata.dsa_compliance.redaction_count,…/openai_handler.go:944) emission. The/api/v1/proxy/messagespath does not currently emit the field at the top level — the SDK surface is forward-compatible so that callers receive it automatically when the gateway promotes it. Until that happens the field staysNoneand consumers should treat that as "data not available on this tier/path" rather than "zero redactions". Closes the workaround Hannah's example app at https://github.com/Declade/lucairn-example-feedback-summarizer carries (counting[TYPE_N]placeholders by regex).
__version__inlucairn/__init__.pywas pinned at1.0.0and had drifted frompyproject.toml(last at1.1.1). Both now read1.1.2. Surfaced as friction note #6 in Sim 1 M4 (Opus Advisor/specs/sim1-m4-build-app.md).
ISOLATION_PROBE_BYOK_EXEMPTliteral value onIsolationProbeStatusand the matching probe-status enum surface, mirroring the gateway'sISOLATION_PROBE_BYOK_EXEMPTproto enum (dual-sandbox-architectureproto field onIsolationProbeStatus).byok_exempt: bool = Falsefield onVeilVerificationResult(proto field number 9 onVerificationResult). Surfaces the gateway's BYOK-exempt verification flag while keeping backward-compat with older certs that omit the field.- BYOK-exempt cert fixture (signed with the existing test keypair) plus parse + verify tests asserting end-to-end witness verification on byok_exempt certs.
- Backward-compat coverage:
verify_certificateis now exercised against a pre-byok_exempt-shape cert to lock in that the 7-key witness signable map has not regressed (DRIFT-002). - Signable freeze test (
TestSignableFreeze) — pinsderive_witness_signed_bytes(cert_go_signed_reference)byte-for-byte against the newsignable-go-reference.hexfixture (TOB-001). Catches any future change to the 7-key signable map at the byte-identity layer rather than only at the signature-verification layer.
ISOLATION_PROBE_BYOK_EXEMPTliteral added to theIsolationProbeStatusunion type.byok_exempt?: booleanoptional field on theVeilVerificationResultinterface (proto field number 9). Optional rather than defaulted so the wire-absent state remains observable to TS callers.- New BYOK-exempt cert fixture
(
ts/src/verify-certificate/__fixtures__/cert-byok-exempt.json), signed with the existing test keypair so SDK verification passes end-to-end. - Parse + verify tests for the byok_exempt path.
- Backward-compat coverage:
verifyCertificateis now exercised against a pre-byok_exempt-shape cert to lock in that the 7-key witness signable map has not regressed (DRIFT-002). - Signable freeze test
(
describe('deriveWitnessSignedBytes — signable freeze (TOB-001)')) — pinsderiveWitnessSignedBytes(cert-go-signed-reference)byte-for-byte against the newsignable-go-reference.hexfixture (TOB-001).
ByokExempt boolfield onVeilVerificationResultwithjson:"byok_exempt,omitempty"(proto field number 9 onVerificationResult).- Test asserting the field round-trips through SDK JSON parse and is surfaced on the parsed cert.
- Signable freeze test (
TestDeriveSignedBytes_MatchesSignableFreezeHexTestDeriveSignedBytes_SignableContainsExactlySevenKeys) ingo/internal/verify/canonical_test.go— pinsDeriveSignedBytesbyte-for-byte against the newsignable-go-reference.hexfixture (TOB-001) and asserts the 7-key invariant structurally.
- Cross-language docstring on the
ByokExemptfield documenting the Python / TS / Go absence-vs-false semantic asymmetry (DRIFT-001 / TOB-003).
Initial public releases of @lucairn/mcp-server (1.0.0), lucairn
on PyPI (0.1.0), the github.com/declade/theveil-sdks/go Go module
(v0.1.0), and the TypeScript surface that preceded the
@lucairn/sdk rename. Listed under a single header because the
three language surfaces shipped against the same gateway contract
on the same day; the per-package entries below give the per-surface
detail.
- MCP server [1.0.0] — new
@lucairn/mcp-serverpackage atmcp-server/. Stdio-transport Model Context Protocol server that wraps the Lucairn gateway'sPOST /api/v1/mcp/messagesendpoint (Anthropic Messages API-compatible) and exposes it to Claude Desktop and any other MCP client as a single tool,chat_via_lucairn. Pinned to@modelcontextprotocol/sdk^1.29.0. Supports bothDSA_*andLUCAIRN_*env-var prefixes for backward-compat during the Stage 3 rebrand. No@lucairn/sdkdependency — HTTP-direct to the gateway.dist/is the published surface;npx -y @lucairn/mcp-serveris the canonical Claude Desktop entry pertheveil-website/src/app/[lang]/developer/mcp/page.tsx:9-21. - Python [0.1.0] — first full implementation.
theveilon PyPI.TheVeilclient withmessages,get_certificate,verify_certificate. Six typed exception classes (TheVeilErrorbase +TheVeilConfigError/TheVeilHttpError/TheVeilResponseValidationError/TheVeilTimeoutError/TheVeilCertificateError).TheVeilResponseValidationErroris raised on a 2xx response whose body doesn't fit the declared type (wrong shape OR over-cap), distinct fromTheVeilHttpErrorwhich is reserved for non-2xx transport failures + the 202 pending wrapper. FullVeilCertificate+ sub-type Pydantic models withextra='ignore'to match TS thin-transport.httpxsync client; async client in a later arc. Cross-language byte-equivalence via Go-assembler-reference hex fixture + Go-oracle-signed cert fixture. 155+ tests passing on Python 3.10–3.13. - Go [v0.1.0] — first full implementation. Module
github.com/declade/theveil-sdks/go.theveil.ClientwithMessages,GetCertificate,VerifyCertificate. Six typed error structs satisfying atheveil.Errorinterface, all withUnwrap()forerrors.As/errors.Is:*ConfigError,*HTTPError,*ResponseValidationError,*TimeoutError,*NetworkError,*CertificateError.*ResponseValidationErrorsurfaces on a 2xx response whose body fails to decode OR fails required-field validation (json.Unmarshal is permissive — a body like{"unrelated":"junk"}would otherwise zero-value the struct), OR on a 2xx over-cap body. Functional options pattern (WithBaseURL,WithTimeout,WithHTTPClient,WithMaxResponseBytes,WithCallTimeout,WithCallHeader). Zero runtime dependencies.context.Contextfor cancellation/timeout. Cross-language byte- equivalence via the same shared fixtures. 97+ tests passing on Go 1.22–1.23;go vetandgo test -raceclean. - Monorepo scaffolding (TypeScript subdir initialized; Python and Go placeholders)
- TypeScript:
TheVeilclient withapiKeyvalidation,baseUrlnormalization, per-call timeout composition, and four typed error classes (TheVeilError,TheVeilConfigError,TheVeilHttpError,TheVeilTimeoutError). - TypeScript:
client.messages(params, options?)against/api/v1/proxy/messages, returning aProxyResponsediscriminated union over sync (200) vs. async-processing (202) gateway results. - TypeScript [0.2.0]:
client.getCertificate(requestId, options?)againstGET /api/v1/veil/certificate/{request_id}, returning a narrowPromise<VeilCertificate>. The gateway's 202 pending wrapper surfaces asTheVeilHttpError{ status: 202, body: { status: "pending", retry_after_seconds, ... } }so the happy-path type stays narrow and callers get an explicit retry signal on the error branch.requestIdisencodeURIComponent-wrapped before URL interpolation. No auto-verify — chainverifyCertificate()explicitly.
- TypeScript: proxy-specific types now carry a
Proxyprefix (ProxyMessagesRequest,ProxyResponse,ProxyPIIAnnotation) so future endpoint families can introduce their own non-conflicting type names. - Breaking — TypeScript:
TheVeil.apiKeyis now a JS private class field (#apiKey). Readingclient.apiKeyreturnsundefinedat runtime and is a TS error at compile time. The constructor input shape{ apiKey, baseUrl?, timeoutMs? }is unchanged.
- TypeScript: API key storage moved to a JS private class field so the
credential cannot leak through
JSON.stringify,util.inspect, structured-clone, or compile-time property access on the client instance.