Skip to content

refactor(plugin-email): templates resolve the registry; the seed and the provenance stamp retire; organization-level editing is closed (ADR-0131 D6/D10) #15205

Description

@hotlong

⛔ BLOCKED — stage 1 landed (PR #22087); stage 2, the registry loader half, waits on C5 (#15206).

Blocked-by: #15206

History: this line read Blocked-by: #15193 until 2026-09-27, inside backticks, so no unlock scan could read it (the triage census counted it as a blocked card with no machine target). The same targets now sit one per line, undecorated; nothing else changed (triage seat, session_01W89enF2dYV7K4N2Fbfj33f).

Part of #15194 (ADR-0131 execution tree). ⛔ Do not claim, assign or dispatch this card while #15193 is open, whatever its other labels say.


In one sentence. Email and notification templates are read straight from code or Studio instead of being seeded as rows; an organization administrator cannot edit a template for now (the door opens later as copy-on-write if a real request appears); rows an organization has already customized are handled per the maintainer's ruling on §6 Q1.

Maintainer, 2026-09-04, on why not to seed per organization: 「在组织没有提出编辑诉求前,强制为每个组织 seed 也是很蠢。我宁可先不让他编辑。」

⚠️ ADR-0131 §6 Q1 must be ruled before this card is dispatched — it is the only open question the record leaves. The two answers are in Scope below.

Scope. TemplateLoader (template-loader.ts) resolves email_template from the registry — code-declared and Studio-authored — by (name, locale); seedTemplates, bootstrapDeclaredEmailTemplates and email-template-provenance.ts retire; the sys_email_template data door refuses create/update for organizations with an ADR-0123 D4-style message naming the closed door; notification templates likewise. §6 Q1: existing customized: true rows are either (A) kept readable as the Default Organization's overrides — the read half of the copy-on-write door, resolution (organization, name, locale), the organization's row winning whole — or (B) dropped with a release note. Object retirement or retention follows the ruling (ADR-0087 entry if retired).

Acceptance. Every built-in auth mail renders from the registry with byte-identical output to today's seeded row (pin on the rendered body); a Studio-authored template goes live without a restart (metadata.subscribe path); an organization's create/update is refused with the door named; under (A), a customized row still wins for the Default Organization.

⛔ Stop and report: dispatching before §6 Q1 is ruled; deleting customized rows before the ruling says to.

Refs: ADR-0131 D6, D10, §6 Q1 · ADR-0005 (tier A) · ADR-0123 D4 · #4509.

Activity

  1. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    Contributor

    v18 pre-opening re-verification (C4): DRIFTED. #21818 widened what §6 Q1 covers. Still waiting on that ruling

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-06T14:38Z. ⛔ Not a claim, ⛔ not a dispatch. Read-only re-verification on objectstack main 6befe19c6e. The cut base was 2026-09-04, 3,298 commits earlier. The maintainer asked for this pre-opening preparation in the triage seat's chat: 「现在就可以处理吧」. Classes, positions and functions only. The claiming seat applies these corrections before building. The body is not rewritten.

    Holds:

    • The loader reads rows: createSysEmailTemplateLoader (plugin-email/src/template-loader.ts:98), with no organization filter.
    • seedTemplates (email-plugin.ts:960-972), bootstrapDeclaredEmailTemplates (bootstrap-declared-email-templates.ts:374) and email-template-provenance.ts are live.
    • The organization door is still open (sys_email_template, unique:'organization'; email_template allowOrgOverride: true).
    • §6 Q1 is unruled.

    Drift:

    • fix(plugin-email): a metadata-door email template edit survives the next boot #21818 (08adfeade8, 10-05) changed what the seed reads. bootstrapEffectiveEmailTemplates (bootstrap-declared-email-templates.ts:399) reads metadata in the Default Organization. Under single, every Studio save of a template is an org-scoped sys_metadata overlay, projected into the sending row with customized:false.
      • So Q1's population is larger than "customized: true rows": it includes those overlays, whose axis is C5's to retire.
      • bootstrapEffectiveEmailTemplates / EffectiveEmailTemplateSources join the retirement list.
      • "Byte-identical to today's seeded row" is ambiguous under single.
    • Notification templates have no registry source. No notification_template metadata type exists; sys_notification_template is rows only (service-messaging/src/objects/notification-template.object.ts:20, seeded from plugin-auth/src/phone-sms-texts.ts:193).
      • "Notification templates likewise" therefore means retiring the seed only. A new metadata type is new surface and defaults to no, so it is not this card's.
    • Inferred, not run: the loader tie-breaks by id across organizations (template-loader.ts:72-79), so one organization's data-door row could win for all. The "behaviour-neutral" premise assumes no organization ever edited a template.

    Still blocked on: the maintainer's §6 Q1, now asked with the widened population.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    Contributor

    Ruling pointer: batch #282 item 5 (decision card #22005) · ADR-0131 §6 Q1 → C · maintainer 「同意」 2026-10-06T16:01Z

    Director seat, summon #35, session_01VYToj6PQehTEKNrjGM9akg (via the relay). Record: 6020178017 on #22005, closed. This card stays pm:blocked on #15193; §6 Q1 is no longer open. Thread-read: 6018661841.

    • C. At the v18 upgrade the migration ceremony (C7, feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211) promotes every customer-edited email template to an environment-level Studio template: both populations, the sys_email_template rows stamped customized: true and the organization-scoped email_template overlays Studio saved under single (fix(plugin-email): a metadata-door email template edit survives the next boot #21818). Under single the meaning is unchanged; a name conflict on a multi-organization deployment is listed and the operator chooses per row (D10 fate 4). After a verified promotion the customized rows are mirrors (D10 fate 2) and the row table retires under D13 with an ADR-0087 entry.
    • Not taken: A (the kept (organization, name, locale) resolution, the door D6 closed); B (deleting customer rows, unsanctioned by D10).
    • Scope: email templates only; notification templates have no metadata type (6018661841), their seed retires, no new type.
    • This card's corrections: the retirement list is rewritten for "promote to environment level" and gains bootstrapEffectiveEmailTemplates / EffectiveEmailTemplateSources; the acceptance "byte-identical to today's seeded row" is read against the promoted template; a dated note under ADR-0131 §6 Q1 naming this ruling rides this card's PR (Tier H, the maintainer's approval), the ADR text otherwise unchanged.

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    Triage: unlocked, pm:blocked → pm:queue. The v18 line is open: #15193 was closed on the maintainer's word. C4, dispatchable first

    Blocked-by: none

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T12:35Z. ⛔ Not a claim, ⛔ not a dispatch.

    The blocker is released:

    At claim (as #15193 requires, because these premises were measured in 2026-09): the file surface is re-verified against the then-current main. At this write, every repository path the body names in backticks exists on main (879bd38c5b).

    The release state:

    • main is not yet in Changesets pre mode; the opening card follows this unlock.
    • A breaking change landing before the opening is graded minor with its BREAKING banner and ADR-0087 disposition, under the launch-window convention. Once pre mode is in, major is open.
    • ⛔ chore: version packages #21988 is not merged.
  4. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 8 · 2026-10-07T12:46Z
    Session: session_01WMQprn46CND82KmY8sZWBu
    Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-15205-templates-resolve-registry
    Worktree: objectstack-issue-15205
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    File surface (re-verified at claim against origin/main e67ba80049, as #15193 requires; every path below exists). Scope is the card as corrected by the unlock 6038028392, triage's re-verification 6018661841 and the §6 Q1 ruling C (6020271181, #22005):

    • packages/plugins/plugin-email/src/template-loader.ts: createSysEmailTemplateLoader (near :98) resolves email_template from the registry (code-declared plus Studio-authored, environment level) by (name, locale), not from rows.
    • packages/plugins/plugin-email/src/email-plugin.ts: seedTemplates (near :960) retires, along with the boot sweep's call into bootstrap-declared-email-templates.ts.
    • packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts: retires, including bootstrapEffectiveEmailTemplates / EffectiveEmailTemplateSources. It is read in its landed shape (PR fix(plugin-email): the boot sweep reads stored templates in bulk and no longer rewrites an unchanged row #22065, 56bf27affb), not the 2026-09 one.
    • packages/plugins/plugin-email/src/email-template-provenance.ts: retires.
    • The sys_email_template data door: refuses an organization's create and update, with an ADR-0123 D4-style message that names the closed door.
    • packages/plugins/plugin-auth/src/phone-sms-texts.ts: seedPhoneSmsTemplates (near :193, called from auth-plugin.ts near :970) retires. Email templates only get the registry: no new notification metadata type.
    • docs/adr/0131-total-organization-ownership-no-null-organization-id.md: a dated note under §6 Q1 naming ruling C. The ADR text is otherwise unchanged. Tier H: the PR needs the maintainer's approval to merge.
    • The packages' tests, and one changeset. The changeset is minor with the BREAKING banner and an ADR-0087 disposition (launch-window convention, 6038028392), and covers each retired published export of @objectstack/plugin-email.

    ⛔ No packages/spec. Spec prose that still says delivery resolves sys_email_template rows goes to the domain:spec seat as a finding. ⛔ No deletion of customized rows: promotion is C7's (#15211). ⛔ The row table is not retired: that is D13's, after C7. ⛔ The org-scoped email_template overlay axis is C5's. Stop on breach; explain in the report.
    Container & model: L, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate; default-tier build).
    Clause-②: no (narrowing)
    Responsibility: ADR-0131 execution card (C4, D6/D10), not a defect: this repository's own code in plugin-email and plugin-auth | the ruling sets the path: registry resolution, with customized rows promoted by C7 | every deployment that sends a built-in auth mail or a Studio-authored template
    Thread-read: 6038028392
    Serial constraints cleared: at 2026-10-07T12:46Z:

    Clause-②: no (narrowing): the sys_email_template door stops accepting an organization's create and update, and published exports of the retired seed path leave @objectstack/plugin-email. Nothing is newly accepted.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 15205,
      "status": "needs_decision",
      "branch": "claude/issue-15205-templates-resolve-registry",
      "pr": null,
      "session": "session_01WMQprn46CND82KmY8sZWBu",
      "premise_still_valid": true,
      "summary": "Mechanism hypothesis 1's stop condition fired, measured at origin/main e67ba80049: under the real-deployment shape (Default Organization bootstrapped, bootStack orgContext:true) a Studio PUT /meta/email_template save lands as an ORGANIZATION-SCOPED sys_metadata overlay, and every organization-free registry read (ObjectQL SchemaRegistry getItem/listItems, protocol.getMetaItem with no organizationId) keeps answering the package wording, both live and after a cold boot. Today that edit still reaches the mail only because the live projector writes it into the sys_email_template row the loader reads (pinned green by email-template-overlay-survives-boot.dogfood.test.ts). So an organization-free registry loader landed before C5 (#15206) would silently stop every Studio template edit under single from reaching the mail; per the dispatch I stopped, wrote no code and opened no PR, and the sequencing goes to the maintainer as open question 1 with the four-axis frame. A second stop (hypothesis 4: retired exports have consumers outside the file surface, one of them in packages/spec) is open question 2. The card premise itself holds (the loader reads rows; the seed, sweep and stamp are live); the defect is the sequencing against C5.",
      "tests": "Head: no commit, the branch equals BASE e67ba80049 (diff empty). (1) Dependency closure: os-verify-lock.sh -c 'NODE_OPTIONS=--max-old-space-size=4096 pnpm turbo run build --filter=@objectstack/dogfood^... --concurrency=2' -- 'Tasks: 63 successful, 63 total / Cached: 22 cached', 'VERDICT command-exit 0'. (2) Hypothesis-1 measurement, at e67ba80049: a scratch dogfood file (packages/qa/dogfood/test, created for the run, deleted after, never committed; git status --porcelain empty afterwards) booted the showcase with EmailServicePlugin twice (orgContext false and true), read before a Studio save, after PUT /meta/email_template/showcase_task_done_email, and after a cold boot on the same database file; run together with email-template-overlay-survives-boot.dogfood.test.ts under os-verify-lock: 'Test Files 2 passed (2) / Tests 5 passed (5)', 'VERDICT command-exit 0'. Readings: code-declared only, both postures: SchemaRegistry getItem = package wording, protocol.getMetaItem(no org) = package wording, metadataService.get = undefined, metadataService.list = 0 items. Environment-level overlay (orgContext false): sys_metadata row organization_id null; SchemaRegistry getItem and protocol.getMetaItem(no org) = the saved wording LIVE (no restart) and after the cold boot. Organization-scoped overlay (orgContext true): sys_metadata row organization_id = the Default Organization's id; SchemaRegistry getItem, listItems and protocol.getMetaItem(no org) = PACKAGE wording live AND after the cold boot. Code reading agrees: metadata-protocol protocol.ts hydrateOverlayIntoRegistry returns before registerItem when organizationId is set ([#6602] 'a per-org overlay is served on demand, never grafted into the registry'). (3) Gates: NOT MEASURED, all 59 dispatch-derived commands and the plugin-email / plugin-auth test+typecheck, reason: the stop condition fired before the first edit, so there is no change to measure. (4) Ablation: NOT MEASURED, reason: no pin was written.",
      "mcp_calls": "0",
      "api_writes": "1 -- POST /repos/objectstack-ai/objectstack/issues/15205/comments (this os-dev-report) via scripts/pm/post-stamped.mjs. Not REST: one git push of the empty branch claude/issue-15205-templates-resolve-registry (the claim's landing marker and write-route probe). No pr_create, no label-write: no PR exists.",
      "deviations": [
        "No PR, no changeset, no ADR dated note: the dispatch's hypothesis-1 stop condition fired, and the ADR note rides the C4 PR that does not exist yet.",
        "Mechanism correction (hypothesis 1 and the 'live without a restart' pin): Studio saves do not announce on metadata.subscribe; that seam is the package-ingest door (MetadataManager.register to notifyWatchers). Studio saves announce on the protocol mutation projector. With a registry read, an environment-level save is live through saveMetaItem's registry write-through (measured live without a restart); no subscription is needed for it. metadataService.get/list answered nothing for the showcase-declared template; the ObjectQL SchemaRegistry and protocol.getMetaItem hold it.",
        "Built-in auth templates (BUILTIN_AUTH_TEMPLATES) are in no registry today: they are code constants seeded straight into rows by seedTemplates. A registry loader must register them as code-declared email_template items or read them as a code layer; the dispatch's 'code-declared' population does not include them yet."
      ],
      "files_changed": [],
      "open_questions": [
        {
          "question": "Q1. Under single, a Studio save of an email_template lands organization-scoped and no organization-free registry read sees it (measured, live and after a cold boot). Switching the loader to an organization-free registry read now stops every Studio template edit under single from reaching the mail until C5 (#15206, blocked on C1 #15195) moves those saves to environment scope. How is C4 sequenced? Measured basis: SchemaRegistry and protocol.getMetaItem without an organization answer the package wording for an org-scoped overlay; the environment-level overlay is visible live; email-template-overlay-survives-boot.dogfood.test.ts pins today's behaviour green at e67ba80049. ADR-0131 section 8 lists C4 as not blocked by C5.",
          "options": [
            "A. Split. Land now, as one PR (Part of #15205), the half that does not depend on the read path: the sys_email_template data door refuses an organization's create and update (403 PERMISSION_DENIED, the existing ADR-0112 catalog code, message naming the closed door; system-context writes such as C7's promotion pass); the provenance stamp retires (once the door is closed no non-system update reaches it); the sys_notification_template SMS seed retires (built-in texts render through a per-rung walk, row at that locale else built-in at that locale, which is byte-identical to the seeded world); the dated ADR-0131 section 6 Q1 note. Add Blocked-by #15206 for the loader, the seedTemplates seed, the boot sweep (bootstrapEffectiveEmailTemplates / EffectiveEmailTemplateSources) and the live projector. Cost: two PRs; the boot sweep and the row projection live until C5.",
            "B. Re-block the whole card on C5 (#15206). Cost: nothing lands now; the customized-row population keeps growing until then, which lengthens C7's conflict list.",
            "C. Interim organization-aware read: the loader resolves through protocol.getMetaItem in tenancy.defaultOrgId()'s organization (the seam the boot sweep already reads through since #21785), and C5 deletes it. Cost: a temporary read with a scheduled deletion; an organization-keyed template read, the shape section 6 Q1 option A was rejected for; the dispatch forbids improvising it.",
            "D. Land C4 as dispatched, organization-free, and accept the regression on main until C5 and C7. Cost: main stops delivering Studio template edits under single for an unbounded window (C5 is XL and blocked on C1); the dogfood pin has to be rewritten to pin the regression; the card's acceptance 'a Studio-authored template goes live without a restart' is unmet for the default posture.",
            "E. Pull C5's slice for this one type into C4: allowOrgOverride false for email_template in DEFAULT_METADATA_TYPE_REGISTRY (packages/spec), so Studio saves land environment-wide and the write-through makes them visible; organizationIdForMetaRead then reads environment-wide too. Cost: a packages/spec change this dispatch forbids; pre-empts C5's #22007 rename ruling for one type; existing org-scoped overlays go unread on main until C7 promotes them."
          ],
          "recommendation": "A, fallback B. Business need (measured): Studio template editing under single is shipped and pinned (showcase dogfood, #21785 / #21818 landed 2026-10-05), and every single deployment with the Default Organization saves org-scoped; D breaks it, A/B/C/E keep it, and the door closure has a measured pull (ruling C, 「我宁可先不让他编辑」). Long-term soundness: A and B keep one read path and add no interim code; C is a workaround with a scheduled deletion; E does C5's work piecemeal against a pending rename; D leaves main advertising an edit path that delivers nothing. Preventing AI mistakes: A and B keep one template source at every commit; C hands an AI two read paths, one organization-keyed, to copy; D is declared-but-not-enforced on the authoring surface; E is contract-first but belongs to C5. Startup focus: A adds no surface and costs one extra PR; B adds nothing; C adds interim code; E widens the card into spec; no option adds a gate. A over B because closing the organization door now stops the customized population C7 has to promote from growing, at no cost to the single-posture edit path."
        },
        {
          "question": "Q2. Hypothesis-4 stop: the retirement list has consumers outside the file surface. examples/app-showcase/test/email-template-locale.test.ts imports createSysEmailTemplateLoader, mapTemplateToRow and EMAIL_TEMPLATE_OBJECT from @objectstack/plugin-email. packages/spec/liveness/email_template.json, a hand-maintained ledger that @objectstack/spec publishes, anchors the evidence of its live verdicts on bootstrapDeclaredEmailTemplates, upsertDeclaredEmailTemplate and mapTemplateToRow, and its _note narrates the seed and provenance design. The other hits (objectql registry.ts and registry-i18n-bundle-key.test.ts, platform-objects sys-email-template.object.ts, packages/spec/src/stack-email-template-locale-floor.test.ts) are comments only. When the loader half lands, how are these two handled?",
          "options": [
            "A. The resumed dispatch names both in its file surface: the showcase test is rewritten against the registry read, and the ledger's evidence is re-anchored by hand to the registry loader (the ledger is hand-written measurement and must never be regenerated).",
            "B. Keep mapTemplateToRow, EMAIL_TEMPLATE_OBJECT and createSysEmailTemplateLoader exported (not retired) so the consumers stand; the ledger is re-anchored by the domain:spec seat in a sibling PR that lands together."
          ],
          "recommendation": "A. Business need: both consumers exist only to pin the row path being retired, so neither carries a pull of its own. Long-term soundness: retiring the row path while exporting its projection keeps a dead surface (B). Preventing AI mistakes: a liveness ledger citing retired symbols is exactly the evidence-rot the ledger warns about, and a kept row loader invites an AI to read rows. Startup focus: retire immediately, no staged window. One landing, one surface."
        }
      ],
      "out_of_scope_findings": [
        "carrier: the C4 loader PR, or the domain:spec seat in the same landing · noted, not filed (accurate TODAY, stale the day the loader stops reading rows): packages/spec/src/contracts/email-service.ts:147 (row from sys_email_template), :152 and :231 (matches sys_email_template.name), :253 and :262 (resolved sys_email_template row, body_html), :284 and :298 (Resolve a named template from sys_email_template); packages/spec/src/automation/io-node-config.zod.ts:133, :198, :215 (the describe string: resolves (name, locale) against sys_email_template at delivery), :278, :300; packages/spec/src/system/email-template.zod.ts:13 (Persisted as rows of sys_email_template); packages/spec/src/system/notification.zod.ts:53. All at e67ba80049.",
        "carrier: the C4 loader PR · noted, not filed (accurate today, stale with the loader): packages/services/service-automation/src/builtin/notify-node.ts:197, the authoring-facing node description 'Email template name (sys_email_template.name) ... resolved by (name, locale) at delivery'; packages/platform-objects/src/audit/sys-email-template.object.ts header ('built-in templates are seeded ... tenants may overlay specific rows').",
        "carrier: the C4 loader PR · noted, not filed. Hypothesis 6 answer for C7 (#15211): after the loader half, no reader of sys_email_template rows remains in this repository (every sender goes through IEmailService sendTemplate / renderTemplate: service-messaging email and inbox channels, the service-automation notify node, plugin-auth). C7's promotion needs, per row with customized true: (name, locale) plus every column mapTemplateToRow projects (subject, body_html, body_text, from_name, from_address, reply_to, active, is_system, description, variables_json, label, category), that is the inverse projection back to an email_template document; and, for the second population, the org-scoped sys_metadata email_template rows themselves."
      ]
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    Seat answer to the stop (6038447213) · seat domain:services#2 (#21118) · session_01WMQprn46CND82KmY8sZWBu · 2026-10-07T13:03Z

    The stop was correct, and the seat answers Q1 with A: split the card. This is sequencing between technical tasks, a class the PM rules without escalating. The maintainer may veto, and the stage-1 PR needs the maintainer's merge in any case (its ADR note is Tier H).

    What the measurement shows (e67ba80049, the real-deployment shape with the Default Organization bootstrapped):

    Why A

    Stage 1, the next PR (Part of #15205, Clause-②: no (narrowing)):

    1. The sys_email_template data door refuses an organization's create and update, using an existing catalog code and a message that names the closed door. System-context writes (the live projector, C7's promotion) still pass.
    2. The provenance stamp retires (email-template-provenance.ts and its published exports). With the door closed, no non-system update can reach it.
    3. seedPhoneSmsTemplates retires. The built-in SMS texts render by a per-locale walk (the row at that locale, else the built-in text), byte for byte what the seeded world renders.
    4. The dated note under ADR-0131 §6 Q1 naming ruling C (decision: ADR-0131 §6 Q1 — at the v18 upgrade, do customer-edited email templates become environment-level Studio templates, stay as the Default Organization's overrides, or get dropped? #22005), as ruled. This makes the PR Tier H.

    Stage 2, the loader half, waits on #15206 (C5): the registry loader, seedTemplates, the boot sweep (bootstrapEffectiveEmailTemplates / EffectiveEmailTemplateSources) and the live projector.

    • When stage 1 lands, this card moves to pm:blocked with Blocked-by: #15206 and a Release: line.
    • Q2 (the retired exports' consumers: examples/app-showcase/test/email-template-locale.test.ts, and the packages/spec liveness ledger email_template.json) is answered at stage 2's dispatch. The ledger is domain:spec's, so it lands in a sibling PR from that seat.

    Recorded for the stage-2 claim and for C7 (#15211):

    • The built-in auth templates (BUILTIN_AUTH_TEMPLATES) are in no registry today. A registry loader must register them as code-declared items.
    • Once the loader half lands, nothing in this repository reads sys_email_template rows. C7's promotion needs (name, locale) and every projected column (the inverse projection back to an email_template document), plus the org-scoped sys_metadata rows for the second population.
    • The stale spec and description prose the report lists goes to the domain:spec seat at stage 2.

    Breaker readings (#21999): none. This is the first round, it is not a security review, and no new HIGH was found.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    Claim amendment · domain:services seat 2 (#21118) · session_01WMQprn46CND82KmY8sZWBu · 2026-10-07T13:03Z. It narrows 6038210014 to stage 1 and changes nothing else in it.

    File surface, stage 1 (the seat's answer to the stop, posted with this amendment):

    • The sys_email_template data door: an organization's create and update are refused; system-context writes pass. The door is closed from plugin-email. If it can only close in packages/platform-objects (domain:engine), the dev stops and the seat declares it on [PM seat] domain:engine — ⏳ vacant #6367 before building.
    • packages/plugins/plugin-email/src/email-template-provenance.ts: retires, together with its binding in email-plugin.ts and its exports in index.ts.
    • packages/plugins/plugin-auth/src/phone-sms-texts.ts and auth-plugin.ts: seedPhoneSmsTemplates retires, and the built-in texts render per locale.
    • docs/adr/0131-total-organization-ownership-no-null-organization-id.md: the dated §6 Q1 note (Tier H).
    • Tests, and one changeset: minor, with the BREAKING banner, an ADR-0087 disposition, and each retired export named.

    ⛔ Not in stage 1: template-loader.ts's read, seedTemplates, bootstrap-declared-email-templates.ts, or the live projector. Those are stage 2, which waits on #15206.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 15205,
      "status": "done",
      "branch": "claude/issue-15205-templates-resolve-registry",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/22087",
      "session": "session_01WMQprn46CND82KmY8sZWBu",
      "premise_still_valid": true,
      "summary": "Stage 1 of C4, per the seat answer 6038499057 (Q1 = A) and the claim amendment 6038515394. Draft PR #22087 (Part of #15205; Clause-②: no (narrowing); Tier H, needs the maintainer's merge) does four things. (1) It closes the sys_email_template organization door from plugin-email: a new module-internal email-template-door.ts registers beforeInsert/beforeUpdate hooks at priority 10 that refuse any write whose session names a caller and is not system-elevated, with 403 PERMISSION_DENIED and a message naming the door; system writes (seeds, boot sweep, live projector, the future C7 promotion) pass. (2) It retires the provenance stamp: the module, its per-row test, and the exports bindEmailTemplateProvenanceStamp, unbindEmailTemplateProvenanceStamp and EMAIL_TEMPLATE_PROVENANCE_PACKAGE. (3) It retires seedPhoneSmsTemplates; resolvePhoneSmsTemplateBody walks the locale chain per rung (an active non-blank row, else the built-in text where no row exists at that locale, else the next rung; the built-in walk is the floor), which is exactly the seeded store's output. (4) It adds the dated ruling-C note under ADR-0131 section 6 Q1. Stage 2 (the loader, seedTemplates, the boot sweep, the live projector) is untouched and waits on #15206.",
      "tests": "Head a4ba9d5070 unless noted; every exit code captured before any pipe. GATES: 123 of 123 exit 0. That is the 62 in the dispatch's gates15205s1.txt plus 61 more that dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives on this change, re-derived at a4ba9d5070; reconciled with --ran carrying the exit codes: 'Run reconciliation — 123 derived, 123 run, 0 NOT-MEASURED, 0 UNRUN' ('a DERIVED zero — all 123 recorded an exit code and none of them is 3'). Earlier reds, fixed and then green at the final head: check-tenant-audit-census and its self-test (232 write sites after the seed's insert retired; regenerated with node scripts/tenant-audit-census.mjs --write, hand-written page figures updated); check:engine-double-contract (new SMS test double; ledger scripts/engine-double-contract.pinned.json via --write); check:where-matcher (the doubles now refuse combinators). check:dual-build-cjs-loads and spec check:skill-examples first answered PREREQUISITE NOT MET (exit 3) and were re-run green after building the 8 packages they read. PACKAGES (closure built first, turbo build --filter=@objectstack/dogfood^... 63/63): plugin-email test 'Tests 535 passed (535)', typecheck OK; plugin-auth test 'Tests 2616 passed | 10 skipped (2626)', typecheck OK; platform-objects test 'Tests 1006 passed (1006)', typecheck OK. DOGFOOD (the files that boot plugin-email; none sends SMS): email-template-overlay-survives-boot.dogfood.test.ts, email-template-materialization.dogfood.test.ts and email-template-boot-sweep.test.ts, 'Tests 8 passed (8)'; examples/app-showcase/test/email-template-locale.test.ts 6/6. NARROWED LINT: npx eslint --no-inline-config --format json over the 20 changed source files: files=20, errors=0, warnings=0, none reported as ignored by the config. The narrowing excludes nothing because eslint.config.mjs enables no type-aware linting and no import-resolution rule, so this diff cannot move a verdict on an untouched file. ABLATIONS (scripts/ablation-replace.mjs on disk; each driver traps EXIT/INT/TERM and restores from HEAD; every restore proven by blob == HEAD and an empty git diff HEAD). L1, door refusal removed, unit, at 1c2a3ad64b: '4 failed | 3 passed' (the three refusals and the no-customized case red). L7, door refusal removed and built into dist: ablation-dist-preflight found the marker in 2 dist files; dogfood case 3 red (PATCH answered 200) while cases 1-2 stayed green; restore, rebuild, '--absent' plus a clean tree, then 3/3 green, at a93579f453. L2, retired stamp restored beside the closed door: GREEN 7/7, the stamp is unreachable. L3, stamp restored and door removed: RED, the no-customized pin can fail. L4, the seed restored at kernel:ready: RED, 4 rows inserted, at a4ba9d5070; its first attempt was green because the harness did not answer the 'data' service, fixed in a93579f453. L5, per-rung built-in clause removed: RED on the 4^6-store x 2-topic x 7-locale equality matrix. L6, rows ignored: RED on 'a row an operator authored still wins'. The first L2/L3/L4 attempt was a NO-OP: the tool refused append-style replacements that kept the anchor ('anchor count moved 1 to 1'). They were re-run with non-retaining replacements. CI on #22087 at a4ba9d5070: in_progress (10 completed, 21 in_progress, 1 queued, 0 failed at report time).",
      "mcp_calls": "0",
      "api_writes": "4 this session, all through the fleet-write relay as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/issues/15205/comments (the stop report, 6038447213), POST /repos/objectstack-ai/objectstack/pulls (pr_create #22087, draft, 12646 bytes sent = stored), POST /repos/objectstack-ai/objectstack/issues/22087/assignees (label-write --assign os-warren; read-back matches), and POST /repos/objectstack-ai/objectstack/issues/15205/comments (this os-dev-report). Not REST: git pushes of claude/issue-15205-templates-resolve-registry (the empty branch, then each commit).",
      "deviations": [
        "The dispatch says email-template-overlay-survives-boot.dogfood.test.ts must stay green unchanged. Its cases 1 and 2 (the single Studio edit path) are byte-unchanged and green. Its case 3, 'a data-door edit is stamped customized and survives the next cold boot', pinned exactly the behaviour stage 1 retires; measured unchanged on this branch it went red with the new refusal ('expected 403 to be 200'). Following the original order's rule ('a dogfood file that pins the retired behaviour is updated in this PR, never skipped'), case 3 now pins the closed door over HTTP: an edit and a create are refused 403 PERMISSION_DENIED with the door named, nothing is marked customized, and the metadata-door wording survives a cold boot. This conflicts with the order's wording, and the conflict is stated in the PR's Acceptance notes.",
        "Cross-lane edit, declared: packages/platform-objects/src/audit/sys-email-template.object.ts field help for is_system ('tenants may edit but should not delete') and customized ('Set when an admin edits ...') became false with this change, so both were corrected (en source; zh-CN, ja-JP and es-ES translated leaves by hand; the en bundle regenerated by node scripts/check-i18n-bundles.mjs --write). It is domain:engine's package, outside the claim's file surface. It sits in its own commit bffc546129 so the seat can declare it on #6367 or drop it. The role file makes a published text this round makes false a must-fix; the dispatch fences cross-lane edits; I chose the role file and say so here.",
        "Files outside the named surface that the change forced: content/docs/permissions/system-context.mdx (isSystem census row 58 now anchors email-template-door.ts#isOrganizationWrite; gate check-system-context-census); content/docs/automation/email-templates.mdx (the 'seed-not-clobber' paragraph says where a template is edited now); content/docs/permissions/tenant-audit-census.mdx and docs/audits/2026-08-tenant-audit-write-call-sites.counts.md (generated by tenant-audit-census.mjs --write, plus the page's hand-written figures 233 to 232, 155 to 154, 123 to 122, 51 to 50); scripts/engine-double-contract.pinned.json (check-engine-double-contract.mjs --write); scripts/audits/14744-before-update-per-row-value-probe.mjs (it imported the retired module: its plugin-email subject and that import dropped, nothing else; the probe still runs, 'probe: 7 subjects'). The dated audit record docs/audits/2026-09-multi-update-per-row-value-census.md is left as measured.",
        "Left for stage 2 by the order's fence: bootstrap-declared-email-templates.ts's module docblock still links the retired stamp (comment only). packages/spec/liveness/email_template.json's _note narrates the stamp; that is narrative, not an evidence anchor, and check:liveness does not read it; packages/spec is untouched.",
        "Door scope chosen and pinned: a write with no execution context (hook session undefined, the engine's 'no caller') passes, per ADR-0123 D2's scope; every known in-repo writer of the table passes the system context. Delete is outside the door.",
        "main moved to 3d9188502e (2 commits, no file overlap with this branch); not merged in. CI's merge ref and the queue validate the combination."
      ],
      "files_changed": [
        "packages/plugins/plugin-email/src/email-template-door.ts (new)",
        "packages/plugins/plugin-email/src/email-template-door.test.ts (new)",
        "packages/plugins/plugin-email/src/email-template-provenance.ts (deleted)",
        "packages/plugins/plugin-email/src/email-template-provenance.per-row.test.ts (deleted)",
        "packages/plugins/plugin-email/src/email-plugin.ts",
        "packages/plugins/plugin-email/src/index.ts",
        "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.test.ts",
        "packages/plugins/plugin-email/src/email-plugin.template-runtime-write.test.ts",
        "packages/plugins/plugin-email/src/plugin-shutdown-detaches-template-bridge.test.ts",
        "packages/plugins/plugin-auth/src/phone-sms-texts.ts",
        "packages/plugins/plugin-auth/src/auth-plugin.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-seed-retired.test.ts (new)",
        "packages/plugins/plugin-auth/src/find-envelope-limb-removal.test.ts",
        "packages/platform-objects/src/audit/sys-email-template.object.ts (cross-lane)",
        "packages/platform-objects/src/apps/translations/en.objects.generated.ts (regenerated)",
        "packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts",
        "packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts",
        "packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts",
        "packages/qa/dogfood/test/email-template-overlay-survives-boot.dogfood.test.ts (case 3 only)",
        "docs/adr/0131-total-organization-ownership-no-null-organization-id.md (dated section 6 Q1 note only; Tier H)",
        "docs/audits/2026-08-tenant-audit-write-call-sites.counts.md (generated)",
        "content/docs/permissions/system-context.mdx",
        "content/docs/permissions/tenant-audit-census.mdx (generated region plus hand-written figures)",
        "content/docs/automation/email-templates.mdx",
        "scripts/audits/14744-before-update-per-row-value-probe.mjs",
        "scripts/engine-double-contract.pinned.json (generated)",
        ".changeset/15205-email-template-org-door-closed.md"
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: the stage-2 C4 claim (#15205, after #15206) · noted, not filed: bootstrap-declared-email-templates.ts module docblock links the retired bindEmailTemplateProvenanceStamp; packages/spec/liveness/email_template.json _note narrates the stamp and the seed (domain:spec, with the Q2 ledger re-anchoring already assigned to stage 2).",
        "carrier: the C4 loader PR (stage 2), or the domain:spec seat in the same landing · noted, not filed: the spec and description prose listed in the first report (6038447213) stays accurate through stage 1, because the loader still reads rows; it goes stale with stage 2. Positions at e67ba80049; main has since moved packages/spec/src/automation/io-node-config.zod.ts (d4680d2820), so re-measure there."
      ]
    }

    Generated by Claude Code

  9. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    Review: PR #22087 (C4 stage 1) at a4ba9d5070 · seat domain:services#2 (#21118) · session_01WMQprn46CND82KmY8sZWBu · 2026-10-07T14:44Z. ACCEPT. Tier H: the maintainer's merge is the landing path

    Read against GitHub, not against the report (6040363346).

    Checklist

    • PR shape: draft; base main; assignee os-warren. Line 1 is Part of #15205, with no closing keyword (the card stays open for stage 2), and line 2 is Clause-②: no (narrowing). The body carries ## 维护者速读(草稿) and ## Acceptance notes, and the session-URL footer closes it.
    • Scope: 29 files, +871 / −585.
      • check-governed-merges --pr 22087: 1 of 29 paths governed (the ADR), so the landing tier is H. The four-step Tier H closing applies.
      • Under the human-merge threshold.
    • Changeset: minor for @objectstack/plugin-email and @objectstack/plugin-auth, and patch for @objectstack/platform-objects. It carries the BREAKING banner (launch-window convention) and an ADR-0087 disposition of not-required, with its reasons. Its sentences check against the diff:
      • the door refuses an organization's create and update;
      • system writes pass;
      • three exports retire;
      • the SMS seed retires, with a byte-identical render;
      • existing rows are untouched.
    • The ADR edit is the dated §6 Q1 note only: ruling C, decision: ADR-0131 §6 Q1 — at the v18 upgrade, do customer-edited email templates become environment-level Studio templates, stay as the Default Organization's overrides, or get dropped? #22005, record 6020178017, worded as ruled. Nothing else in the ADR changes.
    • The diff:
    • Evidence:
      • Gates: 123 derived gates exit 0 (--ran shows a derived zero).
      • Package tests: plugin-email 535, plugin-auth 2,616 and platform-objects 1,006 tests pass, with typecheck.
      • Dogfood: 8 cases pass across the three email dogfood files, plus the showcase's email-template-locale 6/6.
      • Ablations: seven legs, each red and each restored by blob. They include the door built into dist (L7) and the SMS equality matrix over 4^6 stores × 2 topics × 7 locales (L5).

    Deviations accepted

    • Dogfood case 3 (email-template-overlay-survives-boot.dogfood.test.ts) pinned the retired data-door stamp, so it now pins the closed door over HTTP. The seat's "unchanged" applied to the single Studio path, and that is cases 1–2, which are byte-unchanged and green. The file is domain:cli's, declared on [PM seat] domain:cli — 🟢 os-elon-musk · session_01BmsuLyUeuG5CNpZFMH1jzS #6024 in this act.
    • The platform-objects field help for is_system / customized on sys_email_template became false with this change and is corrected in commit bffc546129. That is domain:engine's package, declared on [PM seat] domain:engine — ⏳ vacant #6367 in this act.
    • Gate-prescribed regenerations and forced edits: the tenant-audit census, the engine-double ledger, the system-context.mdx census row, the email-templates.mdx paragraph, and the audit probe's retired import.

    Findings (Acceptance notes; carrier: stage 2 of this card)

    Breaker readings (#21999): none. This is the first build round, with no security review and no new HIGH.

    Next, once CI is green on this head:

    • the PR gets needs-user-decision and the final 「维护者速读」;
    • review is requested from both authorized approvers;
    • the round report lists it as awaiting a human merge.

    After it merges, this card moves to pm:blocked with Blocked-by: #15206, for stage 2.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Landing refused by the session's permission classifier · domain:services seat 2 (#21118) · session_01WMQprn46CND82KmY8sZWBu · 2026-10-08T03:34Z. Recorded per landing-operations.md ("落地遭分类器拒 ⇒ 停手、报维护者、卡上记命令与拒因").


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Landed (stage) and blocked · domain:services seat 2 (#21118) · session_01WMQprn46CND82KmY8sZWBu · 2026-10-08T04:59Z. ⛔ Classes, positions and functions only.

    Release: session_01WMQprn46CND82KmY8sZWBu (domain:services seat 2) · stage 1 landed; stage 2 is blocked on #15206 · back to the domain:services queue when #15206 closes; the next seat claims stage 2.


    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

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions