Skip to content

Commit 52281b0

Browse files
os-zhuangclaude
andauthored
chore(i18n): purge dead sys_webhook_delivery bundle block + roll out bundle-ownership guards (#3502)
- Remove the stale sys_webhook_delivery translation block from the four plugin-webhooks i18n bundles (object removed in ADR-0018 M3 → sys_http_delivery); surgical, the sys_webhook block is untouched. - Fix three stale sys_webhook_delivery doc comments (platform-objects integration/index.ts + setup.app.ts, plugin-webhooks sys-webhook.object.ts). - Add the bundle-ownership guard test (#2834 5 / ADR-0029 D8) to the 8 packages that own i18n bundles, so a stray object block in a generated bundle fails the build instead of dying silently on the next `os i18n extract`. - Fix a live-object omission the guard caught: re-add SysCapability to plugin-security's extract config (it had curated translations in the bundles but had been dropped from the config), preserving the strings instead of deleting them. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 74f7339 commit 52281b0

12 files changed

Lines changed: 392 additions & 6 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
"@objectstack/plugin-webhooks": patch
3+
---
4+
5+
chore(i18n): purge the dead sys_webhook_delivery translation block and guard against recurrence
6+
7+
`sys_webhook_delivery` was removed when webhook delivery moved to
8+
`@objectstack/service-messaging` (`sys_http_delivery`, ADR-0018 M3), but a full
9+
translation block for it lingered in the four generated plugin-webhooks i18n
10+
bundles (en/zh-CN/ja-JP/es-ES) — dead weight bound to an object that no longer
11+
exists, and destined to be dropped silently (with any curated strings) on the
12+
next `os i18n extract`.
13+
14+
- Removed the stale `sys_webhook_delivery` block from all four locale bundles
15+
(surgical; the `sys_webhook` block is untouched).
16+
- Corrected three stale `sys_webhook_delivery` doc comments (platform-objects
17+
`integration/index.ts` + `setup.app.ts`, plugin-webhooks `sys-webhook.object.ts`)
18+
that still named it as a plugin-webhooks-owned object.
19+
- Rolled out the platform-objects `bundle-ownership` test guard (#2834 ⑤ /
20+
ADR-0029 D8) to the eight packages that own i18n bundles, so a stray object
21+
block in a generated bundle now fails the build instead of dying silently.
22+
- That guard immediately surfaced a live-object omission: `sys_capability` was
23+
present in plugin-security's bundles with curated translations but had been
24+
dropped from its extract config — re-added to the config so the strings are
25+
preserved, rather than deleted.

packages/platform-objects/src/apps/setup.app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* alongside this app.
1515
* - Items owned by a capability plugin are contributed by that plugin — e.g.
1616
* `@objectstack/plugin-webhooks` fills `group_integrations` with its
17-
* `sys_webhook` / `sys_webhook_delivery` entries (ADR-0029 K2.a).
17+
* `sys_webhook` entry (ADR-0029 K2.a).
1818
*
1919
* The runtime merges all contributions into this app's `navigation` tree by
2020
* group id + priority on read, so the rendered Setup nav is identical to the

packages/platform-objects/src/integration/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
* platform-objects/integration — External Integration Platform Objects
55
*
66
* **Empty since ADR-0029 (K2.a).** `sys_webhook` moved to its owner,
7-
* `@objectstack/plugin-webhooks` (alongside `sys_webhook_delivery`), so the
8-
* plugin ships its data model and behavior as one unit. Import the schema
9-
* from `@objectstack/plugin-webhooks/schema` instead.
7+
* `@objectstack/plugin-webhooks`, so the plugin ships its data model and
8+
* behavior as one unit. Import the schema from
9+
* `@objectstack/plugin-webhooks/schema` instead.
1010
*
1111
* The subpath (`@objectstack/plugin-webhooks/integration`) is retained as an
1212
* empty barrel to avoid churning the package `exports` map / tsup entries
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// Bundle-ownership guard (#2834 ⑤ / ADR-0029 D8): this package's generated
4+
// object-translation bundles must carry ONLY objects its extract config
5+
// (`scripts/i18n-extract.config.ts`) actually imports. When an object moves to
6+
// another package, its translations move with it — a leftover copy here
7+
// silently DIES on the next `os i18n extract` run, taking curated translations
8+
// with it (the sys_audit_log incident). This test turns that silent loss into a
9+
// red build: an object present in the bundle but not in the ownership list below
10+
// means either (a) the extract config gained an object — add it here — or (b) a
11+
// moved/removed object's keys were left behind — remove them from the bundles
12+
// (or migrate them to the owning package), then keep this list in sync.
13+
//
14+
// NB: this package defines more approval objects (sys_approval_approver,
15+
// sys_approval_token) that the extract config deliberately does NOT translate —
16+
// they are absent from both the config and the bundles, so they are correctly
17+
// out of scope here.
18+
19+
import { describe, it, expect } from 'vitest';
20+
import { enObjects } from './en.objects.generated.js';
21+
22+
// Objects the extract config (scripts/i18n-extract.config.ts) imports —
23+
// keep the two lists in sync when adding/moving objects.
24+
const OWNED_OBJECTS = new Set([
25+
'sys_approval_request',
26+
'sys_approval_action',
27+
'sys_approval_delegation',
28+
]);
29+
30+
describe('objects translation bundle ownership (ADR-0029 D8)', () => {
31+
it('the en bundle contains no objects owned by other packages', () => {
32+
const strays = Object.keys(enObjects).filter((o) => !OWNED_OBJECTS.has(o));
33+
expect(
34+
strays,
35+
`bundle carries objects this package's extract config does not own: ${strays.join(', ')} — ` +
36+
'their curated translations would be silently deleted on the next `os i18n extract`. ' +
37+
'Remove the dead block from the four locale bundles, or add the object to the extract config + this list.',
38+
).toEqual([]);
39+
});
40+
41+
it('every owned object is present in the bundle (extract config regression)', () => {
42+
const missing = [...OWNED_OBJECTS].filter((o) => !(o in enObjects));
43+
expect(
44+
missing,
45+
`objects the extract config should emit are missing from the bundle: ${missing.join(', ')} — was an import dropped from scripts/i18n-extract.config.ts?`,
46+
).toEqual([]);
47+
});
48+
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// Bundle-ownership guard (#2834 ⑤ / ADR-0029 D8): this package's generated
4+
// object-translation bundles must carry ONLY objects its extract config
5+
// (`scripts/i18n-extract.config.ts`) actually imports. When an object moves to
6+
// another package, its translations move with it — a leftover copy here
7+
// silently DIES on the next `os i18n extract` run, taking curated translations
8+
// with it (the sys_audit_log incident). This test turns that silent loss into a
9+
// red build: an object present in the bundle but not in the ownership list below
10+
// means either (a) the extract config gained an object — add it here — or (b) a
11+
// moved/removed object's keys were left behind — remove them from the bundles
12+
// (or migrate them to the owning package), then keep this list in sync.
13+
14+
import { describe, it, expect } from 'vitest';
15+
import { enObjects } from './en.objects.generated.js';
16+
17+
// Objects the extract config (scripts/i18n-extract.config.ts) imports —
18+
// keep the two lists in sync when adding/moving objects.
19+
const OWNED_OBJECTS = new Set([
20+
'sys_audit_log',
21+
'sys_activity',
22+
'sys_comment',
23+
]);
24+
25+
describe('objects translation bundle ownership (ADR-0029 D8)', () => {
26+
it('the en bundle contains no objects owned by other packages', () => {
27+
const strays = Object.keys(enObjects).filter((o) => !OWNED_OBJECTS.has(o));
28+
expect(
29+
strays,
30+
`bundle carries objects this package's extract config does not own: ${strays.join(', ')} — ` +
31+
'their curated translations would be silently deleted on the next `os i18n extract`. ' +
32+
'Remove the dead block from the four locale bundles, or add the object to the extract config + this list.',
33+
).toEqual([]);
34+
});
35+
36+
it('every owned object is present in the bundle (extract config regression)', () => {
37+
const missing = [...OWNED_OBJECTS].filter((o) => !(o in enObjects));
38+
expect(
39+
missing,
40+
`objects the extract config should emit are missing from the bundle: ${missing.join(', ')} — was an import dropped from scripts/i18n-extract.config.ts?`,
41+
).toEqual([]);
42+
});
43+
});

packages/plugins/plugin-security/scripts/i18n-extract.config.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,19 @@
1313
*/
1414

1515
import { defineStack } from '@objectstack/spec';
16-
import { SysPosition, SysPermissionSet, SysUserPermissionSet, SysPositionPermissionSet } from '../src/objects/index.js';
16+
// SysCapability carries curated translations already present in the bundles; it
17+
// must stay in this list so `os i18n extract` keeps emitting it (dropping it
18+
// here silently deletes those strings on the next run — the sys_audit_log
19+
// incident). Enforced by src/translations/bundle-ownership.test.ts.
20+
import { SysPosition, SysCapability, SysPermissionSet, SysUserPermissionSet, SysPositionPermissionSet } from '../src/objects/index.js';
1721
import { enObjects } from '../src/translations/en.objects.generated.js';
1822
import { zhCNObjects } from '../src/translations/zh-CN.objects.generated.js';
1923
import { jaJPObjects } from '../src/translations/ja-JP.objects.generated.js';
2024
import { esESObjects } from '../src/translations/es-ES.objects.generated.js';
2125

2226
export default defineStack({
2327
name: 'plugin-security-i18n-extract',
24-
objects: [SysPosition, SysPermissionSet, SysUserPermissionSet, SysPositionPermissionSet] as any,
28+
objects: [SysPosition, SysCapability, SysPermissionSet, SysUserPermissionSet, SysPositionPermissionSet] as any,
2529
translations: [
2630
{ en: { objects: enObjects } },
2731
{ 'zh-CN': { objects: zhCNObjects } },
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// Bundle-ownership guard (#2834 ⑤ / ADR-0029 D8): this package's generated
4+
// object-translation bundles must carry ONLY objects its extract config
5+
// (`scripts/i18n-extract.config.ts`) actually imports. When an object moves to
6+
// another package, its translations move with it — a leftover copy here
7+
// silently DIES on the next `os i18n extract` run, taking curated translations
8+
// with it (the sys_audit_log incident). This test turns that silent loss into a
9+
// red build: an object present in the bundle but not in the ownership list below
10+
// means either (a) the extract config gained an object — add it here — or (b) a
11+
// moved/removed object's keys were left behind — remove them from the bundles
12+
// (or migrate them to the owning package), then keep this list in sync.
13+
//
14+
// NB: this package defines more objects (sys_user_position,
15+
// sys_audience_binding_suggestion) that the extract config deliberately does NOT
16+
// translate — absent from both the config and the bundles, correctly out of
17+
// scope. `sys_capability` IS translated (curated strings live in the bundles),
18+
// so it must stay in the extract config; this guard is what caught it being
19+
// dropped from that list.
20+
21+
import { describe, it, expect } from 'vitest';
22+
import { enObjects } from './en.objects.generated.js';
23+
24+
// Objects the extract config (scripts/i18n-extract.config.ts) imports —
25+
// keep the two lists in sync when adding/moving objects.
26+
const OWNED_OBJECTS = new Set([
27+
'sys_position',
28+
'sys_capability',
29+
'sys_permission_set',
30+
'sys_user_permission_set',
31+
'sys_position_permission_set',
32+
]);
33+
34+
describe('objects translation bundle ownership (ADR-0029 D8)', () => {
35+
it('the en bundle contains no objects owned by other packages', () => {
36+
const strays = Object.keys(enObjects).filter((o) => !OWNED_OBJECTS.has(o));
37+
expect(
38+
strays,
39+
`bundle carries objects this package's extract config does not own: ${strays.join(', ')} — ` +
40+
'their curated translations would be silently deleted on the next `os i18n extract`. ' +
41+
'Remove the dead block from the four locale bundles, or add the object to the extract config + this list.',
42+
).toEqual([]);
43+
});
44+
45+
it('every owned object is present in the bundle (extract config regression)', () => {
46+
const missing = [...OWNED_OBJECTS].filter((o) => !(o in enObjects));
47+
expect(
48+
missing,
49+
`objects the extract config should emit are missing from the bundle: ${missing.join(', ')} — was an import dropped from scripts/i18n-extract.config.ts?`,
50+
).toEqual([]);
51+
});
52+
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// Bundle-ownership guard (#2834 ⑤ / ADR-0029 D8): this package's generated
4+
// object-translation bundles must carry ONLY objects its extract config
5+
// (`scripts/i18n-extract.config.ts`) actually imports. When an object moves to
6+
// another package, its translations move with it — a leftover copy here
7+
// silently DIES on the next `os i18n extract` run, taking curated translations
8+
// with it (the sys_audit_log incident). This test turns that silent loss into a
9+
// red build: an object present in the bundle but not in the ownership list below
10+
// means either (a) the extract config gained an object — add it here — or (b) a
11+
// moved/removed object's keys were left behind — remove them from the bundles
12+
// (or migrate them to the owning package), then keep this list in sync.
13+
14+
import { describe, it, expect } from 'vitest';
15+
import { enObjects } from './en.objects.generated.js';
16+
17+
// Objects the extract config (scripts/i18n-extract.config.ts) imports —
18+
// keep the two lists in sync when adding/moving objects.
19+
const OWNED_OBJECTS = new Set([
20+
'sys_record_share',
21+
'sys_sharing_rule',
22+
'sys_share_link',
23+
]);
24+
25+
describe('objects translation bundle ownership (ADR-0029 D8)', () => {
26+
it('the en bundle contains no objects owned by other packages', () => {
27+
const strays = Object.keys(enObjects).filter((o) => !OWNED_OBJECTS.has(o));
28+
expect(
29+
strays,
30+
`bundle carries objects this package's extract config does not own: ${strays.join(', ')} — ` +
31+
'their curated translations would be silently deleted on the next `os i18n extract`. ' +
32+
'Remove the dead block from the four locale bundles, or add the object to the extract config + this list.',
33+
).toEqual([]);
34+
});
35+
36+
it('every owned object is present in the bundle (extract config regression)', () => {
37+
const missing = [...OWNED_OBJECTS].filter((o) => !(o in enObjects));
38+
expect(
39+
missing,
40+
`objects the extract config should emit are missing from the bundle: ${missing.join(', ')} — was an import dropped from scripts/i18n-extract.config.ts?`,
41+
).toEqual([]);
42+
});
43+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// Bundle-ownership guard (#2834 ⑤ / ADR-0029 D8): this package's generated
4+
// object-translation bundles must carry ONLY objects its extract config
5+
// (`scripts/i18n-extract.config.ts`) actually imports. When an object moves to
6+
// another package, its translations move with it — a leftover copy here
7+
// silently DIES on the next `os i18n extract` run, taking curated translations
8+
// with it (the sys_audit_log incident). This test turns that silent loss into a
9+
// red build: an object present in the bundle but not in the ownership list below
10+
// means either (a) the extract config gained an object — add it here — or (b) a
11+
// moved/removed object's keys were left behind — remove them from the bundles
12+
// (or migrate them to the owning package), then keep this list in sync.
13+
14+
import { describe, it, expect } from 'vitest';
15+
import { enObjects } from './en.objects.generated.js';
16+
17+
// Objects the extract config (scripts/i18n-extract.config.ts) imports —
18+
// keep the two lists in sync when adding/moving objects.
19+
const OWNED_OBJECTS = new Set([
20+
'sys_webhook',
21+
]);
22+
23+
describe('objects translation bundle ownership (ADR-0029 D8)', () => {
24+
it('the en bundle contains no objects owned by other packages', () => {
25+
const strays = Object.keys(enObjects).filter((o) => !OWNED_OBJECTS.has(o));
26+
expect(
27+
strays,
28+
`bundle carries objects this package's extract config does not own: ${strays.join(', ')} — ` +
29+
'their curated translations would be silently deleted on the next `os i18n extract`. ' +
30+
'Remove the dead block from the four locale bundles, or add the object to the extract config + this list.',
31+
).toEqual([]);
32+
});
33+
34+
it('every owned object is present in the bundle (extract config regression)', () => {
35+
const missing = [...OWNED_OBJECTS].filter((o) => !(o in enObjects));
36+
expect(
37+
missing,
38+
`objects the extract config should emit are missing from the bundle: ${missing.join(', ')} — was an import dropped from scripts/i18n-extract.config.ts?`,
39+
).toEqual([]);
40+
});
41+
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// Bundle-ownership guard (#2834 ⑤ / ADR-0029 D8): this package's generated
4+
// object-translation bundles must carry ONLY objects its extract config
5+
// (`scripts/i18n-extract.config.ts`) actually imports. When an object moves to
6+
// another package, its translations move with it — a leftover copy here
7+
// silently DIES on the next `os i18n extract` run, taking curated translations
8+
// with it (the sys_audit_log incident). This test turns that silent loss into a
9+
// red build: an object present in the bundle but not in the ownership list below
10+
// means either (a) the extract config gained an object — add it here — or (b) a
11+
// moved/removed object's keys were left behind — remove them from the bundles
12+
// (or migrate them to the owning package), then keep this list in sync.
13+
14+
import { describe, it, expect } from 'vitest';
15+
import { enObjects } from './en.objects.generated.js';
16+
17+
// Objects the extract config (scripts/i18n-extract.config.ts) imports —
18+
// keep the two lists in sync when adding/moving objects.
19+
const OWNED_OBJECTS = new Set([
20+
'sys_inbox_message',
21+
'sys_notification_receipt',
22+
'sys_notification_delivery',
23+
'sys_notification_preference',
24+
'sys_notification_subscription',
25+
'sys_notification_template',
26+
'sys_http_delivery',
27+
]);
28+
29+
describe('objects translation bundle ownership (ADR-0029 D8)', () => {
30+
it('the en bundle contains no objects owned by other packages', () => {
31+
const strays = Object.keys(enObjects).filter((o) => !OWNED_OBJECTS.has(o));
32+
expect(
33+
strays,
34+
`bundle carries objects this package's extract config does not own: ${strays.join(', ')} — ` +
35+
'their curated translations would be silently deleted on the next `os i18n extract`. ' +
36+
'Remove the dead block from the four locale bundles, or add the object to the extract config + this list.',
37+
).toEqual([]);
38+
});
39+
40+
it('every owned object is present in the bundle (extract config regression)', () => {
41+
const missing = [...OWNED_OBJECTS].filter((o) => !(o in enObjects));
42+
expect(
43+
missing,
44+
`objects the extract config should emit are missing from the bundle: ${missing.join(', ')} — was an import dropped from scripts/i18n-extract.config.ts?`,
45+
).toEqual([]);
46+
});
47+
});

0 commit comments

Comments
 (0)