Repository navigation
plugin-email: the boot sweep rewrites every effective email template on every boot (4 serial statements and a full sys_email_template scan each); since 08adfeade the default org's overlays are in that loop #22062
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p1High: required for production / M2High: required for production / M2
on Oct 7, 2026 objectstack-fleet commented
on Oct 7, 2026 ContributorAuthorMore actionsPath: the road — publish/install: a hosted environment boots inside its timeout | 缺项 | P1
Triage: first grade,
bug·priority:p1·domain:services·area:devpath·pm:queue(needs-triageremoved). This is release-priority: it must land before the v18 openingTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T06:13Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts(the boot sweep andupsertDeclaredEmailTemplate) and its test ⇒domain:services; rationale: plugin-email is the services lane, as #21785 and #15205 are.- Verified on
main(2015c54028) at this write:- the sweep's loop (
:412) callsupsertDeclaredEmailTemplatefor every declared template, and that function looks the row up bywhere: { name, locale }(:286); - it
updates a package-managed row unconditionally (:303); - it is called at
EmailServicePluginstart (email-plugin.ts:1105); 08adfeade8(fix(plugin-email): a metadata-door email template edit survives the next boot #21818) is in 17.7.0 (4e4e881427), and the list comes fromprotocol.getMetaItemsintenancy.defaultOrgId()(:229–:234).
- the sweep's loop (
- Why p1:
- The maintainer ranked cloud#2637 first today.
- The cost is measured on every hosted HotCRM environment, whatever the staging log reads (316 of about 830 serial round trips per boot).
- It is release-priority. cloud stays on its last pre-opening pin, so a framework fix that lands after the v18 opening reaches cloud only mixed with v18 (decision: open v18 now and ship it in stages — release the last 17.x from main first without waiting for #21908's deny (A), skip the last 17.x (B), or keep #22009's order (C)? #22050).
- Scope, as the body states it: fix it at the producer, and keep plugin-email: an email template edited through PUT /meta/email_template reverts to the package wording on the next boot (the overlay is stamped customized:false, the boot sweep overwrites it) #21785's contract (an org-scoped edit survives a boot), ⛔ with no revert of
08adfeade8. That means a bulk read of the declared names, compare-before-write for amanaged_by: 'package'row, and the per-template lookup as the fallback.Clause-②: no: no spec, schema or contract change. Patch changeset. - Pins beyond the body's test:
- plugin-email: an email template edited through PUT /meta/email_template reverts to the package wording on the next boot (the overlay is stamped customized:false, the boot sweep overwrites it) #21785's own test stays green. An org-scoped edit still survives the next boot.
- The row chosen for a
(name, locale)that more than one organization holds is the row the per-templatefind(..., { limit: 1 })chose. The declared unique index includesorganization_id, so such rows can exist. The draft orders by string id, so pin it rather than assume it. - A row whose projected columns differ is still rewritten, as the control.
- Not this card's to wait on: the one staging log line asked on cloud#2637 decides whether this step is
1ac85ba2's time. The fix is justified without it, so the claim does not wait.
- Verified on
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iterateand removed
on Oct 7, 2026 objectstack-fleet commented
on Oct 7, 2026 ContributorAuthorMore actionsClaim: PM loop round 6 · 2026-10-07T06:32Z
Session:session_01WMQprn46CND82KmY8sZWBu
Account:os-warren(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-22062-email-template-boot-sweep
Worktree:objectstack-issue-22062
Domain:domain:services
Seat:domain:services#2(seat post #21118)
File surface (atorigin/main56c88446ea, per triage's direction6032154904):packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts:bootstrapEffectiveEmailTemplates(its loop near:412) reads the stored rows for the declared names in bounded bulk pages, instead of onefindper template.upsertDeclaredEmailTemplate(near:276) and the sweep share one write step. That step does not rewrite amanaged_by: 'package'row that already holds the projected columns.- The per-template lookup stays as the fallback when the bulk read fails. A key missing from the bulk read is not trusted as absent unless the read is proven complete.
- Its test:
bootstrap-declared-email-templates.test.ts. - One
patchchangeset for@objectstack/plugin-email.
⛔ No revert or narrowing of
08adfeade8(#21785's contract: an org-scoped edit survives a boot). ⛔ Nopackages/spec, no schema, index or migration change, no change toemail-plugin.ts's wiring. Stop on breach; explain in the report.
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; default-tier build).
Clause-②: no
Responsibility:this repository's own code: plugin-email's boot sweep rewrites every effective template on every boot, with one serial lookup and one unconditional update per template | none: the sweep is the only boot materializer, and nothing lets an environment skip or batch it | every hosted environment on every boot (measured by the repo:cloud seat: 316 of about 830 serial round trips on the plain HotCRM shape, and past the 600 s timeout with 5,000 default-org overlays)
Thread-read: 6032154904
Serial constraints cleared: at 2026-10-07T06:32Z:- No open PR touches
packages/plugins/plugin-email/(every open PR's file list was read). - No in-flight claim of any lane declares a
plugin-emailfile. - refactor(plugin-email): templates resolve the registry; the seed and the provenance stamp retire; organization-level editing is closed (ADR-0131 D6/D10) #15205 (
plugin-emailtemplates) ispm:blockedand not in flight. plugin-email: an email template edited through PUT /meta/email_template reverts to the package wording on the next boot (the overlay is stamped customized:false, the boot sweep overwrites it) #21785 is closed.
Clause-②: no: the publishedupsertDeclaredEmailTemplateandbootstrapDeclaredEmailTemplateskeep their signatures and their documented result:true/seededfor a written row,false/skippedfor a row deliberately not written. A row already holding its projection is now a skip. No accepted or refused input changes.
Generated by Claude Code
objectstack-fleet commented
on Oct 7, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22062,
"status": "done",
"branch": "claude/issue-22062-email-template-boot-sweep",
"pr": "#22065",
"session": "session_01WMQprn46CND82KmY8sZWBu (shared with the PM; mode:subagent)",
"premise_still_valid": true,
"summary": "Premise verified at origin/main 56c8844: the sweep looked every effective template up on its own and rewrote it unconditionally; measured on ObjectQL over SqlDriver (better-sqlite3), a steady boot over 79 / 450 templates sent 316 / 1,800 statements. The sweep now reads the stored rows in $in pages of 200 names, each bounded at 1,000 rows and deliberately PAGED with no orderBy (the per-template lookup's own shape, so the SQL driver orders both by id asc and each key keeps the first row it meets, which is the lookup's row); a key the bulk read did not answer (new template, read cut short at its bound, failed read) is looked up on its own before any insert (the preferred miss shape); the write step shared by both doors skips a managed_by:'package' row that already holds every column mapTemplateToRow projects (booleans also as 1/0, variables_json also parsed, absent/null never held), and drops the key's bulk copy after any write. After: 1 / 3 reads and no write on a steady boot; first boot +1 read per 200 names; one changed template costs 4 / 6 statements. The card's draft would have reverted every env-wide overlay on each registry-path boot (the registry lists the overlay after the package entry for the same slot and the draft compared the later item against a stale copy); this branch invalidates the copy and pins it. Admin-owned and customized rows untouched, legacy rows still adopted, #21785's unit cases and its real-showcase dogfood file green, 08adfea untouched, no spec/schema/index/migration change, no email-plugin.ts change. Labels: zero label writes (the dispatch named none; skip-changeset does not apply because the package publishes); the path labeler set documentation, size/l, tests, tooling on the PR. CI on ef2a35d at report time: 13 check runs completed, 0 failed, 19 in_progress.",
"tests": "Head ef2a35d (source and test bytes identical to e1171ca; the last commit is the regenerated census, docs only). GATES: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack derived 93 families on this change (the dispatch's 55 + 12 from the changeset/test files + 26 doc families from the census regen); all 93 ran and exited 0 at ef2a35d, reconciled with --ran over a 'command :: exit N' record: 'Run reconciliation - 93 derived, 93 run, 0 NOT-MEASURED, 0 UNRUN' (a DERIVED zero, every line carries an exit code). An earlier full run at e1171ca had check-tenant-audit-census red (census-drift, 17 to 19 object:string-parameter sites; fixed by its own prescription, the --write regen) and check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (8 unrelated packages had no dist; built from turbo cache; final run exit 0, '106 published require entry point(s) across 66 package(s) load'). PACKAGE: closure built first (turbo, dogfood^... 63 tasks, plugin-email... and driver-sql... via pnpm). pnpm --filter @objectstack/plugin-email test: 31 files, 533 tests passed; typecheck: tsc --noEmit + check:test-typecheck OK (0 errors); --listFiles confirms both new/edited test files are in their tsc programs. DOGFOOD (boots plugin-email): test/email-template-boot-sweep.test.ts (3, new, real ObjectQL + SqlDriver), test/email-template-materialization.dogfood.test.ts (2), test/email-template-overlay-survives-boot.dogfood.test.ts (3, the #21785 real-showcase pin: org-scoped edit survives a cold boot): 3 files, 8 passed; pnpm --filter @objectstack/dogfood typecheck exit 0. LINT (declared narrowing, CI owns pnpm lint): population = the 3 touched TS files, each resolved by eslint --print-config; eslint --no-inline-config --format json at ef2a35d: files 3, errors 0, warnings 0; invariance: eslint.config.mjs enables no type-aware linting (its own header says so), so this diff cannot move a verdict on an untouched file. MEASUREMENTS (scratch harness, base sweep vs branch sweep bundled identically, real engine): steady 79: 316 stmts {select 237, update 79} -> 1 {select 1}; steady 450: 1800 {select 1350, update 450} -> 3 {select 3}; first boot 79: 158 -> 159, 450: 900 -> 903; one template changed 79: 316 -> 4, 450: 1800 -> 6. H1 engine default page size: none (find, no limit, 1,500 rows -> 1,500). H2 per-template lookup SQL: 'select * from sys_email_template where name = ? and locale = ? order by id asc limit ?' -> etpl_c; unpaged bulk read gets no ORDER BY -> etpl_m first; paged bulk read -> 'order by id asc limit ?' -> etpl_c. H3 stored shapes: booleans as JS booleans, variables_json text, unset optional columns null. H4 $in honoured ('where name in (?)', 3+1 absent -> 3; 200 -> 200). H5 upsertDeclaredEmailTemplate callers: email-plugin.ts:1237 and :1258 only, neither reads the return value. ABLATIONS (scripts/ablation-replace.mjs: anchor 1 -> 0, blob changed, restore proven blob == HEAD 04568c89c620 and git diff HEAD empty; trap EXIT/INT/TERM restore in the driver scripts), unit legs re-run at e1171ca after the fake fix: A1 no compare -> 8 red; A2 no bulk read -> 3 red; A3 every value held -> 12 red (all 7 column-kind controls); A5 last row wins -> 1 red; A6 miss taken as no row -> 3 red; A6+A7 failed read answers empty set -> 4 red; A8 compare ignores provenance -> 1 red (legacy adoption); A9 customized written -> 2 red; A10 admin written -> 2 red; A11 bulk copy never dropped -> 1 red (overlay reverted: expected 'Admin reworded...' received 'Reset your password...'); restored: 40/40. A11's FIRST run was green: the fake engine handed out live stored objects, so the copy was never stale; the fake now returns copies (as a real driver does) and A11 is red. Real-engine legs at 5275c86, same sweep blob 04568c89c620 (rebuild plugin-email -> ablation-dist-preflight marker present -> dogfood file; restore leg rebuild -> preflight --absent -> green 3/3): D1 unpaged bulk read -> 2 red (row choice rewrote etpl_m; statement shape); D2 no compare -> 1 red ({ seeded: 450, skipped: 0 } on the steady boot); D3 last row wins -> 1 red (etpl_z rewritten). D2's first attempt exited 1 at the BUILD (the mutation left rowHoldsProjection unused, a TS6133 in the DTS pass), so it measured nothing; re-run with the call kept, red as above. Final preflight: no ablation marker in plugin-email dist/, tree clean against HEAD.",
"mcp_calls": "0",
"api_writes": "3 - each one POST /repos/objectstack-ai/objectstack/dispatches through scripts/pm (fleet-write relay, objectstack-fleet[bot]): (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (#22065, draft; body read back byte-identical, one footer); (2) label-write.mjs --issue 22065 --assign os-warren -> POST /repos//issues/22065/assignees (read back: os-warren); (3) post-stamped.mjs -> POST /repos//issues/22062/comments (this report). git push: 7 pushes of the branch (the empty branch first, then 6 commits), not REST.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none - noted in the PR Acceptance notes, not filed. Unmeasured boundary: on a case-insensitive MySQL collation the bulk map keys (name, locale) by exact string while the per-template lookup matches by collation; a case-variant miss falls back to the lookup (safe direction), but two organizations holding one slot in different letter case could resolve to different rows on the two paths. Dedupe words: email template, collation, case-insensitive, boot sweep"
],
"deviations": [
"File surface, real-engine test: landed at packages/qa/dogfood/test/email-template-boot-sweep.test.ts, not beside the sweep. @objectstack/plugin-email cannot import a driver for its own suite: driver-sql is not its dependency, and its KNOWN_UNALIASED_TEST_IMPORTS entry in scripts/check-test-source-alias.mjs is shrink-only (adding driver-sql would widen it; an alias would pull driver-sql source deps in and widen it further). The dogfood package already declares and is ledgered for objectql, driver-sql, platform-objects and plugin-email; engine-only tests there have precedent (engine-where-shape-refusal.test.ts).",
"File surface, generated census: content/docs/permissions/tenant-audit-census.mdx and docs/audits/2026-08-tenant-audit-write-call-sites.counts.md regenerated by node scripts/tenant-audit-census.mjs --write, the prescription check-tenant-audit-census printed: the two sys_email_template writes moved into writeTemplateRow, whose object name is a typed object:string parameter, so 2 sites moved row (61 -> 59, 17 -> 19); population unchanged.",
"The unit file's FakeEngine.find now returns row copies (it handed out the stored objects, which hid the stale-copy defect from every pin) and learns $in. Same file as the card's test.",
"Two module-internal constants (SWEEP_NAMES_PER_READ, SWEEP_ROWS_PER_READ) are exported from bootstrap-declared-email-templates.ts for this package's tests; src/index.ts does not re-export them (published dist/index.d.ts unchanged in that respect).",
"Commit trailers: the model-free pair AGENTS.md requires (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By; the pre-push hook confirmed no model identifier. PR body footer: the session-URL form the dispatch named, not the reminder's emoji line."
],
"files_changed": [
".changeset/22062-email-template-boot-sweep.md (+27)",
"packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts (+184 -11)",
"packages/plugins/plugin-email/src/bootstrap-declared-email-templates.test.ts (+213 -2)",
"packages/qa/dogfood/test/email-template-boot-sweep.test.ts (+160, new)",
"content/docs/permissions/tenant-audit-census.mdx (+4 -4, generated)",
"docs/audits/2026-08-tenant-audit-write-call-sites.counts.md (+2 -2, generated)"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 7, 2026 ContributorAuthorMore actionsClaim amendment ·
domain:servicesseat 2 (#21118) ·session_01WMQprn46CND82KmY8sZWBu· 2026-10-07T07:54Z. It adds to6032396161and changes nothing else in it.File surface revised to PR #22065's measured landing. The PR body states each addition with its reason.
packages/qa/dogfood/test/email-template-boot-sweep.test.ts(new): the real-engine pins (row choice on a slot several organizations hold, and the steady-boot statement count onObjectQLoverSqlDriver).- They cannot live beside the sweep:
@objectstack/plugin-emaildoes not depend ondriver-sql, and itscheck:test-source-aliasledger entry is shrink-only. - The dogfood package already declares
objectql,driver-sql,platform-objectsandplugin-email. - Lane:
domain:cli(packages/qa). It is declared on [PM seat] domain:cli — 🟢 os-elon-musk · session_01BmsuLyUeuG5CNpZFMH1jzS #6024 in this act.
- They cannot live beside the sweep:
content/docs/permissions/tenant-audit-census.mdxanddocs/audits/2026-08-tenant-audit-write-call-sites.counts.md: regenerated bynode scripts/tenant-audit-census.mjs --write, ascheck:tenant-audit-censusprescribes.- The two
sys_email_templatewrites moved into one write step that takes the object name as a typed parameter, so two sites change row. - The population is unchanged.
- The two
Clause-②: nostands. Onepatchchangeset.
Generated by Claude Code
objectstack-fleet commented
on Oct 7, 2026 ContributorAuthorMore actionsReview: PR #22065 at
ef2a35d986· seatdomain:services#2(#21118) ·session_01WMQprn46CND82KmY8sZWBu· 2026-10-07T07:55Z. ACCEPT (the queue waits for green CI on this head)Read against GitHub, not against the report (
6033497721).Checklist
- PR shape: draft; base
main; assigneeos-warren. Line 1 isFixes #22062and line 2 isClause-②: no. No other closing keyword is in the body: objectstack-ai/cloud#2637 and plugin-email: an email template edited through PUT /meta/email_template reverts to the package wording on the next boot (the overlay is stamped customized:false, the boot sweep overwrites it) #21785 appear as references only.## Acceptance notesis present, and the session-URL footer closes the body. - Scope: 6 files, +590 / −19.
check-governed-merges --pr 22065: 0 of 6 paths governed, under the human-merge threshold.- Two files in
plugin-email: the sweep and its test. - One new dogfood test (
domain:cli, declared on [PM seat] domain:cli — 🟢 os-elon-musk · session_01BmsuLyUeuG5CNpZFMH1jzS #6024 in this act). - Two generated census files.
- One changeset.
- Two files in
- Changeset:
patchfor@objectstack/plugin-email. Each sentence checks against the diff:- the
$inpages of 200; - the first row a key meets wins;
- a key the read did not answer is looked up before insert;
- a held
managed_by: 'package'row is skipped and counted underskipped; - other provenances are unchanged.
- the
Clause-②: noholds. The signatures ofupsertDeclaredEmailTemplateandbootstrapDeclaredEmailTemplatesare unchanged. A held row returnsfalse/skipped, which both already document as "deliberately not written". The two new constants are not re-exported fromsrc/index.ts. Bothemail-plugin.tscallers ignore the return value.- The diff:
- Bulk read:
readStoredTemplateRowsreads the stored rows in pages of 200 names, bounded at 1,000 rows each, with noorderBy. - One write step:
writeTemplateRowis shared by both doors, and the compare-before-write sits after the admin and customized skips. - Copy dropped: after any write, the sweep drops that key's bulk copy.
- Untouched:
08adfeade8's effective read,email-plugin.ts, and every schema and index.
- Bulk read:
- Evidence:
- Hypotheses measured on
ObjectQLoverSqlDriver:- no default page size;
- the per-template lookup goes out
order by id asc limit ?, while the unpaged bulk read gets noORDER BYand chose a different row; $inis honoured;- booleans read back as JS booleans.
- Statements on a steady boot: 316 → 1 at 79 templates, and 1,800 → 3 at 450.
- Ablations: 10 unit legs and 3 real-engine legs, each red, each restored by blob, with the
dist/marker proven in and then out. - Gates: 93 derived gates exit 0 (
--ranshows 0 not measured and 0 unrun). - Tests: the package's 533 tests and typecheck pass. So do 3 dogfood files, including
email-template-overlay-survives-boot.dogfood.test.ts, plugin-email: an email template edited through PUT /meta/email_template reverts to the package wording on the next boot (the overlay is stamped customized:false, the boot sweep overwrites it) #21785's real-showcase pin.
- Hypotheses measured on
Seat's own reads on the branch
- Every projected column is always defined.
label,subjectandbodyHtmlare required.category,locale,activeandisSystemare defaulted. The optional keys are spread only when set. So no real template's compare meetsundefinedagainst a storednull, and the steady-boot skip is reachable for every template, not only the test fixture's. - A read cut short is safe. The bounded read is ordered by
idacross the whole page. A cut-off page is therefore a prefix of that order:- a key with any row in it holds its first row by
id, which is the row the per-template lookup returns; - a key with no row in it goes to the lookup before any insert.
- a key with any row in it holds its first row by
- A failed or non-list answer sends every template to the lookup, as before, with one warning.
Deviations accepted
- Real-engine pins in dogfood rather than beside the sweep. The reason is stated: there is no
driver-sqldependency, and the alias ledger is shrink-only. This revises the claim (amendment in this act). - Census regenerated by its own prescription. Two sites change row and the population is unchanged.
- The unit test's
FakeEnginenow returns row copies and learns$in. Handing out live stored objects hid the stale-copy defect from every pin. - Commit trailers are the model-free pair
AGENTS.mdrequires.
Recorded: the card's draft compared an env-wide overlay against the bulk copy taken before the package entry's write. It would have reverted every env-wide overlay on each registry-path boot. The dev found it, fixed it (the copy is dropped after a write) and pinned it (A11).
Findings (all go to Acceptance notes; carrier: none)
- Case-insensitive collation, unmeasured. On a case-insensitive MySQL collation, two organizations holding one slot in different letter case could resolve to different rows on the two paths. A case-variant miss still falls back to the lookup, which is the safe direction.
- Log wording. The live doors log "materialized" after a skip, as they already did after an admin or customized skip.
- A duplicated slot. On the registry path, a slot listed twice (a package entry, then its overlay) still pays both writes per boot, as before. This is not a regression; the plugin's own boot reads the effective list.
Breaker readings (#21999): none. This is the first build round, with no independent security review and no new HIGH.
Generated by Claude Code
- PR shape: draft; base
objectstack-fleet commented
on Oct 7, 2026 ContributorAuthorMore actionsLanded ·
domain:servicesseat 2 (#21118) ·session_01WMQprn46CND82KmY8sZWBu· 2026-10-07T08:58Z- PR fix(plugin-email): the boot sweep reads stored templates in bulk and no longer rewrites an unchanged row #22065 merged through the queue as
56bf27affb, read onorigin/main. This card closedcompletedbyFixes #22062, andpm:dispatchedis stripped in this act. - Closing-keyword check: the PR body carries one closing keyword, and only this card closed at the merge.
- What landed:
- Bulk read. The boot sweep reads the stored rows in
$inpages of 200 names, each bounded at 1,000 rows. The read is paged, so the SQL driver orders it as it orders the per-template lookup, and a(name, locale)several organizations hold resolves to the same row as before. - Miss safety. A key the bulk read did not answer is looked up on its own before anything is inserted. That covers a new template, a read cut short and a failed read.
- Compare before write. A
managed_by: 'package'row that already holds its projection is not rewritten. Admin-owned and customized rows are untouched, as before. Legacy rows are still adopted. - plugin-email: an email template edited through PUT /meta/email_template reverts to the package wording on the next boot (the overlay is stamped customized:false, the boot sweep overwrites it) #21785's contract holds: an org-scoped edit survives a boot.
08adfeade8is untouched.
- Bulk read. The boot sweep reads the stored rows in
- Measured on
ObjectQLoverSqlDriver: a steady boot over 79 unchanged templates sent 316 statements and now sends 1 read; over 450 templates, 1,800 statements become 3 reads and no write. Review:6033573187. - For the
repo:cloudseat: the framework half is onmainat56bf27affb. The hosted half of "Done when",1ac85ba2's first build inside the 600 s timeout on a pin that carries this fix, is verified on objectstack-ai/cloud#2637 if the staging reading attributes the time to this step.
Generated by Claude Code
- PR fix(plugin-email): the boot sweep reads stored templates in bulk and no longer rewrites an unchanged row #22065 merged through the queue as
- added 2 commits that reference this issue
on Oct 7, 2026
Filing gate: ① a product defect, with its reach measured locally (cloud's real kernel factory on the hosted Turso face). The staging attribution is pending one log read (see Reach).
Filed by the
repo:cloudseat (repo:cloud#1, sessionsession_01Wxo1xhh2bU66T73q23jzE4, R44), from objectstack-ai/cloud#2637 (p1), round 2 (os-dev-report 6031983341 on that card). ⛔ Not a claim. The maintainer ranked cloud#2637 first today.The step
bootstrapEffectiveEmailTemplatesinpackages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts(loop at about:412, called fromemail-plugin.ts:1105atEmailServicePluginstart) callsupsertDeclaredEmailTemplate(about:276-323) for every template of the effective list, on every boot. Each call:find(..., { where: { name, locale }, limit: 1 }). That isSCAN sys_email_template, because the declared unique index is(COALESCE(organization_id,'__global__'), name, locale)and cannot serve(name, locale);UPDATEs it unconditionally, even when nothing changed;That is 4 serial round trips per template per boot, and K × T rows visited.
08adfeade(#21818, Fixes #21785) reads the list throughprotocol.getMetaItemsintenancy.defaultOrgId()(about:234), instead of the registry. That is correct for #21785: an org-scoped template edit must survive a boot. But it means the default organization'semail_templateoverlays now enter this per-template loop. The unconditional per-template rewrite itself predates that commit.Reach (measured)
All figures are from cloud's
ArtifactKernelFactoryon the hosted Turso remote face, with byte-identical database copies per pin, steady-state boots, and 36 ms injected per round trip.7d0781484e4e881427(17.7.0)8832655amatches4e4e881427on every shape.8832655a, every build of the large environment1ac85ba2has passed the 600 s hard timeout, with a phase of about 14 minutes. Whether1ac85ba2holds thousands of default-org email templates is not yet known. One staging log line decides it, and the maintainer is asked for it on cloud#2637.Proposed fix (at the producer; keeps #21785's contract)
$inpages of 200, instead of onefindper template. The first row by id wins, as before.managed_by: 'package'row that already holds the projected columns is not rewritten.The live doors keep their own lookup. One source file (+72/-1) and its test (+18/-1). No spec, schema, contract or migration change.
A rig-only trial at
4e4e881427(reverted, never pushed) measured:expected { seeded: 450, skipped: 0 } to deeply equal { seeded: 0, skipped: 450 }.The draft patch below is against
4e4e881427; the file pair is identical at8832655a. It is a starting point for the implementer, not a reviewed change.⛔ Not the fix: reverting or narrowing
08adfeadeback to the registry. That reopens #21785: an org-scoped template edit lost on the next boot.Timing
Cloud stays on 17.x until its own v18 ceremony (objectstack-ai/cloud#1979; ruling recorded on #15193). So this fix reaches cloud only if it lands on objectstack
mainbefore the v18 opening. Its size fits that window.Done when
sys_email_templatewrite, and one read per 200 template names (the pin test above).1ac85ba2's first kernel build reacheskernel readyinside the 600 s hard timeout on a cloud pin that carries the fix. That half is verified on cloud#2637.Also measured, not filed (costs, not defect classes)
18c2ddc1eadds one index-servedsys_metadataread per stored active permission set per boot (overlayLockLayerAtbesidegetMetaItem'sfindOne): about +22 s at 36 ms for 600 sets.backfillOrgAdminGrants(plugin-security) reconciles every member on every boot, 5 serial statements per member up to 5,000. That is about 25,900 round trips per boot at 20,000 members, at every pin.Reader
The objectstack lane that owns
plugin-email. Therepo:cloudseat verifies the cloud half on cloud#2637 after the pin carries the fix.Generated by Claude Code