Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,40 @@ jobs:
[ $fail -eq 0 ] || exit 1
echo "==> snapshot guardrails verified in both directions."

# A bare `Label` in a Service Bus SQL filter is `user.` scope and matches
# nothing; the label is `sys.Label`. The generator copies a filterOverride
# verbatim and never checks it, so this warn rule is the only net
# (RestoManager handoff). Assert it fires on every bare form AND stays
# silent on qualified filters — a rule that flags `sys.Label` is noise.
- name: filterOverride sys.Label scope (regression)
run: |
set +e
set -uo pipefail
REPORT="${RUNNER_TEMP}/sys-label.txt"
spectral lint --ruleset schemas/spectral/specfuse-asyncapi.yaml \
schemas/spectral/fixtures/subscription-filter-sys-label.yaml >"$REPORT" 2>&1
STATUS=$?
cat "$REPORT"
[ $STATUS -ge 2 ] && { echo "::error::Spectral crashed on the sys.Label fixture" >&2; exit 1; }

fail=0
for bad in on-bare-equality on-bare-or-chain on-bare-like on-mixed; do
if ! grep "specfuse-subscription-filter-sys-label" "$REPORT" \
| grep -q "operations\.$bad\.x-subscription\.filterOverride"; then
echo "::error::specfuse-subscription-filter-sys-label did not flag $bad." >&2
fail=1
fi
done
for clean in on-sys-label on-sys-label-header; do
if grep "specfuse-subscription-filter-sys-label" "$REPORT" \
| grep -q "operations\.$clean\.x-subscription\.filterOverride"; then
echo "::error::$clean qualifies the label as sys.Label but was flagged." >&2
fail=1
fi
done
[ $fail -eq 0 ] || exit 1
echo "==> sys.Label scope rule verified in both directions."

# A bootstrapped project must validate cleanly with the scripts the kit
# hands it. Until now CI only linted examples/hello-orders/, so defects
# fixed there could survive in the template it came from — and did: every
Expand Down
34 changes: 34 additions & 0 deletions compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,40 @@ Each finding was reported first by a consumer adopting the vocabulary.

**At the next pin bump:** re-run the §14.10 matrix (two pillars on `hello-orders`, generate each group) and retire rows as `#2392`/`#2393`/`#2394`/`#2400` close. When `#2397` ships, rewrite §14.11 around `resolve: remote` and add a Spectral rule for the object form of `x-references`.

### 45. RestoManager handoff batch 2026-10-09: what the kit corrected, what it forwarded

**Status:** kit side **done** for every item that needed no generator design (`AsyncAPI_Handbook.md` §0.8 and §6.3, `Arazzo_Handbook.md` `x-actors` and §4.6, `API_Handbook.md` §8, §14 and Enums, `Vendor_Extensions.md` §3.1, §5.1, §6.1, `x-domain`, `x-ai`, value-object protection; new Spectral rule `specfuse-subscription-filter-sys-label`). Every generator claim was checked against the released `0.14.0` jar or its release commit `bd96ddeaf`, not against the SNAPSHOT a handoff named. Five of the nine handoffs rested on stale generator behaviour.

**Corrected in the kit because they were wrong at the pin:**
- **Subscription filters used a bare `Label`.** Service Bus reads that as `user.Label`, so it matches nothing. The generator has emitted `sys.Label` since at least `0.11.0`.
- **`x-actors.<name>.ref` was described as a domain entity.** It is minted into the principal's `claims.userId` claim.
- **§6.1 forbade `x-emits: []`.** Both the kit rule and the generator accept it, so it is now defined as a deliberately silent write.
- **`delegable` was described as read by the runtime.** It is only published in `role-grants.json`; enforcement is planned (`#2180`, FEAT-2026-0203).
- **`codeGenHints` (all five keys) and `x-ai.capabilities` are parsed and read by nothing.** Three §5.1 categories removed in `0.5.4` were still listed as live.
- **The second value-object protection site was given as `x-value-object.protection`.** It is the embed property's `x-protection`; the generator never reads protection on a value object's own schema.

**Forwarded:**
- `clabonte/generator#2414` — warn when an actor `ref` is not the principal identity, or the actor's role is not in the step operation's `x-roles`.
- `#2415` — a `WRITE_OPERATION_DECLARED_SILENT` census for `x-emits: []`.
- `#2416` — split `ENUM_VALUE_SET_DUPLICATE` (SUGGESTION) into identical and overlapping, and report identical groups once.
- `#2417` — the data-protection audit omits an encrypted value-object embed property; the usage-site form is reported correctly.
- `#2418` — **`ARAZZO_ACTOR_LACKS_SCOPE` never fires at `0.14.0`.** Under `mode: scopes`, with an actor whose grants miss its steps' scopes, the scenario got no finding while `OPERATION_ROLE_LACKS_SCOPE` reported the same gap ×5. §43 lists the code as shipped; it is in the jar but inert.

**Deferred, waiting on unscheduled generator design:**
- caching: `x-cache` and `x-entity.cache` (`#2173`–`#2177`);
- streaming: `x-stream`, `client-stream`, webhooks (`#2163`, `#2164`, `#2167`, `#2169`, `#2170`);
- client-side grants (`#2180`);
- a durable-run convention, which waits on `#2164`.

None of these is settled enough to document ahead of the pin (§24 precedent).

**Open questions:**
- Whether a read-only `:search` POST may omit `x-emits`. A category-based exemption cannot work, because `query` on POST is `QUERY_MUST_BE_GET` (ERROR).
- `examples/hello-orders` binds `x-actors.*.ref` to the `createCustomer` domain id (`place-order.arazzo.yaml:51`). That is the shape the handbook now calls wrong. The example has no identity entity to bind to, so fixing it is a modelling change, not a rename.
- Two `Vendor_Extensions.md` lines found during the batch and not yet checked against the generator: §5.1 "Automatic Categorization" says an uncategorisable operation is an error, while the code defaults it to `Resource`; and the value-object section says protection keys "exist at both levels".

**At the next pin bump:** re-test `ARAZZO_ACTOR_LACKS_SCOPE` (`#2418`), then drop the "does not fire" notes in `Arazzo_Handbook.md` §4.6 and §9.2.

---

## Outstanding kit-side work
Expand Down
37 changes: 14 additions & 23 deletions handbooks/API_Handbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ This handbook defines authoritative rules for designing REST APIs and OpenAPI sp
- Never inline inside a model. Define as a separate schema named `{Resource}{EnumName}` (e.g., `OrderStatus`).
- Enum values are **camelCase**.
- Clients must tolerate new/unknown enum values.
- **Reuse before you define.** Check the shared enum files (`common/enums.yaml`, and the enums of the domain you are in) before adding one. An enum that is identical across domains belongs in `common/` with `x-domain: common`, defined once. Identical values with a different meaning stay separate: a cost scale and a confidence scale can both be `low/medium/high` and are still two enums.
- **Reading `ENUM_VALUE_SET_DUPLICATE`.** The generator compares every pair of enums and reports those whose value sets overlap with a Jaccard ratio of 0.5 or more, ranked higher when the two names share a word, and capped at 50 findings. The message shows the ratio, e.g. `(Jaccard 0.67, shared name token)`. **Jaccard 1.00** means identical value sets: consolidate, unless the meanings differ as above. **Below 1.00** is usually two different enums that share some values; leave them. It is a SUGGESTION, never a build failure. AsyncAPI's equivalent guidance is `AsyncAPI_Handbook.md` §6.5 *Shared Enums*.

---

Expand Down Expand Up @@ -2781,7 +2783,16 @@ The role set is project-defined; declare it in `info.x-roles` (see `Vendor_Exten
- `x-roles`: privileged + administrative roles
- `x-scopes`: `[<domain>.<Entity>.write]`, or `[<domain>.<Entity>.delete]` for a DELETE — `delete` is a distinct action, not a subset of `write`

> These extensions document intent for reviewers and generators; enforcement occurs in implementation and/or gateway policy.
**Roles can declare their grants.** From generator `0.14.0`, `info.x-roles` may be a mapping of role name to `{ grants, implicit, superuser, delegable, description }` instead of a list of names (`Vendor_Extensions.md` §3.1). With grants declared, the templates above stop being a convention to remember: the generator checks each operation's `x-roles` against the roles whose grants cover its `x-scopes`, and the `authorizationMatrix` artifact shows where the two differ (`Project_File.md` §11.5). A list stays valid; it declares no grants, so there is nothing to compare.

**What is enforced, at generator `0.14.0`:**

- **`x-roles`** — membership in `info.x-roles` (`OPERATION_UNKNOWN_ROLE`, ERROR) in either shape; and, under the default `authorization.mode: roles`, the generated C# controller gate (`[RoleRequired(…)]`).
- **`x-scopes`** — the kit's Spectral rules own the grammar (`error`). The generator checks shape and registry binding, and, against a mapping-form registry, role/scope coherence: WARNING by default, ERROR under `authorization.mode: scopes` (`Vendor_Extensions.md` §3.2).
- **`authorization.mode: scopes`** (`Project_File.md` §16) moves the generated controller gate from roles to scopes (`[ScopesRequired(…)]`), and `shadow` runs both while the role decision still governs. The generated runtime is the opt-in `authorizationRuntime` artifact.
- **`delegable`** is declared and published, not enforced (`Vendor_Extensions.md` §3.1).

What the generator does not emit — gateway policy, an identity provider's role assignment, the tenant-resource check behind the generated `ITenantResourceCheck` seam — remains the implementation's.

---

Expand Down Expand Up @@ -3113,29 +3124,9 @@ parameters:

Advanced patterns for enterprise-scale AI integration:

#### 8. Webhook Subscriptions
**Purpose**: Real-time notifications for AI agents.
#### 8. Webhooks — not yet specified

```yaml
components:
schemas:
WebhookSubscription:
type: object
properties:
id: { type: string, format: uuid }
url: { type: string, format: uri }
events:
type: array
items:
enum: ["customer.created", "customer.updated", "order.submitted"]
secret: { type: string, description: "For signature verification" }
active: { type: boolean, default: true }
filters:
type: object
properties:
tenantId: { type: string }
customerId: { type: string }
```
Webhooks have no Specfuse vocabulary yet: nothing in the kit or the generator declares, validates or generates them. Tracked in `clabonte/generator#2169` (inbound) and `clabonte/generator#2170` (outbound); until they land, do not model a webhook surface in the spec.

#### 9. Transaction Support
**Purpose**: Multi-step operations with rollback capability.
Expand Down
Loading
Loading