Skip to content

docs: TENANT_CLOSED runtime conformance + scope-filter matcher parity (spec v0.1.25.13 / server 0.1.25.47)#787

Merged
amavashev merged 10 commits into
mainfrom
docs/tenant-closed-runtime-conformance
Jul 11, 2026
Merged

docs: TENANT_CLOSED runtime conformance + scope-filter matcher parity (spec v0.1.25.13 / server 0.1.25.47)#787
amavashev merged 10 commits into
mainfrom
docs/tenant-closed-runtime-conformance

Conversation

@amavashev

@amavashev amavashev commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Follow-up docs for the TENANT_CLOSED runtime-conformance wave merged today: runtime spec v0.1.25.13 (runcycles/cycles-protocol#125 — TENANT_CLOSED on the runtime ErrorCode enum + normative closed-tenant binding; companion evidence 0.2.1, protocol-extensions 0.1.27, governance 0.1.25.37) and cycles-server 0.1.25.47 (runcycles/cycles-server#231 — the four-Lua-script Rule 2 guard + webhook scope-filter matcher parity with the admin #206 fix).

MERGE GATES — both SATISFIED (2026-07-10) — ready to merge pending owner approval

  1. cycles-server v0.1.25.47 release tag existsSATISFIED: v0.1.25.47 — TENANT_CLOSED Rule 2 guard + webhook matcher parity is tagged (Docker image published, benchmark gate passed). changelog.md now carries the 0.1.25.47 entry and the current-version row.
  2. The next cycles-server-admin release (first after 0.1.25.48) is taggedSATISFIED: v0.1.25.49 — spec-conformant webhook scope-filter matching + replay filtering is tagged (its full-stack compose pins server 0.1.25.47). The final s/queued/shipped-in-0.1.25.49/ pass over protocol/webhook-scope-filter-syntax.md is done, the divergence callout is now version-scoped history (softened ::: danger::: warning), and changelog.md carries the admin 0.1.25.49 entry.

The PR remains a draft per owner instruction; content is final and ready to merge pending owner approval.

Per-file summary

  • protocol/tenant-close-cascade-semantics.md — Replaced the Reference implementation gap warning: "What the runtime plane sees" now documents the shipped guard — 409 TENANT_CLOSED on persisting reservation create/commit/release/extend once the CLOSED flip is durable; precedence over RESERVATION_FINALIZED/RESERVATION_EXPIRED for non-replay attempts (same-key replays return the original response); fresh dry-run//v1/decide return 200 decision=DENY reason_code=TENANT_CLOSED (attestation rationale); 500 fail-closed on malformed tenant records; no guard without a tenant record; error CyclesEvidence on create/commit/release (extend is not an evidence endpoint); "usually 401 first" observability note; version scope for 0.1.25.46 and earlier. Added a runtime row to the version-gate matrix and the backward-compat list.
  • protocol/webhook-scope-filter-syntax.md — Danger-box "Update:" now records that runtime 0.1.25.47 refined its two matcher edge cases (blank/whitespace scopes unscoped and excluded; trailing-/* requires a non-empty child) and that the runtime and queued-admin matchers are pinned to the same test table. Syntax-summary * and .../* rows and the Edge cases section are version-scoped (0.1.25.46-and-earlier behavior retained).
  • blog/tenant-lifecycle-cascade-semantics-at-scale.md — Surgical: the "does not yet include TENANT_CLOSED" parenthetical now says implemented in cycles-server 0.1.25.47 (spec v0.1.25.13) with a 0.1.25.46-and-earlier note; the 409 example's comment clarifies it's the pre-revocation-window/admin-key path (revoked tenant keys 401 first).
  • how-to/tenant-creation-and-management-in-cycles.md — Runtime-plane sentence after the cascade paragraph (409 on persisting mutations, DENY reason on dry-run/decide, 0.1.25.46-and-earlier fallback); troubleshooting TENANT_CLOSED entry now names both planes. No runtime row invented in the admin version-behavior table.
  • protocol/error-codes-and-error-handling-in-cycles.md — Intro: 16 -> 17 runtime wire codes (TENANT_CLOSED in v0.1.25.13). New runtime-plane paragraph under TENANT_CLOSED (409). Reserve/Commit/Release/Extend tables re-attributed from "(v0.1.25.35+)" (an admin version on runtime endpoints) to cycles-server 0.1.25.47 / spec v0.1.25.13 with precedence notes. Removed the incorrect TENANT_CLOSED row from the Event errors table (see discrepancies) and added a note. Decide-errors note covers the DENY reason + 500 fail-closed. TENANT_CLOSED row added to the DecisionReasonCode table. INTERNAL_ERROR documents the malformed-tenant-record fail-closed variant.
  • protocol/implement.mdTENANT_CLOSED (409) added to the MUST error-semantics list (scope, precedence, DENY rule, fail-closed, governance-plane exemption).
  • protocol/api-reference-for-the-cycles-protocol.md409 TENANT_CLOSED rows added to the create/commit/release/extend error tables; dry-run paragraph includes the closed-tenant condition; both "six documented known values" sentences are now seven incl. TENANT_CLOSED. /v1/events table intentionally unchanged.
  • protocol/how-decide-works-in-cycles-preflight-budget-checks-without-reservation.md / protocol/dry-run-shadow-mode-evaluation-in-cycles.mdTENANT_CLOSED added to the base reason-code enumerations ("six" -> "seven").
  • protocol/event-payloads-reference.mdreservation.denied reason_code known values gain TENANT_CLOSED (fresh dry-run/decide DENYs, 0.1.25.47+ — matches governance spec 0.1.25.37's same addition).
  • glossary.mdTENANT_CLOSED entry now covers both planes with versions.
  • changelog.md — Spec-revision rows refreshed (runtime document revision v0.1.25.13, governance v0.1.25.37 — spec revisions ship on merge, unlike server tags) + a v0.1.25.13 bullet in the spec-suite section. cycles-server row intentionally stays v0.1.25.46 (gate 1).

Docs-vs-shipped discrepancies found beyond the planned updates

  1. Event errors table claimed TENANT_CLOSED on /v1/events (error-codes page, "(v0.1.25.35+)"). The runtime spec binding scopes the guard to the persisting reservation mutations only, and the server guards only reserve/commit/release/extend.luaevent.lua has no tenant-status check. A closed tenant surfaces on /v1/events as 401 or BUDGET_CLOSED. Row removed, note added.
  2. The four reservation-endpoint tables attributed the runtime 409 to admin version "v0.1.25.35+" — an admin release number on runtime endpoints that never enforced it. Re-attributed to cycles-server 0.1.25.47 / spec v0.1.25.13.
  3. changelog.md spec rows were stale (runtime revision v0.1.25.12 dated 2026-05-22; governance v0.1.25.34 dated 2026-04-20) — refreshed to today's merged revisions.
  4. LIMIT_EXCEEDED verification (requested): present in every enumeration meant to be complete — error-codes page (section + summary + intro count), implement.md MUST list, api-reference public-endpoint 429 rows. It is absent from the glossary (no entry; the glossary doesn't enumerate error codes individually beyond TENANT_CLOSED) and from the how-to error-handling "transient vs non-transient" tables — but those tables also lack other codes (401/403/BUDGET_FROZEN etc. depending on page) and are guidance tables, not complete enumerations, so TENANT_CLOSED was likewise not added there.

Full-sweep hit table (zero survivors)

File:line(s) Pattern Verdict
protocol/tenant-close-cascade-semantics.md (whole runtime section) implementation gap, enum lacks, only through the cascade, TENANT_CLOSED Updated — section rewritten; the remaining "reference-implementation gap ... is closed" sentence states the fix
protocol/webhook-scope-filter-syntax.md:10-21,38,55,69-77,87,91,156-157,163-168 0.1.25.48 and earlier, queued conformance, matcher claims Updated where runtime 0.1.25.47 changed behavior; "0.1.25.48 and earlier" admin scoping and "queued" phrasings correct-as-is (admin fix still unreleased; gate 2 renames them)
protocol/error-codes-and-error-handling-in-cycles.md:36,122-134,236,265,279,292,308,367 TENANT_CLOSED enumerations Updated (incl. Event-table row removal)
blog/tenant-lifecycle-cascade-semantics-at-scale.md:63,80,88,119,127,133,178 TENANT_CLOSED, does not yet include Updated at 119 (+example comment); other hits describe the admin-plane contract, correct-as-is
how-to/tenant-creation-and-management-in-cycles.md:309,325,821 TENANT_CLOSED Updated (309, 821); 325 admin version table correct-as-is (admin-only table; no runtime row invented)
glossary.md:294-300 TENANT_CLOSED Updated (296); 300 cascade entry correct-as-is (admin contract)
protocol/event-payloads-reference.md:138,173,441,453,484 reason_code list, 0.1.25.46, tenant_closed 138 updated; others correct-as-is (cascade event kinds / commit_overage since-note remain true)
changelog.md:18-23,49,78,88-89,140,159,162,301,314 0.1.25.46, TENANT_CLOSED 18-19 updated (spec rows) + new spec-suite bullet; server/dashboard version rows correct-as-is (0.1.25.47 untagged — gate 1)
how-to/using-the-cycles-dashboard.md:105,107,153,156 0.1.25.46, TENANT_CLOSED humanizer Correct-as-is (dashboard versions / UI behavior unchanged)
admin-api/guide.md:90,93 tenant_closed Correct-as-is (admin cascade reference)
api/index.md:12; security.md:75; configuration/server-configuration-reference-for-cycles.md:48; quickstart/architecture-overview-how-cycles-fits-together.md:23,314; quickstart/self-hosting-the-cycles-server.md:38,115,142; quickstart/deploying-the-full-cycles-stack.md:66,234,281; quickstart/deploying-the-cycles-dashboard.md:126; quickstart/end-to-end-tutorial.md:91; how-to/production-operations-guide.md:82,89; how-to/security-hardening.md:216,224,234 0.1.25.46 Correct-as-is — pinned images / "since 0.1.25.46" feature notes / "latest tagged versions as of 2026-07-09"; all still accurate while v0.1.25.47 is untagged (image-pin bumps are a gate-1 follow-up, out of scope)
incidents/concurrent-agent-overspend.md:201; incidents/retry-storms-and-idempotency-failures.md:158; incidents/scope-misconfiguration-and-budget-leaks.md:225 0.1.25.46 Correct-as-is — "current server builds (runtime 0.1.25.46)" metrics-roadmap notes; 0.1.25.47 adds no metrics and is untagged
blog/mcp-tool-budgets-before-execution.md:50,64 not yet implement Correct-as-is — v0.1.26 action-governance runtime enforcement genuinely hasn't shipped (no trace in the 0.1.25.47 changelog)
blog/state-of-ai-agent-incidents-2026.md:311 not yet implemented Correct-as-is — quoted Gartner statistic, unrelated
protocol/webhook-event-delivery-protocol.md:164 tenant_closed Correct-as-is — cascade aggregate event description

Verification notes

  • All behavior claims read from the merged spec text (cycles-protocol-v0.yaml ERROR SEMANTICS closed-tenant bullet, DecisionReasonCode KNOWN VALUES, createReservation DRY-RUN RESPONSE RULES, /v1/decide CLOSED TENANT block, CONFORMANCE.md) and cycles-server 0.1.25.47 changelog/README/Lua sources — not secondhand summaries.
  • reservation.denied claim cross-checked against the server controllers (event emitted on any fresh DENY from dry-run create and /v1/decide) and governance spec changelog v0.1.25.37.
  • npm build not run locally (broken machine-wide) — CI builds. All internal link targets and anchors referenced by the edits (#tenant-closed-409, #decision-reason-codes, #edge-cases, page paths) verified to exist.

Runtime spec v0.1.25.13 added TENANT_CLOSED to the runtime ErrorCode enum
with a normative closed-tenant binding, and cycles-server 0.1.25.47
implements it (plus DecisionReasonCode TENANT_CLOSED on fresh dry-run /
decide evaluations, and the webhook scope-filter matcher refinements).
Close the "reference implementation gap" callout, version-scope the old
behavior (<= 0.1.25.46), and add TENANT_CLOSED to every complete runtime
error-code / reason-code enumeration.

- protocol/tenant-close-cascade-semantics.md: rewrite "What the runtime
  plane sees" around the shipped guard (409 on persisting reservation
  create/commit/release/extend, precedence over reservation-state errors,
  replay exception, 200 DENY reason_code=TENANT_CLOSED on fresh
  dry-run/decide, 500 fail-closed on malformed tenant records, error
  CyclesEvidence on create/commit/release, usually-401-first note); add
  runtime rows to the version gate matrix and backward-compat list.
- protocol/webhook-scope-filter-syntax.md: document the 0.1.25.47 runtime
  matcher refinements (blank scopes unscoped; trailing-/* requires a
  non-empty child), converging with the queued admin fix on a shared
  pinned test table; version-scope the old runtime edge cases.
- blog/tenant-lifecycle-cascade-semantics-at-scale.md: replace the
  "does not yet include TENANT_CLOSED" parenthetical; clarify the
  409-vs-401 window in the example comment.
- how-to/tenant-creation-and-management-in-cycles.md: runtime-plane
  sentence after the cascade paragraph; troubleshooting entry notes both
  planes.
- protocol/error-codes-and-error-handling-in-cycles.md: 16 -> 17 runtime
  wire codes; runtime-plane paragraph under TENANT_CLOSED (409); update
  the four reservation-operation tables to cite cycles-server 0.1.25.47 /
  spec v0.1.25.13; remove the incorrect TENANT_CLOSED row from the Event
  errors table (the guard does not cover /v1/events) and add a note;
  closed-tenant note in Decide errors; TENANT_CLOSED reason-code row;
  INTERNAL_ERROR fail-closed variant.
- protocol/implement.md, protocol/api-reference-for-the-cycles-protocol.md,
  protocol/how-decide-works-*.md, protocol/dry-run-shadow-mode-*.md,
  protocol/event-payloads-reference.md, glossary.md: add TENANT_CLOSED to
  the error-code and reason-code enumerations ("six" -> "seven" known
  DecisionReasonCode values).
- changelog.md: refresh the spec-revision rows (runtime v0.1.25.13,
  governance v0.1.25.37) and add the v0.1.25.13 spec-suite bullet.
  cycles-server row stays at v0.1.25.46 (0.1.25.47 is not yet tagged).

Merge-gated (draft) on the cycles-server v0.1.25.47 release tag and the
next cycles-server-admin release; "queued" phrasings in the scope-filter
docs get a final s/queued/shipped-in-X.Y.Z/ pass before merge.
…ime row

Codex external review (REVISE-MINOR) on PR #787.

| # | Finding | Action |
|---|---|---|
| 1 | reservation.released_via_tenant_cascade example showed per-reservation fields | APPLIED — data block rewritten to the verified emission (ledger_id, scope, unit, released_amount, cascade_reason="tenant_closed"; TenantCloseCascadeService.emitReservationReleaseEvent). Sweep found the same bug class in the three sibling examples on the page: budget.closed (final_* / closed_at / cascade_origin -> prior_status/new_status/cascade_reason + actor type "admin"), api_key.revoked (revoked_at/cascade_origin -> prior_status/new_status/cascade_reason), webhook.disabled (url/disabled_at/cascade_origin -> prior_status/new_status/name/cascade_reason) — all fixed against the emitter; added a shared EventDataTenantCascade shape note. No other page carries a cascade-event JSON example. |
| 2 | Blog Rule 1 "strictly ordered sequence ... flip LAST" contradicts Mode B | APPLIED — sequence scoped as the Mode A presentation; explicit note that Mode B (the runcycles implementation) inverts it (flip first, children converge under Rule 2). No restructuring. |
| 3 | "Release-like language" for cycles-server 0.1.25.47 | SKIPPED — PR is draft-gated on the v0.1.25.47 tag (MERGE GATES item 1); prose is accurate at publish time by construction. |
| 4 | changelog.md minimum-versions row lacked the runtime half | APPLIED — split into admin-plane row (unchanged content, retitled) + new runtime-guard row: cycles-server v0.1.25.47 (runtime spec v0.1.25.13). Latest-tag rows stay v0.1.25.46 per gate 1. |
@amavashev

Copy link
Copy Markdown
Contributor Author

Codex review round 1 — REVISE-MINOR — resolved in 0de2e39

# Finding Action
1 reservation.released_via_tenant_cascade JSON example showed per-reservation fields (reservation_id, reserved, release_reason, cascade_origin) contradicting the ledger-level-aggregate prose Applied. Verified against the actual emitter (TenantCloseCascadeService.emitReservationReleaseEvent, cycles-server-admin main): the emitted data map is exactly ledger_id, scope, unit, released_amount, cascade_reason: "tenant_closed" — example rewritten to match. Sweep: no other page carries a cascade-event JSON example (cascade page, blog, webhook-delivery page only reference the event names / "reason tenant_closed" in prose — consistent). However, the three sibling examples on the same page had the same bug class: budget.closed_via_tenant_cascade showed final_allocated/final_spent/final_reserved/final_debt/closed_at/cascade_origin (actual: ledger_id, scope, unit, prior_status, new_status: "CLOSED", cascade_reason) plus an actor.type: "api_key" block (actual: {"type": "admin"}); api_key.revoked_… showed revoked_at/cascade_origin (actual: key_id, prior_status, new_status: "REVOKED", name?, cascade_reason); webhook.disabled_… showed url/disabled_at/cascade_origin (actual: subscription_id, prior_status, new_status: "DISABLED", name?, cascade_reason). All four fixed against the emitter, and a shared EventDataTenantCascade-shape note added to the section intro.
2 Blog described Rule 1 as a strictly ordered sequence with the tenant flip LAST, contradicting Mode B (and the runcycles flip-first implementation) documented later in the same post Applied (surgical). The sequence is now introduced as the Mode A (atomic) presentation's recommended in-transaction order, with an explicit note that Mode B inverts it — flip first, children converge under the Rule 2 guard — matching the post's own Mode A/B section and the cascade reference page. No restructuring.
3 "Release-like language" for cycles-server 0.1.25.47 (error-codes ~128, cascade page ~131, glossary ~296) while the tag doesn't exist Skipped, deliberately. This PR is a draft gated on the v0.1.25.47 release tag existing (MERGE GATES item 1 in the description) — the prose is accurate at publish time by construction. Adding "pending tag" hedges would only need reverting in the pre-merge pass, and the implementation itself is already merged to cycles-server main.
4 changelog.md minimum-versions row framed TENANT_CLOSED around admin/dashboard/governance only Applied. Split per the table's one-feature-per-row style: the existing row is retitled to the admin-plane scope, and a new row carries the runtime half — TENANT_CLOSED (409) guard on reservation create/commit/release/extend + reason_code=TENANT_CLOSED on fresh dry-run//v1/decide DENYs → cycles-server v0.1.25.47 (runtime spec v0.1.25.13). The latest-tag component rows intentionally remain at v0.1.25.46 until gate 1.

Codex round 2 (REVISE, one P2): the round-1 blog caveat said the commit
example's 409 TENANT_CLOSED is reachable with "an admin key", but the
runtime admin dual-auth allowlist (AdminApiKeyAuthenticationFilter:
GET /v1/reservations, GET /v1/reservations/{id},
POST /v1/reservations/{id}/release) exposes only RELEASE of the four
guarded mutations to X-Admin-API-Key — create/commit/extend are
deliberately not admin-reachable.

| Hit | Verdict |
|---|---|
| blog commit-example caveat | APPLIED — tenant-key post-flip window is the reachable path for commit; note that only release also accepts an admin key (admin-on-behalf-of release hits the 409 with no race) |
| tenant-close-cascade-semantics.md observability note ("admin-key mutations") | APPLIED — now: admin-on-behalf-of release (only admin-exposed guarded mutation; allowlist is list/get/release) + tenant-key race window for any of the four |
| error-codes TENANT_CLOSED runtime paragraph (same phrase) | APPLIED — same correction |
| api-reference lines 10/24, glossary AdminKeyAuth/Admin-on-Behalf-of, implement.md dual-auth note, changelog v0.1.25.8 rows | correct-as-is — pre-existing text already states list/get/release only |
| error-codes + api-reference op-table TENANT_CLOSED rows, how-to runtime sentences | correct-as-is — no key-type claims |
@amavashev

Copy link
Copy Markdown
Contributor Author

Codex review round 2 — REVISE (one P2) — resolved in 2e8018c

Finding (P2, applied): the round-1 caveat on the blog's commit example said the 409 TENANT_CLOSED is reachable with "an admin key". Verified against the runtime admin dual-auth filter (AdminApiKeyAuthenticationFilter, cycles-server main): the allowlist is exactly GET /v1/reservations, GET /v1/reservations/{id}, and POST /v1/reservations/{id}/release — the filter's own comment notes commit/extend are "deliberately NOT exposed to admin". So of the four guarded mutations, only release is admin-key reachable; commit's 409 is reachable only via a not-yet-revoked tenant key in the post-flip race window.

Claim-class sweep across the PR's changed files (statements naming which key types can hit 409 TENANT_CLOSED on which operations):

Hit Verdict
blog/tenant-lifecycle-cascade-semantics-at-scale.md — commit-example caveat Applied — the commit example is now framed as the tenant-key post-flip window, with a note that of the four guarded mutations only release also accepts an admin key (an admin-on-behalf-of release hits the 409 with no race)
protocol/tenant-close-cascade-semantics.md — observability note ("surfaces mainly on admin-key mutations ... and in the race window") Applied — now: admin-on-behalf-of release (only guarded mutation exposed to X-Admin-API-Key; allowlist is reservation list/get/release, so create/commit/extend are never admin-key reachable) + the post-flip/pre-revocation race window for tenant-key requests on any of the four
protocol/error-codes-and-error-handling-in-cycles.md — same "admin-key mutations" phrase in the runtime paragraph Applied — same correction
protocol/api-reference-for-the-cycles-protocol.md:10, 24 (dual-auth intro + header table) Correct-as-is — pre-existing text already says list/detail/release only
glossary.md — AdminKeyAuth / Admin-on-Behalf-of entries Correct-as-is — "three runtime endpoints (listReservations, getReservation, release)"
protocol/implement.md:108 — dual-auth note Correct-as-is — lists the same three operations
changelog.md:67, 325 — v0.1.25.8 admin-on-behalf-of release rows Correct-as-is — release only
error-codes + api-reference per-op TENANT_CLOSED table rows; how-to/tenant-creation runtime sentences; cascade-page shipped-behavior bullets Correct-as-is — no key-type claims

Codex verdicts so far: round 1 REVISE-MINOR → applied (0de2e39); round 2 REVISE (this P2) → applied (2e8018c). Everything else from round 2 confirmed: cascade payload fields match TenantCloseCascadeService, Mode A/B wording consistent, changelog split coherent.

@amavashev

Copy link
Copy Markdown
Contributor Author

External review (codex) complete: SHIP after round 3. Round 1 (REVISE-MINOR): all four tenant-cascade event JSON examples rewritten against the actual TenantCloseCascadeService emitter; blog Rule 1 sequence scoped as Mode A with the Mode B flip-first note; changelog minimum-versions row split admin-plane vs runtime-guard; 0.1.25.47 release-language flag skipped with reasoning (draft is merge-gated on the tag). Round 2 (P2): 409 TENANT_CLOSED key-type reachability corrected — of the four guarded mutations only release is admin-key reachable (AdminApiKeyAuthenticationFilter allowlist); create/commit/extend reach the 409 only via the post-flip/pre-revocation tenant-key window. Round 3: confirmed clean. PR remains DRAFT per the merge gates: (1) cycles-server v0.1.25.47 tag, (2) next cycles-server-admin release tag + final queued→shipped pass on the scope-filter page.

… tagged

Both merge gates for this branch are satisfied: cycles-server v0.1.25.47
(TENANT_CLOSED Rule 2 guard + webhook matcher parity) and
cycles-server-admin v0.1.25.49 (spec-conformant webhook scope-filter
matching + replay filtering) are release-tagged 2026-07-10.

- protocol/webhook-scope-filter-syntax.md: every "queued for the next
  cycles-server-admin release" phrasing replaced with the shipped version
  (admin 0.1.25.49); the divergence callout leads with "Resolved in
  current releases" and links the release's migration notes; softened
  ::: danger -> ::: warning now that the divergence is version-scoped
  history rather than a live issue in current releases (matches the
  page corpus convention: danger for issues live in latest, tip/warning
  for resolved version-scoped behavior); syntax-summary spec column and
  Examples intro note both-planes conformance on 0.1.25.49+/0.1.25.47+.
- changelog.md: current-version rows -> cycles-server v0.1.25.47 and
  cycles-server-admin v0.1.25.49 (2026-07-10); new release entries in
  both component sections (styled like neighbors, admin entry carries
  the BEHAVIOR CHANGE migration warning); compatibility-matrix rows
  bumped. Minimum-versions rows were already consistent.
- quickstart/architecture-overview-how-cycles-fits-together.md: the
  "latest tagged versions" line updated to the 2026-07-10 tags (its
  prior correct-as-is verdict was justified by the tags not existing).

Corpus sweep for the queued/pending class ("queued", "next
cycles-server-admin release", "pending release", "not yet tagged",
"untagged", "fix ships"): zero release-pending survivors — every
remaining "queued"/"PENDING" hit is delivery-queue or
graceful-degradation semantics (verdict: correct-as-is).

PR stays a draft per owner instruction; body updated to mark both
gates satisfied.
@amavashev

Copy link
Copy Markdown
Contributor Author

Merge gates satisfied — final version-stamp pass in a0d98ff

Both gates landed today: cycles-server v0.1.25.47 and cycles-server-admin v0.1.25.49 (verified via git fetch --tags in both clones; the admin release's full-stack compose pins server 0.1.25.47).

  • protocol/webhook-scope-filter-syntax.md — all "queued for the next cycles-server-admin release" phrasings replaced with cycles-server-admin 0.1.25.49 (2026-07-10); the callout now leads with "Resolved in current releases" (admin 0.1.25.49 + runtime 0.1.25.47 → both planes match identically, pinned to the same test table) and links the release's migration notes (bare-prefix filters must become …/*; "base + descendants" needs two subscriptions). Severity decision: softened ::: danger::: warning — the corpus convention is danger for divergences live in the latest release and tip/warning for resolved, version-scoped history (cf. the commit_overage tip on the same page); the "0.1.25.48 and earlier" content is retained verbatim as history.
  • changelog.md — current-version rows → server v0.1.25.47 / admin v0.1.25.49 (2026-07-10); new release entries added to both component sections in each section's style (the admin entry carries the BEHAVIOR CHANGE migration warning); compatibility-matrix rows bumped; minimum-versions rows were already consistent from the earlier rounds.
  • quickstart/architecture-overview-how-cycles-fits-together.md — "latest tagged versions" line updated to the 2026-07-10 tags (its earlier correct-as-is verdict was justified only by the tags not existing).

Sweep (queued/pending class — "queued", "next cycles-server-admin release", "pending release", "not yet tagged", "untagged", "fix ships"): zero release-pending survivors. Every remaining corpus hit is delivery-queue or graceful-degradation semantics (webhook PENDING/"Not queued" status tables, "Request queued. Retrying…" client snippets, blog degradation copy) — all correct-as-is. Deployment-example image pins (quickstarts/how-tos pinning server 0.1.25.46 / admin 0.1.25.48) are known-good pinned stacks, not "latest" claims; bumping them is a separate coordinated refresh if desired.

PR body updated: MERGE GATES → both satisfied with tag links. Content is final; the PR remains a draft per owner instruction — ready to merge pending owner approval.

Reviewer round on PR #787: High (changelog rows stale) rejected — branch
head a0d98ff already carries v0.1.25.47/v0.1.25.49 at changelog.md:20-21
and :304-305; reviewer checkout was stale. Medium applied:

- how-to/tenant-creation-and-management-in-cycles.md: the runtime-plane
  sentence after the cascade paragraph now carries the auth-layer caveat
  (tenant-key calls usually 401 at the auth filter before the guard; the
  runtime 409 surfaces on admin-on-behalf-of release and the
  post-flip/pre-revocation race window), cross-referencing the cascade
  page's observability note instead of duplicating it. The v0.1.25.36+
  version-table row is narrowed to the admin plane ("Any admin-plane
  mutation attempt returns 409 TENANT_CLOSED; runtime tenant-key
  mutations usually fail 401 first").
- changelog.md 0.1.25.47 entry: same one-clause qualifier added (sweep
  hit of the same class).
- changelog.md compatibility cell "Cycles Admin Server | v0.1.25.49 |
  Governance spec v0.1.25.34" left as-is deliberately: that column
  tracks the component's own declared spec alignment, and the admin
  repo's README on current main (post-0.1.25.49) still declares
  "Aligned with Cycles Protocol v0.1.25.34" — governance .35-.37 are
  additive revisions admin has not re-declared against. The separate
  "Current component versions" row for the governance spec itself
  correctly reads v0.1.25.37.

Sweep (runtime-409-without-401-qualifier class) across the changed-file
set: remaining "returns 409" hits are per-endpoint error-table cells,
the summary code-by-status enumeration, normative spec statements inside
the section that carries the observability note, glossary/blog
admin-plane contract descriptions, and version-gate capability rows —
all correct-as-is (they enumerate conditions, not observed likelihood).
@amavashev

Copy link
Copy Markdown
Contributor Author

Reviewer round — High rejected as stale, Medium applied in 40161fd

High (changelog.md current-version rows still v0.1.25.46/48): REJECTED — stale checkout. On branch head a0d98ff (the commit before this round), the rows already carry the new tags:

  • changelog.md:20-21| cycles-server (runtime) | v0.1.25.47 | 2026-07-10 | / | cycles-server-admin | v0.1.25.49 | 2026-07-10 |
  • changelog.md:304-305 — compatibility matrix: Cycles Server (runtime) | v0.1.25.47, Cycles Admin Server | v0.1.25.49

No change needed.

Medium (missing 401-first caveat on the how-to runtime sentences): VALID — applied.

  • how-to/tenant-creation-and-management-in-cycles.md (cascade paragraph): added one sentence — even on 0.1.25.47+, tenant-key runtime calls usually fail 401 at the auth filter before the guard is reached; in practice the runtime 409 surfaces on admin-on-behalf-of release and in the post-flip/pre-revocation race window — cross-referencing the cascade page's observability note rather than duplicating it.
  • Same file, "What to expect after close" v0.1.25.36+ row: narrowed to the admin plane — "Any admin-plane mutation attempt returns 409 TENANT_CLOSED; runtime tenant-key mutations usually fail 401 at the auth filter first."

Compatibility cell check (Cycles Admin Server | v0.1.25.49 | Governance spec v0.1.25.34): left as-is, deliberately. That column records the component's own declared spec alignment: the cycles-server-admin README on current main (post-0.1.25.49) still states "Aligned with Cycles Protocol v0.1.25.34", and governance .35/.36/.37 are additive revisions the admin repo hasn't re-declared alignment against (its 0.1.25.48 entry references the .35 EventDataTenantCascade schema, but the declared alignment was not bumped). The spec's own latest revision is tracked separately — the "Current component versions" table correctly shows Governance spec (admin) | v0.1.25.37 | 2026-07-10. If the admin repo bumps its declared alignment in a future release, the cell follows.

Claim-class sweep (runtime mutations "return 409" near closed/cascade without the 401-first qualifier), changed-files set:

Hit Verdict
how-to cascade paragraph + v0.1.25.36+ table row Updated (this round)
changelog.md 0.1.25.47 release entry Updated — same one-clause qualifier added (same class)
cascade page §"What the runtime plane sees" normative sentence + shipped-behavior bullets Correct-as-is — the observability note with the full qualifier lives in the same section
error-codes page: TENANT_CLOSED runtime paragraph Correct-as-is — already carries the qualifier (round 2)
error-codes + api-reference per-op 409 table rows; error-codes summary code-by-status list Correct-as-is — enumerations of possible error conditions, not observed likelihood; qualifier documented in the section they link to
implement.md MUST list Correct-as-is — implementer-facing normative requirement (a conformant server MUST return the 409 when the request reaches the guard)
glossary Tenant-Close Cascade entry; blog Rule 2 paragraph; changelog v0.1.25.35 admin entry Correct-as-is — admin-plane contract descriptions
version-gate capability rows (cascade page matrix, changelog minimum-versions) Correct-as-is — feature-availability statements
blog commit example Correct-as-is — carries the full window/admin-release caveat since round 2

PR stays a draft; content ready pending owner approval.

…0.1.25.37)

cycles-server-admin #207 (merge abda6a9) bumped the admin README's
declared governance-spec alignment v0.1.25.34 -> v0.1.25.37 (verified
per-revision upstream: .35 EventDataTenantCascade schema, .36
admin_on_behalf_of actor, .37 documentation-only). The changelog.md
compatibility cell mirrors the component's declared alignment, so it
follows: "Cycles Admin Server | v0.1.25.49 | Governance spec
v0.1.25.37" — now consistent with the current-versions table's
"Governance spec (admin) | v0.1.25.37" row.

Corpus sweep for other declared-alignment-as-.34 citations: none — the
remaining "0.1.25.34" hits are the admin v0.1.25.34 release entry, the
dashboard v0.1.25.34 component release, and the dashboard v0.1.25.59
historical spec-alignment note, all correct-as-is.
@amavashev

Copy link
Copy Markdown
Contributor Author

Reviewer Medium (compatibility cell) — now resolved in 0c7a77c via the upstream fix

The earlier "left as-is" verdict on changelog.md's compatibility cell (Cycles Admin Server | v0.1.25.49 | Governance spec v0.1.25.34) was based on the admin repo's own declaration, which was itself stale. That declaration has now been corrected upstream: cycles-server-admin #207 (merge abda6a9) bumps the README's declared governance-spec alignment to v0.1.25.37, verified per-revision (.35 EventDataTenantCascade schema field-for-field, .36 admin_on_behalf_of actor emitted since v0.1.25.14, .37 documentation-only).

The cell now follows the corrected declaration — Governance spec v0.1.25.37 — consistent with the current-versions table's Governance spec (admin) | v0.1.25.37 row, rather than the docs contradicting the component README in either direction.

Sweep for other citations of the admin plane's declared alignment as .34: none in the corpus — the remaining 0.1.25.34 hits are the admin v0.1.25.34 release entry, the dashboard v0.1.25.34 component release, and the dashboard v0.1.25.59 historical spec-alignment note (all correct-as-is).

PR remains a draft; ready pending owner approval.

examples, no emission-order guarantee, observable-outcome scoping

Five findings, each verified against the yaml specs (authority) and the
admin implementation before editing.

1 (MEDIUM, applied + class sweep): the four *_via_tenant_cascade names
ARE declared in the governance spec's EventType enum since revision
v0.1.25.35 (cycles-governance-admin-v0.1.25.yaml lines 1625/1641/1663/
1658; enum now 51 values; the runtime spec defines no EventType enum at
all — event_type is a free dotted string there). Audit entries are
MUST / RESERVED; Event emission is SHOULD. Rewrote the "absent from the
published spec enum" claim and every variant, and re-derived the counts:
- event-payloads-reference.md: intro (47->51, per-category counts),
  currently-emitted list gains the cascade row, cascade section intro
  + heading (now "governance spec v0.1.25.35+"), emission-summary table
  (per-category totals + 51), "additive reference-server payloads" list
  now correctly covers only reservation.reserved/.committed/.released/
  .extended + event.applied (verified genuinely absent from the enum).
- tenant-close-cascade-semantics.md + how-to/tenant-creation: enum
  claims corrected; "do not rely on cross-server event_type= filtering"
  replaced (filtering is spec-valid; emission is SHOULD-level).
- webhook-event-delivery-protocol.md: description + "Event types (51)"
  + per-category counts + tenant-accessible 27-of-47 -> 29-of-51 +
  fan-out section rewrite.
- concepts/webhooks-and-events.md (heading, table, 29-of-51),
  admin-api/guide.md, how-to/webhook-integrations.md (29-of-51),
  how-to/managing-webhooks.md (category counts 17/6),
  how-to/migrating-from-custom-rate-limiter (51), prometheus-metrics
  (51 twice), glossary Event entry, blog/real-time-budget-alerts live
  count (dated as-of-post-date figures left untouched).

2 (MEDIUM, applied): WebhookCreateRequest requires event_types with
minItems 1 (yaml :2591/2608; server @notempty). Fixed the two invalid
examples on webhook-scope-filter-syntax.md (event_types: [] and the
omitted-both form): the "everything" form is a representative type +
all-categories event_categories (union semantics), cross-referencing
managing-webhooks' category-based subscriptions. Sweep: no other page
shows an empty/omitted event_types create example.

3 (MEDIUM, applied): the governance spec has NO cascade event-emission
ordering guarantee (its ordering language is the Mode A mutation-order
SHOULD); the implementation interleaves budget.closed + the reservation
aggregate per budget, then webhooks, then keys. Rewrote the event-
payloads "Ordering guarantee" paragraph: order is implementation-
defined, do not rely on arrival order, join via correlation_id. Sweep:
no other order-reliant statements (the cascade page's Rule 1 ordering
text is about mutations and already says "do not rely on this").

4 (LOW, applied): blog "same client-observable outcome ... every
mutation returns 409" scoped to admin-plane mutations, with the
runtime post-auth/401-first door noted per the post's own walkthrough.
Class re-sweep (observable outcome / uniform / identical): cascade
page's variant already says "admin-plane mutations"; no other hits.

5 (NIT, applied): changelog.md `dry_run`//v1/decide markup fixed.
@amavashev

Copy link
Copy Markdown
Contributor Author

Reviewer round 2 — all five findings resolved in 73d0d69

Each finding was verified against the yaml specs (authority: cycles-governance-admin-v0.1.25.yaml on cycles-protocol main) and the admin implementation before editing.

# Finding Verdict Evidence + action
1 MEDIUM — docs claim the four *_via_tenant_cascade names are "absent from the published spec enum" Applied + class sweep The four names ARE in the governance spec's EventType enum (yaml lines 1625 / 1641 / 1658 / 1663), added in document revision v0.1.25.35 — enum total is now 51 (was 47). The runtime spec defines no EventType enum at all (event_type is a free dotted string in its webhook guidance), so the statement is scoped to the governance spec. Normative strength preserved: cascade audit entries are MUST (RESERVED event_kinds); Event emission is SHOULD — so cross-server event_type= filtering is now spec-valid, but non-reference servers may not emit the events. Rewrote every variant and re-derived all counts (47→51; tenant-accessible 27-of-47→29-of-51; per-category budget 17, reservation 6, api_key 7, webhook 7): event-payloads-reference (intro, emitted list, cascade-section heading/intro, emission-summary table), tenant-close-cascade-semantics, how-to/tenant-creation ("do not rely on cross-server event_type= filtering" replaced), webhook-event-delivery-protocol (description, "Event types (51)", fan-out section), concepts/webhooks-and-events, admin-api/guide, webhook-integrations, managing-webhooks (category counts 17/6), migrating-from-custom-rate-limiter, prometheus-metrics-reference, glossary Event entry, blog/real-time-budget-alerts (live-count pointer only; as-of-post-date figures untouched). The remaining "not part of the published enum" sentence now covers only reservation.reserved/.committed/.released/.extended + event.applied — verified genuinely absent from the enum.
2 MEDIUM — event_types: [] / omitted in webhook-create examples Applied WebhookCreateRequest requires event_types with minItems: 1 (yaml :2591, :2608) and the server enforces @NotEmpty (400 on empty/omitted). Fixed both invalid examples on webhook-scope-filter-syntax.md; the "receive everything" form is a representative event_types entry + all-seven event_categories (matching is union — matchesEventType ORs type and category), cross-referenced to managing-webhooks' [Category-based subscriptions] which already documents the pattern. Sweep: no other page shows an empty/omitted event_types create example (admin-api/guide, managing-webhooks, webhook-integrations, quickstarts, blogs all use non-empty lists).
3 MEDIUM — "the spec mandates emission order" for cascade events Applied The governance spec's only ordering language is the Mode A mutation-order SHOULD (yaml :90-93); it is silent on event emission order. The implementation (TenantCloseCascadeService) interleaves per budget — budget.closed… then the reservation aggregate for reserved > 0 budgets — followed by webhook and API-key events, contradicting the documented "reservations released → budgets closed" sequence on both counts. Rewrote the paragraph: no emission-order guarantee, order is implementation detail, subscribers MUST NOT rely on arrival order (at-least-once delivery reorders regardless); join via correlation_id. Sweep: no other order-reliant statements — the cascade page's Rule 1 ordering text is explicitly about mutations, Mode A-scoped, and already says "do not rely on this ordering in practice".
4 LOW — blog "same client-observable outcome: every mutation returns 409" Applied Scoped to admin-plane mutations, with the runtime post-auth door noted (revoked tenant keys 401 before the guard; "the close walkthrough below shows both doors") — consistent with the post's own later explanation. Class re-sweep with the new variants (observable outcome / uniform / identical): the cascade page's equivalent sentence already reads "admin-plane mutations"; no other hits corpus-wide.
5 NIT — `dry_run`//v1/decide markup in changelog.md Applied Now `dry_run` / `/v1/decide`. No other instances of the malformed pattern.

PR remains a draft; ready pending owner approval.

v0.1.25.38 / admin 0.1.25.50)

The reviewer flagged three tenant-self-service-boundary statements as
currently-false; the upstream fix landed — governance spec revision
v0.1.25.38 (cycles-protocol#126) makes category validation NORMATIVE on
both POST /v1/webhooks and PATCH /v1/webhooks/{id} (an admin-only
event_categories entry MUST be rejected 400 INVALID_REQUEST, since
categories are additive with types in delivery matching), and
cycles-server-admin 0.1.25.50 (released, tagged) enforces it and closes
the legacy empty-both match-ALL update door. So the statements are now
true AND enforced.

Verified against cycles-governance-admin-v0.1.25.yaml (version 0.1.25.38,
TENANT-ACCESSIBLE BOUNDARY NORMATIVE block at ~line 7243; "29 of 51
types") and the admin 0.1.25.50 CHANGELOG entry.

- protocol/webhook-event-delivery-protocol.md (~157, normative-ish home):
  added the one version-scoped note — boundary normative in spec
  v0.1.25.38 (both event_types AND event_categories validated, 400 on an
  admin-only entry, rationale: categories are additive); admin 0.1.25.50
  enforces the category check + closes the empty-both door; 0.1.25.49 and
  earlier validated event_types only; operators should audit existing
  tenant subscriptions (release-notes link).
- concepts/webhooks-and-events.md, how-to/webhook-integrations.md,
  admin-api/guide.md tenant-self-service lines: left as plain capability
  descriptions (now simply correct; not over-annotated per the single-
  note guidance). Counts were already 29 of 51 from the prior round.
- Fixed two stale "47-event-type" cross-reference mentions
  (admin-api/guide.md, how-to/webhook-integrations.md related-links) to
  51.

Tenant-context webhook-create examples (admin-api/guide, managing-
webhooks, webhook-integrations) all use tenant-accessible event_types
only, no event_categories, no admin category shown — no change needed.
Dated blog figure (real-time-budget-alerts, "27 of 41 as of post date;
live 29 of 51") left as-is — already points to the live count.
@amavashev

Copy link
Copy Markdown
Contributor Author

Tenant-webhook category-boundary claim — High resolved by upstream fix + release, in f23e03d

The reviewer's High (three docs statements said tenant self-service webhooks are restricted to budget/reservation/tenant with api_key/policy/webhook/system admin-only — accurate for event_types but the event_categories field was unvalidated, so an admin-only category could widen a tenant subscription) is resolved not by softening the docs but by the upstream fix landing:

  • Governance spec revision v0.1.25.38 (cycles-protocol#126, merged) makes the boundary NORMATIVE on both POST /v1/webhooks and PATCH /v1/webhooks/{id}: every event_categories entry MUST be a tenant-accessible category (budget/reservation/tenant), an admin-only one MUST be rejected 400 INVALID_REQUEST — rationale stated in the spec: categories are additive with types in delivery matching, so validating types alone doesn't enforce the boundary. Verified in cycles-governance-admin-v0.1.25.yaml (version: 0.1.25.38, TENANT-ACCESSIBLE BOUNDARY NORMATIVE block).
  • cycles-server-admin 0.1.25.50 (released, tagged) enforces the category check on create AND update, and closes the legacy update path that could leave both arrays empty (delivery-side match-ALL). Verified in the 0.1.25.50 CHANGELOG (Security).
  • Residual provenance risk tracked as cycles-server-admin#209.

So the three statements are now true and enforced. Changes:

Page Verdict Action
protocol/webhook-event-delivery-protocol.md ~157 Updated (version note) Added the single version-scoped note here (the normative-ish home): boundary normative in spec v0.1.25.38 (both event_types and event_categories validated, 400 on admin-only, additive rationale); admin 0.1.25.50 enforces + closes the empty-both door; 0.1.25.49 and earlier validated event_types only; operators should audit existing tenant subscriptions (release-notes link).
concepts/webhooks-and-events.md ~64 Correct-as-is Plain capability description, now simply correct; not annotated (per the "don't over-annotate every mention" guidance). Count already 29 of 51 from the prior round.
how-to/webhook-integrations.md ~919 Correct-as-is Same — plain capability description, now correct.
admin-api/guide.md ~339 Correct-as-is Same.

Sweep of the tenant-webhook-boundary claim class (self-service / admin-only / event_categories / accessible-type counts) across the corpus: no other statement implies categories are unvalidated or shows an admin category on a tenant subscription. Both tenant-context webhook-create examples (admin-api/guide, how-to/webhook-integrations, managing-webhooks) use tenant-accessible event_types only, no event_categories, no admin category — no change needed. The dated blog figure (real-time-budget-alerts: "27 of 41 as of post date; the live count is 29 of 51") already points to the live count — left as-is.

Also fixed two stale "47-event-type" cross-reference mentions (admin-api/guide related-link, webhook-integrations related-link) → 51, completing the 47→51 count sweep from the previous round.

PR remains a draft; ready pending owner approval.

Reviewer Medium: docs said the server rejects an empty event_types list.
Wrong on two counts — category-only subscriptions (empty event_types +
non-empty event_categories) are legal, and what the server actually
rejects is the empty-BOTH state. Aligns docs to the Option-1 design
(governance revision v0.1.25.39, pending; shipped in cycles-server-admin
0.1.25.50).

Verified against the shipped impl (governance v0.1.25.39 not yet on
protocol main — main is 0.1.25.38):
- WebhookCreateRequest.eventTypes = @notempty  -> create requires
  non-empty event_types.
- WebhookUpdateRequest.eventTypes = no validation annotation.
- WebhookService.update guard rejects only empty-BOTH:
  "Subscription must retain at least one event_type or event_category"
  (400 INVALID_REQUEST).
So the create/update asymmetry is real: create needs event_types;
PATCH may clear event_types when event_categories is non-empty.

- protocol/webhook-scope-filter-syntax.md ~95: rewrote the "event_types
  required / server rejects empty list" line to the real rule (at least
  one of the two selectors must be non-empty; empty-both -> 400; create
  needs event_types, update may go category-only). Fixed the "receive
  everything" note's "cannot be empty or omitted (minItems: 1)" claim
  the same way.
- how-to/managing-webhooks.md: scoped the create "required" line and the
  category-note "always required" to create; added a category-only PATCH
  example (event_types: [] + event_categories) to the update section.
- how-to/webhook-integrations.md: budget category count 16 -> 17 (stale
  residual from the 47->51 sweep; budget.closed_via_tenant_cascade is a
  budget.* type delivered to a budget category subscription).

Sweep (event_types-always-required / empty-rejected / must-name-a-type):
managing-webhooks:18 + :70 fixed (were blanket/"always"); create-only
statements left correct; no other hits. Both tenant-context create
examples still use non-empty event_types (create path) — unchanged.
@amavashev

Copy link
Copy Markdown
Contributor Author

Medium (empty-event_types contradiction) resolved — aligned to Option-1 design, in e948995

The docs said the server rejects an empty event_types list. That's wrong on two counts, per the Option-1 design (governance revision v0.1.25.39, pending; shipped in cycles-server-admin 0.1.25.50): category-only subscriptions are legal, and the server actually rejects the empty-both state.

Verified against the shipped impl (governance v0.1.25.39 is not yet on cycles-protocol main — main is 0.1.25.38 — so I read the code rather than the pending spec):

  • WebhookCreateRequest.eventTypes@NotEmptycreate requires non-empty event_types.
  • WebhookUpdateRequest.eventTypes → no validation annotation.
  • WebhookService.update guards only the empty-both case: throws INVALID_REQUEST (400) "Subscription must retain at least one event_type or event_category".

So the create/update asymmetry is real: create needs event_types; PATCH may clear it as long as event_categories is non-empty (a category-only subscription).

Changes

  • protocol/webhook-scope-filter-syntax.md ~95 — rewrote the rule: a subscription must match on at least one selector, so at least one of event_types / event_categories must be non-empty (empty-both → 400 INVALID_REQUEST, governance v0.1.25.39 pending / admin 0.1.25.50); create requires event_types, PATCH may go category-only. Also fixed the "receive everything" note that repeated the "cannot be empty (minItems: 1)" claim.
  • how-to/managing-webhooks.md — scoped the create "required fields" line and the category-note "always required" to create; added a small category-only PATCH example (event_types: [] + event_categories) to the Updating section.
  • how-to/webhook-integrations.md — budget-category count 16 → 17 (stale residual from the prior 47→51 sweep; budget.closed_via_tenant_cascade is a budget.* type delivered to a budget category subscription).

Sweep — claim class (event_types always required / empty rejected / must name a type)

Hit Verdict
webhook-scope-filter-syntax.md:95 ("required, minItems: 1, rejects empty list") Updated — real rule (type-or-category; create-vs-update asymmetry)
webhook-scope-filter-syntax.md:137 ("cannot be empty or omitted") Updated — scoped to create; category-only possible on PATCH
managing-webhooks.md:18 ("Required fields: event_types (at least one)") Updated — scoped to create; added the update/category-only rule
managing-webhooks.md:70 ("event_types is always required") Updated — "always" was the overreach; scoped to create
managing-webhooks.md create examples, tenant-context create examples (admin-api/guide, webhook-integrations) Correct-as-is — all on the create path, which does require non-empty event_types

No other hits corpus-wide. PR #787 stays a draft; ready pending owner approval.

…v0.1.25.39

Releases moved while the PR sat as a draft. Verified against source:
cycles-server-admin latest tag = 0.1.25.50; cycles-governance-admin yaml
info.version on cycles-protocol main = 0.1.25.39 (PR #127 merged); admin
README declares alignment with governance v0.1.25.39.

- changelog.md current-version table: cycles-server-admin 0.1.25.49 ->
  0.1.25.50 (2026-07-10); governance spec (admin) v0.1.25.37 -> v0.1.25.39.
- changelog.md admin component section: new [0.1.25.50] entry above [.49]
  summarizing the SECURITY fix (tenant-plane event_categories validated
  against the tenant-accessible boundary; empty-both match-ALL update
  door closed; upgrade-past-.49 + audit note; release-notes link;
  category-only-valid-on-update per gov v0.1.25.39; provenance issue
  cycles-server-admin#209). The [.49] entry was already present.
- changelog.md compatibility matrix: Cycles Admin Server v0.1.25.49 ->
  0.1.25.50, governance spec v0.1.25.37 -> v0.1.25.39 (matches the admin
  README's declared alignment).
- quickstart/architecture-overview: latest-tagged line date 2026-07-10 ->
  2026-07-11, admin 0.1.25.49 -> 0.1.25.50.
- protocol/webhook-scope-filter-syntax.md ~95: dropped "pending" from the
  governance v0.1.25.39 reference (merged on main).

Sweep verdicts:
- "0.1.25.49" current/latest: none. Remaining hits are historical
  provenance (scope-filter matcher parity shipped in .49; the .49
  changelog entry; "0.1.25.49 and earlier validated event_types only"
  version-scoping; dashboard .49; the new .50 entry's upgrade-past-.49
  note) or version-scoped matcher statements ("admin 0.1.25.49+") — all
  correct-as-is.
- Governance ".37"/".38" as current: none. The two .38 refs are correct
  provenance (the revision that made the category boundary normative),
  not current-version claims.
- ".39 (pending)" / pending governance: none remaining after the fix.
@amavashev

Copy link
Copy Markdown
Contributor Author

Version-currency reviewer round — High + Medium resolved in 487bd6f

The releases moved while this PR sat as a draft. Verified against source before editing:

  • gh/git tag — cycles-server-admin latest tag = v0.1.25.50 (2026-07-10, the tenant-webhook event_categories SECURITY fix).
  • cycles-governance-admin-v0.1.25.yaml info.version on cycles-protocol main = 0.1.25.39 (PR Update branding: "budget authority" → "runtime authority" across docs #127 merged: category-only subscriptions).
  • cycles-server-admin README on main declares "Aligned with Cycles Protocol v0.1.25.39" — so the compat-matrix cell tracks that.

HIGH — docs no longer point at the pre-security-fix admin release

  • changelog.md current-version table: cycles-server-admin 0.1.25.49 → 0.1.25.50.
  • changelog.md admin section: added the [0.1.25.50] entry above [.49] — SECURITY: tenant-plane event_categories validated against the tenant-accessible boundary (governance v0.1.25.38); empty-both match-ALL update door closed; upgrade past 0.1.25.49 + audit existing subscriptions; [release-notes link]; category-only-valid-on-update per governance v0.1.25.39; residual provenance issue cycles-server-admin#209. ([.49] entry was already present — no .48→.50 jump.)
  • changelog.md compatibility matrix: Cycles Admin Server 0.1.25.49 → 0.1.25.50.
  • quickstart/architecture-overview-how-cycles-fits-together.md: admin 0.1.25.49 → 0.1.25.50; "latest tagged versions as of" date 2026-07-10 → 2026-07-11.

MEDIUM — governance lineage current, "pending" dropped

  • changelog.md current-version table + compat matrix: governance spec v0.1.25.37 → v0.1.25.39.
  • protocol/webhook-scope-filter-syntax.md ~95: dropped "pending" — governance v0.1.25.39 is merged; now stated as the current governance revision.

Sweep (per-hit)

Class / hit Verdict
changelog.md:79 [0.1.25.49] admin entry Correct-as-is — historical release entry
changelog.md:160 dashboard v0.1.25.49 Correct-as-is — different component, historical
changelog.md:78 new .50 entry "upgrade past 0.1.25.49" Correct-as-is — security-upgrade guidance
webhook-event-delivery-protocol.md:157 "0.1.25.49 and earlier validated event_types only" Correct-as-is — historical version-scoping (pre-fix)
webhook-scope-filter-syntax.md:11 "admin 0.1.25.49 ships the spec-conformant matcher"; :17/:21/:69/:91/:161/:171 "admin 0.1.25.49+" Correct-as-is — accurate provenance/version-scoping for the matcher-parity fix (shipped in .49; .50 is a later security fix that doesn't change matcher behavior), not a "latest/recommended" claim
Governance .37/.38 cited as current None — the two .38 refs (new .50 entry, delivery-protocol note) are correct provenance for the category-boundary rule, not current-version claims
.39 (pending) / pending governance None remaining after the fix
cycles-server-admin 0.1.25.49 as current/latest/recommended None

This resolves the reviewer's High (docs no longer point users at the pre-security-fix admin 0.1.25.49) and Medium (governance lineage current at v0.1.25.39, "pending" dropped). PR #787 stays a draft; ready pending owner approval.

@amavashev
amavashev marked this pull request as ready for review July 11, 2026 13:24
@amavashev
amavashev merged commit 1ec75e4 into main Jul 11, 2026
5 checks passed
@amavashev
amavashev deleted the docs/tenant-closed-runtime-conformance branch July 11, 2026 13:24
@amavashev
amavashev restored the docs/tenant-closed-runtime-conformance branch July 11, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant