diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 28e87c82..fca741dd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -46,10 +46,13 @@ jobs:
run: |
dart test --platform chrome \
test/schemas/numeric_platform_parity_test.dart \
+ test/schemas/duration_range_test.dart \
+ test/schemas/nullability_export_contract_test.dart \
test/schemas/extensions/numeric_extensions_test.dart \
test/schemas/extensions/list_schema_extensions_test.dart \
test/utils/collection_utils_test.dart \
test/json_schema_import_test.dart \
+ test/json_schema_vocabulary_contract_test.dart \
test/json_schema_import_api_test.dart
- name: Test JSON Schema import bridge in Chrome
diff --git a/PUBLISHING.md b/PUBLISHING.md
index d1a4e064..8c690a47 100644
--- a/PUBLISHING.md
+++ b/PUBLISHING.md
@@ -36,8 +36,9 @@ For example, packages at 1.3.0 can still declare `ack: ^1.2.0` when they use onl
Pub workspaces manage one local dependency resolution, not package versions.
Keep hosted version constraints on sibling dependencies and `resolution:
workspace` in members; do not add local path overrides to publishable manifests.
-Keep explicit workspace paths because glob patterns require Dart 3.11 and this
-repository supports Dart 3.9. The root and `ack_example` are private and are not
+Keep the explicit workspace paths aligned with the package list. The supported
+floor is Dart 3.11.0 (Flutter 3.41.2 for the Firebase adapter); preflight checks
+those versions separately. The root and `ack_example` are private and are not
part of the five-package release.
References: [Melos versioning](https://melos.invertase.dev/commands/version),
diff --git a/analysis_options.yaml b/analysis_options.yaml
index c80f7ac5..1f25b11c 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -36,7 +36,7 @@ dart_code_metrics:
prefer-prefixed-global-constants: false
avoid-returning-widgets: false
prefer-trailing-comma: false
- # Enum dot-shorthands require Dart 3.10; the workspace SDK floor is 3.9.
+ # Keep explicit enum qualification as the workspace style.
prefer-shorthands-with-enums: false
# Schema classes extend a non-const AckSchema base, so they cannot declare
# const constructors.
diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx
index 102a32ce..ada4a01e 100644
--- a/docs/getting-started/installation.mdx
+++ b/docs/getting-started/installation.mdx
@@ -91,4 +91,6 @@ Import `package:ack/ack.dart` and you're ready — the [Quickstart Tutorial](./q
## Requirements
-- Dart SDK: `>=3.9.0 <4.0.0` for `ack` and `ack_generator`
+- Dart SDK: `>=3.11.0 <4.0.0` for all Ack packages.
+- Flutter: `>=3.41.2` when using `ack_firebase_ai`.
+- Dart 3.12 or later when your models use primary-constructor syntax.
diff --git a/docs/getting-started/migration-guide.mdx b/docs/getting-started/migration-guide.mdx
index f0c092f2..627b8ef0 100644
--- a/docs/getting-started/migration-guide.mdx
+++ b/docs/getting-started/migration-guide.mdx
@@ -12,10 +12,10 @@ Ack is organized into five coordinated packages that share one version number an
| Package | Role | Key Capabilities |
| --- | --- | --- |
-| **`ack`** | Core runtime & annotations | • Fluent schema builders: `Ack.string()`, `Ack.integer()`, `Ack.double()`, `Ack.number()`, `Ack.boolean()`, `Ack.list()`, `Ack.map()`, `Ack.object()`, `Ack.any()`, `Ack.literal()`, `Ack.enumString()`, `Ack.enumValues()`, `Ack.anyOf()`, `Ack.discriminated()`, `Ack.lazy()`
• Bidirectional `CodecSchema`: `Ack.date()`, `Ack.datetime()`, `Ack.uri()`, `Ack.duration()`, `.codec()`, `.transform()`
• Two-way string normalizers: `.trim()`, `.toLowerCase()`, `.toUpperCase()`
• `JsonMaybe` (`JsonAbsent` / `JsonValue`) for tri-state `PATCH` payloads
• Full JSON Schema Draft 2020-12 runtime (`Ack.fromJsonSchema()`) and exporter (`toJsonSchema()`, `toSchemaModel()`, `toJsonSchemaDraft7()`)
• Sealed `SchemaResult` (`Ok` / `Fail`) and sealed `SchemaError` hierarchy
• Unified codegen annotations in `package:ack/ack.dart`: `@Schemable()`, `@AckKey()`, and `@Validate.*` |
+| **`ack`** | Core runtime & annotations | • Fluent schema builders: `Ack.string()`, `Ack.integer()`, `Ack.double()`, `Ack.number()`, `Ack.boolean()`, `Ack.list()`, `Ack.map()`, `Ack.object()`, `Ack.any()`, `Ack.literal()`, `Ack.enumString()`, `Ack.enumValues()`, `Ack.anyOf()`, `Ack.discriminated()`, `Ack.lazy()`
• Bidirectional `CodecSchema`: `Ack.date()`, `Ack.datetime()`, `Ack.uri()`, `Ack.duration()`, `.codec()`, `.transform()`
• Two-way string normalizers: `.trim()`, `.toLowerCase()`, `.toUpperCase()`
• `JsonMaybe` (`JsonAbsent` / `JsonValue`) for tri-state `PATCH` payloads
• JSON Schema Draft 2020-12 import and runtime validation (`Ack.fromJsonSchema()`); native schemas export Draft-7, while `toJsonSchema()` preserves imported 2020-12 resources (`toJsonSchemaDraft7()` explicitly lowers them)
• Sealed `SchemaResult` (`Ok` / `Fail`) and sealed `SchemaError` hierarchy
• Unified codegen annotations in `package:ack/ack.dart`: `@Schemable()`, `@AckKey()`, and `@Validate.*` |
| **`ack_generator`** | Two-way code generator | • **Schema-first (`@Schemable()` on a top-level schema)**: Generates an immutable model class, `Model.schema`, `parse`/`safeParse`, `fromJson`/`toJson`/`safeToJson`, typed `copyWith`, and deep `==`/`hashCode` in `.g.dart`
• **Class-first (`@Schemable()` on a `final class` or `sealed class`)**: Supports standard and Dart 3.12+ primary constructors; generates `_Schema`, `Schema`, and `_$Ack` in `.g.dart`
• **`.schema.json` codegen (`ack_generator:ack_json_schema`)**: Compiles `lib/**/*.schema.json` assets into `.schema.dart` + `.schema.g.dart` |
-| **`ack_mcp_dart`** | MCP tool adapter | • `server.registerAckTool()`, `registerAckInputTool()`, `registerAckOutputTool()`, and `result.decodeAckOutput()` for `mcp_dart` |
-| **`ack_firebase_ai`** | Firebase AI (Gemini) adapter | • `schema.toFirebaseAiSchema()` for Gemini structured output and tool declarations |
+| **`ack_mcp_dart`** | MCP tool input adapter | • `server.registerAckTool(name, input: schema, callback: ...)`, `schema.toMcpToolInputSchema()`, and `parseMcpToolArguments(...)`; callbacks return `CallToolResult` and own output validation |
+| **`ack_firebase_ai`** | Firebase AI (Gemini) adapter | • `schema.toFirebaseAiResponseJsonSchema()` for the map-based `GenerationConfig.responseJsonSchema` field |
| **`ack_json_schema_builder`** | `json_schema_builder` bridge | • Bidirectional `schema.toJsonSchemaBuilder()` and `jsbSchema.toAckSchema()` |
---
@@ -24,7 +24,7 @@ Ack is organized into five coordinated packages that share one version number an
| Change | Introduced | What Changed | Why It Changed |
| --- | --- | --- | --- |
-| **Annotations merged into `package:ack`** | `1.7.0` | `ack_annotations` was retired. `@Schemable()`, `@AckKey()`, and `@Validate.*` ship directly in `package:ack/ack.dart`. | Eliminated version skew between `ack` and a separate annotations package and reduced model files to a single `import 'package:ack/ack.dart';`. |
+| **Annotations merged into `package:ack`** | `1.7.0` | `ack_annotations` was retired and model/validation annotations moved into `ack`. In 2.0, the public annotation API is `@Schemable()`, `@AckKey()`, and `@Validate.*` from `package:ack/ack.dart`. | Eliminated version skew between `ack` and a separate annotations package and reduced model files to a single `import 'package:ack/ack.dart';`. |
| **Unified `@Schemable()` annotation** | `1.7.0` | `@Schemable()` drives both schema-first and class-first generation, replacing `@AckInfer()`, `@AckModel()`, and `@AckType()`. | One annotation and one mental model whether you start from an `AckSchema` or a hand-written Dart `class`. |
| **Unified `@Validate.*` validation namespace** | `2.0.0` | All field and parameter validation annotations—including presence (`@Validate.required()`, `@Validate.optional()`), nullability (`@Validate.notNull()`), bounds (`@Validate.min(0)`), formats (`@Validate.email()`, `@Validate.dateTime()`, `@Validate.uri()`), and collection rules (`@Validate.notEmpty()`, `@Validate.uniqueItems()`)—live under `@Validate.*` in `package:ack/ack.dart`. | Top-level 1.x classes (`@Required`, `@Email`, `@Uuid`, `@Date`, `@Uri`, `@DateTime`, `@Pattern`) collided with `package:meta` / Flutter (`Required`), `dart:core` (`Uri`, `DateTime`, `Pattern`), and `package:uuid`, forcing import prefixes (`as ack`). `@Validate.*` has zero symbol collisions and one canonical name per validation rule. |
| **Single `.g.dart` part & direct JSON emitter** | `2.0.0` | `ack_generator:ack_generator` is a `SharedPartBuilder` that emits `.g.dart` directly with no `json_serializable` or `json_annotation` dependency. | In 1.x, every model required two `part` directives (`.ack.dart` + `.ack.g.dart`) and a second `json_serializable` pass. Direct emission cuts build steps in half, uses standard `part '.g.dart';`, and coexists with Freezed or Riverpod in the same file. |
@@ -93,7 +93,7 @@ All top-level validation and presence classes were removed in `2.0.0`. Replace t
| `Ack.enumCodec(MyEnum.values)` | `2.0.0` | `Ack.enumValues(MyEnum.values)` |
| `StandardSchemaV1` (`standard_schema.dart`) | `2.0.0` | `AckSchema` + `SchemaResult` |
| `schema.toMap()` | `2.0.0` | `schema.toJsonSchema()` or `schema.toSchemaModel()` |
-| `server.registerAckModelTool(...)` (`ack_mcp_dart`) | `2.0.0` | `server.registerAckTool(name, inputSchema: Model.schema, ...)` |
+| `server.registerAckModelTool(...)` (`ack_mcp_dart`) | `2.0.0` | `server.registerAckTool(name, input: Model.schema, callback: ...)`; return a `CallToolResult` from the callback |
| `convertAckSchemaModelToBuilder(model)` (`ack_json_schema_builder`) | `2.0.0` | `schema.toJsonSchemaBuilder()` |
| `Ack.fromJsonSchema(...)` enforcing `format` by default | `2.0.0` | Pass `Ack.fromJsonSchema(schema, assertFormats: true)` (or declare `format-assertion` in `$vocabulary`) when format assertions are required |
| `schema.toJsonSchema()` lowering imported 2020-12 schemas to Draft-7 | `2.0.0` | `schema.toJsonSchema()` now preserves Draft 2020-12 resources by default; call `schema.toJsonSchemaDraft7()` when Draft-7 lowering is required |
diff --git a/docs/guides/creating-schema-converter-packages.mdx b/docs/guides/creating-schema-converter-packages.mdx
index 26b67119..023b1f44 100644
--- a/docs/guides/creating-schema-converter-packages.mdx
+++ b/docs/guides/creating-schema-converter-packages.mdx
@@ -39,10 +39,11 @@ final jsonSchema = schema.toJsonSchema();
`AckSchemaModel` describes the boundary shape, constraints, export-safe
defaults, discriminator metadata, and warnings that adapters can reuse for
non-JSON targets. JSON-map adapters can call `schema.toJsonSchema()` directly.
-Imported schemas use the existing `AckAllOfSchemaModel` and
-`AckRefSchemaModel` variants, with their preserved definition bodies attached
-to the root model. They do not introduce an import-only variant into the sealed
-model hierarchy.
+Imported JSON Schema resources can surface as `AckRawSchemaModel`, directly or
+inside composition models, preserving their dialect and resource semantics.
+Non-JSON adapters must implement support for those resources or reject them
+explicitly. Native lazy schemas surface as `AckRefSchemaModel`; supporting
+references also requires resolving their definitions, not just emitting a name.
Adapters should not traverse `AckSchema` subclasses or parse rendered JSON
Schema output as their source of truth for non-JSON formats.
@@ -141,9 +142,9 @@ repository: https://github.com/btwld/ack
issue_tracker: https://github.com/btwld/ack/issues
environment:
- sdk: '>=3.9.0 <4.0.0'
+ sdk: '>=3.11.0 <4.0.0'
# Add flutter if target SDK requires it
- # flutter: '>=3.35.0'
+ # flutter: '>=3.41.2'
dependencies:
ack: ^2.0.0-alpha.1
@@ -343,6 +344,9 @@ class SchemaConverter {
AckRefSchemaModel() => throw UnsupportedError(
'Reference schemas (Ack.lazy) are not supported by this target.',
),
+ AckRawSchemaModel() => throw UnsupportedError(
+ 'Imported JSON Schema resources are not supported by this target.',
+ ),
};
}
@@ -351,7 +355,7 @@ class SchemaConverter {
// ========================================================================
static _convertString(AckStringSchemaModel schema) {
- final enumValues = schema.allowedStringValues;
+ final enumValues = schema.enumValues;
if (enumValues != null) {
return _buildEnumSchema(enumValues, schema);
}
@@ -399,7 +403,8 @@ class SchemaConverter {
static _convertObject(AckObjectSchemaModel schema) {
final properties = {};
- for (final entry in schema.properties?.entries ?? const []) {
+ for (final entry in
+ schema.properties?.entries ?? const >[]) {
properties[entry.key] = _convertModel(entry.value);
}
@@ -1193,6 +1198,9 @@ TargetSchema _convert(AckSchemaModel schema) {
AckAllOfSchemaModel() => _convertAllOf(schema),
AckNullSchemaModel() => TargetSchema.nullValue(),
AckRefSchemaModel() => TargetSchema.ref(schema.refName),
+ AckRawSchemaModel() => throw UnsupportedError(
+ 'Implement target-specific support for imported JSON Schema resources.',
+ ),
};
}
@@ -1246,23 +1254,24 @@ TargetSchema _convert(AckSchema schema) {
### Handling Transformed Schemas
-`toSchemaModel()` projects transformed schemas from their boundary input schema
-and preserves adapter-safe metadata such as description, nullability, and
-`x-transformed`.
+`toSchemaModel()` projects codecs and transforms from their boundary input
+schema. It describes external data, not the decoded Dart runtime value.
+Decoder, encoder, and refinement callbacks still belong to Ack runtime
+validation; they cannot be reproduced by a target schema renderer.
-**Option 1: Render projected boundary model** (Recommended)
+**Render the projected boundary model**:
```dart
TargetSchema _convertTransformed(AckSchemaModel schema) {
return _convert(schema);
}
```
-**Option 2: Reject explicit transformed marker** (If target cannot tolerate transforms)
-```dart
-if (schema.extensions['x-transformed'] == true) {
- throw UnsupportedError('Transformed Ack schemas are not supported here.');
-}
-```
+Native transforms do not emit an `x-transformed` detection marker. An imported
+document may preserve that extension as ordinary annotation data, but it is not
+a reliable way to identify native callbacks. Validate returned data with the
+original Ack schema, and inspect export warnings for omitted runtime checks.
+Use a bidirectional `.codec()` when the application also needs runtime-to-wire
+encoding; `.transform()` is parse-only.
### Handling Discriminated Unions
@@ -1375,6 +1384,12 @@ class OpenApiSchemaConverter {
**Target**: GraphQL Schema Definition Language
+This illustration supports object roots with scalar fields and arrays of those
+scalars. Field names and `typeName` must already be valid GraphQL identifiers.
+Nested objects, enums, compositions, references, and imported raw resources are
+rejected until their target definitions are implemented. It is not a lossless
+translation of Ack constraints; for example, GraphQL `Int` has its own range.
+
```dart
// lib/src/converter.dart
import 'package:ack/ack.dart';
@@ -1396,15 +1411,20 @@ class GraphQlSchemaConverter {
if (schema is! AckObjectSchemaModel) {
throw UnsupportedError('Only object schema models can be converted.');
}
+ if (schema.properties?.isNotEmpty != true) {
+ throw UnsupportedError('GraphQL object definitions need at least one field.');
+ }
buffer.writeln('type $typeName {');
final required = schema.required ?? const [];
- for (final entry in schema.properties?.entries ?? const []) {
+ for (final entry in
+ schema.properties?.entries ?? const >[]) {
final fieldName = entry.key;
final fieldSchema = entry.value;
final gqlType = _getGraphQLType(fieldSchema);
- final nullableSuffix = required.contains(fieldName) ? '!' : '';
+ final nullableSuffix =
+ required.contains(fieldName) && !fieldSchema.nullable ? '!' : '';
if (fieldSchema.description != null) {
buffer.writeln(' """${fieldSchema.description}"""');
@@ -1417,22 +1437,20 @@ class GraphQlSchemaConverter {
static String _getGraphQLType(AckSchemaModel schema) {
return switch (schema) {
- AckStringSchemaModel(allowedStringValues: final values)
- when values != null && values.isNotEmpty =>
- _generateEnumType(schema),
+ AckStringSchemaModel(enumValues: final values)
+ when values != null =>
+ throw UnsupportedError('Generate enum definitions before using enums.'),
AckStringSchemaModel() => 'String',
AckIntegerSchemaModel() => 'Int',
AckNumberSchemaModel() => 'Float',
AckBooleanSchemaModel() => 'Boolean',
- AckArraySchemaModel(items: final item?) => '[${_getGraphQLType(item)}]',
- _ => 'String', // Fallback
+ AckArraySchemaModel(items: final item?) =>
+ '[${_getGraphQLType(item)}${item.nullable ? '' : '!'}]',
+ _ => throw UnsupportedError(
+ 'Only scalar fields and arrays of supported scalars are implemented.',
+ ),
};
}
-
- static String _generateEnumType(AckStringSchemaModel schema) {
- // Would need to generate enum definitions separately
- return 'EnumType';
- }
}
```
diff --git a/docs/guides/ecosystem-integrations.mdx b/docs/guides/ecosystem-integrations.mdx
index eb14b660..b0fb8a60 100644
--- a/docs/guides/ecosystem-integrations.mdx
+++ b/docs/guides/ecosystem-integrations.mdx
@@ -2,12 +2,12 @@
title: Ecosystem Integrations
---
-Every `AckSchema` (and every `@Schemable()` model via `Model.schema`) can be reused directly across Ack's official adapter packages:
+Reuse an `AckSchema` (or a `@Schemable()` model's `Model.schema`) across Ack's official adapter packages. Each adapter has its own supported schema shapes; MCP tool inputs, for example, must render as a non-null object root.
| Adapter Package | Target Ecosystem | Primary Entry Point |
| --- | --- | --- |
-| **`ack_mcp_dart`** | [Model Context Protocol (`mcp_dart`)](https://pub.dev/packages/mcp_dart) | `server.registerAckTool(...)`, `result.decodeAckOutput(...)` |
-| **`ack_firebase_ai`** | [Firebase AI Logic (`firebase_ai` / Gemini)](https://pub.dev/packages/firebase_ai) | `schema.toFirebaseAiSchema()` |
+| **`ack_mcp_dart`** | [Model Context Protocol (`mcp_dart`)](https://pub.dev/packages/mcp_dart) | `server.registerAckTool(...)`, `schema.toMcpToolInputSchema()` |
+| **`ack_firebase_ai`** | [Firebase AI Logic (`firebase_ai` / Gemini)](https://pub.dev/packages/firebase_ai) | `schema.toFirebaseAiResponseJsonSchema()` |
| **`ack_json_schema_builder`** | [`json_schema_builder`](https://pub.dev/packages/json_schema_builder) | `schema.toJsonSchemaBuilder()`, `jsbSchema.toAckSchema()` |
---
@@ -49,7 +49,7 @@ final class WeatherReport with _$WeatherReportAck {
## 2. MCP Tool Servers (`ack_mcp_dart`)
-`ack_mcp_dart` bridges `AckSchema` with `McpServer` from `package:mcp_dart/mcp_dart.dart`. It exports the tool's JSON Schema automatically, validates incoming tool arguments before your callback runs, validates structured tool output, and formats validation failures as MCP `CallToolResult(isError: true)` responses.
+`ack_mcp_dart` bridges `AckSchema` with `McpServer` from `package:mcp_dart/mcp_dart.dart`. It exports the tool's input JSON Schema, validates and decodes incoming arguments before your callback runs, and formats Ack validation failures as MCP `CallToolResult(isError: true)` responses. MCP's structural validation runs first; its failures do not reach the Ack callback.
### Install
@@ -57,43 +57,56 @@ final class WeatherReport with _$WeatherReportAck {
dart pub add ack ack_mcp_dart mcp_dart
```
-### Register a typed input + output tool
+### Register a typed input tool
-Pass `WeatherQuery.schema` and `WeatherReport.schema` directly to `server.registerAckTool`:
+Pass `WeatherQuery.schema` through `input`. The callback receives a `WeatherQuery` and returns a `CallToolResult`; output validation and serialization belong to the callback:
```dart
+import 'dart:convert';
+
import 'package:ack_mcp_dart/ack_mcp_dart.dart';
import 'package:mcp_dart/mcp_dart.dart';
import 'weather_models.dart';
void registerWeatherTools(McpServer server) {
- server.registerAckTool(
+ server.registerAckTool(
'get_weather',
description: 'Fetch the current weather for a city.',
- inputSchema: WeatherQuery.schema,
- outputSchema: WeatherReport.schema,
- callback: (query, extra) async {
- // `query` is already validated and decoded into a `WeatherQuery`
- return WeatherReport(
+ input: WeatherQuery.schema,
+ callback: (query, extra) {
+ final report = WeatherReport(
city: query.city,
temperatureC: 21.5,
);
+ // Generated toJson validates and encodes the report.
+ final json = report.toJson();
+ return CallToolResult(
+ content: [TextContent(text: jsonEncode(json))],
+ structuredContent: json,
+ );
},
);
}
```
-Other registration and client helpers:
-- `server.registerAckInputTool(name, inputSchema: ..., callback: ...)` — validates input only and returns a `CallToolResult`.
-- `server.registerAckOutputTool(name, outputSchema: ..., callback: ...)` — takes no input arguments and validates structured output.
-- `callToolResult.decodeAckOutput(WeatherReport.schema)` — validates and decodes `structuredContent` on the client side into a `SchemaResult`.
+For manual registration, use `schema.toMcpToolInputSchema()`,
+`parseMcpToolArguments(schema, args)`, and
+`ackValidationErrorToCallToolResult(error, toolName: name)`. On the client,
+validate structured output explicitly with
+`WeatherReport.schema.safeParse(result.structuredContent)`.
+The adapter does not register or advertise output schemas; use `mcp_dart`
+directly when those are required.
---
## 3. Firebase AI / Gemini Structured Outputs (`ack_firebase_ai`)
-`ack_firebase_ai` converts any `AckSchema` into a `firebase_ai` `Schema` for Gemini structured JSON output or function calling, then lets you parse the model's response back through the same Ack schema.
+`ack_firebase_ai` renders an Ack schema as the JSON-compatible map accepted by
+`GenerationConfig.responseJsonSchema`. Use a model that supports that field,
+initialize Firebase in your app, and validate the response with the same Ack
+schema. This is not a converter to Firebase's native `Schema` type or a function
+declaration helper.
### Install
@@ -111,25 +124,30 @@ import 'package:firebase_ai/firebase_ai.dart';
import 'weather_models.dart';
-Future askGeminiWeather(String prompt) async {
+Future askGeminiWeather(
+ String prompt, {
+ required String modelName,
+}) async {
final model = FirebaseAI.googleAI().generativeModel(
- model: 'gemini-2.5-flash',
+ model: modelName,
generationConfig: GenerationConfig(
responseMimeType: 'application/json',
- // Convert the Ack schema to a Firebase AI Schema
- responseSchema: WeatherReport.schema.toFirebaseAiSchema(),
+ responseJsonSchema: WeatherReport.schema.toFirebaseAiResponseJsonSchema(),
),
);
final response = await model.generateContent([Content.text(prompt)]);
- final jsonMap = jsonDecode(response.text!);
+ final jsonMap = jsonDecode(response.text ?? 'null');
// Validate and decode Gemini's response into WeatherReport
- return WeatherReport.schema.parse(jsonMap);
+ return WeatherReportSchema.fromJson(jsonMap);
}
```
-> **Note:** Gemini's OpenAPI-based schema subset does not enforce every keyword (such as regex `pattern` or custom `.refine()` callbacks) during generation. `toFirebaseAiSchema()` appends human-readable constraint descriptions so the model has full context, and `WeatherReport.schema.parse(...)` enforces the full Ack contract on the response.
+The adapter returns `schema.toJsonSchema()` unchanged. It does not append
+constraint descriptions or adapt keywords to a provider-specific subset.
+Response schemas guide generation; Ack validation remains authoritative for
+constraints and refinements that the provider does not enforce.
---
@@ -153,7 +171,7 @@ import 'package:json_schema_builder/json_schema_builder.dart' as jsb;
import 'weather_models.dart';
void main() {
- // 1. AckSchema -> json_schema_builder Schema (preserves Draft 2020-12 by default)
+ // Native Ack schemas render as Draft-7; imported 2020-12 resources are preserved.
final jsb.Schema builderSchema = WeatherQuery.schema.toJsonSchemaBuilder();
// Need legacy Draft-7 definitions lowering instead?
@@ -165,6 +183,8 @@ void main() {
required: ['city'],
).toAckSchema(assertFormats: true);
+ print(builderSchema.value);
+ print(draft7Schema.value);
final valid = importedValidator.parse({'city': 'Lisbon'});
print(valid);
}
diff --git a/docs/guides/schema-converter-quickstart.mdx b/docs/guides/schema-converter-quickstart.mdx
index cc8bbddd..14f556c6 100644
--- a/docs/guides/schema-converter-quickstart.mdx
+++ b/docs/guides/schema-converter-quickstart.mdx
@@ -37,8 +37,8 @@ version: 1.0.0-beta.1
repository: https://github.com/btwld/ack
environment:
- sdk: '>=3.9.0 <4.0.0'
- # flutter: '>=3.35.0' # Uncomment if needed
+ sdk: '>=3.11.0 <4.0.0'
+ # flutter: '>=3.41.2' # Uncomment if needed
dependencies:
ack: ^2.0.0-alpha.1
@@ -58,8 +58,6 @@ dev_dependencies:
/// schema converter for Ack validation library.
library;
-import 'package:ack/ack.dart';
-
export 'src/extension.dart';
```
@@ -100,7 +98,7 @@ class SchemaConverter {
static Map _convertSchema(AckSchemaModel schema) {
return switch (schema) {
- AckStringSchemaModel(allowedStringValues: final values)
+ AckStringSchemaModel(enumValues: final values)
when values != null =>
_convertEnum(schema),
AckStringSchemaModel() => _convertString(schema),
@@ -113,9 +111,12 @@ class SchemaConverter {
AckOneOfSchemaModel() => _convertOneOf(schema),
AckAllOfSchemaModel() => _convertAllOf(schema),
AckNullSchemaModel() => {'type': 'null'},
- AckRefSchemaModel(refName: final name) => {
- r'$ref': '#/definitions/$name',
- },
+ AckRefSchemaModel() => throw UnsupportedError(
+ 'References need target-specific definition resolution.',
+ ),
+ AckRawSchemaModel() => throw UnsupportedError(
+ 'Imported JSON Schema resources need target-specific support.',
+ ),
};
}
@@ -175,7 +176,8 @@ class SchemaConverter {
return {
'type': 'object',
'properties': {
- for (final entry in s.properties?.entries ?? const [])
+ for (final entry in
+ s.properties?.entries ?? const >[])
entry.key: _convertSchema(entry.value),
},
if (s.required != null && s.required!.isNotEmpty) 'required': s.required,
@@ -202,7 +204,7 @@ class SchemaConverter {
'type': 'string',
if (s.description != null) 'description': s.description,
if (s.nullable) 'nullable': true,
- 'enum': s.allowedStringValues,
+ 'enum': s.enumValues,
};
}
@@ -243,6 +245,11 @@ class SchemaConverter {
}
```
+This non-JSON target scaffold deliberately rejects references and imported raw
+resources rather than emitting unresolved references or discarding their
+dialect semantics. If your target accepts JSON Schema maps directly, forward
+`schema.toJsonSchema()` instead; that path preserves definitions and resources.
+
## 6. Basic Tests (15 minutes)
**`test/to__schema_test.dart`**:
diff --git a/llms.txt b/llms.txt
index f79f7a60..0c4c9b7a 100644
--- a/llms.txt
+++ b/llms.txt
@@ -305,5 +305,5 @@ validates through the union's effective branch.
| `@AckField(description: ...)` | `/// @description ...` doc tag |
| `@AckField(presence: ...)` | `@Validate.optional()`, `@Validate.required()`, `@Validate.notNull()`, or `JsonMaybe` |
| `Ack.enumCodec(MyEnum.values)` | `Ack.enumValues(MyEnum.values)` |
-| `registerAckModelTool(...)` (`ack_mcp_dart`) | `server.registerAckTool(name, inputSchema: Model.schema, ...)` |
+| `registerAckModelTool(...)` (`ack_mcp_dart`) | `server.registerAckTool(name, input: Model.schema, callback: ...)` |
| `convertAckSchemaModelToBuilder(model)` (`ack_json_schema_builder`) | `schema.toJsonSchemaBuilder()` |
diff --git a/packages/ack_firebase_ai/CHANGELOG.md b/packages/ack_firebase_ai/CHANGELOG.md
index 7890114b..44babe91 100644
--- a/packages/ack_firebase_ai/CHANGELOG.md
+++ b/packages/ack_firebase_ai/CHANGELOG.md
@@ -1,3 +1,10 @@
+## Unreleased
+
+### Documentation
+
+* Clarify that the adapter forwards `toJsonSchema()` unchanged to the map-based
+ `responseJsonSchema` field, preserving imported resource dialects.
+
## 2.0.0-alpha.1
### Breaking
diff --git a/packages/ack_firebase_ai/README.md b/packages/ack_firebase_ai/README.md
index 458a184c..aa186449 100644
--- a/packages/ack_firebase_ai/README.md
+++ b/packages/ack_firebase_ai/README.md
@@ -80,9 +80,10 @@ Future main() async {
## Schema Mapping
-The adapter returns ACK's generic Draft-7 JSON Schema renderer output. That
-means adapter behavior matches `AckSchema.toJsonSchema()`, which renders
-`AckSchema.toSchemaModel().toJsonSchema()`.
+The adapter returns `AckSchema.toJsonSchema()` unchanged. Native Ack schemas
+use the Draft-7 representation; imported Draft 2020-12 schemas preserve their
+resources and dialect. The adapter does not lower or filter provider-specific
+keywords.
| ACK schema | JSON Schema output |
| --- | --- |
diff --git a/packages/ack_firebase_ai/analysis_options.yaml b/packages/ack_firebase_ai/analysis_options.yaml
index 72cd0fb5..f1f1ae59 100644
--- a/packages/ack_firebase_ai/analysis_options.yaml
+++ b/packages/ack_firebase_ai/analysis_options.yaml
@@ -3,6 +3,8 @@
include: ../../analysis_options.yaml
analyzer:
+ exclude:
+ - build/**
language:
# This package enforces stricter typing than the workspace default.
strict-casts: true
diff --git a/packages/ack_mcp_dart/CHANGELOG.md b/packages/ack_mcp_dart/CHANGELOG.md
index a0ea9b6a..74ddb72d 100644
--- a/packages/ack_mcp_dart/CHANGELOG.md
+++ b/packages/ack_mcp_dart/CHANGELOG.md
@@ -1,3 +1,10 @@
+## Unreleased
+
+### Documentation
+
+* Correct the Dart SDK floor and document the current typed-input registration
+ API and callback-owned output validation in the ecosystem guide.
+
## 2.0.0-alpha.1
### Breaking
diff --git a/packages/ack_mcp_dart/README.md b/packages/ack_mcp_dart/README.md
index 3974bb16..09d9a71b 100644
--- a/packages/ack_mcp_dart/README.md
+++ b/packages/ack_mcp_dart/README.md
@@ -140,9 +140,9 @@ New emitted keywords require revisiting this dialect assumption.
| Component | Supported / exercised |
| --- | --- |
-| Dart | `>=3.9.0 <4.0.0` |
+| Dart | `>=3.11.0 <4.0.0` |
| ACK | `^2.0.0-alpha.1` |
-| mcp_dart | `^2.4.2` (tests resolved against 2.4.2) |
+| mcp_dart | `^2.4.2` |
| MCP stable discovery | `2026-07-28` |
| MCP initialization | `2025-11-25`, `2025-06-18` |
diff --git a/scripts/stage_min_sdk_workspace.dart b/scripts/stage_min_sdk_workspace.dart
index 46a45eac..3f07adbe 100644
--- a/scripts/stage_min_sdk_workspace.dart
+++ b/scripts/stage_min_sdk_workspace.dart
@@ -9,11 +9,11 @@ import 'src/staging.dart';
/// Copies the repository into a pure-Dart workspace that resolves on the
/// minimum Dart SDK.
///
-/// The complete workspace cannot resolve on Dart 3.9, because `flutter_test`
-/// pins `test_api 0.7.6` while the root `test` constraint and the Analyzer 10
-/// dependency of `ack_generator` require a newer `test_api`. Removing the
-/// Flutter workspace members leaves a resolvable workspace that still covers
-/// every pure-Dart package. The Flutter packages get their own staged lane
+/// The complete workspace cannot resolve on the minimum SDK, because
+/// `flutter_test` pins an older `test_api` than the analyzer dependency of
+/// `ack_generator` allows. Removing the Flutter workspace members leaves a
+/// resolvable workspace that still covers every pure-Dart package. The Flutter
+/// packages get their own staged lane
/// through `scripts/stage_package.dart`.
///
/// Usage: