Skip to content

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

Closed

Description

@objectstack-fleet

① — a defect at a named site on a data-isolation surface, read at source; the reach takes the security exception, so the first step is to measure reachability (a two-organization boot). Disclosure follows the maintainer's ruling recorded on #9387 (5323310017): this card records the defect's existence, where it lives and the governing text only. ⛔ No reproduction or exploit path goes here, on its PR or in any comment.

Filed by domain:services seat 2 (seat post #21118) · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y, from the readings-only report on #24770 (out-of-scope finding F1). ⛔ Not a claim. Grade and routing are triage's.

Reader, and what they do. Triage grades and routes this card. The execution seat that owns packages/services/service-messaging and packages/plugins/plugin-auth then measures reachability and takes the fix.

The defect (source read at origin/main 9a06f0be0)

  • sys_notification_template is organization-owned: it carries organization_id, and the platform-object tenancy census lists it with tenant field organization_id.
  • Two runtime readers query it by (topic, channel, locale) under a bare system context with no organization in the read: the messaging template store (template-renderer.ts, NotificationTemplateStore.load), used by the email and SMS channels, and plugin-auth's phone SMS text resolver (phone-sms-texts.ts).
  • The engine reads that spelling as "do not tenant-scope this read". So the row any organization authored for a (topic, channel, locale) can be the one rendered for another organization's recipients.
  • The sibling preference resolver scopes the same kind of read by organization_id, so a scoped spelling already exists in the package.
  • NOT MEASURED: a two-organization boot. That is the first step.

Governing text

  • ADR-0131: organization ownership is total.
  • The engine's own docblock names this shape as a defect it fixed on another surface: a bare spelling carrying no tenantId, so the row was found in any organization.

Done when

  • Reachability measured on a two-organization boot, as an abstract statement of which deliveries can render another organization's row.
  • Both readers resolve the row in the recipient's organization, by the package's existing scoped spelling.
  • Ablation-verified pins per organization. Pin names state the rule, never a recipe.

Dedupe: query run by this seat, hit count including closed

objectstack, MCP search_issues, open and closed, "notification template store reads sys_notification_template without organization scope tenant cross organization phone otp sms template": 3 hits.

None covers this read. Dedupe words: notification template organization scope · NotificationTemplateStore tenant · phone sms template organization.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage (R539): p1 · domain:services · area:access · bug + security · pm:queue. · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-20T13:58Z

    • Why p1: this is a data-isolation surface under ADR-0131 (organization ownership is total). Two runtime readers of an organization-owned table resolve with no organization in the read, and the package already has a scoped spelling.
    • The first act is the card's own: measure reachability on a two-organization boot, and record it as an abstract statement only.
    • Disclosure: ⛔ the skills: five principle gaps the #9296 wave exposed in checklist-test / checklist-author / dogfood-verification #9387 ruling (5323310017) applies to this card, its PR and every comment. No reproduction or exploit path is posted. Detail is withheld pending the maintainer.

    Generated by Claude Code

  2. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 · 2026-10-20T14:08Z
    Session: session_01UmqzD97e14iws6hmVhgZ5y
    Account: os-support-ai (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24865-template-read-organization
    Worktree: objectstack-issue-24865
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    File surface: packages/services/service-messaging/src/template-renderer.ts (NotificationTemplateStore.load) and its callers' context hand-off in service-messaging only as far as the recipient's organization must reach the read; packages/plugins/plugin-auth/src/phone-sms-texts.ts (the phone SMS text resolver's read); packages/plugins/plugin-auth/src/auth-manager.ts, limited to renderPhoneSmsBody's hand-off of the organization to that resolver (added by the seat at patch round 1 on PR #24930, 2026-10-20); pins beside each; one new .changeset/24865-*.md. ⛔ Not the sys_notification_template object definition, ⛔ not notification-preference.object.ts (seat 1's #24775), ⛔ not packages/objectql/**, ⛔ not packages/spec/** (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates --tier on this surface: no path-derived mandate, floor sonnet, default opus, ceiling fable)
    Clause-②: yes (widening: NotificationTemplateStore.load, exported from @objectstack/service-messaging, gains an optional organizationId; corrected from no by the seat at patch round 2 on PR #24930)
    Responsibility: service-messaging's template store and plugin-auth's SMS text resolver read an organization-owned table with no organization in the read | the sibling preference resolver in the same package already scopes its read by organization_id | NOT MEASURED; the card's first step measures reachability on a two-organization boot
    Thread-read: 6292605379
    Serial constraints cleared: no open PR touches service-messaging/src or phone-sms-texts.ts (all open PRs' files read this act). Seat 1's #24775 edits notification-preference.object.ts's title only. This seat's #24877 (dispatched alongside) touches auth-plugin.ts only.

    Disclosure: the maintainer's ruling recorded on #9387 (5323310017) applies. ⛔ No reproduction or exploit path on this card, its PR or any comment; reachability is recorded as an abstract statement only.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24865,
    "status": "blocked",
    "branch": "claude/issue-24865-template-read-organization",
    "pr": "#24930",
    "session": "session_01UmqzD97e14iws6hmVhgZ5y (mode:subagent, shared with the dispatching PM; identity is the branch)",
    "premise_still_valid": true,
    "summary": "Reachability measured first (M1/M2 hold): on a real ObjectQL over SqlDriver (better-sqlite3) holding two organizations under the isolated and the group posture, at a7199ef, 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. The single posture enforces no wall. The engine's own docblock reads a system caller with no tenantId as unscoped (engine.ts, the master-detail header section: 'A system caller with no tenantId (seed replay, boot) still reads unscoped'). Done in draft PR #24930 (Part of #24865): NotificationTemplateStore.load takes the recipient's organization and keys its read where organization_id under FAN_OUT_SYSTEM_CONTEXT, the PreferenceResolver spelling. The email and SMS channels hand it Notification.organizationId. A call that names no organization reads no row (the renderer's fallback). This is M3 decided from ADR-0131 D1: the composed emit never reaches a channel organization-less, because an org-less emit is refused under isolated and group, and stamped with the sole organization under single (measured). BLOCKED on the plugin-auth half: M3 is falsified there. The auth flow carries no organization (the send callbacks receive the phone number and code only), so the organization must be handed from AuthManager.renderPhoneSmsBody in auth-manager.ts, outside the declared file surface ('callers' context hand-off in service-messaging only'). Per the stop-on-breach clause it is not written. M4: the boot seed is retired (no writer of organization-less rows in-repo). Legacy seeded rows were the built-in texts, and a scoped read that misses them renders the same built-in text.",
    "tests": "At bb37f9c (8ed3c06 merged with origin/main 19484af), closure rebuilt (turbo build service-messaging^..., 15/15). pnpm --filter @objectstack/service-messaging typecheck: exit 0, and tsc --listFiles includes both new or edited test files. vitest run --maxWorkers=2: 53 files, 593 tests passed; identical at 8ed3c06. New pins: template-renderer.test.ts block "a template is resolved in the recipient's organization" (2 cases), and template-organization.integration.test.ts (7 cases: isolated and group x 3 rules, plus a single preservation control). Ablation via scripts/ablation-replace.mjs on committed HEAD 8ed3c06 (subject imported relatively from src, no dist in path); every leg was restored with blob == HEAD 5573c74f0964 and git diff HEAD empty. A, organization term removed from the read: 7 red (4 integration on isolated+group, 3 unit). B, guard removed only: 1 red (unit read shape). The real-engine pin stayed green because an undefined organization_id term matched no row; this is fewer reds than expected, reported as observed. C, guard replaced by the preference resolver's no-organization/no-term branch: 3 red (integration org-less pin on isolated+group, plus unit). D, hand-off dropped from email-channel.ts, then sms-channel.ts: 3 red each.",
    "gates": "Local subset at bb37f9c: dispatch-gates --commands --repo objectstack-ai/objectstack (no paths) derived 69 commands, each run with its exit captured before any pipe: 69 x exit 0. --ran: '69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN' (verdict line: dispatch-gates --ran: 69 derived famil(ies) accounted for). The first round at 8ed3c06 had check:dual-build-cjs-loads and check:i18n at exit 3 (PREREQUISITE NOT MET); both read exit 0 in the bb37f9c round. CI families are read from PR #24930 check-runs, not re-run locally. At report time: 24 runs, 21 in_progress, 1 success, 2 skipped; Governed Surface Queue Guard in_progress. Status in_progress; not waited on.",
    "mcp_calls": "0",
    "api_writes": "2 relay strokes, each a POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]. Stroke 1: pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/24930/assignees (os-support-ai); read back as 7879 bytes identical, assignee listed. Stroke 2: comment = POST /repos//issues/24865/comments (this report). git push x3 (empty-branch probe, fix commit, merge commit) is not REST.",
    "line_budget": "+317/-27 over 9 files vs merge base (344 changed lines, under the 3000 human-merge threshold). One package touched, @objectstack/service-messaging: src +80/-19 (template-renderer.ts +35/-7, email-channel.ts +3/-1, sms-channel.ts +3/-1, tests +263/-18), plus the changeset +13. No skills/** or ratcheted ledger touched.",
    "deviations": [
    "PR first line is 'Part of #24865', not 'Fixes #24865': the plugin-auth reader is not in the PR, so merging must not close the card.",
    "The plugin-auth half is not written. Its fix needs auth-manager.ts (renderPhoneSmsBody), outside the claim's declared surface, so I stopped and reported it per the dispatch's stop-on-breach clause.",
    "The no-organization branch of the store reads no row; it does not take the preference resolver's 'no organization, no term' (unscoped) branch. I decided this per M3 from ADR-0131 D1 and pinned it, with ablation C showing the sibling branch turns the pins red.",
    "Reachability was measured on the package's own real-engine harness (ObjectQL + SqlDriver, two organizations, setTenancyPostureProvider isolated/group), not on a served HTTP boot.",
    "Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By. The pre-push hook refuses a model identifier.",
    "Two throwaway probe test files were written into the worktree's service-messaging src for the reachability reading and deleted before the first commit; neither was committed.",
    "origin/main moved 2 commits (94842ba, b42c621) after my pre-PR merge at 19484af. I did not re-merge: neither touches service-messaging, objectql, driver-sql, platform-objects or spec."
    ],
    "files_changed": [
    ".changeset/24865-template-recipient-organization.md",
    "packages/services/service-messaging/src/template-renderer.ts",
    "packages/services/service-messaging/src/email-channel.ts",
    "packages/services/service-messaging/src/sms-channel.ts",
    "packages/services/service-messaging/src/template-renderer.test.ts",
    "packages/services/service-messaging/src/template-organization.integration.test.ts",
    "packages/services/service-messaging/src/email-channel.test.ts",
    "packages/services/service-messaging/src/sms-channel.test.ts",
    "packages/services/service-messaging/src/system-context.pin.test.ts"
    ],
    "open_questions": [
    {
    "question": "Widen the landing so the plugin-auth half can land: phone-sms-texts.ts plus AuthManager.renderPhoneSmsBody in auth-manager.ts. As a patch round on this branch (PR #24930, then flipped to Fixes #24865 by the seat), or as a separate card?",
    "options": [
    "A. Patch round on this branch: widen the claim's surface to auth-manager.ts renderPhoneSmsBody only (one hand-off). phone-sms-texts.ts gains the organization and scopes firstTemplateRow by it, with pins and ablation beside it, and a plugin-auth patch changeset. The seat rewrites the PR's first line to Fixes #24865.",
    "B. Split: land #24930 as Part of #24865 and dispatch the auth half separately on its own branch."
    ],
    "recommendation": "A. It is one hand-off line plus the reader in a file already in the claim. #24877 (same seat) touches auth-plugin.ts only, so there is no file collision, and one PR closes the card's Done-when list."
    },
    {
    "question": "Which organization does an auth SMS (phone OTP / invitation) read its template in, given that the auth flow carries none?",
    "options": [
    "A. The existing tenancy.defaultOrgId() resolution (ADR-0093 D3): the Default Organization when no wall is requested, null under a requested wall. Null means no row is read and the built-in text is sent. Under single this is unchanged in practice. Under a wall an organization-authored auth text stops reaching other organizations' users. Grounds: ADR-0131 D6 and D12 item 11 (no per-organization template override), D1/D3 (a row is its organization's), ADR-0093 D3 (never guess an organization under a wall). No new read.",
    "B. The recipient user's sole membership organization, with built-in text when there are 0 or several. This needs new sys_member reads per send and is ambiguous for multi-organization users: a new capability with no measured pull.",
    "C. Keep the unscoped read. Rejected: that is the defect."
    ],
    "recommendation": "A. Business need: no measured author of per-organization auth SMS text under a wall, and the pre-auth flow has no organization to target. Long-term soundness: it reuses the one existing answer for 'organization of a deployment-level user act'. AI-proofing: no optional fallback that silently widens. Startup focus: no new capability. I judge this decided by governing text, not a product question; flagged so the seat can confirm when widening the landing."
    }
    ],
    "out_of_scope_findings": [
    "carrier: none, noted in PR #24930 Acceptance notes, not filed (dormant: MessagingService.emit hands the preference filter and the recipient resolver input.organizationId rather than the event row's. The two differ only when the producer names none, which under a wall is refused.)"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24865,
    "status": "done",
    "branch": "claude/issue-24865-template-read-organization",
    "pr": "#24930",
    "session": "session_01UmqzD97e14iws6hmVhgZ5y (mode:subagent, shared with the dispatching PM; identity is the branch). Patch round 1, seat record 6295270052",
    "premise_still_valid": true,
    "summary": "Patch round 1 lands the plugin-auth half on this branch, so both readers now resolve the row in the recipient's organization and the card's Done-when list is met. The seat ruling's premise was measured on the real tenancy service (createTenancyService over a real ObjectQL + SqlDriver holding the Default Organization, alone and beside a second organization): defaultOrgId() answered the Default Organization under single, and null under isolated and group, both enforced and degraded. The premise holds. AuthManager.renderPhoneSmsBody (the only change in auth-manager.ts) hands resolvePhoneSmsTemplateBody tenancy.defaultOrgId(). No tenancy service, or a thrown resolution, counts as null. phone-sms-texts.ts keys every template read (the active-row read and the existence read) with where organization_id. A null or undefined organization reads no row and sends the built-in walk. The organization parameter is required, because the module is internal (not in the package exports). There is no unscoped fallback and no membership lookup. CI red on 076daa7 (check:test-typecheck: phone-sms-seed-retired.test.ts TS2339, Property 'body' does not exist on type '{ organization_id: string; }'): the store double's org-stamping spread narrowed the inferred row type. I fixed it in the test by typing the array as Row[]; test-typecheck-debt.json is untouched and its counts are unchanged at 10 files / 88 errors / 23 signatures. auth-manager.test.ts's pin 'a tenant sys_notification_template row overrides the built-in text' was fixture-triaged: it now composes a tenancy stub answering the Default Organization, and serves the row only to a read keyed by it.",
    "tests": "Final head 386f5d4 (round base 62583ff = bb37f9c merged with origin/main d606fc1; closure rebuilt, 28 tasks). pnpm --filter @objectstack/plugin-auth typecheck: exit 0, including check:test-typecheck OK (10 file(s) / 88 error(s) / 23 pinned signature(s), unchanged). pnpm --filter @objectstack/service-messaging typecheck: exit 0. plugin-auth vitest run --maxWorkers=2 at 386f5d4: 172 files, 3287 passed, 10 skipped. service-messaging vitest run --maxWorkers=2 at aa935bf: 53 files, 593 passed (its source and tests did not change after that). New pins: phone-sms-texts.test.ts block 'an auth SMS reads its template row in the organization the send resolves to' (keys every read by the organization handed in; another organization's row never decides the text; with no organization, no row is read and the built-in text is sent). phone-sms-template-organization.integration.test.ts runs the real AuthManager (public invitation send plus the OTP send), the real tenancy service, and ObjectQL + SqlDriver holding two organizations that each authored rows for both topics. 'single: the Default Organization's row decides the invitation and the OTP text'; 'isolated' and 'group: the built-in text is sent, and no organization's row decides it'. Each case first asserts the stored organizations. Ablation on committed 076daa7 via scripts/ablation-replace.mjs (subjects imported relatively from src); every leg was restored with blob == HEAD and git diff HEAD empty. E, organization term removed from the read: 3 red (single integration pin plus 2 unit). F, guard removed only: 1 red (unit no-organization pin; the real-engine walled pins stayed green because a null term matched no row, fewer reds than expected). G, guard replaced by unscoped-when-no-organization, the forbidden fallback: 3 red (isolated and group integration pins plus the unit pin). H, AuthManager hand-off dropped: 1 red (single integration pin).",
    "gates": "Local subset at 386f5d4: dispatch-gates --commands --repo objectstack-ai/objectstack (no paths, 17 paths vs merge base d606fc1) derived 72 commands, each run with exit captured before any pipe. 70 exit 0. check:dual-build-cjs-loads and check:i18n exited 3 (PREREQUISITE NOT MET, fresh worktree). After a locked workspace build (turbo build, 76/76, 75 cached) both re-ran at exit 0 (check-i18n-bundles: OK (10 package(s) ...)). --ran: '72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN'. CI on PR #24930 head 386f5d4: 41 check-runs completed, 34 success, 7 skipped, 0 non-green. Required: Lint & Repo Gates success, TypeScript Type Check success (Type Check · workspace success), Test Core and its 6 shards success, Build Core success, Governed Surface Queue Guard success.",
    "mcp_calls": "0",
    "api_writes": "This round: 1 relay stroke, a POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: comment = POST /repos//issues/24865/comments (this report). No PR body write (see deviations). git push x4 (merge 62583ff, fix 076daa7, test-type fix aa935bf, test fixture 386f5d4) is not REST.",
    "line_budget": "PR total vs merge base: +601/-56 over 17 files (657 changed lines, under the 3000 human-merge threshold). This round (62583ff..386f5d4): +284/-29 over 8 files. plugin-auth src: phone-sms-texts.ts +29/-5, auth-manager.ts +15/-1 (renderPhoneSmsBody and its docblock only). plugin-auth tests: +226/-23. Changeset +14. No skills/** or ratcheted ledger touched; test-typecheck-debt.json is unchanged.",
    "deviations": [
    "PR body not edited by me. The dispatch asked me to rewrite its first line through a body-patch op; the relay's issue_patch could do it. os-dev.md is unconditional that a dev writes the PR body once, at pr_create, never PATCHes it, and names the edit for the seat; the file prevails over a dispatch, so I name the conflict here. The edit text is in pr_body_edits.",
    "The seat record says the seat rewrites the first line at ACCEPT, and the coordinator message says the dev does it. These conflict; I followed the agent file, which agrees with the seat record.",
    "The plugin-auth changeset is a second new file (.changeset/24865-auth-sms-template-organization.md), one per published package as the original dispatch put it. The claim text says 'one new .changeset/24865-*.md'.",
    "Beyond the claim's listed plugin-auth files, three existing test files changed as fixture triage forced by the scoped read and the required parameter: phone-sms-seed-retired.test.ts (rows and reads in one organization, plus the Row[] type fix), find-envelope-limb-removal.test.ts (B14 row names org_1), and auth-manager.test.ts (the tenant-row pin composes the Default Organization). These are pins beside the reader, not product code.",
    "Walled-posture pins use enforced walls; the degraded walled variant (defaultOrgId null) was measured in the premise probe but is not pinned separately.",
    "Two throwaway probes (the premise reading, plus round 0's reachability probes) lived only in the worktree and were deleted uncommitted.",
    "origin/main moved two commits (74fcec1 driver-sql PG introspection, 7169612 docs) after this round's merge at d606fc1. I did not re-merge: neither touches plugin-auth, service-messaging, or the SQLite path the pins run on.",
    "Worktree /home/user/objectstack-issue-24865 removed (node_modules first, no --force); branch pushed at 386f5d4."
    ],
    "pr_body_edits": "For the seat. (1) Line 1: 'Part of #24865' becomes 'Fixes #24865'. Keep 'Clause-②: no' as line 3. (2) Replace the section '## Not in this PR: the plugin-auth reader' (heading and paragraph) with a section '## The plugin-auth reader (patch round 1)'. Its content: AuthManager.renderPhoneSmsBody hands phone-sms-texts.ts tenancy.defaultOrgId() (ADR-0093 D3), and every template read is keyed by 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 seat ruling is 6295270052, with its premise measured on the real tenancy service. Pins are in phone-sms-texts.test.ts and phone-sms-template-organization.integration.test.ts. Ablation: E 3 red, F 1 red, G 3 red, H 1 red, each restored blob == HEAD. The changeset is .changeset/24865-auth-sms-template-organization.md (plugin-auth patch). (3) The Tests and Gates sections are superseded by this round's 'tests' and 'gates' fields (head 386f5d4). In the Reachability paragraph, drop '(the half this PR does not carry)'.",
    "files_changed": [
    ".changeset/24865-template-recipient-organization.md",
    ".changeset/24865-auth-sms-template-organization.md",
    "packages/services/service-messaging/src/template-renderer.ts",
    "packages/services/service-messaging/src/email-channel.ts",
    "packages/services/service-messaging/src/sms-channel.ts",
    "packages/services/service-messaging/src/template-renderer.test.ts",
    "packages/services/service-messaging/src/template-organization.integration.test.ts",
    "packages/services/service-messaging/src/email-channel.test.ts",
    "packages/services/service-messaging/src/sms-channel.test.ts",
    "packages/services/service-messaging/src/system-context.pin.test.ts",
    "packages/plugins/plugin-auth/src/phone-sms-texts.ts",
    "packages/plugins/plugin-auth/src/auth-manager.ts",
    "packages/plugins/plugin-auth/src/phone-sms-texts.test.ts",
    "packages/plugins/plugin-auth/src/phone-sms-template-organization.integration.test.ts",
    "packages/plugins/plugin-auth/src/phone-sms-seed-retired.test.ts",
    "packages/plugins/plugin-auth/src/find-envelope-limb-removal.test.ts",
    "packages/plugins/plugin-auth/src/auth-manager.test.ts"
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none, unchanged from round 0, kept as a PR Acceptance note per the seat (dormant: emit hands the preference filter input.organizationId, not the event row's)."
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24865,
    "status": "done",
    "branch": "claude/issue-24865-template-read-organization",
    "pr": "#24930",
    "session": "session_01UmqzD97e14iws6hmVhgZ5y (mode:subagent; patch round 2, seat record 6296720561)",
    "premise_still_valid": true,
    "summary": "Changeset only, in commit 5abbc08 on top of 386f5d4. .changeset/24865-template-recipient-organization.md now bumps '@objectstack/service-messaging': minor. Its Clause line reads 'Clause-②: yes (widening)' and names NotificationTemplateStore.load's optional organizationId; the shared reader readClause2Line returns declared/yes/widening. The no-organization answer for direct callers is kept: no row, null, the renderer's fallback. .changeset/24865-auth-sms-template-organization.md is unchanged (plugin-auth patch, Clause-② no). No source or test change.",
    "tests": "None owed: changeset only, and no source or test byte moved since 386f5d4 (round 1's suites and typechecks stand for those bytes).",
    "gates": "There is no 'pnpm check:changeset' script. I ran the changeset family that dispatch-gates --commands derives at 5abbc08, each with its exit captured before any pipe: check-adr-0087-registration --base origin/main :: exit 0 (no declared-breaking changeset; 2 non-breaking seen), and its --self-test :: exit 0. check-changeset-no-major --base origin/main :: exit 0 (its level axis is not applicable locally, since there is no pull_request payload), and its --self-test :: exit 0. check-empty-changeset --base origin/main :: exit 0, and its --self-test :: exit 0. check:changeset-gate-self-tests, check:objectui-changeset and check:pm-changeset-deadline-census :: exit 0 each. I also drove the level axis offline with PR #24930's own body via --event: check-changeset-no-major --base origin/main --event :: exit 0, verdict 'LEVEL AXIS: this PR declares clause-② yes (widening) ... moves at minor or above — the declared widening is accounted for'. Narrowing declared: the other 63 derived families were not re-run for a changeset-only commit; CI on 5abbc08 runs them. Not waited on.",
    "mcp_calls": "0",
    "api_writes": "1 relay stroke, a POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: comment = POST /repos//issues/24865/comments (this report). Plus git push x1 (5abbc08), which is not REST.",
    "line_budget": "This round: .changeset/24865-template-recipient-organization.md only (+3/-3). PR total vs merge base is unchanged in source (17 files).",
    "deviations": [
    "origin/main is 2 commits ahead (74fcec1 driver-sql PG introspection plus its changeset, 7169612 docs). Neither touches service-messaging or plugin-auth, so I did not merge, as the dispatch's condition allows.",
    "Local gates narrowed to the changeset family, declared above; the full union was not re-run for a changeset-only commit.",
    "Worktree /home/user/objectstack-issue-24865 was recreated from 386f5d4 and removed after the push (node_modules first, no --force)."
    ],
    "files_changed": [
    ".changeset/24865-template-recipient-organization.md"
    ],
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24930 comment 6297349962

    domain:services seat 2 · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T17:46Z · head 5abbc08025, all checks green or expected skips; contract review 6296861429 PASS; flipped to ready with auto-merge on.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 2 · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T18:25Z

    Reading Value
    PR #24930 timeline added_to_merge_queue 2026-10-20T17:50Z → merged 2026-10-20T18:25Z
    origin/main merge commit 865d2c8cf4 is an ancestor (git merge-base --is-ancestor, exit 0)
    By content on origin/main template-renderer.ts and phone-sms-texts.ts each key their read with organization_id: organizationId (1 hit each)
    This card closed completed by the PR's Fixes #24865; pm:dispatched removed in this act

    ACCEPT 6297349962 and contract review 6296861429 are on the PR. #24924 (the template channel narrowing, which also carries the authentication.mdx SMS paragraph) is unblocked in this act.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:servicespriority:p1High: required for production / M2security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions