Add an append-only ledger of entitlement set rule changes with per-pool
effect rows, a preview-and-commit rule change flow, and an automatic
drain that settles deferred recomputations. Rules gain a tier reduction
policy, resource keys declare over-limit behavior, and the materializer
now lowers limits when a rule stops applying.
Add entitlement set rule change ledger and preview flow
Add an append-only ledger of entitlement set rule changes with a
preview-and-commit operator flow. Rule writes now go through an enclosed
`core.commit_rule_change` function that files an act row and one
obligation per carrying pool, with a drain workflow settling deferred
recomputations. The preview dry-runs the materializer with a rule
overlay and renders per-pool buckets, reduction-policy disclosures, and
provider over-limit consequences. Materializing transactions take a
shared advisory rendezvous that rule changes hold exclusively, enforced
by a possession assertion. Add History and Entitlement changes surfaces,
a rule-less warning on five product-selection surfaces, and a
`tier_reduction_policy` column that gates FedWiki parking.
Derive Stripe test/live mode from the API key prefix at boot, failing on
unrecognized prefixes, and drop the separate `stripe-mode` config key.
Refine disabled controls to render through the shared `disabledControl`
part with the not-allowed cursor, and add a lint rule refusing
hand-rolled disabled buttons.
Adjust plan cards to offer no purchase control on free rungs, fix bound
checkbox Bool handling, and rename "Public/Private" to "Listed/Unlisted"
with enhanced readiness verdicts.
- Rotate the session token at the OIDC callback and restore the full
lifetime; cap pre-auth sessions at 15 minutes and write no session
for bare anonymous requests
- Treat db-dsn as a secret: accept db-dsn-file, log only host, port,
database and user, and never echo a malformed DSN in an error
- Guard the logout callback with a state cookie so a forged visit
cannot end a live session
- Collapse FedWiki site actions on a foreign tenant's domain to the
not-found answer, as for a domain that does not exist
- Replace gorilla/csrf with net/http CrossOriginProtection
- Require valkey-password and add TLS options for session store
- End session at /logout and revoke refresh tokens
- Re-derive identity and roles from provider every five minutes
- Process each Stripe webhook event in its own Temporal workflow
- Give each outbox entry its own workflow with Temporal retries
- Guard against stale Stripe events with provider timestamps
- Derive transport security from base-url scheme
Introduce a commercial license option alongside AGPL-3.0-only, require a
CLA for contributors, and document the terms in COMMERCIAL.md and
NOTICE. Add a script to stamp SPDX headers on Go files and apply it
across the tree.
Parse every config value through a single declared type at save, boot,
and validate, and expose the settings seam from the terminal so
operators can list, set, clear, and validate overrides without SQL.
Six review rounds on the September walk's 40 findings, executed as the
acceptance-fixes change (design D1 to D31) and archived as
openspec/changes/archive/2026-09-03-acceptance-fixes/ with its 20
deltas synced into openspec/specs.
Shell and conventions: the location trail on every page rooted at the
surface; click-opened help popovers; buttons by role with the pressed
rule retired and tertiary openers with a rotating plus; the code chip
and white card headers; Remove versus Delete on row actions.
Operator surface: three overview tiles, one Integrations card, the
getting-started banner as the first instance setting
(core.instance_settings, migration 00015); record creation on its own
page for products, entitlement sets, and plan ladders; Visibility as
one Public checkbox; the Stripe provider page; the plan ladder
validation page retired and ranks ascending; org-types default change
with dispositions, Discard, and the settle-cloning fix; the rebuilt
composite billing card; hints instead of placeholders; the person page's
identity-provider glyph on its field labels; non-UUID ids answer 404.
Found by review and fixed: a session now ends when its person no longer
exists (the foreign-key failure on a ladder reorder after a snapshot
rebuild); the CSRF cookie lives as long as the session and its toast
says the page is out of date; htmx's settle delay is 0 app-wide.
Verification: unit suites, browser walkthroughs, the screens baseline
(31 screens, accepted), and Lighthouse at 100 on both widths, recorded
in docs/operator-a11y-baseline.md. Three stale issue entries archived.
Rebuild every operator, member, and integration template on the shared
page-anatomy parts, empty the lint allowlist, and move the confirm modal
into both shells. Replace back links with location trails and make row
identifiers the primary link. Make screen captures deterministic by
resetting the app database from a pinned demo snapshot. Send the logout
id_token_hint only while valid.
Replace the entity slugs on organizations, workspaces, resource pools,
and
plan ladders with nullable `key` columns and add keys to products,
prices,
and entitlement sets. Rename `providers.slug` to `provider` and add
partial
unique indexes for system and org role names.
Assign invoice numbers per billing account from a gapless transactional
counter; Stripe's number moves to the invoice mapping as an external
reference.
Seeds, fixtures, and the operator lookup address rows by key, and the
returning-login resync no longer blanks a display name when the IdP
sends
no `name` claim.
- Add deployment-name branding to titles, mastheads, and OG tags
- Share one grant delivery-state query with lineage across grants
surfaces
- Show pool status/usage, org owners, and config readiness
- Make billing views projection-aware with recency and sync vocabulary
- Guard FedWiki creation without domains and render route-aware 404s
Enforce 10j's verified gaps (schema-hardening change):
- Migration 00010: partial unique indexes for one default pool and one
primary assignment per workspace, plus CHECKs pinning
pool/provider/subscription vocabularies and provider lifecycle
timestamps.
- Workspace creation shares a transactional provisioning function;
extension validates its target pool; last-tier deletion of a defaulted
ladder is guarded; signup completes plan-less on a broken ladder.
- Boot asserts integration slug parity and validates declared config
enums; Stripe invoice amounts are range-checked; domain cancellation
runs a final evidence probe; rule authoring is additive-only.
Add lifecycle_status = 'published' to the public-catalog queries
(plans and add-ons listings) and reject checkout before any Stripe
call unless the product behind the price clears the shared member
gate (published + active + public). The currently-enrolled ladder
rung stays renderable even if its product is later drafted or
retired, fetched directly so members keep seeing what they are on.
Introduce a single evaluateMemberGate definition shared by the
catalog paths and the operator readiness panel so the surfaces
cannot disagree about what is publishable for members.
Configure the personal org-type default ladder and floor vacant pools so
the seeded dataset survives grant revocation. Let the modal mechanics
test
fall back to plan-ladder action triggers in full-suite order.
Register bool and duration ConfigSpec keys from the Default's type, move
fedwiki's four sync knobs and discourse's two into their integrations'
ConfigSpecs, and replace core's read of fedwiki-custom-domain-target
with a core domains-connect-target key resolved once and threaded
through server and worker config.
Generate init's optional-integration scaffold sections from each
registered ConfigSpec instead of the hand-maintained list, and reword
the Temporal boot warning generically.
Archives the integration-config-parity change; status bookkeeping and
the verify-skill doc follow with the test-stack commit.
Sweep stranded pending claims at boot and on a Temporal schedule while
preserving evidence-based abandonment semantics.
Apply occupancy and name-policy checks to carves by operator-root owners
without affecting direct operator placements.
Make claim windows and workspace caps configurable, and enforce
initiation
and abandonment budgets without penalizing DNS evidence or system
failures.
Add operator visibility into live claims and default verification to 24
hours.
Domain names become an allocatable resource with one authority. A new
core module (schema `domains`, own migration stream between core and the
integrations) owns claims — a DNS node plus its whole subtree, mutually
disjoint: operator shared-domain roots, member claims carved from them,
and bring-your-own names proven by TXT verification — and placements,
which bind a name inside a claim to a provider slug and resource ref.
Verification moves to the claim and decouples from creation. A member
proves control of a domain once; afterwards every name inside it places
instantly, wildcard-CNAME friendly, with no further DNS work. The claim
workflow activates the claim and stops — it no longer creates a site —
so the sites list offers a one-click create once a domain verifies.
/domains/ask answers from placements and is registered by core rather
than the FedWiki adapter; its HTTP contract is unchanged. A configured
`domains-ask-fallback-url` forwards names the registry does not know to
a legacy answerer, the strangler seam wiki.cafe's migration needs; a
name the registry knows but has archived is refused locally.
FedWiki's create saga reserves the name before the farm call, carrying a
workflow-minted site id so retries are idempotent, and compensates on
failure. Sync places only names it owns, never stealing a member's;
lifecycle transitions and the retention purge maintain servability. An
unconditional boot pass seeds operator roots, releases orphaned
placements, and adopts pre-existing sites — grandfathering member-owned
external domains shortest-name-first, and skipping name policy, so a
live single-letter site cannot lose its certificate.
Members manage domains at /domains: claims with verification status, DNS
records including an optional wildcard row, check-now, cancel, release.
Name policy (reserved, blocked, premium, plus a single-letter guard) is
operator data; refusals collapse to a plain "unavailable" so the console
never becomes an oracle for who holds what.
BREAKING (pre-release): `fedwiki.custom_domain_verifications` and
`sites.is_custom_domain` are dropped, the flag now derived from the
placement's claim kind; resource key `fedwiki_custom_domains` migrates
to the platform-owned `external_domain_claims`; running
verify-custom-domain workflows must be terminated before deploy.
List every provider kind with direct settings and admin links, move
FedWiki
under the integrations route, and add in-shell operator 404s.
Report sync health from Temporal schedule executions and clear one-shot
settings feedback parameters after display.
Persist non-secret ConfigSpec overrides in core and apply them at boot
ahead of environment values and defaults. Validate typed and enum
values,
show pending restart state, and remove Stripe's unused provider config
table.
Render generic HTMX card shells from integration declarations. Move the
FedWiki UI into its partial, add Discourse forum status, and label
member
entitlements with provider attribution.
Add an explicit registry with capability hooks for migrations, routes,
workflows, config, and UI assets. Move FedWiki fully and Stripe's
separable
store, workflow, and webhook pieces under internal/integrations.
Drive startup wiring from declarations, including config validation,
secret
file pairs, CSRF exemptions, UI composition, and workflow startup. Move
integration DB roles and grants into their owning migration streams, and
route outbox writes through a shared enqueue helper.
Fix HTMX expired-session handling, CSP-blocked form behaviors, reorder
recovery, billing currency display, plan/checkout guards, FedWiki quota
edge cases, and operator/member empty/error states.
Add entitlement uniqueness migrations, canonical migration source
wiring,
and regression coverage for the remediated flows. Update status docs
with
the audit triage and model inventory.
Fail fast with aggregated config errors after resolving secret files,
before services initialize. Add Valkey session config, remove the unused
session-secret, and fix the production CSP env key.
Retry initial Temporal dials with bounded backoff and gate the test
Temporal service on healthy DB and Keycloak dependencies.
Add a configurable cooldown window and route restores, reactivations,
and swaps through the same rotation check to close the archive/restore
bypass while preserving headroom and no-reserve activations.
Create the singleton System organization/workspace by natural key and
use it
as the FedWiki sync default workspace. Remove the configured workspace
UUID
and holding-workspace seed, and cover creation/adoption with tests.
Register FedWiki and Stripe providers at boot, persist provider
operations, and stamp owned resource keys.
Rename the FedWiki entitlement key from `sites` to `fedwiki_sites`
and drive the operator integration nav from provisioning providers.
Create Temporal billing sweep schedule, workflow and activities and
register them in the worker/start initialization. Add an HTMX preview
route and banner for plan switches (switch button now GETs a preview;
Confirm posts the switch). Extend the Stripe test mock to support
invoice previews and add integration tests for PreviewSwitch behavior.
Replace the order-dependent, payload-trusting webhook fulfillment with a single
idempotent ReconcileSubscription (internal/fulfillment) that refetches
authoritative subscription state from the Stripe API and converges core records
and pool entitlements via the plan-transitions primitive. checkout.session.completed
and customer.subscription.* become thin triggers; the post-checkout return path
reconciles eagerly. This fixes paid upgrades provisioning zero entitlement, caused
by reading the empty line_items the webhook payload never carries.
- internal/fulfillment: ReconcileSubscription + ReconcileLatestSubscriptionForCustomer,
advisory-locked, convergent/idempotent, with unit + DB-gated integration tests
- internal/stripetest: in-process stripe.Backend mock for offline reconcile tests
- thin webhook handlers; eager reconcile on /?checkout=success (best-effort)
- stripe.Key set process-wide before the Temporal worker starts (cmd/start.go)
- OpenSpec change stripe-fulfillment-reconcile; archive completed member-upgrade-flow
now that its paid-upgrade demonstration (§5.3) is satisfied
Expose /webhooks/stripe as a public path (signature-verified)
Verify webhook signatures with ConstructEventWithOptions and
IgnoreAPIVersionMismatch=true, and log API version mismatches.
Start two Temporal workflows: stripe-webhook-processor and
stripe-outbox-poller; workflow start failures are non-fatal.