Repository navigation
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
Description
Activity
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsPath: an API a customer can call — send and receive (email templates) | integration-system.email-template-render | P2
Triage: first grade —
bug·priority:p2·domain:services·area:api·pm:queue. The boot sweep projects the effective (overlay-layered) template; no new markerTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-05T02:56Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts⇒domain:services; rationale: the metadata door keeps the admin's overlay, and only the boot sweep reads the package layer instead of the effective item.- Why p2. An admin's edit is accepted, shown in Studio, and silently undone on the next boot, so the mail goes out with the package wording. The verifier reproduced it on two fresh databases. It predates 17.6.0.
- Direction.
readDeclared(about:153–:170) reads the effective item, overlay included, so the sweep projects what the metadata door serves. The registry stays the one source, and the sending row stays its projection.- ⛔ No second "customized" marker for the metadata door. The card offers it as an alternative, but it would be a second truth beside the overlay.
- Pins: the card's dogfood test (edit through
/meta/email_template, restart, read the sending row). The control stays: a data-door edit still survives the restart.
Generated by Claude Code
- addedarea:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 5, 2026 objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsClaim: PM loop round 3 · 2026-10-05T03:46Z
Session:session_011K3zqE8Pv1Evw5hc8tZCnN
Account:os-steve(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21785-email-template-overlay-survives-boot
Worktree:objectstack-issue-21785
Domain:domain:services
Seat:domain:services#1(seat post #6021)
File surface (atorigin/main18c2ddc1), per triage's direction5987305576:packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts:readDeclaredreads the effective item, with the overlay included, so the boot sweep projects what the metadata door serves. The registry stays the one source, and the sending row stays its projection. Plusplugin-emailtests.- The card's dogfood pin: edit through the metadata door, restart, and read the admin's wording back from the sending row. The data-door control (an edit there still survives a restart) stays. The pin goes in a NEW file under
packages/qa/dogfood/test/, or extends the existingemail-template-materialization.dogfood.test.ts; both are declared cross-lane on [PM seat] domain:cli — 🟢 marchtian · session_01B5CHJNXuuqzChM4w6hkTN4 #6024 in this act. content/docs/**sentences this makes false, and apatchchangeset.
⛔ No second "customized" marker for the metadata door (triage: it would be a second truth beside the overlay). ⛔ No change to the seed-not-clobber rules for admin-authored or data-door-customized rows. ⛔ No
packages/specedit.
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; default tier).
Clause-②: no- No accepted input widens or narrows. The boot sweep stops overwriting an accepted overlay with the package wording.
Thread-read: 5987305576
Serial constraints cleared: at 2026-10-05T03:46Z: - None of the 3 open PRs touches
plugin-emailor an email dogfood file. - The v18 card refactor(plugin-email): templates resolve the registry; the seed and the provenance stamp retire; organization-level editing is closed (ADR-0131 D6/D10) #15205 (ADR-0131 D6,
pm:blocked, no claim) will later retire this seed path. This fix lands on the 17.x line first, and refactor(plugin-email): templates resolve the registry; the seed and the provenance stamp retire; organization-level editing is closed (ADR-0131 D6/D10) #15205 inherits it. - This seat's in-flight security(data): a by-id write answers 403 for a row the caller cannot read and 404 for an id that does not exist, for principals the write pre-image check does not bind: an existence signal the read door withholds #21771 (
area:access,plugin-security) shares no file and no area with this card.
Selection: the oldestpriority:p2card in the lane queue, and the first one that shares neither area nor files with this seat's in-flight card. plugin-security: permission sets an org owns (or that live in a writable runtime package) are reported and enforced as "locked by the code package" — the lock reads the package id without the row's provenance #21789 and plugin-security: the packaged-permission-set lock refusal carries no userMessage, so the console replaces it with "You don't have permission to save this record" and the "Clone it instead" guidance never reaches the admin #21794 (area:access,plugin-security) wait behind security(data): a by-id write answers 403 for a row the caller cannot read and 404 for an id that does not exist, for principals the write pre-image check does not bind: an existence signal the read door withholds #21771.
Generated by Claude Code
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21785,
"status": "done",
"branch": "claude/issue-21785-email-template-overlay-survives-boot",
"pr": "#21818",
"session": "session_011K3zqE8Pv1Evw5hc8tZCnN — the parent PM session (this run is a subagent); the harness Claude-Session line names the same id",
"premise_still_valid": true,
"summary": "The boot sweep now projects the EFFECTIVE email template:readDeclaredreadsprotocol.getMetaItems(the layered list GET /meta/email_template serves) intenancy.defaultOrgId()'s organization, so a metadata-door edit keeps the admin's wording in sys_email_template across a restart. A failed effective read projects nothing, and seed-not-clobber is unchanged. Measured on the real showcase: the defect lives only in the org-scoped shape (every Studio save on a deployment with a Default Organization; boot hydration keeps org-scoped overlays out of the registry), while an env-wide overlay already survived by registry insertion order. H2 as written was falsified: the door's effective reader answers the overlay only once an organization is named, so the organization comes from the platform's existing rule for org-less readers of org-overridable metadata (the anonymous form doors, precedent #21331): the Default Organization under single, none (env-wide) under any walled request.",
"tests": [
"Pre-fix (dogfood resolves plugin-email from dist/, built at base 18c2ddc, fix-marker count 0): new pin email-template-overlay-survives-boot.dogfood.test.ts → 1 failed | 2 passed; failure is the card symptom, row subject received "✅ Task done: …" instead of the admin wording after the restart.",
"At 3dcc8cd (code-identical to head 94479dd, which adds only the changeset), under os-verify-lock: turbo build @objectstack/plugin-email exit 0;pnpm --filter @objectstack/plugin-email exec vitest run --maxWorkers=2→ Test Files 31 passed, Tests 514 passed;pnpm --filter @objectstack/plugin-email typecheckexit 0 (tsc --noEmit + check:test-typecheck OK, test layer compiles under tsconfig.test.json); dogfoodvitest run --maxWorkers=2 test/email-template-overlay-survives-boot.dogfood.test.ts test/email-template-materialization.dogfood.test.ts→ Test Files 2 passed, Tests 5 passed.",
"Verbose run of src/bootstrap-declared-email-templates.test.ts: 21 passed, including the four #21785 pins (overlay projected in the default organization; env-wide read when defaultOrgId() is null; failed read projects nothing, never the package layer; seed-not-clobber over the effective read).",
"At 94479dd:pnpm --filter @objectstack/dogfood typecheckexit 0; eslint --no-inline-config --format json over the 5 changed code files → 5 files, 0 errors, 0 warnings.",
"Ablation A (the sweep reads the package layer again; scripts/ablation-replace.mjs --hold under a trap restore, absolute path): anchor 1 → 0, blob b0f455bd → cc6d6a3c; rebuild plugin-email; ablation-dist-preflight found the marker in dist/index.js and dist/index.mjs (exit 0). Unit: 4 failed | 17 passed (all four #21785 pins red). Dogfood: cold-boot pin red, received subject "✅ Task done: …". Restore: blob b0f455bd == HEAD blob, git diff HEAD empty; rebuild; preflight --absent exit 0.",
"Ablation B (a failed effective read falls back to the registry; WRAP mode): failed-read pin red, received { seeded: 1 } expected { seeded: 0 }; 1 failed | 20 passed; restored blob == HEAD, git diff HEAD empty.",
"NOT MEASURED: typecheck of plugin-email importers other than dogfood, reason: the public-surface change is additive only (an optional trailing parameter and one new exported type); no caller of bootstrapDeclaredEmailTemplates exists outside plugin-email (grep), and the one importing test package (dogfood) typechecks green.",
"Lint, narrowed and proven: (1) population read from eslint.config.mjs: the 5 code files match its files globs and were linted;eslint --print-configon the .changeset file answers undefined (outside the population); (2) counts read from --format json: 5 files, 0 errors, 0 warnings; (3) invariance: the config never enables type-aware linting (zero parserOptions.project/projectService), its rule plugins are inline per-file AST rules, and its only file reads at config load are two baseline JSONs this diff does not touch, so the diff cannot move the verdict of any untouched file. Fullpnpm lintis CI-owned."
],
"gates": {
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) at 94479dd vs merge base 18c2ddc: 6 paths, 68 commands; all 49 commands of the dispatch-time lead list are inside these 68.",
"commands": [
"exit 0 · node scripts/check-adr-0087-registration.mjs --base origin/main",
"exit 0 · node scripts/check-adr-0087-registration.mjs --self-test",
"exit 0 · node scripts/check-changeset-no-major.mjs --base origin/main",
"exit 0 · node scripts/check-changeset-no-major.mjs --self-test",
"exit 0 · node scripts/check-ci-filter-parity.mjs",
"exit 0 · node scripts/check-closing-keyword-parity.mjs",
"exit 0 · node scripts/check-closing-keyword-parity.mjs --self-test",
"exit 0 · node scripts/check-comment-mask-adoption.mjs",
"exit 0 · node scripts/check-comment-mask-adoption.mjs --self-test",
"exit 0 · node scripts/check-comment-mask-corpus.mjs",
"exit 0 · node scripts/check-dts-emitted.mjs --self-test",
"exit 0 · node scripts/check-empty-changeset.mjs --base origin/main",
"exit 0 · node scripts/check-empty-changeset.mjs --self-test",
"exit 0 · node scripts/check-issue-citations.mjs",
"exit 0 · node scripts/check-keyed-text-bounds.mjs",
"exit 0 · node scripts/check-keyed-text-bounds.mjs --self-test",
"exit 0 · node scripts/check-platform-object-tenancy-census.mjs",
"exit 0 · node scripts/check-platform-object-tenancy-census.mjs --self-test",
"exit 0 · node scripts/check-plugin-teardown-shape.mjs",
"exit 0 · node scripts/check-plugin-teardown-shape.mjs --self-test",
"exit 0 · node scripts/check-registry-log-declared.mjs",
"exit 0 · node scripts/check-registry-log-declared.mjs --self-test",
"exit 0 · node scripts/check-rest-log-spy-declared.mjs",
"exit 0 · node scripts/check-rest-log-spy-declared.mjs --self-test",
"exit 0 · node scripts/check-system-context-census.mjs",
"exit 0 · node scripts/check-system-context-census.mjs --self-test",
"exit 0 · node scripts/check-tenant-audit-census.mjs",
"exit 0 · node scripts/check-tenant-audit-census.mjs --self-test",
"exit 0 · node scripts/check-undeclared-dep-imports.mjs",
"exit 0 · node scripts/check-undeclared-dep-imports.mjs --self-test",
"exit 0 · node scripts/docs-audit/check-affected-docs.mjs",
"exit 0 · node scripts/docs-audit/check-drift-comment.mjs",
"exit 0 · node scripts/pm/release-rehearsal-clone.mjs --self-test",
"exit 0 · node scripts/release-pending-publish.mjs --self-test",
"exit 0 · pnpm --filter @objectstack/spec run check:duration-unit-keys",
"exit 0 · pnpm --filter @objectstack/spec run check:empty-state",
"exit 0 · pnpm --filter @objectstack/spec run check:liveness",
"exit 0 · pnpm --filter @objectstack/spec run check:strictness-ledger",
"exit 0 · pnpm --filter @objectstack/spec run check:variant-docs",
"exit 0 · pnpm check:changeset-gate-self-tests",
"exit 0 · pnpm check:cross-package-test-inputs",
"exit 0 · pnpm check:doc-authoring",
"exit 0 · pnpm check:driver-memory-census",
"exit 0 · pnpm check:dts-closure",
"exit 0 · pnpm check:dual-build-cjs-loads",
"exit 0 · pnpm check:engine-double-contract",
"exit 0 · pnpm check:gitlink-declared",
"exit 0 · pnpm check:issue-citations",
"exit 0 · pnpm check:lean-entry-closure",
"exit 0 · pnpm check:logger-receiver-detach",
"exit 0 · pnpm check:nul-bytes",
"exit 0 · pnpm check:objectql-double-limit",
"exit 0 · pnpm check:objectui-changeset",
"exit 0 · pnpm check:org-identifier",
"exit 0 · pnpm check:page-declaration-shape",
"exit 0 · pnpm check:pm-changeset-deadline-census",
"exit 0 · pnpm check:published-files",
"exit 0 · pnpm check:query-options-erasure",
"exit 0 · pnpm check:refd-timer-probe",
"exit 0 · pnpm check:slot-lookup",
"exit 0 · pnpm check:sourcemap-no-sources-content",
"exit 0 · pnpm check:swallow-census-controls",
"exit 0 · pnpm check:test-source-alias",
"exit 0 · pnpm check:tier-file-adoption",
"exit 0 · pnpm check:type-check-coverage",
"exit 0 · pnpm check:type-check-debt",
"exit 0 · pnpm check:watch-hint-literal",
"exit 0 · pnpm check:where-matcher"
],
"note": "check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET: 8 packages outside this diff had no dist/); afterturbo run buildof those 8 (41 tasks, 41 cached) it exited 0 (106 require entry points across 66 packages load). The record carries that final measured code.",
"ran_verdict": "dispatch-gates --ranexit 0: Run reconciliation — 68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN."
},
"line_budget": "n/a",
"deviations": [
"H2 falsified and re-routed per the ruling intent: GET /meta serves the overlay only because the door passes the caller organization; getMetaItem/getMetaItems with no organization answer the package wording (measured at kernel:ready on boot 2). The sweep therefore also reads thetenancyservice (defaultOrgId) beside the PM-suggested reader; no other route change.",
"The dogfood pin boots with orgContext: true. It is plugin-auth production default (autoDefaultOrganization) and the only shape that reproduces; the harness default (org-less admin, env-wide overlay) passed before the fix, measured. A precondition case asserts the overlay is org-scoped.",
"Measurement used a throwaway probe test file inside packages/qa/dogfood/test in my worktree. It was never committed, was removed before the ablations, and a copy is kept in the scratchpad.",
"Unit/dogfood/typecheck ran at 3dcc8cd and gates at 94479dd; the only difference is the changeset file.",
"Commit trailers use the AGENTS.md model-free pair (Claude-Session + Co-authored-by: Claude) rather than the harness reminder model-named form, per the dispatch contract.",
"Onepgrep -flookup printed the asking shell pid; it was not used for any wait or kill (waits used the recorded os-verify-lock pids)."
],
"files_changed": [
"packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts (readDeclared effective read + EffectiveEmailTemplateSources + docblocks)",
"packages/plugins/plugin-email/src/email-plugin.ts (resolve protocol + tenancy, pass to the sweep)",
"packages/plugins/plugin-email/src/index.ts (export type EffectiveEmailTemplateSources)",
"packages/plugins/plugin-email/src/bootstrap-declared-email-templates.test.ts (4 unit pins)",
"packages/qa/dogfood/test/email-template-overlay-survives-boot.dogfood.test.ts (new; 3 cold boots on one file)",
".changeset/21785-email-template-overlay-survives-boot.md (new; @objectstack/plugin-email patch; Clause-②: no)",
"totals: 6 files, +423 / -9 (dispatch-gates numstat); content/docs unchanged (no sentence made false)"
],
"mcp_calls": "0",
"api_writes": "3 — each one relay POST /repos/objectstack-ai/objectstack/dispatches (fleet-write, as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (#21818, draft, body read back 8598/8598 bytes identical); (2) label-write assign → POST /repos//issues/21818/assignees (os-steve, read back); (3) post-stamped os-dev-report comment → POST /repos//issues/21785/comments. git push x3 not counted (not REST).",
"open_questions": [
"[Redacted by the seat (domain:services#1, session_011K3zqE8Pv1Evw5hc8tZCnN) at 2026-10-05T05:05Z: an adjacent class outside this card, held by the seat for measurement. Detail is withheld from public surfaces.]"
],
"out_of_scope_findings": [
"[Redacted by the seat (domain:services#1, session_011K3zqE8Pv1Evw5hc8tZCnN) at 2026-10-05T05:05Z: an adjacent class outside this card, held by the seat for measurement. Detail is withheld from public surfaces.]",
"carrier: PR #21818 Acceptance notes · noted, not filed — before this change the env-wide overlay shape survived only because the registry lists the hydrated overlay after the package entry, with a package-then-overlay double write per boot; now one write of the effective item.",
"carrier: PR #21818 Acceptance notes · noted, not filed — packages/objectql/src/registry-i18n-bundle-key.test.ts comment says the materializer reads registry.listItems(email_template); still true in substance (it is getMetaItems base layer), left untouched (outside the file surface)."
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsReview: PR #21818 at
94479dd1· patch round 1 · seatdomain:services#1·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-05T05:06ZVerdict on the dev report
5988422080(status: done), checked against GitHub and the tree.Accepted:
- The boot sweep projects the effective (overlay-applied) template, read in
tenancy.defaultOrgId()'s organization. That reuses the platform's existing rule for an org-less reader of org-overridable metadata (precedent security(forms): access-security.public-form-intake clause 7 (withdrawing a public form from anonymous intake) fails on 17.6.0 — detail withheld pending maintainer #21331), so H2's falsification is handled without a new rule. - A failed effective read projects nothing.
- Seed-not-clobber is unchanged.
- No second "customized" marker, as triage ruled.
- Ablation A (the sweep reads the package layer) turned the four unit pins and the cold-boot dogfood pin red. Ablation B (a failed read falls back) turned its pin red. Each restore was proven.
dispatch-gates --ran: 68 derived, 68 run.
Patch round 1: one defect.
- The PR widens
plugin-email's published surface with zero consumers. The package entry gainstype EffectiveEmailTemplateSources, and the index-exportedbootstrapDeclaredEmailTemplatesgains a parameter. - By the maintainer's ruling (batch [WIP] Add query enhancements and advanced validation features #35), a new exported symbol on an index takes at least
minor. By the startup-focus axis, no consumer pulls it. - Criterion for the round: the package entry exports no new symbol and no exported function gains a parameter. The fix stays internal, the changeset stays
patch, and the declaration staysClause-②: no.
Disclosure: an adjacent class outside this card is held by the seat for measurement. The passages describing it were redacted from the PR body and the report in this act.
Generated by Claude Code
- The boot sweep projects the effective (overlay-applied) template, read in
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21785,
"status": "done",
"branch": "claude/issue-21785-email-template-overlay-survives-boot",
"pr": "#21818",
"head": "bae6303d",
"session": "session_011K3zqE8Pv1Evw5hc8tZCnN — the parent PM session (this run is a subagent); the harness Claude-Session line names the same id",
"premise_still_valid": true,
"summary": "Patch round 1. plugin-email's published surface is back to base. The effective boot sweep is the module-internal bootstrapEffectiveEmailTemplates, which EmailServicePlugin calls with the protocol and tenancy services. The exported bootstrapDeclaredEmailTemplates keeps its four-parameter signature and delegates with no sources, so an external caller reads the registry as before: today's behaviour, without the effective read. There is one sweep body. Measured on dist/ at bae6303: no new symbol in index.d.ts or in either export list, and index.ts is identical to base. The changeset stays patch with Clause-②: no. Main is merged at 18c7dfd. Unit, typecheck, both dogfood files, both ablations (restores proven) and 68/68 gates (--ran 0 NOT-MEASURED) are green. The worktree is removed.",
"tests": [
"Surface proof on the built artifact at bae6303: src/index.ts vs base 18c2ddc → 0 diff lines; dist/index.d.ts mentions of EffectiveEmailTemplateSources → 0, of bootstrapEffectiveEmailTemplates → 0; ESM and CJS export lists → 93 keys each, internal function absent; published declarationbootstrapDeclaredEmailTemplates(engine: IDataEngine, metadataService: any, logger?: Logger, object?: string)equals the base source signature; exports map names only ".", tsup entry only src/index.ts, splitting off.",
"At bae6303 under os-verify-lock: plugin-email build exit 0;pnpm --filter @objectstack/plugin-email exec vitest run --maxWorkers=2→ 31 files, 514 tests passed; verbose src/bootstrap-declared-email-templates.test.ts → 21 passed, including the four #21785 pins re-pointed at bootstrapEffectiveEmailTemplates;pnpm --filter @objectstack/plugin-email typecheckexit 0; dogfoodvitest run --maxWorkers=2 test/email-template-overlay-survives-boot.dogfood.test.ts test/email-template-materialization.dogfood.test.ts→ 2 files, 5 tests passed;pnpm --filter @objectstack/dogfood typecheckexit 0; eslint --no-inline-config --format json over the 5 changed code files → 5 files, 0 errors, 0 warnings.",
"Ablation A at 7293048 (bae6303 differs only by moving a comment in the same file): mutation landed anchor 1 → 0, blob f73fb29e → d80f855d; rebuild; dist preflight found the marker in dist/index.js and dist/index.mjs; unit 4 failed | 17 passed (all four #21785 pins red); dogfood cold-boot pin red, received subject "✅ Task done: …"; restore blob f73fb29e == HEAD blob, git diff HEAD empty; rebuild; preflight --absent exit 0.",
"Ablation B at 7293048 (failed effective read falls back to the registry): failed-read pin red, 1 failed | 20 passed; restored blob f73fb29e == HEAD, git diff HEAD empty.",
"Lint narrowing evidence unchanged from round 0: population read from eslint.config.mjs (5 code files linted; the changeset is outside it), counts from --format json, and invariance (no type-aware linting, per-file AST rules, config-load reads only two untouched baseline JSONs)."
],
"gates": {
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) at bae6303 vs merge base 18c7dfd: 5 paths (index.ts no longer in the change set), 68 commands, identical to round 0.",
"commands": [
"exit 0 · node scripts/check-adr-0087-registration.mjs --base origin/main",
"exit 0 · node scripts/check-adr-0087-registration.mjs --self-test",
"exit 0 · node scripts/check-changeset-no-major.mjs --base origin/main",
"exit 0 · node scripts/check-changeset-no-major.mjs --self-test",
"exit 0 · node scripts/check-ci-filter-parity.mjs",
"exit 0 · node scripts/check-closing-keyword-parity.mjs",
"exit 0 · node scripts/check-closing-keyword-parity.mjs --self-test",
"exit 0 · node scripts/check-comment-mask-adoption.mjs",
"exit 0 · node scripts/check-comment-mask-adoption.mjs --self-test",
"exit 0 · node scripts/check-comment-mask-corpus.mjs",
"exit 0 · node scripts/check-dts-emitted.mjs --self-test",
"exit 0 · node scripts/check-empty-changeset.mjs --base origin/main",
"exit 0 · node scripts/check-empty-changeset.mjs --self-test",
"exit 0 · node scripts/check-issue-citations.mjs",
"exit 0 · node scripts/check-keyed-text-bounds.mjs",
"exit 0 · node scripts/check-keyed-text-bounds.mjs --self-test",
"exit 0 · node scripts/check-platform-object-tenancy-census.mjs",
"exit 0 · node scripts/check-platform-object-tenancy-census.mjs --self-test",
"exit 0 · node scripts/check-plugin-teardown-shape.mjs",
"exit 0 · node scripts/check-plugin-teardown-shape.mjs --self-test",
"exit 0 · node scripts/check-registry-log-declared.mjs",
"exit 0 · node scripts/check-registry-log-declared.mjs --self-test",
"exit 0 · node scripts/check-rest-log-spy-declared.mjs",
"exit 0 · node scripts/check-rest-log-spy-declared.mjs --self-test",
"exit 0 · node scripts/check-system-context-census.mjs",
"exit 0 · node scripts/check-system-context-census.mjs --self-test",
"exit 0 · node scripts/check-tenant-audit-census.mjs",
"exit 0 · node scripts/check-tenant-audit-census.mjs --self-test",
"exit 0 · node scripts/check-undeclared-dep-imports.mjs",
"exit 0 · node scripts/check-undeclared-dep-imports.mjs --self-test",
"exit 0 · node scripts/docs-audit/check-affected-docs.mjs",
"exit 0 · node scripts/docs-audit/check-drift-comment.mjs",
"exit 0 · node scripts/pm/release-rehearsal-clone.mjs --self-test",
"exit 0 · node scripts/release-pending-publish.mjs --self-test",
"exit 0 · pnpm --filter @objectstack/spec run check:duration-unit-keys",
"exit 0 · pnpm --filter @objectstack/spec run check:empty-state",
"exit 0 · pnpm --filter @objectstack/spec run check:liveness",
"exit 0 · pnpm --filter @objectstack/spec run check:strictness-ledger",
"exit 0 · pnpm --filter @objectstack/spec run check:variant-docs",
"exit 0 · pnpm check:changeset-gate-self-tests",
"exit 0 · pnpm check:cross-package-test-inputs",
"exit 0 · pnpm check:doc-authoring",
"exit 0 · pnpm check:driver-memory-census",
"exit 0 · pnpm check:dts-closure",
"exit 0 · pnpm check:dual-build-cjs-loads",
"exit 0 · pnpm check:engine-double-contract",
"exit 0 · pnpm check:gitlink-declared",
"exit 0 · pnpm check:issue-citations",
"exit 0 · pnpm check:lean-entry-closure",
"exit 0 · pnpm check:logger-receiver-detach",
"exit 0 · pnpm check:nul-bytes",
"exit 0 · pnpm check:objectql-double-limit",
"exit 0 · pnpm check:objectui-changeset",
"exit 0 · pnpm check:org-identifier",
"exit 0 · pnpm check:page-declaration-shape",
"exit 0 · pnpm check:pm-changeset-deadline-census",
"exit 0 · pnpm check:published-files",
"exit 0 · pnpm check:query-options-erasure",
"exit 0 · pnpm check:refd-timer-probe",
"exit 0 · pnpm check:slot-lookup",
"exit 0 · pnpm check:sourcemap-no-sources-content",
"exit 0 · pnpm check:swallow-census-controls",
"exit 0 · pnpm check:test-source-alias",
"exit 0 · pnpm check:tier-file-adoption",
"exit 0 · pnpm check:type-check-coverage",
"exit 0 · pnpm check:type-check-debt",
"exit 0 · pnpm check:watch-hint-literal",
"exit 0 · pnpm check:where-matcher"
],
"note": "The 8 out-of-closure packages check:dual-build-cjs-loads reads were built first (41 tasks, 35 cached), so every gate ran to a verdict on the first pass.",
"ran_verdict": "dispatch-gates --ranexit 0: Run reconciliation — 68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN."
},
"line_budget": "n/a",
"deviations": [
"Merged origin/main (18c7dfd) as 3191978; the auto-generated merge message named a private fetch ref, so it was reworded before the push (local, unpushed; no history rewrite of anything pushed).",
"Two source comments that had gestured at the adjacent class held by the seat were reworded to state the read scope neutrally: one docblock sentence in bootstrap-declared-email-templates.ts, one test comment. The commits pushed in round 0 keep the old wording; no history was rewritten.",
"The published docblock linked the internal function, and that link was carried into dist/index.d.ts. Found by the surface proof at 7293048 and moved into the function body at bae6303.",
"Ablations ran at 7293048; the final head bae6303 only moves a comment in that file. Gates, unit, typecheck, dogfood and the surface proof ran at bae6303.",
"The lock queue timed out twice (exit 99, holder #21771); both re-queues used the kept slot."
],
"files_changed": [
"packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts (published bootstrapDeclaredEmailTemplates restored to its 4-parameter signature, delegating; module-internal bootstrapEffectiveEmailTemplates carries the sources; EffectiveEmailTemplateSources marked module-internal)",
"packages/plugins/plugin-email/src/email-plugin.ts (boot wiring calls bootstrapEffectiveEmailTemplates)",
"packages/plugins/plugin-email/src/index.ts (back to byte-identical with base)",
"packages/plugins/plugin-email/src/bootstrap-declared-email-templates.test.ts (four pins re-pointed at the internal function)",
"packages/qa/dogfood/test/email-template-overlay-survives-boot.dogfood.test.ts (unchanged this round)",
".changeset/21785-email-template-overlay-survives-boot.md (patch, Clause-②: no; published-API-unchanged bullet replaces the fifth-argument bullet)",
"totals vs merge base 18c7dfd: 5 files, +456 / -10"
],
"mcp_calls": "0",
"api_writes": "1 — one relay POST /repos/objectstack-ai/objectstack/dispatches (fleet-write, as objectstack-fleet[bot]) carrying post-stamped os-dev-report comment → POST /repos//issues/21785/comments. git push x3 this round (3191978..7293048, bae6303), not REST. No PR body PATCH.",
"open_questions": [],
"out_of_scope_findings": [
"An adjacent class is held by the seat (detail withheld from public surfaces)."
],
"private_measurement": "Delivered to the seat privately; not on any public surface."
}
Generated by Claude Code
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsLanded · seat
domain:services#1·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-05T07:06Z- PR fix(plugin-email): a metadata-door email template edit survives the next boot #21818 merged through the merge queue as
08adfeade8, which is an ancestor oforigin/main(verified withgit merge-base --is-ancestorafter a fetch). Merged at 2026-10-05T06:51:52Z. Fixes #21785closed this cardcompleted. In this act the seat clears thepm:dispatchedstate label and the assigneeos-steve. Thedomain:services,area:api,priority:p2andbuglabels stay.- What shipped: an email template edited through the metadata door keeps the admin's wording across a restart. The boot sweep projects the effective (overlay-applied) template, read in the organization
tenancy.defaultOrgId()names, the platform's existing rule for an org-less reader of org-overridable metadata. A failed effective read projects nothing, and seed-not-clobber is unchanged.plugin-email's published surface is unchanged (patch round 1). - Carried: an adjacent class outside this card is held by this seat for measurement and goes to the maintainer; its detail is withheld from public surfaces. The card's other notes are in PR fix(plugin-email): a metadata-door email template edit survives the next boot #21818's Acceptance notes.
Generated by Claude Code
- PR fix(plugin-email): a metadata-door email template edit survives the next boot #21818 merged through the merge queue as
- added 3 commits that reference this issue
on Oct 7, 2026
QA-source: #21784 · integration-system.email-template-render · acceptance[5]
Clause A6 (and negative N1) of
integration-system.email-template-render(rev 5) fail in the 17.7 pre-release run #21784 (subject316be321e, console pin2e818d0b51ec). An independent verifier (RUNNER rule 7) reproduced it on two fresh databases: CONFIRMED, P2. Predates 17.6.0 (the bootstrap file is identical at617f25f8a).Reproduction
GET /api/v1/data/sys_email_template?filter=name eq 'showcase_task_done_email'→ subject✅ Task done: {{title}},managed_by: package,customized: false.PUT /api/v1/meta/email_template/showcase_task_done_emailwith a rewordedsubject(this is the door the Studio email-template editor uses) → 200; the row now carries the new subject, stillcustomized: false.✅ Task done: {{title}}, whileGET /api/v1/meta/email_template/showcase_task_done_emailstill serves the admin's overlay — the metadata the admin sees and the row the mailer sends diverge silently.PATCH /api/v1/data/sys_email_template/{id}with a new subject stampscustomized: trueand survives the restart. Only the metadata door loses the edit.Mechanism
packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts:153-170—readDeclaredreads the registry's package items, not the effective overlay-layered item.:209-227— rows projected from the metadata door are writtencustomized: false(the runtime projector writes under the system context), so thecustomizedguard at:211does not protect them and the boot sweep re-projects the package wording over them.Done when
An overlay authored through
/meta/email_templateis what the boot sweep projects (or the projected row is marked so the sweep leaves it alone), and a dogfood test edits through the metadata door, restarts, and reads the admin's wording back from the sending row.Generated by Claude Code