IntentSharp

Changelog

All notable changes to IntentSharp are documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Nothing beyond 1.0.0-rc.1 yet. 1.0.0-rc.1 itself has not been published, so its section below is still open: it lists everything the packages carry today, including the IntentSharp.Catalog and IntentSharp.Extensions.AI packages, the runtime-shape overload of TransformAsync, worked examples, the ambient scope, the packaging and seam fixes made before release, the rename to IntentSharp, the review fixes made after it (optional loggers and a null-reply rule in IntentSharp.Extensions.AI, the catalog's package readme and icon, and the license service's evaluation email), and the launch preparation of 2026-10-12 (version 1.0.0-rc.1, net10.0, Microsoft.Data.Sqlite 10.0.12, the seller name, the SQL Server deny-list, the alias removal, the checkout success page and the website); and the Azure hosting work of the same day (the website host, the purge of old evaluation records, the forwarded-headers API, and the contact address and licensor name).

[1.0.0-rc.1] - 2026-10-12

IntentSharp 1.0 is the first public release line. This release was developed under the former name SemanticSharp as version 2.0, and its packages were labelled 2.0.0-preview.1 until 2026-10-12. SemanticSharp 1.x was never published, so the first public version is 1.0.0, and 1.0.0 follows this release candidate after a live smoke test (ADR 0014). It is a rewrite: the SemanticSharp 1.x API is gone, and there is no compatibility layer. The reasons are recorded in docs/context/history.md. This version has not been published: there is no NuGet package or public release until the owner decides (see docs/context/repositories.md).

Added

  • A net10.0 build of the three library packages and the signing project, beside net8.0 and net9.0.
  • global.json at the repository root, which pins the .NET SDK to 10.0.401 with rollForward latestFeature.
  • Symbols packages (.snupkg) for all three library packages; before, only IntentSharp had one.
  • The license service's checkout success page, GET /buy/complete; a cancelled checkout returns to /buy. Stripe:SuccessUrl and Stripe:CancelUrl are set for production and development. New settings: Stripe:RequireTermsAcceptance (off by default), LicenseService:SupportEmail, LicenseService:TermsUrl and LicenseService:PrivacyUrl (docs/operations/license-service.md).
  • The website, intentsharp.com, version 1, in website/, to be served from Azure App Service by the website host below; it is not deployed yet. Hand-written pages (home, how it works, intent files, use cases, pricing, status, contact and security) are built by website/build-marketing.ps1, which pastes in the code regions, files and outputs of the compiled website/quickstart demos and highlights them; the documentation, the changelog and the legal pages (license, commercial license guide, terms of sale, privacy notice) stay DocFX pages in the same design. One stylesheet carries the C2Consultants palette, the bracket logo and self-hosted Inter and JetBrains Mono, in light and dark mode; the hand-written pages run no script. Until checkout is live, every Buy button and the evaluation key button send an email to licensing@c2consultants.net (checkoutLive in website/marketing/site.json). website/publish.ps1 checks the demo outputs, links, prices, copy, legal pages, page titles and descriptions, and the size budgets before it writes the deploy zip.
  • A website host for Azure App Service, services/IntentSharp.WebsiteHost. It serves the DocFX site at https://intentsharp.com, redirects the www host names and semanticsharp.com there, and sets the site's security headers.
  • A daily purge in the license service: evaluation records older than LicenseService:LeadRetentionDays (default 730, never less than 365) are deleted (docs/operations/license-service.md, section 9).
  • IntentClient, an instance that holds the providers, options, memory, cache and rate limiter and runs the request pipeline. Build it with IntentClientBuilder or register it with services.AddIntentSharp(...).
  • Real prompting (Prompting/PromptBuilder.cs): every request carries the intent, the serialized input value and an explicit output contract. Typed results use a JSON envelope {"value": T} with a strict JSON schema generated from the .NET type (Prompting/JsonSchemaBuilder.cs).
  • Deterministic verification (Verification/DeterministicVerifier.cs): refusal, truncation, JSON well-formedness, deserialization to T, nullability and caller validators. Findings are concrete and are sent back to the model on the next attempt.
  • Optional model-as-judge step (Verification/ModelJudge.cs) controlled by IntentClientOptions.JudgeMode and AIRequestOptions.UseJudge.
  • AIResult<T> with Success, Value, Issues (ResultIssue with a source and attempt number), Attempts, Usage, GetValueOrThrow() and GetValueOrDefault().
  • Per-request options (AIRequestOptions): attempts, token and call ceilings, timeout, model and provider overrides, validators (Validate<T>, Require<T>), cache bypass and do-not-record.
  • Collection operations that work: AIMapAsync, AIFilterAsync, AISortAsync, AIGroupAsync, AIReduceAsync and AIMapValuesAsync. Filter, sort and group exchange zero-based indices with the model, return the caller's original objects, and reject bad permutations or partitions.
  • AIClassifyAsync<TEnum> and AIConvertAsync<T> helpers.
  • Gated database runtime (Data/SemanticDatabase.cs): read-only by default, table allowlist, dry run, row cap, parameters only, transactions for writes, ExecutePlanAsync for reviewed plans and UpsertAsync. Schema discovery reads SQLite through sqlite_master and PRAGMA table_info, and other databases through INFORMATION_SCHEMA or DbConnection.GetSchema; it is tested on SQLite only.
  • SqlSafetyChecker, a guard that blocks multi-statement SQL, dangerous keywords, tables outside the allowlist and inlined literals.
  • Memory (InMemorySemanticMemory, SqliteSemanticMemory on Microsoft.Data.Sqlite), request cache (MemoryRequestCache) and rate limiter (TokenBucketRateLimiter), all wired into the client.
  • ScriptedProvider in IntentSharp.Testing for tests that never call a real model.
  • Offline test suite in tests/IntentSharp.Tests and a runnable samples project.
  • License enforcement (Licensing/). Keys are signed offline (ISK1.<payload>.<signature>, ECDSA P-256) and checked by LicenseVerifier against the public keys in LicensePublicKeys; there is no phone-home. IntentClientBuilder.Build() evaluates the key with LicenseEnforcer, and IntentClient.License exposes the client's current LicenseStatus (LicenseState, payload, message, days remaining). No key, an invalid key, an ended evaluation, a key due for renewal and a lapsed key keep working and log at Build() and at most once per IntentClientOptions.LicenseReminderInterval (default one hour) on use. At that same interval the date rules are re-applied, so a long-running client moves from licensed to renewal due to lapsed, or from evaluation to ended evaluation, on time; the signature and the gate are not checked again. The one hard stop is the version gate: a build whose ReleaseInfo.ReleaseDate is later than a paid key's maintenance-until date throws LicenseException from Build(). Evaluation keys are never gated. IntentClientOptions.FailWhenUnlicensed (default false) also throws for no key, an invalid key or an ended evaluation. License log lines use EventId 9100, "IntentSharp.License"; notice state is per client. A seat count of 0 means unlimited. See docs/architecture/licensing.md.
  • intentsharp-license, a command-line tool in tools/IntentSharp.LicenseTool with keygen, sign, verify, inspect and help commands. sign takes --seats unlimited (stored as 0) and defaults to it for Enterprise and Oem; verify and inspect read the key from standard input when it is -. The signing project, src/IntentSharp.Licensing.Signing, is not referenced by the library.
  • The license service in services/IntentSharp.LicenseService: an evaluation-key form, Stripe Checkout for annual subscriptions, a Stripe webhook that issues and renews paid keys, SQLite storage and email delivery. It is not deployed. See docs/operations/license-service.md.
  • Runtime-defined result shapes: IntentClient.TransformAsync(input, intent, JsonNode outputSchema, options) returns an AIResult<JsonNode> for a shape given as a JSON schema at run time. JsonSchemaBuilder.BuildEnvelopeFromSchema makes a strict, enveloped copy of the schema for the provider (the caller's node is not modified): it drops $schema, $id and $anchor from the root, moves $defs and definitions to the envelope root, and rewrites root-relative $ref pointers to match. The verifier checks JSON and the envelope (an envelope with keys beside value fails) and returns a detached JsonNode; schema rules are enforced through validators, which receive the JsonNode. AIRequestOptions.OutputSchema carries the schema. A literal null third argument, TransformAsync(input, intent, null), is ambiguous between this overload and the text overload; omit it or write (AIRequestOptions?)null (docs/architecture/public-api.md).
  • Worked examples: AIRequestOptions.Examples, a list of IntentExample(Input, Output), rendered in order in an "## Examples" section of the prompt after the output requirements. AIRequestOptions.Clone() copies the examples and deep-copies OutputSchema.
  • Ambient scope: IntentClient.AsAmbient() returns an IntentClientScope, AsyncLocal state that makes the client ambient for one async flow, and AmbientAIExtensions offers every AIExtensions method except Project without the client parameter, for example value.AIAsync<T>("intent"). Scopes nest and restore on dispose; disposing an outer scope ends the scopes opened inside it, and disposing twice does nothing. Outside a scope the ambient methods throw with guidance. There is still no process-wide default client (ADR 0002, addendum).
  • The IntentSharp.Catalog package, behaviour as data (docs/architecture/intent-catalog.md, ADR 0011): IntentDefinition files in JSON or YAML with a name, version, owner, intent text, shape (text, primitive, clr or schema), declarative IntentRules, named validators, examples and guardrails; IntentDefinitionParser, which reports unknown properties and duplicate names as problems with source and line; IntentRuleEngine, with one path-naming message per violation; JSON Schema validation of schema shapes; FileIntentCatalog, which watches a folder of *.intent.json, *.intent.yaml and *.intent.yml files, debounces changes, reloads atomically and keeps the last good definitions of a file that fails, and reports (but still loads) a definition whose content changed while its version stayed the same or whose version went down, compared with the previous load in the same process; InMemoryIntentCatalog; IntentRuntime, which resolves the definition on every call and tags every run with intent.name, intent.version and intent.source; IntentValidatorRegistry and IntentTypeRegistry; IntentScope and value.RunIntentAsync<T>("Name"); services.AddIntentCatalog(folder), services.AddIntentRuntime(), services.AddIntentValidator(name, ...) and services.AddIntentType<T>(); and IntentNotFoundException, IntentDisabledException and IntentShapeMismatchException. The package carries the core package's readme and icon.
  • The IntentSharp.Extensions.AI package (docs/architecture/extensions-ai.md, ADR 0012): ChatClientProvider, an IAIProvider over any Microsoft.Extensions.AI IChatClient with an optional embedding generator; UseChatClient and UseChatClientAsFallback on IntentClientBuilder; and AsAIFunction and AsAIFunctions on IntentRuntime (and on IIntentInvoker, for other intent runners), which expose catalog intents as AIFunction tools for Agent Framework agents. Tool names replace dots and other characters outside A-Z a-z 0-9 _ - with underscores. A tool returns verified JSON, or the failure with success: false, and never throws into the agent loop. ChatClientProvider translates chat-client failures into IntentSharp provider exceptions with the original as the inner exception: an HTTP status on the exception decides the type (429, 408 and 5xx are transient), other transport failures (HttpRequestException) are transient, a timeout inside the chat client is an AITimeoutException, and any other failure is a non-transient AIProviderException. The client's fallback rules then apply as for the HTTP providers, with one difference: a 408 from a chat client is transient and can fall back, while the HTTP providers treat a 408 as a non-transient AIProviderException (docs/architecture/extensions-ai.md). A chat client that returns a null ChatResponse ends the attempt with a non-transient AIProviderException ("name chat client returned no response.") instead of a NullReferenceException. UseChatClient and UseChatClientAsFallback take an optional ILogger, which logs each translated failure at Debug. AsAIFunction takes an optional ILogger, and AsAIFunctions(logger, names) and AsAIFunctions(logger) overloads exist on IntentRuntime and IIntentInvoker; an exception a tool answers with success: false is logged at Warning. Binding the tool's input argument never throws: an argument that cannot be read as text is answered with success: false and the reason.
  • Inner-exception constructors on every provider exception type, so adapters keep the original failure.
  • Sample intent files for Orders.ClassifyUrgency, Support.Summarize and Invoice.Extract in samples/IntentSharp.Samples/intents, and a catalog section in the sample that shows an edit taking effect while it runs.

Changed

  • Test status: tested against live OpenAI and Anthropic models on .NET 8 and .NET 10, and with a local model through Ollama (2026-10-13). The version stays 1.0.0-rc.1; the packages are not on NuGet yet.

  • The version is 1.0.0-rc.1 with <ReleaseDate> 2026-10-12, in Directory.Build.props (was 2.0.0-preview.1 and 2026-10-11). The license version gate reads the date, not the version, so the renumbering changes no key.

  • The licensor and seller is C2Consultants LLC: in LICENSE (the copyright line, the Licensor line, definition 1.1 and section 6.1 (trademarks)), in COMMERCIAL-LICENSE.md and in LicenseInfo.Licensor. The NuGet Authors and Company are C2Consultants LLC, and Copyright is "Copyright (c) 2025-2026 C2Consultants LLC".

  • LicenseInfo.ContactEmail is licensing@c2consultants.net and LicenseInfo.PurchaseUrl is https://intentsharp.com/pricing; both are compiled into every build and appear in the license notices.

  • The terms and the community files give the same contact address, licensing@c2consultants.net, in place of a personal one: LICENSE (the Contact line and section 12.3), COMMERCIAL-LICENSE.md (the Licensor line, "Paid licenses" and FAQ 7.8), CONTRIBUTING.md and CODE_OF_CONDUCT.md, which takes conduct reports there until a dedicated address exists.

  • The license service uses the .NET 10 forwarded-headers API for LicenseService:TrustedProxies: networks go into ForwardedHeadersOptions.KnownIPNetworks as System.Net.IPNetwork values instead of the obsolete KnownNetworks (ASPDEPR005), with no conditional compilation, because the service and its tests target net10.0 only.

  • The library targets .NET 8, .NET 9 and .NET 10. The license tool, the license service and the samples target net10.0 (was net9.0). The three library test projects target net8.0 and net10.0, so the net8.0 build is tested; the tool and service test projects target net10.0. CI sets up .NET 8 and .NET 10.

  • The core package depends on Microsoft.Data.Sqlite 10.0.12 (was 9.0.9) on every target framework, net8.0 included, so every consumer gets the 10.x package (see Security below).

  • SqlSafetyChecker deny-lists the SQL Server statement words BACKUP, DBCC, KILL, RECONFIGURE, WAITFOR, USE and DENY, matched as whole words, so a trailing statement such as BACKUP DATABASE in a SQL Server batch written without ; is refused. A column or alias named exactly one of those words is refused too; names that only contain one, such as user_id, pass. USE also refuses MySQL's USE INDEX hint and SQL Server's OPTION (USE HINT (...)) and USE PLAN query hints (docs/architecture/database-runtime.md).

  • Breaking: renamed from SemanticSharp to IntentSharp (2026-10-12, before anything was published). The product's centre is intents kept as data that reload while the application runs, and the new name says so; it is also distinct from Microsoft's Semantic Kernel, which the old name sat next to. The package ids are IntentSharp, IntentSharp.Catalog and IntentSharp.Extensions.AI, and the namespaces follow (IntentSharp.*). The entry-point types are renamed: SemanticClient to IntentClient, SemanticClientBuilder to IntentClientBuilder, SemanticOptions to IntentClientOptions, SemanticScope to IntentClientScope, AddSemanticSharp to AddIntentSharp, SemanticSharpSettings to IntentSharpSettings and SemanticSharpException to IntentSharpException; the configuration section is IntentSharp. SemanticDatabase, SemanticDatabaseException and the ISemanticMemory family keep their names, because there "semantic" describes the technique. License keys start with ISK1. instead of SSK1., and a key with the old prefix is refused as malformed. The license tool is intentsharp-license, its key files are intentsharp-license-signing-key.pem and intentsharp-license-public-key.txt, and the license service's address is https://license.intentsharp.com. The GitHub repositories keep their names until the owner renames them. See ADR 0013.

  • AddIntentSharp builds the IntentClient when the host starts (an options ValidateOnStart hook), so a license refusal or any other construction error, such as a missing provider key, stops IHost.StartAsync instead of failing the first request.

  • IntentClientBuilder refuses a primary and a fallback with the same provider name (ignoring case) with ArgumentException, so a provider name in a request or an intent always selects one provider and one rate-limit bucket.

  • The three library packages share one version and one set of package metadata: Version, ReleaseDate (the license version gate), authors, copyright and repository URLs are set in Directory.Build.props at the repository root. IntentSharp.Catalog and IntentSharp.Extensions.AI now pack with that shared version and the author set, instead of the default 1.0.0.

  • Online checkout sells Business for 11 to 50 developers, as COMMERCIAL-LICENSE.md defines it, for JSON posts to /api/checkout as well as the buy page.

  • Breaking: the license is now the IntentSharp Source-Available Commercial License 1.0. Production and commercial use require a paid license; see COMMERCIAL-LICENSE.md.

  • Breaking: all public APIs are async. Extension methods take the client explicitly, for example value.AIAsync<T>(client, intent) instead of value.AI(context), or use the client made ambient with client.AsAmbient().

  • Breaking: namespaces are IntentSharp.* (Extensions, Results, Data, Providers, Memory, Resilience, DependencyInjection, Attributes, Testing, Verification), plus IntentSharp.Catalog and IntentSharp.Extensions.AI in their own packages.

  • Breaking: configuration is IntentClientOptions and, for dependency injection, the IntentSharp configuration section bound to IntentSharpSettings.

  • [AIIntent] and [AIGuardrail] methods run through IntentClient.ExecuteIntentAsync<T>; guardrails are enforced limits, not confidence thresholds.

  • Both providers are called over HTTP with JSON-schema structured output. Default models: Anthropic claude-opus-5-5, OpenAI gpt-4.1.

Removed

  • Breaking: the three [Obsolete] GuardrailType aliases kept from SemanticSharp 1.x: MaxRecursionDepth (use MaxAttempts), MinConfidenceScore (use RequireJudge) and SchemaValidation (use OutputRequirement). They are removed before 1.0.0 because removing them later would break callers.
  • Static configuration and global state (AIEngine, AIOrchestrator, AIExecutor, SemanticRuntime and related types).
  • Confidence scores and the scoring loop (AIScorer, AILoop, MinConfidenceScore). The v1 scores were constants.
  • Synchronous wrappers over async calls.
  • The v1 sample and test files in the repository root.

Fixed

  • The input value now reaches the model. In SemanticSharp 1.x only the intent was sent.
  • Collection transforms are implemented; in SemanticSharp 1.x they were stubs.
  • Memory persistence, the rate limiter and the request cache are used by the client; in SemanticSharp 1.x they were never called.
  • Generated SQL is checked and parameterized before it runs; in SemanticSharp 1.x it was executed as returned.
  • README examples compile against the shipped API. The quick start also compiles when pasted into a console project: its statements come before the types it declares, which used to fail with CS8803.
  • The CI workflow runs: current action versions, test results and packages uploaded as build artifacts, and no publish step. It runs on pushes to main and rework/** and on pull requests to main, builds with warnings treated as errors, runs all five test projects (library, catalog, Extensions.AI, license tool and license service) in separate steps so a failing suite does not hide the others' results, and packs the three library packages.
  • Strict schemas treat $defs and definitions as maps of named schemas and make each named schema strict.
  • Runtime schemas keep working inside the envelope: $schema, $id and $anchor are removed from the caller's root, the caller's $defs and definitions sit at the envelope root, and root-relative $ref pointers, a # self-reference included, resolve from there.
  • A runtime-shape reply that carries value beside other keys fails verification with an issue naming the extra keys.
  • IntentClientScope and IntentScope end with their scope: disposing a scope ends it and every scope opened inside it, a second Dispose is a no-op, and a task still running after its scope was disposed, or a flow whose scope was disposed on another flow, no longer sees the client or runtime.
  • JsonNode, JsonElement and JsonDocument inputs are serialized into the prompt as their JSON text.
  • The license service's evaluation email called the key a 29-day evaluation. It now counts calendar days including the issue day and says "30-day", as the evaluation form, the buy page and the license documents do.

Fixed in the pre-release review (library):

  • SqlSafetyChecker keeps quoted names as written and refuses syntax whose meaning differs between database dialects (dollar quotes, # comments, -- not followed by a space, a lone carriage return inside a -- comment, /*! executable comments, nested comments). The full list is in docs/architecture/database-runtime.md.
  • UpsertAsync accepts only one INSERT, UPDATE or MERGE on its own table and refuses any DELETE in the statement. In write mode, a WITH-led statement that changes data is refused. MySQL connections are detected as MySQL rather than SQL Server.
  • AIGroupAsync reports out-of-range indices and requires every index exactly once. AIMapValuesAsync refuses null values unless the value type is nullable.
  • Strict schemas type numbers as numbers; they used to accept a numeric string. Replies are still read leniently.
  • A reply envelope with keys beside value is refused for every target except dictionaries and types with a value member of their own. Enums are read by name only.
  • Dates and times in the input are written in ISO 8601, matching their prompt labels; DataTable and DataRow inputs are labelled as JSON.
  • An unreadable success body from the OpenAI or Anthropic provider becomes a provider failure instead of escaping as a parser exception. AIValidationException carries the real status, so a 422 reports 422.
  • Only the time budget's own cancellation becomes the time-budget issue; any other cancellation propagates.
  • Per-request MaxAttempts, MaxModelCalls, MaxOutputTokens, Temperature and Timeout are checked before any model call and throw ArgumentOutOfRangeException. A time budget, per request or the client's DefaultTimeout, must be greater than zero and at most about 49.7 days, the longest a cancellation timer accepts.
  • An unknown PrimaryProvider in configuration throws instead of selecting Anthropic.
  • The memory stores and the model judge log swallowed failures at Warning, and a cache hit adds no tokens to Usage.

Fixed in the pre-release review (catalog):

  • YAML files are prescanned without recursion, so nesting deeper than 64 levels and alias loops are load problems rather than a stack overflow. Non-finite numbers and timeouts over about 49.7 days are load problems too.
  • The file watcher recovers from watcher errors and from a deleted folder. ReloadAsync on a missing folder returns a snapshot that reports the problem instead of throwing.
  • Each new definition gets its own default shape, and the runtime runs a checked private copy of each definition.
  • A date or datetime shape run as DateTime asks the model for its declared format and is checked as declared. Rule bounds compare large whole numbers exactly.

Fixed in the pre-release review (license service and tool):

  • During shutdown the outbox keeps sending queued key emails until the host's shutdown timeout.
  • A network failure reaching Stripe shows the buy page's error instead of a bare 500.
  • Enterprise key emails show "unlimited" seats. A Stripe customer name with hidden or control characters falls back to the next name, then to the email address. A request body in an unsupported charset answers 400.
  • Email:Security set to None logs an error at startup, and Auto without an SMTP login logs a warning, because both can send license keys unencrypted.
  • intentsharp-license sign reads a key file whose first PEM block is EC PARAMETERS.
  • The concurrency tests use gates instead of time windows.

Security

  • The core package no longer brings in SQLitePCLRaw 2.1.10, which NuGet's audit flags with a high-severity advisory (NU1903, GHSA-2m69-gcr7-jv3q). Microsoft.Data.Sqlite 9.0.9 brought it in; 10.0.12 brings in SQLitePCLRaw 2.1.12, which the audit does not flag.

SemanticSharp 1.x - 2025-11

The SemanticSharp 1.x line (entries 1.0.0-alpha through 1.0.0-pre.5 plus one labelled 1.0.0, October and November 2025) was the original "AI-Augmented C# Meta Runtime", under the package id SemanticSharp. It was never published to NuGet. Its design is superseded by the SemanticSharp 2.0 rework, which ships as IntentSharp 1.0; its version numbers belong to that old package id and are not IntentSharp versions. The full history, release notes and documentation are preserved in a private archive repository; see docs/context/repositories.md.

No version is tagged yet, so the version headings above have no compare or release links. They are added when the owner creates a release tag.