Skip to content

fix(service-messaging): Notification Preferences says which topics and channels can be entered - #25449

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-8hqiya
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-8hqiya

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Fixes #25346

Clause-②: no
Metadata-protocol: none

Claim of record: 6365308855 on the card (card thread, domain:services seat 1). Ruling of record: 6365616409 (letter A, Sales, on the needs_decision in os-dev-report 6365345478). Branch claude/project-thread-8hqiya is the harness-pinned branch for this session. It names no issue, so the Duplicate Fix Guard's branch-name hint will not match, and the claim is the identity record.

What changes

Setup → Notification Preferences showed Topic and Channel as bare text boxes. Their help only said "Notification topic" and "Channel id (inbox/email/push/…)", and no push channel exists. The card asked for pickers wherever the platform can enumerate the values. The measurement in the report found that neither field can be a picker without a contract change:

  • Topics. A topic is whatever string the sender writes, and nothing registers topics.
  • Channels. Channels are registered at runtime through an open seam: MessagingChannel is exported, and registerChannel is public. A select does not fit:
    • it is closed on the write path;
    • its option values are SystemIdentifierSchema, so the stored * wildcard cannot be an option.

Ruling A keeps both fields as text and makes the help say what to type. packages/services/service-messaging/src/objects/notification-preference.object.ts:

  • Channel help: inbox (always available), email (once an email service is configured), sms (once an SMS service is configured), or the id of a channel an installed plugin adds. Enter * for all channels. An id no channel uses matches nothing. push is gone.
  • Topic help: the topic a flow's notify step names, or one the platform sends, such as approval.requested or collab.mention. Enter * for all topics. A topic nothing sends matches nothing.

The type, default (*), maxLength, index and resolver are unchanged. No stored row changes meaning and no write is newly refused.

The comments beside both declarations record why the field is not a picker.

Translation bundles:

  • en was regenerated by node scripts/check-i18n-bundles.mjs --write.
  • zh-CN, es-ES and ja-JP were translated by hand. The es-ES and ja-JP leaves were byte copies of the old English source, which check:i18n-stale-fill reds on. Re-translating them dropped their two entries from each *.source-hashes.generated.ts, as the tool writes it.

Pins

New file packages/services/service-messaging/src/objects/notification-preference-topic-channel-help.test.ts, 3 cases:

  • Both fields stay free text. Each is text with default *, and its help says Enter * for all.
  • The channel help names exactly the built-in channels. The pin compares it with the ids read from the three channel factories this package builds (createInboxChannel, createEmailChannel, createSmsChannel). So a channel added or renamed here fails until the help says so. The help names none of the spec vocabulary's ids that no channel implements (push, webhook, slack, teams, in-app), and it says a plugin's own channel is valid.
  • The topic help says where a topic comes from. It names the notify step and an example topic.

Ablation leg: the object file was restored from f0fdd45b (base) and the pin file run: 3 failed. HEAD: 3 passed. The file was restored with git checkout HEAD --, and git diff --quiet HEAD passed. No dist/ leg is needed, because the pin imports source.

Tests and gates

Read on 2d1b522b. Then main was merged in, giving head ad7975f4. None of the 7 incoming commits touches a service-messaging path, so nothing was re-run.

  • Package tests: pnpm --filter @objectstack/service-messaging test: Test Files 61 passed (61), Tests 729 passed (729).
  • Type check and lint: pnpm --filter @objectstack/service-messaging typecheck: exit 0. eslint on the two changed source files: exit 0.
  • Derived gates: node scripts/pm/dispatch-gates.mjs --commands derived 68 commands, and all 68 exit 0. Three needed a prerequisite first:
    • check-plugin-teardown-shape --self-test ran after its pinned fixture commit was fetched;
    • check:dual-build-cjs-loads ran after the 8 dists it named were built;
    • check:i18n-stale-fill ran after the es-ES and ja-JP re-translation.
  • --ran reconcile: 68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN.
  • Not run locally, declared to CI: the type-check lanes and the path-scheduled CI jobs. No dogfood battery is owed: there is no ! in the title and no (narrowing) arm.

Acceptance notes

  • The card's "Done when" asks for pickers wherever the platform can enumerate the values. Per the ruling, neither field gets one. A real dropdown over an open vocabulary would need a spec-lane suggestion list on text fields (the report's option C). That is not part of this card and is not filed by it.
  • The help also says a plugin's own channel id is accepted, and that a value nothing uses matches nothing. Both describe today's behaviour, within ruling A.

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01GsN1y3qWPdkVLasbhDB2Fy

@objectstack-fleet
objectstack-fleet Bot requested a review from os-sales October 23, 2026 13:54
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

4 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 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 — 5 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 d0a1361bdabc153b2fb927b8f7ef6f024ecc6891 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from d4e7fe7c735011ed9e2449da7d997705a1037326 — the merge of head ad7975f4e55cdee5f29f38b0fd172556ee765587 into base d0a1361bdabc153b2fb927b8f7ef6f024ecc6891, 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 d4e7fe7c735011ed9e2449da7d997705a1037326 && git checkout d4e7fe7c735011ed9e2449da7d997705a1037326
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d0a1361bdabc153b2fb927b8f7ef6f024ecc6891 ad7975f4e55cdee5f29f38b0fd172556ee765587 && git checkout -B drift-repro d0a1361bdabc153b2fb927b8f7ef6f024ecc6891 && git merge --no-ff ad7975f4e55cdee5f29f38b0fd172556ee765587

node scripts/docs-audit/affected-docs.mjs --json d0a1361bdabc153b2fb927b8f7ef6f024ecc6891

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

os-dev-report

{
  "issue": 25346,
  "status": "done",
  "branch": "claude/project-thread-8hqiya",
  "pr": "https://github.com/objectstack-ai/objectstack/pull/25449",
  "session": "session_01GsN1y3qWPdkVLasbhDB2Fy",
  "premise_still_valid": true,
  "summary": "Ruling A (6365616409, Sales) on the needs_decision in 6365345478. topic and channel on sys_notification_preference stay text with default '*'. Their help now names where values come from: channel lists inbox (always), email and sms (once their service is configured) or a plugin's own channel id, and drops the unimplemented push; topic names a flow's notify step or a platform topic such as approval.requested. Both say '*' means all and that an unused value matches nothing. Bundles: en regenerated; zh-CN, es-ES and ja-JP translated by hand (es-ES and ja-JP were stale byte copies, which check:i18n-stale-fill reds on). One patch changeset.",
  "tests": "New pin file notification-preference-topic-channel-help.test.ts, 3 cases. Ablation: the object file restored from base f0fdd45b gives 3 failed, HEAD gives 3 passed; restore proven with git diff --quiet HEAD. Package suite pnpm --filter @objectstack/service-messaging test: Test Files 61 passed (61), Tests 729 passed (729). typecheck exit 0; eslint on the two changed source files exit 0.",
  "gates": "dispatch-gates --commands derived 68, all 68 exit 0. Prerequisites first: check-plugin-teardown-shape --self-test after fetching its pinned fixture; check:dual-build-cjs-loads after building the 8 dists it named; check:i18n-stale-fill after the es-ES and ja-JP re-translation. --ran: 68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN. Read on 2d1b522b; main then merged in, giving head ad7975f4 (the 7 incoming commits touch no service-messaging path). CI on the PR is pending at report time.",
  "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
  "mcp_calls": "7 — read tools only: issue_read get, issue_read get_comments x4, search_pull_requests, pull_request_read get",
  "api_writes": "6 — all via the fleet-write relay: label-write (assign, pm:queue to pm:dispatched), post-stamped x3 (claim, needs_decision report, ruling record), pr_create (draft, assignee os-sales), pr_request_reviewers (os-sales)",
  "open_questions": [],
  "out_of_scope_findings": []
}

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · 2026-10-23T14:19Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25346 (M, card thread; claim 6365308855, ruling A 6365616409) · reviewed head ad7975f4e5

  • Diff, read file by file against the claim's surface: 9 files, +124 / −14.
    • notification-preference.object.ts: both fields stay text with default *. Only their description changes. The type, default, maxLength, index and resolver are untouched, which is ruling A's letter (help text only, no picker). The comments beside both declarations record why neither field can be a select.
    • The channel help names inbox, email and sms and drops push, which no channel implements. The topic help's examples are real senders on main: approval.requested (plugin-approvals/src/approval-service.ts:3969) and collab.mention (plugin-audit/src/audit-writers.ts:2513).
    • Bundles: en is regenerated by the tool. zh-CN, es-ES and ja-JP are hand-translated. The two *.source-hashes.generated.ts drops are what the tool writes once a leaf is no longer a byte copy of the English source. I read the zh-CN leaves, and they say the same as the English.
    • The changeset is 25346-preference-topic-channel-help.md.
  • Pins (report): a new 3-case pin file. Its channel case reads the ids of the three channel factories this package builds, so a channel added or renamed here reds until the help catches up. Ablation from base: 3 red. HEAD: 3 green, and the restore is clean. service-messaging 61 files / 729 tests pass. dispatch-gates --ran: 68 derived, 68 run, 0 unrun.
  • CI on this head: 34 success and 7 expected skips, none failed. git merge-tree onto today's main is clean.
  • Body: the attribution header and the os-sales reviewer are present. The closing footer is present.
  • Declarations: Clause-②: no is right. No accepted value, default or column changes. Metadata-protocol: none. No (narrowing) arm, so no contract-review record or dogfood battery is owed.
  • Acceptance note: the card's "pickers wherever the platform can enumerate" is answered by the ruling, not by a picker. A dropdown over an open vocabulary would need a spec-lane suggestion list on text fields, which this card does not file.
  • Commit trailers: model-free Co-authored-by: Claude + Claude-Session.
  • Landing: not governed (check-governed-merges --pr 25449). Ready + auto-merge through the relay. Fixes #25346 closes the card on merge.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants