Skip to content

Pre-1.0 Contract API hardening — deferred items from adversarial review #1834

@roryford

Description

@roryford

Tracking issue for Contract-API findings from an adversarial review (Jun 2026) that were not fixed in the first hardening pass. The three highest-severity, freeze-irreversible items (JSONSchemaValue integer fidelity, ToolResult.ErrorKind escape hatch, BackendCapabilities tolerant decode) are handled in the feat!: harden Contract wire types before 1.0 freeze PR. The items below survived red-team scrutiny but were judged lower-severity or design-trade decisions.

Each item notes whether it is freeze-blocking (must land before the 1.0 vocabulary freeze because it can't be fixed non-breakingly after) or deferrable (can ship post-1.0).

Freeze-relevant (decide before 1.0)

  • EmbeddingBackend is thinly specified vs InferenceBackend (fix: stable reverse-scroll when prepending older messages #15). No capabilities, no thread-safety doc, dimensions undefined before loadModel, and embed(_:) has no stated guarantee that output vector count matches input count. EmbeddingError.dimensionMismatch partially mitigates. For a third-party-implementable protocol this contract is under-defined. Freeze-blocking if we want to add capabilities to the protocol later.
  • Document the throw-vs-silent-ignore rule in GenerationConfig (Replace NSLock + DispatchQueue.main.async with actor and @MainActor Task #1). The rule is principled (capability-gated guarantees like grammar throw unsupportedGrammar; advisory hints like seed/minP/jsonMode degrade silently) but is only discoverable by reading individual field docs. State the rule once at the type level. Deferrable (doc-only).
  • Vendor-specific sampler knobs on the shared GenerationConfig (Add XCTSkipIf hardware gates to hardware-dependent tests #2). llamaDRY/llamaXTC/llamaMirostatV2 (llama.cpp-only), yieldEveryNTokens (MLX-only), streamPrefillProgress (OpenAI-compat-only) are frozen into the "shared across all backends" type. Consider a backendSpecific side-channel before freeze. Freeze-blocking — can't relocate frozen public fields after 1.0.

Deferrable (post-1.0 safe)

  • GenerationConfig Codable round-trip is silently lossy (Add XCTMeasure performance baselines for hot paths #3). jsonMode/thinkingMarkers/structuredOutput are intentionally runtime-only and dropped on decode (tested), but a Codable type that drops fields on round-trip is a footgun for new callers. Add a prominent type-level doc note, or split a PersistedGenerationConfig.
  • GenerationStream idle-timeout throws CloudBackendError.timeout (chore(main): release 1.0.0 #9) from a wrapper usable by local backends too — leaky name. Consider a backend-neutral timeout error. Opt-in (idleTimeout defaults nil), so low urgency.
  • BackendCapabilities has two public names for one capability (chore: pin release-as 0.1.1 to bootstrap Release Please versioning #11): streamsToolCallArguments + the computed alias streamsToolCallArgumentDeltas, both frozen. Consider deprecating one.
  • Message (wire enum) can't represent tool/image turns (chore(main): release 0.1.1 #12). Intentional thin one-shot wire shape; richer turns go through the value/@Model types. Documenting it as a known scope boundary may suffice; a multimodal one-shot path would be a feature.

Reviewed and dismissed (no action — recorded for traceability)

Source: adversarial Contract review + red-team verification pass, Jun 2026.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions