Skip to content

feat(service-messaging,spec,cli)!: retire sys_notification_subscription — no reader ever expanded a subscription (ADR-0030 L3, amended) - #25024

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-24923-retire-notification-subscription
Oct 21, 2026
Merged

os-zhuang merged 4 commits into
mainfrom
claude/issue-24923-retire-notification-subscription

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Closes #24923

Clause-②: yes (narrowing: sys_notification_subscription retires, carried into 18.x by its ADR-0087 semantic entry under 「甲」)

Tier H. This diff touches docs/adr/0030-notification-platform-convergence.md, so it stays a draft until an authorized approval (and it owes a CONTRACT_REVIEW_TIER contract review). No AI seat lands it.

What and why

The maintainer's ruling D on the parent card (decision record 6290342354, recorded 6293376907) retires sys_notification_subscription. ADR-0030 described it as Layer-3 audience expansion, and that expansion was never wired:

  • every producer passes an explicit audience (EmitInput.audience is required);
  • no audience member asks for subscribers, and no recipient-resolver branch reads a topic's subscriptions;
  • so the Setup "Notification Subscriptions" grid let an administrator author rows that made nobody receive anything.

The ruling supersedes #9807's keep-with-a-comment decision. The table stays in deployed databases, on the scim-provider-object-retired / sys-view-definition-retired precedent. Under standing rule 「甲」 (#6015, record 6292005854) the ADR-0087 semantic entry carries the narrowing, so it ships in 18.x as a minor with the BREAKING banner.

Re-measured on current main (e4d604efd3) before editing

reading sys_notification_subscription control sys_notification_preference
files under packages/**/src, tests and generated bundles excluded 6 8
of those, readers of the rows 0 2 (preference-resolver.ts, fan-out-system-context.ts)
repo-wide files 44 55
objectui at the console pin cf153eb2e5 0 2 files

The 6 files are the object's own declaration, the plugin's registration and nav item, a translation-bundle comment, PLATFORM_OBJECTS_BY_PACKAGE, the object.zod.ts doc comment, and the CLI ownership inventory. None of them reads a row. The cloud repository is out of reach from this session, so its readers are NOT MEASURED. The entry's reason says this repo and the Studio repo only.

What changed

  • service-messaging: the object file and its organization-unique pin test are deleted. The object is gone from the manifest registration, from boot provisioning, from the Setup nav (nav_notification_subscriptions), from both export barrels and from the i18n extract config. The generated object bundles were regenerated with node scripts/check-i18n-bundles.mjs --write: 178 lines deleted, nothing added. The sibling tests drop the object. plugin-declares-event-object.test.ts gains an absence pin: the object is not registered and has no nav item. The same capture sees the surviving preference item, which is the vacuity control.
  • platform-objects: the Setup nav label nav_notification_subscriptions is removed from the hand-written app translations (en, zh-CN, ja-JP, es-ES), along with its three recorded source hashes. This is outside the claim's listed file surface. It is the same retirement's translation half: the nav item it labelled no longer exists.
  • spec:
    • the name leaves PLATFORM_OBJECTS_BY_PACKAGE['service-messaging'], pinned in platform-object-names.test.ts with the preference as control;
    • the object.zod.ts system-data doc comment no longer names it;
    • new semantic entry 18.sys-notification-subscription-retired.ts, in the precedents' shape: no RETIRED_KEYS_BY_MAJOR entry and no D2 conversion, because no authorable spec key moves;
    • one STEP18_RATIONALE fragment, inserted at its sorted id position.
  • cli: organization-ownership-inventory.ts keeps a row for the retired-but-present table, now census platform-census-2026-08-31 only. It moves from fate 3 attribution to fate 4 report with notNullExempt, citing the ADR-0087 entry, on the sys_scim_provider precedent (bef7bdc). It is not retired-before-census, because the first census counted this object.
    • New unit pin: a RETIRED_AFTER_FIRST_CENSUS population, held to the same premises as RETIRED_BEFORE_CENSUS.
    • New integration pin in organization-ownership-plan.integration.test.ts: the last declared table shape, under isolated and single, plans as fate 4. The NULL row is reported, never attributed, and notNull.willReceive is false. Control: without the row, the plan refuses with uninventoried-platform-table.
  • ADR-0030: a dated Amended (2026-10-20 …) entry on the status line, in the same form as the existing 2026-09-20 amendment. The L3 row is revised to preference-only, and the remaining-work "Subscription-driven fan-out" bullet is struck through and marked Withdrawn.
  • QA checklist: a resolution line in FOLLOW-UPS.md §9d, which is append-never-rewrite, for the §9c row.
  • Census: scripts/platform-object-tenancy-census.json regenerated, 85 → 84 registered.
  • Changeset: .changeset/24923-sys-notification-subscription-retired.md. minor for service-messaging, spec and cli, patch for platform-objects, with the BREAKING banner, ADR-0087 disposition registered sys-notification-subscription-retired and the FROM → TO mapping. content/docs/releases/ is untouched.

Dogfood upgrade-harness fixtures: no edit, deliberately. fixture-18.0 and fixture-18.0.0 are dumps of databases written by released code, and both hold the table with 0 rows. A retired-but-present table is exactly what they should hold. Regenerating them from the same refs would reproduce the same bytes. The two precedents predate the fixtures, so there is no fixture edit to mirror. The harness was run instead (below): HEAD boots over the fixture, and the ceremony plans and applies with the new fate-4 row.

Verification (head 356172a1f9; the package suites ran on the pre-merge tree, and check:generated was re-run after the merge)

  • pnpm --filter @objectstack/service-messaging test: Test Files 53 passed (53), Tests 589 passed (589). typecheck (both tsconfigs): exit 0.
  • pnpm --filter @objectstack/spec test: Test Files 703 passed (703), Tests 21365 passed | 1 todo. typecheck: exit 0.
  • pnpm --filter @objectstack/spec check:generated: All 14 generated artifacts are up to date (re-run after the origin/main merge, on a rebuilt spec).
  • CLI (--project unit in full exceeds the foreground cap, so this run is narrowed, as declared): every organization-ownership* test plus test/migrate-meta-engine-guidance.test.ts, which prints every semantic entry and pins it free of tracker numbers. Test Files 25 passed | 1 skipped (26), Tests 285 passed. pnpm --filter @objectstack/cli typecheck: exit 0. The rest of the unit and integration tiers is left to CI.
  • Dogfood test/upgrade-18x-harness.dogfood.test.ts: Tests 12 passed (12).
  • @objectstack/platform-objects test and typecheck: NOT MEASURED. Three attempts at the shared verify lock queued 9 minutes each without acquiring it (the holder was another worktree's long chain), and a fourth was cut by the foreground cap. The diff there is 7 deleted lines in the hand-written app translation dictionaries. check:i18n, which reads them, is green. CI owns the suite.
  • Gates from node scripts/pm/dispatch-gates.mjs --commands: 101 commands, 99 exit 0. Among them: check-adr-0087-registration --base origin/main (registered sys-notification-subscription-retired (new here …)), check:i18n (all bundles in sync), check:platform-object-tenancy-census, check:spec-changes, check:upgrade-guide, check:doc-authoring, check:nul-bytes, check:cross-package-test-inputs, check:platform-checklist, check:changeset-no-major and check:pm-governed-merges.
    • check:i18n-coverage first hit its build prerequisite. After the closure build it was re-run: OK (14 config(s), 621 baselined untranslated string(s), none new).
    • check:dual-build-cjs-loads: NOT MEASURED. Its prerequisite needs dist/ for packages this diff does not touch (studio, client-react, connector-slack, …). CI owns it.

Acceptance notes

维护者速读(草稿)

改了什么: 下线 Setup 里的「通知订阅」:对象、菜单项、导出与翻译都移除,平台对象名单去掉这个名字;数据库里的旧表原样保留不动。ADR-0030 的 L3 行与剩余工作清单按裁决 D 记一笔带日期的修订。

为什么改: 订阅从来没有接上线:所有发通知的地方都写明了收件人,没有任何代码读订阅表,管理员建了订阅也不会有人收到通知。你在 #24770 裁了 D:退役订阅(取代 #9807「保留并加注释」)。

风险与代价(含回滚): 引用了这个对象名的元数据会被提示「未知对象」;代码里 import NotificationSubscription 会编译失败(changeset 写明了改法)。旧表与旧数据不删,升级仪式把它列为「只报告、不归属」。回滚就是 revert 本 PR,对象与菜单即恢复,数据从未动过。

席位意见:

你要做的: 审阅 ADR-0030 的修订文字;同意则批准本 PR(Tier H,需你的批准才能落地)。


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/cli, @objectstack/platform-objects, @objectstack/service-messaging, @objectstack/spec, touching 32 documentable anchor(s). ⚠️ 4 changed file(s) yielded no anchor (packages/services/service-messaging/src/index.ts, packages/services/service-messaging/src/objects/index.ts, packages/services/service-messaging/src/translations/index.ts, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

46 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 05b28c199b7408da7ca4bc3988635fa9342cd6de.

⛔ 14 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 4 changed file(s) yielded no anchor (packages/services/service-messaging/src/index.ts, packages/services/service-messaging/src/objects/index.ts, packages/services/service-messaging/src/translations/index.ts, …) — pages documenting those are invisible to this run
  • 1 cross-cutting symbol(s) contributed no route anchor: isSystem (5 routes)
  • 1 anchor(s) matched too much of the corpus to be a work list: created_at (literal, 38 pages)
  • 12 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 150 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 05b28c199b7408da7ca4bc3988635fa9342cd6de → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 298ac2fc32c150b9c42a0fbd1a40b5191c1ce8f6 — the merge of head 356172a1f9912baa0c8a1a0a92f77ba9c5b0a557 into base 05b28c199b7408da7ca4bc3988635fa9342cd6de, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 298ac2fc32c150b9c42a0fbd1a40b5191c1ce8f6 && git checkout 298ac2fc32c150b9c42a0fbd1a40b5191c1ce8f6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 05b28c199b7408da7ca4bc3988635fa9342cd6de 356172a1f9912baa0c8a1a0a92f77ba9c5b0a557 && git checkout -B drift-repro 05b28c199b7408da7ca4bc3988635fa9342cd6de && git merge --no-ff 356172a1f9912baa0c8a1a0a92f77ba9c5b0a557

node scripts/docs-audit/affected-docs.mjs --json 05b28c199b7408da7ca4bc3988635fa9342cd6de

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 05b28c199b7408da7ca4bc3988635fa9342cd6de → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 356172a1f9912baa0c8a1a0a92f77ba9c5b0a557
Local-runs: none
Scope: packages/spec/src/data/object.zod.ts, packages/spec/src/migrations/entries/semantic/18.sys-notification-subscription-retired.ts, packages/spec/src/migrations/registry.ts.template, packages/spec/src/system/constants/platform-object-names.ts, docs/adr/0030-notification-platform-convergence.md + packages/spec/src/migrations/entries/semantic/18.sys-view-definition-retired.ts, packages/spec/src/migrations/entries/semantic/18.scim-provider-object-retired.ts, packages/services/service-messaging/src/messaging-service-plugin.ts, packages/services/service-messaging/src/index.ts, packages/services/service-messaging/src/objects/index.ts, packages/services/service-messaging/src/plugin-declares-event-object.test.ts, packages/cli/src/utils/organization-ownership-inventory.ts, packages/cli/src/utils/organization-ownership-inventory.test.ts, packages/cli/src/utils/organization-ownership-plan.integration.test.ts, packages/platform-objects/src/apps/translations/en.ts, .changeset/24923-sys-notification-subscription-retired.md, scripts/pm/clause2-line.mjs, scripts/check-changeset-no-major.mjs, packages/qa/dogfood/test/upgrade-harness/fixture-18.0.0/manifest.json

Inputs read: card #24923 body and its five comments (6294724028, 6301928267, 6302614923, 6303388568, 6305755241); parent #24770 decision 6290342354 and recording 6293376907; standing ruling #6015 comment 6292005854; the PR body and file list; the three-dot diff origin/main...356172a1f9 (41 files, +264/-544) fetched by explicit sha; the head's check-runs. Not the dispatch order, not the seat's conclusions.

① Derived judgments

  • The retirement is complete on the declared surface. The object file and its organization-unique pin test are deleted; NotificationSubscription leaves src/index.ts, src/objects/index.ts and the i18n extract config; messaging-service-plugin.ts drops it from the import, the manifest objects, the Configuration-group nav (nav_notification_subscriptions) and the boot-provisioning list; the seven generated bundles lose exactly the sys_notification_subscription keys (zh-CN source hashes lose 2, es/ja lose 12, consistent with zh-CN being the authoring locale for the other keys); PLATFORM_OBJECTS_BY_PACKAGE['service-messaging'] shrinks to six names with a bidirectional pin (isPlatformProvidedObjectName false, hasPlatformObjectPrefix true, preference as control); the object.zod.ts doc comment and the tenancy census (85 to 84) follow. Residual mentions at the head outside tests, generated files and docs are exactly four, all intentional: the plugin comment, the CLI inventory row and its header, and the registry rationale fragment. content/docs names the object nowhere outside releases/ (read-only). objectui at the pinned console sha cf153eb2e5 (the pin main carries) has zero hits for the symbol, the object name and the nav id, against two files for the control word, so the Console Pin Gate is not put at risk.
  • ADR-0087 entry shape is the precedents' shape, correctly. 18.sys-view-definition-retired.ts and 18.scim-provider-object-retired.ts are both platform-object retirements with no RETIRED_KEYS_BY_MAJOR entry and no D2 conversion, because no authorable spec key moves and nothing in authored metadata is rewritten. This entry copies that reasoning in its header comment and its four fields; surface carries no backticks (the upgrade-guide renderer constraint the view-definition entry documents); acceptanceCriteria contains the phrase left in place untouched, which the new CLI unit pin reads as the licence for fate 4. "No D2 / no retired-key entry" is right for an object retirement: the only conversion an object retirement could carry would rewrite a stack that names the object, and the entry (like both precedents) tells the author to remove the reference instead.
  • Table left in place matches ruling D's 裁后执行 ("ADR-0087 语义条目退役对象(表保留)") and the two precedents; the changeset and entry both say no backfill, no reaper, no migrate command, and name the operator's by-hand DROP.
  • CLI inventory fate-4 row. Census P1_ONLY (platform-census-2026-08-31) is right: the first census counted the object, so retired-before-census would be false; the dev correctly did not reuse the sys_scim_provider population and instead added RETIRED_AFTER_FIRST_CENSUS with the same premises (counted, no longer registered, entry exists, citation carries no tracker number, notNullExempt present). The integration pin builds the table in its last declared shape, plans under isolated and single, asserts the NULL row is reported and never attributed (notNull.willReceive false) and carries a control leg (without the row the plan refuses uninventoried-platform-table). The inventoryDigest change and the plan-digest-mismatch consequence are stated in the changeset.
  • ADR-0030 amendment (Tier H) is in the same form as the 2026-09-20 amendment: a dated status-line entry naming the ruling and record, the L3 row revised to preference-only with a strike-through of the expansion, and the remaining-work bullet struck and marked Withdrawn. It makes the PR Tier H; it stays a draft with no approving review on the head as of this reading, which is the correct state.
  • Zero-reader measurement is adequate for this repo and the console; cloud is honestly NOT MEASURED. The PR re-measured on e4d604efd3 with a control word (preference: 8 files, 2 readers), named each of the 6 hits and classified none as a row reader; the deleted object's own docblock said the expansion was not wired. The decision record 6290342354 already listed cloud as a 置信缺口 and the maintainer ruled D with that gap known, so the gap is not new to this PR. The entry's reason claims this repo and the Studio repo only. Acceptable.
  • Leaving the dogfood fixtures unchanged is correct. fixture-18.0 and fixture-18.0.0 are dumps of databases written by released code; manifest.json records sys_notification_subscription: 0 in the counts, and the table is not among RESHAPED_TABLES, so no harness assertion reads it. A database an earlier release provisioned holding the table with no reader is precisely the state the entry describes. Both precedents predate the fixtures, so there was no fixture edit to mirror.
  • Public export removed that a consumer imports: NotificationSubscription from @objectstack/service-messaging (root and objects barrel) and the name in PLATFORM_OBJECTS_BY_PACKAGE. No in-repo importer remains; objectui at the pin has none; the changeset's FROM to TO names the import removal and the metadata-reference removal.

② Semver level

③ Boundary flags

  • Check-runs on the head: all 42 completed: 35 success and 7 skipped by filter, 0 red. That includes Test Core 1/6 through 6/6, so the platform-objects suite is green in CI, and Type Check · workspace.
  • Platform-objects tests NOT MEASURED locally (verify-lock timeouts); the diff there is seven deleted lines across four hand-written dictionaries and three hash maps, removed symmetrically, so app-nav-translation-parity.test.ts should hold. Must be green in CI Test Core.
  • Deviation 1, platform-objects translations outside the claimed surface: the claim listed service-messaging's generated translations, not packages/platform-objects/src/apps/translations/*. The removed label belongs to the nav item this PR deletes, no other open claim holds those files, the PR body declares it, and check:i18n ran green. Accepted as the same retirement's translation half.
  • Deviation 2, dogfood fixtures unchanged: the dispatch premise ("update the fixtures as the precedents did") was false, as the dev shows; the harness upgrade-18x-harness.dogfood.test.ts was run locally (12 passed) and the CI job for it is opt-in and skipped on this head, so that evidence is local only. The pin at line 335 ("HEAD's schema plan holds no destructive change") is the one that could have bitten; the precedent's measured behaviour (schema sync is additive, reconciles declared objects only) and the local run both say it does not.
  • registry.ts.template overlap with feat(spec)!: addRecord says what renders — enabled required, position top, mode/formView retired #24970: feat(spec)!: addRecord says what renders — enabled required, position top, mode/formView retired #24970 merged at fde7456352, which is an ancestor of this head (the head is a merge of origin/main). git merge-tree origin/main 356172a1f9 against current main 73432299c0 is clean, and the API reports mergeable. The new fragment sits at its sorted id position with order: 106; the template already carries two other order: 106 fragments, and no test or build script asserts unique orders, so the equal order is rendered in id order as the dev states.
  • Tier H: the ADR-0030 edit keeps the whole PR Tier H. This record is the CONTRACT_REVIEW_TIER half; landing still needs the maintainer's approving review, and no seat arms or queues it.
  • Cloud readers NOT MEASURED (carried from the decision record's own 置信缺口); the entry scopes its census claim to this repo and the Studio repo.
  • Historical mentions (superseded ADR-0012 draft, ADR-0103, docs/design/, docs/handoff/) are left as written; execution card ② (templates narrow) on Notification templates and subscriptions authored in Setup have no effect on the app's notifications #24770 is untouched, as the card intends.

Implemented-by: claude/issue-24923-retire-notification-subscription
Reviewed-by: session_01D6F9axfoPzR7t8G1J7Ytzz

VERDICT: PASS

@os-zhuang
os-zhuang added this pull request to the merge queue Oct 21, 2026
Merged via the queue into main with commit 63085c3 Oct 21, 2026
43 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-24923-retire-notification-subscription branch October 21, 2026 05:06
os-steve pushed a commit that referenced this pull request Oct 21, 2026
… into 7b-0

One conflict: service-messaging notification-display-title.test.ts keeps
main's removal of the retired NotificationSubscription import and this
branch's Default Organization testkit import.

Claude-Session: https://claude.ai/code/session_018EszgtA3V2KXBX5tf59Hi4
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

service-messaging: retire sys_notification_subscription (no runtime reader), by the maintainer's ruling D on #24770

3 participants