ListGrantsWithDelivery now takes optional organization, search, product,
state and paging arguments and serves the grants page and the
organization page's ledger. The grouped SELECT's columns and grouping
moved unchanged into a CTE named listed, whose WHERE now holds the
organization filter and the search filter (with the organizations join
the search needs); the outer statement filters on the state computed
once, counts with count(*) OVER() after that filter, orders by
created_at DESC and pages (a NULL limit or offset reads every row).
ListGrantsWithDeliveryPage, toDeliveryRows and loadGrantsListData's
unused success and error-message arguments are deleted. The generated
row is field for field the old page row. Product names are still
matched in Go, so % and _ in a search keep matching themselves there.
The two pages keep the pool difference: with an organization the waits
are evaluated on its active default pool, without one on every pool.
How the commit was shown to change no output:
- The row golden, run first on PostgreSQL against the merged query,
passed with every file under testdata/grants_listing/ and
testdata/org_enrollment/ unchanged. LIMIT NULL OFFSET NULL returns all
rows, and EXPLAIN ANALYZE of the organization-only call and of the
search, state and page call shows no CTE scan on listed (it is inlined).
- internal/server (1398 results) and internal/entitlements (180) give
the same test and subtest names and results at the parent and at this
commit; no test was added, renamed or deleted.
- 13 breaks, each caught by the old and the new test, plus 3 breaks
caught on the old side only: the adapter's copies of the waits, the
default-duplicate and the restored-after columns, which are deleted
with the adapter. No break was dropped because the old test missed it.
- Test calls were re-pointed to the merged query with their assertions
unchanged (every row of the assertion map is same). The comments of the
query, the grants template, docs/models/entitlements.md and
docs/operator-ux-conventions.md now name the one query.
Unprobed: the order of grants that share created_at (a tier removal
writes one default grant per vacated pool in one transaction). The
merged query's plan may order such a tie differently from either old
query, no fixture holds a tie, and the order is accepted until the
tie-break by grant id, as for positions. The zero UUID pool of an
organization with no active default pool: no fixture organization lacks
one. total_count on the organization call, which no caller reads. The
adapter's other 22 copied columns on the old side: the goldens read each,
three are probed, and the adapter is gone so no copy remains to break.
The storefront and consumers rows name the files that now hold the plan,
add-on and entitlement code; the integration guide drops member_products.go
from the files that stay in core and says the files of the split import no
Stripe package; the resource-pools
dimension states what the schema admits and what the console refuses; the plan
ladders card gains a Member position row; the kit table says the fixture
builder covers plan fixtures.
No output changes: only Markdown under docs/ is touched, no Go, SQL or
template file. git grep for member_products.go in docs and scripts is empty,
make lint and the notebook-citation check pass, and the lint snapshot differs
from the previous one by removed 0, added 0.
records, outbox, lifecycle requests, relay, push, read-back, and FedWiki
lifecycle workflows
- Add core.provider_connections table and unique provider connection per
provider
- Add core.target_mappings table linking connections to resource keys
and targets
- Add core.desired_state_records table storing desired entitlements per
connection and recipient with generation and content hash
- Add core.observed_projections table for application-reported state per
connection and recipient
- Add core.lifecycle_requests table recording lifecycle requests by
instance and generation
- Add core.person_cut_offs table and setter function to record persons
cut off everywhere
- Add core.desired_state_outbox table with triggers on entitlement,
membership, organization, connection, mapping, and cut-off tables to
write change notices
- Enforce constraints on recipients, generations, and statuses across
tables
- Implement desired_state_content SQL function computing record content
per recipient rules
- Implement relay workflow to claim outbox rows, expand to records,
recompute records, and dispatch pushes
- Implement push workflow to push records, handle transport answers, and
record push results
- Implement read-back workflow to page through application state, update
projections, and raise records as needed
- Add lifecycle request writers with idempotent recording, finishing,
and acknowledgment
- Add FedWiki integration lifecycle workflows for create, set status,
swap, delete, and reconcile
- Add FedWiki web handlers and forms for lifecycle requests, including
create form with instance and generation
- Add tests for schema, triggers, relay, push, read-back, lifecycle
requests, FedWiki workflows, and operator pages
- Add documentation updates for provider integration, entitlements,
identity, organizations, lifecycle, and FedWiki integration
- Add benchmarks for delivery and push workflows
- Ensure schema-per-provider for in-tree integrations; connector state
lives in generic core tables keyed by connection
- Secure functions and triggers with SECURITY DEFINER and restrict
core_writer role privileges accordingly
- Handle Temporal client integration for workflow execution and
scheduling
- Provide a recording transport for integration tests
- Close Issues 32 and 33 by implementing provider connections and
desired-state records with lifecycle requests and relay mechanisms
An operator can mark a grant to resume. When a plan replaces a marked
grant and that plan later ends (the subscription ends, or a granted plan
is revoked or expires), the grant delivers again from its own row before
the org-type floor runs (core migration 00023, core.grant_waits).
- Grants: a resumes_after_supersession column, off by default and never
on a default grant; delivery states gain On hold, with a badge hover
that names what the grant waits on.
- Operator: one Manage action per grant opens a card under its row
(Note, Valid until, Resumes; Save, Cancel, Revoke). A changed Valid
until extends the grant; otherwise the note and mark change in place.
An extension now keeps its predecessor's valid_from.
- Member: a held rung reads Granted and is reached by cancelling; the
cancel confirmation names each ladder's outcome; Cancel plan sits
beside Keep plan while a downgrade is scheduled.
- Smaller fixes from the screens reviews: 24-hour times everywhere,
search placeholders that name what they match, a checkbox label above
its box, and the org-types walkthrough restoring the default it
changes.
Stripe delivers webhook events in the endpoint's API version. Under a
version later than the 2025-02-24.acacia stripe-go v81 pins, the
payload-shaped invoice handlers projected every invoice with no
subscription link and no lines, and every payment with no payment
intent or method.
- Invoice events read only the invoice id and run ReconcileInvoice,
which reads the invoice, its lines and its payment intent's charges
back at the pinned version and converges the core rows under a
per-invoice lock, in any event order. A payment row is one attempt,
keyed by its Stripe charge (Stripe store migration 00005).
- Two clocks: created_at is when the console wrote a row; invoices gain
issued_at and payments paid_at (core migration 00022, backfilled),
and every invoice and payment list, the activity feed and the
composite's latest invoice read those.
- An hourly and at-boot invoice sweep reads back invoices never read,
open invoices once a day, and invoices of dead-lettered events; the
provider page states when it last finished. The first run is the
repair of invoices projected so far.
- The API version skew is logged once per version, and
payment_method.attached links payments recorded before it.
- A restricted key now needs Invoices read and Charges read.
- scripts/db-reader-role.sql maintains console_reader, a login role
that reads the console's tables without personal data, documented in
docs/database-management.md and checked against a golden column list
by TestConsoleReaderColumns. The demo snapshot keeps owners and
privileges so the role's checks hold on it.
- The demo seed and test fixtures carry issue and paid times.
status/design-feedback.md gains four upstream notes, one written when
#60 was closed.
OpenSpec change stripe-invoice-reconcile is archived and its deltas
synced into openspec/specs/. Follow-ups are #150, #151 and #152.
A paid-to-paid switch sent create_prorations, which charged nothing
now: the difference waited on the next renewal invoice, an immediate
cancellation lost it, and the confirmation's "charged now" was false.
A downgrade took effect at once, so moving to a cheaper paid tier cost
more access than cancelling (OpenSpec change plan-switch-timing,
unarchived: it awaits the maintainer's review, task 11.4).
- An upgrade is invoiced at once (always_invoice) and applies once paid
(pending_if_incomplete). A declined card voids the invoice and leaves
the plan as it was; a card that needs the bank's approval links to
Stripe's invoice page. The preview pins its proration moment and the
switch charges at it, so the amount charged is the amount shown.
- A downgrade takes effect at renewal through a subscription schedule
and is recorded in the scheduled-change ledger.
- Every paid plan's card offers Cancel plan; a scheduled downgrade or
cancellation shows on the card with Keep, which drops it at no
charge. A paid rank-0 tier is a downgrade target, and a rung a grant
holds moves up through Checkout with no move down.
- Reconcile keeps the ledger true to Stripe, and marks ended the
subscription item a schedule's phase change replaced (core migration
00021, subscription_items.ended_at). The sweeper reconciles a
period-end move instead of firing it.
- The form library gains a commit that is a link out of the console.
The verify skill and the first-contact process now say the gap in
scripted screens is tracked as #118 and that a new issue waits for the
maintainer's approval.
A survey of the twenty notebooks on disk found general facts that no
tracked page stated; the maintainer struck the weak rows and the rest
are written into their owning pages in each page's own words. The
design-system page gains the hidden-navigation figures that replaced a
dangling survey pointer, the reason the anatomy is parts and lint, the
record-table grouping and single-tint rules, and when a surface earns
row editing. The first-contact process gains a code-verification phase,
the transcript lane for models without vision, the three kinds of
honesty-audit drift and the click-path guardrail. The operator
conventions gain guards-before-writes, error state over empty state,
copy that states the real effect, toasts that report counts and the
PostForm rule. The Stripe, FedWiki, identifiers, IA, testing, model and
environment pages each gain their facts, the integration guide stops
telling authors to hand-write a page title, issues.md logs the
placeholder security contact, milestones.md records the Codeberg terms
clause and CONTRIBUTING.md notes that the module path is the forge URL.
One claimed fact was checked against the code and not written: the
mass-mutation preview does not run the commit loop in a rolled-back
transaction.
The exploration directory held 245 tracked files, and one week's
notebook had grown to 28M of captures, per-run CSS copies and model
logs, so every change ended with an argument about what to prune. It is
now a local lab notebook: git ignores it, its tracked files are
untracked, and no tracked file cites a path under it, because another
clone does not have it.
Every live citation is rewritten so the fact stands in the sentence:
the docs pages, AGENTS.md, four main specs, one Go comment, issues.md
and milestones.md. The operator IA page gains the landed IA revisions it
used to point at, and the UX rubric moves to docs/first-contact-rubric.md
because the process requires it. Archived changes, status/archive,
status/log and design/ keep their old paths as frozen history; the
design/ ones are logged as an upstream issue. MAINTAINING.md and the
status README describe the notebook and the harvest that replaces
pruning.
The Rules section is one record table grouped by kind, Limit then
Boolean, on fixed columns, edited in place: Edit opens a row's controls
in their columns, Add rule opens a dense row above the table, and every
change is staged into a tray that lists the deltas with Undo and applies
them as one rule-change act. The reduction policy is a column of the
rule beside its limit. History shows counts only. Group rows are a quiet
heading rather than a divider, the maintainer's pick from four rounds of
outside-model ideation.
Dense rows align to the top and render each error under its control in
every form family (design D16), replacing the below-row error block; the
forms library gains the batch form (rows plus one tray) and the RowField
dense and label-hidden options. Migration 00019 records the governing
reduction policy on effect rows.
Archive staged-rule-changes with its spec updates (entitlement-set-
management, entitlement-set-history, entitlements, form-library,
form-conventions, ui-quality-gate). Screens accepted 2026-09-19.
- 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
Move settled investigation records into
`status/explorations/<topic>-<date>/` with README indexes,
promote durable conventions to `docs/`, and add
`docs/identifiers.md` plus upstream Doc 46 person-name
provenance documents. Update cross-references and archive
resolved issues.
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.
Write the eight domain-model cards under `docs/models/`, mark M10 10i
Done, and update the design docs to v16. Also records Doc 44, which
ratifies Decisions 129-133 as amended, and the Doc-39 conformance
check that satisfied its gate.
Introduce `docs/models/` with the catalog index and its first card
(product-catalog), recording invariants, dimensions, and drift traps
per the Decision 141 documentation division. Add the model-cards
openspec change (proposal, design, spec, tasks) and sync the design
docs to v15: product kind taxonomy dissolved (Decisions 134–139),
Decisions 140–141 ratified, and product/pricing helpers updated. The
catalog is the single descriptive home for as-built models.