Repository navigation
v5.3.0
5.3 is a minor release. Two new modules ship (Tagging and Services), and Accounting posts and reverses journal entries from workflows. A content type can take lines and be owned by an account, a tenant has locale settings, and a module can add a sign-in method. Feature flags gain a visibility level. The sign-in, password reset, payment webhook, semantic search and install report routes check more of what they are given. The admin API contract moves from 11 to 12. The delivery contract stays at 9. BarakoCMS.AI moves to HTTP contract 3 and BarakoCMS.Pwa to 1.
Upgrade
From 5.2.x. A 5.1 install upgrades to 5.2 first.
- Take a backup and check it restores (
docs/backup-and-restore.md). - Stop the 5.2 API. Every file below is applied with the API stopped.
- Run
db-migratewith the 5.3.0 image, thendb-assert.FROM_VERSION=5.2.0 scripts/upgrade-check.sh --listprints each file as apsqlline in the order CI applies them. Undermigrations/5.3.0/:- Core, first:
945-account-content-types.sql(the account content types table) and98-locale-settings.sql(the locale settings table). Both are new, empty tables. - Module, after the core files; a host without that module skips its file:
252-tagging.sql(BarakoCMS.Tagging),833-services.sql(BarakoCMS.Services),accounting-mappings.sqlandaccounting-journal-lifecycle.sql(BarakoCMS.Accounting). The journal lifecycle file changes data only: it gives each existing journal entry the state Posted, or Voided where its status says so, and can be run twice.
- Core, first:
- Check the defaults. A tenant's sign-in methods are unchanged, and a module's method stays off until a tenant names it in
signIn.methods. A feature flag keeps its audience: public stays Public and private becomes SignedIn. The compose files now refuse the default JWT key outside Development, so setJWT_SECRETfirst if you run them in another environment. - Start 5.3.0.
Breaking HTTP changes. ApiContract.Version moves from 11 to 12. ApiContract.DeliveryVersion stays at 9. Released barakoBrew reads admin contracts up to 11: upgrade it to a release that reads 12 together with the API.
The Suite image no longer serves the Accounting routes (/api/accounting/accounts, /api/accounting/accounts/{code}/ledger, /api/accounting/balances, /api/accounting/journal-entries and the new 5.3 accounting routes), since basic Accounting left the image. Add BarakoCMS.Accounting to your own host to keep them.
POST /api/users/{userId}/password (admin password reset) answers differently:
- 403 when the caller holds
manage_usersonly through a tenant membership and not in a global role. - 403 when the user holds a platform role and the caller is not a SuperAdmin.
- 400 when the user is the caller's own account.
- 404 when the user is outside the caller's tenant, not only when the user does not exist.
Module contracts: BarakoCMS.AI HttpContractVersion 2 to 3 (a semantic search query over 500 characters answers 400), BarakoCMS.Pwa 0 to 1 (the install report refuses a bad field). New modules BarakoCMS.Tagging and BarakoCMS.Services start at 1. A payment event for an attempt that is no longer pending answers 200 or 409 as described under Changed.
Package changes. BarakoCMS.Tagging and BarakoCMS.Services are new packages. Modules with changes: Accounting 4.6.0, FeatureFlags 4.6.0, AI 4.6.1, Pwa 4.5.1, Diagnostics 4.5.1, Import 4.7.1, Portability 4.7.1, Booking 5.2.1, Qr 5.2.1 (its package icon no longer carries EXIF metadata) and Payments 0.1.1. Nothing is removed from the package surface.
Rolling back to 5.2. Stop 5.3, run the rollback lines --list prints, newest first, then remove each rolled-back file's ledger row with db-migrate --forget <key> and start 5.2.x. rollback-252-tagging.sql, rollback-833-services.sql, rollback-945-account-content-types.sql, rollback-98-locale-settings.sql and rollback-accounting-mappings.sql sit beside their files. Rolling back drops the tables those files created, so their rows are lost: tags, services, account content types, locale settings and accounting mappings. The journal lifecycle file has no rollback, since it only adds a state to existing entries.
Added
- A Tagging module. Vocabularies, optionally nested terms, and term assignments on any record by content type and id, with a query by term across record types (#252).
- The content event stream reaches subscribers on every instance. A change committed on one
instance is sent to the others over PostgresLISTEN/NOTIFY, as the entry id and event name
only. Each instance reads the entry and its type again and projects it through the same masking
as the REST reads, in the tenant it was written in, so a notice cannot loosen masking or carry a
stale level. No setting, no new service, and no route, field or status code changes. Delivery
stays best effort, with no ordering across instances. - A content type can take lines, and a header is read with them.
PUT /api/content-types/{name}/linesdeclares a child type and the single reference field on it that points back at the header (nullclears it), and optionallytotalFieldandlineAmountField, which hold the header's number field equal to the sum of the lines' number field.POST /api/contentson a type that declares lines acceptslines, up to 200 entries of the child type, and writes the header and every line in one transaction: a refused line (reported asLine N: ...) or a total that does not match stores nothing (a line amount that is missing or not a number is refused by name, and a header with a total and no lines must total 0), anddryRunstores nothing. Each line takes the header's status and sensitivity, and the server sets its parent field. The response carrieslineIds. The total is checked after the hooks and numbering have run, on the values that will be stored, and again onPUT /api/contents/{id}for the header and for any line of it, and onPOST /api/contentsof a single line that points at an existing header (a line write that breaks its header's total is refused, naming the header). Only a parent reference in the canonical lowercase or uppercaseDform (with hyphens, no braces) counts as pointing at a header. It is checked on create and update only, not on rollback, bulk import or workflow actions. The total field and the line type's parent and amount fields cannot be renamed or removed while the lines name them.GET /api/contents/{id}/linesreturns the header and the lines the caller may read in one query, scoped to the caller's tenant, (the list stops at 200 andtruncatedsays so).contentTypesresponses carrylines. A type that declares none is created exactly as before. Erasing a line (DELETE /api/contents/{id}/erase) is not checked against its header's total, because an erasure request must not be blocked by it, and erasing a header does not erase its lines. Cascade on delete is not part of this yet. The total is enforced only by the content endpoints named here, so a write that does not go through them (a module storing documents directly, for one) is not held to it.- An estate checklist.docs/estate-checklist.mdanswers the five questions an architect asks (changes, traces, scoped credentials, consumer limits, contract version), each yes tied to a test or code path, and names what is not there yet. Docs only. - A registry of outside services. New package
BarakoCMS.Services:POST /api/servicesregisters a service (name, message types, delivery URL or topic, optional health URL),GET /api/servicesandGET /api/services/{name}read it with a health of Healthy, Late or Down, andPOST /api/services/{name}/heartbeatrecords that it is alive (a service with a registered machine key hears only from that API key, which needs the newservices:heartbeatscope and reaches no other route; otherwise it needs the capability through a global role, never a tenant membership; an unknown name and a service that is not yours both answer 404; a machine key must exist and not be revoked at registration). Gated by the newmanage_servicesandsend_service_heartbeatcapabilities, both given to Admin. Message state, replay and pause come later. Adds the Services tag to the HTTP surface; no existing route changes. - A content type can be owned by an account.
POST /api/accounts/{slug}/content-typesdefines a type that every tenant in the account uses: it is listed inGET /api/content-typeswithownerAccountId, and entries written in those tenants are validated against it while staying tenant scoped. A field added withPOST /api/accounts/{slug}/content-types/{name}/fieldsapplies to all of them. A tenant admin cannot change it, an account manager can, and a tenant outside the account does not see it. A tenant-local type of the same name is refused on create, in a blueprint apply and in an import, and the account's definition wins wherever it is read. Checks across the account's tenants run per tenant, so they hold under Tenancy:DatabaseEnforcement. A tenant holding entries of its account's types cannot move out of it. NewAccountContentTypedocument. First slice of #945: workflows, lifecycles, roles and the field migration of #934 are not in it. - A tenant has locale settings.
GETandPUT /api/settings/locales(admin) hold the tenant's default locale, its language codes and ordered fallback chains.GET /api/metareturns the languages as an optionallocalesfield. Nothing picks a translation from them yet. GET /api/accounting/cash-flow. Returns the receipts, disbursements, opening and closing balance of one month over up to 20 cash accounts. A voided entry stays in, with its reversal. Needsview_ledger. BarakoCMS.Accounting is 4.6.0.- Accounting mappings are a config section.
accountingMappingsholds named rules that say which accounts a business event debits and credits, read and written with the newmanage_accounting_mappingscapability (granted to Accountant and Admin). Applymigrations/5.3.0/accounting-mappings.sqlbefore the upgrade sodb-assertfinds the table. Accounting.PostJournalworkflow action. Posts the journal entry a named mapping describes for the entry that triggered the workflow. The same subject andActionKeypost once, however often the action is retried, and the entry records the run that made it.GET /api/accounting/reconcile. Lists the entries of a content type, made within a range of at most 366 days, whose workflow posting is missing or differs from theirTotal. A posting that was reversed counts as settled. Needsview_ledger.Accounting.Reverseworkflow action. Reverses the entryAccounting.PostJournalposted for a subject and key: posts the lines with debits and credits swapped, then voids the original with the reason given. A retry finishes the job without posting a second reversal. The voided original and its reversal both stay in the books and cancel by date.- Feature flags gain a visibility level. A flag is Public, SignedIn or Admin.
isPublicis still read and written: true means Public, and existing flags keep their audience (public stays Public, private becomes SignedIn). A flag saved with neither field is Admin only, seen by callers who can manage feature flags. The admin list and save response returnvisibility. - Journal entries have a Posted and Voided lifecycle. A new entry starts Posted, the
Voidtransition (with aVoidReason) makes it Voided, and a Voided entry stays in balances, ledgers and cash flow, where its reversal nets it to zero by date. Applymigrations/5.3.0/accounting-journal-lifecycle.sqlto give existing entries and the storedjournalEntrytype their state. - A module can add a tenant sign-in method that is off until named. A module registers an
ISignInMethod, and a tenant can then name it insignIn.methods. An empty list still allows only
the three built-in methods, so a module method stays off for every tenant that does not name it.
TenantSignIn.AllowsExplicitlyAsyncis how a module asks.
Changed
- Staging in place is recorded as a decision. D38 in
DECISIONS.mdsays a client reviews on the
live tenant using drafts, holding mode, share links, scheduled publish and configuration applied through
/api/config/apply, not on a staging copy.docs/delivering-a-client-project.mdhas the review path. No code
or contract change. - The
barako-cmsimage no longer includes the basic Accounting module. It is still published on NuGet; addBarakoCMS.Accountingto your own host to keep using it. Existing accounting data stays in the database. - Admin password reset follows the same rules as admin MFA reset. It needs manage_users in a global role, refuses your own account, answers 404 for a user outside the caller's tenant, needs a SuperAdmin to reset a platform-role holder, and writes an
auth.password.resetaudit entry. - Sign-in mail is held one message per address. A newer code or link replaces the queued one, four senders run at once, a send gives up after 10 seconds, and mail dropped on a full queue is counted in
barakocms_auth_mail_dropped_total. - Booking checks read only the bookings near the requested time. The overlap check and the availability route filter by date in the query instead of loading every booking of the resource. Results are unchanged.
- Client errors are stored under the tenant the request resolved to. A tenant named in the report body is ignored.
- Import counts the bytes an xlsx expands to. The expanded size limit now applies to the bytes actually read from the archive, not only the sizes it declares.
- The API rejects the compose default JWT key outside Development. Set
JWT_SECRETto your own key when you run the compose files withASPNETCORE_ENVIRONMENTset to anything but Development. Local runs in Development are unchanged. - A payment webhook only settles a pending attempt, and checks the figures when the provider sends them. A later event for an attempt already settled answers 200 and changes nothing, except a paid event for an attempt already failed, which answers 409 and logs a warning. A paid event whose amount or currency differs from the attempt answers 409.
PaymentEventgained optionalAmountandCurrency. - The Pwa and AI modules move their HTTP contract numbers.
BarakoCMS.Pwagoes from 0 (unstated) to 1 andBarakoCMS.AIfrom 2 to 3, since the install report and semantic search now refuse input they used to accept. - The install report takes its tenant from the resolved tenant and checks its fields. DeviceId and Platform have length caps, DisplayMode must be browser, standalone, fullscreen or minimal-ui, a report no longer rebinds an install to a different user, and a report from another tenant does not touch the install.
- Semantic search uses the delivery rate limit and caps the query at 500 characters. A longer
qanswers 400. - Sign-in code attempts are counted before the code is checked. Requests that arrive together can no longer each get a guess past the five-attempt cap, because each is counted by an atomic increment before its guess is checked. IPv6 clients now share one rate limit bucket per /64.
- Sign-in code and magic link requests send their email in the background. The request answers the same way, and in about the same time, whether or not the address has an account.
IOtpServicegainsQueueCodeAsyncwith a default that sends inline. - Tagging caps terms per record and cleans up on erase. A record carries at most 100 terms (400 over that). Erasing a content entry removes its term assignments. Record ids and content types are stored in one normal form.
- Tag assignment lists respect record read permission. The records on a term and the terms on a record are shown only for records the caller can read, and a page and its total count only those.