diff --git a/README.md b/README.md index d5803a6..68fcf8c 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ The kit is upstream of both: it defines *what* a Specfuse spec must look like. T ## Status -**Incubating** (`v0.32.0`), Apache-2.0. Handbooks, samples, schemas, the `project-init` template, the bundled `hello-orders` example, the `specfuse-authoring` plugin (in the `specfuse/specfuse` marketplace), and the `specfuse authoring` CLI are all in place. Generator-side alignment items are tracked in [`compatibility.md`](compatibility.md#outstanding-generator-side-follow-ups). +**Incubating** (`v0.33.0`), Apache-2.0. Handbooks, samples, schemas, the `project-init` template, the bundled `hello-orders` example, the `specfuse-authoring` plugin (in the `specfuse/specfuse` marketplace), and the `specfuse authoring` CLI are all in place. Generator-side alignment items are tracked in [`compatibility.md`](compatibility.md#outstanding-generator-side-follow-ups). The kit is distributed on PyPI as `specfuse-authoring` and hosted under [`Specfuse/authoring`](https://github.com/Specfuse/authoring). The code generator it drives is distributed separately as a pinned, checksum-verified release asset (see [`generator.lock`](generator.lock)); `specfuse authoring generate` resolves, verifies, and runs it on demand. diff --git a/compatibility.md b/compatibility.md index fd127a6..34e56e7 100644 --- a/compatibility.md +++ b/compatibility.md @@ -58,7 +58,8 @@ This file tracks which Specfuse generator commits are known compatible with each | `v0.25.0` (incubating) | generator **`0.16.0`** — `Specfuse/generator-dist` release `v0.16.0`, asset `specfuse-generator-0.16.0.jar`, sha256 `dcd77128…7e8151` | **⚠ BREAKING for C# consumers with stored enums or encrypted enums, and for any C# project whose `service` emits events without `eventRuntime`.** *Minor: a generator pin bump that adds an authorable key (`x-ai.dataHandling`), four Spectral rules, and ERRORs that start failing specs which passed `0.15.0`.* Measured on the released jar against `0.15.0` (§52): `hello-orders` (fresh bundle) and a fresh `init` scaffold validate with finding lists identical to `0.15.0`'s; `hello-orders` generates 14 files whose only change is the namespace fix; `templates` is unchanged for all four languages; every new diagnostic code is in the CHANGELOG. **(1) `x-ai.dataHandling`** (#153): `personalData` + `providerRegion`, and the generated Python worker refuses to start when the configured provider region differs (`AsyncAPI_Handbook.md` §4.3). New kit rules `specfuse-async-ai-shape` (warn, closes `x-ai` over its ten documented keys), `specfuse-async-ai-data-handling-shape` (error) and `specfuse-async-ai-personal-data-region` (warn); fixture `ai-data-handling.yaml`. **(2) Breaking C#:** `PROJECT_EVENT_RUNTIME_REQUIRED` (`Project_File.md` §10); enum columns store the wire value and an encrypted enum is really encrypted, both needing data migrations (`Vendor_Extensions.md` §1.4.1, §1.5). **(3) New findings:** `ENTITY_MISSING_ID` (ERROR) / `ENTITY_MISSING_AUDIT_FIELD`; `ARAZZO_STEP_PAYLOAD_MISSING_REQUIRED` (ERROR) / `_UNDECLARED_PROPERTY`; `ASYNC_EMITS_NO_EMIT_OPERATION`, `ASYNC_WORKER_SELF_TRIGGER`, `FUNCTIONAL_TEST_TAG_UNGROUPED`; `TEST_GRANTS_GRANT_INVALID`, `TENANT_REMOTE_REFERENCE_FOREIGN_KEY_MISMATCH`, `INLINE_ENUM_SCHEMA` on an `allOf`-wrapped enum. **(4) Closed:** every §49 issue (`#2564`–`#2571`), §19, §44 item 8. **(5) Kit rules widened to match the jar:** `specfuse-xprotection-unavailable-properties-required` now covers an encrypted value object declared `storage: flatten` (the jar has required it since `0.14.0`); `specfuse-no-inline-enums` catches an `allOf`-wrapped inline enum. **(6) Ships** the business-retention docs and the `Error.code` note (§51, corrected in §52). **Spec-author action:** register `eventRuntime` or drop `x-emits` where `service` is generated; migrate enum columns and encrypted enums; give every entity an id; fix Arazzo step payloads; declare `dataHandling` on AI workers that touch personal data. | | `v0.26.0` (incubating) | generator **`0.17.0`** — `Specfuse/generator-dist` release `v0.17.0`, asset `specfuse-generator-0.17.0.jar`, sha256 `2f2026fb…9ad3` | **⚠ BREAKING for C# consumers that reference a double-pluralized worker name or a `Var`-prefixed navigation, and for clients that relied on an individual list parameter being ignored.** *Minor: a generator pin bump with consumer-breaking C# renames, a list-query behaviour change and one new ERROR path (a nested list across pillars). No kit Spectral rule changes.* Measured on the released jar against `0.16.0` (§53): `hello-orders` (fresh bundle) validates with a JSON report byte-identical to `0.16.0`'s and generates 14 byte-identical files; a fresh `init` scaffold validates with the same five finding codes; `templates` is unchanged and `extensions` differs only in the order of one value set; `check-extension-vocabulary.py --require-jar` passes. **(1) Breaking C#:** worker registration files and classes are pluralized once (`Peoples` → `People`, `CompanyConsumerRegistration.g.cs` → `CompaniesConsumerRegistration.g.cs`, `#2646`); a navigation named after its target type keeps that name (`VarClient` → `Client`, `#2642`). Hand-written code referencing the old names must be renamed; no schema change. **(2) Individual list parameters bind** in the generated C# list (`#2648`): key, filterable, enum, searchable, range and repeated values each narrow the list (`API_Handbook.md` §10.3). An enum or value-object array parameter is not bound and is reported `OPERATION_LIST_PARAMETER_NOT_BOUND` (WARNING). **(3) Pillars** (`Vendor_Extensions.md` §14.6, §14.10): a filtered C# group with `event` + `service` now builds (`#2639`, closes §44 item 9); a nested list under another pillar's parent fails `validate` with `OPERATION_NESTED_LIST_NO_RELATIONSHIP` (`#2652`); `PROJECT_EVENT_RUNTIME_REQUIRED` counts only the operations a group's scope keeps (`#2651`). **(4) New finding:** `ENTITY_TAG_UNDECLARED_PUBLISHED` (WARNING), a safe read whose C# controller sends an `ETag` its response does not declare (`#2641`, `Vendor_Extensions.md` ETag table). **(5) Fixed without a kit change:** `OPERATION_NON_SEARCHABLE_QUERY_PARAM` checks a `Basic{Entity}` list item against its own entity (`#2665`); Dart and TypeScript test builders seed a declared date (`#2594`, `#2638`); C# timer triggers record a `ScheduleHeartbeat` event (`#2666`). **(6) Ships** the corrections of authoring#157. **Spec-author action:** rename consumer references to the C# names in (1); review list operations that declare individual parameters, since they now filter; declare `ETag` on reads `ENTITY_TAG_UNDECLARED_PUBLISHED` names; flatten any list nested under another pillar's parent. | | `v0.27.0` (incubating) | generator **`0.18.0`** — `Specfuse/generator-dist` release `v0.18.0`, asset `specfuse-generator-0.18.0.jar`, sha256 `b8ef150e…5e3b1ae` | **⚠ BREAKING for any project scaffolded by kit `≤ 0.26.0` that adopts the new `Error` shape, for clients that branch on `Error.code` as a rule name, and for a spec that nests a write route under another pillar's parent.** *Minor: a generator pin bump that adds C# exception constructors, turns one generate-time failure into a `validate` ERROR and adds three findings, shipped with the kit's `Error` shape change.* Measured on the released jar against `0.17.0` (§55): `hello-orders` (fresh bundle) validates with a JSON report byte-identical to `0.17.0`'s and generates 15 files, identical but for the timestamp in `Domain.generated-paths.json`; a fresh `init` scaffold validates with the same finding codes; `templates` is unchanged and `extensions` differs only in the order of one value set; `check-extension-vocabulary.py --require-jar` passes; the §14.10 pillar matrix gives the same verdict on every row. **(1) The kit's `Error`** carries the HTTP status in an integer `code` and the rule name in `reason`, drops `status`, makes `id` a plain string and adds `type` with an `ErrorType` enum (§54, authoring#159). Breaking on the wire for a client deployed against the old shape; §54 has the migration. A new prose rule, `specfuse-error-rule-name-in-code` (warn), lists the prose still writing a rule name into `code` (authoring#163). **(2) Reason constructors** (`clabonte/generator#2671`): the six exceptions a rule refuses with take `(string reason, string message)` and set `Code`, `Type` and `Reason`; `Error.Id` follows the schema (`API_Handbook.md` §11). Closes §54's generator follow-up. CI's error-shape probe now calls all six. **(3) Pillars** (`Vendor_Extensions.md` §14.3, §14.10, `Project_File.md` §8.13.2): a write route nested under another pillar's parent fails `validate` with `OPERATION_NESTED_CROSS_PILLAR` (ERROR, `#2680`), where it used to pass and fail `generate`; `PROJECT_HELD_ENTITY_MISSING` (warn, `#2682`) and `PROJECT_CONFIGURATION_CONVERTERS_MISSING` (warn, `#2673`) report a group that would not build; a Domain group with nothing to register builds (`#2678`); a value object or enum no entity reaches follows its own `x-domain` (`#2679`). **(4) Tenancy:** a marker named `ITenantScoped` or `ISubTenantScoped` is refused at load, `PROJECT_TENANCY_MARKER_RESERVED` (`#2672`, `Project_File.md` §4.1); closes §53 item 1. **(5) Fixed without a kit change:** a schema named after an event-runtime or implicitly imported .NET type compiles (FEAT-2026-0226); snapshot doc comments cite `Change{T}` (`#2701`); the Markdown channel topology and event catalog place publishers on the producing side and list every message a multi-message worker consumes (`#2677`, `#2704`). **(6) Ideation:** the dossier records `started:` with `graduated_to` (authoring#161). **Spec-author action:** adopt §54's `Error` shape and throw rule refusals with the reason constructors; move any write operation nested under another pillar's parent to the entity's own path; register `heldEntity` in each pillar that holds, and `heldEntityConfiguration` beside `infrastructureProject`; rename a tenancy marker called `ITenantScoped`. | -| `v0.32.0` (incubating, current) | generator **`0.24.0`** — `Specfuse/generator-dist` release `v0.24.0`, asset `specfuse-generator-0.24.0.jar`, sha256 `85d1496e…c088902e7` | **⚠ BREAKING for every C# consumer of `eventRuntime` (EF migrations), for a spec with an `x-entity` lacking `aiAccess`, for AsyncAPI messages whose headers bind no `eventId` or tenant, for clients filtering on a property not in `filterableProperties` or sending an enum in another case, and for `outboxDialect: postgres`.** *Minor.* **⚠ Do not register `mcpHost` or `mcpEvalHarness` on a real manifest at this pin: a preview tool commits instead of rolling back.** Measured on the released jar against `0.23.0` (§60): `hello-orders` and a fresh scaffold validate with 0 errors; the `add-component csharp` seed builds on regenerated output. **Kit:** `fieldSets` and `untrusted` admitted by the rulesets; Arazzo `x-mcp` shape rules replaced by one warning; `check-extension-vocabulary.py` no longer falls back to its heuristic on the new `mcp-manifest` surface. Release notes: `docs/generator-releases.md`. | +| `v0.33.0` (incubating, current) | generator **`0.25.0`** — `Specfuse/generator-dist` release `v0.25.0`, asset `specfuse-generator-0.25.0.jar`, sha256 `a8649a70…5df1cc9` | **⚠ BREAKING for a tenant-scoped AsyncAPI event on a trait where the tenant is optional (`ASYNC_TENANT_HEADER_NOT_REQUIRED`), and for hand-written C# or TypeScript test code relying on wrapped exceptions, unchecked references or an invented parent.** *Minor.* Measured on the released jar against `0.24.0` (§61): `hello-orders` and a fresh scaffold validate and generate; `extensions` is unchanged in content; `templates` adds one supporting template. **Kit:** the AsyncAPI envelope rule reads `x-envelope` and leaves the tenant to the generator (#199); `check-gen-contract.sh` finds the engine beside `specfuse` (#200); `belongsTo` is documented as ownership, not a delete behaviour (#201). Release notes: `docs/generator-releases.md`. | +| `v0.32.0` (incubating) | generator **`0.24.0`** — `Specfuse/generator-dist` release `v0.24.0`, asset `specfuse-generator-0.24.0.jar`, sha256 `85d1496e…c088902e7` | **⚠ BREAKING for every C# consumer of `eventRuntime` (EF migrations), for a spec with an `x-entity` lacking `aiAccess`, for AsyncAPI messages whose headers bind no `eventId` or tenant, for clients filtering on a property not in `filterableProperties` or sending an enum in another case, and for `outboxDialect: postgres`.** *Minor.* **⚠ Do not register `mcpHost` or `mcpEvalHarness` on a real manifest at this pin: a preview tool commits instead of rolling back.** Measured on the released jar against `0.23.0` (§60): `hello-orders` and a fresh scaffold validate with 0 errors; the `add-component csharp` seed builds on regenerated output. **Kit:** `fieldSets` and `untrusted` admitted by the rulesets; Arazzo `x-mcp` shape rules replaced by one warning; `check-extension-vocabulary.py` no longer falls back to its heuristic on the new `mcp-manifest` surface. Release notes: `docs/generator-releases.md`. | | `v0.31.0` (incubating) | generator **`0.23.0`** — `Specfuse/generator-dist` release `v0.23.0`, asset `specfuse-generator-0.23.0.jar`, sha256 `b906ed3f…5ffacc6` | **⚠ BREAKING for C# projects whose `name` differs from their groups' root, for two C# groups emitting the same layer without `pillar`, for kiosk apps that relied on path-level admission, and for hand-written `[ScopesRequired]` without `OperationId`.** *Minor: a two-release pin bump (`0.22.0` and `0.23.0`, both published 2026-10-28/29), plus the project generator pin (#169), Markdown groups read from the project file (#191) and several-pillar worker guidance (#192).* Measured on the released jar against `0.21.0` (§59): `hello-orders` (fresh bundle) validates with identical finding codes (19 warnings, 0 errors); `extensions --format json` is unchanged; `templates` adds `EventActorScope` and four authorization templates (`IOperationAuthorizer`, `OperationAuthorizer`, `OperationInvocation`, `OperationAuthorizationCatalog`); `check-extension-vocabulary.py --require-jar` passes with output identical to `0.21.0`. **(1) Breaking:** per-group `basePackage` namespaces and `AMBIGUOUS_LAYER_OWNER` (`Project_File.md` §8.5, §9); kiosk-session admission per operation (`Vendor_Extensions.md` §3.3, security fix); `[ScopesRequired]` must carry a catalogued `OperationId` (`Project_File.md` §16); generated service tests assert event contents and sets; `TestSeed.SeedSpareParents` consumer step. **(2) New:** named `genericHost` pools (`Project_File.md` §17.1, already documented in #193); `IOperationAuthorizer`; `ARAZZO_ACTOR_LACKS_SCOPE` exempts 403 denial steps; nested `emit.expect` maps and `ARAZZO_MATCH_EXPRESSION_UNBOUND` (`Arazzo_Handbook.md`); `EventEnvelope.UserId` filled via `EventActorScope`. **(3) Kit:** `specfuse authoring generate` honours a project `.specfuse/generator.lock` and `SPECFUSE_GENERATOR_JAR` (#175); `generate-scenario-docs.sh` runs the project file's Markdown groups (#194); C# guide §4.4 documents the new consumer steps. Release notes: `docs/generator-releases.md`. **Spec-author action:** flag every operation a kiosk app calls; give same-layer C# groups distinct `pillar`s; C# components call `TestSeed.SeedSpareParents(context)` in `EnsureFakeData()`, set `OperationId` on hand-written `[ScopesRequired]`, and fix `using .` lines if `name` differs from the groups' root. | | `v0.30.0` (incubating) | generator **`0.21.0`** — unchanged; `Specfuse/generator-dist` release `v0.21.0`, asset `specfuse-generator-0.21.0.jar`, sha256 `a78b5ea7…27f08a94` | **Adopter docs ship with the kit.** *Minor, not patch: the overlay delivers new files into every project.* **No pin change.** `init` and `upgrade` now copy into `.specfuse/authoring/docs/`, beside `flows.html`: `component-repos.md`, `add-component.md`, `csharp-component-guide.md` with its interactive map `csharp-component.html`, and `generator-releases.md`; and the starter generated-code contract to `.specfuse/authoring/templates/component-contract/`. A component repo checked out beside its spec repo reads the guide and seeds the contract from there, and the seeded `CLAUDE.md` of a new component points its agent at the guide. Links from those docs to files the overlay does not carry (`compatibility.md`, `getting-started.md`, `generator-access.md`, `examples/`) are now GitHub URLs, held by the new CI check `scripts/check-shipped-docs.py`. The `0.21.0` entry in `generator-releases.md` is marked published. **Also fixed:** `upgrade` counted every changed kit file as "locally-modified or unowned" in its summary, so an ordinary upgrade reported overwrites with no warning to explain them; only files it actually warns about are counted now. **Spec-author action:** none; run `specfuse authoring upgrade` to receive the docs. | | `v0.29.0` (incubating) | generator **`0.21.0`** — `Specfuse/generator-dist` release `v0.21.0`, asset `specfuse-generator-0.21.0.jar`, sha256 `a78b5ea7…27f08a94` | **⚠ BREAKING for a spec whose operation emits another entity's event, for C# components of a project that declares `tenancy`, and for event handlers still overriding `HandleCoreAsync`.** *Minor: a two-release pin bump (`0.20.0` was never published to `generator-dist`, so it ships here), with breaking validation and runtime changes, four new spec surfaces and the end of the release hold on `clabonte/generator#2882`–`#2884`.* Measured on the released jar against `0.19.0` (§58): `hello-orders` (fresh bundle) validates with identical finding codes and generates the same files; a fresh `init` scaffold validates with the same 19 codes; `extensions` adds only `x-entity.visibility`; `templates --language csharp` adds `hostedTopicConsumer`, `tenantIsolationTest`, `bareIgnoreSourceScanTest` and 32 templates; `check-extension-vocabulary.py --require-jar` passes once `specfuse-xprotection-shape` accepts `unmask`. **(1) Breaking:** `OPERATION_EMITS_FOREIGN_ENTITY` (ERROR, `API_Handbook.md`); `TRIGGER_REQUIRES_EVENT_RUNTIME_IMPL` (load failure, `Project_File.md`); `ASYNC_SUBSCRIPTION_FILTER_TOO_LONG` (ERROR on a `filterOverride`, `AsyncAPI_Handbook.md`); `HandleAsync` abstract and `HandleCoreAsync` removed; C# projects with `tenancy` get a generated `Tenant` query filter and must register an `IRecordVisibilityResolver`; hidden rows answer 404, not 403; Python AI workers need `primary_model`. **(2) New spec surfaces** (`Vendor_Extensions.md`): `uniqueBy` `where` / `nullsDistinct`; `x-protection.unmask` and `x-unmask` on PUT/PATCH (#182); `x-entity.visibility`. `specfuse-xentity-shape` and `specfuse-xprotection-shape` widened to match, fixture `uniqueby-visibility-unmask.yaml`. **(3) Project file** (`Project_File.md` §4.1, §17, §18): `workerPlacement`, `tenancy.enforcement: route`, `functionalTests.emitAssertions`, `generateUserHelper`; `tenancy` no longer required for services or the event runtime. **(4) Kit:** `add-component` drops both refusals and derives Python roots as the generator now does, and its C# seed now configures JWT bearer authentication: since `0.20.0` registers the role filters itself, a seed with no scheme answered every role-gated request with a 500 (caught by `example-regen` on this bump). The C# guide documents both and the `Tenant` filter. Release notes: `docs/generator-releases.md`. **Spec-author action:** move a foreign `x-emits` entry to the owning entity's operation; register `eventRuntimeImpl` beside any topic trigger; C# components with `tenancy` register an `IRecordVisibilityResolver` (or set `tenancy.enforcement: route` while migrating); replace `HandleCoreAsync` overrides; set `_PRIMARY_MODEL` for Python AI workers. | @@ -1670,6 +1671,32 @@ This bump processes the consumer's `uniqueby-nulls-distinct-where.md` and `unmas **Handoffs adopted in this release:** the MCP vocabulary and manifest, `x-effects`, `x-mcp` retirement and `x-trigger-when` enforcement. A client-writable reference to a global entity is documented as a known gap; the generator-side diagnostic and opt-in check stay open. A Spectral rule with a file `$ref` inside `functionOptions.schema` validates nothing, as reported from Spectral 6.16.2; no kit rule uses one. +### 61. Generator `0.25.0` pin bump (kit `0.33.0`) + +**How it was measured.** On the released jar (`v0.25.0`, sha256 `a8649a7062835134826aa49a460edfc8a3890a278e2b2923ccd0f78745df1cc9`, downloaded from `Specfuse/generator-dist` and checked against its `.sha256` asset), with `0.24.0` (`85d1496e…c088902e7`) as the baseline. + +- `hello-orders`, freshly bundled: `validate` and `generate` exit 0 on both jars, with identical finding codes and the same 18 files. The only generated difference is `Domain.generated-paths.json`. +- Fresh scaffold: `validate` and `generate` exit 0 on both jars, with identical finding codes. +- `extensions --format json`: the same content; one list changes order. +- `templates` adds `csharp/api/Controllers/PaginationLinks.mustache`. +- `check-extension-vocabulary.py --require-jar` passes. +- `add-component csharp` seed on `hello-orders`, regenerated on `0.25.0`: `dotnet build` succeeds, 0 errors. `hello-orders` declares no `tenancy`, so a tenancy-scoped build is not covered by this. + +**Measured with probe specs (`hello-orders` plus `tenancy`, on both jars).** + +| Layout | `0.24.0` | `0.25.0` | +|---|---|---| +| `Order` (direct) and `OrderLine` (chain) on a tenant-optional trait | passes | `ASYNC_TENANT_HEADER_NOT_REQUIRED` on the three `Order` events | +| `OrderLine` (chain) alone on a tenant-optional trait | passes | passes | +| `OrderLine` (chain) on the tenant-required trait | `ASYNC_TENANT_HEADER_ON_GLOBAL_EVENT` | `ASYNC_TENANT_HEADER_ON_GLOBAL_EVENT` | +| Split traits, no chain-owned event moved | one `ON_GLOBAL_EVENT` for `OrderLineCreated` | the same | + +**Where the jar and the notes differ.** The `0.24.0` notes said the legacy header names were written for that release only. The `0.25.0` C# dispatcher and Python `envelope_headers` templates still write them. + +**Not measured:** any runtime behaviour, including the exception change, the write guard, `links`, `Location`, `deletionState`, the dispatcher and `BlastRadius`; Python and TypeScript output. These rest on the generator CHANGELOG. That the producer leaves the tenant off a chain-owned event also rests on the generator's release notes and its comment on `#3295`, not on a generated producer run here. + +**Handoffs:** `tenant-check-on-global-references` stays open: `0.25.0` compares tenants, but a global target compares with nothing. + ## Outstanding kit-side work These are gaps in the kit itself, separate from the generator-side follow-ups above. Each represents an enforcement promise the handbooks make that has no implementation today. diff --git a/docs/csharp-component-guide.md b/docs/csharp-component-guide.md index df59541..3e0cb3c 100644 --- a/docs/csharp-component-guide.md +++ b/docs/csharp-component-guide.md @@ -236,6 +236,8 @@ The generated `TestSeed` (stable GUIDs and a pre-wired entity graph) and the `A. From generator 0.22.0, also call `TestSeed.SeedSpareParents(context)` from `EnsureFakeData()`, beside the rows you already add. It seeds a spare parent row for each `uniqueBy` tuple made only of references (one gadget per owner), so the generated create test does not collide with the seeded child and answer 409. It is emitted on every `TestSeed`, empty when no tuple needs it, so the call can stay put. Without it, the create test answers 404 on the parent lookup. +**Known failures through generator 0.25.0.** On a relational provider the call can fail the whole seed, because a spare repeats its builder's values and hits a unique index on the parent itself (`clabonte/generator#3366`). Until that is fixed, add those parents yourself first with values of your own; the method skips a row already present. The in-memory provider enforces no index. Separately, a create test that names `TestSeed.SpareId` can answer 403 for a tenant-scoped role, because that role's token does not reach the spare (`clabonte/generator#3365`). The API is right to refuse, so treat those rows as known failures in a functional baseline. + Also from 0.22.0, the generated service unit tests assert what each emitted event carries and the exact set of events an update emits, including `x-trigger-when` transitions. A red service test after a regen means the service emits something other than what the spec implies, not a flaky test: fix the service, there is no opt-out. Events now carry the acting user: generated controllers and worker handler bases enter `EventActorScope`, so `EventEnvelope.UserId` is filled. Hand-written code that emits on behalf of a user outside those entry points (a hand-written endpoint, a hub, a background service) wraps the work in `using var _ = EventActorScope.Enter(userId);` with the domain user id. ## 5. Extending generated code @@ -467,6 +469,18 @@ Raw SQL or a dashboard reading `Status = 0` must change. Code using `OutboxStatu - **A `400` where the client used to succeed.** `filter` accepts only `filterableProperties` plus `Id`, `CreatedAt` and `UpdatedAt`, and an enum value in a request must match the spec exactly, case included, with `"unknown"` refused unless the enum declares it or is `x-extensible`. Stored enum data is unaffected. - **A `403` or `404` on a write.** A project declaring `tenancy` gets a write guard: a write outside the caller's visibility answers `403`, and a body reference outside it `404`, where a missing id used to be a `500` and another tenant's id was accepted. +### 9.3 Upgrading to generator 0.25 + +No migration is required by this release. It changes what hand-written code and tests can see: + +- **A test expecting a wrapped exception.** A domain exception now arrives as itself, not inside `InternalException`, so a 401, 403, 409, 412 or 422 raised below a service no longer answers 500. A test asserting the wrapper must assert the exception, and a host filter that unwraps `InternalException` can go. A group that writes its own exception classes must declare all ten that `domainExceptions` emits. +- **A `404` from a system path.** The write guard now checks references for an unrestricted caller (`RecordVisibility.All`) too: a missing target answers `404` rather than a foreign-key `500`, and a reference into another tenant is refused for every caller. A system path that wrote a cross-tenant reference on purpose must have its column declare `x-references: none`. A pillar-bound group's guard also compares a reference to a held `Read{Entity}` replica. A guarded save of a changed reference costs one more query per target type. +- **A `400` for a bad `filter` value.** A value that cannot convert to its property's type answers `400` for every type, where several answered `500`. +- **Partitioned Service Bus topics.** The dispatcher now sends one batch per session. Rows already `Poisoned` by the old refusal stay `Poisoned`. +- **`BlastRadius` values move.** It counts the route again, and a top-level list only superuser roles may call reads `platform`. `RequiresConfirmation` does not change. Recheck anything gating on the radius. +- **Hand-written workarounds you can delete:** a filter adding `Location`, `API-Version` or `ETag`, a `` of the authorization conformance test in a spec with no roles, and a placeholder type written to get past CS0246 in a `pillar` or `domains` group. +- **`Location` on a create can be absent on purpose.** When several reads spell the created entity's key, none is set. A client must then take the id from the response body. + ## 10. The audit checklist Run from the component repo root. Every check names the section with the explanation and the fix. The IDs are stable; the interactive page uses the same ones. diff --git a/docs/generator-releases.md b/docs/generator-releases.md index 2489bb3..5880999 100644 --- a/docs/generator-releases.md +++ b/docs/generator-releases.md @@ -24,6 +24,54 @@ Newest first. An entry is **pending** until the kit release that pins it is publ | **Tracked issues** | you were waiting on a fix | | **Kit changes** | you want to know what moved in the handbooks, rules and tooling | +## 0.25.0 + +**Status: pending.** To be pinned by kit `v0.33.0` (jar sha256 `a8649a70…5df1cc9`; full record in [`compatibility.md`](https://github.com/specfuse/authoring/blob/main/compatibility.md) §61). Generator release date: 2026-11-04. + +### Do not use yet + +**Still do not register `mcpHost` or `mcpEvalHarness` on a real manifest.** This is unchanged from `0.24.0`: **a preview tool commits instead of rolling back.** FEAT-GEN-2026-0259 tracks the repair. + +### Before you regenerate + +**Spec authors** + +- **A tenant-scoped event must require the tenant header (ERROR).** An event under the tenant, on a trait that leaves the header optional, fails with `ASYNC_TENANT_HEADER_NOT_REQUIRED`. Move it to the tenant-required trait. A nullable tenant foreign key and the sub-tenant are not reported. An event below the tenant through a `belongsTo` chain still has to stay on the global trait: see the known issue below. + +**C# component repos.** No migration is required. [`csharp-component-guide.md`](csharp-component-guide.md) §9.3 has the steps. + +- **Domain exceptions are no longer wrapped.** A 401, 403, 409, 412 or 422 raised below a service stops answering 500. A test asserting `InternalException` must assert the exception itself. A group with its own exception classes must declare all ten that `domainExceptions` emits. +- **The write guard covers unrestricted callers and tenant crossings.** Under `RecordVisibility.All`, a missing target answers 404, and a reference into another tenant is refused for every caller, including a held `Read{Entity}` replica. A column meant to cross declares `x-references: none`. +- **A bad `filter` value answers 400** for every type, not 500. +- **`BlastRadius` values move.** `RequiresConfirmation` does not. + +**TypeScript test support** + +- **A generated fake's child create refuses a missing or archived parent.** Seed a live parent into `RepositoryFakeStores` first. Your next regen reports every guarded create as breaking once, because the stored baseline never recorded the guard. + +### Known issues + +- **An event on an entity below the tenant through a `belongsTo` chain carries no tenant header (`clabonte/generator#3295`).** `validate` treats the entity as global, so requiring the header fails. Validation does not report the missing header, and a subscriber filtering on it misses these events. When this is fixed, those events must move to the tenant-required trait in that upgrade. +- **C#: `TestSeed.SeedSpareParents` can fail the whole seed on a relational provider (`#3366`),** and **some generated create tests answer 403 for a tenant-scoped role (`#3365`).** Both are unchanged from `0.24.0`. See `csharp-component-guide.md` §4.4. + +### New in the spec + +- **`x-sample: { provider: fixed, format: "null" }`** seeds null on a `nullable: true` scalar (C#, Dart, TypeScript). +- **C# list responses fill the `links` their envelope declares.** Additive on the wire. +- **C# controllers set `Location`, `API-Version` and `ETag`** where the spec declares them. A create with several reads that spell its key sets no `Location`. +- **`deletionState` works on reads below an aggregate root (C#).** It used to be ignored there. + +The vendor-extension vocabulary is unchanged. + +### Tracked issues + +`clabonte/generator#3296` is fixed. `#3295` is open (FEAT-GEN-2026-0269), and `#3289` (the closed MCP `server` list) is planned for `0.26.0`. + +### Kit changes + +- `AsyncAPI_Handbook.md` adds `ASYNC_TENANT_HEADER_NOT_REQUIRED`, rewrites the tenant-gap warning, and corrects the legacy header window: the `0.24.0` notes said those names were written for that release only, and `0.25.0` still writes them. +- Handbooks and the C# component guide document the changes above. + ## 0.24.0 **Status: published.** Pinned by kit `v0.32.0` (released 2026-11-02) (jar sha256 `85d1496e…c088902e7`; full record in [`compatibility.md`](https://github.com/specfuse/authoring/blob/main/compatibility.md) §60). Generator release date: 2026-11-02. diff --git a/generator.lock b/generator.lock index 4b7ca66..176b271 100644 --- a/generator.lock +++ b/generator.lock @@ -1,12 +1,12 @@ { - "kit_version": "0.32.0", + "kit_version": "0.33.0", "min_java": 21, "generator": { - "version": "0.24.0", - "asset": "specfuse-generator-0.24.0.jar", - "sha256": "85d1496ec1d58353379dd4ed93faec36829497b86e88da5c4b74f49c088902e7", + "version": "0.25.0", + "asset": "specfuse-generator-0.25.0.jar", + "sha256": "a8649a7062835134826aa49a460edfc8a3890a278e2b2923ccd0f78745df1cc9", "release_repo": "Specfuse/generator-dist", - "release_tag": "v0.24.0" + "release_tag": "v0.25.0" }, "_note": "Pin the kit<->generator contract here. On each generator release: set version/asset/sha256/release_tag, then publish a matching kit patch. sha256=PENDING means no generator is bundled yet; `specfuse authoring generate` will refuse with a clear message until a real jar is pinned." } diff --git a/handbooks/API_Handbook.md b/handbooks/API_Handbook.md index 092ba5c..0a1fc67 100644 --- a/handbooks/API_Handbook.md +++ b/handbooks/API_Handbook.md @@ -385,7 +385,7 @@ CustomerSearchRequest: ### 1.7 Paginated list wrapper - **Schema name:** `{Resource}List` (e.g., `CustomerList`). - **Data:** array of `Basic{Resource}` within the standard pagination envelope. -- **Required:** every envelope field except `links`. `hasPrev` and `hasNext` are required because clients page on them: the generated C# `PaginatedList` always serializes both, computed from `page` and `pageCount`, and the generated Dart client reads `hasNext` as the server's answer rather than guessing from the row count. A schema that leaves them optional lets a conformant server omit the one field a client needs to decide whether to render a "next" control. `links` stays optional. +- **Required:** every envelope field except `links`. `hasPrev` and `hasNext` are required because clients page on them: the generated C# `PaginatedList` always serializes both, computed from `page` and `pageCount`, and the generated Dart client reads `hasNext` as the server's answer rather than guessing from the row count. A schema that leaves them optional lets a conformant server omit the one field a client needs to decide whether to render a "next" control. `links` stays optional. From generator `0.25.0` a generated C# list response fills the `links` its envelope declares: relative URLs that keep the request's query and change only `page`, with `prev` and `next` null where no such page exists, and `last` null when the total is unknown. ```yaml CustomerList: @@ -1094,6 +1094,7 @@ GET /orders/123?deletionState=archived # 404 if 123 is live, so a caller can c - `deletionState` is legal only on a read of an entity declaring `archiveVisibility: operational`. On a `restricted` entity `validate` fails with `ARCHIVE_DELETION_STATE_TARGET_NOT_OPERATIONAL`, and on a hard-delete entity with `ARCHIVE_DELETION_STATE_HARD_TARGET` (both ERROR). The operation must name its entity in `x-operation.target`. - **`filter=deletedAt ne null` does not reach archived rows.** The `filter` runs inside the default query filter, which has already removed them, so it returns an empty page. `deletionState` is the only generated route to an archived row. - `deletionState=all` or `archived` still does not return a row whose owning **ancestor** is archived. The generated read bypasses the filter for this entity's own archive state only. +- **Below an aggregate root, `deletionState` needs generator `0.25.0` (C#).** Before it, a generated service accepted the parameter on a child or related-aggregate read and ignored it, so `archived` returned live rows. - Standard GET by ID (`GET /orders/123`) returns `404` for an archived row. #### AI Agent Considerations @@ -2187,7 +2188,7 @@ Sentinel name list to scan against (case-insensitive): `unknown`, `unspecified`, | Provider | When to use | Required fields | Example | |----------|-------------|-----------------|---------| | `faker` | Realistic data — names, emails, addresses, dates, numbers | `path` (required), `locale` (optional) | `{ provider: faker, path: person.firstName, locale: en-US }` | -| `fixed` | Enum values, known constants, boolean defaults | `format` (required — the literal value) | `{ provider: fixed, format: "premium" }` | +| `fixed` | Enum values, known constants, boolean defaults; from generator `0.25.0`, `format: "null"` seeds null on a `nullable: true` scalar | `format` (required — the literal value) | `{ provider: fixed, format: "premium" }` | | `template` | Compound/derived values — formatted codes, composed strings | `format` (required — template with `{faker.path}` interpolation) | `{ provider: template, format: "ORD-{{string.numeric(6)}}" }` | **JSON Schema**: `schemas/arazzo-extensions/x-sample.schema.json` @@ -2773,6 +2774,8 @@ The `type` each one sets is fixed: `validationError`, `insufficientPrivileges`, If `Error` also declares an `automatedDecision` property naming a schema, the constructor takes an optional `automatedDecision` of that type as a third argument. `InternalException` and `UnauthorizedException` get no reason constructor. Through `0.17.0` the exceptions took only an `int errorCode` or a message, so a project built the `Error` itself and passed it to the `(Error error)` constructor. That still works. +**A domain exception is never wrapped, from `0.25.0` (C#).** Every generated service method and application-service gate lets all ten classes `domainExceptions` emits through unchanged, and wraps only anything else in `InternalException`. A group that writes its own exception classes instead of registering `domainExceptions` must declare all ten. + **`id` follows the schema from `0.18.0`.** The exceptions set `Error.Id` to `Guid.NewGuid().ToString()` for the kit's plain-string `id`, and to `Guid.NewGuid()` for a `format: uuid` one. Through `0.17.0` it was `-`, which two errors raised in the same tick shared. Treat `id` as opaque: it identifies one error occurrence in logs and support requests, and its format is not a contract. ### Error Types and Common Reasons diff --git a/handbooks/Arazzo_Handbook.md b/handbooks/Arazzo_Handbook.md index b488d36..4527252 100644 --- a/handbooks/Arazzo_Handbook.md +++ b/handbooks/Arazzo_Handbook.md @@ -1031,7 +1031,7 @@ tools: source: { type: workflow, workflowId: get-order-status } ``` -`Project_File.md` §1.5 covers the `mcpManifests` setting, what `validate` checks, and the 0.24.0 warning against registering `mcpHost` on a real manifest. +`Project_File.md` §1.5 covers the `mcpManifests` setting, what `validate` checks, and the warning against registering `mcpHost` on a real manifest. --- diff --git a/handbooks/AsyncAPI_Handbook.md b/handbooks/AsyncAPI_Handbook.md index 15c6732..d152cab 100644 --- a/handbooks/AsyncAPI_Handbook.md +++ b/handbooks/AsyncAPI_Handbook.md @@ -381,6 +381,7 @@ The generator reads each message's merged `traits[]` headers and works out which |---|---|---| | `ASYNC_ENVELOPE_HEADER_MISSING` | ERROR | A message declares headers but binds no header to `eventId`, or, on a scoped event, to the `tenant` / `subTenant` the project declares | | `ASYNC_TENANT_HEADER_ON_GLOBAL_EVENT` | ERROR | An event not under the tenant has headers that require the tenant header | +| `ASYNC_TENANT_HEADER_NOT_REQUIRED` | ERROR | From generator `0.25.0`. An event under the tenant has headers that do not require the tenant header. A nullable tenant foreign key and the sub-tenant are not reported | | `ASYNC_ENVELOPE_ROLE_UNKNOWN` | ERROR | An `x-envelope` value outside the role vocabulary | | `ASYNC_ENVELOPE_ROLE_DUPLICATE` | ERROR | Two headers of one message mark the same role | | `ASYNC_ENVELOPE_HEADER_RECOMMENDED` | WARNING | `eventVersion`, `aggregateId`, `aggregateType`, `producedAt` or `correlationId` is unbound | @@ -413,12 +414,14 @@ Neither trait layers on the other: AsyncAPI applies traits as JSON Merge Patch a The Spectral rule `specfuse-async-auditable-event-envelope-shape` accepts both traits. It requires every envelope field above except the tenant, finds each by `x-envelope` or canonical name, and leaves the tenant to the generator, which knows the aggregates. -**Check an event before moving it to the global trait; two generator gaps at `0.24.0` make a wrong move pass.** +**An event below the tenant through a `belongsTo` chain still counts as global (generator `0.25.0`).** The tenant checks look only at the aggregate's direct links to the tenant. They do not follow a `belongsTo` chain, although the generated tenant query filter does. Measured: with `OrderLine` under `Order` and `Order` under the tenant, `OrderLineCreated` on the tenant-required trait reports `ASYNC_TENANT_HEADER_ON_GLOBAL_EVENT`, and on the global trait it passes. So an event on a chain-owned entity has to use the global trait for now. -- The check that reports `ASYNC_TENANT_HEADER_ON_GLOBAL_EVENT` looks only at the aggregate's direct links to the tenant. It does not follow a `belongsTo` chain, although the generated tenant query filter does. Measured: with `OrderLine` under `Order` and `Order` under the tenant, `OrderLineCreated` is reported as not under the tenant. That event is tenant-owned, so the finding does not show it is global. Whether its producer stamps the tenant was not measured, so confirm that before a subscriber filters on it. -- Neither plane checks that a scoped event requires its tenant. The generator only checks that a header is bound, so an event directly under the tenant passes when pointed at the global trait. +- **Its producer leaves the tenant header out.** Per the generator's release notes, the C# producer does not resolve the tenant through the chain, and the Python template leaves it optional. Validation does not report this, so a subscriber filtering on the tenant header misses these events. Check any subscriber that filters that way. +- **This will change.** When the generator follows the chain (`clabonte/generator#3295`), these events count as tenant-scoped, so they fail `ASYNC_TENANT_HEADER_NOT_REQUIRED` on the global trait and have to move to the tenant-required trait in that upgrade. -**The legacy header names go away in the next minor release.** A bound name now replaces `companyId`, `restaurantId` and `producedAt` on the wire. For `0.24.0` only, the C# producer also writes those literals when the bound name differs, and the C# and Python readers try the bound name and then the literal. A subscriber outside the generated code that reads `companyId` off the application properties must read the bound name before the next minor release. A message that declares no headers is unchanged. +An event directly under the tenant cannot make this mistake from `0.25.0`: on the global trait it fails `ASYNC_TENANT_HEADER_NOT_REQUIRED`. + +**The legacy header names are still written at `0.25.0`.** A bound name replaces `companyId`, `restaurantId` and `producedAt` on the wire, but the C# and Python producers also write those literals when the bound name differs, and the readers try the bound name and then the literal. The `0.24.0` notes said this would end with that release; it did not, and the `0.25.0` notes name no removal release. A subscriber outside the generated code that reads `companyId` off the application properties should still move to the bound name. A message that declares no headers is unchanged. **`aggregateVersion` travels on every event of a C# entity.** It is written as an `aggregateVersion` application property even when a message's headers do not bind it, and it moves once per save of the record. Handler bases do not drop stale events. To compare, treat a version equal to or lower than the last one you applied for that record as stale, and treat `null` as no ordering information. Two events raised in one save share a version, so compare per label if a handler treats them as distinct work. A property that maps to the C# member `AggregateVersion` is reported as `ENTITY_AGGREGATE_VERSION_RESERVED`: an error on an event's subject, a warning otherwise. diff --git a/handbooks/Project_File.md b/handbooks/Project_File.md index 21cf043..70f3809 100644 --- a/handbooks/Project_File.md +++ b/handbooks/Project_File.md @@ -193,7 +193,7 @@ The three spec-path fields tell the generator where to find the bundled inputs. ### 1.5 `mcpManifests` (generator `0.24.0`) -> **⚠ Do not register `mcpHost` or `mcpEvalHarness` on a real manifest at `0.24.0`.** They are verified on fixtures only. On a real manifest the generated tools fail to compile, five composite tools advertise required ids a caller cannot obtain, and **a preview tool commits instead of rolling back, so a `preview-*` tool performs the real write with no confirmation**. A group that does not register these two is unaffected. `mcpServerResources`, `validate` and the `MCP_*` findings work as documented. +> **⚠ Do not register `mcpHost` or `mcpEvalHarness` on a real manifest at `0.24.0` or `0.25.0`.** They are verified on fixtures only. On a real manifest the generated tools fail to compile, five composite tools advertise required ids a caller cannot obtain, and **a preview tool commits instead of rolling back, so a `preview-*` tool performs the real write with no confirmation**. A group that does not register these two is unaffected. `mcpServerResources`, `validate` and the `MCP_*` findings work as documented. **Purpose**: A file, or a directory of `*.mcp.yaml` files, resolved against the project file's directory like `arazzoSpecifications`. A manifest declares one MCP server's tools, prompts and resources. @@ -217,7 +217,7 @@ tools: | `source.type` | `operation`, `workflow`, `composite` | | Resource | `name`, `enum`, `schema` | -**`server` is a closed list on `0.24.0`**: `admin`, `compliance`, `inbound`, `ops`, `scheduling`, `workforce`. Anything else is `MCP_MANIFEST_SHAPE_INVALID`. `budget` takes 1 to 40, `maxPageSize` 1 to 100. +**`server` is a closed list through `0.25.0`**: `admin`, `compliance`, `inbound`, `ops`, `scheduling`, `workforce`. Anything else is `MCP_MANIFEST_SHAPE_INVALID`. `budget` takes 1 to 40, `maxPageSize` 1 to 100. **What `validate` checks**, all ERROR unless marked (`java -jar extensions` prints the current vocabulary): @@ -1112,7 +1112,7 @@ Three opt-in C# ids, none in the snapshot list above. Each emits only when regis | `hostedScheduledJob` | `0.24.0` | The generic-host group | `{Job}ScheduledJob.g.cs` per job placed under `schedules.inProcess` or `schedules.cronJob`, plus `ScheduledJobRunner` for `cronJob` (§17.3). `inProcess` needs the `Cronos` package. | | `eventRuntimeHosted` | `0.24.0` | The generic-host group, beside `hostedTopicConsumer` | `HostedEventOutboxDrainer`, `HostedEventOutboxRetention` and `HostedEventInboxRetention`, when `runtime.host` is `genericHost` (§17.3). | | `mcpServerResources` | `0.24.0` | A C# group | An effects and a descriptions file per manifest server (§1.5). | -| `mcpHost`, `mcpEvalHarness` | `0.24.0` | **Do not register on a real manifest at `0.24.0`** (§1.5) | `AddSpecfuseMcp()` and the tool, prompt and resource classes; a harness scoring a model's tool selection. | +| `mcpHost`, `mcpEvalHarness` | `0.24.0` | **Do not register on a real manifest at `0.24.0` or `0.25.0`** (§1.5) | `AddSpecfuseMcp()` and the tool, prompt and resource classes; a harness scoring a model's tool selection. | | `tenantIsolationTest` | `0.21.0` | Test | `Generated/Unit/Infrastructure/TenantIsolationTests.g.cs`: per tenant-owned entity, a list never returns the foreign tenant's rows, a get, update or delete of a foreign row answers not found, `Unrestricted` sees both tenants, an empty visibility sees nothing, the model carries the `Tenant` filter. Adds the within-tenant facts for entities that declare `x-entity.visibility`. `TestSeed` gains the foreign tenant's graph. | Needs a `tenancy` block. Emits nothing unless a `Tenant` or `Visibility` filter is declared. The Test project must reference Infrastructure. Runs in memory under the unit-test step, not under the functional suite. | | `bareIgnoreSourceScanTest` | `0.21.0` | Test | `Generated/Unit/Infrastructure/BareIgnoreSourceScanTest.g.cs`: fails when hand-written C# calls `IgnoreQueryFilters()` with no argument, which drops the `Tenant` filter too, unless the call is listed in `bare-ignore-query-filters.baseline` at the solution root. | Needs a `tenancy` block and `tenancy.enforcement` unset; otherwise it emits nothing. The baseline is yours, never generated, and only shrinks: a missing file reads as empty, and a line for a call that is gone fails as stale. | diff --git a/handbooks/Vendor_Extensions.md b/handbooks/Vendor_Extensions.md index 0fc88b1..415302e 100644 --- a/handbooks/Vendor_Extensions.md +++ b/handbooks/Vendor_Extensions.md @@ -2201,7 +2201,7 @@ Article: **Optional**: Yes, but see "Every FK-shaped property must be classified" below — in practice an unclassified uuid property is a spec defect, not a default. -> **A client-writable reference to a global entity gets no tenant check.** From generator `0.24.0` the save-time write guard resolves a reference to a tenant-owned entity, and `TENANT_REFERENCE_NOT_WRITE_CHECKED` warns on one it cannot check. Neither covers a target with no `belongsTo` path to a tenancy root, such as a global `User`. A request body carrying any existing id of that entity passes, so a task can be assigned to a stranger, and the difference between an error and success reveals that the record exists. Until the generator has a way to declare the membership that scopes such an entity, check it in hand-written code on every write path, and answer the same way whether the record is unknown or foreign. Fields the server fills from the caller (`x-populate-from`) are not exposed to this. +> **A client-writable reference to a global entity gets no tenant check.** From generator `0.24.0` the save-time write guard resolves a reference to a tenant-owned entity, and `TENANT_REFERENCE_NOT_WRITE_CHECKED` warns on one it cannot check. From `0.25.0` it refuses a reference into another tenant for every caller, including one under `RecordVisibility.All`, so a column meant to cross tenants must declare `x-references: none`. None of this covers a target with no `belongsTo` path to a tenancy root, such as a global `User`. A request body carrying any existing id of that entity passes, so a task can be assigned to a stranger, and the difference between an error and success reveals that the record exists. Until the generator has a way to declare the membership that scopes such an entity, check it in hand-written code on every write path, and answer the same way whether the record is unknown or foreign. Fields the server fills from the caller (`x-populate-from`) are not exposed to this. **Schema**: @@ -3524,7 +3524,7 @@ properties: **Providers**: - **`faker`** — generates data via faker library paths. The `path` field is required and maps to a faker method (e.g., `person.firstName`, `company.name`). -- **`fixed`** — returns a literal value from `format`. Useful for enums (weighted pick) and known constants. +- **`fixed`** — returns a literal value from `format`. Useful for enums (weighted pick) and known constants. From generator `0.25.0`, `format: "null"` on a `nullable: true` scalar seeds null; on a property that is not nullable it stays the text. - **`template`** — builds a string from a format template that interpolates faker paths in `{path}` placeholders. **Rules**: diff --git a/pyproject.toml b/pyproject.toml index ece3500..163d72a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "specfuse-authoring" -version = "0.32.0" +version = "0.33.0" description = "Specfuse authoring kit: handbooks, samples, Claude assets, project bootstrap, and the generator launcher." readme = "README.md" requires-python = ">=3.10" diff --git a/specfuse/authoring/__init__.py b/specfuse/authoring/__init__.py index ce355ff..211f1ca 100644 --- a/specfuse/authoring/__init__.py +++ b/specfuse/authoring/__init__.py @@ -3,7 +3,7 @@ from importlib.resources import files from pathlib import Path -__version__ = "0.32.0" +__version__ = "0.33.0" def kit_root() -> Path: