Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
420fb8c
wip(plugin-webhooks): declared webhooks read from the registry; seede…
claude Oct 19, 2026
e44c642
test(plugin-webhooks): pin the registry read, the ledger switch and t…
claude Oct 19, 2026
8189a84
wip: liveness ledger re-anchored to the registry read; dogfood proof …
claude Oct 19, 2026
e412cb8
docs+ledger: the registry read named across spec comments, liveness, …
claude Oct 19, 2026
bdad70b
spec(liveness): state counts regenerated; isActive named at the dispa…
claude Oct 19, 2026
723af97
fix(plugin-webhooks): a declared webhook is deployment-wide when the …
claude Oct 19, 2026
e0a6dc9
fix(gates): tenant-audit census regenerated for the seeder's two reti…
claude Oct 19, 2026
6b3042a
test(plugin-webhooks): the fake engine refuses a WHERE combinator it …
claude Oct 19, 2026
820121c
fix(lint,showcase): the dead label/description verdict is carried to …
claude Oct 19, 2026
b378ff5
Merge remote-tracking branch 'origin/main' into claude/issue-24529-8b…
claude Oct 19, 2026
c3b1452
test(dogfood): the R2 harness pins one delivery per task change from …
claude Oct 19, 2026
9bce0aa
test(dogfood): the harness case switches the declaration on, so it ca…
claude Oct 19, 2026
7e580dc
Merge remote-tracking branch 'origin/main' into claude/issue-24529-8b…
claude Oct 19, 2026
fa6a837
Merge remote-tracking branch 'origin/main' into claude/issue-24529-8b…
claude Oct 19, 2026
b4ae8e6
Merge remote-tracking branch 'origin/main' into claude/issue-24529-8b…
claude Oct 19, 2026
206d2bc
Merge remote-tracking branch 'origin/claude/issue-24529-8b0g-webhooks…
claude Oct 19, 2026
f89c0f2
wip: birth marker, shapeGeneration, boot guard rule
claude Oct 19, 2026
6e14afa
wip: ceremony steps 5a-5d, inventory, flips, differ ownership
claude Oct 19, 2026
50a2d34
wip: open the ceremony command, ports for webhooks and NOT NULL
claude Oct 19, 2026
9ea4312
wip: existing ceremony tests follow the new step set; NOT NULL routed…
claude Oct 19, 2026
719ab13
wip: the guard's pins follow the birth marker and shapeGeneration
claude Oct 19, 2026
036a2a1
wip: the birth half in every CLI boot, so a CLI-created fresh store b…
claude Oct 19, 2026
0128f07
test(cli): pin the generation-1 ceremony steps over an 18.0-shaped da…
claude Oct 19, 2026
8e6a33a
test: the tenant column's NOT NULL is the ceremony's, not drift; the …
claude Oct 19, 2026
32d1e22
test(dogfood): R2 and R3 restated — the 18.0 and v17 databases take t…
claude Oct 19, 2026
13446cb
fix(cli): a rotation shard's NOT NULL is reported unsupported, never …
claude Oct 19, 2026
06e0556
docs,qa: the open ceremony and the birth marker in the CLI docs and t…
claude Oct 19, 2026
e0c1d2b
chore(spec): regenerate the api-surface, export-origin, declaration-m…
claude Oct 19, 2026
a0fa816
test(platform-objects,service-settings): sys_secret is deployment-lev…
claude Oct 19, 2026
e7a788d
chore: regenerate the platform-object tenancy census (sys_secret and …
claude Oct 19, 2026
f1264d7
chore: the gates the sweep named — the guard double's findOne predica…
claude Oct 19, 2026
a3064ef
test(cli): fixtures standing in for an earlier release's database car…
claude Oct 19, 2026
d6195b9
Merge origin/main into claude/issue-24581-ceremony-open
claude Oct 19, 2026
0232108
docs(qa): the ceremony item's revision 3 cites its PR
claude Oct 19, 2026
dceaa56
fix(driver-turso): the remote face counts what its schema doors creat…
claude Oct 19, 2026
33b7943
test(runtime): the remote-face guard pin reads driver-turso's source
claude Oct 19, 2026
d28f4cf
chore(changeset): the remote face's schema-sync count
claude Oct 19, 2026
edb2db1
test(dogfood): the upgrade ceremony over the 18.0 fixture is a shared…
claude Oct 19, 2026
1699ec5
Merge origin/main after #24667 (step 4) into the ceremony branch
claude Oct 20, 2026
b5010d9
test(dogfood): ceremoniedFixture boots the 18.0 bytes once, refused, …
claude Oct 20, 2026
0f83a8d
fix(text): no shipped text says the ownership ceremony is closed; an …
claude Oct 20, 2026
0050a74
Merge remote-tracking branch 'origin/claude/issue-24581-ceremony-open…
claude Oct 20, 2026
c6f775c
fix(text): the delta review's text items — fresh installs restated, t…
claude Oct 20, 2026
174abe9
refactor(plugin-webhooks)!: a row holding a declared webhook's name a…
claude Oct 20, 2026
ef2cb52
refactor(platform-objects,service-settings)!: sys_setting_audit retir…
claude Oct 20, 2026
43801b0
test(service-settings): the global-scope refusal pin asserts the engi…
claude Oct 20, 2026
b1f0f40
test(platform-objects): the es-ES echo ledger drops sys_setting_audit…
claude Oct 20, 2026
f6a7281
test(dogfood): the global-rung sibling dogfood's second boot pins the…
claude Oct 20, 2026
eb54066
chore(changeset): 18.x step 7c — the dual reads retire (minor, BREAKI…
claude Oct 20, 2026
e63b1b2
Merge remote-tracking branch 'origin/claude/issue-24581-ceremony-open…
claude Oct 20, 2026
dca9958
Merge remote-tracking branch 'origin/main' into claude/issue-24583-7c…
claude Oct 20, 2026
78f99c3
Merge remote-tracking branch 'origin/main' into claude/issue-24583-7c…
claude Oct 20, 2026
8f6bf26
chore(regen): discharge the merge's deferred generated reference (mig…
claude Oct 20, 2026
594465f
Merge remote-tracking branch 'origin/main' into claude/issue-24583-7c…
claude Oct 20, 2026
cdd1621
docs(text): step 7c residuals — sys_setting.scope's audit sibling not…
claude Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .changeset/24529-declared-webhooks-from-registry.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
---
'@objectstack/plugin-webhooks': minor
'@objectstack/runtime': minor
Expand All @@ -22,9 +22,9 @@

On a database an 18.0 install created (expand first — nothing is moved or deleted):

- The rows the 18.0 seeder wrote stay where they are. A pristine one (`managed_by: 'package'`, not `customized`) is ignored in favour of its declaration, so a declared webhook is never delivered twice; it follows the declaration's URL and switch, as the seeder's rewrite on every boot made it do.
- The rows the 18.0 seeder wrote stay where they are until the ownership ceremony settles them, and the v18 boot guard refuses the database until the ceremony has certified it. A pristine copy (`managed_by: 'package'`, not `customized`) is deleted by the ceremony, its deliveries re-pointed at the declaration, so a declared webhook is never delivered twice.
- A seeded row an administrator edited (`customized: true`), or a row an administrator authored under a declared name, answers as itself, and the declaration is not dispatched beside it — an administrator's `active: false` on a noisy webhook keeps holding. This is logged at `warn` once per name per process, with the remedy.
- A seeded row whose declaration was removed keeps delivering, as it did on 18.0.
- Redelivering an old delivery of a pristine seeded row is judged by the declaration that now answers for it, so a webhook switched off through the ledger is not re-sent.
- Redelivering an old delivery of a pristine seeded copy is judged by the declaration, because the ceremony re-points that delivery at `declared:<name>`; a webhook switched off through the ledger is not re-sent.

Deleting the seeded rows, and re-pointing their `sys_http_delivery` history at the declaration, is the ownership ceremony's `declared-webhooks` step (`os migrate organization-ownership`).
16 changes: 16 additions & 0 deletions .changeset/24583-7c-dual-reads-retired.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@objectstack/plugin-webhooks': minor
'@objectstack/platform-objects': minor
---

refactor(plugin-webhooks,platform-objects)!: 18.x step 7c retires the dual reads steps 1–2 added for 18.0 databases. Any `sys_webhook` row that holds a declared webhook's name now answers as itself. `sys_setting_audit.scope` no longer declares `global`.

Clause-②: yes (narrowing)

<!-- adr-0087: not-required (no-migration-prescription) No key of any metadata schema is removed, renamed or re-shaped, and no public export, type or error code is removed or renamed. Two things narrow at run time. The dispatcher no longer skips a pristine seeded webhook row in favour of its declaration (an internal reader, never exported). A stored select value is refused on one platform object. Both apply only to rows the v18 ownership ceremony settles or moves before a database is certified, and the v18 boot guard refuses a database it has not certified. -->

**BREAKING** narrowing, shipped as `minor` under the launch-window convention for breaking changes (ADR-0087, the ratified pre-launch launch-window section; the decisions retired here are ADR-0131 D2, D6 and D7). The 18.x upgrade ceremony settles every row these readers existed for before a database is certified, so a certified database and a fresh install read the same either way.

- **Declared webhooks (plugin-webhooks).** Before: a `sys_webhook` row seeded by the 18.0 seeder under a declared name, and never edited (`managed_by` `package`/`platform`, not `customized`), was skipped as a "mirror". The declaration answered in its place, for dispatch and for redelivery. Now: any row holding a declared name answers as itself and the declaration is withheld, reported once per name (regime C). The ceremony (step 5a) deletes or settles every such copy, so on a certified database this changes one case only. A seeded row the ceremony kept, because no package declared its name then, now withholds a declaration of that name added later, where before it was skipped. **Remedy:** rename or delete that row, and the declaration answers. The provenance stamp (`customized`) stays: the ceremony still reads it when a superseding reviewed plan re-runs the ceremony from the start.
- **Settings audit (platform-objects).** Before: `sys_setting_audit.scope` declared `global`, `tenant` and `user`, and a reader of the global rung's history read both `sys_setting_audit` and `sys_platform_setting_audit`. Now: `sys_setting_audit.scope` declares `tenant` and `user` only, and an insert with `scope: 'global'` is refused (`VALIDATION_FAILED` on `scope`). The global rung's history is `sys_platform_setting_audit` alone. The settings service already wrote global-rung rows there, and the ceremony (step 5b) moves every 18.0 row. **Remedy:** none for a certified database. A script that wrote audit rows by hand writes global-scope rows to `sys_platform_setting_audit`.
- **Unchanged.** Identity mail: only docblocks change. The tenant mail log keeps its body read gate, and a mail job with no object still reads `sys_email`, because a tenant mail row an 18.0 process queued stays there.
Original file line number Diff line number Diff line change
Expand Up @@ -3411,7 +3411,6 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
label: "Scope",
help: "Cascade layer the row was written to.",
options: {
global: "Global",
tenant: "Tenant",
user: "User"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3411,7 +3411,6 @@ export const esESObjects: NonNullable<TranslationData['objects']> = {
label: "Ámbito",
help: "Capa de cascada en la que se escribió la fila.",
options: {
global: "Global",
tenant: "Inquilino",
user: "Usuario"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ export const esESGeneratedSourceHashes: Readonly<Record<string, string>> = {
"objects.sys_secret.fields.id.label": "00b0385c9c152888",
"objects.sys_setting_audit.fields.actor_id.label": "b155813f8a7f06e3",
"objects.sys_setting_audit.fields.id.label": "00b0385c9c152888",
"objects.sys_setting_audit.fields.scope.options.global": "5e377106508d2ecd",
"objects.sys_setting_audit.fields.source.options.api": "4baeeff968e192d1",
"objects.sys_setting_audit.fields.source.options.ui": "95caad102a5c97ba",
"objects.sys_sso_provider._actions.register_saml_provider.params.identifierFormat.placeholder": "5945ff9a6b644855",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3411,7 +3411,6 @@ export const jaJPObjects: NonNullable<TranslationData['objects']> = {
label: "スコープ",
help: "行が書き込まれたカスケードレイヤー。",
options: {
global: "グローバル",
tenant: "テナント",
user: "ユーザー"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,6 @@ const SAME_WORD_ERROR =
'Spanish spells the word exactly as English (el error); this bundle already writes it so in the authored label Último error on sys_job_queue.';
const SAME_WORD_ACTOR =
'Spanish spells the word exactly as English (el actor); this bundle already writes it so in authored help (Último actor que escribió esta fila), and the messaging bundle authors its actor label as Actor.';
const SAME_WORD_GLOBAL =
'Spanish spells the word exactly as English (global); the security bundle already writes it so in authored help (nulo = global (entre inquilinos)), beside the authored sibling scope options Inquilino and Usuario.';
const SAME_WORD_VARIABLES =
'Spanish spells the word exactly as English (las variables); the authored description of this same object already says (asunto + contenido + variables).';
const BUNDLE_LOANWORD =
Expand Down Expand Up @@ -180,7 +178,6 @@ const DECISIONS: readonly Decision[] = [
echo('sys_notification.fields.actor_id.label', 'Actor', SAME_WORD_ACTOR),
echo('sys_metadata_audit.fields.actor.label', 'Actor', SAME_WORD_ACTOR),
echo('sys_setting_audit.fields.actor_id.label', 'Actor', SAME_WORD_ACTOR),
echo('sys_setting_audit.fields.scope.options.global', 'Global', SAME_WORD_GLOBAL),
echo('sys_email_template.fields.variables_json.label', 'Variables (JSON)', SAME_WORD_VARIABLES),

echo('sys_oauth_access_token.fields.token.label', 'Token', BUNDLE_LOANWORD),
Expand Down Expand Up @@ -239,13 +236,16 @@ function undeclaredEchoes(rows: readonly Decision[]): string[] {
describe('#20493 es-ES — the ledger itself (controls before verdicts)', () => {
// [ADR-0131 D7] 47 → 46 echoes: `sys_setting.scope` no longer declares the
// `global` option (the rung moved to `sys_platform_setting`), so its leaf left
// the catalog with it. `sys_setting_audit.scope`'s `global` option stays.
// the catalog with it.
// [ADR-0131 D13] 46 → 45: `sys_view_definition` retired as inert, and its
// bare `ID` leaf left the catalog with the object.
it('is the size it claims: 3 pinned translations and 45 declared echoes, no path twice', () => {
expect(DECISIONS.length).toBe(48);
// [18.x step 7] 45 → 44: `sys_setting_audit.scope` no longer declares the
// `global` option either (the ceremony moved those rows to
// `sys_platform_setting_audit`), so its leaf left the catalog too.
it('is the size it claims: 3 pinned translations and 44 declared echoes, no path twice', () => {
expect(DECISIONS.length).toBe(47);
expect(DECISIONS.filter((d) => d.verdict === 'translate').length).toBe(3);
expect(DECISIONS.filter((d) => d.verdict === 'echo').length).toBe(45);
expect(DECISIONS.filter((d) => d.verdict === 'echo').length).toBe(44);
expect(new Set(DECISIONS.map((d) => d.path)).size).toBe(DECISIONS.length);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3411,7 +3411,6 @@ export const zhCNObjects: NonNullable<TranslationData['objects']> = {
label: "范围",
help: "该记录写入的级联层。",
options: {
global: "全局",
tenant: "租户",
user: "用户"
}
Expand Down
10 changes: 6 additions & 4 deletions packages/platform-objects/src/audit/sys-email.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,12 @@ export const SysEmail = ObjectSchema.create({
// on read for a caller without it). An 18.0 install wrote its identity
// mail — password reset, account invitation, verification, magic link,
// email-change notice — into this log, stamped with the organization, and
// those bodies carry sign-in and reset links. The rows stay here until the
// 18.x ceremony moves them, and no column tells them apart from tenant mail
// reliably: a magic link carries no `related_object`, and the log has no
// template column. So the gate is on the columns, for every row: an
// those bodies carry sign-in and reset links. The 18.x ceremony moves them
// to `sys_identity_email` before the database is certified, selected by
// shape — and no column tells them apart from tenant mail reliably (a
// magic link carries no `related_object`, and the log has no template
// column), so a row the shape misses stays here. So the gate is on the
// columns, for every row: an
// organization administrator keeps the row (envelope, subject, delivery
// state, linkage) and loses only the body. The same field-level answer
// closes every door that serves a row through the engine — the data API,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';
* hook, the boot outbox sweep and attachment reclamation read it by id and
* status exactly as they read `sys_email`.
*
* Expand-first: rows an 18.0 install wrote to `sys_email` for this mail stay
* there; the mail pipeline keeps reading them, and moving them is the 18.x
* upgrade ceremony's.
* The rows an 18.0 install wrote to `sys_email` for this mail are moved here,
* id for id, by the 18.x upgrade ceremony before the database is certified —
* their queued jobs and activity timeline pointed at this log first — and the
* v18 boot guard refuses a database it has not certified.
*
* `systemFields: { tenant: false }` removes the injected organization column.
* With no column there is no tenant wall, so who may read is object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';
* Tenant and user rows stay in `sys_setting_audit`, each stamped with the
* organization whose value changed.
*
* Same row shape as `sys_setting_audit`, so one reader reads both tables the
* same way; `scope` is always `global` here.
* Same row shape as `sys_setting_audit`, so one reader reads either table the
* same way; `scope` is always `global` here, and never `global` there.
*
* ## No organization column, governed by object permission
*
Expand All @@ -27,8 +27,10 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';
* ## The 18.0 rows
*
* An 18.0 install wrote its global-rung audit rows into `sys_setting_audit`,
* stamped with the Default Organization. They stay there until the upgrade
* ceremony moves them here; a reader of the global rung's history reads both.
* stamped with the Default Organization. The upgrade ceremony moves them here,
* id for id, before the database is certified (18.x step 5b), and the v18 boot
* guard refuses a database it has not certified — so the global rung's history
* is this table alone.
*
* Append-only: the settings audit writer is the only writer, and the generic
* data API offers reads only.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';
* - The schema here carries settings-specific fields (`scope`,
* `cascade_source`) that don't make sense on a generic row.
*
* Tenant and user rungs only (ADR-0131 D7, ruling record 6257510973 · Q4 A):
* a global-rung change is deployment-level, and its row lives in
* `sys_platform_setting_audit`. The upgrade ceremony moved every global-rung
* row an 18.0 install wrote here, so `scope` declares no `global` option and a
* row claiming it is refused.
*
* Append-only contract: enforced at the application layer (the only
* writer is SettingsService). Operators MUST NOT delete rows; instead
* use lifecycle policies to archive cold rows to a separate bucket.
Expand Down Expand Up @@ -71,7 +77,6 @@ export const SysSettingAudit = ObjectSchema.create({

scope: Field.select(
[
{ label: 'Global', value: 'global' },
{ label: 'Tenant', value: 'tenant' },
{ label: 'User', value: 'user' },
],
Expand Down
7 changes: 4 additions & 3 deletions packages/platform-objects/src/system/sys-setting.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,10 @@ export const SysSetting = ObjectSchema.create({
// The option list is the storage-side mirror of the cascade layers THIS
// table stores: `SpecifierScopeSchema`
// (`packages/spec/src/system/settings-manifest.zod.ts`) minus `global`.
// `sys-setting.scope-options.test.ts` pins the parity. The sibling audit
// object (`sys_setting_audit.scope`) keeps all three, because the audit
// writer records a global-scope change too.
// `sys-setting.scope-options.test.ts` pins the parity. The audit trail
// splits the same way: `sys_setting_audit.scope` keeps `tenant` and
// `user`, and a global-scope change is recorded in
// `sys_platform_setting_audit`.
// `global` left with the rung itself (ADR-0131 D7): a global value is
// written to `sys_platform_setting`, no writer produces a `global` row
// here, and the resolver excludes one from its reads — so the option was a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
// [ADR-0131 D7] Since the global rung moved to the tenant-less
// `sys_platform_setting`, this table stores the cascade's OTHER layers: its
// option list is the spec enum minus `global`, and that difference is pinned
// as such. The audit trail still records every layer, `global` included.
// as such. The audit trail follows the same split: `sys_setting_audit` keeps
// the tenant and user rungs, `sys_platform_setting_audit` the global one.
import { describe, expect, it } from 'vitest';
import { SpecifierScopeSchema } from '@objectstack/spec/system';
import { SysSetting } from './sys-setting.object.js';
import { SysSettingAudit } from './sys-setting-audit.object.js';
import { SysPlatformSettingAudit } from './sys-platform-setting-audit.object.js';

/** Declared option values of a select field, in declaration order. */
function optionValues(object: unknown, field: string): string[] {
Expand Down Expand Up @@ -59,13 +61,16 @@ describe('sys_setting.scope — value domain (#6036)', () => {
);
});

it('the audit trail object records EVERY layer — the global rung\'s changes included', () => {
// `buildSettingAuditWriter` writes the changed key's scope, and a
// global-scope change is still a change: the audit option list is the
// full spec enum, not this table's subset.
it('[18.x step 7] the audit trail splits the layers as the values do: global in the sibling, the rest here', () => {
// `buildSettingAuditWriter` routes a global-scope change to
// `sys_platform_setting_audit`, and the upgrade ceremony moved every
// global-rung row an 18.0 install wrote to `sys_setting_audit`. So the
// tenant table's option list is this table's subset, and the sibling's is
// the rung it holds; together they are the full spec enum.
expect([...optionValues(SysSettingAudit, 'scope')].sort()).toEqual(
[...SpecifierScopeSchema.options].sort(),
SpecifierScopeSchema.options.filter((s) => s !== 'global').sort(),
);
expect(optionValues(SysPlatformSettingAudit, 'scope')).toEqual(['global']);
});

it('keeps `tenant` as the default, and the default is a declared option', () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/plugins/plugin-email/src/email-log-objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
* reader cannot recover from the id is WHICH object holds it, so a durable
* job carries the object beside the id. A job with no object is one an 18.0
* process published, when `sys_email` was the only log — it reads
* `sys_email` (expand-first: those rows and jobs stay where they are).
* `sys_email`. The upgrade ceremony points the jobs of the identity mail it
* moves at `sys_identity_email` first, so what still reads this way names a
* tenant mail row, which stays in `sys_email`.
*/

/** The tenant mail log. */
Expand Down
6 changes: 3 additions & 3 deletions packages/plugins/plugin-email/src/email-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -997,9 +997,9 @@ export class EmailServicePlugin implements Plugin {
// not grounds to refuse an otherwise healthy boot; the gate above is
// where this plugin refuses.
//
// Both logs, each on its own: `sys_email` (which keeps every identity
// row an 18.0 install wrote there — expand-first) and its sibling
// `sys_identity_email`. One log failing to sweep does not stop the
// Both logs, each on its own: `sys_email` and its sibling
// `sys_identity_email` (where the upgrade ceremony moved the identity
// rows an 18.0 install wrote to `sys_email`). One log failing to sweep does not stop the
// other, and the settled result is their sum.
if (persistence) {
const svc = this.service;
Expand Down
Loading
Loading