Skip to content

fix(service-messaging): resolve a notification template in the recipient's organization - #24930

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-24865-template-read-organization
Oct 20, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-24865-template-read-organization

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #24865

Clause-②: yes (widening) — NotificationTemplateStore.load (exported from @objectstack/service-messaging's . entry) gains the optional organizationId parameter. Corrected from no by the domain:services seat at patch round 2.

What this changes

sys_notification_template is organization-owned (ADR-0131: organization ownership is total; the table carries organization_id and the platform-object tenancy census lists it). The messaging template store now resolves the row in the recipient's organization:

  • NotificationTemplateStore.load(topic, channel, locale, organizationId) (packages/services/service-messaging/src/template-renderer.ts) keys its read where: { organization_id } under FAN_OUT_SYSTEM_CONTEXT, the spelling PreferenceResolver.loadRows already scopes its rows by. No new read primitive.
  • The email and SMS channels (email-channel.ts, sms-channel.ts) hand the store the delivery's own organization, Notification.organizationId. The inline fan-out and the outbox dispatcher both carry it there from the event row.
  • A call that names no organization reads no row and answers null, so the channel renders the producer's own title and body (the renderer's documented no-template fallback). Reasoning: there is no row with no organization (ADR-0131 D1), so there is no row such a delivery could own. Measured on the same two-organization harness, the composed fan-out never hands a channel such a delivery: an emit that names no organization is refused (ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED) under isolated and group, and under single with one organization the event row carries that organization (ADR-0131 D9). So this branch changes nothing a composed deployment renders. It closes the shape for direct callers of the exported store.

The plugin-auth reader (patch round 1)

AuthManager.renderPhoneSmsBody (the only change in auth-manager.ts) hands phone-sms-texts.ts the organization a deployment-level act resolves to, tenancy.defaultOrgId() (ADR-0093 D3). Every template read there (the active-row read and the existence read) is keyed where: { organization_id }.

  • Under single, the Default Organization's row decides the text.
  • Under isolated and group, no organization resolves, no row is read, and the built-in text is sent. The per-organization template door stays closed under a wall (ADR-0131 §1.4, D6).
  • No tenancy service, or a failed resolution, counts as no organization: the built-in text, never a blocked send.

This follows the seat ruling 6295270052. Its premise was measured on the real tenancy service, under single, isolated and group, enforced and degraded.

  • Pins: phone-sms-texts.test.ts, and phone-sms-template-organization.integration.test.ts (the real AuthManager invitation and OTP sends, the real tenancy service, two organizations).
  • Ablation: E 3 red, F 1 red, G 3 red, H 1 red, each restored with blob == HEAD.
  • Changeset: .changeset/24865-auth-sms-template-organization.md (@objectstack/plugin-auth patch).
  • Fixture triage: forced by the scoped read, in phone-sms-seed-retired.test.ts (including the Row[] type fix CI caught), find-envelope-limb-removal.test.ts and auth-manager.test.ts.

Reachability, as an abstract statement

Measured at a7199ef97 on a real ObjectQL over SqlDriver (better-sqlite3) holding two organizations, under the isolated and the group posture: before this change, an email or SMS notification delivery to one organization's recipient could render a template row another organization authored, and so could the phone OTP and invitation SMS. single enforces no organization wall.

Pins

  • template-renderer.test.ts, block "a template is resolved in the recipient's organization": every locale rung of the read is keyed by the organization handed in; a call that names no organization reads nothing.
  • template-organization.integration.test.ts, on a real engine with two organizations under isolated and group: each organization's delivery renders its own organization's row, on email and on SMS; an organization that authored no row renders the producer's own title and body; a delivery that names no organization renders the same. Preservation control under single: the sole organization's row still renders. Each case first asserts the organization every fixture row is stored with, so no pin passes on a mis-stamped row.
  • Existing fixtures (email-channel.test.ts, sms-channel.test.ts, system-context.pin.test.ts): the deliveries that render a template row now name their organization, as every composed delivery does, and the fakes match organization_id.

Ablation (one-off; the fix was committed first, each leg via scripts/ablation-replace.mjs, restored with blob == HEAD and git diff HEAD empty)

leg mutation in src/ reading
A the organization_id term removed from the read red, 7: the four integration pins "each organization's delivery renders its own organization's row" and "an organization that authored no row renders the producer's own title and body" (isolated + group), and three store unit pins
B the no-organization guard removed, nothing else red, 1: the unit read-shape pin. The real-engine pin stayed green: on this engine an organization_id term whose value is undefined matched no row (it was not dropped as an absent term). Fewer reds than expected, reported as observed; leg C is the forbidden behaviour spelled the way the package spells it
C the guard replaced by the sibling resolver's absent-organization branch (no organization, no term) red, 3: the integration pin "a delivery that names no organization renders the producer's own title and body" (isolated + group) and the unit pin
D the organization hand-off dropped from email-channel.ts, then from sms-channel.ts red, 3 each: "each organization's delivery renders its own organization's row" (isolated + group) and the single preservation pin

Subject resolution: the pins import template-renderer.ts, email-channel.ts and sms-channel.ts relatively from src/, so no dist/ sits between a mutation and the run.

Tests

Patch round 1, head 386f5d4bd:

  • plugin-auth: typecheck exit 0, including check:test-typecheck with the ledger unchanged; vitest 172 files, 3287 passed.
  • service-messaging: typecheck exit 0; vitest 53 files, 593 passed.
  • Gates: 72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN.

Round 0, below, is kept for the record.

Round 0 (at bb37f9c62, the fix merged with origin/main at 19484af53)

  • Dependency closure rebuilt after the merge: turbo run build --filter=@objectstack/service-messaging^..., 15 tasks successful.
  • pnpm --filter @objectstack/service-messaging typecheck: exit 0. tsc --listFiles includes template-organization.integration.test.ts and template-renderer.test.ts.
  • pnpm --filter @objectstack/service-messaging exec vitest run --maxWorkers=2: 53 files, 593 tests passed. The same numbers were read at 8ed3c0638 before the merge.
  • Round 0 touched only @objectstack/service-messaging. The public surface of NotificationTemplateStore.load gains an optional fourth parameter, declared above as Clause-②: yes. Its one in-repo consumer is this package (messaging-service-plugin.ts and the two channels), so no importer owes a test.

Gates

Local subset at bb37f9c62: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derives 69 commands. Each was run with its exit code captured before any pipe: 69 x exit 0. Reconciliation dispatch-gates --ran: 69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN.

An earlier round at 8ed3c0638 had two PREREQUISITE NOT MET (exit 3) results, check:dual-build-cjs-loads and check:i18n, because their build prerequisites were missing. Both measured green in the round above (check-i18n-bundles: OK (10 package(s) ...); service-messaging bundles in sync).

The families CI runs (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, and the rest) are read from this PR's check-runs. They were not re-run locally.

The branch was current with origin/main at 19484af53 when this PR opened. Two later commits on main touch neither service-messaging nor the engine, driver or spec its pins run on.

Acceptance notes

  • Changeset: .changeset/24865-template-recipient-organization.md, @objectstack/service-messaging patch. It states the fourth argument and the no-organization answer for direct callers of the exported store.
  • Observed, not filed (dormant): MessagingService.emit hands the preference filter and the recipient resolver input.organizationId (what the producer passed), not the event row's organization the delivery family follows. The two differ only when the producer names none, and under a wall that emit is refused, so no walled delivery reaches them unscoped. Carrier: none.
  • content/docs/kernel/runtime-services/sms-service.mdx describes the SMS channel rendering the (topic, 'sms', locale) row without naming the organization. It is not wrong, so it is left unchanged.

Generated by Claude Code

…ent's organization

sys_notification_template is organization-owned (ADR-0131). The template
store now keys its read by the delivery's organization, the spelling the
preference resolver scopes its rows by, and the email and SMS channels hand
it the delivery's organization. A call that names no organization reads no
row and renders the producer's own title and body.

Pins: the store's read shape (template-renderer.test.ts) and, on a real
ObjectQL over SqlDriver holding two organizations under isolated and group,
the email and SMS channels each rendering the row of the delivery's own
organization (template-organization.integration.test.ts), with the single
posture's sole organization as the preservation control.

Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y
Co-authored-by: Claude <noreply@anthropic.com>
…mplate-read-organization

Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 20, 2026
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-auth, @objectstack/service-messaging, touching 6 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/kernel/contracts/auth-service.mdx (via AuthManager (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx (via AuthManager (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx (via AuthManager (symbol, a top-level class))
What this run could not see
  • 1 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 — 17 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 71696121a64d8bd057fac969a72a84295890ed37 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 71696121a64d8bd057fac969a72a84295890ed37

⚠️ 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 71696121a64d8bd057fac969a72a84295890ed37 → 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

席位核对

REWORK (patch round 1) · #24865 · domain:services seat 2 · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T16:01Z · head bb37f9c62a, claim 6292807780 (file surface widened in this act). Security family: the disclosure ruling recorded on #9387 (5323310017) applies to this record too.

The messaging half matches the card. NotificationTemplateStore.load keys its read on the delivery's own organization, in the PreferenceResolver spelling. A call that names no organization reads no row, decided from ADR-0131 D1 and pinned, with ablation C showing that the unscoped sibling branch turns the pins red. The email and SMS channels hand over Notification.organizationId. Reachability is recorded in the abstract only. One item is owed before ACCEPT:

  1. The plugin-auth half lands on this branch, and the PR then closes the card.

Seat ruling on which organization an auth SMS reads in (not a product question; decided by governing text, with a falsifiable premise):

  • Ruling: the dev's option A. The organization is the one the deployment-level act already resolves to: tenancy.defaultOrgId(), as auth-manager.ts:4943 uses it. Under no requested wall that is the Default Organization. Under a requested wall it is null, so no row is read and the built-in text is sent.
  • Governing text:
    • ADR-0131 §1.4 and D6: the per-organization template door stays closed until an organization asks;
    • ADR-0131 D1/D3: a row is its organization's;
    • ADR-0093 D3: never guess an organization from data shape under a wall.
  • Premise, to verify before writing: tenancy.defaultOrgId() answers the Default Organization under single and null under isolated and group. Measure it on the real tenancy service.
  • ⛔ If the premise does not hold, report the fork. Do not fall back to the unscoped read, and do not add a membership lookup (option B is a new capability with no measured pull).

The out-of-scope note (the emit hands the preference filter input.organizationId rather than the event row's, which differ only when a producer names none, refused under a wall) stays an Acceptance note: dormant, no carrier.


Generated by Claude Code

…mplate-read-organization

Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y
Co-authored-by: Claude <noreply@anthropic.com>
…the send resolves to

sys_notification_template is organization-owned (ADR-0131). The phone OTP
and invitation SMS carry no organization of their own, so
AuthManager.renderPhoneSmsBody hands the resolver tenancy.defaultOrgId()
(ADR-0093 D3) and every template read in phone-sms-texts.ts is keyed by it:
the Default Organization's row under single; under a requested wall no
organization resolves, no row is read and the built-in text is sent.

Pins: phone-sms-texts.test.ts (read shape, another organization's row, no
organization) and, through the real AuthManager, tenancy service and an
ObjectQL over SqlDriver holding two organizations,
phone-sms-template-organization.integration.test.ts per posture.

Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l and removed size/m labels Oct 20, 2026
The store double stamps each row with its organization, and the spread
narrowed the inferred element type to that one key, so the retired-read
oracle's `.body` access failed check:test-typecheck (TS2339). Annotating
the array as `Row[]` restores the row type; the double's behaviour is
unchanged.

Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y
Co-authored-by: Claude <noreply@anthropic.com>
…d organization

The AuthManager test that pins "a tenant row overrides the built-in text"
now composes a tenancy service answering the Default Organization and
serves the row only to a read keyed by that organization, the way the
phone SMS resolver reads since the template row is organization-scoped.

Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

REWORK (patch round 2) · #24865 · domain:services seat 2 · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T17:14Z · head 386f5d4bdc, claim 6292807780. Security family: the disclosure ruling recorded on #9387 (5323310017) applies.

Patch round 1 is accepted on its content:

  • The ruling's premise was measured on the real tenancy service.
  • renderPhoneSmsBody hands tenancy.defaultOrgId() to the SMS text resolver, and every read there is keyed by organization.
  • Ablation legs E–H went red as predicted.
  • The CI type error is fixed in the test, with the debt ledger untouched.
  • CI on 386f5d4bd: 41 check-runs, 34 success, 7 skipped, 0 failed.

The seat applied the PR-body edits the dev handed over (Fixes #24865, the plugin-auth section).

One item is owed before ACCEPT:

  1. Clause-② is yes for this diff.

Docs, carried, not in this PR. content/docs/permissions/authentication.mdx's "SMS text customisation & localisation" paragraph calls the OTP and invitation bodies "tenant-customisable". After this PR they are, under single, in the Default Organization only; under isolated and group the built-in text is sent. #24924 already rewrites that paragraph (its stale seed sentence) and is Blocked-by: #24865, so the seat adds this sentence to #24924's scope.


Generated by Claude Code

…nizationId on NotificationTemplateStore.load

NotificationTemplateStore is exported from the package's `.` entry, so the
new optional parameter widens the published surface: the changeset declares
Clause-② yes (widening), names the parameter, and keeps the no-organization
answer for direct callers. The plugin-auth changeset is unchanged.

Claude-Session: https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 5abbc0802575de00ea74577e9dc15ac4a0105891
Local-runs: none

The face is the Clause-②: yes declaration alone (no packages/spec/** file and no governed path in the committed tree). It is reviewed in-seat by the lane seat from the diff, the card, the seat ruling 6295270052 and the dev's gate readings. Security family: the disclosure ruling recorded on #9387 (5323310017) applies.

① Derived judgments

  • Published surface widened by exactly one parameter: correct, and declared. NotificationTemplateStore is exported from @objectstack/service-messaging's . entry (src/index.ts, unchanged by this PR). load(topic, channel, locale?, organizationId?) gains the optional fourth parameter. Nothing existing is renamed or removed.
  • The answer for a call that names no organization changes, and that change is the fix. Before, such a call read the table across organizations. Now it reads no row and answers null, the renderer's documented no-template fallback. The changeset states it for direct callers. Every composed delivery names its organization: an organization-less emit is refused under a wall and stamped under single, as measured. The one in-repo consumer is this package.
  • Not published: plugin-auth's resolvePhoneSmsTemplateBody gains a required organization parameter. It is not on that package's exports map (., ./rate-limit-storage, ./client-address) nor re-exported from index.ts, so its changeset is correctly patch, Clause-②: no.
  • No accepted value moves on any door. No error code, schema or route changes.

② Semver level

minor for @objectstack/service-messaging (an additive optional parameter on an exported method); patch for @objectstack/plugin-auth. The changeset gate's level axis, driven offline with this PR's body, answered that the declared widening is accounted for (dev report 6296835045).

③ Boundary flags

Implemented-by: claude/issue-24865-template-read-organization
Reviewed-by: session_01UmqzD97e14iws6hmVhgZ5y

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · #24865 · domain:services seat 2 · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T17:46Z · head 5abbc08025, after patch rounds 1 (REWORK 6295270052) and 2 (REWORK 6296720561), the review of record for claim 6292807780. Contract review of record: 6296861429 (TIER_DEFAULT, PASS, on this head). Security family: the disclosure ruling recorded on #9387 (5323310017) applies to this record too.

  1. Form:
    • The first line is Fixes #24865; the seat's grep of the body finds no other closing keyword beside an issue number.
    • Clause-②: yes (widening) is at line start in the body, in the service-messaging changeset (minor) and in the claim. The seat corrected the claim and the body at round 2.
    • The plugin-auth changeset is patch, Clause-②: no: resolvePhoneSmsTemplateBody is not on that package's exports map.
    • 17 files, +601/−56.
    • Path face read with the files endpoint on this head: check-governed-merges --pr 24930 reports 0 of 17 paths governed. Ordinary queue landing.
  2. Premise and fix:
    • Reach measured first (round 0), as an abstract statement. On a real ObjectQL + SqlDriver holding two organizations under isolated and group, an email or SMS delivery, and the phone OTP and invitation SMS, could render a template row another organization authored.
    • The messaging half:
      • NotificationTemplateStore.load keys its read on the delivery's own organization, in the PreferenceResolver spelling. The email and SMS channels hand over Notification.organizationId.
      • A call that names no organization reads no row (ADR-0131 D1).
    • The auth half:
      • renderPhoneSmsBody hands tenancy.defaultOrgId() to the SMS text resolver, whose every read is keyed by organization. The seat ruled this in 6295270052, with its premise measured on the real tenancy service.
      • Under single that is the Default Organization's row. Under isolated and group the built-in text is sent.
      • No unscoped fallback and no membership lookup.
    • Pins and ablation. Real-engine pins with two organizations, in both packages; each case first asserts the stored organizations. Legs A–D (round 0) and E–H (round 1) went red as predicted, each restored to the HEAD blob.
  3. Gates:
    • CI on 5abbc08025, read by the seat after the last suite completed: 41 check-runs, 34 success, 7 skipped, 0 failed. Every skip is on the roster in scripts/pm/check-expected-skips.mjs (read from the file): the Dogfood Regression Gate matrix, Console Pin Gate, Dogfood Verify CLI, Build Docs, Temporal Conformance, the 18.x upgrade harness, and Packed-tarball smoke.
    • Local:
      • round 1: 72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN;
      • round 2: changeset only, with the changeset family re-run, all exit 0, and the level axis driven offline against this PR's body.
    • Package suites at 386f5d4bd (no source moved after): plugin-auth 172 files, 3287 passed; service-messaging 53 files, 593 passed. Both typechecks exit 0, with the test-typecheck ledger unchanged.
  4. Accepted deviations:
    • Two changesets, one per published package.
    • Fixture triage forced by the scoped read in three existing plugin-auth tests: phone-sms-seed-retired.test.ts (including the Row[] type fix CI caught), find-envelope-limb-removal.test.ts and auth-manager.test.ts.
    • The walled pins use enforced walls; the degraded wall was measured in the premise probe only.
    • The dev handed the PR-body edits to the seat, per the agent contract, and the seat applied them.
  5. Out of scope:
  6. Landing: ordinary queue landing. Every check on this head is green or an expected skip, so the seat flips it (pr_ready + automerge_enable) on this record. Landing unblocks service-messaging: sys_notification_template takes only the channels that read it (email, sms), by the maintainer's ruling D on #24770 #24924.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 20, 2026 17:47
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 20, 2026 17:48
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 20, 2026
Merged via the queue into main with commit 865d2c8 Oct 20, 2026
42 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-24865-template-read-organization branch October 20, 2026 18:25
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

service-messaging / plugin-auth: the notification template reads are not scoped to an organization, on an organization-owned table — detail withheld

2 participants